UNPKG

syntropylog

Version:

An instance manager with observability for Node.js applications

539 lines (524 loc) 21.5 kB
import { z } from 'zod'; /** * @file src/brokers/adapter.types.ts * @description Defines the "Universal Broker Contract" for any messaging client * that wants to be instrumented by SyntropyLog. These generic interfaces * are key to decoupling the framework from specific implementations like * RabbitMQ or Kafka. */ /** * @interface BrokerMessage * @description Represents a standard message format that the framework understands. * The adapter is responsible for converting the broker-specific message * format to this structure, and vice-versa. */ interface BrokerMessage { /** * The actual content of the message. Using `Buffer` is the most flexible * approach as it supports any type of serialization (JSON, Avro, Protobuf, etc.). */ payload: Buffer; /** * Key-value metadata attached to the message. * This is where SyntropyLog will inject tracing headers like `correlation-id`. */ headers?: Record<string, string | Buffer>; } /** * @interface MessageLifecycleControls * @description Defines the controls for handling a received message's lifecycle. * An instance of this is passed to the user's message handler, allowing them * to confirm or reject the message. */ interface MessageLifecycleControls { /** * Acknowledges that the message has been successfully processed. * This typically removes the message from the queue. * @returns {Promise<void>} */ ack: () => Promise<void>; /** * Negatively acknowledges the message, indicating a processing failure. * @param {boolean} [requeue=false] - If true, asks the broker to re-queue the message * for another attempt. If false (or omitted), the broker might move it to a dead-letter queue * or discard it, depending on its configuration. * @returns {Promise<void>} */ nack: (requeue?: boolean) => Promise<void>; } /** * @type MessageHandler * @description The signature for the user-provided function that will process incoming messages. * @param {BrokerMessage} message - The received message in the framework's standard format. * @param {MessageLifecycleControls} controls - The functions to manage the message's lifecycle (ack/nack). * @returns {Promise<void>} */ type MessageHandler = (message: BrokerMessage, controls: MessageLifecycleControls) => Promise<void>; /** * @interface IBrokerAdapter * @description The interface that every Broker Client Adapter must implement. * This is the "plug" where users will connect their specific messaging clients * (e.g., `amqplib`, `kafkajs`). */ interface IBrokerAdapter { /** * Establishes a connection to the message broker. * @returns {Promise<void>} */ connect(): Promise<void>; /** * Gracefully disconnects from the broker. */ /** * Gracefully disconnects from the message broker. * @returns {Promise<void>} */ disconnect(): Promise<void>; /** * Publishes a message to a specific topic or routing key. * @param {string} topic - The destination for the message (e.g., a topic name, queue name, or routing key). * @param {BrokerMessage} message - The message to be sent, in the framework's standard format. * @returns {Promise<void>} */ publish(topic: string, message: BrokerMessage): Promise<void>; /** * Subscribes to a topic or queue to receive messages. * @param {string} topic - The source of messages to listen to (e.g., a topic name or queue name). * @param {MessageHandler} handler - The user's function that will be called for each incoming message. * @returns {Promise<void>} */ subscribe(topic: string, handler: MessageHandler): Promise<void>; } /** * @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 message broker client instance. * It validates that a valid `IBrokerAdapter` is provided. * @private */ declare const brokerInstanceConfigSchema: z.ZodObject<{ instanceName: z.ZodString; adapter: z.ZodType<IBrokerAdapter, z.ZodTypeDef, IBrokerAdapter>; /** * An array of context keys to propagate as message headers/properties. * To propagate all keys, provide an array with a single wildcard: `['*']`. * If not provided, only `correlationId` and `transactionId` are propagated by default. */ propagate: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; /** * @deprecated Use `propagate` instead. * If true, propagates the entire asynchronous context map as headers. * If false (default), only propagates `correlationId` and `transactionId`. */ propagateFullContext: z.ZodOptional<z.ZodBoolean>; isDefault: z.ZodOptional<z.ZodBoolean>; }, "strip", z.ZodTypeAny, { instanceName: string; adapter: IBrokerAdapter; isDefault?: boolean | undefined; propagate?: string[] | undefined; propagateFullContext?: boolean | undefined; }, { instanceName: string; adapter: IBrokerAdapter; isDefault?: boolean | undefined; propagate?: string[] | undefined; propagateFullContext?: boolean | undefined; }>; /** * @description Schema for the main message broker configuration block. */ declare const brokerConfigSchema: z.ZodOptional<z.ZodObject<{ /** An array of broker client instance configurations. */ instances: z.ZodArray<z.ZodObject<{ instanceName: z.ZodString; adapter: z.ZodType<IBrokerAdapter, z.ZodTypeDef, IBrokerAdapter>; /** * An array of context keys to propagate as message headers/properties. * To propagate all keys, provide an array with a single wildcard: `['*']`. * If not provided, only `correlationId` and `transactionId` are propagated by default. */ propagate: z.ZodOptional<z.ZodArray<z.ZodString, "many">>; /** * @deprecated Use `propagate` instead. * If true, propagates the entire asynchronous context map as headers. * If false (default), only propagates `correlationId` and `transactionId`. */ propagateFullContext: z.ZodOptional<z.ZodBoolean>; isDefault: z.ZodOptional<z.ZodBoolean>; }, "strip", z.ZodTypeAny, { instanceName: string; adapter: IBrokerAdapter; isDefault?: boolean | undefined; propagate?: string[] | undefined; propagateFullContext?: boolean | undefined; }, { instanceName: string; adapter: IBrokerAdapter; isDefault?: boolean | undefined; propagate?: string[] | undefined; propagateFullContext?: boolean | undefined; }>, "many">; /** The name of the default broker instance to use when no name is provided to `getInstance()`. */ default: z.ZodOptional<z.ZodString>; }, "strip", z.ZodTypeAny, { instances: { instanceName: string; adapter: IBrokerAdapter; isDefault?: boolean | undefined; propagate?: string[] | undefined; propagateFullContext?: boolean | undefined; }[]; default?: string | undefined; }, { instances: { instanceName: string; adapter: IBrokerAdapter; isDefault?: boolean | undefined; propagate?: string[] | undefined; propagateFullContext?: boolean | undefined; }[]; default?: string | 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 message broker client instance. */ type BrokerInstanceConfig = z.infer<typeof brokerInstanceConfigSchema>; /** * @description The configuration type for the global message broker settings block. * `NonNullable` is used to ensure it's always an object, even if optional in the main config. */ type SyntropyBrokerConfig = NonNullable<z.infer<typeof brokerConfigSchema>>; /** * FILE: src/brokers/BrokerManager.ts * DESCRIPTION: * Manages the lifecycle and creation of multiple instrumented broker client instances, * following the same pattern as HttpManager and RedisManager. */ /** * @class BrokerManager * @description Manages the lifecycle and creation of multiple instrumented broker client instances. * It reads the configuration, creates an `InstrumentedBrokerClient` for each defined * instance, and provides a way to retrieve them and shut them down gracefully. */ declare class BrokerManager { private readonly instances; private defaultInstance?; private readonly logger; private readonly config; private readonly contextManager; constructor(config: SyntropyBrokerConfig, logger: ILogger, contextManager: IContextManager); init(): Promise<void>; getInstance(name?: string): InstrumentedBrokerClient; shutdown(): Promise<void>; } /** * @file src/brokers/InstrumentedBrokerClient.ts * @description The core instrumentation class. It wraps any `IBrokerAdapter` * implementation and adds logging and automatic context propagation for * distributed tracing. */ /** * @class InstrumentedBrokerClient * @description Wraps a user-provided broker adapter to automatically handle * logging, context propagation, and distributed tracing. */ declare class InstrumentedBrokerClient { private readonly adapter; private readonly logger; private readonly contextManager; private readonly config; readonly instanceName: string; /** * @constructor * @param {IBrokerAdapter} adapter - The concrete broker adapter implementation (e.g., for RabbitMQ, Kafka). * @param {ILogger} logger - The logger instance for this client. * @param {IContextManager} contextManager - The manager for handling asynchronous contexts. * @param {BrokerInstanceConfig} config - The configuration for this specific instance. */ constructor(adapter: IBrokerAdapter, logger: ILogger, contextManager: IContextManager, config: BrokerInstanceConfig); /** * Establishes a connection to the broker, wrapping the adapter's connect * method with logging. * @returns {Promise<void>} */ connect(): Promise<void>; /** * Disconnects from the broker, wrapping the adapter's disconnect method * with logging. * @returns {Promise<void>} */ disconnect(): Promise<void>; /** * Publishes a message, automatically injecting the current `correlation-id` * from the active context into the message headers. * @param {string} topic - The destination topic or routing key for the message. * @param {BrokerMessage} message - The message to be published. The `correlation-id` * will be added to its headers if not present. * @returns {Promise<void>} */ publish(topic: string, message: BrokerMessage): Promise<void>; /** * Subscribes to a topic. It wraps the user's message handler to automatically * create a new asynchronous context for each incoming message. If a `correlation-id` * is found in the message headers, it is used to initialize the new context. * @param {string} topic - The topic or queue to subscribe to. * @param {MessageHandler} handler - The user-provided function to process incoming messages. * @returns {Promise<void>} */ subscribe(topic: string, handler: MessageHandler): Promise<void>; } export { BrokerManager, InstrumentedBrokerClient }; export type { BrokerMessage, IBrokerAdapter, MessageHandler, MessageLifecycleControls };