UNPKG

workers-loki-logger

Version:

A Logger for the Cloudflare workers environment that sends the logs to grafana loki

128 lines 4.29 kB
import { formatErrorToString } from './error-formatter.js'; import { isNotNullOrUndefined } from './lib.js'; export class Logger { messages = []; mdcString = null; getTimeNanoSecondsCallCount = 0; mdc; stream; lokiSecret; lokiUrl; fetch; cloudflareContext; loggerReceiver; _getTimeNanoSeconds; constructor(loggerConfig) { this.stream = loggerConfig.stream; this.lokiSecret = loggerConfig.lokiSecret; this.mdc = new Map(Object.entries(loggerConfig.mdc ?? {})); this.lokiUrl = loggerConfig.lokiUrl ?? 'https://logs-prod-eu-west-0.grafana.net'; this.fetch = loggerConfig.fetch ?? ((input, init) => fetch(input, init)); this.cloudflareContext = loggerConfig.cloudflareContext ?? {}; this.loggerReceiver = loggerConfig.logReceiver ?? console; this._getTimeNanoSeconds = loggerConfig.getTimeNanoSeconds ?? ((count) => Date.now() * 1000000 + count); } getTimeNanoSeconds() { return this._getTimeNanoSeconds(this.getTimeNanoSecondsCallCount++); } mdcSet(key, value) { this.mdcString = null; this.mdc.set(key, value); } mdcDelete(key) { this.mdcString = null; this.mdc.delete(key); } mdcGet(key) { return this.mdc.get(key); } async flush() { if (this.messages.length === 0) { this.loggerReceiver.debug('logger has no messages to flush'); return; } const mdcString = this.mdcFormatString(); const request = { streams: [ { stream: this.stream, values: this.messages.map((messageEntry) => [ messageEntry.time.toString(), mdcString + 'level=' + messageEntry.level + ' ' + messageEntry.message, ]), }, ], }; const saveLogsPromise = this.fetch(`${this.lokiUrl}/loki/api/v1/push`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Basic ${this.lokiSecret}`, }, body: JSON.stringify(request), }); this.messages = []; if (isCloudflareContext(this.cloudflareContext)) { await this.cloudflareContext.waitUntil(saveLogsPromise); } else { await saveLogsPromise; } } info(message) { this.messages.push({ time: this.getTimeNanoSeconds(), message, level: 'info', }); this.loggerReceiver.info(this.mdcFormatString() + message); } error(message, error) { if (isNotNullOrUndefined(error)) { message += ' ' + formatErrorToString(error); } this.messages.push({ time: this.getTimeNanoSeconds(), message, level: 'error', }); this.loggerReceiver.error(this.mdcFormatString() + message, error); } fatal(message, error) { if (isNotNullOrUndefined(error)) { message += ' ' + formatErrorToString(error); } this.messages.push({ time: this.getTimeNanoSeconds(), message, level: 'fatal', }); this.loggerReceiver.error(this.mdcFormatString() + message, error); } warn(message, error) { if (isNotNullOrUndefined(error)) { message += ' ' + formatErrorToString(error); } this.messages.push({ time: this.getTimeNanoSeconds(), message, level: 'warn', }); this.loggerReceiver.warn(this.mdcFormatString() + message, error); } mdcFormatString() { if (isNotNullOrUndefined(this.mdcString)) { return this.mdcString; } let newMdcString = ''; for (const entry of this.mdc.entries()) { newMdcString += entry[0] + '=' + entry[1] + ' '; } this.mdcString = newMdcString; return this.mdcString; } } function isCloudflareContext(context) { return isNotNullOrUndefined(context) && Object.getPrototypeOf(context).hasOwnProperty('waitUntil'); } //# sourceMappingURL=logger.js.map