Grove
1.x
Docs/Grove/Logging Middleware
Open

Reading3 min
Updated31 Jul 2026
Sourcev1/kv/middleware/logging.mdx

The logging middleware records every KV operation with structured fields using Go's standard log/slog package. Each log entry includes the operation type, key, and latency, making it straightforward to trace performance and debug issues.

Installation01

import "github.com/xraph/grove/kv/middleware"

Usage02

import (
    "log/slog"

    "github.com/xraph/grove/kv"
    "github.com/xraph/grove/kv/middleware"
)

store, err := kv.Open(drv,
    kv.WithHook(middleware.NewLogging(slog.Default())),
)

Constructor

func NewLogging(logger *slog.Logger) *LoggingHook
ParameterDescription
loggerAn *slog.Logger instance for structured log output

How It Works03

The logging hook implements both PreQueryHook and PostQueryHook:

  1. BeforeQuery records the start time in qc.Values under the key _log_start.

  2. AfterQuery calculates the elapsed time and emits a structured log entry.

Log Fields

Each log entry includes:

FieldTypeDescription
opstringOperation name (e.g., "GET", "SET", "DEL")
keystringThe key being operated on
latencydurationTime elapsed between BeforeQuery and AfterQuery

Example Output

With the default text handler:

level=INFO msg="kv command" op=GET key=user:123 latency=1.234ms
level=INFO msg="kv command" op=SET key=user:123 latency=2.567ms
level=INFO msg="kv command" op=DEL key=user:123 latency=0.891ms

With a JSON handler:

{"level":"INFO","msg":"kv command","op":"GET","key":"user:123","latency":"1.234ms"}

Integration with Structured Logging04

The middleware accepts any *slog.Logger, so you can configure it with custom handlers, levels, and attributes:

Custom Logger with Attributes

logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
})).With(
    slog.String("service", "user-api"),
    slog.String("component", "kv"),
)

store, err := kv.Open(drv,
    kv.WithHook(middleware.NewLogging(logger)),
)

This produces log entries with the additional service and component fields on every KV operation.

Using with Third-Party Loggers

Any logger that provides an *slog.Logger interface works. For example, with a Zap-backed slog handler:

import "go.uber.org/zap/exp/zapslog"

zapLogger, _ := zap.NewProduction()
slogLogger := slog.New(zapslog.NewHandler(zapLogger.Core()))

store, err := kv.Open(drv,
    kv.WithHook(middleware.NewLogging(slogLogger)),
)

Middleware Ordering05

Place the logging hook early in the middleware chain if you want to capture the total latency including all other middleware. Place it later if you want to measure only the time spent in the driver:

// Logs total latency (including namespace prefixing, cache check, etc.)
store, err := kv.Open(drv,
    kv.WithHook(middleware.NewLogging(logger)),
    kv.WithHook(middleware.NewNamespace("tenant:acme")),
    kv.WithHook(middleware.NewCache(1000, 5*time.Minute)),
)
// Logs only driver latency (excludes middleware overhead)
store, err := kv.Open(drv,
    kv.WithHook(middleware.NewNamespace("tenant:acme")),
    kv.WithHook(middleware.NewCache(1000, 5*time.Minute)),
    kv.WithHook(middleware.NewLogging(logger)),
)

API Reference06

MethodDescription
NewLogging(logger)Create a new logging hook with the given *slog.Logger