@hyperlane-xyz/utils
Version:
General utilities and types for the Hyperlane network
149 lines • 5.61 kB
JavaScript
import { BigNumber } from 'ethers';
import { pino } from 'pino';
import { inKubernetes, safelyAccessEnvVar } from './env.js';
// Level and format here should correspond with the agent options as much as possible
// https://docs.hyperlane.xyz/docs/operate/config-reference#logfmt
// A custom enum definition because pino does not export an enum
// and because we use 'off' instead of 'silent' to match the agent options
export var LogLevel;
(function (LogLevel) {
LogLevel["Trace"] = "trace";
LogLevel["Debug"] = "debug";
LogLevel["Info"] = "info";
LogLevel["Warn"] = "warn";
LogLevel["Error"] = "error";
LogLevel["Off"] = "off";
})(LogLevel || (LogLevel = {}));
let logLevel = toPinoLevel(safelyAccessEnvVar('LOG_LEVEL', true)) || 'info';
function toPinoLevel(level) {
if (level && pino.levels.values[level])
return level;
// For backwards compat and also to match agent level options
else if (level === 'none' || level === 'off')
return 'silent';
else
return undefined;
}
export function getLogLevel() {
return logLevel;
}
export var LogFormat;
(function (LogFormat) {
LogFormat["Pretty"] = "pretty";
LogFormat["JSON"] = "json";
})(LogFormat || (LogFormat = {}));
let logFormat = LogFormat.JSON;
const envLogFormat = safelyAccessEnvVar('LOG_FORMAT', true);
if (envLogFormat && Object.values(LogFormat).includes(envLogFormat))
logFormat = envLogFormat;
export function getLogFormat() {
return logFormat;
}
// Note, for brevity and convenience, the rootLogger is exported directly
export let rootLogger = createHyperlanePinoLogger(logLevel, logFormat);
export function getRootLogger() {
return rootLogger;
}
export function configureRootLogger(newLogFormat, newLogLevel) {
logFormat = newLogFormat;
logLevel = toPinoLevel(newLogLevel) || logLevel;
rootLogger = createHyperlanePinoLogger(logLevel, logFormat);
return rootLogger;
}
export function setRootLogger(logger) {
rootLogger = logger;
return rootLogger;
}
export function createHyperlanePinoLogger(logLevel, logFormat) {
// In development, pino-pretty is used for a better dev experience,
// but only if the log format is 'pretty'. This allows for JSON logs
// in development as well if explicitly configured.
if (process.env.NODE_ENV === 'development' &&
logFormat === LogFormat.Pretty) {
return pino({
level: logLevel,
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname',
},
},
});
}
// In production (or other envs), use the original hook-based logger
return pino({
level: logLevel,
name: 'hyperlane',
formatters: {
// Remove pino's default bindings of hostname but keep pid
bindings: (defaultBindings) => ({ pid: defaultBindings.pid }),
},
hooks: {
logMethod(inputArgs, method, level) {
// Pino has no simple way of setting custom log shapes and they
// recommend against using pino-pretty in production so when
// pretty is enabled we circumvent pino and log directly to console
if (logFormat === LogFormat.Pretty &&
level >= pino.levels.values[logLevel]) {
// eslint-disable-next-line no-console
console.log(...inputArgs);
// Then return null to prevent pino from logging
return null;
}
return method.apply(this, inputArgs);
},
},
});
}
export function ethersBigNumberSerializer(key, value) {
// Check if the value looks like a serialized BigNumber
if (typeof value === 'object' &&
value !== null &&
value.type === 'BigNumber' &&
value.hex) {
return BigNumber.from(value.hex).toString();
}
if (typeof value === 'bigint') {
return value.toString();
}
return value;
}
export async function tryInitializeGcpLogger(options) {
if (!inKubernetes())
return null;
try {
const { createGcpLoggingPinoConfig } = await import(
/* webpackIgnore: true */ '@google-cloud/pino-logging-gcp-config');
const serviceContext = options
? {
service: options.service ?? 'hyperlane-service',
version: options.version ?? 'unknown',
}
: {};
const gcpConfig = createGcpLoggingPinoConfig({ serviceContext }, {
base: undefined,
name: 'hyperlane',
});
const gcpLogger = pino(gcpConfig);
return gcpLogger;
}
catch (err) {
rootLogger.warn(err, 'Could not initialize GCP structured logging, ensure @google-cloud/pino-logging-gcp-config is installed');
return null;
}
}
export async function createServiceLogger(options) {
const { service, version, module } = options;
const gcpLogger = await tryInitializeGcpLogger({ service, version });
if (gcpLogger) {
// Also update rootLogger so SDK components (like SmartProvider) that use
// rootLogger.child() will inherit the GCP logging configuration
setRootLogger(gcpLogger);
return gcpLogger;
}
// For local development, create a child logger with module info
return rootLogger.child({ module: module ?? service });
}
//# sourceMappingURL=logging.js.map