diff --git a/.env.complete b/.env.complete index 4aea194..b970009 100644 --- a/.env.complete +++ b/.env.complete @@ -121,6 +121,7 @@ LOKI_USERNAME= LOKI_PASSWORD= LOKI_TENANT_ID= LOKI_PUSH_TIMEOUT=10000 +LOKI_FLUSH_INTERVAL_MS=5000 LOKI_BATCH_SIZE=1024 LOKI_BATCH_WAIT=1000 LOKI_RETENTION_PERIOD=30d diff --git a/.env.prod b/.env.prod index 2aa8d1d..255aaa8 100644 --- a/.env.prod +++ b/.env.prod @@ -116,6 +116,7 @@ LOKI_USERNAME=${LOKI_USERNAME} LOKI_PASSWORD=${LOKI_PASSWORD} LOKI_TENANT_ID=${LOKI_TENANT_ID} LOKI_PUSH_TIMEOUT=30000 +LOKI_FLUSH_INTERVAL_MS=5000 LOKI_BATCH_SIZE=2048 LOKI_BATCH_WAIT=5000 LOKI_RETENTION_PERIOD=90d diff --git a/libs/config/src/loki.ts b/libs/config/src/loki.ts index 121e1d4..1c8e54e 100644 --- a/libs/config/src/loki.ts +++ b/libs/config/src/loki.ts @@ -35,6 +35,8 @@ export const lokiConfig = cleanEnv(process.env, { LOKI_DEFAULT_LABELS: str({ default: '', desc: 'Default labels for all log entries (JSON format)' }), LOKI_SERVICE_LABEL: str({ default: 'stock-bot', desc: 'Service label for log entries' }), LOKI_ENVIRONMENT_LABEL: str({ default: 'development', desc: 'Environment label for log entries' }), + + LOKI_FLUSH_INTERVAL_MS: num({ default: 5000, desc: 'Flush interval ms' }), }); // Export typed configuration object @@ -58,4 +60,5 @@ export const { LOKI_DEFAULT_LABELS, LOKI_SERVICE_LABEL, LOKI_ENVIRONMENT_LABEL, + LOKI_FLUSH_INTERVAL_MS, } = lokiConfig; diff --git a/libs/utils/src/index.ts b/libs/utils/src/index.ts index 36daa7f..100b993 100644 --- a/libs/utils/src/index.ts +++ b/libs/utils/src/index.ts @@ -1,3 +1,3 @@ export * from './dateUtils'; export * from './logger'; -export * from './lokiClient.ts'; +export * from './calculations/index'; \ No newline at end of file diff --git a/libs/utils/src/lokiClient.ts b/libs/utils/src/lokiClient.ts deleted file mode 100644 index 231ca3b..0000000 --- a/libs/utils/src/lokiClient.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * 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 = {}) { - 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 = { - '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(); - } -}