@graphql-hive/logger
Version:
495 lines (481 loc) • 12.9 kB
JavaScript
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;
;