Skip to main content

Logging Context System

CONA uses a separate logging context system to enrich logs with organization and user metadata. This is intentionally kept separate from the RLS (Row-Level Security) system to avoid circular dependencies and maintain clean separation of concerns.

Architecture


Why Separate from RLS?

RLS System (packages/database/src/rls.ts)

  • Purpose: Database security and multi-tenant isolation
  • Scope: PostgreSQL session configuration variables
  • Data: orgId, userId, sub (Auth0 ID)
  • Used by: PostgreSQL RLS policies

Logging Context (apps/webapp/lib/logging-context.ts)

  • Purpose: Enrich logs with metadata for monitoring and debugging
  • Scope: Request-scoped context (isolated via AsyncLocalStorage)
  • Data: organizationId, organizationSlug, userId, userSub
  • Used by: Axiom logging, middleware logging, security monitoring

Benefits of Separation

  1. No Circular Dependencies: Logging doesn’t depend on database queries
  2. Clean Separation of Concerns: Security (RLS) vs Observability (Logging)
  3. Independent Evolution: Can change logging strategy without affecting RLS
  4. Performance: Logging context is lightweight and doesn’t touch the database

API Reference

initLoggingContext(params)

Initialize logging context for a new request (typically called once per request).

updateLoggingContext(params)

Update specific fields in the logging context (called as more data becomes available).

getLoggingContext()

Get the current logging context (read-only).

Usage Examples

Middleware Logging

Logged Fields:
  • organizationSlug (if auth’d)
  • organizationId (if auth’d)
  • userId (if auth’d)
  • Request URL, method, IP, etc.

Security Event Logging

Logged Fields:
  • All fields from logging context
  • Security-specific metadata
  • source: "security" tag

Server Action Logging


Where Context is Set

1. Authentication Flow (auth.ts)

getAuth0Session() - Sets userSub
getAuth0User() - Adds userId
requireAuth() - Adds organizationId and organizationSlug

Best Practices

  1. Initialize Early: Call initLoggingContext() as soon as you have the Auth0 session
  2. Update Incrementally: Use updateLoggingContext() as more data becomes available
  3. Read-Only Access: Always use getLoggingContext() to access context (don’t modify directly)
  4. Request Isolation: Context is automatically isolated per-request using AsyncLocalStorage

Security Considerations

Request Isolation with AsyncLocalStorage

The logging context uses Node.js’s AsyncLocalStorage to ensure each request has its own isolated context. This prevents race conditions where concurrent requests could overwrite each other’s context. Without AsyncLocalStorage (vulnerable):
With AsyncLocalStorage (secure):

Why This Matters for Security Auditing

Security events must be accurately attributed to the correct user and organization. Without proper request isolation:
  • Brute force detection could flag the wrong IP/user
  • Unauthorized access logs could show incorrect user IDs
  • Audit trails would be unreliable for compliance
  • Security incidents couldn’t be accurately investigated
AsyncLocalStorage ensures security logs are always attributed to the correct request context.

Comparison: RLS vs Logging Context


Migration Notes

Prior to this system, organization slug was stored in RLS state, causing circular dependencies between logging and database layers. The new system cleanly separates these concerns: Before:
After: