/* This file is part of TALER (C) 2019 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * Check if we are running under nodejs. */ const isNode = typeof process !== "undefined" && typeof process.release !== "undefined" && process.release.name === "node"; export enum LogLevel { Trace = "trace", Message = "message", Info = "info", Warn = "warn", Error = "error", None = "none", } let globalLogLevel = LogLevel.Info; const byTagLogLevel: Record = {}; let nativeLogging: boolean = false; // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString Error.prototype.toString = function () { if ( this === null || (typeof this !== "object" && typeof this !== "function") ) { throw new TypeError(); } let name = this.name; name = name === undefined ? "Error" : `${name}`; let msg = this.message; msg = msg === undefined ? "" : `${msg}`; let cause = ""; if ("cause" in this) { cause = `\n Caused by: ${this.cause}`; } return `${name}: ${msg}${cause}`; }; export function getGlobalLogLevel(): string { return globalLogLevel; } export function setGlobalLogLevelFromString(logLevelStr: string): void { globalLogLevel = getLevelForString(logLevelStr); } export function setLogLevelFromString(tag: string, logLevelStr: string): void { byTagLogLevel[tag] = getLevelForString(logLevelStr); } export function enableNativeLogging() { nativeLogging = true; } function getLevelForString(logLevelStr: string): LogLevel { switch (logLevelStr.toLowerCase()) { case "trace": return LogLevel.Trace; case "info": return LogLevel.Info; case "warn": case "warning": return LogLevel.Warn; case "error": return LogLevel.Error; case "none": return LogLevel.None; default: if (isNode) { process.stderr.write(`Invalid log level, defaulting to WARNING\n`); } else { console.warn(`Invalid log level, defaulting to WARNING`); } return LogLevel.Warn; } } function writeNativeLog( message: any, tag: string, level: number, args: any[], ): void { const logFn = (globalThis as any).__nativeLog; if (logFn) { let m: string; if (args.length == 0) { m = message; } else { m = message + " " + args.toString(); } logFn(level, tag, message); } } function writeNodeLog( message: any, tag: string, level: string, args: any[], ): void { try { let msg = `${new Date().toISOString()} ${tag} ${level} ${message}`; if (args.length != 0) { msg += ` ${JSON.stringify(args, undefined, 2)}\n`; } else { msg += `\n`; } process.stderr.write(msg); } catch (e) { // This can happen when we're trying to log something that doesn't want to be // converted to a string. let msg = `${new Date().toISOString()} (logger) FATAL `; if (e instanceof Error) { msg += `failed to write log: ${e.message}\n`; } else { msg += "failed to write log\n"; } process.stderr.write(msg); } } /** * Logger that writes to stderr when running under node, * and uses the corresponding console.* method to log in the browser. */ export class Logger { constructor(private tag: string) {} shouldLogTrace(): boolean { const level = byTagLogLevel[this.tag] ?? globalLogLevel; switch (level) { case LogLevel.Trace: return true; case LogLevel.Message: case LogLevel.Info: case LogLevel.Warn: case LogLevel.Error: case LogLevel.None: return false; } } shouldLogInfo(): boolean { const level = byTagLogLevel[this.tag] ?? globalLogLevel; switch (level) { case LogLevel.Trace: case LogLevel.Message: case LogLevel.Info: return true; case LogLevel.Warn: case LogLevel.Error: case LogLevel.None: return false; } } shouldLogWarn(): boolean { const level = byTagLogLevel[this.tag] ?? globalLogLevel; switch (level) { case LogLevel.Trace: case LogLevel.Message: case LogLevel.Info: case LogLevel.Warn: return true; case LogLevel.Error: case LogLevel.None: return false; } } shouldLogError(): boolean { const level = byTagLogLevel[this.tag] ?? globalLogLevel; switch (level) { case LogLevel.Trace: case LogLevel.Message: case LogLevel.Info: case LogLevel.Warn: case LogLevel.Error: return true; case LogLevel.None: return false; } } info(message: string, ...args: any[]): void { if (!this.shouldLogInfo()) { return; } if (nativeLogging) { writeNativeLog(message, this.tag, 2, args); return; } if (isNode) { writeNodeLog(message, this.tag, "INFO", args); } else { console.info( `${new Date().toISOString()} ${this.tag} INFO ` + message, ...args, ); } } warn(message: string, ...args: any[]): void { if (!this.shouldLogWarn()) { return; } if (nativeLogging) { writeNativeLog(message, this.tag, 3, args); return; } if (isNode) { writeNodeLog(message, this.tag, "WARN", args); } else { console.warn( `${new Date().toISOString()} ${this.tag} INFO ` + message, ...args, ); } } error(message: string, ...args: any[]): void { if (!this.shouldLogError()) { return; } if (nativeLogging) { writeNativeLog(message, this.tag, 4, args); return; } if (isNode) { writeNodeLog(message, this.tag, "ERROR", args); } else { console.info( `${new Date().toISOString()} ${this.tag} ERROR ` + message, ...args, ); } } trace(message: string, ...args: any[]): void { if (!this.shouldLogTrace()) { return; } if (nativeLogging) { writeNativeLog(message, this.tag, 1, args); return; } if (isNode) { writeNodeLog(message, this.tag, "TRACE", args); } else { console.info( `${new Date().toISOString()} ${this.tag} TRACE ` + message, ...args, ); } } reportBreak(): void { if (!this.shouldLogError()) { return; } const location = new Error("programming error"); this.error(`assertion failed: ${location.stack}`); } }