zulogs
v0.1.4
Published
Official Zulogs Node.js SDK
Readme
Zulogs
Official Node.js SDK for sending logs to the Zulogs API.
Why this SDK
zulogs gives you a small, typed, production-ready client for Zulogs log ingestion with a focus on:
- strong DX (strict TypeScript types and typed errors)
- reliability (timeouts, retry with exponential backoff, optional batching)
- low overhead (no heavy runtime dependencies)
Requirements
- Node.js
>= 18 - Zulogs API key
- Zulogs project ID
Install
npm install zulogsCredentials setup
export ZULOGS_API_KEY="your_zulogs_api_key"
export ZULOGS_PROJECT_ID="your_zulogs_project_id"Quickstart
import { createZulogsClient } from 'zulogs';
const client = createZulogsClient({
apiKey: process.env.ZULOGS_API_KEY!,
projectId: process.env.ZULOGS_PROJECT_ID!,
context: {
environment: process.env.NODE_ENV ?? 'development',
service: 'payments-api',
},
});
client.log({
message: 'checkout completed',
payload: {
orderId: 'ord_123',
amount: 4999,
currency: 'EUR',
},
});
client.info({
message: 'checkout completed (shortcut)',
payload: { orderId: 'ord_123' },
});
await client.shutdown();API contract (implemented by this SDK)
- Base URL default:
https://api.zulogs.com - Endpoint:
POST /logs - Auth header:
Authorization: Bearer <API_KEY> - Request payload includes
projectId - Content type:
application/json - Success response: HTTP
202with{ accepted: number } - Error response body:
{ error: { code, message, details? } }
Expected behavior with multi-project routing:
- valid API key + matching
projectIdin same organization -> success (202) - missing
projectIdin SDK config -> localZulogsValidationError - invalid/deactivated/expired API key ->
401 - unknown
projectId-> backend404(or403, depending on backend mapping) projectIdfrom another organization ->403
Public API
createZulogsClient(options)
const client = createZulogsClient({
apiKey: process.env.ZULOGS_API_KEY!,
projectId: process.env.ZULOGS_PROJECT_ID!,
baseUrl: 'https://api.zulogs.com',
timeoutMs: 5_000,
batchSize: 20,
flushIntervalMs: 2_000,
maxRetries: 3,
retryBaseDelayMs: 200,
retryMaxDelayMs: 5_000,
context: {
environment: process.env.NODE_ENV ?? 'development',
service: 'worker-ingest',
tags: {
region: 'eu-west-1',
},
},
headers: {
'x-request-source': 'worker-ingest',
},
mirrorToConsole: true,
});createClient(...) remains available as a deprecated alias for backward compatibility.
Client options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| apiKey | string | required | Zulogs API key sent as Bearer token. |
| projectId | string | required | Zulogs project target for ingestion requests. |
| baseUrl | string | https://api.zulogs.com | Override for self-hosted or staging endpoints. |
| timeoutMs | number | 5000 | Request timeout in milliseconds. |
| batchSize | number | 1 | Queue size threshold before auto-flush. |
| flushIntervalMs | number | 0 | Timer-based flush interval (0 = disabled). |
| maxRetries | number | 3 | Number of retries after initial attempt for retryable failures. |
| retryBaseDelayMs | number | 200 | Base delay used for exponential backoff. |
| retryMaxDelayMs | number | 5000 | Upper bound for retry delay. |
| context | ZulogsClientContext | {} | Default event fields (for example environment, service, tags) merged into every log. |
| headers | Record<string, string> | {} | Extra headers merged into each request. |
| includeCodeLocation | boolean | false | Automatically attaches call-site location for log() and single-event ingest(). |
| mirrorToConsole | boolean | false | Mirrors all outgoing log events to the local runtime console. |
| fetch | custom fetch fn | global fetch | Optional fetch override (testing, instrumentation, custom runtime). |
client.log(event)
Sends one event in fire-and-forget mode.
- In non-batching mode, it sends immediately.
- In batching mode, it enqueues until the batch is sent.
- Returns
void(noawaitrequired). - For short-lived scripts/workers, call
client.flush()orclient.shutdown()before exit to avoid dropped logs.
client.logAsync(event)
Sends one event and returns { accepted: number }.
- Use this when you want explicit success/error handling with
await/try-catch.
client.trace/debug/info/warn/warning/error/fatal(...)
Typed shortcut methods for the officially supported message types.
client.info({ message: '...' })setslevel: 'info'client.warning({ message: '...' })setslevel: 'warning'client.error({ message: '...' })setslevel: 'error'client.error('...')is also supportedclient.error('...', { payloadKey: 'payloadValue' })setspayload- all shortcuts delegate to
client.log(...) - Message overload contract: first argument is the required
messagestring; second argument is an optional payload object.
Client Context Defaults
Use context during initialization to avoid repeating environment metadata:
const client = createZulogsClient({
apiKey: process.env.ZULOGS_API_KEY!,
projectId: process.env.ZULOGS_PROJECT_ID!,
context: {
environment: 'production',
service: 'api',
tags: { region: 'eu-west-1' },
},
});Merge behavior:
- context fields are added to every event
- event-level fields override context fields
tagsandattributesare shallow-merged (eventvalues win on conflicts)
client.ingest(eventsOrEnvelope)
Supports all accepted request formats:
await client.ingest({ message: 'single event' });
await client.ingest([{ message: 'a' }, { message: 'b' }]);
await client.ingest({ logs: [{ message: 'a' }, { message: 'b' }] });Behavior notes:
- single object: can be queued when batching is enabled
- array and envelope: sent immediately (not queued)
- SDK always injects
projectIdinto outgoing payloads
client.flush()
Forces all currently queued batch events to be sent immediately.
client.shutdown()
Graceful shutdown method for apps/workers.
- marks client as closed
- flushes remaining queued events
- rejects new
logAsync()/ingest()calls afterwards
Log event schema
The SDK validates required and structural fields, but keeps unknown additional fields and forwards them unchanged.
| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| id | string | no | Optional event id. |
| timestamp | string \| number | no | ISO string or Unix sec/ms number. |
| level | string | no | Known: trace/debug/info/warn/warning/error/fatal; unknown custom levels are allowed. |
| message | string | yes | Required non-empty string. |
| payload | Record<string, unknown> | no | Structured payload in addition to message. |
| logger | string | no | Logger name. |
| service | string | no | Service name. |
| environment | string | no | Environment label (prod, staging, ...). |
| host | string | no | Hostname/source host. |
| source | string | no | Source label (worker, cron, API, etc). |
| tags | Record<string, string \| number \| boolean> | no | Indexed tags for filtering/aggregation. |
| attributes | Record<string, unknown> | no | Additional structured metadata. |
| extra fields | unknown | no | Preserved and transmitted to API. |
Optional: include source code location automatically
If you want each log to include where it was called in your codebase, enable location capture:
const client = createZulogsClient({
apiKey: process.env.ZULOGS_API_KEY!,
projectId: process.env.ZULOGS_PROJECT_ID!,
includeCodeLocation: true,
});
client.log({
message: 'payment failed',
payload: { orderId: 'ord_42' },
});The SDK will add a field like:
{
"location": {
"file": "/app/src/payments/handler.ts",
"line": 84,
"column": 17,
"function": "handlePayment"
}
}Notes:
- It uses runtime stack traces, so output format depends on runtime/bundler.
- Existing event field values are not overwritten. If
locationalready exists, SDK leaves it unchanged.
Optional: mirror logs to local console
If you want each outgoing event to also appear locally, enable console mirroring:
const client = createZulogsClient({
apiKey: process.env.ZULOGS_API_KEY!,
projectId: process.env.ZULOGS_PROJECT_ID!,
mirrorToConsole: true,
});Console routing is level-aware:
trace/debug->console.debuginfo->console.infowarn/warning->console.warnerror/fatal->console.error- other or missing levels ->
console.log
Validation behavior
The SDK throws ZulogsValidationError before making a network call when:
apiKeyis missing/emptyprojectIdis missing/empty (projectId is required. Update SDK configuration.)messageis missing or emptypayload,tags, orattributeshave invalid shapes- options (
timeoutMs,batchSize, etc.) are invalid
Migration note (legacy SDKs)
Legacy SDK configurations that only provide apiKey no longer work.
Update initialization to always include both:
apiKeyprojectId
Error handling
import {
ZulogsApiError,
ZulogsNetworkError,
ZulogsValidationError,
} from '@zulogs/node';
try {
await client.logAsync({ message: 'hello' });
} catch (error) {
if (error instanceof ZulogsValidationError) {
console.error('Validation failed', error.issues);
} else if (error instanceof ZulogsApiError) {
console.error('API error', {
status: error.status,
code: error.code,
message: error.message,
details: error.details,
});
} else if (error instanceof ZulogsNetworkError) {
console.error('Network or timeout issue', error.message);
} else {
console.error('Unexpected error', error);
}
}Error classes
| Class | Meaning |
| --- | --- |
| ZulogsValidationError | Invalid client options or event payload, detected locally before request. |
| ZulogsNetworkError | Transport-level failure (network issue or timeout). |
| ZulogsApiError | Non-202 API response with parsed status, code, and optional details. |
Retry strategy
Retries apply to:
- HTTP
429 - HTTP
5xx - network/timeout errors (
ZulogsNetworkError)
Retry uses exponential backoff with jitter, bounded by retryMaxDelayMs.
Batching strategy
Batch mode is enabled when at least one is true:
batchSize > 1flushIntervalMs > 0
Queue flush triggers:
- queue reaches
batchSize - flush timer ticks (
flushIntervalMs) - explicit
client.flush() client.shutdown()
Example integration
- Plain Node example: examples/plain-node.ts
Publishing checklist
npm run typecheck
npm run test
npm run build