Skip to content

Local observability and metrics

Genkit provides a robust set of built-in observability features, including tracing and metrics collection powered by OpenTelemetry. For local observability, such as during the development phase, the Genkit Developer UI provides detailed trace viewing and debugging capabilities. For production observability, we provide Genkit Monitoring in the Firebase console via the Firebase plugin. Alternatively, you can export your OpenTelemetry data to the observability tooling of your choice.

Genkit automatically collects traces and metrics without requiring explicit configuration, allowing you to observe and debug your Genkit code’s behavior in the Developer UI. Genkit stores these traces, enabling you to analyze your Genkit flows step-by-step with detailed input/output logging and statistics. In production, Genkit can export traces and metrics to Firebase Genkit Monitoring for further analysis.

Genkit Go logs through github.com/firebase/genkit/go/core/logger, a thin layer over the standard library’s log/slog. Log with the package-level functions, passing the context first:

import "github.com/firebase/genkit/go/core/logger"
genkit.DefineFlow(g, "summarize", func(ctx context.Context, input string) (string, error) {
logger.Info(ctx, "summarizing", "size", len(input))
resp, err := genkit.Generate(ctx, g, ai.WithPrompt(input))
if err != nil {
logger.Warn(ctx, "generation failed, returning the input unchanged", "error", err)
return input, nil
}
logger.Debug(ctx, "summary ready", "outputSize", len(resp.Text()))
return resp.Text(), nil
})

Passing the context is what ties a record to its surroundings. The context carries the trace span that is active at that moment, so the record is correlated with the step that produced it, and it carries any attributes bound to the context’s logger. The basic-errors sample logs through these helpers as it classifies and recovers from failures.

logger.FromContext(ctx) is still supported and still correlates: it returns the context’s logger bound to that context, so records logged through its plain methods keep the span. Reach for it when you want to bind attributes once and have them follow everything logged downstream:

ctx = logger.WithContext(ctx, logger.FromContext(ctx).With("requestId", requestID))
// Everything logged below carries requestId, with no extra plumbing.
logger.Info(ctx, "handling request")

Plain slog works too, with one caveat: only the *Context methods carry the span. slog.InfoContext(ctx, ...) is correlated, slog.Info(...) is not.

Running your app under the CLI is enough to get logs into the Developer UI:

Terminal window
genkit start -- go run .

No code change and no configuration is needed. The CLI sets the environment the runtime looks for, genkit.Init installs the export handler, and records stream to the Developer UI, which lists them against the span that emitted them in the trace viewer. Starting the Developer UI separately with genkit ui:start also works, since the CLI hands the telemetry server’s address to the running app.

The Developer UI receives every record at debug level and above, independent of the console level, so the terminal can stay quiet while the full debug narrative lands in the trace viewer. Genkit’s own records travel the same channel: span start and finish, the resolved generate request, each model turn with its finish reason and token counts, tool batches, and each middleware hook with its duration and whether it short-circuited.

A record logged without a context is still exported, but it carries no span, so it never appears against a step.

// Mirror every record to a file without disturbing console output or
// Developer UI streaming.
f, err := os.Create("genkit.log")
if err != nil {
log.Fatal(err)
}
logger.AddHandler(slog.NewJSONHandler(f, &slog.HandlerOptions{Level: slog.LevelDebug}))

The console starts at info. To see Genkit’s per-request detail in the terminal as well, lower the level:

// Console only. In dev the Developer UI already receives debug and above,
// whatever this is set to.
logger.SetLevel(slog.LevelDebug)

SetLevel installs Genkit’s console handler as the process default. If your application configured its own default slog handler, Genkit leaves it alone and warns instead, so set that handler’s level rather than calling SetLevel.

VariableAccepted valuesDefaultEffect
GENKIT_LOG_LEVELdebug, info, warn, error, case-insensitive, with an optional offset such as error+2infoMinimum level for the console. It never changes what the Developer UI receives. A value it cannot parse, such as warning, verbose, or a number, is ignored with a warning, and so is the variable as a whole if your application installed its own default slog handler.
GENKIT_OTEL_ENABLE_LOGSany value that parses as false (false, 0, f) turns export offunset, so export is onAn opt-out. You do not have to set it to see logs. Unset, true, and anything that does not parse as false all leave export on.
GENKIT_ENVdev installs the Developer UI log sinkprodgenkit start sets dev. Nothing is exported outside the dev environment.
GENKIT_TELEMETRY_SERVERa base URL, for example http://localhost:4033emptyWhere records are posted. genkit start sets it. When it is empty, the CLI supplies the address at runtime instead.

Attribute values reach the Developer UI as strings, integers, or booleans. Everything else is rendered to text on the way: a float64 arrives as its decimal string, a time.Duration as 412ms, an error as its Error() text, and anything else as JSON. slog groups flatten into dotted keys.

Export never blocks your code. Records are batched and posted in the background, and dropped rather than queued when the buffer fills, with one warning on stderr. There is no flush at process exit either, so a short-lived go run . can lose its last few records.

The Genkit Monitoring dashboard helps you understand the overall health of your Genkit features. It is also useful for debugging stability and content issues that may indicate problems with your LLM prompts and/or Genkit Flows. See the Getting Started guide for more details.