Step 01 of 06
Learn the concept
A monitoring company is a few hundred engineers and a billion-dollar valuation built on one four-line function: ask a server a question, write down the answer. Everything else — dashboards, alerts, certificate warnings, status pages — is scaffolding around that single act. You are about to write the four lines.
The ideas this is made of
A check is a question, not a state
"Is the site up?" has no answer. The answerable version is: did this URL respond successfully when I asked it, just now, from here? A site can be up from your laptop and down from Frankfurt. Up at 10:00, down at 10:01. Beacon never claims a site is up — it records that a check succeeded, at a time, from a place.
The status code is the server's own verdict
Every HTTP response opens with three digits the server chose about itself. 2xx: I handled it. 3xx: it lives elsewhere. 4xx: your request was wrong. 5xx: I broke. Bytes coming back is not success — reading the code is the minimum honest check.
Go splits the two failures for you, if you let it
resp, err := http.Get(url) returns both. A non-nil err means no HTTP transaction ever happened and resp is nil — touching it panics. A nil err means the transaction completed perfectly, even if it carried a 503. Conflating them is the classic beginner bug, and it hides which layer actually broke.
The body is an open connection, not a string
Go hands you the response before the body has been read, and that body is a live socket. Never close it and the connection is never released. Do that in a loop and the process climbs towards its file-descriptor limit until the OS refuses to open any more. defer resp.Body.Close() goes immediately after the error check — build the reflex now, while the program is four lines long.
package main
import (
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("https://example.com")
if err != nil {
// Branch one: no transaction happened. resp is nil — do not touch it.
fmt.Println("unreachable:", err)
return
}
defer resp.Body.Close()
// Branches two and three: the transaction worked. Now read the verdict.
fmt.Println("status:", resp.StatusCode)
}Nine lines holding all three branches, the closed body, and a status code read rather than assumed. Type it out — don't paste it — and run it. Then break it deliberately: point it at https://not-a-real-host.invalid and watch the other branch run. A failure path you have never executed is a failure path you do not have.
The two failures, side by side
| No answer arrived | A bad answer arrived | |
|---|---|---|
What happened | The request never completed | The request completed fine |
In Go |
|
|
Typical cause | DNS, routing, refusal, timeout | Bad deploy, dependency down, bad path |
Who you'd wake up | Network or platform | Whoever owns the service |
Beacon records it as | A transport error, named by layer | A reachable endpoint, unhealthy |
What these are called on the job
Probe — One execution of a check against one target. Beacon's unit of work — and the same word Kubernetes uses for its liveness and readiness checks, which you will meet again in course four.
Target — The thing being watched: a URL, or a host and port. One constant today, a configured list by course two.
Transport error — A failure that happened before any HTTP response existed. In Go, the non-nil error from the client.
Status code — The server's own three-digit verdict on the request it just handled.
