ethers.js/packages/logger/src.ts/index.ts

330 lines
9.8 KiB
TypeScript

"use strict";
let _permanentCensorErrors = false;
let _censorErrors = false;
const LogLevels: { [ name: string ]: number } = { debug: 1, "default": 2, info: 2, warning: 3, error: 4, off: 5 };
let _logLevel = LogLevels["default"];
import { version } from "./_version";
let _globalLogger: Logger = null;
function _checkNormalize(): string {
try {
const missing: Array<string> = [ ];
// Make sure all forms of normalization are supported
["NFD", "NFC", "NFKD", "NFKC"].forEach((form) => {
try {
if ("test".normalize(form) !== "test") {
throw new Error("bad normalize");
};
} catch(error) {
missing.push(form);
}
});
if (missing.length) {
throw new Error("missing " + missing.join(", "));
}
if (String.fromCharCode(0xe9).normalize("NFD") !== String.fromCharCode(0x65, 0x0301)) {
throw new Error("broken implementation")
}
} catch (error) {
return error.message;
}
return null;
}
const _normalizeError = _checkNormalize();
export enum LogLevel {
DEBUG = "DEBUG",
INFO = "INFO",
WARNING = "WARNING",
ERROR = "ERROR",
OFF = "OFF"
}
export enum ErrorCode {
///////////////////
// Generic Errors
// Unknown Error
UNKNOWN_ERROR = "UNKNOWN_ERROR",
// Not Implemented
NOT_IMPLEMENTED = "NOT_IMPLEMENTED",
// Unsupported Operation
// - operation
UNSUPPORTED_OPERATION = "UNSUPPORTED_OPERATION",
// Network Error (i.e. Ethereum Network, such as an invalid chain ID)
// - event ("noNetwork" is not re-thrown in provider.ready; otherwise thrown)
NETWORK_ERROR = "NETWORK_ERROR",
// Some sort of bad response from the server
SERVER_ERROR = "SERVER_ERROR",
// Timeout
TIMEOUT = "TIMEOUT",
///////////////////
// Operational Errors
// Buffer Overrun
BUFFER_OVERRUN = "BUFFER_OVERRUN",
// Numeric Fault
// - operation: the operation being executed
// - fault: the reason this faulted
NUMERIC_FAULT = "NUMERIC_FAULT",
///////////////////
// Argument Errors
// Missing new operator to an object
// - name: The name of the class
MISSING_NEW = "MISSING_NEW",
// Invalid argument (e.g. value is incompatible with type) to a function:
// - argument: The argument name that was invalid
// - value: The value of the argument
INVALID_ARGUMENT = "INVALID_ARGUMENT",
// Missing argument to a function:
// - count: The number of arguments received
// - expectedCount: The number of arguments expected
MISSING_ARGUMENT = "MISSING_ARGUMENT",
// Too many arguments
// - count: The number of arguments received
// - expectedCount: The number of arguments expected
UNEXPECTED_ARGUMENT = "UNEXPECTED_ARGUMENT",
///////////////////
// Blockchain Errors
// Call exception
// - transaction: the transaction
// - address?: the contract address
// - args?: The arguments passed into the function
// - method?: The Solidity method signature
// - errorSignature?: The EIP848 error signature
// - errorArgs?: The EIP848 error parameters
// - reason: The reason (only for EIP848 "Error(string)")
CALL_EXCEPTION = "CALL_EXCEPTION",
// Insufficien funds (< value + gasLimit * gasPrice)
// - transaction: the transaction attempted
INSUFFICIENT_FUNDS = "INSUFFICIENT_FUNDS",
// Nonce has already been used
// - transaction: the transaction attempted
NONCE_EXPIRED = "NONCE_EXPIRED",
// The replacement fee for the transaction is too low
// - transaction: the transaction attempted
REPLACEMENT_UNDERPRICED = "REPLACEMENT_UNDERPRICED",
// The gas limit could not be estimated
// - transaction: the transaction passed to estimateGas
UNPREDICTABLE_GAS_LIMIT = "UNPREDICTABLE_GAS_LIMIT",
};
export class Logger {
readonly version: string;
static errors = ErrorCode;
static levels = LogLevel;
constructor(version: string) {
Object.defineProperty(this, "version", {
enumerable: true,
value: version,
writable: false
});
}
_log(logLevel: LogLevel, args: Array<any>): void {
const level = logLevel.toLowerCase();
if (LogLevels[level] == null) {
this.throwArgumentError("invalid log level name", "logLevel", logLevel);
}
if (_logLevel > LogLevels[level]) { return; }
console.log.apply(console, args);
}
debug(...args: Array<any>): void {
this._log(Logger.levels.DEBUG, args);
}
info(...args: Array<any>): void {
this._log(Logger.levels.INFO, args);
}
warn(...args: Array<any>): void {
this._log(Logger.levels.WARNING, args);
}
makeError(message: string, code?: ErrorCode, params?: any): Error {
// Errors are being censored
if (_censorErrors) {
return this.makeError("censored error", code, { });
}
if (!code) { code = Logger.errors.UNKNOWN_ERROR; }
if (!params) { params = {}; }
const messageDetails: Array<string> = [];
Object.keys(params).forEach((key) => {
try {
messageDetails.push(key + "=" + JSON.stringify(params[key]));
} catch (error) {
messageDetails.push(key + "=" + JSON.stringify(params[key].toString()));
}
});
messageDetails.push(`code=${ code }`);
messageDetails.push(`version=${ this.version }`);
const reason = message;
if (messageDetails.length) {
message += " (" + messageDetails.join(", ") + ")";
}
// @TODO: Any??
const error: any = new Error(message);
error.reason = reason;
error.code = code
Object.keys(params).forEach(function(key) {
error[key] = params[key];
});
return error;
}
throwError(message: string, code?: ErrorCode, params?: any): never {
throw this.makeError(message, code, params);
}
throwArgumentError(message: string, name: string, value: any): never {
return this.throwError(message, Logger.errors.INVALID_ARGUMENT, {
argument: name,
value: value
});
}
checkNormalize(message?: string): void {
if (message == null) { message = "platform missing String.prototype.normalize"; }
if (_normalizeError) {
this.throwError("platform missing String.prototype.normalize", Logger.errors.UNSUPPORTED_OPERATION, {
operation: "String.prototype.normalize", form: _normalizeError
});
}
}
checkSafeUint53(value: number, message?: string): void {
if (typeof(value) !== "number") { return; }
if (message == null) { message = "value not safe"; }
if (value < 0 || value >= 0x1fffffffffffff) {
this.throwError(message, Logger.errors.NUMERIC_FAULT, {
operation: "checkSafeInteger",
fault: "out-of-safe-range",
value: value
});
}
if (value % 1) {
this.throwError(message, Logger.errors.NUMERIC_FAULT, {
operation: "checkSafeInteger",
fault: "non-integer",
value: value
});
}
}
checkArgumentCount(count: number, expectedCount: number, message?: string): void {
if (message) {
message = ": " + message;
} else {
message = "";
}
if (count < expectedCount) {
this.throwError("missing argument" + message, Logger.errors.MISSING_ARGUMENT, {
count: count,
expectedCount: expectedCount
});
}
if (count > expectedCount) {
this.throwError("too many arguments" + message, Logger.errors.UNEXPECTED_ARGUMENT, {
count: count,
expectedCount: expectedCount
});
}
}
checkNew(target: any, kind: any): void {
if (target === Object || target == null) {
this.throwError("missing new", Logger.errors.MISSING_NEW, { name: kind.name });
}
}
checkAbstract(target: any, kind: any): void {
if (target === kind) {
this.throwError(
"cannot instantiate abstract class " + JSON.stringify(kind.name) + " directly; use a sub-class",
Logger.errors.UNSUPPORTED_OPERATION,
{ name: target.name, operation: "new" }
);
} else if (target === Object || target == null) {
this.throwError("missing new", Logger.errors.MISSING_NEW, { name: kind.name });
}
}
static globalLogger(): Logger {
if (!_globalLogger) { _globalLogger = new Logger(version); }
return _globalLogger;
}
static setCensorship(censorship: boolean, permanent?: boolean): void {
if (!censorship && permanent) {
this.globalLogger().throwError("cannot permanently disable censorship", Logger.errors.UNSUPPORTED_OPERATION, {
operation: "setCensorship"
});
}
if (_permanentCensorErrors) {
if (!censorship) { return; }
this.globalLogger().throwError("error censorship permanent", Logger.errors.UNSUPPORTED_OPERATION, {
operation: "setCensorship"
});
}
_censorErrors = !!censorship;
_permanentCensorErrors = !!permanent;
}
static setLogLevel(logLevel: LogLevel): void {
const level = LogLevels[logLevel];
if (level == null) {
Logger.globalLogger().warn("invalid log level - " + logLevel);
return;
}
_logLevel = level;
}
}