Logging from a Go Agent
Overview
Golem routes Go logging through the host’s structured logging channel (wasi:logging), so each record carries a typed level and a context (category) string and shows up in worker logs and the oplog with the right severity. Unlike writing to stdout/stderr — which the host records as raw bytes with no level — this gives you filterable, leveled logs.
The runtime installs the SDK’s handler as the default log/slog handler on agent startup, so ordinary standard-library structured logging just works with no setup code. You only reach for the SDK’s logging package (github.com/golemcloud/golem/sdks/go/golem/logging) when you want to customize the level or category, or build a handler yourself.
Steps
- Log with the standard library — call
slog.Info,slog.Warn, etc. Nothing to import beyondlog/slog. - (Optional) Customize the minimum level or base category with
logging.SetDefault(...)from the SDK’sloggingpackage. - View logs with
golem agent streamor duringgolem agent invoke.
Quick Start (no setup)
The runtime already installed the host-logging handler as slog’s default, so just use log/slog:
import "log/slog"
slog.Info("order created", "orderID", orderID, "total", total) // -> level=info
slog.Warn("retrying", "attempt", attempt, "reqID", reqID) // -> level=warn
slog.Error("db connect failed", "err", err) // -> level=error
slog.Debug("processing item", "id", item.ID) // -> level=debugStructured context is passed as trailing key/value pairs (slog attributes) and is rendered into the log record. slog.With(...) returns a logger that prepends attributes to every record:
logger := slog.With("area", "billing")
logger.Error("declined", "code", code)Because slog.SetDefault also bridges Go’s standard log package, plain log.Print / log.Printf output flows through the same channel too (as info-level records).
Log Levels
Records map onto the wasi:logging severities. slog’s built-in levels cover debug/info/warn/error; the wasi:logging levels trace and critical (logging.Trace, logging.Critical) are reached with slog levels below Debug and above Error:
| slog call | Level | Use for |
|---|---|---|
slog.Log(ctx, level-4, ...) (below Debug) | trace | Fine-grained control flow |
slog.Debug | debug | Debugging information |
slog.Info | info | Normal operations, monitoring |
slog.Warn | warn | Degraded behavior, hazards |
slog.Error | error | Serious errors |
slog.Log a full step above Error | critical | Fatal conditions |
The SDK exposes the raw severities as logging.Trace, logging.Debug, logging.Info, logging.Warn, logging.Error, logging.Critical (type logging.Level) for the low-level logging.Log helper.
Customizing the default logger
Import the SDK’s logging package and call logging.SetDefault to change the minimum level or the base category. Options has just Level (an slog.Leveler) and Context (the base category string).
import (
"log/slog"
"github.com/golemcloud/golem/sdks/go/golem/logging"
)
func init() {
// Emit debug-and-up, tagging every record with the "billing" category.
logging.SetDefault(&logging.Options{
Level: slog.LevelDebug,
Context: "billing",
})
}Building a handler yourself
logging.NewHandler(opts) returns a *logging.Handler, an slog.Handler you can pass to slog.New (for a scoped logger, or to compose):
handler := logging.NewHandler(&logging.Options{Context: "worker"})
logger := slog.New(handler)
logger.Info("started")slog groups map onto the category: logger.WithGroup("retry") extends the context to read like worker/retry rather than key-prefixing attributes.
Lower-level helpers
logging.Log(level, context, message)— emit one raw record directly.logging.Writer(level, context)— anio.Writerwhere each write becomes one record at that level/category, handy for wiring a sink that expects anio.Writer.
logging.Log(logging.Warn, "startup", "cache warm skipped")Viewing Logs
Stream live agent output (stdout, stderr, and the log channel):
golem agent stream '<agent-id>'Logs also stream automatically during an invocation:
golem agent invoke '<agent-id>' '<method>' [args]
# use --no-stream to suppressKey Constraints
- No initialization needed — the runtime installs the host-logging handler as slog’s default on startup; just use
log/slog. - Prefer
slog.*overfmt.Println/stdout: stdout/stderr are captured as raw bytes with no level or category. - Logging is a side effect: during replay (crash recovery) log calls from replayed operations are skipped — only new invocations produce log output.
int/uint-width and other value formatting follow slog’s attribute rendering (values are resolved and stringified into the message).
Related Guides
| Guide | Description |
|---|---|
golem-enable-otlp-go | Forward logs (and traces/metrics) to an OTLP collector |
golem-add-agent-go | Define the agent whose handlers emit these logs |