logsneat SDK
A drop-in SDK to trace your AI agents — built on OpenTelemetry. Capture every workflow, tool call and LLM request, then explore it all in your dashboard.
Quickstart
Create an API key in the dashboard, initialize the SDK, and wrap your entry point in a trace.
import * as logsneat from 'logsneat';
import OpenAI from 'openai';
await logsneat.init({
apiKey: process.env.LOGSNEAT_API_KEY, // from Dashboard -> API Keys
endpoint: 'https://server-production-ddcd.up.railway.app', // your backend URL (see note below)
workflowName: 'my-agent',
instrumentations: ['openai'],
});
const openai = new OpenAI();
await logsneat.trace('handle_request', { kind: 'WORKFLOW' }, async () => {
const res = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Give me one fun fact about Paris.' }],
});
console.log(res.choices[0]?.message.content);
});
await logsneat.flush();
await logsneat.shutdown();endpoint to your backend.If you don't, it defaults to http://localhost:3004 — and if nothing is listening there, spans are dropped silently and nothing appears in the dashboard (the SDK never throws). You can also use the LOGSNEAT_ENDPOINT env var.Installation
Install the SDK alongside any provider you want to instrument (e.g. OpenAI).
npm install logsneat openaiConfiguration
Everything is configured through a single call to init().
await logsneat.init({
apiKey: process.env.LOGSNEAT_API_KEY,
endpoint: 'https://server-production-ddcd.up.railway.app', // required in prod (defaults to localhost)
workflowName: 'checkout-agent',
instrumentations: ['openai'],
tags: ['prod', 'checkout'],
userId: 'user_42',
autoSession: true,
});| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | LOGSNEAT_API_KEY env | Your project API key (required). Create one in the dashboard. |
| endpoint | string | https://ingest.logsneat.com | Where traces are sent. Override for self-hosting. |
| workflowName | string | "default" | Name of this app, shown across the dashboard. |
| instrumentations | string[] | ["openai"] | Libraries to auto-instrument. |
| tags | string[] | — | Free-form labels attached to every trace. |
| userId | string | — | Your end-user's id, for grouping and filtering. |
| sessionId | string | — | Group related traces into one conversation. |
| autoSession | boolean | false | Generate a session id automatically at startup. |
Core concepts
A trace is one end-to-end operation (a request, a job, a conversation turn). Inside it, spans represent the individual steps — tool calls, LLM requests, retrievals. Spans nest automatically: anything that runs inside an active trace becomes its child, so you get a full tree without wiring anything up.
Manual instrumentation
Use trace() to open a span around a block of work. The callback receives the span so you can add attributes.
// Wrap any unit of work. Child spans nest under it automatically.
await logsneat.trace(
'process_order',
{ kind: 'WORKFLOW', attributes: { 'order.id': 'A-1234' } },
async (span) => {
span.setAttribute('items', 3);
// ... your logic, LLM calls, tool calls ...
},
);Use span() to wrap a function so each call to it is recorded — ideal for tools.
// Wrap a function so every call to it produces a span.
const getWeather = logsneat.span(
{ kind: 'TOOL', name: 'get_weather', attributes: { provider: 'open-meteo' } },
async (city: string) => {
return fetchWeather(city);
},
);
await getWeather('Paris'); // -> a TOOL span, nested under the active traceSpan kinds
The kind tells the dashboard how to categorize a span. Pass it via the kind option on trace() or span().
| Kind | Use for |
|---|---|
| WORKFLOW | Top-level entry point of a request. |
| AGENT | An autonomous agent loop. |
| CHAIN | A fixed sequence of steps. |
| TOOL | A tool or function call. |
| RETRIEVER | Fetching documents (RAG). |
| RERANKER | Reranking retrieved results. |
| EMBEDDING | Generating embeddings. |
| GUARDRAIL | A safety or moderation check. |
| MCP_TOOL | A tool called over MCP. |
| VECTOR_STORE | A vector database query. |
Span attributes
Set logsneat.* attributes on a span (via the span passed to trace()) and the dashboard renders a specialized view for that span kind — a document list for retrievers, a pass/fail chip for guardrails, and so on.
// Set logsneat.* attributes on a span so the dashboard renders a rich view.
await logsneat.trace('retrieve', { kind: 'RETRIEVER' }, async (span) => {
span.setAttribute('logsneat.retrieval.query', query);
span.setAttribute('logsneat.retrieval.top_k', 5);
const docs = await store.search(query, 5);
span.setAttribute('logsneat.retrieval.documents', JSON.stringify(docs));
return docs;
});| Kind | Attribute | Description |
|---|---|---|
| RETRIEVER | logsneat.retrieval.query | The search query (string) |
| RETRIEVER | logsneat.retrieval.top_k | Number of results requested (int) |
| RETRIEVER | logsneat.retrieval.documents | Retrieved documents, JSON array → rendered as a doc list |
| RERANKER | logsneat.reranker.query | The original query |
| RERANKER | logsneat.reranker.input_documents | Documents before reranking (JSON) |
| RERANKER | logsneat.reranker.output_documents | Documents after reranking (JSON) |
| TOOL | input.value / output.value | Arguments and return value (auto for wrapped fns) |
| TOOL | logsneat.tool.name | Tool name shown in the dashboard |
| GUARDRAIL | logsneat.guardrail.input | Content being checked |
| GUARDRAIL | logsneat.guardrail.passed | Whether the check passed (bool) → pass/fail chip |
| GUARDRAIL | logsneat.guardrail.output | Result or failure reason |
| VECTOR_STORE | logsneat.vectordb.index_name | Index / collection name |
| VECTOR_STORE | logsneat.vectordb.embedding_model | Embedding model used |
| VECTOR_STORE | logsneat.vectordb.vector_dimension | Vector dimension (int) |
| VECTOR_STORE | logsneat.vectordb.similarity_algorithm | Distance metric (e.g. cosine) |
Auto-instrumentation
List a provider in instrumentations and the SDK patches it for you — its calls become spans with model, token usage and cost, with zero changes to your call sites.
await logsneat.init({
apiKey: process.env.LOGSNEAT_API_KEY,
instrumentations: ['openai'], // patches the OpenAI client
});
const openai = new OpenAI();
// No extra code needed — this call is captured as an LLM span with
// model, token counts and cost, nested under the active trace.
await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello!' }],
});Flushing & shutdown
Spans are batched and sent in the background. Long-running servers need nothing extra, but short scripts should flush before exiting.
// In short-lived scripts, flush before the process exits so
// no spans are dropped.
await logsneat.flush();
await logsneat.shutdown();Full example
A support agent: a tool call and an LLM response under one AGENT trace, attributed to a user and session.
import * as logsneat from 'logsneat';
import OpenAI from 'openai';
await logsneat.init({
apiKey: process.env.LOGSNEAT_API_KEY,
workflowName: 'support-agent',
instrumentations: ['openai'],
userId: 'user_42',
autoSession: true,
});
const openai = new OpenAI();
const lookupOrder = logsneat.span(
{ kind: 'TOOL', name: 'lookup_order' },
async (id: string) => ({ id, status: 'shipped' }),
);
await logsneat.trace('handle_ticket', { kind: 'AGENT' }, async () => {
const order = await lookupOrder('ORD-8842');
const res = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'user', content: 'Write a friendly status update for the customer.' },
],
});
console.log(order.status, res.choices[0]?.message.content);
});
await logsneat.flush();
await logsneat.shutdown();