Convert a go-go-golems package to logcopter

A step-by-step playbook for adopting generated logcopter package loggers in go-go-golems repositories.

Sections

Terminology & Glossary
📖 Documentation
Navigation
77 sectionsv0.1
📄 Convert a go-go-golems package to logcopter — glaze help logcopter-package-rollout-playbook
logcopter-package-rollout-playbook

Convert a go-go-golems package to logcopter

A step-by-step playbook for adopting generated logcopter package loggers in go-go-golems repositories.

Tutoriallogcopterlogginggo-go-golemsmigrationgo generatego toollog-arealog-configstrict-log-areas

This tutorial is the operational playbook for moving an existing go-go-golems Go repository from package-level github.com/rs/zerolog/log diagnostics to generated logcopter package loggers. It describes the exact dependency, generation, code conversion, validation, and smoke-test steps that should be used for Glazed-family repositories such as Glazed, Geppetto, Pinocchio, and Clay.

The goal is not to replace every explicit zerolog.Logger value. Keep injected loggers, request-scoped loggers, middleware loggers, and APIs that intentionally accept a concrete logger. Convert package diagnostics: calls that currently use the global zerolog log package only because the package needs a convenient local logger.

The target shape

A converted repository has four visible properties:

  1. go.mod directly requires github.com/go-go-golems/logcopter.
  2. go.mod registers github.com/go-go-golems/logcopter/cmd/logcopter-gen as a Go tool.
  3. A repository-local go:generate file makes go generate ./... regenerate package logger files.
  4. Packages use a generated package variable named log, backed by logcopter, instead of importing the global zerolog log package.

A generated package logger looks like this:

// Code generated by logcopter-gen; DO NOT EDIT.

package store

import logcopter "github.com/go-go-golems/logcopter/pkg/logcopter"

var log = logcopter.Package("go-go-golems.example.pkg.help.store")

The area name is stable, path-derived, and configurable at runtime through Glazed logging flags or a logcopter profile file.

Step 1: choose the area prefix

Use the repository prefix convention:

go-go-golems.<repo>

Examples:

go-go-golems.glazed
go-go-golems.geppetto
go-go-golems.pinocchio
go-go-golems.clay

Keep path components such as pkg and cmd unless the repository has an explicit mapping plan. Keeping the raw path-derived form makes generation reproducible and avoids hand-maintained area aliases.

Step 2: add the dependency and tool

From the repository root, add logcopter and register the generator as a Go tool:

go get github.com/go-go-golems/logcopter@latest
go get -tool github.com/go-go-golems/logcopter/cmd/logcopter-gen@latest
go mod tidy
go tool logcopter-gen -h

If the repository has a local replace github.com/go-go-golems/logcopter => ..., remove it before committing. Released repositories should depend on the published module version unless you are intentionally testing a local logcopter change.

Step 3: add the go generate entry point

Create a small file at the repository root. Name it logcopter_generate.go.

Use the repository's root package name, not package main, unless the repository root is already a real command package. A root package main file in a library module causes go build ./... to fail with function main is undeclared in the main package.

package <repo>

//go:generate go tool logcopter-gen -area-prefix go-go-golems.<repo> -strip-prefix github.com/go-go-golems/<repo> ./pkg/...

Replace <repo> with the module's repository name. For example, Geppetto uses:

package geppetto

//go:generate go tool logcopter-gen -area-prefix go-go-golems.geppetto -strip-prefix github.com/go-go-golems/geppetto ./pkg/...

Pinocchio may generate both library packages and command packages:

package pinocchio

//go:generate go tool logcopter-gen -area-prefix go-go-golems.pinocchio -strip-prefix github.com/go-go-golems/pinocchio ./pkg/... ./cmd/...

Use ./pkg/... for library-only repositories. Add ./cmd/... when command packages also contain diagnostics that should be package-scoped.

Step 4: generate package loggers

Run:

go generate ./...

This writes logcopter.go files into each selected package. If generation fails with a log already declared error, the package already imports something named log.

Typical error:

logcopter.go:7:5: log already declared through import of package log ("github.com/rs/zerolog/log")

That is expected during the migration. Resolve it by converting imports in the affected package.

Step 5: convert package diagnostics

For ordinary package diagnostics, remove this import:

import "github.com/rs/zerolog/log"

Leave call sites such as these unchanged:

log.Debug().Str("path", path).Msg("loading help file")
log.Warn().Err(err).Msg("could not load optional source")
log.Error().Err(err).Msg("request failed")

The generated package variable named log preserves the fluent zerolog-style call shape while routing through logcopter's reload-aware manager.

Step 6: handle real logger dependencies carefully

Do not blindly convert every log identifier. Some files intentionally use concrete loggers or the global zerolog package.

Use these rules:

SituationAction
Package imports github.com/rs/zerolog/log only for diagnosticsRemove the import and use the generated log variable.
Package assigns log.Logger = ... or reads the global logger objectAlias the import as zlog and update those references.
Package imports standard library logAlias it as stdlog or migrate the call to the generated logger.
Public API accepts zerolog.LoggerKeep the API. Do not force logcopter into injected-logger paths.
Middleware receives a request-scoped loggerKeep the request-scoped logger and only convert unrelated package diagnostics.
Tests are in the same package and import logAlias the test import because generated variables are package-level names.
Tests use an external package such as store_testNo conflict with the package's generated variable.

The key distinction is ownership. Package diagnostics belong to logcopter; explicit logger injection still belongs to the caller.

Step 7: add fast generation checks

The repository root logcopter_generate.go should make go generate ./... run the generator through the Go tool mechanism:

package <repo>

//go:generate go tool logcopter-gen -area-prefix go-go-golems.<repo> -strip-prefix github.com/go-go-golems/<repo> ./pkg/...

That means contributors only need this mutating command when they intentionally refresh generated files:

logcopter-generate:
	go generate ./...

For verification, add a separate check target that does not rewrite files:

logcopter-check:
	go tool logcopter-gen -area-prefix go-go-golems.<repo> -strip-prefix github.com/go-go-golems/<repo> -check ./pkg/...

For repositories that generate command packages too, keep the same package patterns in the go:generate directive and the check target:

//go:generate go tool logcopter-gen -area-prefix go-go-golems.pinocchio -strip-prefix github.com/go-go-golems/pinocchio ./pkg/... ./cmd/...
logcopter-check:
	go tool logcopter-gen -area-prefix go-go-golems.pinocchio -strip-prefix github.com/go-go-golems/pinocchio -check ./pkg/... ./cmd/...

In CI, run logcopter-check before any mutating go generate ./... step. Otherwise the workflow can hide stale generated files: go generate rewrites pkg/**/logcopter.go first, and the later -check only validates the regenerated workspace instead of the checked-in PR contents.

Use this ordering:

-
  name: verify logcopter package loggers
  run: make logcopter-check
-
  name: generate assets
  run: go generate ./...
-
  name: run unit tests
  run: go test ./...

Do not use this ordering for drift checks:

-
  name: generate assets
  run: go generate ./...
-
  name: verify logcopter package loggers
  run: make logcopter-check

The second form is only useful for validating that the generator can run; it does not prove the committed generated files were fresh.

Step 8: add standalone smoke coverage

For repositories that import logcopter docs or other newly published cross-repository symbols, add a fast smoke target that disables the local workspace:

logcopter-smoke:
	GOWORK=off go test ./cmd/<app> ./pkg/cmds/logging ./pkg/cmds/fields
	GOWORK=off go run ./cmd/<app> help logcopter-logging-architecture >/tmp/<repo>-logcopter-help-smoke.txt

Use GOWORK=off deliberately. It catches failures where local go.work makes a symbol available but the published module version in go.mod does not. Glazed uses this to verify that the imported embedded github.com/go-go-golems/logcopter/pkg/doc.FS is available from the required logcopter version.

Run this in CI after the generated-file check and before the broader test suite.

Step 9: validate compilation and behavior

Start with narrow tests around changed packages:

go test ./pkg/...

Then run repository-specific smoke tests. For CLIs that use Glazed logging, verify command-line area overrides:

go run ./cmd/<app> --log-area go-go-golems.<repo>.pkg.some.package=debug --log-level warn <safe-command>

Also verify explicit logcopter config files. A minimal profile can be written as:

logging:
  level: warn
  format: json
  areas:
    go-go-golems.<repo>.pkg.some.package: debug

Then run:

go run ./cmd/<app> --log-config /tmp/logcopter-profile.yaml <safe-command>

Use format: json when checking fields mechanically. Generated logcopter package logs include an area field, for example:

{"level":"debug","area":"go-go-golems.glazed.pkg.help","message":"Failed to load section from file"}

Global zerolog logs do not have an area field. If a smoke command emits DBG Logger initialized or another global logger message without area, that is expected; use a generated package area that actually emits during the command if you need to see area=... in text output.

The global --log-level or profile logging.level sets the default logcopter level. Area overrides can still lower one package area to debug or trace while the default remains warn or info:

logging:
  level: warn
  areas:
    go-go-golems.glazed.pkg.help: debug

This should emit debug logs from go-go-golems.glazed.pkg.help without turning every area to debug.

Use --strict-log-areas to validate configured area names, but remember that strict validation only sees packages linked into the running binary.

The exact smoke command should be safe, deterministic, and should not require network credentials. Help commands are usually good smoke tests.

Step 10: commit in layers

Use focused commits:

  1. Documentation/playbook commit if you are adding or updating this tutorial.
  2. Repository conversion commit with dependency, generation, imports, generated files, and validation target.
  3. Ticket diary commit that records what changed, what failed, and how to review it.

This makes generated-file diffs easier to review and lets downstream repositories adopt the same recipe.

Troubleshooting

ProblemCauseSolution
log already declared through import of package logA file in the same package imports a package whose local name is log.Remove github.com/rs/zerolog/log diagnostic imports, or alias real imports as zlog/stdlog.
go tool logcopter-gen: no such toolThe generator was not registered with go get -tool.Run go get -tool github.com/go-go-golems/logcopter/cmd/logcopter-gen@latest or pin the current approved logcopter version.
-check fails after a clean generationThe check command uses different package patterns or prefixes than go:generate.Keep -area-prefix, -strip-prefix, and package patterns identical.
Area overrides do nothingThe CLI did not initialize Glazed logging, the area name is different from the generated path, or the package is not linked into the running binary.Inspect generated logcopter.go files, verify the root command calls Glazed logging initialization, and test with an area known to emit during the command.
Trace/debug logs remain hiddenThe default level is higher than the package area level, or output format hides the fields you expect.Set the exact package area with --log-area area=debug or in a --log-config file; use format: json to inspect fields.
--log-level debug shows logs without areaThose records are emitted through the global zerolog logger, not a generated logcopter package logger.This is expected. Only generated package loggers include area. Convert package diagnostics if they should be area-scoped.
GOWORK=off fails but workspace tests passThe repo relies on a local checkout that has symbols not present in the required published version.Publish the dependency version first, update go.mod, then rerun GOWORK=off smoke tests.
Same-package tests fail after generationTest files import standard library log in the same package.Alias the import as stdlog.

Review checklist

Before merging a conversion, verify:

  • go.mod has a direct logcopter requirement.
  • go.mod has a tool entry for logcopter-gen.
  • The repository has logcopter_generate.go.
  • go generate ./... succeeds.
  • go tool logcopter-gen ... -check ... succeeds.
  • No package diagnostics still import github.com/rs/zerolog/log without a deliberate reason.
  • Concrete zerolog.Logger APIs remain intact.
  • CLI smoke tests cover both --log-area and --log-config.
  • At least one smoke test uses GOWORK=off when cross-repository imports are involved.
  • Area field visibility is verified with JSON output or by checking area=... in text output from a generated package logger.
  • The ticket diary records failures and exact validation commands.

See Also

  • logging-section-reference for Glazed logging flags and configuration shape.
  • logcopter-logging-architecture for the runtime model and area hierarchy.
  • logcopter-package-logging for basic package logger usage.