/** * Internal redaction-safe structured file logger. * * This module is service-side observability only: it is deliberately NOT * exported from the root `src/index.ts` barrel and must never become part of * the application-facing API. Logging is opt-in through environment variables * and every sink failure is fail-open — a logging problem can never change * transaction, flush, worker, or polling results. * * Redaction contract (enforced structurally, not by scrubbing text): * * - The entry type accepts only known structured fields. There is no * free-form `message` field, so error messages, provider payloads, stack * traces, URLs, and filesystem paths can never reach the log. * - Identifier-like string fields (`event`, `component`, `code`, `table`, * `errorClass`) must match strict allowlist patterns; anything else is * replaced with the literal `"[redacted]"` before serialization. * - Numeric context is passed through a typed `counts` record whose values * must be finite numbers; every other value shape is dropped. * * Output is one JSON object per line, appended to the `.txt` file named by * `HIKOUTEI_LOG_FILE`. When the file would exceed `HIKOUTEI_LOG_MAX_BYTES` * it rotates to `.1.txt`, `.2.txt`, ... keeping at most * `HIKOUTEI_LOG_BACKUPS` rotated files (defaults: 10 MiB and 5 backups, * matching the soak runner's plan; explicit env overrides stay bounded). * All I/O is serialized through one promise queue and any failure degrades * the sink to a silent no-op. The first write creates the parent directory * of the log file so nested or custom paths work out of the box. The * append never follows a pre-existing symlink at the log path * (`O_NOFOLLOW` where available, lstat rejection elsewhere): a symlinked * log file is rejected fail-open and the external target is never * appended to, truncated, or renamed. */ /** Environment keys that configure the internal file logger. */ export declare const HIKOUTEI_LOG_ENV_KEYS: { /** Target log file; absent/blank disables file logging entirely. */ readonly LOG_FILE: "HIKOUTEI_LOG_FILE"; /** Minimum level written: debug, info, warn, or error. Defaults to info. */ readonly LOG_LEVEL: "HIKOUTEI_LOG_LEVEL"; /** Rotation threshold in bytes. Defaults to 10 MiB; minimum 4 KiB. */ readonly LOG_MAX_BYTES: "HIKOUTEI_LOG_MAX_BYTES"; /** Rotated backup files kept. Defaults to 5; minimum 0 (truncate). */ readonly LOG_BACKUPS: "HIKOUTEI_LOG_BACKUPS"; }; /** Emission levels, ordered from most to least verbose. */ export declare const HIKOUTEI_LOG_LEVELS: { readonly DEBUG: "debug"; readonly INFO: "info"; readonly WARN: "warn"; readonly ERROR: "error"; }; /** One emission level. */ export type HikouteiInternalLogLevel = (typeof HIKOUTEI_LOG_LEVELS)[keyof typeof HIKOUTEI_LOG_LEVELS]; /** Default rotation threshold applied when the env var is absent/malformed (10 MiB). */ export declare const DEFAULT_MAX_BYTES: number; /** Default rotated backup count (matches the approved soak plan). */ export declare const DEFAULT_BACKUPS = 5; /** * One structured log entry. * * Every field is optional except `event`. Only these fields are serialized; * unknown properties on the input object are dropped by the writer. */ export interface HikouteiInternalLogEntry { /** Stable dotted event name such as `hikoutei.em.flush_failed`. */ readonly event: string; /** Emission level; defaults to info. */ readonly level?: HikouteiInternalLogLevel; /** Owning subsystem tag such as `entity-manager` or `outbox`. */ readonly component?: string; /** Stable machine-readable error code (e.g. a HikouteiError code). */ readonly code?: string; /** Whether the described failure is transient and worth retrying. */ readonly retryable?: boolean; /** Attempt counter for retried operations. */ readonly attempts?: number; /** Sanitized entity/table scope; never a Sheet ID, URL, or row anchor. */ readonly table?: string; /** Sanitized error class name such as `HikouteiError`. */ readonly errorClass?: string; /** Allowlisted provider operation the invalid state was detected in. */ readonly providerOperation?: string; /** Allowlisted provider reason for the invalid state. */ readonly providerReason?: string; /** Request-start pacing lane (`polling`, `preflight`, or `write`). */ readonly pacing?: string; /** Operation duration in milliseconds. */ readonly durationMs?: number; /** Numeric context only (counts, sizes). Non-numeric values are dropped. */ readonly counts?: Readonly>; } /** Result of validating one entry: the serializable field set or a reason. */ export type LogEntryValidation = { readonly status: "valid"; readonly line: string; } | { readonly status: "invalid"; readonly reason: string; }; /** Handle over one configured log file. */ export interface HikouteiInternalLogger { /** True when logging is enabled and the sink has not degraded. */ readonly enabled: boolean; /** Effective minimum level (always defined, even when disabled). */ readonly level: HikouteiInternalLogLevel; /** Resolved log file path (undefined when disabled). */ readonly filePath: string | undefined; /** Enqueues one entry; returns false when the entry was filtered/dropped. */ log(entry: HikouteiInternalLogEntry): boolean; /** Waits for every queued write (and rotation) to settle. Test support. */ drain(): Promise; } /** Injectable clock so tests can pin timestamps. */ export type InternalLoggerClock = () => Date; export interface CreateInternalLoggerOptions { /** Environment map; defaults to an empty map (logging disabled). */ readonly env?: Readonly>; readonly now?: InternalLoggerClock; } /** * Builds one JSONL line from an entry, applying the redaction allowlists. * * Exported for focused redaction tests. Returns `invalid` only when the * entry is not an object or carries no usable `event`; callers treat that * as a silent drop (fail-open). */ export declare function formatHikouteiLogLine(entry: HikouteiInternalLogEntry, now?: Date): LogEntryValidation; /** * Creates the internal file logger from an environment map. * * When `HIKOUTEI_LOG_FILE` is absent or blank the returned logger is a * disabled no-op. Malformed numeric env values fall back to the defaults * (fail-open) instead of throwing. */ export declare function createHikouteiInternalLogger(options?: CreateInternalLoggerOptions): HikouteiInternalLogger; /** * Returns the cached process logger, creating it from `process.env` on first * use. Boundary call sites use this plus {@link logHikouteiInternalEvent}; * both are silent no-ops when file logging is not opted in. */ export declare function getHikouteiInternalLogger(): HikouteiInternalLogger; /** * Emits one entry through the process logger, swallowing every failure. * * This is the boundary call-site helper: it never throws, never blocks (the * write is queued), and returns immediately when logging is disabled. */ export declare function logHikouteiInternalEvent(entry: HikouteiInternalLogEntry): void; /** Test hook: drops the cached process logger so a new env is honored. */ export declare function resetHikouteiInternalLoggerForTests(): void; /** * Emits the single stable WARN for ENTERING a writer-lease startup wait * (every startup wait-gate site and the sync bootstrap's injected callback * share this shape). Callers that need once-per-startup semantics latch this * at their own boundary; the log module stays emission-only. */ export declare function logWriterLeaseStartupWait(): void; /** * Forces the `.txt` extension required by the log-collection contract. * * `hikoutei-log` becomes `hikoutei-log.txt`; an existing `.txt` suffix is * preserved as-is. */ export declare function ensureTxtExtension(filePath: string): string; /** Rotated backup path for one base path and index (base `x.txt` -> `x.1.txt`). */ export declare function rotatedLogPath(filePath: string, index: number): string; /** * Extracts a sanitized error class/code pair for boundary logging. * * Returns only the constructor name and, for `HikouteiError`-shaped errors, * the stable `code` string. The error `message` is deliberately never * returned: messages can embed emails, spreadsheet IDs, and paths. */ export declare function describeErrorForInternalLog(error: unknown): { readonly errorClass: string; readonly code?: string; }; /** * Stable, allowlisted error tag for DEFAULT console diagnostics. * * Emits only error-class names from the stable class allowlist and codes * from the stable code allowlist; unknown values collapse to the fixed * `unknown` category. The raw message, stack, path, URL, id, and email can * never reach a default console warning — injected diagnostic hooks and * thrown public errors keep their full contracts unchanged. */ export declare function stableConsoleErrorTag(error: unknown): string; //# sourceMappingURL=internalLog.d.ts.map