UNPKG

@flarelabs-net/workers-observability-utils

Version:

A collection of Utilities for Capturing Logs and Metrics from Cloudflare Workers

81 lines (80 loc) 3.12 kB
import { TraceItemDb } from "./traceItem"; export class LogsTail { #logSinks; #maxBufferSize; #maxBufferDuration; #flushId = 0; #traceItems = new TraceItemDb(); #flushScheduled = false; constructor(options) { this.#logSinks = options.sinks; this.#maxBufferSize = options.maxBufferSize || 25; this.#maxBufferDuration = Math.min(options.maxBufferDuration || 5, 30); } processTraceItems(traceItems, ctx) { // Store all trace items in the buffer this.#traceItems.storeTraceItems(traceItems); // Check if we need to flush immediately due to buffer size if (this.#traceItems.getTraceItems().length >= this.#maxBufferSize) { if (this.#flushScheduled) { this.#flushScheduled = false; } this.#flushId++; // Flush immediately ctx.waitUntil(this.#performFlush()); return; } if (this.#flushScheduled) { return; } // Only schedule flush if there are trace items to flush if (this.#traceItems.getTraceItems().length > 0) { this.#flushScheduled = true; const scheduleFlush = async () => { try { const localFlushId = ++this.#flushId; await scheduler.wait(this.#maxBufferDuration * 1000); if (localFlushId === this.#flushId) { await this.#performFlush(); } } catch (error) { } }; ctx.waitUntil(scheduleFlush()); } } async #performFlush() { const items = this.#traceItems.getTraceItems(); // Reset batch and flush state this.#flushScheduled = false; this.#traceItems.clear(); // Skip if no items to flush if (items.length === 0) { return; } try { const results = await Promise.allSettled(this.#logSinks.map((sink) => sink.sendLogs(items))); const successfulSinks = results.filter((el) => el.status === "fulfilled"); if (successfulSinks.length > 0) { console.debug(`Flushed ${items.length} logs to ${successfulSinks.length} sink(s) successfully.`, { logs: items.length, sinks: successfulSinks.length, }); } const errors = results.filter((el) => el.status === "rejected"); if (errors.length > 0) { const sinkErrors = errors.map((error) => { return `${error.reason instanceof Error ? error.reason.message : String(error.reason)}`; }); console.error(`Failed to flush logs to ${errors.length} sink(s): ${sinkErrors.join(', ')}`, { logs: items.length, sinks: errors.length, errors: sinkErrors, }); } } catch (error) { console.error("Error flushing logs batch:", error); } } }