Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

veb.request_id: New middleware that implements request ID tracking #23727

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

xD0135
Copy link
Contributor

@xD0135 xD0135 commented Feb 14, 2025

Request ID Middleware

This module implements request ID tracking functionality for V web applications. Request IDs are unique identifiers assigned to each HTTP request, which is essential for request tracing, debugging, and maintaining distributed systems.

Purpose

Request IDs help in:

  • Tracking requests across distributed systems
  • Correlating logs from different services
  • Debugging and troubleshooting
  • Performance monitoring
  • Request chain tracing

Usage

To enable request ID tracking in your veb app, you must embed the RequestIdContext struct in your Context struct.

Example:

import veb
import veb.request_id

pub struct Context {
    veb.Context
    request_id.RequestIdContext
}

Basic Configuration

Here's a simple configuration example:

const request_id_config := request_id.Config{
    header: 'X-Request-ID'
    generator: rand.uuid_v4
}

Middleware Setup

Enable request ID tracking for all routes or specific routes using veb's middleware system.

Example:

pub struct App {
    veb.Middleware[Context]
}

fn main() {
    mut app := &App{}
    // Register the RequestID middleware with custom configuration
    app.use(request_id.middleware[Context](request_id_config))
    veb.run[App, Context](mut app, 8080)
}

Accessing the Request ID

You can access the request ID in your route handlers:

fn (app &App) handler(mut ctx Context) veb.Result {
    // Get the current request ID
    request_id := ctx.request_id
    // Use the request ID for logging, etc.
    return ctx.text('Request ID: $request_id')
}

Configuration Options

The Config struct provides several configuration options:

pub struct Config {
pub:
    // Next defines a function to skip this middleware when returned true
    next ?fn (ctx &veb.Context) bool
    // Generator defines a function to generate the unique identifier
    generator fn () string = rand.uuid_v4
    // Header is the header key where to get/set the unique request ID
    header string = 'X-Request-ID'
    // Allow empty sets whether to allow empty request IDs
    allow_empty bool
    // Force determines whether to always generate a new ID even if one exists
    force bool
}

Configuration Options Explained

  • next: Optional function to conditionally skip the middleware
  • generator: Function to generate unique IDs (defaults to UUID v4)
  • header: HTTP header name for the request ID (defaults to "X-Request-ID")
  • allow_empty: Whether to allow empty request IDs
  • force: Whether to generate a new ID even when one already exists

Advanced Usage

Custom ID Generator

You can provide your own ID generator function:

fn custom_id_generator() string {
    return 'custom-prefix-${rand.uuid_v4()}'
}

config := request_id.Config{
    generator: custom_id_generator
}

Conditional Middleware Execution

Use the next function to skip the middleware based on custom logic:

config := request_id.Config{
    next: fn (ctx &veb.Context) bool {
        // Skip for health check endpoints
        return ctx.req.url.starts_with('/health')
    }
}

Forcing New IDs

When you want to ensure a new ID is generated regardless of existing headers:

config := request_id.Config{
    force: true
}

Best Practices

  1. Consistent Headers: Use consistent header names across your services
  2. ID Propagation: Forward request IDs to downstream services
  3. Logging Integration: Include request IDs in your logging system
  4. ID Format: Use a reliable ID generator (UUID v4 is recommended)

Security Considerations

While request IDs are not security features, consider these points:

  • Don't include sensitive information in request IDs
  • Validate request ID format if using custom generators
  • Be cautious with request ID length (recommended: 8-128 characters)

Examples

Basic Integration

module main

import veb
import veb.request_id

pub struct Context {
    veb.Context
    request_id.RequestIdContext
}

pub struct App {
    veb.Middleware[Context]
}

@['/request-id'; get]
pub fn (app &App) index(mut ctx Context) veb.Result {
    return ctx.text("Current request ID: ${ctx.request_id}")
}

fn main() {
    mut app := &App{}
    config := request_id.Config{
        header:      'X-Request-ID'
        force:       false
        allow_empty: false
	}
    app.use(request_id.middleware[Context](config))
    veb.run[App, Context](mut app, 8080)
}

With Custom Generator and Conditional Execution

config := request_id.Config{
    generator: fn () string {
        return 'app-${rand.uuid_v4()}'
    }
    next: fn (ctx &veb.Context) bool {
        return ctx.req.url.starts_with('/public')
    }
}

Copy link

Connected to Huly®: V_0.6-22145

@spytheman
Copy link
Member

Please run v check-md vlib/veb/request_id/README.md locally.

@spytheman
Copy link
Member

Use ```v ignore ... for code sections containing partial code.

@spytheman
Copy link
Member

(you can see the rest of the modifiers with v check-md without other arguments)

@spytheman
Copy link
Member

@medvednikov what do you think?

@xD0135
Copy link
Contributor Author

xD0135 commented Feb 18, 2025

@spytheman Thanks for fixing the formatting issues on README.md! I've learned something new and I'll keep that in mind for the future 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants