UNPKG

syntropylog

Version:

An instance manager with observability for Node.js applications

464 lines (451 loc) 18.9 kB
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 };