Skip to content
Last9
Book demo

Log-Trace Correlation

Automatically inject trace_id and span_id into Go log entries with the Last9 Go Agent for slog and zap

The Last9 Go Agent automatically injects trace_id and span_id into your log entries so you can jump from a log line directly to its trace. Supported for both Go’s standard log/slog and Uber’s zap.

Trace fields are only injected when the context passed to a log call contains an active OpenTelemetry span. Logs without a span context pass through unchanged.

slog

One line replaces the global logger with a trace-aware version:

package main
import (
"context"
"log"
"log/slog"
"os"
"github.com/last9/go-agent"
slogagent "github.com/last9/go-agent/instrumentation/slog"
)
func main() {
if err := agent.Start(); err != nil {
log.Fatalf("failed to start agent: %v", err)
}
defer agent.Shutdown()
// Replaces the global slog logger
slogagent.SetDefault(os.Stdout, nil, nil)
// Use *Context methods — trace_id and span_id are injected automatically
ctx := context.Background() // in practice, this comes from your HTTP handler
slog.InfoContext(ctx, "processing request", "user_id", 42)
// Output: {"time":"...","level":"INFO","msg":"processing request","user_id":42,"trace_id":"abc123...","span_id":"def456..."}
}

Using slog in HTTP handlers

Always use *Context methods and pass the request context:

func getUserHandler(w http.ResponseWriter, r *http.Request) {
// r.Context() carries the active span set by your framework middleware
slog.InfoContext(r.Context(), "fetching user", "user_id", userID)
user, err := db.QueryContext(r.Context(), "SELECT * FROM users WHERE id = ?", userID)
if err != nil {
slog.ErrorContext(r.Context(), "db query failed", "error", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
slog.InfoContext(r.Context(), "user fetched", "user_id", userID)
}

zap

The lightest approach — no wrapper needed. Spread trace fields inline into any log call:

package main
import (
"log"
"go.uber.org/zap"
"github.com/last9/go-agent"
zapagent "github.com/last9/go-agent/instrumentation/zap"
)
func main() {
if err := agent.Start(); err != nil {
log.Fatalf("failed to start agent: %v", err)
}
defer agent.Shutdown()
logger, _ := zap.NewProduction()
defer logger.Sync()
// ctx comes from your HTTP handler or message consumer
logger.Info("request handled",
zap.String("path", "/api/users"),
zap.Int("status", 200),
zapagent.TraceFields(ctx)...,
)
// Output: {"level":"info","msg":"request handled","path":"/api/users","status":200,"trace_id":"abc...","span_id":"def..."}
}

Using zap in HTTP handlers

func orderHandler(c *gin.Context) {
logger.InfoContext(c.Request.Context(), "processing order",
zap.String("order_id", c.Param("id")),
)
if err := processOrder(c.Request.Context()); err != nil {
logger.ErrorContext(c.Request.Context(), "order failed",
zap.String("order_id", c.Param("id")),
zap.Error(err),
)
c.JSON(http.StatusInternalServerError, gin.H{"error": "order failed"})
return
}
logger.InfoContext(c.Request.Context(), "order completed",
zap.String("order_id", c.Param("id")),
)
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}

Viewing Correlated Logs and Traces

Once log-trace correlation is active, navigate to Logs in Last9. Each log entry with a trace_id field links directly to its trace in Trace Explorer.


Troubleshooting

Please get in touch with us on Discord or Email if you have any questions.