Skip to content

FileLogWriter config

  • Source: src/config/FileLogWriter.ts

Usage

Import from the framework:

ts
import { FileLogWriter } from '@zintrust/core/node';

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

Snapshot (top)

ts
/**
 * FileLogWriter (Node.js only)
 *
 * Provides best-effort file logging with daily + size-based rotation.
 * This module imports Node built-ins and should be loaded only in Node environments.
 */

import { ensureDirSafe } from '@zintrust/core';
import { Env } from '@zintrust/core';
import * as fs from 'node:fs';
import * as path from 'node:path';

const getCwdSafe = (): string => {
  try {
    if (typeof process === 'undefined' || typeof process.cwd !== 'function') return '';
    return process.cwd();
  } catch {
    return '';
  }
};

const getDateStr = (d: Date): string => d.toISOString().slice(0, 10);

const isAppLogFile = (fileName: string): boolean =>
  fileName.startsWith('app-') && fileName.endsWith('.log');

const parseDateFromLogFilename = (fileName: string): number | null => {
  const match = /^app-(\d{4}-\d{2}-\d{2})(?:-\d+)?\.log$/.exec(fileName);
  if (match?.[1] === undefined) return null;

  const parsed = Date.parse(`${match[1]}T00:00:00.000Z`);
  return Number.isNaN(parsed) ? null : parsed;
};

const isOlderThanCutoffByMtime = (fullPath: string, cutoff: number): boolean => {
  try {
    const stat = fs.statSync(fullPath);
    return stat.mtime.getTime() < cutoff;
  } catch {
    return false;
  }
};

const safeUnlink = (fullPath: string): void => {
  try {
    fs.unlinkSync(fullPath);
  } catch {
    // best-effort
  }
};

const readDirSafe = (dirPath: string): string[] => {
  try {
    return fs.readdirSync(dirPath);
  } catch {
    return [];
  }
};

const shouldDeleteLogFile = (fileName: string, fullPath: string, cutoff: number): boolean => {
  const parsed = parseDateFromLogFilename(fileName);
  if (parsed !== null) return parsed < cutoff;

  return isOlderThanCutoffByMtime(fullPath, cutoff);
};

const cleanupOldLogs = (logsDir: string, keepDays: number): void => {
  if (keepDays <= 0) return;

  const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1000;

Snapshot (bottom)

ts

  // Step 1: delete by age
  cleanupOldLogs(logsDir, keepDays);

  const deleted: string[] = [];

  // Step 2: enforce keepFiles (keep newest N files)
  const filesNewestFirst = listAppLogFiles(logsDir, 'newest-first');
  enforceKeepFilesPolicy(filesNewestFirst, keepFiles, deleted);

  // Step 3: enforce max total size
  enforceMaxTotalSizePolicy(logsDir, maxTotalSize, deleted);

  return deleted;
};

const rotateIfNeeded = (logFile: string, maxSizeBytes: number): void => {
  if (maxSizeBytes <= 0) return;

  try {
    if (!fs.existsSync(logFile)) return;

    const stat = fs.statSync(logFile);
    if (stat.size <= maxSizeBytes) return;

    const ext = '.log';
    const base = logFile.endsWith(ext) ? logFile.slice(0, -ext.length) : logFile;
    const rotated = `${base}-${Date.now()}${ext}`;
    fs.renameSync(logFile, rotated);
  } catch {
    // best-effort
  }
};

export const FileLogWriter = Object.freeze({
  write(line: string): void {
    const cwd = getCwdSafe();
    if (cwd === '') return;

    const logsDir = path.join(cwd, 'logs');
    ensureDirSafe(logsDir);

    const dateStr = getDateStr(new Date());
    const logFile = path.join(logsDir, `app-${dateStr}.log`);

    rotateIfNeeded(logFile, Env.LOG_ROTATION_SIZE);

    try {
      fs.appendFileSync(logFile, `${line}\n`);
    } catch {
      // best-effort
      return;
    }

    cleanupOldLogs(logsDir, Env.LOG_ROTATION_DAYS);
  },
});

export default FileLogWriter;

Released under the MIT License.