UNPKG

@graphql-hive/logger

Version:
495 lines (481 loc) 12.9 kB
'use strict'; var json = require('./json-bRIaD3jo.cjs'); function createDeferredPromise() { if (Promise.withResolvers) { return Promise.withResolvers(); } let resolveFn; let rejectFn; const promise = new Promise(function deferredPromiseExecutor(resolve, reject) { resolveFn = resolve; rejectFn = reject; }); return { promise, get resolve() { return resolveFn; }, get reject() { return rejectFn; }, }; } const DisposableSymbols = { get asyncDispose() { return Symbol.asyncDispose || Symbol.for('asyncDispose'); }, }; function tryStringify (o) { try { return JSON.stringify(o) } catch(e) { return '"[Circular]"' } } var quickFormatUnescaped = format; function format(f, args, opts) { var ss = (opts && opts.stringify) || tryStringify; var offset = 1; if (typeof f === 'object' && f !== null) { var len = args.length + offset; if (len === 1) return f var objects = new Array(len); objects[0] = ss(f); for (var index = 1; index < len; index++) { objects[index] = ss(args[index]); } return objects.join(' ') } if (typeof f !== 'string') { return f } var argLen = args.length; if (argLen === 0) return f var str = ''; var a = 1 - offset; var lastPos = -1; var flen = (f && f.length) || 0; for (var i = 0; i < flen;) { if (f.charCodeAt(i) === 37 && i + 1 < flen) { lastPos = lastPos > -1 ? lastPos : 0; switch (f.charCodeAt(i + 1)) { case 100: // 'd' case 102: // 'f' if (a >= argLen) break if (args[a] == null) break if (lastPos < i) str += f.slice(lastPos, i); str += Number(args[a]); lastPos = i + 2; i++; break case 105: // 'i' if (a >= argLen) break if (args[a] == null) break if (lastPos < i) str += f.slice(lastPos, i); str += Math.floor(Number(args[a])); lastPos = i + 2; i++; break case 79: // 'O' case 111: // 'o' case 106: // 'j' if (a >= argLen) break if (args[a] === undefined) break if (lastPos < i) str += f.slice(lastPos, i); var type = typeof args[a]; if (type === 'string') { str += '\'' + args[a] + '\''; lastPos = i + 2; i++; break } if (type === 'function') { str += args[a].name || '<anonymous>'; lastPos = i + 2; i++; break } str += ss(args[a]); lastPos = i + 2; i++; break case 115: // 's' if (a >= argLen) break if (lastPos < i) str += f.slice(lastPos, i); str += String(args[a]); lastPos = i + 2; i++; break case 37: // '%' if (lastPos < i) str += f.slice(lastPos, i); str += '%'; lastPos = i + 2; i++; a--; break } ++a; } ++i; } if (lastPos === -1) return f else if (lastPos < flen) { str += f.slice(lastPos); } return str } var format$1 = /*@__PURE__*/json.getDefaultExportFromCjs(quickFormatUnescaped); const asciMap = { timestamp: "\x1B[90m", // bright black trace: "\x1B[36m", // cyan debug: "\x1B[90m", // bright black info: "\x1B[32m", // green warn: "\x1B[33m", // yellow error: "\x1B[41;39m", // red; white message: "\x1B[1m", // bold key: "\x1B[35m", // magenta reset: "\x1B[0m" // reset }; class ConsoleLogWriter { #console; #noColor; #noTimestamp; #async; constructor(opts = {}) { const { console = globalThis.console, // no color if we're running in browser-like (edge) environments noColor = typeof process === "undefined" || // or no color if https://no-color.org/ json.truthyEnv("NO_COLOR"), noTimestamp = false, async = false } = opts; this.#console = console; this.#noColor = noColor; this.#noTimestamp = noTimestamp; this.#async = async; } color(style, text) { if (!text) { return text; } if (this.#noColor) { return text; } return asciMap[style] + text + asciMap.reset; } #writeToConsole(level, attrs, msg) { this.#console[level === "trace" ? "debug" : level]( [ !this.#noTimestamp && this.color("timestamp", (/* @__PURE__ */ new Date()).toISOString()), this.color(level, json.logLevelToString(level)), this.color("message", msg), attrs && this.stringifyAttrs(attrs) ].filter(Boolean).join(" ") ); } write(level, attrs, msg) { if (this.#async) { const { promise, resolve } = createDeferredPromise(); setTimeout(() => { this.#writeToConsole(level, attrs, msg); resolve(); }, 0); return promise; } this.#writeToConsole(level, attrs, msg); } stringifyAttrs(attrs) { let log = "\n"; for (const line of json.jsonStringify(attrs, true).split("\n")) { if (line === "{" || line === "}" || line === "[" || line === "]") { continue; } let formattedLine = line; formattedLine = formattedLine.replace( /"([^"]+)":/, this.color("key", "$1:") ); let indentationSize = line.match(/^\s*/)?.[0]?.length || 0; if (indentationSize) indentationSize++; formattedLine = formattedLine.replaceAll( /\\n/g, "\n" + [...Array(indentationSize)].join(" ") ); formattedLine = formattedLine.replace(/,$/, ""); formattedLine = formattedLine.replace( /(\[|\{|\]|\})$/, this.color("key", "$1") ); log += formattedLine + "\n"; } log = log.slice(0, -1); return log; } } class MemoryLogWriter { logs = []; write(level, attrs, msg) { this.logs.push({ level, ...msg ? { msg } : {}, ...attrs ? { attrs } : {} }); } } class Logger { #level; #prefix; #attrs; #writers; #pendingWrites; constructor(opts = {}) { let logLevelEnv = json.getEnv("LOG_LEVEL"); if (logLevelEnv && !(logLevelEnv in json.logLevel)) { throw new Error( `Invalid LOG_LEVEL environment variable "${logLevelEnv}". Must be one of: ${[...Object.keys(json.logLevel), "false"].join(", ")}` ); } this.#level = opts.level ?? logLevelEnv ?? (json.truthyEnv("DEBUG") ? "debug" : "info"); this.#prefix = opts.prefix; this.#attrs = opts.attrs; this.#writers = opts.writers ?? (json.truthyEnv("LOG_JSON") ? [new json.JSONLogWriter()] : [new ConsoleLogWriter()]); } /** The prefix that's prepended to each log message. */ get prefix() { return this.#prefix; } /** * The attributes that are added to each log. If the log itself contains * attributes with keys existing in {@link attrs}, the log's attributes will * override. */ get attrs() { return this.#attrs; } /** The current {@link LogLevel} of the logger. You can change the level using the {@link setLevel} method. */ get level() { return typeof this.#level === "function" ? this.#level() : this.#level; } /** * Sets the new {@link LogLevel} of the logger. All subsequent logs, and {@link child child loggers} whose * level did not change, will respect the new level. */ setLevel(level) { this.#level = level; } write(level, attrs, msg) { for (const w of this.#writers) { const write$ = w.write(level, attrs, msg); if (json.isPromise(write$)) { this.#pendingWrites ??= /* @__PURE__ */ new Set(); this.#pendingWrites.add(write$); write$.then(() => { this.#pendingWrites.delete(write$); }).catch((e) => { console.error("Failed to write async log", e); }); } } } flush() { const writerFlushes = this.#writers.map((w) => w.flush).filter((f) => !!f); if (this.#pendingWrites?.size || writerFlushes.length) { const errs = []; return Promise.allSettled([ ...Array.from(this.#pendingWrites || []).map( (w) => w.catch((err) => errs.push(err)) ), ...Array.from(writerFlushes || []).map(async (f) => { try { await f(); } catch (err) { errs.push(err); } }) ]).then(() => { this.#pendingWrites?.clear(); if (errs.length === 1) { throw new Error("Failed to flush", { cause: errs[0] }); } else if (errs.length) { throw new AggregateError( errs, `Failed to flush with ${errs.length} errors` ); } }); } return; } async [DisposableSymbols.asyncDispose]() { return this.flush(); } child(prefixOrAttrs, prefix) { if (typeof prefixOrAttrs === "string") { return new Logger({ level: () => this.level, // inherits the parent level (yet can be changed on child only when using setLevel) prefix: (this.#prefix || "") + prefixOrAttrs, attrs: this.#attrs, writers: this.#writers }); } return new Logger({ level: () => this.level, // inherits the parent level (yet can be changed on child only when using setLevel) prefix: (this.#prefix || "") + (prefix || "") || void 0, attrs: json.shallowMergeAttributes(this.#attrs, prefixOrAttrs), writers: this.#writers }); } log(level, maybeAttrsOrMsg, ...rest) { if (!json.shouldLog(this.#level, level)) { return; } let msg; let attrs; if (typeof maybeAttrsOrMsg === "string") { msg = maybeAttrsOrMsg; } else if (maybeAttrsOrMsg) { attrs = maybeAttrsOrMsg; if (typeof rest[0] === "string") { msg = rest.shift(); } } if (this.#prefix) { msg = `${this.#prefix}${msg || ""}`.trim(); } attrs = json.shallowMergeAttributes(json.parseAttrs(this.#attrs), json.parseAttrs(attrs)); msg = msg && rest.length ? format$1(msg, rest, { stringify: json.fastSafeStringify }) : msg; this.write(level, attrs, msg); if (json.truthyEnv("LOG_TRACE_LOGS")) { console.trace("\u{1F446}"); } } trace(...args) { this.log( "trace", ...args ); } debug(...args) { this.log( "debug", ...args ); } info(...args) { this.log( "info", ...args ); } warn(...args) { this.log( "warn", ...args ); } error(...args) { this.log( "error", ...args ); } } class LegacyLogger { #logger; constructor(logger) { this.#logger = logger; } static from(logger) { return new LegacyLogger(logger); } #log(level, ...[maybeMsgOrArg, ...restArgs]) { if (typeof maybeMsgOrArg === "string") { if (restArgs.length) { this.#logger.log(level, restArgs, maybeMsgOrArg); } else { this.#logger.log(level, maybeMsgOrArg); } } else { if (restArgs.length) { this.#logger.log(level, [maybeMsgOrArg, ...restArgs]); } else { this.#logger.log(level, maybeMsgOrArg); } } } log(...args) { this.#log("info", ...args); } warn(...args) { this.#log("warn", ...args); } info(...args) { this.#log("info", ...args); } error(...args) { this.#log("error", ...args); } debug(...lazyArgs) { if (!json.shouldLog(this.#logger.level, "debug")) { return; } this.#log("debug", ...handleLazyMessage(lazyArgs)); } child(name) { name = stringifyName(name) + // append space if object is strigified to space out the prefix (typeof name === "object" ? " " : ""); if (this.#logger.prefix === name) { return this; } return LegacyLogger.from(this.#logger.child(name)); } addPrefix(prefix) { prefix = stringifyName(prefix); if (this.#logger.prefix?.includes(prefix)) { return this; } return LegacyLogger.from(this.#logger.child(prefix)); } } function stringifyName(name) { if (typeof name === "string" || typeof name === "number") { return `${name}`; } const names = []; for (const [key, value] of Object.entries(name)) { names.push(`${key}=${value}`); } return `${names.join(", ")}`; } function handleLazyMessage(lazyArgs) { return lazyArgs.flat(Infinity).flatMap((arg) => { if (typeof arg === "function") { return arg(); } return arg; }); } exports.JSONLogWriter = json.JSONLogWriter; exports.jsonStringify = json.jsonStringify; exports.ConsoleLogWriter = ConsoleLogWriter; exports.LegacyLogger = LegacyLogger; exports.Logger = Logger; exports.MemoryLogWriter = MemoryLogWriter;