Logging and Tracing from an Effect Agent
@golemcloud/effect-golem automatically installs a host-backed Effect logger and tracer for
agent initialization, method handlers, and user-defined snapshot handlers. Use normal Effect v4
logging and span operators; do not install logging or tracing layers in the agent.
Logs accepted by Effect's level filter are forwarded to wasi:logging/logging. The SDK includes
log annotations, log spans, and the active host trace and span IDs in a logfmt-style line.
Complete Agent Example
Logging calls are Effects, so compose or yield* them inside the method handler. Effect's default
minimum log level is "Info"; lower it around a handler when debug or trace output is required.
import { Effect, References, Schema } from "effect";
import { defineAgent, method } from "@golemcloud/effect-golem";
export const LogDemoAgent = defineAgent({
name: "LogDemoAgent",
mode: "durable",
id: {
instanceName: Schema.String,
},
methods: {
doWork: method({
input: { taskName: Schema.String },
success: Schema.String,
}),
},
}).implement({
init: ({ instanceName }) => Effect.succeed(instanceName),
methods: (instanceName) => ({
doWork: ({ taskName }) =>
Effect.gen(function* () {
yield* Effect.logInfo(`starting task: ${taskName}`);
yield* Effect.logDebug(`processing task: ${taskName}`);
yield* Effect.logWarning(`task is slow: ${taskName}`);
return "done";
}).pipe(
Effect.annotateLogs({ instanceName, taskName }),
Effect.withSpan("LogDemoAgent.doWork", {
attributes: { instanceName, taskName },
}),
Effect.provideService(References.MinimumLogLevel, "Debug"),
),
}),
});
Register the implementation from the component entry point:
// src/main.ts
import "./log-demo-agent.js";
Use the emitted .js suffix for local imports because generated Effect projects use ESM with
NodeNext module resolution.
Log Levels
| Effect function | Golem/WASI level | Use for |
|---|---|---|
Effect.logTrace(...) |
trace | Fine-grained control flow and values |
Effect.logDebug(...) |
debug | Development and diagnostic details |
Effect.logInfo(...) |
info | Significant normal operations |
Effect.logWarning(...) |
warn | Recoverable or degraded situations |
Effect.logError(...) |
error | Serious operation failures |
Effect.logFatal(...) |
critical | Fatal failures |
The warning function is named logWarning, not logWarn.
Structured Context and Causes
Prefer stable messages with dynamic values in Effect.annotateLogs when the exact rendered
message is not part of an output contract:
const createOrder = Effect.gen(function* () {
yield* Effect.logInfo("order created").pipe(
Effect.annotateLogs({ orderId, customerId }),
);
});
Applying Effect.annotateLogs({...}) to a whole handler Effect adds the fields to every log in
that scope. Annotation values are safely rendered into the host log line.
Observe an Effect failure without changing it by tapping its full cause:
const observed = operation.pipe(
Effect.tapCause((cause) => Effect.logError("order processing failed", cause)),
);
The Golem logger renders the Effect Cause in a cause field. In Effect v4 the operator is
Effect.tapCause, not the Effect v3 name tapErrorCause.
Debug and Trace Filtering
Effect's default minimum level is "Info", so logDebug and logTrace are normally filtered
before reaching Golem. Override the threshold only around the Effect that needs verbose output:
const verboseHandler = handler.pipe(
Effect.provideService(References.MinimumLogLevel, "Debug"),
);
Use "Trace" to include trace logs or "All" to accept every level. This fiber-context-local
override also applies to child fibers started by the wrapped Effect; it does not lower the level
for unrelated handlers.
Log Spans and Tracing Spans
These two span operators serve different purposes:
Effect.withLogSpan("lookup")adds a named duration to log lines in its scope.Effect.withSpan("OrderAgent.process", { attributes: {...} })creates a real child tracing span under the Golem invocation span. Logs inside it include the active trace and span IDs.
Add tracing attributes discovered during execution from inside an active tracing span:
const processOrder = Effect.gen(function* () {
const queue = "priority";
yield* Effect.annotateCurrentSpan({ queue, retryable: true });
yield* Effect.logInfo("order accepted").pipe(Effect.annotateLogs({ queue }));
}).pipe(Effect.withSpan("OrderAgent.process"));
Effect.withSpan finishes the host span on success, failure, or interruption. Failed Effects mark
the span as an error. Do not manually finish it. Host span attributes are string-valued, so the SDK
safely renders non-string Effect attribute values before forwarding them.
Viewing Logs
Invocations stream logs by default:
golem agent invoke 'LogDemoAgent("demo")' doWork '"my-task"'
# Add --no-stream to suppress live log output.
Stream an agent's output independently:
golem agent stream 'LogDemoAgent("demo")'
Golem records logs in the oplog. Logging is a durable side effect: replay skips log calls from already-recorded operations, so recovery does not duplicate their output.
Key Constraints
- Import Effect APIs from
effectand agent APIs from@golemcloud/effect-golem. - Prefer
Effect.log*overconsole.*and the lower-levelLogging.log(...); Effect logging keeps annotations, log spans, causes, and trace correlation. - Do not invent methods such as
Logging.info(...)orEffect.logWarn(...). - Do not provide
Logging.layerorTracing.layer; the agent dispatcher already installs both. - Keep the generated
effectand@golemcloud/effect-golemversions aligned. - Do not edit generated files under
golem-temp/.