@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
379 lines • 12.3 kB
JavaScript
/**
* Correlation ID management for tracking requests across components
*/
import { AsyncLocalStorage } from 'async_hooks';
import { randomBytes } from 'crypto';
/**
* Async local storage for correlation context
* Provides thread-safe context storage across async operations
*/
export const correlationStorage = new AsyncLocalStorage();
/**
* Correlation Context Monitoring Metrics
* Tracks usage, performance, and potential issues for production monitoring
*/
const correlationMonitoring = {
contextsCreated: 0,
activeContexts: 0,
errors: 0,
lastError: null,
lastErrorTime: 0,
startTime: Date.now(),
};
/**
* Legacy context has been removed in favor of AsyncLocalStorage-only approach
* This eliminates race conditions in concurrent operations
* All code should now use withCorrelationContext() or withNewCorrelationContext()
*/
/**
* Get correlation context monitoring metrics
* Provides insights into context usage, performance, and health
*/
export function getCorrelationMonitoring() {
return { ...correlationMonitoring };
}
/**
* Log correlation context monitoring metrics
* Useful for periodic logging of system health
* @internal
*/
export function logCorrelationMonitoring(level = 'info') {
const metrics = getCorrelationMonitoring();
const uptime = Date.now() - metrics.startTime;
const uptimeMinutes = Math.floor(uptime / (1000 * 60));
const errorRate = metrics.contextsCreated > 0
? ((metrics.errors / metrics.contextsCreated) * 100).toFixed(2) + '%'
: '0%';
const message = `
[Correlation Monitoring]
- Uptime: ${uptimeMinutes} minutes
- Contexts Created: ${metrics.contextsCreated}
- Active Contexts: ${metrics.activeContexts}
- Errors: ${metrics.errors}
- Error Rate: ${errorRate}
- Last Error: ${metrics.lastError || 'None'}`;
switch (level) {
case 'debug':
console.debug(message);
break;
case 'info':
console.info(message);
break;
case 'warn':
console.warn(message);
break;
case 'error':
console.error(message);
break;
}
}
/**
* Reset correlation context monitoring metrics
* Useful for testing or periodic reporting
*/
export function resetCorrelationMonitoring() {
correlationMonitoring.contextsCreated = 0;
correlationMonitoring.activeContexts = 0;
correlationMonitoring.errors = 0;
correlationMonitoring.lastError = null;
correlationMonitoring.lastErrorTime = 0;
correlationMonitoring.startTime = Date.now();
}
/**
* Perform health check on correlation context system
* Verifies that the AsyncLocalStorage context system is functioning properly
*/
export function checkCorrelationHealth() {
try {
// Test basic context creation and retrieval
const testContext = {
id: 'health-check-' + generateShortCorrelationId(),
metadata: { healthCheck: true, timestamp: Date.now() },
};
let contextWorking = false;
withCorrelationContext(testContext, () => {
const current = getCurrentCorrelationContext();
contextWorking = current?.id === testContext.id;
});
if (!contextWorking) {
return {
healthy: false,
message: 'Correlation context system failed basic functionality test',
metrics: getCorrelationMonitoring(),
};
}
// Check for excessive errors
const errorRate = correlationMonitoring.contextsCreated > 0
? correlationMonitoring.errors / correlationMonitoring.contextsCreated
: 0;
if (errorRate > 0.1) {
// More than 10% error rate
return {
healthy: false,
message: `High error rate detected: ${(errorRate * 100).toFixed(1)}%`,
metrics: getCorrelationMonitoring(),
};
}
return {
healthy: true,
message: 'Correlation context system is healthy',
metrics: getCorrelationMonitoring(),
};
}
catch (error) {
return {
healthy: false,
message: `Health check failed: ${error instanceof Error ? error.message : String(error)}`,
metrics: getCorrelationMonitoring(),
};
}
}
/**
* Generate a new correlation ID
*/
export function generateCorrelationId() {
// Generate 16-byte random hex string (32 characters)
return randomBytes(16).toString('hex');
}
/**
* Generate a short correlation ID (8 characters) for display
*/
export function generateShortCorrelationId() {
// Generate 4-byte random hex string (8 characters)
return randomBytes(4).toString('hex');
}
/**
* Create a new correlation context with specific ID
*/
export function createCorrelationContextWithId(id, metadata) {
return {
id,
metadata,
};
}
/**
* Create a new correlation context with optional parent ID
*/
export function createCorrelationContext(parentId, metadata) {
return {
id: generateCorrelationId(),
parentId,
metadata,
};
}
/**
* Get the current correlation context
* Now uses AsyncLocalStorage exclusively - no legacy fallback
*/
export function getCurrentCorrelationContext() {
const asyncContext = correlationStorage.getStore();
return asyncContext || null;
}
/**
* Run a function with a specific correlation context
*/
export function withCorrelationContext(context, fn) {
try {
if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') {
console.debug(`[Correlation] Context started: ${context.id}`);
}
correlationMonitoring.activeContexts++;
const result = correlationStorage.run(context, fn);
correlationMonitoring.activeContexts--;
if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') {
console.debug(`[Correlation] Context completed: ${context.id}`);
}
return result;
}
catch (error) {
correlationMonitoring.errors++;
correlationMonitoring.lastError =
error instanceof Error ? error.message : String(error);
correlationMonitoring.lastErrorTime = Date.now();
correlationMonitoring.activeContexts--;
if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') {
console.error(`[Correlation] Context error: ${context.id}`, error); // nosemgrep
}
throw error;
}
}
/**
* Run a function with a new correlation context
*/
export function withNewCorrelationContext(fn, correlationId, metadata) {
const context = correlationId
? createCorrelationContextWithId(correlationId, metadata)
: createCorrelationContext(undefined, metadata);
correlationMonitoring.contextsCreated++;
if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') {
console.debug(`[Correlation] New context created: ${context.id}`);
}
try {
correlationMonitoring.activeContexts++;
const result = correlationStorage.run(context, () => fn(context));
correlationMonitoring.activeContexts--;
if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') {
console.debug(`[Correlation] New context completed: ${context.id}`);
}
return result;
}
catch (error) {
correlationMonitoring.errors++;
correlationMonitoring.lastError =
error instanceof Error ? error.message : String(error);
correlationMonitoring.lastErrorTime = Date.now();
correlationMonitoring.activeContexts--;
if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') {
console.error(`[Correlation] New context error: ${context.id}`, error); // nosemgrep
}
throw error;
}
}
/**
* Get the correlation ID for the current context
*/
export function getCorrelationId() {
const asyncContext = correlationStorage.getStore();
return asyncContext?.id || null;
}
/**
* Check if correlation is enabled
*/
export function isCorrelationEnabled() {
return process.env.NANOCODER_CORRELATION_ENABLED !== 'false';
}
/**
* Get correlation header for HTTP requests
*/
export function getCorrelationHeader() {
const correlationId = getCorrelationId();
if (!correlationId || !isCorrelationEnabled()) {
return {};
}
return {
'X-Correlation-ID': correlationId,
};
}
/**
* Extract correlation ID from HTTP headers
*/
export function extractCorrelationId(headers) {
// Try various header names
const possibleHeaders = [
'x-correlation-id',
'x-request-id',
'x-trace-id',
'x-span-id',
'correlation-id',
'request-id',
'trace-id',
'span-id',
];
for (const header of possibleHeaders) {
const value = headers[header] || headers[header.toUpperCase()];
if (value && typeof value === 'string') {
return value;
}
}
return null;
}
/**
* Create a correlation context from HTTP headers
*/
export function createCorrelationFromHeaders(headers, metadata) {
if (!isCorrelationEnabled()) {
return null;
}
const correlationId = extractCorrelationId(headers);
if (correlationId) {
return createCorrelationContextWithId(correlationId, metadata);
}
return createCorrelationContext(undefined, metadata);
}
/**
* Get correlation metadata
*/
export function getCorrelationMetadata(key) {
const asyncContext = correlationStorage.getStore();
if (asyncContext?.metadata) {
return key ? asyncContext.metadata[key] : asyncContext.metadata;
}
return key ? undefined : {};
}
/**
* Format correlation context for logging
*/
export function formatCorrelationForLog() {
const asyncContext = correlationStorage.getStore();
if (asyncContext && isCorrelationEnabled()) {
const result = {
correlationId: asyncContext.id,
};
if (asyncContext.parentId) {
result.parentCorrelationId = asyncContext.parentId;
}
return result;
}
return {};
}
/**
* Correlation middleware for Express-like frameworks
*/
export function correlationMiddleware() {
return (req, res, next) => {
// Extract or create correlation ID
let correlationId = extractCorrelationId(req.headers || {});
if (!correlationId) {
correlationId = generateCorrelationId();
}
// Create correlation context with the extracted or generated ID
const context = createCorrelationContextWithId(correlationId, {
method: req.method,
url: req.url,
userAgent: req.headers?.['user-agent'],
});
// Run the request handler within the correlation context
return correlationStorage.run(context, () => {
// Add correlation ID to response headers
if (res.setHeader) {
res.setHeader('X-Correlation-ID', correlationId);
}
next();
});
};
}
/**
* Wrap an async function with correlation tracking
*/
export function withCorrelation(fn, getCorrelationIdFromArgs) {
return (async (...args) => {
let context = null;
// Try to get correlation ID from arguments if provided
if (getCorrelationIdFromArgs) {
const correlationId = getCorrelationIdFromArgs(...args);
if (correlationId) {
context = createCorrelationContextWithId(correlationId);
}
}
// If no context from args, try current context or create new
if (!context) {
const current = getCurrentCorrelationContext();
if (current) {
context = createCorrelationContext(current.id);
}
else if (isCorrelationEnabled()) {
context = createCorrelationContext();
}
}
// Run function within correlation context
if (context) {
return correlationStorage.run(context, async () => {
return await fn(...args);
});
}
else {
return await fn(...args);
}
});
}
//# sourceMappingURL=correlation.js.map