Atatus Go APM Agent extends your application's logging capabilities with log correlation, allowing you to connect logs, traces, and spans for easier debugging and deeper observability.

These features help you:

  • Automatically attach trace, transaction, and span IDs to your application logs.
  • Seamlessly connect logs and traces within the Atatus dashboard.
  • Navigate from any log entry to its corresponding trace and vice versa.

Log Correlation

Log correlation lets you trace individual requests across systems by adding correlation identifiers to your logs. To correlate logs from your application with transactions captured by the Atatus Go Agent, your logs must contain one or more of the following identifiers:

Field Description
trace.id Uniquely identifies a request across distributed services
transaction.id Marks the primary transaction being traced
span.id Refers to the specific operation within a transaction

These IDs connect your logs with traces, making it easy to jump from a log entry to its related transaction in the Atatus dashboard.

Supported Logging Frameworks

The Atatus Go Agent ships with dedicated integrations for the following logging frameworks:

  • logrus — via go.atatus.com/agent/module/atlogrus
  • zap — via go.atatus.com/agent/module/atzap
  • zerolog — via go.atatus.com/agent/module/atzerolog

If your logging framework is not listed above, you can still correlate logs with traces by manually injecting the IDs using the agent's public API.

logrus

Install the Atatus Go agent and the logrus integration module:

go get go.atatus.com/agent
go get go.atatus.com/agent/module/atlogrus

Register the hook and use atlogrus.TraceContext to enrich log entries with the active trace, transaction, and span IDs:

import (
    "github.com/sirupsen/logrus"

    "go.atatus.com/agent"
    "go.atatus.com/agent/module/atlogrus"
)

func main() {
    // atlogrus.Hook will send "error", "panic", and "fatal" level
    // log messages to Atatus APM as errors.
    logrus.AddHook(&atlogrus.Hook{})
}

func handleRequest(w http.ResponseWriter, req *http.Request) {
    // atlogrus.TraceContext extracts the transaction and span (if any)
    // from the given context, and returns logrus.Fields containing the
    // trace, transaction, and span IDs.
    traceContextFields := atlogrus.TraceContext(req.Context())
    logrus.WithFields(traceContextFields).Debug("handling request")
}

Example log output:

{"level":"debug","msg":"handling request","time":"1970-01-01T00:00:00Z","trace.id":"67829ae467e896fb2b87ec2de50f6c0e","transaction.id":"67829ae467e896fb"}

zap

Install the Atatus Go agent and the zap integration module:

go get go.atatus.com/agent
go get go.atatus.com/agent/module/atzap

Wrap the zap core and use atzap.TraceContext to enrich log entries with the active trace, transaction, and span IDs:

import (
    "go.atatus.com/agent"
    "go.atatus.com/agent/module/atzap"

    "go.uber.org/zap"
)

// atzap.Core.WrapCore wraps the core created by zap.NewExample
// such that logs are also sent to atzap.Core.
//
// atzap.Core sends "error", "panic", and "fatal" level log messages
// to Atatus APM as errors.
var logger = zap.NewExample(zap.WrapCore((&atzap.Core{}).WrapCore))

func handleRequest(w http.ResponseWriter, req *http.Request) {
    // atzap.TraceContext extracts the transaction and span (if any)
    // from the given context, and returns []zapcore.Field containing
    // the trace, transaction, and span IDs.
    traceContextFields := atzap.TraceContext(req.Context())
    logger.With(traceContextFields...).Debug("handling request")
}

Example log output:

{"level":"debug","msg":"handling request","trace.id":"67829ae467e896fb2b87ec2de50f6c0e","transaction.id":"67829ae467e896fb"}

zerolog

Install the Atatus Go agent and the zerolog integration module:

go get go.atatus.com/agent
go get go.atatus.com/agent/module/atzerolog

Register atzerolog.Writer as an additional output, and add atzerolog.TraceContextHook to inject trace IDs into request-scoped loggers:

import (
    "net/http"
    "os"

    "github.com/rs/zerolog"

    "go.atatus.com/agent/module/atzerolog"
)

// atzerolog.Writer sends log records with the level "error" or greater
// to Atatus APM as errors.
var baseLogger = zerolog.New(zerolog.MultiLevelWriter(os.Stdout, &atzerolog.Writer{}))

func init() {
    // atzerolog.MarshalErrorStack extracts stack traces from errors
    // produced by github.com/pkg/errors. Unlike
    // github.com/rs/zerolog/pkgerrors.MarshalStack, the atzerolog
    // implementation records fully-qualified function names so errors
    // reported to Atatus APM are attributed to the correct package.
    zerolog.ErrorStackMarshaler = atzerolog.MarshalErrorStack
}

func traceLoggingMiddleware(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
        ctx := req.Context()
        logger := zerolog.Ctx(ctx).Hook(atzerolog.TraceContextHook(ctx))
        req = req.WithContext(logger.WithContext(ctx))
        h.ServeHTTP(w, req)
    })
}

Manual Log Correlation

If the agent's built-in logging integrations don't fit your application — for example, you use a different logging framework or want full control over how IDs are written — you can use the agent's public API to inject trace IDs manually.

There are two approaches depending on whether your logs are structured or unstructured.

Manual Correlation in Structured Logs

For structured logs, attach the trace.id, transaction.id, and span.id fields directly to your log events.

Given a *atatus.Transaction, obtain its trace and transaction IDs using Transaction.TraceContext(). Similarly, given a *atatus.Span, obtain its span ID using Span.TraceContext().

If you start transactions and spans through the context APIs, retrieve them with atatus.TransactionFromContext and atatus.SpanFromContext. Note that if a transaction is not sampled, TransactionFromContext returns nil. Similarly, spans may be dropped, so SpanFromContext may also return nil.

import "go.atatus.com/agent"

labels := make(map[string]string)
tx := atatus.TransactionFromContext(ctx)
if tx != nil {
    traceContext := tx.TraceContext()
    labels["trace.id"] = traceContext.Trace.String()
    labels["transaction.id"] = traceContext.Span.String()
    if span := atatus.SpanFromContext(ctx); span != nil {
        labels["span.id"] = span.TraceContext().Span.String()
    }
}

Manual Correlation in Unstructured Logs

For unstructured (plain text) logs — such as the standard library's log package or basic printf-style logging — embed the trace IDs directly into the log message.

If you already have a transaction or span object, use the TraceContext() methods. The trace, transaction, and span ID types all provide String() methods that return their canonical hex-encoded representations:

traceContext := tx.TraceContext()
spanID := span.TraceContext().Span
log.Printf("ERROR [trace.id=%s transaction.id=%s span.id=%s] an error occurred",
    traceContext.Trace, traceContext.Span, spanID)

If you are working with context.Context objects instead, use atatus.TraceFormatter for a more concise call site:

import "go.atatus.com/agent"

log.Printf("ERROR [%+v] an error occurred", atatus.TraceFormatter(ctx))

This produces a log line similar to:

2025/01/01 14:48:02 ERROR [trace.id=cd04f33b9c0c35ae8abe77e799f126b7 transaction.id=cd04f33b9c0c35ae span.id=960834f4538880a4] an error occurred