@gati-framework/observability
Version:
Observability stack for Gati framework - Prometheus, Grafana, Loki, and Tracing
83 lines (82 loc) • 2.52 kB
JavaScript
import winston from 'winston';
import LokiTransport from 'winston-loki';
export class LokiLogger {
logger;
lokiTransport;
constructor(config) {
this.lokiTransport = new LokiTransport({
host: config.host,
basicAuth: config.basicAuth,
labels: {
app: 'gati',
...config.labels,
},
batching: config.batching !== false,
interval: config.interval || 5000,
json: true,
});
this.logger = winston.createLogger({
level: 'info',
format: winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json()),
transports: [
this.lokiTransport,
new winston.transports.Console({
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
}),
],
});
}
info(message, meta) {
this.logger.info(message, meta);
}
warn(message, meta) {
this.logger.warn(message, meta);
}
error(message, error, meta) {
this.logger.error(message, {
error: error?.message,
stack: error?.stack,
...meta,
});
}
debug(message, meta) {
this.logger.debug(message, meta);
}
child(labels) {
const transport = this.lokiTransport;
const childLogger = new LokiLogger({
host: transport.host || 'http://localhost:3100',
basicAuth: transport.basicAuth,
labels: {
...(transport.labels || {}),
...labels,
},
batching: transport.batching !== false,
interval: transport.interval || 5000,
});
return childLogger;
}
async flush() {
await new Promise((resolve) => {
this.logger.on('finish', resolve);
this.logger.end();
});
}
}
export function createLokiMiddleware(logger) {
return (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info('HTTP Request', {
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration,
userAgent: req.get('user-agent'),
ip: req.ip,
});
});
next();
};
}