@200systems/mf-logger
Version:
Structured logging with multiple outputs and performance timing for TypeScript applications
213 lines • 7.3 kB
JavaScript
import { createOutput } from './outputs.js';
import { defaultFormatter } from './formatter.js';
import { ENV } from './environment.js';
class MicroLogger {
config;
persistentMetadata = {};
outputs = [];
constructor(config) {
this.config = config;
this.persistentMetadata = { ...config.metadata };
this.outputs = config.outputs?.map(createOutput) || [];
}
debug(message, metadata) {
if (this.shouldLog('debug')) {
this.log('debug', message, undefined, metadata);
}
}
info(message, metadata) {
if (this.shouldLog('info')) {
this.log('info', message, undefined, metadata);
}
}
warn(message, metadata) {
if (this.shouldLog('warn')) {
this.log('warn', message, undefined, metadata);
}
}
error(message, error, metadata) {
if (this.shouldLog('error')) {
this.log('error', message, error, metadata);
}
}
child(context, metadata) {
const childContext = this.config.context
? `${this.config.context}.${context}`
: context;
return new MicroLogger({
...this.config,
context: childContext,
metadata: { ...this.persistentMetadata, ...metadata }
});
}
addMetadata(key, value) {
this.persistentMetadata[key] = value;
}
removeMetadata(key) {
delete this.persistentMetadata[key];
}
clearMetadata() {
this.persistentMetadata = {};
}
startTimer(name) {
const startTime = performance.now();
const startMemory = ENV.isNode && typeof process !== 'undefined'
? process.memoryUsage()
: undefined;
return () => {
const duration = performance.now() - startTime;
const endMemory = ENV.isNode && typeof process !== 'undefined'
? process.memoryUsage()
: undefined;
this.info(`Timer ${name} completed`, {
timer: name,
duration: Math.round(duration * 100) / 100, // Round to 2 decimal places
...(startMemory && endMemory && {
memoryDelta: {
rss: endMemory.rss - startMemory.rss,
heapUsed: endMemory.heapUsed - startMemory.heapUsed,
heapTotal: endMemory.heapTotal - startMemory.heapTotal
}
})
});
};
}
// Browser-specific utility methods
retrieveStoredLogs() {
if (!ENV.isBrowser)
return null;
for (const output of this.outputs) {
if ('retrieveLogs' in output && typeof output.retrieveLogs === 'function') {
return output.retrieveLogs();
}
}
return null;
}
clearStoredLogs() {
if (!ENV.isBrowser)
return;
for (const output of this.outputs) {
if ('clearLogs' in output && typeof output.clearLogs === 'function') {
output.clearLogs();
}
}
}
shouldLog(level) {
const levels = ['debug', 'info', 'warn', 'error'];
const configLevel = this.config.level || 'info';
return levels.indexOf(level) >= levels.indexOf(configLevel);
}
log(level, message, error, metadata) {
const entry = this.createLogEntry(level, message, error, metadata);
const formatted = this.formatEntry(entry);
// Write to all outputs
for (const output of this.outputs) {
try {
const result = output.write(entry, formatted);
if (result instanceof Promise) {
result.catch(err => {
console.error('Failed to write to output:', err);
});
}
}
catch (err) {
console.error('Failed to write to output:', err);
}
}
}
createLogEntry(level, message, error, metadata) {
const entry = {
timestamp: this.config.includeTimestamp ? new Date().toISOString() : '',
level,
message,
};
if (this.config.context) {
entry.context = this.config.context;
}
// Merge persistent metadata with provided metadata
const allMetadata = { ...this.persistentMetadata, ...metadata };
if (Object.keys(allMetadata).length > 0) {
entry.metadata = allMetadata;
}
if (error) {
entry.error = {
name: error.name,
message: error.message,
code: error.code,
};
if (this.config.includeStack && error.stack) {
entry.error.stack = error.stack;
}
}
// Add memory usage if available and enabled
if (ENV.isNode && typeof process !== 'undefined' && process.memoryUsage) {
entry.performance = {
memory: process.memoryUsage(),
};
}
return entry;
}
formatEntry(entry) {
return this.config.format === 'json'
? defaultFormatter.formatJson(entry)
: defaultFormatter.formatPretty(entry);
}
}
// Environment-aware default configuration
function getDefaultConfig() {
const processEnv = (typeof process !== 'undefined' && process.env) || {};
const level = processEnv.LOG_LEVEL || 'info';
const format = processEnv.LOG_FORMAT || 'pretty';
return {
level,
format,
includeTimestamp: true,
includeStack: !ENV.isBrowser && processEnv.NODE_ENV !== 'production',
outputs: [{
type: 'console',
options: ENV.isBrowser ? { enhanced: true } : {}
}],
};
}
// Factory function with environment detection
export function createLogger(config = {}) {
const defaultConfig = getDefaultConfig();
const finalConfig = {
...defaultConfig,
...config,
};
return new MicroLogger(finalConfig);
}
// Browser-specific factory with enhanced defaults
export function createBrowserLogger(config = {}) {
const browserDefaults = {
format: 'pretty',
includeTimestamp: true,
includeStack: false,
outputs: [
{ type: 'console', options: { enhanced: true } },
...(config.outputs || [])
]
};
return createLogger({ ...browserDefaults, ...config });
}
// Node.js-specific factory with enhanced defaults
export function createServerLogger(config = {}) {
const processEnv = (typeof process !== 'undefined' && process.env) || {};
const level = processEnv.LOG_LEVEL || 'info';
const format = processEnv.LOG_FORMAT || 'pretty';
const serverDefaults = {
level,
format,
includeTimestamp: true,
includeStack: !ENV.isBrowser && processEnv.NODE_ENV !== 'production',
outputs: [{
type: 'console',
options: ENV.isBrowser ? { enhanced: true } : {}
}],
};
return createLogger({ ...serverDefaults, ...config });
}
// Convenience export
export const defaultConfig = getDefaultConfig();
//# sourceMappingURL=logger.js.map