KoraLog

Introductionv0.1.4

Koralog is an observability SDK for applications using LLMs (Anthropic, OpenAI, Gemini). It monitors latency, errors, estimated cost, and usage per feature/user — sending data to the Koralog backend without interfering with your application's flow.

The SDK is designed to be non-intrusive: if not initialized, track() passes through without instrumenting. Network failures are always silent and never propagate to your application.

How it works

1Call init() once at application boot with your API key and options.
2Wrap LLM calls with track() — it measures latency, captures errors, and builds a structured payload.
3Payloads are queued and sent in batch to the Koralog ingestion endpoint.

Installation

Install Koralog from npm:

bash
npm install koralog

AI-powered Setup

Instead of applying track() manually, paste the Koralog setup prompt into any AI coding assistant (Claude, Cursor, Copilot) and it will handle everything autonomously:

1Detect your package manager and install the SDK
2Create the koralog.ts client singleton (src/lib/ or lib/)
3Find every LLM call in the project via grep
4Wrap all calls with track() — SDK, fetch, and axios patterns
5Run tsc --noEmit to verify the changes compile

How to use

Open a new conversation with your AI assistant, paste the full prompt, and let it run. No extra context needed — the prompt is self-contained.

View setup prompt

fetch() calls — different wrapping pattern

The prompt handles this automatically. If you're applying manually: when wrapping a raw fetch call, the arrow function must resolve to parsed JSON. Chain .then(res => res.json()) inside the arrow function, and remove any .json() call that previously existed after the await — it will throw since the response is already consumed.

ts
// Before
const response = await fetch('https://api.openai.com/v1/chat/completions', options)
const data = await response.json()
const content = data.choices[0].message.content
ts
// After
import { track } from '@/lib/koralog'

const data = await track(
  () => fetch('https://api.openai.com/v1/chat/completions', options).then(res => res.json()),
  {
    feature: 'chat-sendMessage',
    provider: 'openai',
    model: 'gpt-4o', // required for fetch — not auto-detected
  }
)
const content = data.choices[0].message.content
The model field is always required for fetch and axios calls — the SDK cannot read it from the response automatically.

Quick Start

Full working example — init, identify a user, track an LLM call, and flush before shutdown.

ts
import { init, track, setUser, shutdown } from 'koralog'

init({
  apiKey: 'kora_live_abc123',
  serverless: false,
  debug: false,
})

setUser({ id: 'usr_456', email: 'maria@company.com', name: 'Maria' })

const response = await track(
  () => anthropic.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: 1024,
    messages: [{ role: 'user', content: 'Explain JWT in 3 lines.' }],
  }),
  {
    feature: 'explain-tech',
    provider: 'anthropic',
    model: 'claude-sonnet-4-6',
    tags: { env: 'prod', version: '2' },
    requestParams: { temperature: 0.5, max_tokens: 1024 },
  }
)

// On server shutdown:
await shutdown()

init()

Initializes the SDK. Must be called once at application boot, before any track() calls. Calling it multiple times has no effect after the first call.

ts
import { init } from 'koralog'

init({
  apiKey: 'kora_live_abc123',
  debug: false,
  serverless: true,
})
If the apiKey starts with kora_test_, events are sent to http://localhost:8080. Use kora_live_ for production.

track()

Wraps an async function (typically an LLM call) and records latency, status, and error type. Returns the same value the original function would return. If the SDK is not initialized, the function runs without any overhead.

ts
import { track } from 'koralog'

const response = await track(
  () => anthropic.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: 1024,
    messages: [{ role: 'user', content: 'Hello' }],
  }),
  {
    feature: 'chat',
    provider: 'anthropic',
    model: 'claude-sonnet-4-6',
    tags: { env: 'prod' },
  }
)
The original exception is always rethrown normally after recording. track() never suppresses errors from your application.

setUser()

Defines the active user for the current session or request. User data (id, email, name) is merged into all subsequent events until clearUser() is called.

clearUser()

Removes the active user. Events after this call will not include user data unless setUser() is called again.

ts
import { setUser, clearUser } from 'koralog'

setUser({ id: 'usr_123', email: 'user@example.com', name: 'Alice' })

// later, to remove the active user:
clearUser()

flush()

Forces immediate dispatch of all queued events. Returns a Promise that resolves when the flush completes.

shutdown()

Alias for flush(). Semantically clearer at process termination — call it in your SIGTERM or process.on('exit') handler to ensure all events are sent before the process exits.

ts
import { flush, shutdown } from 'koralog'

// Force immediate send:
await flush()

// Before process exit:
await shutdown()

KoralogConfig

Options object passed to init(). Only apiKey is required.

ParameterTypeDefaultDescription
apiKeystringrequiredYour Koralog API key. Prefix determines environment: kora_test_* sends to localhost, kora_live_* sends to production.
baseUrlstringCustom ingestion endpoint URL. Overrides the endpoint auto-detected from the key prefix.
debugbooleanfalseLogs all events to the console. Useful during development and local testing.
serverlessbooleantrueFlush immediately after each event. Default true — correct for Vercel, AWS Lambda, and any ephemeral runtime.
batchSizenumber10Maximum events to accumulate before an automatic flush is triggered. Only active when serverless: false.
flushIntervalnumber5000Timer interval in milliseconds for automatic flushing. Only active when serverless: false.
capturePromptsbooleanfalseWhen true, includes the full LLM response content in the payload (content, choices, candidates fields).
truncateAtnumber500Maximum character count for text fields in the payload. Longer values are truncated.
showSensitiveFieldsbooleanfalseWhen true, includes userIp and custom sensitive fields in the payload.

TrackMetadata

Optional second argument to track(). All fields are optional and can be combined freely.

ParameterTypeDescription
featurestringFeature name for grouping and filtering (e.g. "chat", "summary", "search").
userIdstringOverrides the userId set via setUser() for this specific event only.
userEmailstringOverrides the userEmail set via setUser() for this specific event only.
modelstringLLM model identifier (e.g. "claude-sonnet-4-6", "gpt-4o", "gemini-2.0-flash").
providerstringLLM provider: "anthropic" | "openai" | "gemini" | "other".
tagsobjectFree key-value pairs for filtering in the dashboard (e.g. { env: "prod", version: "2" }).
requestParamsobjectParameters passed to the LLM request (temperature, max_tokens, top_p, etc.).
userIpstringUser IP address. Only included in the payload when showSensitiveFields: true.
sensitiveobjectArbitrary sensitive fields. Only included in the payload when showSensitiveFields: true.

Payload Reference

Each event sent to the Koralog backend contains these fields:

ParameterTypeDescription
timestampstringISO 8601 timestamp of when the LLM call was initiated.
api_keystringAPI key used for authentication.
sdk_versionstringVersion string of the SDK that generated the event.
latency_msnumberTotal execution time of the wrapped function in milliseconds.
statusstring"success" if the function resolved, "error" if it threw.
error_typestring?Classified error type. Present only when status is "error".
error_messagestring?The error message string. Present only when status is "error".
raw_responseobjectRaw LLM response object. Sensitive content fields are stripped by default. Set capturePrompts: true to include them.
metadataobjectAll TrackMetadata fields merged with the active user data from setUser().

Error Classification

The SDK automatically classifies exceptions intercepted inside track(). After recording, the original exception is always rethrown.

Error typeTriggered when
timeoutAbortError thrown, or message contains "timeout".
rate_limitHTTP 429, or message contains "rate limit".
context_exceededMessage contains "context" + "limit", or "token" + "limit".
invalid_requestHTTP 400 or 422.
unknownAny other exception type.
The original exception is always rethrown after recording. track() never suppresses errors.

Batching Strategy

Serverless mode (default)

Each event triggers an immediate flush(). Correct for Vercel, AWS Lambda, and any runtime where the process may be killed between requests.

Long-running mode

Set serverless: false to accumulate events in memory and flush in bulk. A flush is triggered when:

  • The queue reaches batchSize events (default: 10)
  • The flushInterval timer fires (default: 5 000 ms)
  • flush() or shutdown() is called explicitly
  • The Node.js process emits beforeExit
All flush requests are POST with a 3-second timeout. Network failures are silent — they never propagate errors to your application.

Privacy & Sensitive Fields

Koralog is privacy-first by default. The SDK strips response content fields before sending payloads:

ProviderStripped fields
Anthropiccontent
OpenAIchoices output
Geminicandidates

To capture full response content, set capturePrompts: true in init().

To include userIp and custom sensitive fields, set showSensitiveFields: true.

Review your privacy policy and data retention terms before enabling capturePrompts or showSensitiveFields in production.

API Key Modes

The API key prefix determines where events are sent. A custom baseUrl overrides both prefix-based endpoints.

Key prefixEndpointDescription
kora_test_*localhost:8080Sends events to http://localhost:8080. Ideal for local development — run the Koralog dev server to inspect payloads.
kora_live_*api.koralog.comSends events to https://api.koralog.com. Use this key in production.

Get your API key

Create a project in the Koralog dashboard to generate your API key.

Open dashboard