# Log-Trace Correlation

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

Source: https://last9.io/docs/integrations/frameworks/go/log-trace-correlation/

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

**Global setup**

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

```go
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..."}
}
```

**Wrap existing handler**

Wrap any `slog.Handler` to add trace correlation:

```go
import (
    "log/slog"
    "os"

    slogagent "github.com/last9/go-agent/instrumentation/slog"
)

// JSON handler
base := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
handler := slogagent.NewHandler(base, nil)
logger := slog.New(handler)

// Text handler
handler = slogagent.NewTextHandler(os.Stdout, nil, nil)

// JSON shorthand
handler = slogagent.NewJSONHandler(os.Stdout, nil, nil)
```

**Custom attribute keys**

Override the default `trace_id` and `span_id` keys to match your log aggregation backend:

```go
import slogagent "github.com/last9/go-agent/instrumentation/slog"

// Datadog-style
handler := slogagent.NewJSONHandler(os.Stdout, nil, &slogagent.Options{
    TraceKey: "dd.trace_id",
    SpanKey:  "dd.span_id",
})

logger := slog.New(handler)
```

### Using slog in HTTP handlers

Always use `*Context` methods and pass the request context:

```go
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)
}
```

:::note
Calls without context (`slog.Info`, `slog.Error`) cannot have trace correlation — Go has no thread-local storage. Migrate to `*Context` variants to enable log-trace linking.
:::

---

## zap

**TraceFields helper**

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

```go
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..."}
}
```

**Logger wrapper**

Use the `zapagent.Logger` wrapper for context-aware `*Context` methods that inject trace fields automatically:

```go
import (
    "go.uber.org/zap"
    zapagent "github.com/last9/go-agent/instrumentation/zap"
)

base, _ := zap.NewProduction()
logger := zapagent.New(base, nil)

// trace_id and span_id are injected automatically
logger.InfoContext(ctx, "user created", zap.String("user_id", "42"))
logger.WarnContext(ctx, "rate limit approaching", zap.Int("remaining", 5))
logger.ErrorContext(ctx, "payment failed", zap.Error(err))
```

Chaining works as expected:

```go
// Pre-set fields are preserved alongside trace fields
reqLogger := logger.With(zap.String("request_id", reqID))
reqLogger.InfoContext(ctx, "started")

// Named loggers
authLogger := logger.Named("auth")
authLogger.WarnContext(ctx, "token expiring")
```

**Custom attribute keys**

Override the default `trace_id` and `span_id` keys:

```go
logger := zapagent.New(base, &zapagent.Options{
    TraceKey: "dd.trace_id",
    SpanKey:  "dd.span_id",
})
```

### Using zap in HTTP handlers

```go
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](https://app.last9.io/logs) in Last9. Each log entry with a `trace_id` field links directly to its trace in [Trace Explorer](https://app.last9.io/traces).

---

## Troubleshooting

Please get in touch with us on [Discord](https://discord.com/invite/Q3p2EEucx9) or [Email](mailto:support@last9.io) if you have any questions.
