Skip to main content

Overview

The logging middleware automatically logs incoming requests (server) and outgoing requests (client) with detailed information including operation name, status code, error reason, latency, and request arguments.

Installation

go get github.com/go-kratos/kratos/v2/middleware/logging

Server Middleware

The Server function creates a server-side logging middleware:
func Server(logger log.Logger) middleware.Middleware

Basic Usage

import (
    "os"
    
    "github.com/go-kratos/kratos/v2"
    "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/middleware/logging"
    "github.com/go-kratos/kratos/v2/transport/http"
    "github.com/go-kratos/kratos/v2/transport/grpc"
)

func main() {
    // Create logger
    logger := log.NewStdLogger(os.Stdout)
    
    // Create HTTP server with logging
    httpSrv := http.NewServer(
        http.Address(":8000"),
        http.Middleware(
            logging.Server(logger),
        ),
    )
    
    // Create gRPC server with logging
    grpcSrv := grpc.NewServer(
        grpc.Address(":9000"),
        grpc.Middleware(
            logging.Server(logger),
        ),
    )
    
    app := kratos.New(
        kratos.Server(httpSrv, grpcSrv),
    )
    
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

Client Middleware

The Client function creates a client-side logging middleware:
func Client(logger log.Logger) middleware.Middleware

Usage Example

import (
    "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/middleware/logging"
    "github.com/go-kratos/kratos/v2/transport/http"
)

// Create logger
logger := log.NewStdLogger(os.Stdout)

// Create HTTP client with logging
conn, err := http.NewClient(
    context.Background(),
    http.WithEndpoint("127.0.0.1:8000"),
    http.WithMiddleware(
        logging.Client(logger),
    ),
)

Log Fields

The logging middleware automatically includes the following fields:
FieldDescriptionExample
kindAlways “server” or “client”"server", "client"
componentTransport kind"http", "grpc"
operationOperation name"/api.v1.Greeter/SayHello"
argsRequest arguments"{\"name\":\"world\"}"
codeResponse status code200, 500
reasonError reason (if error)"INTERNAL_ERROR", ""
stackError stack trace (if error)Stack trace string
latencyRequest latency in seconds0.123

Structured Logging Example

import (
    "os"
    
    "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/middleware/logging"
    "github.com/go-kratos/kratos/v2/middleware/tracing"
)

// Create structured logger with additional fields
logger := log.With(
    log.NewStdLogger(os.Stdout),
    "service.name", "my-service",
    "service.version", "v1.0.0",
    "trace.id", tracing.TraceID(),
    "span.id", tracing.SpanID(),
)

// Use in middleware
httpSrv := http.NewServer(
    http.Address(":8000"),
    http.Middleware(
        tracing.Server(),
        logging.Server(logger),
    ),
)

Log Output Examples

Successful Request

{
  "level": "INFO",
  "kind": "server",
  "component": "http",
  "operation": "/api.v1.Greeter/SayHello",
  "args": "{\"name\":\"world\"}",
  "code": 200,
  "reason": "",
  "stack": "",
  "latency": 0.123,
  "service.name": "my-service",
  "trace.id": "4bf92f3577b34da6a3ce929d0e0e4736"
}

Failed Request

{
  "level": "ERROR",
  "kind": "server",
  "component": "http",
  "operation": "/api.v1.Greeter/SayHello",
  "args": "{\"name\":\"\"}",
  "code": 400,
  "reason": "INVALID_ARGUMENT",
  "stack": "github.com/go-kratos/kratos/v2/errors.BadRequest...\n",
  "latency": 0.001,
  "service.name": "my-service",
  "trace.id": "5cf93f4688c45ea7b4df039e1f1f5847"
}

Sensitive Data Redaction

The logging middleware supports the Redacter interface for redacting sensitive data from logs:
type Redacter interface {
    Redact() string
}

Usage Example

// Define a request type with sensitive data
type LoginRequest struct {
    Username string
    Password string
}

// Implement Redacter interface
func (r *LoginRequest) Redact() string {
    return fmt.Sprintf("{username: %s, password: [REDACTED]}", r.Username)
}

// Now the password will not appear in logs
Without implementing Redacter, the middleware will attempt to use fmt.Stringer interface, or fall back to fmt.Sprintf("%+v", req).

Redaction Priority

The middleware checks for log-friendly interfaces in this order:
  1. Redacter interface - Custom redaction logic
  2. fmt.Stringer interface - Custom string representation
  3. Default formatting - Uses fmt.Sprintf("%+v", req)
// Custom string representation
func (r *LoginRequest) String() string {
    return fmt.Sprintf("LoginRequest{Username: %s}", r.Username)
}

Custom Logger Implementation

You can use any logger that implements the log.Logger interface:
type Logger interface {
    Log(level Level, keyvals ...interface{}) error
}

Zap Logger Example

import (
    "github.com/go-kratos/kratos/v2/log"
    "go.uber.org/zap"
)

// Create Zap logger
zapLogger, _ := zap.NewProduction()
defer zapLogger.Sync()

// Wrap with Kratos logger
logger := log.NewHelper(log.With(
    log.NewStdLogger(zapLogger),
    "service", "my-service",
))

// Use in middleware
logging.Server(logger)

Logrus Logger Example

import (
    "github.com/go-kratos/kratos/v2/log"
    "github.com/sirupsen/logrus"
)

// Create Logrus logger
logrusLogger := logrus.New()
logrusLogger.SetFormatter(&logrus.JSONFormatter{})

// Wrap with Kratos logger
logger := log.NewHelper(log.With(
    log.NewStdLogger(logrusLogger.Writer()),
    "service", "my-service",
))

// Use in middleware
logging.Server(logger)

Complete Example

package main

import (
    "context"
    "log"
    "os"
    
    "github.com/go-kratos/kratos/v2"
    kratoslog "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/middleware/logging"
    "github.com/go-kratos/kratos/v2/middleware/recovery"
    "github.com/go-kratos/kratos/v2/middleware/tracing"
    "github.com/go-kratos/kratos/v2/transport/http"
    
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "go.opentelemetry.io/otel/sdk/resource"
    tracesdk "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)

func initTracer(serviceName string) (*tracesdk.TracerProvider, error) {
    exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(
        jaeger.WithEndpoint("http://localhost:14268/api/traces"),
    ))
    if err != nil {
        return nil, err
    }
    
    tp := tracesdk.NewTracerProvider(
        tracesdk.WithBatcher(exporter),
        tracesdk.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String(serviceName),
        )),
    )
    
    otel.SetTracerProvider(tp)
    return tp, nil
}

func main() {
    // Initialize tracer
    tp, err := initTracer("logging-example")
    if err != nil {
        log.Fatal(err)
    }
    defer tp.Shutdown(context.Background())
    
    // Create structured logger
    logger := kratoslog.With(
        kratoslog.NewStdLogger(os.Stdout),
        "service.name", "logging-example",
        "service.version", "v1.0.0",
        "service.id", "12345",
        "trace.id", tracing.TraceID(),
        "span.id", tracing.SpanID(),
    )
    
    // Create HTTP server with logging
    httpSrv := http.NewServer(
        http.Address(":8000"),
        http.Middleware(
            recovery.Recovery(),
            tracing.Server(),
            logging.Server(logger),
        ),
    )
    
    app := kratos.New(
        kratos.Name("logging-example"),
        kratos.Logger(logger),
        kratos.Server(httpSrv),
    )
    
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

Log Levels

The logging middleware uses different log levels based on the result:
  • INFO - Successful requests (no error)
  • ERROR - Failed requests (with error)
if err != nil {
    return log.LevelError, fmt.Sprintf("%+v", err)
}
return log.LevelInfo, ""

Best Practices

Implement the Redacter interface for any request types containing passwords, tokens, or PII.
func (r *SecureRequest) Redact() string {
    return fmt.Sprintf("{id: %s, token: [REDACTED]}", r.ID)
}
Always include trace IDs in logs for correlation with distributed traces.
logger := log.With(
    logger,
    "trace.id", tracing.TraceID(),
    "span.id", tracing.SpanID(),
)
Use structured (JSON) logging in production for easier parsing and analysis.
Include service name, version, and instance ID in all logs.
logger := log.With(
    logger,
    "service.name", "my-service",
    "service.version", version,
    "service.id", instanceID,
)
High-traffic services can generate large log volumes. Consider sampling or filtering in production.
Send logs to a centralized logging system (ELK, Loki, CloudWatch) for aggregation and analysis.

Filtering Requests

Use the selector middleware to log only specific operations:
import (
    "github.com/go-kratos/kratos/v2/middleware/logging"
    "github.com/go-kratos/kratos/v2/middleware/selector"
)

// Log only API requests, skip health checks
http.Middleware(
    selector.Server(
        logging.Server(logger),
    ).Match(func(ctx context.Context, operation string) bool {
        // Don't log health check requests
        return operation != "/healthz"
    }).Build(),
)

Source Reference

The logging middleware implementation can be found in:
  • middleware/logging/logging.go:22 - Server middleware
  • middleware/logging/logging.go:63 - Client middleware
  • middleware/logging/logging.go:17 - Redacter interface
  • middleware/logging/logging.go:102 - extractArgs function

Next Steps

Tracing

Add distributed tracing for request correlation

Metrics

Collect performance metrics

Build docs developers (and LLMs) love