wiretap-llm
v0.2.0
Published
Telemetry client for sending LLM logs to Wiretap
Maintainers
Readme
wiretap-llm
Telemetry client for sending LLM logs to your Wiretap instance.
Installation
npm install wiretap-llm
# or
bun add wiretap-llmQuick Start - Automatic Instrumentation
The easiest way to use wiretap-llm is with createInstrumentedFetch. It automatically captures all OpenRouter/OpenAI requests:
import OpenAI from "openai";
import { TelemetryClient, createInstrumentedFetch } from "wiretap-llm";
// Create telemetry client
const telemetry = new TelemetryClient({
baseUrl: process.env.WIRETAP_BASE_URL!,
apiKey: process.env.WIRETAP_API_KEY!,
});
// Create OpenAI client with instrumented fetch
const openrouter = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY!,
fetch: createInstrumentedFetch(telemetry),
});
// Use normally - all requests are automatically logged
const response = await openrouter.chat.completions.create({
model: "anthropic/claude-sonnet-4",
messages: [{ role: "user", content: "Hello!" }],
});
// Streaming works too
const stream = await openrouter.chat.completions.create({
model: "anthropic/claude-sonnet-4",
messages: [{ role: "user", content: "Count to 5" }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
// On app shutdown
await telemetry.shutdown();Manual Reporting
For custom logging scenarios, you can report logs manually:
import { TelemetryClient, createId, toIso } from "wiretap-llm";
const client = new TelemetryClient({
baseUrl: process.env.WIRETAP_BASE_URL!,
apiKey: process.env.WIRETAP_API_KEY!,
});
client.report({
id: createId(),
timestamp: toIso(new Date()),
project: "", // Inferred from API key
source: "openrouter",
method: "POST",
url: "https://openrouter.ai/api/v1/chat/completions",
path: "/v1/chat/completions",
model: "anthropic/claude-sonnet-4",
streamed: false,
timing: {
started_at: toIso(startTime),
ended_at: toIso(endTime),
duration_ms: endTime.getTime() - startTime.getTime(),
},
request: {
body_json: requestBody,
},
response: {
status: 200,
body_json: responseBody,
},
usage: responseBody.usage,
});
await client.shutdown();Configuration
TelemetryClient Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| baseUrl | string | required | Your Wiretap instance URL |
| apiKey | string | required | Your Wiretap API key |
| project | string | - | Project slug (usually inferred from API key) |
| environment | string | - | Environment name (e.g., "production") |
| defaultMetadata | object | - | Metadata added to all logs |
| timeoutMs | number | 5000 | Request timeout |
| maxBatchSize | number | 25 | Max logs per batch |
| flushIntervalMs | number | 2000 | Auto-flush interval |
| maxLogBytes | number | 1000000 | Max log size before truncation |
| maxQueueSize | number | 1000 | Max queue size (drops oldest when exceeded) |
| retryBaseDelayMs | number | 1000 | Initial retry delay |
| retryMaxDelayMs | number | 30000 | Maximum retry delay |
| maxRetries | number | 5 | Max retries before dropping batch |
| onError | function | - | Error handler callback |
createInstrumentedFetch Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| baseFetch | fetch | globalThis.fetch | Base fetch function to wrap |
| defaultMetadata | object | - | Metadata added to all logs |
| source | string | "openrouter" | Source identifier in logs |
Reliability Features
Automatic Retry with Exponential Backoff
Failed batches are automatically retried with exponential backoff (1s, 2s, 4s, ...) up to 30s max delay.
Circuit Breaker
After 5 consecutive failures, the circuit breaker opens for 30 seconds. During this time, flushes are skipped to prevent hammering a dead server.
Queue Limits
If the queue exceeds 1000 logs (configurable), oldest logs are dropped to prevent memory exhaustion.
Graceful Shutdown
Call shutdown() on app exit to flush remaining logs:
process.on("SIGTERM", async () => {
await telemetry.shutdown();
process.exit(0);
});Helper Utilities
import { createId, toIso, sanitizeHeaders, safeJsonParse } from "wiretap-llm";
// Generate unique ID
const id = createId(); // "abc123..."
// ISO timestamp
const ts = toIso(new Date()); // "2024-01-20T..."
// Remove sensitive headers
const headers = sanitizeHeaders(req.headers); // Strips auth headers
// Safe JSON parse
const { ok, value } = safeJsonParse(jsonString);Environment Variables
WIRETAP_BASE_URL=https://your-wiretap-instance.com
WIRETAP_API_KEY=wt_your_api_keyLicense
MIT
