A step-by-step playbook for adopting generated logcopter package loggers in go-go-golems repositories.
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, dependency-bump, PR submission, and Codex-review readiness 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.
A converted repository has five visible properties:
go.mod directly requires github.com/go-go-golems/logcopter.go.mod registers github.com/go-go-golems/logcopter/cmd/logcopter-gen as a Go tool.go:generate file makes go generate ./... regenerate package logger files.log, backed by logcopter, instead of importing the global zerolog log package.bump-go-go-golems target that scans go.mod and bumps every github.com/go-go-golems/... dependency instead of hand-maintaining a stale bump-glazed list.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.
When adding logcopter across many go-go-golems repositories, move from foundational dependencies toward leaf applications. A downstream repository should not be bumped until the upstream PR it needs has been merged and published, otherwise local go.work can hide missing published symbols.
Use this loop for each repository:
make bump-go-go-golems to update every direct go-go-golems dependency listed in go.mod.GOWORK=off tests or smoke tests that prove the published dependency graph works without local workspace checkouts.For the logcopter rollout, the order is generally:
logcopter -> glazed/clay -> geppetto -> pinocchio -> leaf applications
The exact order depends on the current go.mod graph. Always inspect go.mod rather than relying on memory.
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.
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.
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.
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.
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.
Do not blindly convert every log identifier. Some files intentionally use concrete loggers or the global zerolog package.
Use these rules:
| Situation | Action |
|---|---|
Package imports github.com/rs/zerolog/log only for diagnostics | Remove the import and use the generated log variable. |
Package assigns log.Logger = ... or reads the global logger object | Alias the import as zlog and update those references. |
Package imports standard library log | Alias it as stdlog or migrate the call to the generated logger. |
Public API accepts zerolog.Logger | Keep the API. Do not force logcopter into injected-logger paths. |
| Middleware receives a request-scoped logger | Keep the request-scoped logger and only convert unrelated package diagnostics. |
Tests are in the same package and import log | Alias the test import because generated variables are package-level names. |
Tests use an external package such as store_test | No 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.
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.
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.
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.
Do not maintain repository-specific bump-glazed target bodies such as:
bump-glazed:
go get github.com/go-go-golems/glazed@latest
go get github.com/go-go-golems/clay@latest
go get github.com/go-go-golems/logcopter@latest
go mod tidy
Those lists go stale as repositories gain or lose go-go-golems dependencies. Instead, add one generic target that scans go.mod for direct github.com/go-go-golems/... requirements and bumps each of them:
bump-go-go-golems:
@deps="$(awk '/^require[[:space:]]+github\.com\/go-go-golems\// { print $2 } /^[[:space:]]*github\.com\/go-go-golems\// { print $1 }' go.mod | sort -u)"; \
if [ -z "$deps" ]; then \
echo "No github.com/go-go-golems dependencies in go.mod"; \
else \
echo "Bumping go-go-golems dependencies:"; \
echo "$deps"; \
for dep in $deps; do go get "${dep}@latest"; done; \
fi
go mod tidy
For repositories that intentionally run all Makefile Go commands outside a local workspace, keep that convention inside the loop:
bump-go-go-golems:
@deps="$(awk '/^require[[:space:]]+github\.com\/go-go-golems\// { print $2 } /^[[:space:]]*github\.com\/go-go-golems\// { print $1 }' go.mod | sort -u)"; \
if [ -z "$deps" ]; then \
echo "No github.com/go-go-golems dependencies in go.mod"; \
else \
echo "Bumping go-go-golems dependencies:"; \
echo "$deps"; \
for dep in $deps; do GOWORK=off go get "${dep}@latest"; done; \
fi
GOWORK=off go mod tidy
Run the target after each upstream merge/release before validating downstream logcopter support:
make bump-go-go-golems
go test ./...
make logcopter-check
If the bump changes too many unrelated dependencies, split the update into a separate PR before doing the logcopter conversion. This keeps review smaller and makes dependency-order failures easier to diagnose.
After pushing a repository's logcopter branch, create or update the PR and trigger Codex review:
gh pr create --fill --base main --head task/logcopter
# or, for an existing PR:
gh pr comment <pr-number-or-url> --body '@codex review'
Use the PR readiness scripts stored with the PR-REVIEW-READY-001 ticket to avoid manually inspecting every repository:
READY_SCRIPTS=/home/manuel/workspaces/2026-05-25/logcopter/logcopter/ttmp/2026/05/26/PR-REVIEW-READY-001--automate-pr-readiness-checks-for-codex-reviews/scripts
$READY_SCRIPTS/00-pr-ready-check.sh https://github.com/go-go-golems/<repo>/pull/<number>
The script returns success only when:
If it reports an eyes reaction, Codex is still reviewing. If it reports a substantive Codex body, fix the review comments, push, and trigger another @codex review.
For a large dependency-order rollout, keep a simple queue:
repo upstream requirement PR state
logcopter none #... merged/released
glazed logcopter release #... wait for Codex
clay glazed/logcopter release #... wait for Actions
geppetto glazed/clay/logcopter release #... blocked on clay
pinocchio geppetto/glazed/clay release #... blocked on geppetto
Only move to the next row when the required upstream release is available and the current PR is ready by script.
Use focused commits:
make bump-go-go-golems changes existing go-go-golems module versions.This makes generated-file diffs easier to review and lets downstream repositories adopt the same recipe.
| Problem | Cause | Solution |
|---|---|---|
log already declared through import of package log | A 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 tool | The 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 generation | The check command uses different package patterns or prefixes than go:generate. | Keep -area-prefix, -strip-prefix, and package patterns identical. |
| Area overrides do nothing | The 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 hidden | The 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 area | Those 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 pass | The repo relies on a local checkout that has symbols not present in the required published version. | Publish the dependency version first, run make bump-go-go-golems, then rerun GOWORK=off smoke tests. |
make bump-go-go-golems updates an unexpected module | The module is already a direct github.com/go-go-golems/... requirement in go.mod, so the target treats it as part of the repo family. | Inspect the diff. If the update is unrelated to logcopter, split it into a dependency-only PR. |
| Codex review has an eyes reaction | Codex is still reviewing the PR. | Wait and rerun 00-pr-ready-check.sh; do not merge while eyes is present. |
| Codex review has a substantive body | Codex left review comments or suggestions. | Fix the comments, push, and trigger another @codex review. |
| Same-package tests fail after generation | Test files import standard library log in the same package. | Alias the import as stdlog. |
Before merging a conversion, verify:
go.mod has a direct logcopter requirement.go.mod has a tool entry for logcopter-gen.logcopter_generate.go.go generate ./... succeeds.go tool logcopter-gen ... -check ... succeeds.github.com/rs/zerolog/log without a deliberate reason.zerolog.Logger APIs remain intact.--log-area and --log-config.GOWORK=off when cross-repository imports are involved.area=... in text output from a generated package logger.bump-go-go-golems, not a hand-maintained bump-glazed list.make bump-go-go-golems was run after any upstream go-go-golems release needed by this repository.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.