npm package: @nomie7/zen-logs-client
Description: Node.js TypeScript client for Zen Logs ingestion endpoints.
Author: nomie7
Latest version: 0.1.0

# @nomie7/zen-logs-client

Node.js (>=20) TypeScript client for sending logs to Zen Logs ingestion endpoints.

## Installation

```bash
npm install @nomie7/zen-logs-client
```

## Integration test (optional)

Starts skipped unless `ZEN_LOGS_URL` is set.

```bash
# In one terminal:
# start Zen Logs (e.g. go run ./cmd/logservice or docker-compose up)

# In another terminal:
cd sdk/ts
ZEN_LOGS_URL=http://localhost:7777 npm test
```

Test file: [`sdk/ts/test/integration.test.ts`](sdk/ts/test/integration.test.ts:1)

## Usage

### Create a client

```ts
import { ZenLogsClient } from '@nomie7/zen-logs-client';

const client = new ZenLogsClient({
  baseUrl: 'http://localhost:7777',
  token: process.env.LOG_AUTH_TOKEN, // optional
  defaultServiceName: 'billing-service',
});
```

## Standard logging conventions (internal)

Use these conventions across all services to make Zen Logs queries and stats consistent.

### Required/expected metadata (recommendation)

Add these keys to `metadata` wherever available:

- `env`: `dev` | `staging` | `prod`
- `service_version`: semantic version or build number
- `build_sha`: git SHA
- `region`: e.g. `us-east-1`
- `request_id`: stable per-request identifier
- `trace_id`: distributed tracing trace id (if you have one)
- `span_id`: distributed tracing span id (optional)

Avoid secrets (tokens/passwords). For more guidance, see [`docs/sending-logs.md`](docs/sending-logs.md:1).

### Endpoint cardinality rule (usage logs)

For `usage.endpoint`, prefer route templates (low cardinality):

- ✅ `/api/users/{id}`
- ❌ `/api/users/123`

This keeps the stats endpoint usable (top endpoints) and reduces noise.

## Examples (copy/paste standards)

### 1) Events: error with correlation + typed metadata

```ts
await client.event({
  level: 'ERROR',
  message: 'db connect failed',
  stack_trace: err instanceof Error ? err.stack : undefined,
  metadata: {
    env: process.env.NODE_ENV ?? 'dev',
    service_version: process.env.SERVICE_VERSION ?? 'dev',
    build_sha: process.env.BUILD_SHA ?? 'local',
    region: process.env.AWS_REGION ?? 'local',
    request_id: req.id,
    trace_id: req.traceId,
    error_code: 'DB_CONN_FAILED',
    component: 'db',
    retry_count: 3,
  },
});
```

### 2) Events: startup / lifecycle message

```ts
await client.event({
  level: 'INFO',
  message: 'service started',
  metadata: {
    env: process.env.NODE_ENV ?? 'dev',
    service_version: process.env.SERVICE_VERSION ?? 'dev',
    build_sha: process.env.BUILD_SHA ?? 'local',
  },
});
```

### 3) Audit: user action (who did what, on what)

```ts
await client.audit({
  action: 'UPDATE_PROFILE',
  result: 'SUCCESS',
  user_id: user.id,
  resource: `users/${user.id}`,
  ip_address: req.ip,
  user_agent: req.headers['user-agent'],
  metadata: {
    env: process.env.NODE_ENV ?? 'dev',
    request_id: req.id,
    trace_id: req.traceId,
    actor_type: 'user',
    fields_changed: ['email', 'phone'],
  },
});
```

### 4) Audit: failure example (important for stats)

```ts
await client.audit({
  action: 'DELETE_API_KEY',
  result: 'FAILURE',
  user_id: user.id,
  resource: `apikeys/${apiKeyId}`,
  metadata: {
    request_id: req.id,
    trace_id: req.traceId,
    reason: 'insufficient_permissions',
  },
});
```

### 5) Usage: API request latency (recommended fields)

```ts
await client.usage({
  endpoint: '/api/users/{id}',
  method: 'GET',
  duration_ms: timing.ms,
  status_code: res.statusCode,
  user_id: user?.id,
  metadata: {
    env: process.env.NODE_ENV ?? 'dev',
    request_id: req.id,
    trace_id: req.traceId,
    response_size_bytes: res.bytesWritten,
    cache: res.cacheStatus, // e.g. hit/miss
  },
});
```

### 6) Adding consistent metadata automatically

Use `enrichMetadata` in [`ZenLogsClientConfig`](sdk/ts/src/types.ts:62) to ensure every log includes the same baseline keys.

```ts
import { ZenLogsClient } from '@nomie7/zen-logs-client';

const client = new ZenLogsClient({
  baseUrl: process.env.ZEN_LOGS_URL ?? 'http://localhost:7777',
  token: process.env.LOG_AUTH_TOKEN,
  defaultServiceName: 'billing-service',
  enrichMetadata: (metadata) => ({
    env: process.env.NODE_ENV ?? 'dev',
    service_version: process.env.SERVICE_VERSION ?? 'dev',
    build_sha: process.env.BUILD_SHA ?? 'local',
    region: process.env.AWS_REGION ?? 'local',
    ...metadata,
  }),
});
```

### 7) Graceful shutdown (recommended)

Call `shutdown()` during process termination so the in-memory queue is drained best-effort.

```ts
const shutdown = async () => {
  await client.shutdown({ timeoutMs: 5000 });
  process.exit(0);
};

process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
```

### Send an event

```ts
await client.event({
  level: 'ERROR',
  message: 'Failed to connect to database',
  stack_trace: '...',
  metadata: {
    error_code: 'DB_CONN_FAILED',
    request_id: 'req_123',
  },
});
```

### Send an audit log

```ts
await client.audit({
  action: 'UPDATE_PROFILE',
  result: 'SUCCESS',
  user_id: 'user-123',
  resource: 'users/123',
  metadata: {
    fields_changed: ['email', 'phone'],
  },
});
```

### Send a usage log

```ts
await client.usage({
  endpoint: '/api/products',
  method: 'GET',
  duration_ms: 45,
  status_code: 200,
  metadata: {
    cache: 'hit',
  },
});
```

### Flush / shutdown

```ts
await client.flush();
await client.shutdown({ timeoutMs: 5000 });
```

## Defaults

Low-latency defaults (configurable in [`ZenLogsClientConfig`](sdk/ts/src/types.ts:62)):

- `flushIntervalMs=250`
- `maxBatchSize=50`
- `maxQueueSize=2000`
- `maxRetries=2`
- `baseBackoffMs=200`
- `maxBackoffMs=2000`
- `retryOn=[network, 5xx]`
- Backpressure: `block` with `enqueueTimeoutMs=5000`, then drop-newest

## Notes

- The client prefers `POST /api/v1/batch` for flushes (implemented in [`ZenLogsClient`](sdk/ts/src/client.ts:1)).
- Timestamps are assigned by the server (see [`models.*.To*Log()`](internal/models/models.go:78)).