@apify/log
Version:
Tools and constants shared across Apify projects.
540 lines (530 loc) • 19.2 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/index.ts
var index_exports = {};
__export(index_exports, {
IS_APIFY_LOGGER_EXCEPTION: () => IS_APIFY_LOGGER_EXCEPTION,
LEVELS: () => LEVELS,
LEVEL_TO_STRING: () => LEVEL_TO_STRING,
Log: () => Log,
LogFormat: () => LogFormat,
LogLevel: () => LogLevel,
Logger: () => Logger,
LoggerJson: () => LoggerJson,
LoggerText: () => LoggerText,
PREFIX_DELIMITER: () => PREFIX_DELIMITER,
default: () => index_default,
getFormatFromEnv: () => getFormatFromEnv,
getLevelFromEnv: () => getLevelFromEnv,
limitDepth: () => limitDepth,
truncate: () => truncate
});
module.exports = __toCommonJS(index_exports);
// src/log_consts.ts
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
LogLevel2[LogLevel2["OFF"] = 0] = "OFF";
LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
LogLevel2[LogLevel2["SOFT_FAIL"] = 2] = "SOFT_FAIL";
LogLevel2[LogLevel2["WARNING"] = 3] = "WARNING";
LogLevel2[LogLevel2["INFO"] = 4] = "INFO";
LogLevel2[LogLevel2["DEBUG"] = 5] = "DEBUG";
LogLevel2[LogLevel2["PERF"] = 6] = "PERF";
return LogLevel2;
})(LogLevel || {});
var LogFormat = /* @__PURE__ */ ((LogFormat2) => {
LogFormat2["JSON"] = "JSON";
LogFormat2["TEXT"] = "TEXT";
return LogFormat2;
})(LogFormat || {});
var PREFIX_DELIMITER = ":";
var LEVELS = LogLevel;
var LEVEL_TO_STRING = Object.keys(LogLevel).filter((x) => Number.isNaN(+x));
var IS_APIFY_LOGGER_EXCEPTION = Symbol("apify.processed_error");
// src/log_helpers.ts
var import_consts = require("@apify/consts");
function truncate(str, maxLength, suffix = "...[truncated]") {
maxLength = Math.floor(maxLength);
if (suffix.length > maxLength) {
throw new Error("suffix string cannot be longer than maxLength");
}
if (typeof str === "string" && str.length > maxLength) {
str = str.substr(0, maxLength - suffix.length) + suffix;
}
return str;
}
__name(truncate, "truncate");
function getLevelFromEnv() {
const envVar = process.env[import_consts.APIFY_ENV_VARS.LOG_LEVEL];
if (!envVar) return 4 /* INFO */;
if (Number.isFinite(+envVar)) return +envVar;
if (LogLevel[envVar]) return LogLevel[envVar];
return +envVar;
}
__name(getLevelFromEnv, "getLevelFromEnv");
function getFormatFromEnv() {
const envVar = process.env[import_consts.APIFY_ENV_VARS.LOG_FORMAT] || "TEXT" /* TEXT */;
switch (envVar.toLowerCase()) {
case "JSON" /* JSON */.toLowerCase():
return "JSON" /* JSON */;
case "TEXT" /* TEXT */.toLowerCase():
return "TEXT" /* TEXT */;
default:
console.warn(`Unknown value for environment variable ${import_consts.APIFY_ENV_VARS.LOG_FORMAT}: ${envVar}`);
return "TEXT" /* TEXT */;
}
}
__name(getFormatFromEnv, "getFormatFromEnv");
function limitDepth(record, depth, maxStringLength) {
if (typeof record === "string") {
return maxStringLength && record.length > maxStringLength ? truncate(record, maxStringLength) : record;
}
if (["number", "boolean", "symbol", "bigint"].includes(typeof record) || record == null || record instanceof Date) {
return record;
}
if (record instanceof Error) {
const { name, message, stack, cause, ...rest } = record;
record = { name, message, stack, cause, ...rest, [IS_APIFY_LOGGER_EXCEPTION]: true };
}
const nextCall = /* @__PURE__ */ __name((rec) => limitDepth(rec, depth - 1, maxStringLength), "nextCall");
if (Array.isArray(record)) {
return depth ? record.map(nextCall) : "[array]";
}
if (typeof record === "object" && record !== null) {
const mapObject = /* @__PURE__ */ __name((obj) => {
const res = {};
Reflect.ownKeys(obj).forEach((key) => {
res[key] = nextCall(obj[key]);
});
return res;
}, "mapObject");
return depth ? mapObject(record) : "[object]";
}
if (typeof record === "function") {
return "[function]";
}
console.log(`WARNING: Object cannot be logged: ${record}`);
return void 0;
}
__name(limitDepth, "limitDepth");
// src/logger.ts
var import_node_events = require("events");
var _Logger = class _Logger extends import_node_events.EventEmitter {
constructor(options) {
super();
this.options = options;
}
setOptions(options) {
this.options = { ...this.options, ...options };
}
getOptions() {
return this.options;
}
_outputWithConsole(level, line) {
switch (level) {
case 1 /* ERROR */:
console.error(line);
break;
case 3 /* WARNING */:
console.warn(line);
break;
case 5 /* DEBUG */:
console.debug(line);
break;
default:
console.log(line);
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_log(level, message, data, exception, opts = {}) {
throw new Error("log() method must be implemented!");
}
log(level, message, ...args) {
const line = this._log(level, message, ...args);
this.emit("line", line);
}
};
__name(_Logger, "Logger");
var Logger = _Logger;
// src/logger_json.ts
var DEFAULT_OPTIONS = {
skipLevelInfo: false,
skipTime: false
};
var _LoggerJson = class _LoggerJson extends Logger {
constructor(options = {}) {
super({ ...DEFAULT_OPTIONS, ...options });
}
_log(level, message, data, exception, opts = {}) {
const { prefix, suffix } = opts;
if (exception) data = { ...data, exception };
if (prefix) message = `${prefix}${PREFIX_DELIMITER} ${message}`;
if (suffix) message = `${message} ${suffix}`;
const rec = {
time: !this.options.skipTime ? /* @__PURE__ */ new Date() : void 0,
level: this.options.skipLevelInfo && level === 4 /* INFO */ ? void 0 : LogLevel[level],
msg: message,
...data
};
const line = JSON.stringify(rec);
this._outputWithConsole(level, line);
return line;
}
};
__name(_LoggerJson, "LoggerJson");
var LoggerJson = _LoggerJson;
// src/logger_text.ts
var import_ansi_colors2 = __toESM(require("ansi-colors"));
// src/node_internals.ts
var import_ansi_colors = __toESM(require("ansi-colors"));
function identicalSequenceRange(a, b) {
for (let i = 0; i < a.length - 3; i++) {
const pos = b.indexOf(a[i]);
if (pos !== -1) {
const rest = b.length - pos;
if (rest > 3) {
let len = 1;
const maxLen = Math.min(a.length - i, rest);
while (maxLen > len && a[i + len] === b[pos + len]) {
len++;
}
if (len > 3) {
return { len, offset: i };
}
}
}
}
return { len: 0, offset: 0 };
}
__name(identicalSequenceRange, "identicalSequenceRange");
function getStackString(error) {
return error.stack ? String(error.stack) : Error.prototype.toString.call(error);
}
__name(getStackString, "getStackString");
function getStackFrames(err, stack) {
const frames = stack.split("\n");
let cause;
try {
({ cause } = err);
} catch {
}
if (cause != null && typeof cause === "object" && IS_APIFY_LOGGER_EXCEPTION in cause) {
const causeStack = getStackString(cause);
const causeStackStart = causeStack.indexOf("\n at");
if (causeStackStart !== -1) {
const causeFrames = causeStack.slice(causeStackStart + 1).split("\n");
const { len, offset } = identicalSequenceRange(frames, causeFrames);
if (len > 0) {
const skipped = len - 2;
const msg = ` ... ${skipped} lines matching cause stack trace ...`;
frames.splice(offset + 1, skipped, import_ansi_colors.default.grey(msg));
}
}
}
return frames;
}
__name(getStackFrames, "getStackFrames");
// src/logger_text.ts
var SHORTEN_LEVELS = {
SOFT_FAIL: "SFAIL",
WARNING: "WARN"
};
var LEVEL_TO_COLOR = {
[1 /* ERROR */]: "red",
[2 /* SOFT_FAIL */]: "red",
[3 /* WARNING */]: "yellow",
[4 /* INFO */]: "green",
[5 /* DEBUG */]: "blue",
[6 /* PERF */]: "magenta"
};
var SHORTENED_LOG_LEVELS = LEVEL_TO_STRING.map((level) => SHORTEN_LEVELS[level] || level);
var MAX_LEVEL_LENGTH_SPACES = Math.max(...SHORTENED_LOG_LEVELS.map((l) => l.length));
var getLevelIndent = /* @__PURE__ */ __name((level) => {
let spaces = "";
for (let i = 0; i < MAX_LEVEL_LENGTH_SPACES - level.length; i++) spaces += " ";
return spaces;
}, "getLevelIndent");
var DEFAULT_OPTIONS2 = {
skipTime: true
};
var _LoggerText = class _LoggerText extends Logger {
constructor(options = {}) {
super({ ...DEFAULT_OPTIONS2, ...options });
}
_log(level, message, data, exception, opts = {}) {
let { prefix, suffix } = opts;
let maybeDate = "";
if (!this.options.skipTime) {
maybeDate = `${(/* @__PURE__ */ new Date()).toISOString().replace("Z", "").replace("T", " ")} `;
}
const errStack = exception ? this._parseException(exception) : "";
const color = LEVEL_TO_COLOR[level];
const levelStr = SHORTENED_LOG_LEVELS[level];
const levelIndent = getLevelIndent(levelStr);
const dataStr = !data ? "" : ` ${JSON.stringify(data)}`;
prefix = prefix ? ` ${prefix}${PREFIX_DELIMITER}` : "";
suffix = suffix ? ` ${suffix}` : "";
const line = `${import_ansi_colors2.default.gray(maybeDate)}${import_ansi_colors2.default[color](levelStr)}${levelIndent}${import_ansi_colors2.default.yellow(prefix)} ${message || ""}${import_ansi_colors2.default.gray(dataStr)}${import_ansi_colors2.default.yellow(suffix)}${errStack}`;
this._outputWithConsole(level, line);
return line;
}
_parseException(exception, indentLevel = 1) {
if (["string", "boolean", "number", "undefined", "bigint"].includes(typeof exception)) {
return `
${exception}`;
}
if (exception === null) {
return "\nnull";
}
if (typeof exception === "symbol") {
return `
${exception.toString()}`;
}
if (typeof exception === "object" && IS_APIFY_LOGGER_EXCEPTION in exception) {
return this._parseLoggerException(exception, indentLevel);
}
return `
${JSON.stringify(exception, null, 2)}`;
}
_parseLoggerException(exception, indentLevel = 1) {
const errDetails = [];
if (exception.type) {
errDetails.push(`type=${exception.type}`);
}
if (exception.details) {
Object.entries(exception.details).map(([key, val]) => errDetails.push(`${key}=${val}`));
}
const errorString = exception.stack || exception.reason || exception.message;
const isStack = errorString === exception.stack;
const errorLines = getStackFrames(exception, errorString);
if (isStack) {
errorLines[0] = exception.message || errorLines[0];
}
if (errDetails.length) {
errorLines[0] += import_ansi_colors2.default.gray(`(details: ${errDetails.join(", ")})`);
}
for (let i = 1; i < errorLines.length; i++) {
errorLines[i] = import_ansi_colors2.default.gray(errorLines[i]);
}
if (exception.cause) {
const causeString = this._parseException(exception.cause, indentLevel + 1);
const causeLines = causeString.trim().split("\n");
errorLines.push(import_ansi_colors2.default.red(` CAUSE: ${import_ansi_colors2.default.reset(causeLines[0])}`), ...causeLines.slice(1));
}
return `
${errorLines.map((line) => `${" ".repeat(indentLevel * 2)}${line}`).join("\n")}`;
}
};
__name(_LoggerText, "LoggerText");
var LoggerText = _LoggerText;
// src/log.ts
var getLoggerForFormat = /* @__PURE__ */ __name((format) => {
switch (format) {
case "JSON" /* JSON */:
return new LoggerJson();
case "TEXT" /* TEXT */:
default:
return new LoggerText();
}
}, "getLoggerForFormat");
var getDefaultOptions = /* @__PURE__ */ __name(() => ({
level: getLevelFromEnv(),
maxDepth: 4,
maxStringLength: 2e3,
prefix: null,
suffix: null,
logger: getLoggerForFormat(getFormatFromEnv()),
data: {}
}), "getDefaultOptions");
var _Log = class _Log {
constructor(options = {}) {
/**
* Map of available log levels that's useful for easy setting of appropriate log levels.
* Each log level is represented internally by a number. Eg. `log.LEVELS.DEBUG === 5`.
*/
__publicField(this, "LEVELS", LogLevel);
// for BC
__publicField(this, "options");
__publicField(this, "warningsOnceLogged", /* @__PURE__ */ new Set());
this.options = { ...getDefaultOptions(), ...options };
if (!LogLevel[this.options.level]) throw new Error('Options "level" must be one of log.LEVELS enum!');
if (typeof this.options.maxDepth !== "number") throw new Error('Options "maxDepth" must be a number!');
if (typeof this.options.maxStringLength !== "number") throw new Error('Options "maxStringLength" must be a number!');
if (this.options.prefix && typeof this.options.prefix !== "string") throw new Error('Options "prefix" must be a string!');
if (this.options.suffix && typeof this.options.suffix !== "string") throw new Error('Options "suffix" must be a string!');
if (typeof this.options.logger !== "object") throw new Error('Options "logger" must be an object!');
if (typeof this.options.data !== "object") throw new Error('Options "data" must be an object!');
}
_limitDepth(obj) {
return limitDepth(obj, this.options.maxDepth);
}
/**
* Returns the currently selected logging level. This is useful for checking whether a message
* will actually be printed to the console before one actually performs a resource intensive operation
* to construct the message, such as querying a DB for some metadata that need to be added. If the log
* level is not high enough at the moment, it doesn't make sense to execute the query.
*/
getLevel() {
return this.options.level;
}
/**
* Sets the log level to the given value, preventing messages from less important log levels
* from being printed to the console. Use in conjunction with the `log.LEVELS` constants such as
*
* ```
* log.setLevel(log.LEVELS.DEBUG);
* ```
*
* Default log level is INFO.
*/
setLevel(level) {
if (!LogLevel[level]) throw new Error('Options "level" must be one of log.LEVELS enum!');
this.options.level = level;
}
internal(level, message, data, exception) {
if (level > this.options.level) return;
data = { ...this.options.data, ...data };
data = Reflect.ownKeys(data).length > 0 ? this._limitDepth(data) : void 0;
exception = this._limitDepth(exception);
this.options.logger.log(level, message, data, exception, {
prefix: this.options.prefix,
suffix: this.options.suffix
});
}
/**
* Configures logger.
*/
setOptions(options) {
this.options = { ...this.options, ...options };
}
/**
* Returns the logger configuration.
*/
getOptions() {
return { ...this.options };
}
/**
* Creates a new instance of logger that inherits settings from a parent logger.
*/
child(options) {
let { prefix } = this.options;
if (options.prefix) {
prefix = prefix ? `${prefix}${PREFIX_DELIMITER}${options.prefix}` : options.prefix;
}
const data = options.data ? { ...this.options.data, ...options.data } : this.options.data;
const newOptions = {
...this.options,
...options,
prefix,
data
};
return new _Log(newOptions);
}
/**
* Logs an `ERROR` message. Use this method to log error messages that are not directly connected
* to an exception. For logging exceptions, use the `log.exception` method.
*/
error(message, data) {
this.internal(1 /* ERROR */, message, data);
}
/**
* Logs an `ERROR` level message with a nicely formatted exception. Note that the exception is the first parameter
* here and an additional message is only optional.
*/
exception(exception, message, data) {
this.internal(1 /* ERROR */, message, data, exception);
}
softFail(message, data) {
this.internal(2 /* SOFT_FAIL */, message, data);
}
/**
* Logs a `WARNING` level message. Data are stringified and appended to the message.
*/
warning(message, data) {
this.internal(3 /* WARNING */, message, data);
}
/**
* Logs an `INFO` message. `INFO` is the default log level so info messages will be always logged,
* unless the log level is changed. Data are stringified and appended to the message.
*/
info(message, data) {
this.internal(4 /* INFO */, message, data);
}
/**
* Logs a `DEBUG` message. By default, it will not be written to the console. To see `DEBUG`
* messages in the console, set the log level to `DEBUG` either using the `log.setLevel(log.LEVELS.DEBUG)`
* method or using the environment variable `APIFY_LOG_LEVEL=DEBUG`. Data are stringified and appended
* to the message.
*/
debug(message, data) {
this.internal(5 /* DEBUG */, message, data);
}
perf(message, data) {
this.internal(6 /* PERF */, message, data);
}
/**
* Logs a `WARNING` level message only once.
*/
warningOnce(message) {
if (this.warningsOnceLogged.has(message)) return;
this.warningsOnceLogged.add(message);
this.warning(message);
}
/**
* Logs given message only once as WARNING. It's used to warn user that some feature he is using has been deprecated.
*/
deprecated(message) {
this.warningOnce(message);
}
};
__name(_Log, "Log");
var Log = _Log;
// src/index.ts
var log = new Log();
var index_default = log;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
IS_APIFY_LOGGER_EXCEPTION,
LEVELS,
LEVEL_TO_STRING,
Log,
LogFormat,
LogLevel,
Logger,
LoggerJson,
LoggerText,
PREFIX_DELIMITER,
getFormatFromEnv,
getLevelFromEnv,
limitDepth,
truncate
});
//# sourceMappingURL=index.cjs.map