npm package: @grouchoab/utilog
Description: Wraps pino and provides various utility funcitions.
Author: dspnikder
Latest version: 0.2.0

# groucho-utilog

Lightweight logging utilities built on top of pino. utilog provides a preconfigured logger, plus small helpers for consistent tool-call logging and simple duration measurements. It is designed to be drop-in, Node.js 18+ compatible, and friendly for CLIs and services.

## Install

```bash
npm install @grouchoab/utilog
```

Peer/runtime requirements:

- Node.js >= 18

## Quick Start

```ts
import { log, logToolCall, logDuration } from '@grouchoab/utilog';

// Basic logging
log.info({ userId: 'u_123' }, 'User logged in');
log.debug('Debug details');
log.error({ err: new Error('boom') }, 'Something failed');

// Tool-call lifecycle logging
logToolCall('embedText', 'entry', { size: 1024 });
try {
  // ... do work ...
  logToolCall('embedText', 'exit', { tokens: 256 });
} catch (e) {
  logToolCall('embedText', 'error', { err: e });
}

// Duration measurement
const t0 = performance.now();
// ... work ...
logDuration('buildIndex', t0, { items: 42 }); // debug level by default
logDuration('httpRequest', t0, { path: '/health' }, /* asInfo */ true);
```

## Why utilog?

- Opinionated pino setup that logs to both a file and stderr (to avoid interfering with stdout protocols).
- Tiny helpers for common patterns (tool-call phases and timing) with structured fields.
- Configurable via environment variables, no code changes required.

## Output Destinations

By default utilog logs to:

- A file at `/tmp/utilog.log` (all messages at your configured level)
- No console output (stdout/stderr) unless explicitly enabled

You can control behavior with environment variables (see below). Console output can be directed to stdout or stderr at a chosen level.

## API

### `log`

A configured pino logger.

```ts
import { log } from '@grouchoab/utilog';

log.debug({ ctx: 'init' }, 'Starting up');
log.info('Ready');
log.warn({ retryInMs: 2000 }, 'Temporary issue');
log.error({ err }, 'Unhandled error');
```

The logger writes in structured JSON suitable for ingestion by log processors.

### `logToolCall(toolName, phase, metadata?)`

Structured logging helper for tool or RPC lifecycle events.

Parameters:

- `toolName` (string): Logical name of the tool/operation.
- `phase` ('entry' | 'exit' | 'error'): Lifecycle phase.
- `metadata` (object, optional): Extra fields to include.

Emits a `debug` log with fields: `tool`, `phase`, `timestamp`, plus your metadata.

### `logDuration(operation, startTime, metadata?, asInfo?)`

Measure and log execution time.

Parameters:

- `operation` (string): Name/description to identify the work.
- `startTime` (number): Milliseconds from `performance.now()` captured before the work.
- `metadata` (object, optional): Extra fields to include.
- `asInfo` (boolean, default `false`): When `true`, logs at `info` instead of `debug`.

Emits a log with fields: `operation`, `duration_ms` (rounded to 2 decimals), `timestamp`, plus your metadata.

## Configuration

Configure via environment variables:

- `UTILOG_LEVEL`: pino level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`). Default: `info`.
- `UTILOG_FILE`: file path for persisted logs. Default: `/tmp/utilog.log`.
- `UTILOG_2CONSOLE`: console output control. `0` (default) disables console logging, `1` logs to stdout, `2` logs to stderr.
- `UTILOG_2CONSOLE_LEVEL`: console log level. Any valid pino level. Defaults to `UTILOG_LEVEL` (or `info`).

Example:

```bash
UTILOG_LEVEL=debug \
UTILOG_FILE=/var/log/myapp.log \
UTILOG_2CONSOLE=2 \
UTILOG_2CONSOLE_LEVEL=info \
node dist/index.js
```

## TypeScript

Type definitions are included. Import directly from the package:

```ts
import type { Logger } from 'pino';
import { log } from '@grouchoab/utilog';

const l: Logger = log;
```

## Programmatic Use with ESM

This package is ESM-only (`"type": "module"`). Use `import` syntax as shown above. For CommonJS, use dynamic `import()` or a transpilation step.

## Notes

- If you enable console logging, prefer `UTILOG_2CONSOLE=2` (stderr) to avoid interfering with stdout-based protocols (e.g., MCP, piping JSON). File output always receives messages at the configured level.
- For timings, prefer `performance.now()` over `Date.now()` for better precision, then pass the captured start value to `logDuration`.

## License

MIT