azify-logger
Version:
Azify Logger Client - Centralized logging for OpenSearch
326 lines (299 loc) • 10 kB
JavaScript
const axios = require('axios')
let context, propagation, trace, W3CTraceContextPropagator
try {
const otelApi = require('@opentelemetry/api')
const otelCore = require('@opentelemetry/core')
context = otelApi.context
propagation = otelApi.propagation
trace = otelApi.trace
W3CTraceContextPropagator = otelCore.W3CTraceContextPropagator
} catch (_) {
context = { active: () => ({}) }
propagation = { setGlobalPropagator: () => {} }
trace = { getSpan: () => null }
W3CTraceContextPropagator = class {}
}
if (process.env.AZIFY_LOGGER_AUTOREG_DISABLE !== '1') {
try { require('./register-otel') } catch (_) {}
try { require('./register') } catch (_) {}
try { require('./register-restify') } catch (_) {}
}
/**
* AzifyLogger class for structured logging with OpenTelemetry integration
* @class
*/
class AzifyLogger {
/**
* Creates an instance of AzifyLogger
* @param {Object} options - Configuration options
* @param {string} [options.serviceName='azipay'] - Name of the service
* @param {string} [options.loggerUrl='http://localhost:3001'] - URL of the azify-logger service
* @param {string} [options.environment] - Environment name (defaults to NODE_ENV or 'development')
*/
constructor(options = {}) {
this.options = {
serviceName: options.serviceName || 'azipay',
loggerUrl: options.loggerUrl || 'http://localhost:3001',
environment: options.environment || process.env.NODE_ENV || 'development',
...options
}
this.propagator = new W3CTraceContextPropagator()
propagation.setGlobalPropagator(this.propagator)
}
/**
* Sends a log entry to the azify-logger service
* @param {string} level - Log level (info, error, warn, debug)
* @param {string} message - Log message
* @param {Object} [meta={}] - Additional metadata to include in the log
* @returns {Promise<void>}
*/
async log(level, message, meta = {}) {
const span = trace.getSpan(context.active())
const spanContext = span && span.spanContext()
const logData = {
level,
message,
meta: {
...meta,
service: {
name: this.options.serviceName,
version: (meta.service && meta.service.version) || '1.0.0'
},
environment: this.options.environment,
timestamp: new Date().toISOString(),
hostname: require('os').hostname()
}
}
if (spanContext) {
logData.meta.traceId = spanContext.traceId
logData.meta.spanId = spanContext.spanId
}
try {
const headers = {}
try {
propagation.inject(context.active(), headers, {
set (carrier, key, value) {
carrier[key] = value
}
})
} catch (_) {}
await axios.post(`${this.options.loggerUrl}`, logData, {
timeout: 5000,
headers
})
} catch (error) {
console.error('Erro ao enviar log:', error.message)
}
}
/**
* Logs an info level message
* @param {string} message - Log message
* @param {Object} [meta={}] - Additional metadata
* @returns {Promise<void>}
*/
info(message, meta = {}) {
return this.log('info', message, meta)
}
/**
* Logs an error level message with optional error object
* @param {string} message - Log message
* @param {Error} [error=null] - Error object to include in metadata
* @param {Object} [meta={}] - Additional metadata
* @returns {Promise<void>}
*/
error(message, error = null, meta = {}) {
const logMeta = { ...meta }
if (error) {
logMeta.error = {
message: error.message,
stack: error.stack,
name: error.name
}
}
return this.log('error', message, logMeta)
}
/**
* Logs a warning level message
* @param {string} message - Log message
* @param {Object} [meta={}] - Additional metadata
* @returns {Promise<void>}
*/
warn(message, meta = {}) {
return this.log('warn', message, meta)
}
/**
* Logs a debug level message
* @param {string} message - Log message
* @param {Object} [meta={}] - Additional metadata
* @returns {Promise<void>}
*/
debug(message, meta = {}) {
return this.log('debug', message, meta)
}
}
/**
* Creates a new AzifyLogger instance with custom options
* @param {Object} options - Configuration options
* @param {string} [options.serviceName] - Name of the service
* @param {string} [options.loggerUrl] - URL of the azify-logger service
* @param {string} [options.environment] - Environment name
* @returns {AzifyLogger} New AzifyLogger instance
* @example
* const logger = createAzifyLogger({ serviceName: 'my-app' });
* logger.info('Hello world');
*/
function createAzifyLogger(options = {}) {
return new AzifyLogger(options)
}
/**
* Creates a new AzifyLogger instance using environment variables and automatically intercepts console
* @returns {AzifyLogger} New AzifyLogger instance configured from environment
* @example
* const logger = createAzifyLoggerFromEnv();
* logger.info('Hello world');
* console.log('This will also appear in OpenSearch!');
*/
function createAzifyLoggerFromEnv() {
const logger = createAzifyLogger({
serviceName: process.env.APP_NAME || 'azipay',
loggerUrl: process.env.AZIFY_LOGGER_URL || 'http://localhost:3001',
environment: process.env.NODE_ENV || 'development'
})
interceptConsole(logger)
return logger
}
/**
* Intercepts all console methods to send logs to azify-logger
* @param {AzifyLogger} logger - AzifyLogger instance to use for logging
*/
function interceptConsole(logger) {
const originalConsoleLog = console.log;
const originalConsoleError = console.error;
const originalConsoleWarn = console.warn;
const originalConsoleInfo = console.info;
const originalConsoleDebug = console.debug;
console.log = (...args) => {
originalConsoleLog(...args);
const { getRequestContext } = require('./store');
const ctx = getRequestContext();
const message = args.join(' ');
if (message.includes('failed after')) {
}
if (ctx && ctx.traceId) {
void logger.info(message, { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId });
} else {
void logger.info(message);
}
};
console.error = (...args) => {
originalConsoleError(...args);
const { getRequestContext } = require('./store');
const ctx = getRequestContext();
if (ctx && ctx.traceId) {
void logger.error(args.join(' '), { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId });
} else {
void logger.error(args.join(' '));
}
};
console.warn = (...args) => {
originalConsoleWarn(...args);
const { getRequestContext } = require('./store');
const ctx = getRequestContext();
if (ctx && ctx.traceId) {
void logger.warn(args.join(' '), { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId });
} else {
void logger.warn(args.join(' '));
}
};
console.info = (...args) => {
originalConsoleInfo(...args);
const { getRequestContext } = require('./store');
const ctx = getRequestContext();
if (ctx && ctx.traceId) {
void logger.info(args.join(' '), { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId });
} else {
void logger.info(args.join(' '));
}
};
console.debug = (...args) => {
originalConsoleDebug(...args);
const { getRequestContext } = require('./store');
const ctx = getRequestContext();
if (ctx && ctx.traceId) {
void logger.debug(args.join(' '), { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId, requestId: ctx.requestId });
} else {
void logger.debug(args.join(' '));
}
};
}
/**
* Creates a NestJS logger configuration that sends logs to azify-logger
* @returns {Object} NestJS logger configuration
* @example
* const app = await NestFactory.create(AppModule, {
* logger: createNestLogger()
* });
*/
function createNestLogger() {
const { getRequestContext } = require('./store')
const pino = require('pino')
return {
log: (message, context) => {
const ctx = getRequestContext()
if (ctx && ctx.traceId) {
pino().info(message, context || '')
} else {
pino().info(message, context || '')
}
},
error: (message, trace, context) => {
const ctx = getRequestContext()
if (ctx && ctx.traceId) {
pino().error(message, trace || '', context || '')
} else {
pino().error(message, trace || '', context || '')
}
},
warn: (message, context) => {
const ctx = getRequestContext()
if (ctx && ctx.traceId) {
pino().warn(message, context || '')
} else {
pino().warn(message, context || '')
}
},
debug: (message, context) => {
const ctx = getRequestContext()
if (ctx && ctx.traceId) {
pino().debug(message, context || '')
} else {
pino().debug(message, context || '')
}
},
verbose: (message, context) => {
const ctx = getRequestContext()
if (ctx && ctx.traceId) {
pino().info(message, context || '')
} else {
pino().info(message, context || '')
}
}
}
}
if (process.env.AZIFY_LOGGER_DISABLE !== '1') {
require('./init')
require('./register')
}
module.exports = AzifyLogger
module.exports.createAzifyLogger = createAzifyLogger
module.exports.createAzifyLoggerFromEnv = createAzifyLoggerFromEnv
module.exports.createNestLogger = createNestLogger
module.exports.interceptConsole = interceptConsole
module.exports.streams = {
createBunyanStream: require('./streams/bunyan'),
createPinoStream: require('./streams/pino')
}
module.exports.middleware = {
restify: require('./middleware-restify'),
express: require('./middleware-express')
}