Logging and Request Correlation

ActingWeb provides comprehensive logging capabilities with automatic request correlation, making it easy to trace requests across distributed actor-to-actor communication.

Overview

ActingWeb’s logging system automatically adds context to every log statement:

  • Request ID: Unique identifier for each request

  • Actor ID: The actor handling the request

  • Peer ID: The peer making the request (when authenticated)

This context appears in every log line, enabling easy grepping and request tracing.

Quick Start

Basic Configuration

from actingweb.logging_config import configure_actingweb_logging
import logging

# Development: verbose logging
configure_actingweb_logging(logging.DEBUG)

# Production: minimal logging
configure_actingweb_logging(logging.WARNING, db_level=logging.ERROR)

Log Format

Default Format

When the context filter is enabled, logs use this format:

%(asctime)s %(context)s %(name)s:%(levelname)s: %(message)s

Example output:

2024-01-15 10:23:45,123 [a1b2c3d4:actor123:peer456] actingweb.handlers.properties:INFO: Property 'email' updated
2024-01-15 10:23:45,456 [a1b2c3d4:actor123:peer456] actingweb.auth:DEBUG: Checking authentication

Context Format

The %(context)s field shows:

[short_request_id:actor_id:short_peer_id]

Where:

  • short_request_id: Last 8 characters of the request UUID (sufficient for correlation)

  • actor_id: Full actor identifier

  • short_peer_id: Last segment after final colon in peer ID

  • -: Used when a field is not available

Examples:

[a1b2c3d4:actor123:peer456]    # Authenticated peer request
[a1b2c3d4:actor123:-]          # Creator/trustee request (no peer)
[-:actor123:-]                 # No request context set

Request Correlation

How It Works

ActingWeb automatically tracks requests through multiple layers:

  1. Incoming Requests: Extract or generate request ID from X-Request-ID header

  2. Request Context: Store request ID, actor ID, and peer ID in thread-local context

  3. Logging: Automatically inject context into every log statement

  4. Outgoing Requests: Propagate request IDs to peer actors with parent tracking

Request Flow Example

When Actor A calls Actor B, which then calls Actor C:

Client → Actor A (request_id: UUID-A)
  ├─ Actor A logs: [UUID-A:actorA:-]
  └─ Actor A → Actor B
       ├─ Headers: X-Request-ID: UUID-B, X-Parent-Request-ID: UUID-A
       ├─ Actor B logs: [UUID-B:actorB:actorA]
       └─ Actor B → Actor C
            ├─ Headers: X-Request-ID: UUID-C, X-Parent-Request-ID: UUID-B
            └─ Actor C logs: [UUID-C:actorC:actorB]

This creates a traceable chain: UUID-A → UUID-B → UUID-C

Peer-to-Peer Correlation

When actors communicate, ActingWeb automatically:

  1. Generates a new request ID for the outgoing request

  2. Includes the current request ID as the parent

  3. Logs the correlation for debugging

Example from actingweb.aw_proxy:

DEBUG actingweb.aw_proxy:aw_proxy.py:113 Peer request correlation: new_id=fa5959b7... parent_id=a1b2c3d4...

Request bodies sent to a peer are logged as a summary — the sorted key names (at most 20) and the serialised size — never their values (since 3.15):

DEBUG actingweb.aw_proxy:aw_proxy.py:251 Creating trust peer resource at (https://peer.example.com/abc/methods/notify) with data(keys=[message, priority] bytes=42)

Key names of a /properties write are the property names. The MCP authorize endpoint and the FastAPI trust routes use the same summary.

Grepping Logs

The context format makes it easy to filter logs using standard tools.

Find All Logs for a Request

# Find all logs for request ID starting with "a1b2c3d4"
grep "a1b2c3d4" application.log

Example output:

2024-01-15 10:23:45,123 [a1b2c3d4:actor123:peer456] actingweb.handlers:INFO: Request received
2024-01-15 10:23:45,234 [a1b2c3d4:actor123:peer456] actingweb.auth:DEBUG: Authentication successful
2024-01-15 10:23:45,345 [a1b2c3d4:actor123:peer456] actingweb.handlers:INFO: Property updated

Find All Logs for an Actor

# Find all logs for a specific actor
grep ":actor123:" application.log

Find All Logs for Actor-Peer Interaction

# Find logs for specific actor-peer combination
grep ":actor123:peer456" application.log

Trace Request Chains

To trace a complete request chain across actors:

# Step 1: Find logs for the parent request
grep "a1b2c3d4" application.log

# Step 2: Look for child requests in peer logs
grep "parent_id=a1b2c3d4" application.log

# Step 3: Find the child request ID and repeat
grep "new_id=fa5959b7" application.log

Advanced Filtering

Combine grep patterns for precise filtering:

# Find ERROR logs for a specific request
grep "a1b2c3d4" application.log | grep "ERROR"

# Find all property operations for an actor
grep ":actor123:" application.log | grep "properties"

# Find authentication logs for peer requests
grep "peer456" application.log | grep "auth"

Configuration Options

Convenience Functions

ActingWeb provides pre-configured logging setups:

from actingweb.logging_config import (
    configure_production_logging,
    configure_development_logging,
    configure_testing_logging
)

# Production: WARNING level, minimal output
configure_production_logging()

# Development: DEBUG level, verbose output
configure_development_logging()

# Testing: WARNING level, suppress most output
configure_testing_logging()

Per-Logger Configuration

Control logging levels for specific components:

configure_actingweb_logging(
    level=logging.INFO,            # Default level
    db_level=logging.WARNING,      # Database operations
    auth_level=logging.INFO,       # Authentication
    handlers_level=logging.INFO,   # Request handlers
    proxy_level=logging.DEBUG      # Peer communication
)

Environment Variables

Configure logging via environment variables:

# Set default log level
export ACTINGWEB_LOG_LEVEL=INFO

# Set database log level
export ACTINGWEB_DB_LOG_LEVEL=WARNING

Performance Considerations

Context injection has minimal overhead:

  • Setting context: ~1-2 microseconds per request

  • Getting context: ~0.5 microseconds per log statement

  • Filter processing: ~2-3 microseconds per log statement

Total impact: <1% for typical INFO-level logging

Best Practices for Production

  1. Use WARNING level for default logging:

    configure_actingweb_logging(logging.WARNING)
    
  2. Suppress verbose loggers:

    configure_actingweb_logging(
        level=logging.INFO,
        db_level=logging.ERROR,      # Database very verbose
        auth_level=logging.WARNING,   # Auth moderately verbose
        proxy_level=logging.WARNING   # Proxy moderately verbose
    )
    
  3. Enable context filter for easier debugging:

    enable_request_context_filter()
    

Integration with Frameworks

Flask Integration

ActingWeb’s Flask integration automatically manages request context:

from actingweb.interface import ActingWebApp
from actingweb.interface.integrations.flask_integration import FlaskIntegration
from flask import Flask

flask_app = Flask(__name__)
aw_app = ActingWebApp(...)

# Context is automatically managed
integration = FlaskIntegration(aw_app, flask_app)

Context lifecycle:

  1. Before request: Extract/generate request ID, extract actor ID from URL

  2. During request: Context available in all log statements

  3. After request: Add X-Request-ID to response headers, clear context

FastAPI Integration

ActingWeb’s FastAPI integration uses middleware for async context:

from actingweb.interface import ActingWebApp
from actingweb.interface.integrations.fastapi_integration import FastAPIIntegration
from fastapi import FastAPI

fastapi_app = FastAPI()
aw_app = ActingWebApp(...)

# Context automatically managed via middleware
integration = FastAPIIntegration(aw_app, fastapi_app)

Context is automatically propagated across await boundaries.

Manual Context Management

For custom integrations, manage context manually:

from actingweb import request_context

# Set up context at request start
request_context.set_request_context(
    request_id="550e8400-e29b-41d4-a716-446655440000",
    actor_id="actor123"
)

# During authentication, add peer ID
request_context.set_peer_id("peer456")

# Clear context at request end
request_context.clear_request_context()

Context API

Available context functions:

from actingweb import request_context

# Set full context
request_context.set_request_context(
    request_id="uuid",
    actor_id="actor123",
    peer_id="peer456"  # Optional
)

# Set individual fields
request_context.set_request_id("uuid")
request_context.set_actor_id("actor123")
request_context.set_peer_id("peer456")

# Get context
request_id = request_context.get_request_id()
actor_id = request_context.get_actor_id()
peer_id = request_context.get_peer_id()

# Generate new request ID
new_id = request_context.generate_request_id()

# Clear all context
request_context.clear_request_context()

Troubleshooting

No Context in Logs

Problem: Log statements don’t show context information.

Solution: Enable the context filter:

from actingweb.logging_config import enable_request_context_filter
enable_request_context_filter()

Context Shows Dashes

Problem: Context shows [-:-:-] or similar.

Explanation: This means context fields are not set:

  • [-:-:-]: No context set at all

  • [uuid:-:-]: Request ID set, but no actor or peer

  • [uuid:actor:-]: No peer ID (normal for creator/trustee requests)

Solution: Ensure context is set at request start. For framework integrations, this happens automatically.

Context Not Cleared Between Requests

Problem: Context from previous request appears in new request.

Solution: Ensure clear_request_context() is called at request end. Framework integrations do this automatically in their cleanup hooks.

Missing Parent Request ID

Problem: Peer request logs don’t show parent request ID.

Explanation: Parent request ID only appears when:

  1. A request context exists (parent request)

  2. That request makes a peer-to-peer call

Solution: Normal behavior. Requests without a parent context won’t have a parent ID.

Examples

Complete Flask Application

from flask import Flask
from actingweb.interface import ActingWebApp
from actingweb.interface.integrations.flask_integration import FlaskIntegration
from actingweb.logging_config import (
    configure_actingweb_logging,
    enable_request_context_filter
)
import logging

# Configure logging
configure_actingweb_logging(logging.INFO, db_level=logging.WARNING)
enable_request_context_filter()

# Set up Flask and ActingWeb
flask_app = Flask(__name__)
aw_app = (
    ActingWebApp(
        aw_type="urn:actingweb:example.com:myapp",
        database="dynamodb",
        fqdn="myapp.example.com",
        proto="https://"
    )
    .with_oauth(client_id="...", client_secret="...")
    .with_web_ui(enable=True)
)

# Context is automatically managed
integration = FlaskIntegration(aw_app, flask_app)

if __name__ == "__main__":
    flask_app.run()

Custom Context in Request Handler

import logging
from actingweb import request_context

logger = logging.getLogger(__name__)

def my_handler(actor_id, data):
    # Context is already set by framework integration
    logger.info("Processing request")  # Shows context automatically

    # Make peer request (correlation automatic)
    from actingweb.aw_proxy import AwProxy
    proxy = AwProxy(peer_target={"id": actor_id, "peerid": "peer123"}, config=config)
    result = proxy.get_resource(path="resource")

    logger.info("Request completed")
    return result

Log Analysis Script

#!/bin/bash
# analyze_request.sh - Trace a request through logs

REQUEST_ID=$1

echo "=== Request: $REQUEST_ID ==="
echo ""

echo "Main request logs:"
grep "$REQUEST_ID" application.log

echo ""
echo "Child requests:"
grep "parent_id=${REQUEST_ID:0:8}" application.log | \
    sed -n 's/.*new_id=\([a-f0-9]*\).*/\1/p' | \
    while read child_id; do
        echo "  Child: $child_id"
        grep "$child_id" application.log | head -3
    done

See Also