UNPKG

@logtail/core

Version:
337 lines 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const types_1 = require("@logtail/types"); const tools_1 = require("@logtail/tools"); const serialize_error_1 = require("serialize-error"); // Set default options for Logtail const defaultOptions = { // Default sync endpoint (protocol + domain) endpoint: "https://in.logs.betterstack.com", // Maximum number of logs to sync in a single request to Better Stack batchSize: 1000, // Size of logs (in KiB) to trigger sync to Better Stack (0 to disable) batchSizeKiB: 0, // Max interval (in milliseconds) before a batch of logs proceeds to syncing batchInterval: 1000, // Maximum number of times to retry a failed sync request retryCount: 3, // Minimum number of milliseconds to wait before retrying a failed sync request retryBackoff: 100, // Maximum number of sync requests to make concurrently syncMax: 5, // Length of the checked window for logs burst protection in milliseconds (0 to disable) burstProtectionMilliseconds: 5000, // Maximum number of accepted logs in the specified time window (0 to disable) burstProtectionMax: 10000, // If true, errors when sending logs will be ignored // Has precedence over throwExceptions ignoreExceptions: false, // If true, errors when sending logs will result in a thrown exception throwExceptions: false, // Maximum depth (number of attribute levels) of a context object contextObjectMaxDepth: 50, // Produce a warn log when context object max depth is reached contextObjectMaxDepthWarn: true, // Produce a warning when circular reference is found in context object contextObjectCircularRefWarn: true, // If true, all logs will be sent to standard console output functions (console.info, console.warn, ...) sendLogsToConsoleOutput: false, // If true, all logs will be sent to Better Stack sendLogsToBetterStack: true, // Function to be used to calculate size of logs in bytes (to evaluate batchSizeLimitKiB) calculateLogSizeBytes: tools_1.calculateJsonLogSizeBytes, }; /** * Logtail core class for logging to the Better Stack service */ class Logtail { /* CONSTRUCTOR */ /** * Initializes a new Logtail instance * * @param sourceToken: string - Private source token for logging to Better Stack * @param options?: ILogtailOptions - Optionally specify Logtail options */ constructor(sourceToken, options) { // Middleware this._middleware = []; // Number of logs logged this._countLogged = 0; // Number of logs successfully synced with Logtail this._countSynced = 0; // Number of logs that failed to be synced to Logtail this._countDropped = 0; // First, check we have a valid source token if (typeof sourceToken !== "string" || sourceToken === "") { throw new Error("Logtail source token missing"); } // Store the source token, to use for syncing with Better Stack this._sourceToken = sourceToken; // Merge default and user options this._options = Object.assign(Object.assign({}, defaultOptions), options); // Create a throttler, for sync operations const throttle = (0, tools_1.makeThrottle)(this._options.syncMax); // Sync after throttling const throttler = throttle((logs) => { return this._sync(logs); }); // Burst protection for logging this._logBurstProtection = (0, tools_1.makeBurstProtection)(this._options.burstProtectionMilliseconds, this._options.burstProtectionMax, "Logging"); this.log = this._logBurstProtection(this.log.bind(this)); // Create a batcher, for aggregating logs by buffer size/interval const batcher = (0, tools_1.makeBatch)(this._options.batchSize, this._options.batchInterval, this._options.retryCount, this._options.retryBackoff, this._options.batchSizeKiB * 1024, this._options.calculateLogSizeBytes); this._batch = batcher.initPusher((logs) => { return throttler(logs); }); this._flush = batcher.flush; } /* PUBLIC METHODS */ /** * Flush batched logs to Logtail */ async flush() { return this._flush(); } /** * Number of entries logged * * @returns number */ get logged() { return this._countLogged; } /** * Number of log entries synced with Better Stack * * @returns number */ get synced() { return this._countSynced; } /** * Number of entries dropped * * @returns number */ get dropped() { return this._countDropped; } /** * Log an entry, to be synced with Better Stack * * @param message: string - Log message * @param level (LogLevel) - Level to log at (debug|info|warn|error) * @param context: (Context) - Context (optional) * @returns Promise<ILogtailLog> after syncing */ async log(message, level = types_1.LogLevel.Info, context = {}) { // Wrap context in an object, if it's not already if (typeof context !== "object") { const wrappedContext = { extra: context }; context = wrappedContext; } if (context instanceof Error) { const wrappedContext = { error: context }; context = wrappedContext; } if (this._options.sendLogsToConsoleOutput) { switch (level) { case "debug": console.debug(message, context); break; case "info": console.info(message, context); break; case "warn": console.warn(message, context); break; case "error": console.error(message, context); break; default: console.log(`[${level.toUpperCase()}]`, message, context); break; } } // Check that we have a sync function if (typeof this._sync !== "function") { throw new Error("No Logtail logger sync function provided"); } // Increment log count this._countLogged++; // Start building the log message let log = Object.assign(Object.assign({ // Implicit date timestamp dt: new Date(), // Explicit level level }, context), (message instanceof Error ? (0, serialize_error_1.serializeError)(message) : { message })); let transformedLog = log; for (const middleware of this._middleware) { let newTransformedLog = await middleware(transformedLog); if (newTransformedLog == null) { // Don't push the log if it was filtered out in a middleware return transformedLog; } transformedLog = newTransformedLog; } // Manually serialize the log data transformedLog = this.serialize(transformedLog, this._options.contextObjectMaxDepth); if (!this._options.sendLogsToBetterStack) { // Return the resulting log before sending it return transformedLog; } try { // Push the log through the batcher, and sync await this._batch(transformedLog); // Increment sync count this._countSynced++; } catch (e) { // Increment dropped count this._countDropped++; // Catch any errors - re-throw if `ignoreExceptions` == false if (!this._options.ignoreExceptions) { if (this._options.throwExceptions) { throw e; } else { // Output to console console.error(e); } } } // Return the resulting log return transformedLog; } serialize(value, maxDepth, visitedObjects = new WeakSet()) { if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") { return value; } else if (value instanceof Date) { // Date instances can be invalid & toISOString() will fail if (isNaN(value.getTime())) { return value.toString(); } return value.toISOString(); } else if (value instanceof Error) { return (0, serialize_error_1.serializeError)(value); } else if ((typeof value === "object" || Array.isArray(value)) && (maxDepth < 1 || visitedObjects.has(value))) { if (visitedObjects.has(value)) { if (this._options.contextObjectCircularRefWarn) { console.warn(`[Logtail] Found a circular reference when serializing logs. Please do not use circular references in your logs.`); } return "<omitted circular reference>"; } if (this._options.contextObjectMaxDepthWarn) { console.warn(`[Logtail] Max depth of ${this._options.contextObjectMaxDepth} reached when serializing logs. Please do not use excessive object depth in your logs.`); } return `<omitted context beyond configured max depth: ${this._options.contextObjectMaxDepth}>`; } else if (Array.isArray(value)) { visitedObjects.add(value); const serializedArray = value.map((item) => this.serialize(item, maxDepth - 1, visitedObjects)); visitedObjects.delete(value); return serializedArray; } else if (typeof value === "object") { const serializedObject = {}; visitedObjects.add(value); Object.entries(value).forEach((item) => { const key = item[0]; const value = item[1]; const serializedValue = this.serialize(value, maxDepth - 1, visitedObjects); if (serializedValue !== undefined) { serializedObject[key] = serializedValue; } }); visitedObjects.delete(value); return serializedObject; } else if (typeof value === "undefined") { return undefined; } else { return `<omitted unserializable ${typeof value}>`; } } /** * * Debug level log, to be synced with Better Stack * * @param message: string - Log message * @param context: (Pick<ILogtailLog, "context">) - Context (optional) * @returns Promise<ILogtailLog> after syncing */ async debug(message, context = {}) { return this.log(message, types_1.LogLevel.Debug, context); } /** * * Info level log, to be synced with Better Stack * * @param message: string - Log message * @param context: (Pick<ILogtailLog, "context">) - Context (optional) * @returns Promise<ILogtailLog> after syncing */ async info(message, context = {}) { return this.log(message, types_1.LogLevel.Info, context); } /** * * Warning level log, to be synced with Better Stack * * @param message: string - Log message * @param context: (Pick<ILogtailLog, "context">) - Context (optional) * @returns Promise<ILogtailLog> after syncing */ async warn(message, context = {}) { return this.log(message, types_1.LogLevel.Warn, context); } /** * * Warning level log, to be synced with Better Stack * * @param message: string - Log message * @param context: (Pick<ILogtailLog, "context">) - Context (optional) * @returns Promise<ILogtailLog> after syncing */ async error(message, context = {}) { return this.log(message, types_1.LogLevel.Error, context); } /** * Sets the sync method - i.e. the final step in the pipeline to get logs * over to Better Stack * * @param fn - Pipeline function to use as sync method */ setSync(fn) { this._sync = fn; } /** * Add a middleware function to the logging pipeline * * @param fn - Function to add to the log pipeline * @returns void */ use(fn) { this._middleware.push(fn); } /** * Remove a function from the pipeline * * @param fn - Pipeline function * @returns void */ remove(fn) { this._middleware = this._middleware.filter((p) => p !== fn); } } class default_1 extends Logtail { async log(message, level = types_1.LogLevel.Info, context = {}) { return super.log(message, level, context); } } exports.default = default_1; //# sourceMappingURL=base.js.map