Skip to main content

Overview

The audit middleware captures detailed information about every HTTP request for compliance, debugging, and security monitoring. It provides flexible handlers for storing audit logs. Use it when you need:
  • Compliance audit trails
  • Security event logging
  • Request debugging and analysis

Installation

import "github.com/go-mizu/mizu/middlewares/audit"

Quick Start

app := mizu.New()

// Log all requests
app.Use(audit.New(func(entry *audit.Entry) {
    log.Printf("%s %s -> %d", entry.Method, entry.Path, entry.Status)
}))

Configuration

Options

OptionTypeDefaultDescription
Handlerfunc(*Entry)RequiredCallback for each entry
Skipfunc(*mizu.Ctx) boolnilSkip logging for requests
IncludeRequestBodyboolfalseInclude request body
MaxBodySizeint1024Max body size to capture
Metadatafunc(*mizu.Ctx) map[string]stringnilCustom metadata

Entry Fields

FieldTypeDescription
Timestamptime.TimeRequest time
MethodstringHTTP method
PathstringRequest path
QuerystringQuery string
StatusintResponse status
Latencytime.DurationRequest duration
IPstringClient IP
UserAgentstringUser-Agent header
RequestIDstringX-Request-ID header
RequestBodystringRequest body (if enabled)
Metadatamap[string]stringCustom metadata

Examples

Basic Logging

app.Use(audit.New(func(entry *audit.Entry) {
    fmt.Printf("[%s] %s %s %d %v\n",
        entry.Timestamp.Format(time.RFC3339),
        entry.Method,
        entry.Path,
        entry.Status,
        entry.Latency,
    )
}))

With Request Body

app.Use(audit.WithOptions(audit.Options{
    Handler: func(entry *audit.Entry) {
        log.Printf("Body: %s", entry.RequestBody)
    },
    IncludeRequestBody: true,
    MaxBodySize:        4096,
}))

Skip Health Checks

app.Use(audit.WithOptions(audit.Options{
    Handler: logEntry,
    Skip: func(c *mizu.Ctx) bool {
        return c.Request().URL.Path == "/health"
    },
}))

With Custom Metadata

app.Use(audit.WithOptions(audit.Options{
    Handler: logEntry,
    Metadata: func(c *mizu.Ctx) map[string]string {
        return map[string]string{
            "version": "1.0",
            "env":     os.Getenv("ENV"),
            "user_id": getUserID(c),
        }
    },
}))

Channel Handler (Async)

ch := make(chan *audit.Entry, 100)

// Consumer goroutine
go func() {
    for entry := range ch {
        // Process entries asynchronously
        saveToDatabase(entry)
    }
}()

app.Use(audit.New(audit.ChannelHandler(ch)))

Buffered Handler (Batch)

handler := audit.NewBufferedHandler(
    100,           // Batch size
    time.Minute,   // Flush interval
    func(entries []*audit.Entry) {
        // Process batch
        bulkInsert(entries)
    },
)
defer handler.Close()

app.Use(audit.New(handler.Handler()))

API Reference

Functions

// New creates audit middleware with handler
func New(handler func(*Entry)) mizu.Middleware

// WithOptions creates middleware with configuration
func WithOptions(opts Options) mizu.Middleware

// ChannelHandler creates async handler
func ChannelHandler(ch chan *Entry) func(*Entry)

// NewBufferedHandler creates batching handler
func NewBufferedHandler(maxSize int, interval time.Duration, flush func([]*Entry)) *BufferedHandler

BufferedHandler Methods

func (h *BufferedHandler) Handler() func(*Entry)
func (h *BufferedHandler) Flush()
func (h *BufferedHandler) Close()

Technical Details

Implementation Architecture

The audit middleware is built around a flexible handler pattern that processes audit entries containing comprehensive request/response metadata.

Core Components

Entry Structure: The Entry type captures all relevant information about an HTTP request/response cycle:
  • Request metadata (timestamp, method, path, query, remote address, user agent)
  • Request identification (request ID from configurable header)
  • Optional request body capture with size limits
  • Response status and latency measurement
  • Error information if handler returns an error
  • Custom metadata extension point
Response Writer Wrapping: The middleware uses a custom auditResponseWriter that wraps the standard http.ResponseWriter to capture the response status code. This wrapper intercepts WriteHeader and Write calls to record the status, defaulting to 200 OK if not explicitly set. Request Body Capture: When enabled, the middleware reads the request body up to MaxBodySize using io.LimitReader, then restores it using io.MultiReader so downstream handlers can still access it. This ensures body capture doesn’t interfere with normal request processing. Timing Measurement: Latency is calculated by recording the start time before calling the next handler and computing the elapsed duration after it returns. This provides accurate end-to-end request processing time.

Handler Patterns

Synchronous Handler: The basic handler function receives each entry immediately and processes it in the request path. Suitable for logging to stdout or fast operations. Channel Handler: Sends entries to a buffered channel for asynchronous processing. Uses a non-blocking select with default case to drop entries if the channel is full, preventing request blocking. Buffered Handler: Collects entries in memory and flushes them in batches either when reaching maxSize or on a timer interval. Includes proper mutex protection for concurrent access and a background goroutine for periodic flushing.

Configuration Defaults

  • RequestIDHeader: β€œX-Request-ID”
  • MaxBodySize: 1024 bytes
  • Handler: Defaults to encoding JSON to io.Discard if not specified
  • IncludeRequestBody: false (opt-in for performance and privacy)

Best Practices

  • Use async handlers for high-traffic applications
  • Set reasonable MaxBodySize to prevent memory issues
  • Skip logging for health checks and metrics endpoints
  • Include request IDs for correlation with other logs
  • Buffer writes for database storage

Testing

The audit middleware includes comprehensive test coverage for all functionality:
Test CaseDescriptionExpected Behavior
TestNewBasic middleware creation with handlerCaptures all entry fields (method, path, query, user agent, request ID, status, latency) correctly
TestWithOptions_RequestBodyRequest body capture with size limitReads and stores request body up to MaxBodySize, body remains readable by handler
TestWithOptions_SkipConditional logging with skip functionSkips audit logging for matched requests (/health), logs others normally
TestWithOptions_MetadataCustom metadata injectionAdds custom key-value pairs to entry metadata via callback function
TestWithOptions_ErrorError handling and status captureRecords error status codes (500) correctly when handler returns error
TestChannelHandlerAsync channel-based handlerSends entries to channel without blocking request processing
TestBufferedHandlerBatch processing with size triggerBuffers entries and flushes when batch size (3) is reached
TestBufferedHandler_FlushManual flush operationImmediately flushes buffered entries when Flush() is called
TestBufferedHandler_HandlerHandler function retrievalReturns valid handler function for middleware integration