syntropylog
Version:
An instance manager with observability for Node.js applications
464 lines (451 loc) • 18.9 kB
TypeScript
import { z } from 'zod';
/**
* @file src/http/adapters/adapter.types.ts
* @description Defines the "Universal HTTP Contract" for any HTTP client that
* wants to be instrumented by SyntropyLog. These generic interfaces are key
* to decoupling the framework from specific implementations like axios or got.
*/
/**
* @interface AdapterHttpRequest
* @description Represents a generic, normalized HTTP request that the framework
* can understand. The adapter is responsible for converting this to the
* specific format of the underlying library (e.g., AxiosRequestConfig).
*/
interface AdapterHttpRequest {
/** The full URL for the request. */
url: string;
/** The HTTP method. */
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
/** A record of request headers. */
headers: Record<string, string | number | string[]>;
/** The request body, if any. */
body?: unknown;
/** A record of URL query parameters. */
queryParams?: Record<string, any>;
}
/**
* @interface AdapterHttpResponse
* @description Represents a generic, normalized HTTP response. The adapter
* will convert the library-specific response into this format.
* @template T The expected type of the response body data.
*/
interface AdapterHttpResponse<T = any> {
/** The HTTP status code of the response. */
statusCode: number;
/** The response body data. */
data: T;
/** A record of response headers. */
headers: Record<string, string | number | string[]>;
}
/**
* @interface AdapterHttpError
* @description Represents a generic, normalized HTTP error. The adapter
* will convert the library-specific error into this format.
*/
interface AdapterHttpError extends Error {
/** The original request that caused the error. */
request: AdapterHttpRequest;
/** The response received, if any. */
response?: AdapterHttpResponse;
/** A flag to identify this as a normalized adapter error. */
isAdapterError: true;
}
/**
* @interface IHttpClientAdapter
* @description The interface that every HTTP Client Adapter must implement.
* This is the "plug" where users will connect their clients.
*/
interface IHttpClientAdapter {
/**
* The core method that the SyntropyLog instrumenter needs. It executes an
* HTTP request and returns a normalized response, or throws a normalized error.
* @template T The expected type of the response body data.
* @param {AdapterHttpRequest} request The generic HTTP request to execute.
* @returns {Promise<AdapterHttpResponse<T>>} A promise that resolves with the normalized response.
*/
request<T>(request: AdapterHttpRequest): Promise<AdapterHttpResponse<T>>;
}
/**
* @file src/logger/levels.ts
* @description Defines the available log levels, their names, and their severity weights.
*/
/**
* @description A mapping of log level names to their severity weights.
* Higher numbers indicate higher severity.
*/
declare const LOG_LEVEL_WEIGHTS: {
readonly fatal: 60;
readonly error: 50;
readonly warn: 40;
readonly info: 30;
readonly debug: 20;
readonly trace: 10;
readonly silent: 0;
};
/**
* @description The type representing a valid log level name.
*/
type LogLevel = keyof typeof LOG_LEVEL_WEIGHTS;
/**
* Internal Types for SyntropyLog Framework
*
* These types and utilities are for advanced usage and internal framework operations.
* Use with caution - they may change between versions.
*/
/**
* Represents any value that can be safely serialized to JSON.
* This is a recursive type used to ensure type safety for log metadata.
*/
type JsonValue = string | number | boolean | null | {
[key: string]: JsonValue;
} | JsonValue[];
/**
* Type for log metadata objects that can be passed to logging methods
*/
type LogMetadata = Record<string, JsonValue>;
/**
* Type for log bindings that are attached to logger instances
*/
type LogBindings = Record<string, JsonValue>;
/**
* Type for retention rules that can be attached to loggers
*/
type LogRetentionRules = {
ttl?: number;
maxSize?: number;
maxEntries?: number;
archiveAfter?: number;
deleteAfter?: number;
[key: string]: JsonValue | number | undefined;
};
/**
* Type for format arguments that can be passed to logging methods
*/
type LogFormatArg = string | number | boolean | null | undefined;
/**
* Type for values that can be stored in context
*/
type ContextValue = string | number | boolean | null | undefined | Buffer | JsonValue;
/**
* Type for context data structure
*/
type ContextData = Record<string, ContextValue>;
/**
* Type for context configuration options
*/
type ContextConfig = {
correlationIdHeader?: string;
transactionIdHeader?: string;
[key: string]: ContextValue;
};
/**
* Type for context headers used in HTTP requests
*/
type ContextHeaders = Record<string, string>;
/**
* Type for context callback functions
*/
type ContextCallback = () => void | Promise<void>;
/**
* Type for logging matrix configuration
*/
type LoggingMatrix = Partial<Record<string, string[]>>;
/**
* Type for filtered context based on log level
*/
type FilteredContext = Record<string, unknown>;
/**
* Defines the public interface for a logger instance.
* This ensures a consistent API for logging across the application,
* including standard logging methods and a fluent API for contextual logging.
*/
interface ILogger {
level: LogLevel;
/**
* Logs a message at the 'fatal' level. The application will likely exit.
* @param {...(LogFormatArg | LogMetadata | JsonValue)[]} args - The arguments to log (metadata object, message, or format args).
*/
fatal(...args: (LogFormatArg | LogMetadata | JsonValue)[]): Promise<void>;
/**
* Logs a message at the 'error' level.
* @param {...(LogFormatArg | LogMetadata | JsonValue)[]} args - The arguments to log (metadata object, message, or format args).
*/
error(...args: (LogFormatArg | LogMetadata | JsonValue)[]): Promise<void>;
/**
* Logs a message at the 'warn' level.
* @param {...(LogFormatArg | LogMetadata | JsonValue)[]} args - The arguments to log (metadata object, message, or format args).
*/
warn(...args: (LogFormatArg | LogMetadata | JsonValue)[]): Promise<void>;
/**
* Logs a message at the 'info' level.
* @param {...(LogFormatArg | LogMetadata | JsonValue)[]} args - The arguments to log (metadata object, message, or format args).
*/
info(...args: (LogFormatArg | LogMetadata | JsonValue)[]): Promise<void>;
/**
* Logs a message at the 'debug' level.
* @param {...(LogFormatArg | LogMetadata | JsonValue)[]} args - The arguments to log (metadata object, message, or format args).
*/
debug(...args: (LogFormatArg | LogMetadata | JsonValue)[]): Promise<void>;
/**
* Logs a message at the 'trace' level.
* @param {...(LogFormatArg | LogMetadata | JsonValue)[]} args - The arguments to log (metadata object, message, or format args).
*/
trace(...args: (LogFormatArg | LogMetadata | JsonValue)[]): Promise<void>;
/**
* Creates a new child logger instance with bindings that will be present in every log.
* The child inherits all settings from the parent, adding or overriding the specified bindings.
* @param {LogBindings} bindings - Key-value pairs to bind to the child logger.
* @returns {ILogger} A new `ILogger` instance.
*/
child(bindings: LogBindings): ILogger;
/**
* Dynamically updates the minimum log level for this logger instance.
* Any messages with a severity lower than the new level will be ignored.
* @param {LogLevel} level - The new log level to set.
*/
setLevel(level: LogLevel): void;
/**
* Creates a new logger instance with a `source` field bound to it.
* This is useful for creating a logger for a specific module or component.
* @param {string} source - The name of the source (e.g., 'redis', 'AuthModule').
* @returns {ILogger} A new `ILogger` instance with the `source` binding.
*/
withSource(source: string): ILogger;
/**
* Creates a new logger instance with a `retention` field bound to it.
* The provided rules object will be deep-cloned to ensure immutability.
* @param {LogRetentionRules} rules - A JSON object containing the retention rules.
* @returns {ILogger} A new `ILogger` instance with the `retention` binding.
*/
withRetention(rules: LogRetentionRules): ILogger;
/**
* Creates a new logger instance with a `transactionId` field bound to it.
* This is useful for tracking a request across multiple services.
* @param {string} transactionId - The unique ID of the transaction.
* @returns {ILogger} A new `ILogger` instance with the `transactionId` binding.
*/
withTransactionId(transactionId: string): ILogger;
}
/**
* @interface IContextManager
* @description The contract for managing asynchronous context.
*/
interface IContextManager {
/**
* Configures the context manager with specific options.
* This should be called once during initialization.
* @param options The configuration options.
* @param options.correlationIdHeader The custom header name to use for the correlation ID.
* @param options.transactionIdHeader The custom header name for the transaction ID.
*/
configure(options: ContextConfig): void;
/**
* Executes a function within a new, isolated asynchronous context.
* The new context can inherit data from the parent context.
* @template T The return type of the callback function.
* @param callback The function to execute within the new context.
* @returns The return value of the callback function.
*/
run(fn: ContextCallback): Promise<void>;
/**
* Sets a value in the current asynchronous context.
* @param key The key for the value.
* @param value The value to store.
*/
set(key: string, value: ContextValue): void;
/**
* Gets a value from the current asynchronous context.
* @template T The expected type of the value.
* @param key The key of the value to retrieve.
* @returns The value associated with the key, or `undefined` if not found.
*/
get<T = ContextValue>(key: string): T | undefined;
/**
* Gets the entire key-value store from the current context.
* @returns {ContextData} An object containing all context data.
*/
getAll(): ContextData;
/**
* A convenience method to get the correlation ID from the current context.
* If no correlation ID exists, generates one automatically to ensure tracing continuity.
* @returns {string} The correlation ID (never undefined).
*/
getCorrelationId(): string;
/**
* Gets the configured HTTP header name used for the correlation ID.
* @returns {string} The header name.
*/
getCorrelationIdHeaderName(): string;
/**
* Gets the configured HTTP header name used for the transaction ID.
* @returns {string} The header name.
*/
getTransactionIdHeaderName(): string;
/**
* A convenience method to get the transaction ID from the current context.
* @returns {string | undefined} The transaction ID, or undefined if not set.
*/
getTransactionId(): string | undefined;
/**
* A convenience method to set the transaction ID in the current context.
* @param transactionId The transaction ID to set.
*/
setTransactionId(transactionId: string): void;
/** Gets the tracing headers to propagate the context (e.g., W3C Trace Context). */
getTraceContextHeaders(): ContextHeaders;
/**
* Gets a filtered context based on the specified log level.
* This is useful for logging purposes to ensure only relevant context is included.
* @param level The log level to filter by.
* @returns A record containing only the context data relevant for the specified level.
*/
getFilteredContext(level: LogLevel): FilteredContext;
/**
* Reconfigures the logging matrix dynamically.
* This method allows changing which context fields are included in logs
* without affecting security configurations like masking or log levels.
* @param newMatrix The new logging matrix configuration
*/
reconfigureLoggingMatrix(newMatrix: LoggingMatrix): void;
}
/**
* FILE: src/config.schema.ts
* DESCRIPTION: Defines the Zod validation schemas for the entire library's configuration.
* These schemas are the single source of truth for the configuration's structure and types.
*/
/**
* @description Schema for a single HTTP client instance.
*/
declare const httpInstanceConfigSchema: z.ZodObject<{
instanceName: z.ZodString;
adapter: z.ZodType<IHttpClientAdapter, z.ZodTypeDef, IHttpClientAdapter>;
isDefault: z.ZodOptional<z.ZodBoolean>;
propagate: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
propagateFullContext: z.ZodOptional<z.ZodBoolean>;
logging: z.ZodOptional<z.ZodObject<{
onSuccess: z.ZodOptional<z.ZodDefault<z.ZodEnum<["trace", "debug", "info"]>>>;
onError: z.ZodOptional<z.ZodDefault<z.ZodEnum<["warn", "error", "fatal"]>>>;
logSuccessBody: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
logSuccessHeaders: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
onRequest: z.ZodOptional<z.ZodDefault<z.ZodEnum<["trace", "debug", "info"]>>>;
logRequestBody: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
logRequestHeaders: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
}, "strip", z.ZodTypeAny, {
onSuccess?: "info" | "debug" | "trace" | undefined;
onError?: "fatal" | "error" | "warn" | undefined;
logSuccessBody?: boolean | undefined;
logSuccessHeaders?: boolean | undefined;
onRequest?: "info" | "debug" | "trace" | undefined;
logRequestBody?: boolean | undefined;
logRequestHeaders?: boolean | undefined;
}, {
onSuccess?: "info" | "debug" | "trace" | undefined;
onError?: "fatal" | "error" | "warn" | undefined;
logSuccessBody?: boolean | undefined;
logSuccessHeaders?: boolean | undefined;
onRequest?: "info" | "debug" | "trace" | undefined;
logRequestBody?: boolean | undefined;
logRequestHeaders?: boolean | undefined;
}>>;
}, "strip", z.ZodTypeAny, {
instanceName: string;
adapter: IHttpClientAdapter;
logging?: {
onSuccess?: "info" | "debug" | "trace" | undefined;
onError?: "fatal" | "error" | "warn" | undefined;
logSuccessBody?: boolean | undefined;
logSuccessHeaders?: boolean | undefined;
onRequest?: "info" | "debug" | "trace" | undefined;
logRequestBody?: boolean | undefined;
logRequestHeaders?: boolean | undefined;
} | undefined;
isDefault?: boolean | undefined;
propagate?: string[] | undefined;
propagateFullContext?: boolean | undefined;
}, {
instanceName: string;
adapter: IHttpClientAdapter;
logging?: {
onSuccess?: "info" | "debug" | "trace" | undefined;
onError?: "fatal" | "error" | "warn" | undefined;
logSuccessBody?: boolean | undefined;
logSuccessHeaders?: boolean | undefined;
onRequest?: "info" | "debug" | "trace" | undefined;
logRequestBody?: boolean | undefined;
logRequestHeaders?: boolean | undefined;
} | undefined;
isDefault?: boolean | undefined;
propagate?: string[] | undefined;
propagateFullContext?: boolean | undefined;
}>;
/**
* @file src/config.ts
* @description Defines and exports the configuration types for the library.
* These types are now explicitly defined for better TypeScript intellisense and autocompletion,
* while still using Zod schemas for runtime validation.
*/
/**
* @description The configuration type for a single HTTP client instance.
*/
type HttpClientInstanceConfig = z.infer<typeof httpInstanceConfigSchema>;
/**
* @file src/http/InstrumentedHttpClient.ts
* @description This class is the heart of the HTTP instrumentation architecture.
* It wraps any adapter that complies with `IHttpClientAdapter` and adds a centralized
* layer of instrumentation (logging, context, timers).
*/
/**
* @class InstrumentedHttpClient
* @description Wraps an `IHttpClientAdapter` to provide automatic logging,
* context propagation, and timing for all HTTP requests.
*/
declare class InstrumentedHttpClient {
private readonly adapter;
private readonly logger;
private readonly contextManager;
private readonly config;
readonly instanceName: string;
private readonly instrumentorOptions;
/**
* @constructor
* @param {IHttpClientAdapter} adapter - The underlying HTTP client adapter (e.g., AxiosAdapter).
* @param {ILogger} logger - The logger instance for this client.
* @param {IContextManager} contextManager - The manager for handling asynchronous contexts.
* @param {HttpClientInstanceConfig} config - The configuration for this specific instance.
*/
constructor(adapter: IHttpClientAdapter, logger: ILogger, contextManager: IContextManager, config: HttpClientInstanceConfig);
/**
* The single public method. It executes an HTTP request through the wrapped
* adapter, applying all instrumentation logic.
* @template T The expected type of the response data.
* @param {AdapterHttpRequest} request - The generic HTTP request to execute.
* @returns {Promise<AdapterHttpResponse<T>>} A promise that resolves with the normalized response.
* @throws {AdapterHttpError | Error} Throws the error from the adapter, which is re-thrown after being logged.
*/
request<T>(request: AdapterHttpRequest): Promise<AdapterHttpResponse<T>>;
/**
* @private
* Logs the start of an HTTP request, respecting the configured options.
* @param {AdapterHttpRequest} request - The outgoing request.
*/
private logRequestStart;
/**
* @private
* Logs the successful completion of an HTTP request.
* @template T
* @param {AdapterHttpRequest} request - The original request.
* @param {AdapterHttpResponse<T>} response - The received response.
* @param {number} durationMs - The total duration of the request in milliseconds.
*/
private logRequestSuccess;
/**
* @private
* Logs the failure of an HTTP request.
* @param {AdapterHttpRequest} request - The original request.
* @param {unknown} error - The error that was thrown.
* @param {number} durationMs - The total duration of the request until failure.
*/
private logRequestFailure;
}
export { InstrumentedHttpClient };
export type { AdapterHttpError, AdapterHttpRequest, AdapterHttpResponse, IHttpClientAdapter };