@nomie7/zen-logs-client
v0.1.0
Published
Node.js TypeScript client for Zen Logs ingestion endpoints.
Maintainers
Readme
@nomie7/zen-logs-client
Node.js (>=20) TypeScript client for sending logs to Zen Logs ingestion endpoints.
Installation
npm install @nomie7/zen-logs-clientIntegration test (optional)
Starts skipped unless ZEN_LOGS_URL is set.
# 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 testTest file: sdk/ts/test/integration.test.ts
Usage
Create a client
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|prodservice_version: semantic version or build numberbuild_sha: git SHAregion: e.g.us-east-1request_id: stable per-request identifiertrace_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.
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
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
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)
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)
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)
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 to ensure every log includes the same baseline keys.
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.
const shutdown = async () => {
await client.shutdown({ timeoutMs: 5000 });
process.exit(0);
};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);Send an event
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
await client.audit({
action: 'UPDATE_PROFILE',
result: 'SUCCESS',
user_id: 'user-123',
resource: 'users/123',
metadata: {
fields_changed: ['email', 'phone'],
},
});Send a usage log
await client.usage({
endpoint: '/api/products',
method: 'GET',
duration_ms: 45,
status_code: 200,
metadata: {
cache: 'hit',
},
});Flush / shutdown
await client.flush();
await client.shutdown({ timeoutMs: 5000 });Defaults
Low-latency defaults (configurable in ZenLogsClientConfig):
flushIntervalMs=250maxBatchSize=50maxQueueSize=2000maxRetries=2baseBackoffMs=200maxBackoffMs=2000retryOn=[network, 5xx]- Backpressure:
blockwithenqueueTimeoutMs=5000, then drop-newest
Notes
- The client prefers
POST /api/v1/batchfor flushes (implemented inZenLogsClient). - Timestamps are assigned by the server (see
models.*.To*Log()).
