---
title: Convert a go-go-golems package to logcopter
description: A step-by-step playbook for adopting generated logcopter package loggers in go-go-golems repositories.
doc_version: 1
last_updated: 2026-07-02
---


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:

```go
// 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:

```text
go-go-golems.<repo>
```

Examples:

```text
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:

```bash
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`.

```go
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:

```go
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:

```go
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:

```bash
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:

```text
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:

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

Leave call sites such as these unchanged:

```go
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:

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

## Step 7: add fast generation checks

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

```go
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:

```make
logcopter-generate:
	go generate ./...
```

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

```make
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
//go:generate go tool logcopter-gen -area-prefix go-go-golems.pinocchio -strip-prefix github.com/go-go-golems/pinocchio ./pkg/... ./cmd/...
```

```make
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:

```yaml
-
  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:

```yaml
-
  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:

```make
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:

```bash
go test ./pkg/...
```

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

```bash
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:

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

Then run:

```bash
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:

```json
{"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`:

```yaml
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

| 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, update `go.mod`, then rerun `GOWORK=off` smoke tests. |
| Same-package tests fail after generation | Test 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.
