/*
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}`);
}
}