Skip to Content
How-To GuidesGoLogging from a Go Agent

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

  1. Log with the standard library — call slog.Info, slog.Warn, etc. Nothing to import beyond log/slog.
  2. (Optional) Customize the minimum level or base category with logging.SetDefault(...) from the SDK’s logging package.
  3. View logs with golem agent stream or during golem 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=debug

Structured 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 callLevelUse for
slog.Log(ctx, level-4, ...) (below Debug)traceFine-grained control flow
slog.DebugdebugDebugging information
slog.InfoinfoNormal operations, monitoring
slog.WarnwarnDegraded behavior, hazards
slog.ErrorerrorSerious errors
slog.Log a full step above ErrorcriticalFatal 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) — an io.Writer where each write becomes one record at that level/category, handy for wiring a sink that expects an io.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 suppress

Key Constraints

  • No initialization needed — the runtime installs the host-logging handler as slog’s default on startup; just use log/slog.
  • Prefer slog.* over fmt.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).
GuideDescription
golem-enable-otlp-goForward logs (and traces/metrics) to an OTLP collector
golem-add-agent-goDefine the agent whose handlers emit these logs