Skip to content

HttpLogger config

  • Source: src/config/logging/HttpLogger.ts

Usage

Import from the framework:

ts
import { HttpLogger } from '@zintrust/core';

// Example (if supported by the module):
// HttpLogger.*

Snapshot (top)

ts
/**
 * HTTP Endpoint Logger
 * Sends logs to an external HTTP logging service.
 *
 * Enabled via env:
 *  - HTTP_LOG_ENABLED (default: false)
 *  - HTTP_LOG_ENDPOINT_URL
 *  - HTTP_LOG_BATCH_SIZE (default: 50)
 *  - HTTP_LOG_AUTH_TOKEN (optional)
 */

import { delay, Env, ErrorFactory, HttpClient } from '@zintrust/core';

export type HttpLogEvent = {
  timestamp: string;
  level: 'debug' | 'info' | 'warn' | 'error' | 'fatal';
  message: string;
  category?: string;
  data?: unknown;
  error?: string;
};

const isEnabled = (): boolean => Env.getBool('HTTP_LOG_ENABLED', false);

let buffer: HttpLogEvent[] = [];
let flushPromise: Promise\<void> | undefined;

const postBatch = async (events: HttpLogEvent[]): Promise\<void> => {
  const endpoint = Env.get('HTTP_LOG_ENDPOINT_URL').trim();
  if (endpoint.length === 0) {
    throw ErrorFactory.createConfigError(
      'HTTP_LOG_ENDPOINT_URL is required when HTTP logging is enabled'
    );
  }

  const token = Env.get('HTTP_LOG_AUTH_TOKEN').trim();

  const builder = HttpClient.post(endpoint, {
    sentAt: new Date().toISOString(),
    count: events.length,
    events,
  });

  if (token.length > 0) {
    builder.withAuth(token, 'Bearer');
  }

  await builder.send();
};

const flushNow = async (): Promise\<void> => {
  const toSend = buffer;
  buffer = [];

  if (!isEnabled()) return;
  if (toSend.length === 0) return;

  const maxRetries = 3;

  const attemptPost = async (attempt: number): Promise\<void> => {
    try {
      await postBatch(toSend);
    } catch {
      if (attempt >= maxRetries) return;
      const backoffMs = 100 * 2 ** attempt;
      await delay(backoffMs);
      await attemptPost(attempt + 1);

Snapshot (bottom)

ts
  const attemptPost = async (attempt: number): Promise\<void> => {
    try {
      await postBatch(toSend);
    } catch {
      if (attempt >= maxRetries) return;
      const backoffMs = 100 * 2 ** attempt;
      await delay(backoffMs);
      await attemptPost(attempt + 1);
    }
  };

  await attemptPost(0);
};

const scheduleFlush = async (): Promise\<void> => {
  if (flushPromise !== undefined) return flushPromise;

  const promise = new Promise\<void>((resolve) => {
    const run = async (): Promise\<void> => {
      try {
        await flushNow();
      } finally {
        resolve(undefined);
      }
    };

    if (typeof globalThis.setTimeout !== 'function') {
      void run();
      return;
    }

    globalThis.setTimeout(() => {
      void run();
    }, 0);
  });

  flushPromise = promise.finally(() => {
    flushPromise = undefined;
  });

  return flushPromise;
};

export const HttpLogger = Object.freeze({
  async enqueue(event: HttpLogEvent): Promise\<void> {
    if (!isEnabled()) return Promise.resolve(); // NOSONAR

    buffer.push(event);

    const batchSize = Math.max(1, Env.getInt('HTTP_LOG_BATCH_SIZE', 50));
    if (buffer.length >= batchSize) {
      return scheduleFlush();
    }

    return scheduleFlush();
  },
});

export default HttpLogger;

Released under the MIT License.