npm package: @kitamersion/kita-logging
Description: Lightweight IndexDB logging library for web applications.
Author: kitamersion
Latest version: 1.0.6

<h1 align="center"><strong>Kita Logging</strong></h1>

`@kitamersion/kita-logging` a lightweight, browser-focused logging library that buffers logs in-memory and persists them to IndexedDB in batches.

## Key features

- Buffered, fire-and-forget logging for minimal runtime impact
- Persists logs to IndexedDB with a `by_timestamp` index (newest-first reads)
- Configurable log prefix and retention period
- Small public API: `logger` (default), `config`, and `history`
- Safe local snapshot to `localStorage` on failures (optional)

## Install

```bash
npm install @kitamersion/kita-logging
```

## Quick usage

Default exports: a buffered `logger` instance (default export) plus named `history` and `config` helpers.

```ts
import logger, { history, config } from "@kitamersion/kita-logging";

// fire-and-forget
logger.info("App started");
logger.warn("Possible issue detected");

// wait for persistence when needed
await logger.flush();

// read logs (newest first)
const logs = await history.getLogs();
console.log(logs[0].timestampISO, logs[0].message, logs[0].prefix);

// configure
await config.setLogPrefix("[MY_APP]");
await config.setLogRetentionDays(14);
// configure buffered logger options (persisted)
await config.setBufferedOptions({ flushIntervalMs: 1000, batchSize: 25 });
const current = await config.getBufferedOptions();
console.log(current);

// capture an Error inside a try/catch — the stack will be persisted on the log entry
try {
  throw new Error("example");
} catch (err) {
  // logger.error accepts an optional Error (or string) as a second argument
  await logger.error("caught failure", err);
}
```

## React: Provider + hook example

```tsx
import React, {
  createContext,
  useContext,
  useEffect,
  useState,
  PropsWithChildren,
} from "react";
import { config } from "@kitamersion/kita-logging";

type SimpleLoggerConfig = {
  logPrefix: string;
  logRetentionDays: number;
};

const SimpleLoggerContext = createContext<SimpleLoggerConfig | undefined>(
  undefined
);

export const useSimpleLoggerConfig = () => {
  const ctx = useContext(SimpleLoggerContext);
  if (!ctx)
    throw new Error(
      "useSimpleLoggerConfig must be used within LoggerProviderSimple"
    );
  return ctx;
};

// Minimal provider that ensures defaults and exposes current values
export const LoggerProviderSimple = ({
  children,
}: PropsWithChildren<object>) => {
  const [logPrefix, setLogPrefix] = useState("[KITA_LOGGING]");
  const [logRetentionDays, setLogRetentionDays] = useState(7);

  useEffect(() => {
    let mounted = true;
    (async () => {
      const cfg = await config.viewCurrentConfigurations();
      let prefix = cfg.logPrefix;
      if (!prefix || prefix.trim() === "") {
        prefix = "[KITA_LOGGING]";
        await config.setLogPrefix(prefix);
      }
      let days = cfg.logRetentionDays;
      if (!days || days <= 0) {
        days = 1;
        await config.setLogRetentionDays(days);
      }
      if (!mounted) return;
      setLogPrefix(prefix);
      setLogRetentionDays(days);
    })();
    return () => {
      mounted = false;
    };
  }, []);

  return (
    <SimpleLoggerContext.Provider value={{ logPrefix, logRetentionDays }}>
      {children}
    </SimpleLoggerContext.Provider>
  );
};

export default LoggerProviderSimple;
```

## Exposed APIs

logger (default export)

- `logger.info(message: string): void` — push an info-level log
- `logger.debug(message: string): void` — push a debug-level log
- `logger.warn(message: string): void` — push a warn-level log
- `logger.error(message: string): void` — push an error-level log
- `logger.error(message: string, err?: Error | string): Promise<void>` — push an error-level log; if an `Error` or stack string is provided it will be saved on the log entry as `stack`
- `logger.flush(): Promise<void>` — force flush buffered entries to IndexedDB
- `logger.start(): void` — start periodic flush (enabled by default)
- `logger.stop(): Promise<void>` — stop periodic flush and flush remaining entries
- `logger.config` — read-only object with runtime configuration values
- `logger.refresh(): Promise<void>` — refresh prefix from persisted config

config (named export)

- `config.setLogPrefix(prefix: string): Promise<void>`
- `config.getLogPrefix(): Promise<string>`
- `config.setLogRetentionDays(days: number): Promise<void>`
- `config.getLogRetentionDays(): Promise<number>`
- `config.viewCurrentConfigurations(): Promise<{ logPrefix: string, logRetentionDays: number }>`
- `config.setBufferedOptions(opts: BufferedOptions): Promise<void>` — persist buffered logger options (flushIntervalMs, batchSize, maxBufferSize, persistToLocalStorage, captureStack, maxStackChars)
- `config.getBufferedOptions(): Promise<BufferedOptions>` — read persisted buffered options (or defaults)
- `config.onBufferedOptionsChange(fn: (opts: BufferedOptions) => void): () => void` — subscribe to changes to buffered options; returns an unsubscribe function
- `config.setCaptureStack(capture: boolean): Promise<void>` — toggle capturing of stacks for error logs (persists to config)
- `config.getCaptureStack(): Promise<boolean>` — read persisted captureStack setting
- `config.setMaxStackChars(chars: number): Promise<void>` — set maximum characters to store for stack traces (persists to config)
- `config.getMaxStackChars(): Promise<number>` — read persisted maximum stack chars

history (named export)

- `history.getLogs(): Promise<LogEntry[]>` — returns newest-first; `LogEntry` includes: `id`, `timestamp` (ms), `timestampISO`, `level`, `message`, `prefix`, and `stack` (when available)
- `history.deleteExpiredLogs(retentionDays?: number): Promise<void>`
- `history.deleteAllLogs(): Promise<void>`

logger helpers

- `createLogger(opts?: BufferedOptions)` — create a custom buffered logger instance with the same API as the default `logger` (useful for per-module or per-environment customization). The `BufferedOptions` shape is the same as described in "Configuration options (buffered logger)" below.
- The default exported `logger` instance will attempt to read persisted buffered options from `config` on startup and will reconfigure itself if `config.setBufferedOptions` is called at runtime. Existing references to the exported `logger` remain valid when the implementation is swapped.

## Configuration options (buffered logger)

When creating a custom buffered logger via `createLogger(opts)` or when persisting options via `config.setBufferedOptions`, available options:

- `flushIntervalMs` (number) — automatic flush interval in ms (default: `2000`)
- `batchSize` (number) — entries per flush (default: `50`)
- `maxBufferSize` (number) — max entries in memory (default: `5000`)
- `persistToLocalStorage` (boolean) — snapshot buffer to `localStorage` on failures (default: `true`)

- `captureStack` (boolean) — capture and persist stack traces for error logs (default: `true`)

- `maxStackChars` (number) — maximum characters stored for stack traces (default: `2000`)

Note: stacks are truncated to a reasonable default length (2000 characters) to avoid very large DB entries; this limit is configurable via `config.setMaxStackChars` and the default is defined in `src/defaults.ts`.

## Tests & development

Run build and tests locally:

```bash
npm run build
npm run test
```

## Notes

- The logger is intentionally buffered. Call `logger.flush()` when you need to guarantee persistence (e.g., before a critical navigation or test assertion).
- Log entries include both a numeric `timestamp` (ms since epoch) and `timestampISO` for UI display.
- `history.getLogs()` returns newest-first by default. For very large datasets use cursor-based reads on the `by_timestamp` index for streaming/pagination.