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.

ts
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();
Set 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).

bash
npm install logsneat openai

logsneat on npm ↗

Configuration

Everything is configured through a single call to init().

ts
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,
});
OptionTypeDefaultDescription
apiKeystringLOGSNEAT_API_KEY envYour project API key (required). Create one in the dashboard.
endpointstringhttps://ingest.logsneat.comWhere traces are sent. Override for self-hosting.
workflowNamestring"default"Name of this app, shown across the dashboard.
instrumentationsstring[]["openai"]Libraries to auto-instrument.
tagsstring[]Free-form labels attached to every trace.
userIdstringYour end-user's id, for grouping and filtering.
sessionIdstringGroup related traces into one conversation.
autoSessionbooleanfalseGenerate 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.

ts
// 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.

ts
// 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 trace

Span kinds

The kind tells the dashboard how to categorize a span. Pass it via the kind option on trace() or span().

KindUse for
WORKFLOWTop-level entry point of a request.
AGENTAn autonomous agent loop.
CHAINA fixed sequence of steps.
TOOLA tool or function call.
RETRIEVERFetching documents (RAG).
RERANKERReranking retrieved results.
EMBEDDINGGenerating embeddings.
GUARDRAILA safety or moderation check.
MCP_TOOLA tool called over MCP.
VECTOR_STOREA 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.

ts
// 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;
});
KindAttributeDescription
RETRIEVERlogsneat.retrieval.queryThe search query (string)
RETRIEVERlogsneat.retrieval.top_kNumber of results requested (int)
RETRIEVERlogsneat.retrieval.documentsRetrieved documents, JSON array → rendered as a doc list
RERANKERlogsneat.reranker.queryThe original query
RERANKERlogsneat.reranker.input_documentsDocuments before reranking (JSON)
RERANKERlogsneat.reranker.output_documentsDocuments after reranking (JSON)
TOOLinput.value / output.valueArguments and return value (auto for wrapped fns)
TOOLlogsneat.tool.nameTool name shown in the dashboard
GUARDRAILlogsneat.guardrail.inputContent being checked
GUARDRAILlogsneat.guardrail.passedWhether the check passed (bool) → pass/fail chip
GUARDRAILlogsneat.guardrail.outputResult or failure reason
VECTOR_STORElogsneat.vectordb.index_nameIndex / collection name
VECTOR_STORElogsneat.vectordb.embedding_modelEmbedding model used
VECTOR_STORElogsneat.vectordb.vector_dimensionVector dimension (int)
VECTOR_STORElogsneat.vectordb.similarity_algorithmDistance 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.

ts
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.

ts
// 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.

ts
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();