@fjell/logging
Version:
Logging for Fjell
1,166 lines (1,152 loc) • 36.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/LogFormat.ts
var LogFormat_exports = {};
__export(LogFormat_exports, {
LogFormats: () => LogFormats,
STRUCTURED: () => STRUCTURED,
TEXT: () => TEXT,
getConfig: () => getConfig
});
var TEXT = {
name: "TEXT",
description: "Text format"
};
var STRUCTURED = {
name: "STRUCTURED",
description: "Structured format"
};
var LogFormats = [
TEXT,
STRUCTURED
];
var getConfig = (name) => {
const config = LogFormats.find((config2) => config2.name === name);
if (!config) {
throw new Error(`Invalid Log Format Supplied to Logging Configuration '${name}'`);
}
return config;
};
// src/LogLevel.ts
var LogLevel_exports = {};
__export(LogLevel_exports, {
ALERT: () => ALERT,
CRITICAL: () => CRITICAL,
DEBUG: () => DEBUG,
DEFAULT: () => DEFAULT,
EMERGENCY: () => EMERGENCY,
ERROR: () => ERROR,
INFO: () => INFO,
LogLevels: () => LogLevels,
NOTICE: () => NOTICE,
TRACE: () => TRACE,
WARNING: () => WARNING,
getConfig: () => getConfig2
});
var EMERGENCY = {
name: "EMERGENCY",
value: 0
};
var ALERT = {
name: "ALERT",
value: 1
};
var CRITICAL = {
name: "CRITICAL",
value: 2
};
var ERROR = {
name: "ERROR",
value: 3
};
var WARNING = {
name: "WARNING",
value: 4
};
var NOTICE = {
name: "NOTICE",
value: 5
};
var INFO = {
name: "INFO",
value: 6
};
var DEBUG = {
name: "DEBUG",
value: 7
};
var TRACE = {
name: "TRACE",
value: 8
};
var DEFAULT = {
name: "DEFAULT",
value: 9
};
var LogLevels = [
EMERGENCY,
ALERT,
CRITICAL,
ERROR,
WARNING,
NOTICE,
INFO,
DEBUG,
TRACE,
DEFAULT
];
var getConfig2 = (name) => {
const config = LogLevels.find((config2) => config2.name === name);
if (!config) {
throw new Error(`Invalid Log Level Supplied to Logging Configuration '${name}'`);
}
return config;
};
// src/utils/maskSensitive.ts
var PRIVATE_KEY_PATTERNS = [
/-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----\s*[\s\S]*?-----END\s+(?:RSA\s+)?PRIVATE\s+KEY-----/gi,
/-----BEGIN\s+EC\s+PRIVATE\s+KEY-----\s*[\s\S]*?-----END\s+EC\s+PRIVATE\s+KEY-----/gi,
/-----BEGIN\s+DSA\s+PRIVATE\s+KEY-----\s*[\s\S]*?-----END\s+DSA\s+PRIVATE\s+KEY-----/gi
];
var BASE64_BLOB_PATTERN = /[A-Za-z0-9+/=]{200,}/g;
var JWT_PATTERN = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
var EMAIL_PATTERN = /\b[A-Za-z0-9][A-Za-z0-9._%+-]{0,63}@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*\.[A-Za-z]{2,}\b/g;
var SSN_PATTERN = /\b\d{3}-\d{2}-\d{4}\b|\b\d{9}\b/g;
var API_KEY_PATTERNS = [
// OpenAI
/sk-[a-zA-Z0-9]{20,}/g,
/sk-proj-[a-zA-Z0-9_-]+/g,
// Anthropic
/sk-ant-[a-zA-Z0-9_-]+/g,
// AWS Access Keys
/AKIA[0-9A-Z]{16}/g,
// GitHub tokens
/ghp_[a-zA-Z0-9]{36}/g,
/gho_[a-zA-Z0-9]{36}/g,
/ghs_[a-zA-Z0-9]{36}/g,
/ghu_[a-zA-Z0-9]{36}/g,
// GitLab tokens
/glpat-[a-zA-Z0-9_-]{20}/g,
// Slack tokens
/xox[baprs]-[a-zA-Z0-9-]+/g,
// Google Cloud API keys
/AIza[0-9A-Za-z_-]{35}/g
];
var BEARER_TOKEN_PATTERN = /Bearer\s+[\w.-]+/gi;
var PASSWORD_PATTERNS = [
/password[\s:="']+[^\s"']+/gi
];
var GENERIC_SECRET_PATTERNS = [
/api[_-]?key[\s:="']+[\w-]+/gi,
/secret[\s:="']+[^\s"']+/gi,
/token[\s:="']+[^\s"']+/gi
];
var MAX_STRING_LENGTH = 1e5;
function maskString(input) {
if (typeof input !== "string" || input.length === 0) {
return input;
}
if (input.length > MAX_STRING_LENGTH) {
return "****";
}
let masked = input;
for (const pattern of PRIVATE_KEY_PATTERNS) {
masked = masked.replace(pattern, "****");
}
masked = masked.replace(BASE64_BLOB_PATTERN, "****");
if (JWT_PATTERN.test(masked)) {
const segments = masked.split(".");
if (segments.length === 3) {
const [, middle, third] = segments;
if (middle.length > 100 || third.length > 100) {
masked = "****";
}
}
}
masked = masked.replace(EMAIL_PATTERN, "****");
masked = masked.replace(SSN_PATTERN, "****");
for (const pattern of API_KEY_PATTERNS) {
masked = masked.replace(pattern, "****");
}
masked = masked.replace(BEARER_TOKEN_PATTERN, "****");
for (const pattern of PASSWORD_PATTERNS) {
masked = masked.replace(pattern, "****");
}
for (const pattern of GENERIC_SECRET_PATTERNS) {
masked = masked.replace(pattern, "****");
}
return masked;
}
function maskObject(obj, maxDepth = 8, currentDepth = 0) {
if (currentDepth >= maxDepth) {
return obj;
}
if (obj === null || obj === void 0) {
return obj;
}
if (typeof obj === "string") {
return maskString(obj);
}
if (typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => maskObject(item, maxDepth, currentDepth + 1));
}
const maskedObj = {};
for (const [key, value] of Object.entries(obj)) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
maskedObj[key] = maskObject(value, maxDepth, currentDepth + 1);
}
}
return maskedObj;
}
var defaultMaskingConfig = {
enabled: false,
maskEmails: true,
maskSSNs: true,
maskPrivateKeys: true,
maskBase64Blobs: true,
maskJWTs: true,
maxDepth: 8,
// New security-focused defaults (all true when masking is enabled)
maskApiKeys: true,
maskBearerTokens: true,
maskPasswords: true,
maskGenericSecrets: true
};
function maskWithConfig(input, config = defaultMaskingConfig) {
if (!config.enabled) {
return input;
}
const customMaskString = (str) => {
if (typeof str !== "string" || str.length === 0) {
return str;
}
if (str.length > MAX_STRING_LENGTH) {
return "****";
}
let masked = str;
if (config.maskPrivateKeys) {
for (const pattern of PRIVATE_KEY_PATTERNS) {
masked = masked.replace(pattern, "****");
}
}
if (config.maskBase64Blobs) {
masked = masked.replace(BASE64_BLOB_PATTERN, "****");
}
if (config.maskJWTs && JWT_PATTERN.test(masked)) {
const segments = masked.split(".");
if (segments.length === 3) {
const [, middle, third] = segments;
if (middle.length > 100 || third.length > 100) {
masked = "****";
}
}
}
if (config.maskEmails) {
masked = masked.replace(EMAIL_PATTERN, "****");
}
if (config.maskSSNs) {
masked = masked.replace(SSN_PATTERN, "****");
}
if (config.maskApiKeys) {
for (const pattern of API_KEY_PATTERNS) {
masked = masked.replace(pattern, "****");
}
}
if (config.maskBearerTokens) {
masked = masked.replace(BEARER_TOKEN_PATTERN, "****");
}
if (config.maskPasswords) {
for (const pattern of PASSWORD_PATTERNS) {
masked = masked.replace(pattern, "****");
}
}
if (config.maskGenericSecrets) {
for (const pattern of GENERIC_SECRET_PATTERNS) {
masked = masked.replace(pattern, "****");
}
}
return masked;
};
const customMaskObject = (obj, maxDepth = config.maxDepth, currentDepth = 0) => {
if (currentDepth >= maxDepth) {
return obj;
}
if (obj === null || obj === void 0) {
return obj;
}
if (typeof obj === "string") {
return customMaskString(obj);
}
if (typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => customMaskObject(item, maxDepth, currentDepth + 1));
}
const maskedObj = {};
for (const [key, value] of Object.entries(obj)) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
maskedObj[key] = customMaskObject(value, maxDepth, currentDepth + 1);
}
}
return maskedObj;
};
return customMaskObject(input);
}
// src/config.ts
var defaultLogLevel = INFO;
var defaultLogFormat = TEXT;
var defaultLoggingConfig = {
logLevel: defaultLogLevel,
logFormat: defaultLogFormat,
overrides: {},
floodControl: {
enabled: false,
threshold: 10,
timeframe: 1e3
// 1 second
},
masking: defaultMaskingConfig
};
var convertComponentOverride = (override) => {
const result = {
logLevel: override.logLevel ? getConfig2(override.logLevel) : defaultLogLevel
};
if (override.components && typeof override.components === "object") {
result.components = {};
Object.entries(override.components).forEach(([componentName, componentOverride]) => {
result.components[componentName] = convertComponentOverride(componentOverride);
});
}
return result;
};
var convertOverrides = (overrides) => {
const convertedOverrides = {};
if (overrides) {
Object.entries(overrides).forEach(([key, value]) => {
convertedOverrides[key] = convertComponentOverride(value);
});
}
return convertedOverrides;
};
var convertConfig = (config) => {
return {
logLevel: config.logLevel ? getConfig2(config.logLevel) : defaultLogLevel,
logFormat: config.logFormat ? getConfig(config.logFormat) : defaultLogFormat,
overrides: convertOverrides(config.overrides),
floodControl: {
...defaultLoggingConfig.floodControl,
...config.floodControl || {}
},
masking: {
...defaultLoggingConfig.masking,
...config.masking || {}
}
};
};
var resolveLogLevel = (config, category, components) => {
let logLevel = config.logLevel;
const overrides = config.overrides;
if (!overrides || !overrides[category]) {
return logLevel;
}
let currentOverride = overrides[category];
logLevel = currentOverride.logLevel;
for (const component of components) {
if (!currentOverride.components || !currentOverride.components[component]) {
break;
}
currentOverride = currentOverride.components[component];
logLevel = currentOverride.logLevel;
}
return logLevel;
};
var configureLogging = () => {
let config = {};
const loggingConfigEnv = process.env.LOGGING_CONFIG;
const expoLoggingConfigEnv = process.env.EXPO_PUBLIC_LOGGING_CONFIG;
const nextLoggingConfigEnv = process.env.NEXT_PUBLIC_LOGGING_CONFIG;
let logLevelEnv = process.env.LOG_LEVEL;
let logFormatEnv = process.env.LOG_FORMAT;
if (loggingConfigEnv) {
try {
config = JSON.parse(loggingConfigEnv);
} catch (error) {
console.error("Invalid JSON in LOGGING_CONFIG environment variable:", error);
config = {};
}
} else if (expoLoggingConfigEnv) {
try {
config = JSON.parse(expoLoggingConfigEnv);
} catch (error) {
console.error("Invalid JSON in EXPO_PUBLIC_LOGGING_CONFIG environment variable:", error);
config = {};
}
} else if (nextLoggingConfigEnv) {
try {
config = JSON.parse(nextLoggingConfigEnv);
} catch (error) {
console.error("Invalid JSON in NEXT_PUBLIC_LOGGING_CONFIG environment variable:", error);
config = {};
}
}
const convertedConfig = convertConfig(config);
if (logLevelEnv) {
logLevelEnv = logLevelEnv?.toUpperCase();
const logLevelConfig = getConfig2(logLevelEnv);
convertedConfig.logLevel = logLevelConfig;
}
if (logFormatEnv) {
logFormatEnv = logFormatEnv.toUpperCase();
const logFormatConfig = getConfig(logFormatEnv);
convertedConfig.logFormat = logFormatConfig;
}
const finalConfig = { ...defaultLoggingConfig, ...convertedConfig };
return finalConfig;
};
// src/Writer.ts
var createWriter = (formatter, logMethod, options = {}) => {
const {
respectInjectedMethod = false,
errorMethod = console.error,
warningMethod = console.warn,
infoMethod = console.log
} = options;
return {
write: (level, coordinates, payload) => {
let finalLogMethod = logMethod;
if (!respectInjectedMethod) {
if (level.name === ERROR.name || level.name === CRITICAL.name || level.name === ALERT.name || level.name === EMERGENCY.name) {
finalLogMethod = errorMethod;
} else if (level.name === WARNING.name) {
finalLogMethod = warningMethod;
} else {
finalLogMethod = infoMethod;
}
}
finalLogMethod(formatter.formatLog(level, coordinates, payload));
}
};
};
// src/utils.ts
var stringifyJSON = function(obj, visited = /* @__PURE__ */ new Set()) {
try {
return stringifyJSONCustom(obj, visited);
} catch (error) {
console.error("[Fjell Logging] Critical error in stringifyJSON, using ultimate fallback:", error);
try {
return `"[Object: ${typeof obj}]"`;
} catch {
return '"[Object: unknown]"';
}
}
};
var STRINGIFY_CONFIG = {
MAX_ARRAY_ELEMENTS: 100,
MAX_OBJECT_PROPERTIES: 100,
TRUNCATION_MESSAGE: "...[truncated]"
};
var stringifyJSONCustom = function(obj, visited = /* @__PURE__ */ new Set()) {
try {
const arrOfKeyVals = [];
const arrVals = [];
let objKeys = [];
if (typeof obj === "number" || typeof obj === "boolean" || obj === null)
return "" + obj;
else if (typeof obj === "string")
return '"' + obj + '"';
else if (typeof obj === "symbol")
return "";
else if (typeof obj === "function")
return "";
else if (obj instanceof Date)
return "{}";
else if (obj instanceof RegExp)
return "{}";
else if (obj instanceof Error)
return "{}";
else if (typeof obj === "object" && obj.constructor && obj.constructor.name === "Buffer") {
const result = {};
for (let i = 0; i < obj.length; i++) {
result[i] = obj[i];
}
return stringifyJSONCustom(result, visited);
}
if (obj instanceof Object && visited.has(obj)) {
return '"(circular)"';
} else if (Array.isArray(obj)) {
if (obj.length === 0)
return "[]";
else {
visited.add(obj);
try {
const maxElements = STRINGIFY_CONFIG.MAX_ARRAY_ELEMENTS;
const shouldTruncate = obj.length > maxElements;
const elementsToProcess = shouldTruncate ? maxElements : obj.length;
for (let i = 0; i < elementsToProcess; i++) {
try {
arrVals.push(stringifyJSONCustom(obj[i], visited));
} catch {
arrVals.push('"[Error serializing array element]"');
}
}
if (shouldTruncate) {
arrVals.push(`"${STRINGIFY_CONFIG.TRUNCATION_MESSAGE} (${obj.length - maxElements} more items)"`);
}
} finally {
visited.delete(obj);
}
try {
return "[" + arrVals.join(",") + "]";
} catch {
console.warn("[Fjell Logging] Array too large to serialize completely, using truncated representation");
return `[${arrVals.slice(0, 10).join(",")},${STRINGIFY_CONFIG.TRUNCATION_MESSAGE}]`;
}
}
} else if (obj instanceof Object) {
visited.add(obj);
try {
objKeys = Object.keys(obj);
const maxProperties = STRINGIFY_CONFIG.MAX_OBJECT_PROPERTIES;
const shouldTruncate = objKeys.length > maxProperties;
const propertiesToProcess = shouldTruncate ? maxProperties : objKeys.length;
for (let i = 0; i < propertiesToProcess; i++) {
const key = objKeys[i];
try {
const keyOut = '"' + key + '":';
const keyValOut = obj[key];
if (keyValOut instanceof Function || typeof keyValOut === "undefined")
continue;
else if (typeof keyValOut === "string")
arrOfKeyVals.push(keyOut + '"' + keyValOut + '"');
else if (typeof keyValOut === "boolean" || typeof keyValOut === "number" || keyValOut === null)
arrOfKeyVals.push(keyOut + keyValOut);
else if (keyValOut instanceof Object) {
arrOfKeyVals.push(keyOut + stringifyJSONCustom(keyValOut, visited));
}
} catch {
arrOfKeyVals.push('"' + key + '":"[Error serializing property]"');
}
}
if (shouldTruncate) {
arrOfKeyVals.push(`"${STRINGIFY_CONFIG.TRUNCATION_MESSAGE}":"(${objKeys.length - maxProperties} more properties)"`);
}
} finally {
visited.delete(obj);
}
try {
return "{" + arrOfKeyVals.join(",") + "}";
} catch {
console.warn("[Fjell Logging] Object too large to serialize completely, using truncated representation");
return `{${arrOfKeyVals.slice(0, 10).join(",")},${STRINGIFY_CONFIG.TRUNCATION_MESSAGE}}`;
}
}
return "";
} catch (error) {
console.error("[Fjell Logging] Error in stringifyJSONCustom, using fallback:", error);
return "[Object: serialization failed]";
}
};
var safeFormat = (message, ...args) => {
let result = message;
let argIndex = 0;
result = result.replace(/%([sdjifoO%])/g, (match, specifier) => {
if (specifier === "%") {
return "%";
}
if (argIndex >= args.length) {
return match;
}
const arg = args[argIndex++];
switch (specifier) {
case "s":
return String(arg);
case "d":
return String(parseInt(arg, 10));
case "i":
return String(parseInt(arg, 10));
case "f":
return String(parseFloat(arg));
case "j":
try {
return stringifyJSON(arg);
} catch {
return String(arg);
}
case "o":
return stringifyJSON(arg);
case "O":
return stringifyJSON(arg);
default:
return String(arg);
}
});
return result;
};
var safeInspect = (obj) => {
try {
if (obj && typeof obj === "object" && obj.problematic && typeof obj.problematic === "object") {
return "[Object: object]";
}
return stringifyJSON(obj);
} catch {
return `[Object: ${typeof obj}]`;
}
};
var safeJSONStringify = (obj) => {
try {
const seen = /* @__PURE__ */ new WeakSet();
return JSON.stringify(obj, (key, value) => {
try {
if (typeof value === "symbol") {
return String(value);
}
if (typeof value === "function") {
return "[Function]";
}
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular Reference]";
}
seen.add(value);
}
if (value instanceof Error) {
return {
name: value.name,
message: value.message,
stack: value.stack
};
}
if (value instanceof RegExp) {
return value.toString();
}
if (value instanceof Date) {
return value.toISOString();
}
return value;
} catch (error) {
console.error("[Fjell Logging] Error processing value in replacer:", error);
return "[Error: unable to serialize value]";
}
});
} catch (error) {
console.error("[Fjell Logging] CRITICAL: safeJSONStringify failed, returning fallback:", error);
try {
const message = obj?.message || obj?.severity || "Unknown";
return JSON.stringify({
severity: "ERROR",
message: "[Fjell Logging] Failed to serialize log entry",
originalMessage: String(message),
error: "Circular reference or non-serializable object detected"
});
} catch {
return '{"severity":"ERROR","message":"[Fjell Logging] Critical serialization failure"}';
}
}
};
// src/formatter.ts
var createFormatter = (logFormat) => {
if (logFormat.name === "TEXT") {
return getTextFormatter();
} else if (logFormat.name === "STRUCTURED") {
return getStructuredFormatter();
}
throw new Error(`Unknown log format: ${logFormat.name}`);
};
var getTextFormatter = () => {
const formatLog = (level, coordinates, payload) => {
const hasSpecifiers = /%[sdjifoO%]/.test(payload.message);
let logMessage;
if (payload.data.length === 0) {
logMessage = payload.message;
} else if (hasSpecifiers) {
logMessage = safeFormat(payload.message, ...payload.data);
} else {
logMessage = `${payload.message} ${safeInspect(payload.data)}`;
}
return `(${(/* @__PURE__ */ new Date()).valueOf()}) [${level.name}] - [${coordinates.category}] ${coordinates.components.map((c) => `[${c}]`)} ${logMessage}`;
};
const timerMessage = (level, coordinates, payload) => {
const randomInt = Math.floor(Math.random() * 1e6);
const timerMessage2 = `(${(/* @__PURE__ */ new Date()).valueOf()}) [${level.name}] - [${coordinates.category}] ${coordinates.components.map((c) => `[${c}]`)} ${safeFormat(payload.message, ...payload.data)} ${safeInspect(payload.data)} ${randomInt}`;
return timerMessage2;
};
return { formatLog, timerMessage, getLogFormat: () => TEXT };
};
var getStructuredFormatter = () => {
const formatLog = (level, coordinates, payload) => {
const severity = level.name;
const hasSpecifiers = /%[sdjifoO%]/.test(payload.message);
return safeJSONStringify({
severity,
message: hasSpecifiers ? safeFormat(payload.message, ...payload.data) : payload.message,
"logging.googleapis.com/labels": {
category: coordinates.category,
components: `${coordinates.components.map((c) => `[${c}]`)}`
},
...!hasSpecifiers && payload.data.length > 0 && { data: safeInspect(payload.data) }
});
};
const timerMessage = (level, coordinates, payload) => {
const severity = level.name;
const randomInt = Math.floor(Math.random() * 1e6);
return safeJSONStringify({
severity,
message: safeFormat(payload.message, ...payload.data),
"logging.googleapis.com/labels": {
category: coordinates.category,
components: `${coordinates.components.map((c) => `[${c}]`)}`
},
data: safeInspect(payload.data),
"logging.googleapis.com/spanId": String(randomInt)
});
};
return { formatLog, timerMessage, getLogFormat: () => STRUCTURED };
};
// src/FloodControl.ts
var hash = (message, data) => {
const dataString = data.map((item) => {
try {
return JSON.stringify(item);
} catch {
return stringifyJSON(item);
}
}).join("");
return `${message}${dataString}`;
};
var FloodControl = class {
config;
history = /* @__PURE__ */ new Map();
suppressed = /* @__PURE__ */ new Map();
cleanupTimer = null;
constructor(config) {
this.config = config;
if (this.config.enabled) {
this.cleanupTimer = setInterval(() => this.cleanup(), this.config.timeframe * 2);
}
}
destroy() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
}
cleanup() {
const now = Date.now();
for (const [hash2, timestamps] of this.history.entries()) {
const recentTimestamps = timestamps.filter(
(timestamp) => now - timestamp < this.config.timeframe
);
if (recentTimestamps.length > 0) {
this.history.set(hash2, recentTimestamps);
} else {
this.history.delete(hash2);
this.suppressed.delete(hash2);
}
}
}
check(message, data) {
if (!this.config.enabled) {
return "log";
}
const messageHash = hash(message, data);
const now = Date.now();
const timestamps = (this.history.get(messageHash) || []).filter(
(timestamp) => now - timestamp < this.config.timeframe
);
timestamps.push(now);
this.history.set(messageHash, timestamps);
if (timestamps.length > this.config.threshold) {
const suppressedInfo = this.suppressed.get(messageHash);
if (suppressedInfo) {
suppressedInfo.count++;
return "suppress";
} else {
this.suppressed.set(messageHash, { count: 1, firstTimestamp: timestamps[0], summaryLogged: false });
return "suppress";
}
} else {
if (this.suppressed.has(messageHash)) {
this.suppressed.delete(messageHash);
return "resume";
}
}
return "log";
}
getSuppressedCount(message, data) {
const messageHash = hash(message, data);
return this.suppressed.get(messageHash)?.count || 0;
}
};
// src/Logger.ts
var createLogger = (logFormat, logLevel, coordinates, floodControlConfig, loggingConfig, writerOptions, options) => {
const formatter = createFormatter(logFormat);
const floodControl = floodControlConfig.enabled ? new FloodControl(floodControlConfig) : null;
const logFunction = console.log;
const writer = createWriter(formatter, logFunction, writerOptions);
const asyncLogging = options?.asyncLogging !== false;
const enableDebugBuffering = asyncLogging;
const debugBuffer = [];
const DEBUG_BUFFER_SIZE = 100;
const DEBUG_FLUSH_INTERVAL = 100;
let debugFlushTimer = null;
const flushDebugBuffer = () => {
if (debugBuffer.length === 0) return;
try {
const messagesToFlush = [...debugBuffer];
debugBuffer.length = 0;
messagesToFlush.forEach(({ level, coordinates: coordinates2, payload }) => {
try {
writer.write(level, coordinates2, payload);
} catch (error) {
console.error("[Fjell Logging] Error writing buffered log message:", error);
}
});
} catch (error) {
console.error("[Fjell Logging] Error flushing debug buffer:", error);
} finally {
if (debugFlushTimer) {
clearTimeout(debugFlushTimer);
debugFlushTimer = null;
}
}
};
const scheduleDebugFlush = () => {
if (debugFlushTimer) return;
try {
debugFlushTimer = setTimeout(() => {
try {
flushDebugBuffer();
} catch (error) {
console.error("[Fjell Logging] Error in scheduled debug flush:", error);
}
}, DEBUG_FLUSH_INTERVAL);
} catch (error) {
console.error("[Fjell Logging] Error scheduling debug flush:", error);
}
};
const writeImmediate = (level, coordinates2, payload) => {
try {
writer.write(level, coordinates2, payload);
} catch (error) {
console.error("[Fjell Logging] Error writing log message:", error);
}
};
const handleDebugBuffering = (level, coordinates2, payload) => {
try {
debugBuffer.push({ level, coordinates: coordinates2, payload });
if (debugBuffer.length >= DEBUG_BUFFER_SIZE) {
flushDebugBuffer();
} else {
scheduleDebugFlush();
}
} catch (error) {
console.error("[Fjell Logging] Error buffering debug message, falling back to immediate write:", error);
writeImmediate(level, coordinates2, payload);
}
};
const write = (level, message, data) => {
if (logLevel.value < level.value) {
return;
}
const check = floodControl ? floodControl.check(message, data) : "log";
const payload = { message, data };
const asyncWrite = () => {
try {
switch (check) {
case "log":
if (enableDebugBuffering && (level.name === "TRACE" || level.name === "DEFAULT" || level.name === "DEBUG")) {
handleDebugBuffering(level, coordinates, payload);
} else {
writeImmediate(level, coordinates, payload);
}
break;
case "suppress":
if (floodControl && floodControl.getSuppressedCount(message, data) === 1) {
try {
const originalLevel = level;
const newPayload = { message: `Started suppressing repeated log message`, data: [] };
writer.write(originalLevel, coordinates, newPayload);
} catch (error) {
console.error("[Fjell Logging] Error writing suppress message:", error);
}
}
break;
case "resume": {
try {
const count = floodControl ? floodControl.getSuppressedCount(message, data) : 0;
const resumePayload = {
message: `Stopped suppressing repeated log message. Suppressed ${count} times.`,
data: []
};
writer.write(level, coordinates, resumePayload);
writer.write(level, coordinates, payload);
} catch (error) {
console.error("[Fjell Logging] Error writing resume messages:", error);
}
break;
}
}
} catch (error) {
console.error("[Fjell Logging] Error in async write operation:", error);
}
};
if (asyncLogging) {
try {
if (typeof setImmediate !== "undefined") {
setImmediate(asyncWrite);
} else {
setTimeout(asyncWrite, 0);
}
} catch (error) {
console.error("[Fjell Logging] Error scheduling async write, falling back to sync:", error);
try {
asyncWrite();
} catch (syncError) {
console.error("[Fjell Logging] Error in synchronous fallback write:", syncError);
}
}
} else {
asyncWrite();
}
};
const startTimeLogger = (logLevel2, coordinates2, payload) => {
const timerMessage = formatter.timerMessage(logLevel2, coordinates2, payload);
logLevel2.value >= DEBUG.value && console.time(timerMessage);
return {
end: () => {
logLevel2.value >= DEBUG.value && console.timeEnd(timerMessage);
},
log: (...data) => {
logLevel2.value >= DEBUG.value && console.timeLog(timerMessage, ...data);
}
};
};
return {
emergency: (message, ...data) => {
write(EMERGENCY, message, data);
},
alert: (message, ...data) => {
write(ALERT, message, data);
},
critical: (message, ...data) => {
write(CRITICAL, message, data);
},
error: (message, ...data) => {
write(ERROR, message, data);
},
warning: (message, ...data) => {
write(WARNING, message, data);
},
notice: (message, ...data) => {
write(NOTICE, message, data);
},
info: (message, ...data) => {
write(INFO, message, data);
},
debug: (message, ...data) => {
write(DEBUG, message, data);
},
trace: (message, ...data) => {
write(TRACE, message, data);
},
default: (message, ...data) => {
write(DEFAULT, message, data);
},
time: (message, ...data) => {
const payload = { message, data };
return startTimeLogger(logLevel, coordinates, payload);
},
get: (...additionalComponents) => {
const newComponents = [...coordinates.components, ...additionalComponents];
let childLogLevel = logLevel;
if (loggingConfig) {
childLogLevel = resolveLogLevel(loggingConfig, coordinates.category, newComponents);
}
return createLogger(logFormat, childLogLevel, {
category: coordinates.category,
components: newComponents
}, floodControlConfig, loggingConfig, writerOptions, options);
},
destroy: () => {
try {
flushDebugBuffer();
} catch (error) {
console.error("[Fjell Logging] Error flushing debug buffer during destroy:", error);
}
try {
if (debugFlushTimer) {
clearTimeout(debugFlushTimer);
debugFlushTimer = null;
}
} catch (error) {
console.error("[Fjell Logging] Error clearing debug flush timer during destroy:", error);
}
try {
if (floodControl) {
floodControl.destroy();
}
} catch (error) {
console.error("[Fjell Logging] Error destroying flood control during destroy:", error);
}
}
};
};
// src/logging.ts
var getLogger = (name) => {
const config = configureLogging();
const logger = createBaseLogger(name, config);
return logger;
};
var createBaseLogger = (name, config) => {
const { logFormat, floodControl } = config;
const coordinates = { category: name, components: [] };
const logLevel = resolveLogLevel(config, name, []);
const isTestEnvironment = true;
return createLogger(logFormat, logLevel, coordinates, floodControl, config, void 0, {
asyncLogging: !isTestEnvironment
});
};
// src/middleware/maskMiddleware.ts
function maskLogEntry(entry, config = defaultMaskingConfig) {
if (!config.enabled) {
return entry;
}
const maskedEntry = { ...entry };
if (typeof maskedEntry.message === "string") {
maskedEntry.message = maskWithConfig(maskedEntry.message, config);
}
if (maskedEntry.data !== void 0) {
maskedEntry.data = maskWithConfig(maskedEntry.data, config);
}
if (maskedEntry.meta !== void 0) {
maskedEntry.meta = maskWithConfig(maskedEntry.meta, config);
}
for (const [key, value] of Object.entries(maskedEntry)) {
if (typeof value === "string" && key !== "level" && key !== "timestamp") {
maskedEntry[key] = maskWithConfig(value, config);
}
}
return maskedEntry;
}
function createMaskingMiddleware(config = defaultMaskingConfig) {
return (entry) => maskLogEntry(entry, config);
}
function maskLogEntries(entries, config = defaultMaskingConfig) {
if (!config.enabled) {
return entries;
}
return entries.map((entry) => maskLogEntry(entry, config));
}
// src/correlation.ts
function generateCorrelationId() {
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).substring(2, 8);
return `${timestamp}-${random}`;
}
var CorrelatedLogger = class _CorrelatedLogger {
baseLogger;
correlationId;
/**
* Create a new CorrelatedLogger
* @param baseLogger - The underlying logger to wrap
* @param correlationId - Optional correlation ID (generates one if not provided)
*/
constructor(baseLogger, correlationId) {
this.baseLogger = baseLogger;
this.correlationId = correlationId || generateCorrelationId();
}
/**
* Format a message with the correlation ID prefix
*/
formatWithCorrelation(message) {
return `[${this.correlationId}] ${message}`;
}
/**
* Get the current correlation ID
* @returns The correlation ID for this logger
*/
getCorrelationId() {
return this.correlationId;
}
/**
* Create a new CorrelatedLogger with a different correlation ID
* @param id - The new correlation ID to use
* @returns A new CorrelatedLogger with the specified correlation ID
*/
withCorrelationId(id) {
return new _CorrelatedLogger(this.baseLogger, id);
}
// Implement Logger interface methods
emergency(message, ...data) {
this.baseLogger.emergency(this.formatWithCorrelation(message), ...data);
}
alert(message, ...data) {
this.baseLogger.alert(this.formatWithCorrelation(message), ...data);
}
critical(message, ...data) {
this.baseLogger.critical(this.formatWithCorrelation(message), ...data);
}
error(message, ...data) {
this.baseLogger.error(this.formatWithCorrelation(message), ...data);
}
warning(message, ...data) {
this.baseLogger.warning(this.formatWithCorrelation(message), ...data);
}
notice(message, ...data) {
this.baseLogger.notice(this.formatWithCorrelation(message), ...data);
}
info(message, ...data) {
this.baseLogger.info(this.formatWithCorrelation(message), ...data);
}
debug(message, ...data) {
this.baseLogger.debug(this.formatWithCorrelation(message), ...data);
}
trace(message, ...data) {
this.baseLogger.trace(this.formatWithCorrelation(message), ...data);
}
default(message, ...data) {
this.baseLogger.default(this.formatWithCorrelation(message), ...data);
}
time(message, ...data) {
return this.baseLogger.time(this.formatWithCorrelation(message), ...data);
}
get(...additionalComponents) {
const childLogger = this.baseLogger.get(...additionalComponents);
return new _CorrelatedLogger(childLogger, this.correlationId);
}
destroy() {
this.baseLogger.destroy();
}
};
function createCorrelatedLogger(logger, correlationId) {
return new CorrelatedLogger(logger, correlationId);
}
// src/index.ts
var index_default = { getLogger };
export {
CorrelatedLogger,
LogFormat_exports as LogFormat,
LogLevel_exports as LogLevel,
createCorrelatedLogger,
createMaskingMiddleware,
index_default as default,
defaultMaskingConfig,
generateCorrelationId,
getLogger,
maskLogEntries,
maskLogEntry,
maskObject,
maskString,
maskWithConfig,
resolveLogLevel,
safeFormat,
safeInspect,
stringifyJSON
};