@sonatel-os/juf-xpress-logger-edge
Version:
JUF XPress logger for Node Edge
1,038 lines (1,037 loc) • 35.1 kB
JavaScript
var __typeError = (msg) => {
throw TypeError(msg);
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _apmClient, _crypt, _findLogFile, _logger, _apm, _appName, _hasInstance, _LoggerService_instances, isInstanciated_fn, update_fn, replaceSensitiveKeys_fn;
class BaseLogger {
/**
* Writes a log entry.
* @abstract
* @param {object} logData - The data to be logged.
*/
writeLog(logData) {
throw new Error("writeLog method must be implemented by subclasses");
}
}
class ConsoleLogger extends BaseLogger {
/**
* @constructor
* @param {object} config - Configuration for the logger.
* @param {boolean} config.sendToRemote - Whether to send logs to a remote server.
* @param {string} [config.remoteUrl] - URL of the remote logging server.
* @param {string} [config.logLevel='info'] - Log level for filtering logs.
*/
constructor({ sendToRemote = false, remoteUrl = "", logLevel = "info" } = {}) {
super();
this.sendToRemote = sendToRemote;
this.remoteUrl = remoteUrl;
this.logLevel = logLevel;
}
/**
* Writes a log entry to the console or remote server.
* @param {object} logData - The data to be logged.
*/
async writeLog(logData) {
const { logLevel = "info", message, meta } = logData;
const logEntry = {
"@timestamp": (/* @__PURE__ */ new Date()).toISOString(),
"log.level": logLevel.toLowerCase(),
"meta": {
...meta,
"event.action": message,
"event.category": logLevel.toUpperCase()
}
};
console.log(JSON.stringify(logEntry));
if (this.sendToRemote && this.remoteUrl) {
try {
await fetch(this.remoteUrl, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(logEntry)
});
} catch (error) {
console.error("Failed to send log to remote server:", error);
}
}
}
}
class LoggerFactory {
/**
* Creates a logger instance based on the provided configuration.
* @param {object} config - Configuration for the logger.
* @param {string} config.type - Type of logger ('console' or 'file').
* @param {string} [config.appName] - Application name for file logging.
* @param {string} [config.logDir] - Directory for file logging.
* @param {boolean} [config.constantFileName=false] - Whether to use a constant file name for logs.
* @returns {BaseLogger} - Logger instance.
*/
static createLogger(config) {
switch (config.type) {
case "console":
return new ConsoleLogger();
case "file":
default:
console.log(`Logger type ${config.type} is not supported yet`);
}
}
}
class ElasticApmClient {
constructor({ serviceName, serverUrl, secretToken, environment = "development", logLevel = "info" }) {
this.serviceName = serviceName;
this.serverUrl = serverUrl;
this.secretToken = secretToken;
this.environment = environment;
this.logLevel = logLevel;
this.apiEndpoint = `${serverUrl}/intake/v2/events`;
}
/**
* Send a payload to the Elastic APM server with retries
* @param {Object} payload - The payload to send.
* @param {number} retries - Number of retries in case of failure.
*/
async sendPayload(payload, retries = 3) {
try {
const response = await fetch(this.apiEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-ndjson",
Authorization: `Bearer ${this.secretToken}`
},
body: `${JSON.stringify(payload)}
`
// NDJSON format required by Elastic APM
});
if (!response.ok) {
console.error(`Failed to send APM data (Status: ${response.status}):`, response.statusText);
if (retries > 0) {
console.log(`Retrying... (${3 - retries} attempts left)`);
return this.sendPayload(payload, retries - 1);
}
}
} catch (error) {
console.error("Error sending APM data:", error);
if (retries > 0) {
console.log(`Retrying... (${3 - retries} attempts left)`);
return this.sendPayload(payload, retries - 1);
}
}
}
/**
* Report a transaction to Elastic APM
* @param {Object} transaction - The transaction object to report.
*/
async reportTransaction(transaction) {
const payload = {
transaction: {
...transaction,
service: {
name: this.serviceName,
environment: this.environment
}
}
};
await this.sendPayload(payload);
}
/**
* Report an error to Elastic APM
* @param {Object} error - The error object to report.
*/
async reportError(error) {
const payload = {
error: {
...error,
service: {
name: this.serviceName,
environment: this.environment
}
}
};
await this.sendPayload(payload);
}
/**
* Report a span to Elastic APM
* @param {Object} span - The span object to report.
*/
async reportSpan(span) {
const payload = {
span: {
...span,
service: {
name: this.serviceName,
environment: this.environment
}
}
};
await this.sendPayload(payload);
}
}
class StructError extends TypeError {
constructor(failure, failures) {
let cached;
const { message, explanation, ...rest } = failure;
const { path } = failure;
const msg = path.length === 0 ? message : `At path: ${path.join(".")} -- ${message}`;
super(explanation ?? msg);
if (explanation != null)
this.cause = msg;
Object.assign(this, rest);
this.name = this.constructor.name;
this.failures = () => {
return cached ?? (cached = [failure, ...failures()]);
};
}
}
function isIterable(x) {
return isObject(x) && typeof x[Symbol.iterator] === "function";
}
function isObject(x) {
return typeof x === "object" && x != null;
}
function isNonArrayObject(x) {
return isObject(x) && !Array.isArray(x);
}
function isPlainObject(x) {
if (Object.prototype.toString.call(x) !== "[object Object]") {
return false;
}
const prototype = Object.getPrototypeOf(x);
return prototype === null || prototype === Object.prototype;
}
function print(value) {
if (typeof value === "symbol") {
return value.toString();
}
return typeof value === "string" ? JSON.stringify(value) : `${value}`;
}
function shiftIterator(input) {
const { done, value } = input.next();
return done ? void 0 : value;
}
function toFailure(result, context, struct, value) {
if (result === true) {
return;
} else if (result === false) {
result = {};
} else if (typeof result === "string") {
result = { message: result };
}
const { path, branch } = context;
const { type } = struct;
const { refinement, message = `Expected a value of type \`${type}\`${refinement ? ` with refinement \`${refinement}\`` : ""}, but received: \`${print(value)}\`` } = result;
return {
value,
type,
refinement,
key: path[path.length - 1],
path,
branch,
...result,
message
};
}
function* toFailures(result, context, struct, value) {
if (!isIterable(result)) {
result = [result];
}
for (const r of result) {
const failure = toFailure(r, context, struct, value);
if (failure) {
yield failure;
}
}
}
function* run(value, struct, options = {}) {
const { path = [], branch = [value], coerce: coerce2 = false, mask: mask2 = false } = options;
const ctx = { path, branch, mask: mask2 };
if (coerce2) {
value = struct.coercer(value, ctx);
}
let status = "valid";
for (const failure of struct.validator(value, ctx)) {
failure.explanation = options.message;
status = "not_valid";
yield [failure, void 0];
}
for (let [k, v, s] of struct.entries(value, ctx)) {
const ts = run(v, s, {
path: k === void 0 ? path : [...path, k],
branch: k === void 0 ? branch : [...branch, v],
coerce: coerce2,
mask: mask2,
message: options.message
});
for (const t of ts) {
if (t[0]) {
status = t[0].refinement != null ? "not_refined" : "not_valid";
yield [t[0], void 0];
} else if (coerce2) {
v = t[1];
if (k === void 0) {
value = v;
} else if (value instanceof Map) {
value.set(k, v);
} else if (value instanceof Set) {
value.add(v);
} else if (isObject(value)) {
if (v !== void 0 || k in value)
value[k] = v;
}
}
}
}
if (status !== "not_valid") {
for (const failure of struct.refiner(value, ctx)) {
failure.explanation = options.message;
status = "not_refined";
yield [failure, void 0];
}
}
if (status === "valid") {
yield [void 0, value];
}
}
class Struct {
constructor(props) {
const { type, schema, validator, refiner, coercer = (value) => value, entries = function* () {
} } = props;
this.type = type;
this.schema = schema;
this.entries = entries;
this.coercer = coercer;
if (validator) {
this.validator = (value, context) => {
const result = validator(value, context);
return toFailures(result, context, this, value);
};
} else {
this.validator = () => [];
}
if (refiner) {
this.refiner = (value, context) => {
const result = refiner(value, context);
return toFailures(result, context, this, value);
};
} else {
this.refiner = () => [];
}
}
/**
* Assert that a value passes the struct's validation, throwing if it doesn't.
*/
assert(value, message) {
return assert(value, this, message);
}
/**
* Create a value with the struct's coercion logic, then validate it.
*/
create(value, message) {
return create(value, this, message);
}
/**
* Check if a value passes the struct's validation.
*/
is(value) {
return is(value, this);
}
/**
* Mask a value, coercing and validating it, but returning only the subset of
* properties defined by the struct's schema. Masking applies recursively to
* props of `object` structs only.
*/
mask(value, message) {
return mask(value, this, message);
}
/**
* Validate a value with the struct's validation logic, returning a tuple
* representing the result.
*
* You may optionally pass `true` for the `coerce` argument to coerce
* the value before attempting to validate it. If you do, the result will
* contain the coerced result when successful. Also, `mask` will turn on
* masking of the unknown `object` props recursively if passed.
*/
validate(value, options = {}) {
return validate(value, this, options);
}
}
function assert(value, struct, message) {
const result = validate(value, struct, { message });
if (result[0]) {
throw result[0];
}
}
function create(value, struct, message) {
const result = validate(value, struct, { coerce: true, message });
if (result[0]) {
throw result[0];
} else {
return result[1];
}
}
function mask(value, struct, message) {
const result = validate(value, struct, { coerce: true, mask: true, message });
if (result[0]) {
throw result[0];
} else {
return result[1];
}
}
function is(value, struct) {
const result = validate(value, struct);
return !result[0];
}
function validate(value, struct, options = {}) {
const tuples = run(value, struct, options);
const tuple = shiftIterator(tuples);
if (tuple[0]) {
const error = new StructError(tuple[0], function* () {
for (const t of tuples) {
if (t[0]) {
yield t[0];
}
}
});
return [error, void 0];
} else {
const v = tuple[1];
return [void 0, v];
}
}
function define(name, validator) {
return new Struct({ type: name, schema: null, validator });
}
function any() {
return define("any", () => true);
}
function array(Element) {
return new Struct({
type: "array",
schema: Element,
*entries(value) {
if (Element && Array.isArray(value)) {
for (const [i, v] of value.entries()) {
yield [i, v, Element];
}
}
},
coercer(value) {
return Array.isArray(value) ? value.slice() : value;
},
validator(value) {
return Array.isArray(value) || `Expected an array value, but received: ${print(value)}`;
}
});
}
function boolean() {
return define("boolean", (value) => {
return typeof value === "boolean";
});
}
function func() {
return define("func", (value) => {
return typeof value === "function" || `Expected a function, but received: ${print(value)}`;
});
}
function never() {
return define("never", () => false);
}
function number() {
return define("number", (value) => {
return typeof value === "number" && !isNaN(value) || `Expected a number, but received: ${print(value)}`;
});
}
function object(schema) {
const knowns = schema ? Object.keys(schema) : [];
const Never = never();
return new Struct({
type: "object",
schema: schema ? schema : null,
*entries(value) {
if (schema && isObject(value)) {
const unknowns = new Set(Object.keys(value));
for (const key of knowns) {
unknowns.delete(key);
yield [key, value[key], schema[key]];
}
for (const key of unknowns) {
yield [key, value[key], Never];
}
}
},
validator(value) {
return isNonArrayObject(value) || `Expected an object, but received: ${print(value)}`;
},
coercer(value, ctx) {
if (!isNonArrayObject(value)) {
return value;
}
const coerced = { ...value };
if (ctx.mask && schema) {
for (const key in coerced) {
if (schema[key] === void 0) {
delete coerced[key];
}
}
}
return coerced;
}
});
}
function optional(struct) {
return new Struct({
...struct,
validator: (value, ctx) => value === void 0 || struct.validator(value, ctx),
refiner: (value, ctx) => value === void 0 || struct.refiner(value, ctx)
});
}
function string() {
return define("string", (value) => {
return typeof value === "string" || `Expected a string, but received: ${print(value)}`;
});
}
function unknown() {
return define("unknown", () => true);
}
function coerce(struct, condition, coercer) {
return new Struct({
...struct,
coercer: (value, ctx) => {
return is(value, condition) ? struct.coercer(coercer(value, ctx), ctx) : struct.coercer(value, ctx);
}
});
}
function defaulted(struct, fallback, options = {}) {
return coerce(struct, unknown(), (x) => {
const f = typeof fallback === "function" ? fallback() : fallback;
if (x === void 0) {
return f;
}
if (!options.strict && isPlainObject(x) && isPlainObject(f)) {
const ret = { ...x };
let changed = false;
for (const key in f) {
if (ret[key] === void 0) {
ret[key] = f[key];
changed = true;
}
}
if (changed) {
return ret;
}
}
return x;
});
}
const NullableObject = define("NullableObject", (value) => {
return value === null || typeof value === "object";
});
const Params$1 = object({
path: string(),
method: string(),
headers: object(),
routeParams: optional(NullableObject),
queryParams: optional(NullableObject),
payload: optional(any())
});
const HttpListener = object({
onFunction: optional(func()),
callback: optional(func())
});
const CaptureApmMiddlewareConfig = object({
params: Params$1,
callback: func(),
httpListener: optional(HttpListener)
});
const StartTransactionSpanConfig = object({
spanName: string(),
spanType: string(),
payload: optional(object())
});
const _Observability = class _Observability {
/**
* Creates an instance of Observability with APM configurations.
* @constructor
* @param {object} options - Configuration options for initializing the APM client.
* @param {string} options.serviceName - The name of the service being monitored.
* @param {string} options.serverUrl - The URL of the APM server.
* @param {boolean} [options.startApmAgent=false] - Whether to start the observability agent.
* @param {string} [options.secretToken] - Secret token for the APM server.
* @param {string} [options.environment='development'] - The environment in which the service is running.
* @param {string} [options.logLevel='info'] - The log level for logging.
*/
constructor(options) {
__privateAdd(this, _apmClient);
__privateSet(this, _apmClient, new ElasticApmClient(options));
if (options.startApmAgent) {
this.start();
}
}
/**
* Factory method to create an instance of Observability.
* @static
* @param {object} options - Configuration options for initializing the observability agent.
* @returns {Observability} - An instance of Observability.
*/
static createObservabilityAgent(options) {
return new _Observability(options);
}
/**
* Starts the observability agent.
*/
start() {
console.log("Observability agent started with configuration:", __privateGet(this, _apmClient));
}
/**
* Middleware for capturing APM data and handling transactions.
* @param {object} params - Parameters for the middleware.
* @param {string} params.path - The path of the request.
* @param {string} params.method - The HTTP method of the request.
* @param {object} params.headers - The headers of the request.
* @param {object} params.routeParams - The route parameters of the request.
* @param {object} params.queryParams - The query parameters of the request.
* @param {object} [params.payload] - The request payload.
* @param {Function} callback - Callback function to be executed after middleware processing.
* @param {object} httpListener - HTTP listener object with response handling functions.
* @param {Function} httpListener.onFunction - Function that listens to HTTP events (e.g., 'finish').
* @param {Function} httpListener.callback - Callback function to execute after the listener function.
* @throws {Error} Throws error if there is an issue with processing.
*/
async middleware({ path, method, headers, routeParams, queryParams, payload }, callback, httpListener) {
try {
const structure = {
params: { path, method, headers, routeParams, queryParams, payload },
callback,
httpListener
};
CaptureApmMiddlewareConfig.create(structure);
} catch (error) {
console.log(error.toString());
return;
}
const transaction = {
name: `${method.toUpperCase()} ${path}`,
type: "request",
result: "success",
// or 'failure' based on logic
duration: 100,
// you can calculate duration in ms
context: {
request: {
method,
url: path,
headers,
routeParams,
queryParams,
body: payload
}
}
};
await __privateGet(this, _apmClient).reportTransaction(transaction);
const { onFunction, callback: listenerCallback } = httpListener ?? {};
if (onFunction) {
onFunction("finish", async () => {
if (listenerCallback) listenerCallback();
});
} else if (listenerCallback) {
listenerCallback();
}
callback();
}
/**
* Starts a new span for an existing transaction, typically for monitoring external requests or operations.
* @param {string} spanName - The name of the span to be started.
* @param {string} spanType - The type of the span (e.g., 'external', 'db').
* @param {object} payload - The data sent or being treated in your span.
* @returns {Object|null} - Returns a dummy span object or null.
*/
async startTransactionSpan(spanName, spanType = "custom", payload) {
try {
const structure = {
spanName,
spanType,
payload
};
StartTransactionSpanConfig.create(structure);
} catch (error) {
console.log(error.toString());
return;
}
const span = {
name: spanName,
type: spanType,
context: payload
};
await __privateGet(this, _apmClient).reportSpan(span);
return span;
}
/**
* Ends a given span and logs its duration for debugging purposes.
* @param {object} span - The span object to be ended.
* @throws {Error} Throws error if the span cannot be ended.
* @example
* observability.endTransactionSpan(span);
*/
async endTransactionSpan(span) {
try {
if (span) {
console.log(`[Observability] Ended span: ${span.name} of type ${span.type}`);
} else {
console.warn("No span provided to end.");
}
} catch (error) {
console.error("Error ending transaction span:", error);
throw new Error("Failed to end transaction span.");
}
}
};
_apmClient = new WeakMap();
let Observability = _Observability;
const apmConfig = {
verifyServerCert: {
doc: "Verify APM server certificate",
format: Boolean,
default: false,
env: "JUF_ELK_APM_VERIF_CERT"
},
logLevel: {
doc: "Log level for APM",
format: String,
default: "info",
env: "JUF_ELK_APM_LOG_LEVEL"
},
environment: {
doc: "Environment name for APM",
format: String,
default: "<JUF_JS>",
env: "JUF_ELK_APM_ENV_NAME"
},
serviceName: {
doc: "Service name for APM",
format: String,
default: "<JUF_JS>",
env: "JUF_ELK_APM_SERVICE_NAME"
},
secretToken: {
doc: "Secret token for APM",
format: String,
default: "<PASSWORD>",
env: "JUF_ELK_APM_SECRET_TOKEN"
},
serverUrl: {
doc: "Server URL for APM",
format: String,
default: "http://127.0.0.1:8200",
env: "JUF_ELK_APM_SERVER"
}
};
const environmentConfig = {
env: {
doc: "The application environment",
format: ["test", "dev", "development", "pprod", "prod", "production"],
default: "dev",
env: "NODE_ENV"
},
port: {
doc: "The port to bind",
format: "port",
default: 3e3,
env: "PORT"
}
};
const configSchema = {
...environmentConfig,
apm: apmConfig
};
const loadConfigFromEnv = (schema) => {
const config = {};
Object.entries(schema).forEach(([key, { env, default: defaultValue, format }]) => {
const envValue = process.env[env];
let value = envValue !== void 0 ? envValue : defaultValue;
if (typeof format === "function") {
value = format(value);
}
config[key] = value;
});
return config;
};
const envConfig = {
get: (name) => {
return loadConfigFromEnv(configSchema[name]);
}
};
const DEFAULT_CRYPT_KEYS = [
"confirmPassword",
"client_id",
"client_secret",
"cookie",
"content-secured",
"authorization",
"password",
"pass",
"captcha"
];
const LoggerBootstrapStructure = object({
appName: optional(defaulted(string(), "app")),
crypt: optional(defaulted(array(string()), [])),
logConsole: optional(defaulted(boolean(), true)),
logDir: optional(string()),
constantFileName: optional(defaulted(boolean(), false)),
findLogFile: optional(func()),
logLevel: optional(defaulted(string(), "info")),
startApmAgent: optional(defaulted(boolean(), false))
});
const StringOrObject = define("StringOrObject", (value) => {
return typeof value === "string" || typeof value === "object";
});
const Params = object({
logFrom: string(),
userIp: string(),
method: string(),
payload: StringOrObject,
headers: StringOrObject,
logTarget: string(),
userAgent: string(),
logStatus: number(),
logStatusCode: string()
});
const WriteLogOptions = object({
params: Params,
userName: optional(defaulted(string(), "anonymousUser")),
logLevel: optional(defaulted(string(), "INFO")),
action: string(),
duration: optional(number())
});
class LoggerService {
/**
* Constructs an instance of LoggerService with the specified options.
*
* @constructor
* @param {Object} options - Options for initializing the logger service.
* @param {string} [options.appName='app'] - Application name for log identification.
* @param {Array<string>} [options.crypt=[]] - List of keys to mask in the log data.
* @param {boolean} [options.logConsole=true] - Whether to log to the console.
* @param {string} [options.remoteUrl] - URL for sending logs to a remote server.
* @param {boolean} [options.sendToRemote=false] - Whether to send logs to a remote server.
* @param {string} [options.logLevel='info'] - Log level for APM configuration.
* @param {boolean} [options.startApmAgent=false] - Whether to start the APM agent.
* @throws {Error} Throws an error if APM or logger initialization fails.
*/
constructor({ appName = "app", crypt = [], logLevel = "info", startApmAgent = false }) {
__privateAdd(this, _LoggerService_instances);
__privateAdd(this, _crypt);
__privateAdd(this, _findLogFile);
/**
* @type {ConsoleLogger}
* @description Logger instance created by the LoggerFactory.
*/
__privateAdd(this, _logger);
/**
* @type {Observability}
* @description Observability agent instance for monitoring and tracing purposes.
*/
__privateAdd(this, _apm);
/**
* @type {Observability}
* @description Observability agent instance for monitoring and tracing purposes.
*/
__privateAdd(this, _appName);
/**
* @type {boolean}
* @description Flag to determine if the logger service has been initialized.
*/
__privateAdd(this, _hasInstance, false);
if (__privateGet(this, _hasInstance)) {
__privateSet(this, _appName, appName);
__privateSet(this, _crypt, crypt);
__privateSet(this, _logger, LoggerFactory.createLogger({
type: "console",
logLevel
}));
__privateSet(this, _apm, Observability.createObservabilityAgent({ ...envConfig.get("apm"), logLevel, crypt, startApmAgent }));
}
}
/**
* Factory method to bootstrap and initialize a new instance of LoggerService with the provided configuration.
*
* @param {Object} config - Configuration options for LoggerService.
* @param {Array<string>} [config.crypt=[]] - List of keys to mask in the log data for sensitive information.
* @param {boolean} [config.logConsole=true] - Determines whether to log to the console.
* @param {string} [config.logLevel='info'] - Log level for both console and APM configuration ('info', 'warn', 'error', etc.).
* @param {boolean} [config.startApmAgent=false] - Indicates whether to start the APM agent for observability.
* @returns {LoggerService} An initialized instance of LoggerService.
* @throws {Error} Throws an error if the provided configuration is invalid.
*/
bootstrap({ crypt, logConsole = true, logLevel, startApmAgent }) {
const config = { crypt: DEFAULT_CRYPT_KEYS.concat(crypt).filter(Boolean), logConsole, logLevel, startApmAgent };
try {
LoggerBootstrapStructure.create(config);
} catch (error) {
console.log(error.toString());
return;
}
__privateSet(this, _hasInstance, true);
return __privateMethod(this, _LoggerService_instances, update_fn).call(this, config);
}
/**
* Writes a log entry using the configured logger instance.
* Supports logging to the console or sending to a remote server.
* Handles structured logging with sensitive data masking and optional duration tracking.
*
* @param {Object} options - Options for logging.
* @param {Object} options.params - Log parameters including request and response details.
* @param {string} options.params.logFrom - The origin IP address of the log entry.
* @param {string} options.params.userIp - The user's IP address for the log entry.
* @param {string} options.params.method - The HTTP method of the request (e.g., 'GET', 'POST').
* @param {Object|string} options.params.payload - The request body content, can be an object or a stringified JSON.
* @param {Object|string} options.params.headers - The request headers, can be an object or a stringified JSON.
* @param {string} options.params.logTarget - The target URL of the log entry.
* @param {string} options.params.userAgent - The User-Agent header string.
* @param {number} options.params.logStatus - The HTTP status code of the response.
* @param {string} options.params.logStatusCode - The HTTP status message (e.g., 'OK', 'Not Found').
* @param {string} [options.userName='anonymousUser'] - The username associated with the log entry.
* @param {string} [options.logLevel='INFO'] - The log level (e.g., 'INFO', 'ERROR', 'WARN').
* @param {string} options.action - A brief description of the action being logged.
* @param {number} [options.duration] - Optional duration in milliseconds for how long the action took to complete.
* @throws {Error} Throws an error if logging fails due to invalid input or processing errors.
*/
writeLog({ params, userName = "anonymousUser", logLevel = "INFO", action, duration }) {
if (!__privateMethod(this, _LoggerService_instances, isInstanciated_fn).call(this)) return;
try {
WriteLogOptions.create({ params, userName, logLevel, action, duration });
} catch (error) {
console.log(error.toString());
return;
}
try {
const { logFrom, userIp, method, payload, headers, logTarget, userAgent, logStatus, logStatusCode } = params;
const customAction = __privateGet(this, _findLogFile) ? __privateGet(this, _findLogFile).call(this, logTarget) : action;
let parsedHeaders;
let parsedPayload;
try {
parsedHeaders = typeof headers === "string" ? JSON.parse(headers) : headers;
parsedPayload = payload ? typeof payload === "string" ? JSON.parse(payload) : payload : void 0;
} catch (parseError) {
__privateGet(this, _logger).writeLog({ logLevel: "warn", message: "Failed to parse headers or payload", meta: parseError });
return;
}
parsedPayload = parsedPayload ? __privateMethod(this, _LoggerService_instances, replaceSensitiveKeys_fn).call(this, parsedPayload, __privateGet(this, _crypt)) : parsedPayload;
parsedHeaders = __privateMethod(this, _LoggerService_instances, replaceSensitiveKeys_fn).call(this, parsedHeaders, __privateGet(this, _crypt));
const logData = {
logLevel: logLevel.toLowerCase(),
message: customAction,
meta: {
"client.ip": logFrom,
"source.name": userIp,
"user.name": userName,
"event.category": logLevel,
"event.action": customAction,
"http.request.method": method == null ? void 0 : method.toUpperCase(),
"http.request.body.content": parsedPayload,
"http.request.headers": parsedHeaders,
"url.path": logTarget,
"user_agent.original": userAgent,
"http.response.status_code": logStatus,
"http.response.status_message": logStatusCode == null ? void 0 : logStatusCode.toUpperCase(),
...duration && { "event.duration": duration }
}
};
__privateGet(this, _logger).writeLog(logData);
} catch (err) {
__privateGet(this, _logger).writeLog({
logLevel: "error",
message: "Could not log entry, please review your implementation or read the documentation and try again.",
meta: err
});
}
}
}
_crypt = new WeakMap();
_findLogFile = new WeakMap();
_logger = new WeakMap();
_apm = new WeakMap();
_appName = new WeakMap();
_hasInstance = new WeakMap();
_LoggerService_instances = new WeakSet();
/**
* Private method to check if the LoggerService has been instantiated. If not, logs a warning and returns `false`.
*
* @private
* @returns {boolean} Returns `true` if the logger service has been initialized, otherwise `false`.
*/
isInstanciated_fn = function() {
if (!__privateGet(this, _hasInstance)) {
console.warn("Warning: Logger has not been initialized. Please use `logger.bootstrap` before defining your method.");
return false;
}
return true;
};
/**
* Updates the logger configuration and reinitializes the logger and APM agent with new settings.
*
* @private
* @param {Object} options - Options to update the logger configuration.
* @param {string} [options.appName='app'] - Application name for log identification.
* @param {Array<string>} [options.crypt=[]] - List of keys to mask in the log data.
* @param {string} [options.logLevel='info'] - Log level for APM configuration.
* @param {boolean} [options.startApmAgent=false] - Whether to start the APM agent.
* @returns {void}
*/
update_fn = function({ appName = "app", crypt = [], logLevel = "info", startApmAgent = false }) {
__privateSet(this, _appName, appName);
__privateSet(this, _crypt, crypt);
__privateSet(this, _logger, LoggerFactory.createLogger({
type: "console",
logLevel
}));
__privateSet(this, _apm, Observability.createObservabilityAgent({ ...envConfig.get("apm"), logLevel, crypt, startApmAgent }));
};
/**
* Replaces sensitive keys in an object with masked values to avoid logging sensitive information.
*
* @private
* @param {Object} obj - The object containing potentially sensitive information.
* @param {Array<string>} sensitiveKeys - List of keys to mask in the object.
* @returns {Object} A new object with sensitive keys replaced by masked values.
*/
replaceSensitiveKeys_fn = function(obj, sensitiveKeys) {
const MASK = "*".repeat(15);
const isJSONParsable = (str) => {
if (typeof str !== "string") return false;
try {
JSON.parse(str);
return true;
} catch {
return false;
}
};
const replaceKeysRecursive = (data) => {
if (typeof data !== "object" || data === null) return data;
if (Array.isArray(data)) {
return data.map((item) => replaceKeysRecursive(item));
}
const result = { ...data };
for (const [key, value] of Object.entries(result)) {
if (sensitiveKeys.includes(key)) {
result[key] = MASK;
} else if (typeof value === "object") {
result[key] = replaceKeysRecursive(value);
} else if (isJSONParsable(value)) {
result[key] = JSON.stringify(replaceKeysRecursive(JSON.parse(value)));
}
}
return result;
};
return replaceKeysRecursive({ ...obj });
};
const logger = new LoggerService({});
export {
logger
};
//# sourceMappingURL=index.esm.js.map