Skip to main content

Context Management

SyntropyLog's context management system provides automatic correlation and context propagation across your entire application.

🎯 Overview

The context system enables:

  • Automatic correlation - Single ID connects all operations
  • Context propagation - Data flows through HTTP calls, message brokers, and Redis
  • Framework agnostic - Works with any Node.js application
  • No manual context passing - Use getLogger() and contextManager.run() instead of passing context through every function

🚀 Basic Usage

Get the context manager after initialization (ready). Use run(callback) to execute code with a dedicated context (and correlation ID). Inside the callback, set context and use the logger; all logs share that context.

import { syntropyLog } from 'syntropylog';

// After init and ready:
const contextManager = syntropyLog.getContextManager();

await contextManager.run(async () => {
contextManager.set('userId', 123);
contextManager.set('requestId', 'req-456');

const logger = syntropyLog.getLogger('api');
logger.info('User authenticated successfully');
// All logs in this run() share the same correlation ID
});

🔗 Correlation and scoped context

Use contextManager.run(callback) so that each logical flow (e.g. one HTTP request) has its own correlation ID. The framework generates or propagates the correlation ID within that scope.

const contextManager = syntropyLog.getContextManager();

await contextManager.run(async () => {
const correlationId = contextManager.getCorrelationId();
const headerName = contextManager.getCorrelationIdHeaderName(); // e.g. 'x-correlation-id'

const logger = syntropyLog.getLogger('main');
logger.info('Processing request');
// Use correlationId or headerName to inject into HTTP headers, message metadata, etc.
});

HTTP / Brokers / Redis: The library does not provide getHttp(), getBroker(), or getRedis(). Use your own clients and inject the correlation ID (e.g. via Axios interceptors) using getCorrelationId() and getCorrelationIdHeaderName(). See HTTP instrumentation.

🏗️ Singleton Pattern

Context manager uses singleton pattern for consistency:

// First call - creates and returns new instance
const context1 = syntropyLog.getContextManager();

// Second call - returns the SAME instance (singleton)
const context2 = syntropyLog.getContextManager();

// context1 === context2 ✅
// All context data is shared

🎯 Context Data Types

SyntropyLog supports various context data types:

const contextManager = syntropyLog.getContextManager();

// String values
contextManager.set('requestId', 'req-123');

// Number values
contextManager.set('userId', 123);

// Boolean values
contextManager.set('isAuthenticated', true);

// Object values
contextManager.set('user', {
id: 123,
email: 'user@example.com',
role: 'admin',
});

// Array values
contextManager.set('permissions', ['read', 'write', 'delete']);

🔄 Context lifecycle with run()

Use run() so each request or job has its own context and correlation ID. A reusable context middleware pattern: capture the correlation ID from the incoming header when present (using the configured getCorrelationIdHeaderName()); if absent, getCorrelationId() generates one.

function withContext(
contextManager: ReturnType<typeof syntropyLog.getContextManager>,
handler: () => Promise<void>,
options?: { correlationId?: string }
) {
return contextManager.run(async () => {
if (options?.correlationId != null && options.correlationId !== '') {
contextManager.setCorrelationId(options.correlationId);
}
contextManager.getCorrelationId(); // ensure ID exists (generate if not set)
await handler();
});
}

// Express/Fastify: one run() per request, correlation ID from header or generated
const contextManager = syntropyLog.getContextManager();
app.use((req, res, next) => {
const fromHeader = req.get(contextManager.getCorrelationIdHeaderName()) ?? undefined;
withContext(contextManager, () => next(), { correlationId: fromHeader });
});

For a background job, call run() directly and optionally set other context:

await contextManager.run(async () => {
contextManager.set('jobId', 'job-123');
const logger = syntropyLog.getLogger('worker');
logger.info('Job started');
// ... work ...
logger.info('Job completed');
});

🧠 Smart Context Logging with LoggingMatrix

The loggingMatrix is an intermediary layer that controls what context data gets passed to the logger. It acts as a filter that determines which context fields are included in each log level, without affecting the security pipeline or data masking.

How LoggingMatrix Works

await syntropyLog.init({
loggingMatrix: {
default: ['correlationId'], // Minimal context for success logs
trace: ['*'], // Full context for debugging
debug: ['correlationId', 'userId', 'operation'],
info: ['correlationId', 'serviceName'],
warn: ['correlationId', 'userId', 'errorCode'],
error: ['*'], // Full context when things go wrong
fatal: ['*'], // Everything for critical failures
},
});

Key Features

  • Selective Context: Only specified context fields are included in logs
  • Wildcard Support: Use '*' to include ALL context fields
  • Hot Reload: Can be modified at runtime without restarting
  • Security Preserved: All masking and sanitization rules still apply
  • Performance Optimized: Only serializes what's needed

Examples

// Set context data
const contextManager = syntropyLog.getContextManager();
contextManager.set('userId', 123);
contextManager.set('serviceName', 'user-service');
contextManager.set('operation', 'create-user');
contextManager.set('tenantId', 'tenant-1');
contextManager.set('sessionId', 'sess-789');

// With loggingMatrix: { info: ['correlationId', 'serviceName'] }
logger.info('User created successfully');
// Log output: { correlationId: 'abc-123', serviceName: 'user-service', message: 'User created successfully' }
// Note: userId, operation, tenantId, sessionId are NOT included

// With loggingMatrix: { error: ['*'] }
logger.error('Database connection failed');
// Log output: { correlationId: 'abc-123', userId: 123, serviceName: 'user-service', operation: 'create-user', tenantId: 'tenant-1', sessionId: 'sess-789', message: 'Database connection failed' }
// Note: ALL context fields are included

Runtime Configuration

You can change which context fields are included per level at runtime with reconfigureLoggingMatrix():

// Change logging matrix dynamically
syntropyLog.reconfigureLoggingMatrix({
info: ['correlationId', 'serviceName', 'userId'],
error: ['correlationId', 'userId', 'errorCode'],
});

// The changes take effect immediately
logger.info('Operation completed'); // Now includes userId

🔧 Configuration Options

await syntropyLog.init({
context: {
correlationIdHeader: 'X-Correlation-ID',
transactionIdHeader: 'X-Trace-ID',
},
});

🎯 Best Practices

  1. Set context early - Initialize context at the beginning of each request
  2. Use meaningful names - Choose descriptive context keys
  3. Keep context minimal - Only include relevant data
  4. Avoid sensitive data - Don't put passwords or tokens in context
  5. Use correlation IDs - Always enable correlation for distributed tracing
  6. Clean up context - Context is automatically cleaned up per request

🔍 Reading context

// Get all current context
const allContext = contextManager.getAll();

// Get specific context value
const userId = contextManager.get('userId');

// Correlation and headers (for outbound HTTP / messaging)
const correlationId = contextManager.getCorrelationId();
const headerName = contextManager.getCorrelationIdHeaderName();
const traceHeaders = contextManager.getTraceContextHeaders();

⚡ Performance Considerations

  • Minimal overhead - Context operations are optimized
  • Memory efficient - Context is cleaned up automatically
  • Fast propagation - Context flows through all operations
  • Singleton pattern - Prevents memory leaks