We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

This lesson's interactive features are locked, please to keep using them

Build Information

Build information is frequently overlooked in production logging, but do so at your own peril. It includes:

  • The version of your code that produced the log
  • The time at which the code was built
  • The VCS revision (such as a Git commit SHA) of the code that produced the log

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!

Avoid Code Version Confusion

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?"

Log Aggregation

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.

Deployment Tracking

If you use canary deployments, rolling deployments, or any strategy that runs multiple app versions at once, build information is a lifesaver.

How to Add Build Info

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:

  • It requires VCS metadata (like .git) at build time, which is often missing in container builds.
  • It only exposes limited fields (revision + build time).

So I recommend an alternate method...

Using ldflags for Build Info

go 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')"

Assignment

Attach build metadata to every log entry.

  1. logger = logger.With(
    	slog.String("git_sha", build.GitSHA),
    	slog.String("build_time", build.BuildTime),
    )
    
  2. 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!

  3. 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.