syntropylog
Version:
An instance manager with observability for Node.js applications
539 lines (524 loc) • 21.5 kB
TypeScript
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 };