no idea- added loki and other stuff to market-data-gateway, also added config lib

This commit is contained in:
Bojan Kucera 2025-06-03 11:37:58 -04:00
parent b957fb99aa
commit 1b71fc87ab
72 changed files with 6178 additions and 153 deletions

View file

@ -1,3 +1,4 @@
export * from './dateUtils';
export * from './financialUtils';
export * from './logger';
export * from './lokiClient';

View file

@ -1,6 +1,20 @@
/**
* Logger utility with consistent formatting and log levels
* Supports console and Loki logging
*/
import { loggingConfig } from '@stock-bot/config';
import { LokiClient } from './lokiClient';
// Singleton Loki client
let lokiClient: LokiClient | null = null;
function getLokiClient(): LokiClient {
if (!lokiClient) {
lokiClient = new LokiClient(loggingConfig);
}
return lokiClient;
}
export class Logger {
constructor(private serviceName: string, private level: LogLevel = LogLevel.INFO) {}
@ -26,22 +40,51 @@ export class Logger {
const timestamp = new Date().toISOString();
const levelStr = LogLevel[level].padEnd(5);
const logMessage = `[${timestamp}] [${levelStr}] [${this.serviceName}] ${message}`;
const formattedArgs = args.length ? this.formatArgs(args) : '';
const fullMessage = `${message}${formattedArgs}`;
const logMessage = `[${timestamp}] [${levelStr}] [${this.serviceName}] ${fullMessage}`;
switch (level) {
case LogLevel.ERROR:
console.error(logMessage, ...args);
break;
case LogLevel.WARN:
console.warn(logMessage, ...args);
break;
case LogLevel.INFO:
console.info(logMessage, ...args);
break;
case LogLevel.DEBUG:
default:
console.debug(logMessage, ...args);
break;
// Console logging
if (loggingConfig.console) {
switch (level) {
case LogLevel.ERROR:
console.error(logMessage);
break;
case LogLevel.WARN:
console.warn(logMessage);
break;
case LogLevel.INFO:
console.info(logMessage);
break;
case LogLevel.DEBUG:
default:
console.debug(logMessage);
break;
}
}
// Loki logging
try {
const loki = getLokiClient();
loki.log(LogLevel[level].toLowerCase(), fullMessage, this.serviceName);
} catch (error) {
console.error('Failed to send log to Loki:', error);
}
}
private formatArgs(args: any[]): string {
try {
return args.map(arg => {
if (arg instanceof Error) {
return ` ${arg.message}\n${arg.stack}`;
} else if (typeof arg === 'object') {
return ` ${JSON.stringify(arg)}`;
} else {
return ` ${arg}`;
}
}).join('');
} catch (error) {
return ` [Error formatting log arguments: ${error}]`;
}
}

View file

@ -0,0 +1,86 @@
/**
* Loki client for sending logs to Grafana Loki
*/
import { LoggingConfig } from '@stock-bot/config';
export class LokiClient {
private batchQueue: any[] = [];
private flushInterval: NodeJS.Timeout;
private lokiUrl: string;
private authHeader?: string;
constructor(private config: LoggingConfig) {
const { host, port, username, password } = config.loki;
this.lokiUrl = `http://${host}:${port}/loki/api/v1/push`;
if (username && password) {
const authString = Buffer.from(`${username}:${password}`).toString('base64');
this.authHeader = `Basic ${authString}`;
}
this.flushInterval = setInterval(
() => this.flush(),
config.loki.flushIntervalMs
);
}
async log(level: string, message: string, serviceName: string, labels: Record<string, string> = {}) {
const timestamp = Date.now() * 1000000; // Loki expects nanoseconds
this.batchQueue.push({
streams: [{
stream: {
level,
service: serviceName,
...this.config.loki.labels,
...labels,
},
values: [[`${timestamp}`, message]],
}],
});
if (this.batchQueue.length >= this.config.loki.batchSize) {
await this.flush();
}
}
private async flush() {
if (this.batchQueue.length === 0) return;
try {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (this.authHeader) {
headers['Authorization'] = this.authHeader;
}
const response = await fetch(this.lokiUrl, {
method: 'POST',
headers,
body: JSON.stringify({
streams: this.batchQueue.flatMap(batch => batch.streams),
}),
});
if (!response.ok) {
console.error(`Failed to send logs to Loki: ${response.status} ${response.statusText}`);
const text = await response.text();
if (text) {
console.error(text);
}
}
} catch (error) {
console.error('Error sending logs to Loki:', error);
} finally {
this.batchQueue = [];
}
}
async destroy() {
clearInterval(this.flushInterval);
return this.flush();
}
}