

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Difficulty: 5
click for more info
Not enough gems
Cost: 6 gems
1: Logging Context
incomplete
2: Build Information
incomplete
3: Instance Context
incomplete
4: Request Context
incomplete
5: User Context
incomplete
6: HTTP Error Responses
incomplete
7: Inter-Process Context
incomplete
Back
ctrl+,
Next
ctrl+.
This lesson's interactive features are locked, please to keep using them
Build information is frequently overlooked in production logging, but do so at your own peril. It includes:
Build information can feel redundant. Why log the same data on every line if it only changes every few hours?
Your team does build and deploy a few times each day... right? If not, you should!
I include build info so there's never ambiguity about which version produced an error, even days or weeks later.
2024-06-10T12:34:56Z level=ERROR msg="Failed to connect to database" version="1.2.3"
Nobody reading this log will be left wondering "Was that before or after we added feature X?" or "Does that code include Bob's fix for caching?"
Build info also makes trend analysis easier. Many logging services do this automatically, and some integrate with your VCS (such as GitHub) to link errors to specific commits.
If you use canary deployments, rolling deployments, or any strategy that runs multiple app versions at once, build information is a lifesaver.
So we need a way to get build information into our logs at runtime... and we don't want to hardcode it. Someone will forget to update it, and wrong build information is worse than none at all!
The go build command supports -buildvcs, which stamps VCS metadata into the binary so you can read it at runtime with runtime/debug.ReadBuildInfo. It's convenient, but has tradeoffs:
.git) at build time, which is often missing in container builds.So I recommend an alternate method...
ldflags for Build Infogo build also supports -ldflags, which lets the linker set package variables at build time.
To use this, create placeholder string variables in your code. You can put them anywhere, but I prefer a dedicated build package that only contains these variables, defaulting to "unknown":
package build
// default build-time variables
var (
GitSHA = "unknown"
BuildTime = "unknown"
)
Then, we can set them in the logger:
logger = logger.With(
slog.String("git_sha", build.GitSHA),
slog.String("build_time", build.BuildTime),
)
At build time, inject the real values with -ldflags:
go build -ldflags "-X my/package/build.GitSHA=$(git rev-parse HEAD) -X my/package/build.BuildTime=$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
Attach build metadata to every log entry.
logger = logger.With(
slog.String("git_sha", build.GitSHA),
slog.String("build_time", build.BuildTime),
)
go build \
-ldflags "-X boot.dev/linko/internal/build.GitSHA=$(git rev-parse HEAD) -X boot.dev/linko/internal/build.BuildTime=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
-o linko
The boot.dev/linko/internal/build part of the -X flag is the full import path to the variable you want to set. Yours may be a bit different from mine!
LINKO_LOG_FILE=linko.access.log ./linko
Run and submit the CLI tests from the root of the Linko repo.
go run does not support -ldflags variable injection – the variables will remain "unknown" unless you use go build first.