npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

neatlogs

v1.0.2

Published

AI agent debugging, collaboration, and trace observability. Built for teams using CrewAI, OpenAI, and more.

Readme

neatlogs

OpenTelemetry-native observability for LLM applications — TypeScript SDK.

Automatically trace LLM calls, agent workflows, tool invocations, and retrieval pipelines. Ship production-ready observability with a few lines of code.

Quick Start

import { init, span, shutdown } from 'neatlogs';
import OpenAI from 'openai';

async function main() {
  // 1. Initialize the SDK
  await init({
    apiKey: process.env.NEATLOGS_API_KEY,
    instrumentations: ['openai'],
  });

  // 2. Create your LLM client AFTER init()
  const client = new OpenAI();

  // 3. Wrap functions with span() for observability
  const myWorkflow = span({ kind: 'WORKFLOW', name: 'qa-bot' }, async (query: string) => {
    const res = await client.chat.completions.create({
      model: 'gpt-4o',
      messages: [{ role: 'user', content: query }],
    });
    return res.choices[0].message.content;
  });

  const answer = await myWorkflow('What is TypeScript?');
  console.log(answer);

  await shutdown();
}

main().catch(console.error);

Installation

npm install neatlogs

For auto-instrumentation of specific LLM providers, install the corresponding peer dependency:

# OpenAI
npm install @arizeai/openinference-instrumentation-openai

# Anthropic
npm install @arizeai/openinference-instrumentation-anthropic

# AWS Bedrock
npm install @arizeai/openinference-instrumentation-bedrock

# LangChain
npm install @arizeai/openinference-instrumentation-langchain

# MCP (Model Context Protocol)
npm install @arizeai/openinference-instrumentation-mcp

# BeeAI
npm install @arizeai/openinference-instrumentation-beeai

# Claude Agent SDK
npm install @arizeai/openinference-instrumentation-claude-agent-sdk

# Google GenAI (@google/genai)
npm install @google/genai

Core Concepts

| Function | Purpose | |----------|---------| | init() | Initialize the SDK — sets up OTel providers, exporters, and instrumentation | | span() | Wrap a function with observability — captures inputs, outputs, timing, and errors | | trace() | Create a manual span with prompt template tracking and multi-turn session support | | log() | Capture timestamped log steps within the active trace | | shutdown() | Flush all pending data and shut down the SDK gracefully |

Important: Initialization Order

init() is async and must be called before creating any LLM client instances. This is because instrumentation works by monkey-patching libraries at init time.

// ✅ Correct
await init({ instrumentations: ['openai'] });
const client = new OpenAI(); // patched

// ❌ Wrong — client created before patching
const client = new OpenAI(); // NOT patched
await init({ instrumentations: ['openai'] });

Important: No Top-Level Await

Always wrap your code in an async function main() pattern:

async function main() {
  await init({ ... });
  // ... your code
  await shutdown();
}

main().catch(console.error);

API Reference

init(options?)

Initialize the Neatlogs SDK. Returns Promise<void>.

await init({
  apiKey: process.env.NEATLOGS_API_KEY,
  instrumentations: ['openai', 'anthropic'],
  debug: true,
});

InitOptions

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | process.env.NEATLOGS_API_KEY | Neatlogs API key. Export disabled if not set. | | baseUrl | string | 'https://app.neatlogs.com' | Base URL for the Neatlogs API. | | workflowName | string | Derived from process.argv[1] | Name of the workflow being traced. | | sessionId | string | — | Explicit session ID for grouping traces. | | autoSession | boolean | false | Auto-generate a session ID if none provided. | | userId | string | — | User identifier for the session. | | tags | string[] | — | Tags attached to all spans. | | metadata | Record<string, any> | — | Custom metadata attached to all spans. | | debug | boolean | false | Enable debug logging. | | disableExport | boolean | false | Disable export to Neatlogs backend. | | instrumentations | string[] | — | Libraries to auto-instrument (e.g., ['openai']). | | mask | MaskFunction | — | Global mask function applied to all spans. | | sampleRate | number | 1.0 | Sampling rate (0.0 to 1.0). | | captureLogs | boolean | false | Capture log records via OTel LoggerProvider. | | traceContent | boolean | true | Capture input/output content on spans. | | pii | 'redact' &#124; 'hash' &#124; false | — | PII detection mode. | | endpoint | string | 'https://staging-cloud.neatlogs.com/api/data/v4/batch' | Backend endpoint URL. | | batchSize | number | 100 | Maximum spans per export batch. | | flushInterval | number | 5 | Seconds between batch flushes. | | piiEnabled | boolean | — | Override team-level PII redaction toggle. | | piiSpanTypes | string[] | — | Override which span types have server-side PII redaction. |


span(options, fn)

Wrap a function with OpenTelemetry span instrumentation. Returns a new function with the same signature that automatically creates a span when called.

const myFn = span({ kind: 'WORKFLOW', name: 'my-workflow' }, async (input: string) => {
  return await process(input);
});

const result = await myFn('hello');

The span() function is a higher-order function: it takes your function and returns a new, instrumented version. The returned function has the same arguments and return type as the original.

SpanOptions

| Option | Type | Default | Description | |--------|------|---------|-------------| | kind | SpanKind | — | Required. The kind of span. | | name | string | Function name | Custom name for the span. | | captureInput | boolean | true | Capture function input. | | captureOutput | boolean | true | Capture function output. | | captureStdout | boolean | false | Capture stdout during execution. | | tags | string[] | — | Tags for this span. | | metadata | Record<string, any> | — | Custom metadata for this span. | | mask | MaskFunction | — | Per-span mask function. | | internal | boolean | — | Mark span as internal (not user-facing). | | role | string | — | Agent role (for kind: 'AGENT'). | | goal | string | — | Agent goal (for kind: 'AGENT'). | | toolName | string | — | Tool name (for kind: 'TOOL'). | | parameters | Record<string, any> | — | Tool parameters schema (for kind: 'TOOL'). | | model | string | — | Embedding model name (for kind: 'EMBEDDING'). | | dimension | number | — | Embedding dimension (for kind: 'EMBEDDING'). |

SpanKind Values

| Kind | Use For | |------|---------| | WORKFLOW | Top-level orchestration / pipelines | | AGENT | Autonomous agents with roles and goals | | CHAIN | Sequential processing steps | | TOOL | External tool calls (APIs, databases, etc.) | | RETRIEVER | Document / vector retrieval | | EMBEDDING | Vector embedding operations | | MCP_TOOL | Model Context Protocol tool calls | | GUARDRAIL | Safety checks and content filters |


Span() Decorator

TC39 Stage 3 class-method decorator for instrumenting class methods.

class MyAgent {
  @Span({ kind: 'AGENT', role: 'researcher' })
  async run(query: string) {
    // automatically traced
    return await this.search(query);
  }

  @Span({ kind: 'TOOL', name: 'web-search' })
  async search(query: string) {
    return { results: ['...'] };
  }
}

Note: Requires TypeScript 5.0+ with "experimentalDecorators": false (the new TC39 Stage 3 decorators, not legacy decorators).


trace(options, fn)

Create a manual span that runs a callback. Unlike span(), which wraps a reusable function, trace() executes inline and is ideal for:

  • Prompt template tracking — associate PromptTemplate instances with spans
  • Multi-turn sessions — automatically creates root traces when sessionId is set
  • Grouping operations — wrap a block of code in an ad-hoc span
const result = await trace({
  name: 'llm-call',
  promptTemplate: myTemplate,
}, async (activeSpan) => {
  const rendered = myTemplate.compile({ name: 'world' });
  return await callLLM(rendered);
});

TraceOptions

| Option | Type | Default | Description | |--------|------|---------|-------------| | name | string | — | Required. Name for the trace span. | | kind | SpanKind | 'CHAIN' | Span kind. | | promptTemplate | string &#124; PromptTemplate | — | Prompt template to track. | | promptVariables | Record<string, any> | — | Prompt variables for the template. | | userPromptTemplate | string &#124; UserPromptTemplate | — | User prompt template. | | userPromptVariables | Record<string, any> | — | User prompt variables. | | version | string | — | Prompt version identifier. | | captureStdout | boolean | false | Capture stdout during execution. | | mask | MaskFunction | — | Per-trace mask function. | | attributes | Record<string, any> | — | Custom attributes on the span. | | tags | string[] | — | Tags for this trace. | | metadata | Record<string, any> | — | Custom metadata. |

span() vs trace()

| | span() | trace() | |---|----------|-----------| | Pattern | Higher-order function wrapper | Inline callback | | Reuse | Returns a reusable function | Executes immediately | | Prompt tracking | No | Yes — promptTemplate, promptVariables | | Session-aware | No | Yes — creates root traces for multi-turn sessions | | Best for | Wrapping functions/methods | Ad-hoc tracing blocks, prompt versioning |


log(template, options?)

Capture a timestamped log step within the current trace. Uses {key} placeholders for template variables.

log('Processing query: {query}', { query: 'What is TypeScript?' });
log('Retrieved {count} documents in {ms}ms', { count: 5, ms: 120 });
log('Classification result', { category: 'technical', level: 'debug' });

Requires captureLogs: true in init(). Log records are emitted as OTel LogRecords associated with the active span.

The special level key sets the log severity ('info', 'debug', 'warn', 'error'). All other keys are template variables and are also recorded as log.{key} attributes.


PromptTemplate / UserPromptTemplate

Template classes for prompt versioning with {{variable}} placeholders. When used with trace(), variables are automatically captured on the span for prompt tracking.

// String template
const systemPrompt = new PromptTemplate(
  'You are a {{role}} assistant specializing in {{topic}}.'
);

// Message array template
const chatPrompt = new PromptTemplate([
  { role: 'system', content: 'You are a {{role}} assistant.' },
  { role: 'user', content: '{{question}}' },
]);

// Compile with variables
const rendered = systemPrompt.compile({ role: 'helpful', topic: 'TypeScript' });
// => 'You are a helpful assistant specializing in TypeScript.'

// Access template metadata
systemPrompt.variables;  // ['role', 'topic']
systemPrompt.template;   // raw template string

UserPromptTemplate is identical but stores context separately — use it for the user/human turn in multi-template setups:

const systemTpl = new PromptTemplate('You are a {{role}} assistant.');
const userTpl = new UserPromptTemplate('{{question}}');

await trace({
  name: 'qa',
  promptTemplate: systemTpl,
  userPromptTemplate: userTpl,
}, async () => {
  const system = systemTpl.compile({ role: 'helpful' });
  const user = userTpl.compile({ question: 'What is TypeScript?' });
  // Variables from both templates are captured on the span
});

PromptClient

Server-side prompt management for storing, versioning, and retrieving prompts from the Neatlogs backend.

import { PromptClient } from 'neatlogs';

const client = new PromptClient({
  baseUrl: 'https://app.neatlogs.com',
  apiKey: process.env.NEATLOGS_API_KEY!,
});

// Create a prompt
const prompt = await client.createPrompt({
  name: 'qa-system',
  content: 'You are a {{role}} assistant for {{company}}.',
  labels: ['production'],
});

// Fetch by name (returns latest version)
const handle = await client.getPrompt('qa-system');

// Fetch by label or version
const prod = await client.getPrompt('qa-system', { label: 'production' });
const v2 = await client.getPrompt('qa-system', { version: 2 });

// Compile with variables
const rendered = handle.compile({ role: 'helpful', company: 'Acme' });

// Compile as message array
const messages = handle.compileMessages({ role: 'helpful', company: 'Acme' });

// List all prompts
const all = await client.listPrompts();

// Update prompt content
await client.updatePrompt('qa-system', { content: 'Updated: {{role}} for {{company}}.' });

// Save a new version
await client.saveAsVersion('qa-system', { label: 'v2' });

// Delete a prompt
await client.deletePrompt('qa-system');

Module-level convenience functions are also available after init():

import { init, getPrompt, fetchPrompt, listPrompts, createPrompt, updatePrompt, saveAsVersion, deletePrompt, removeTag } from 'neatlogs';

await init({ apiKey: process.env.NEATLOGS_API_KEY });

const handle = await getPrompt('my-prompt');
const rendered = handle.compile({ name: 'world' });

flush() / shutdown()

// Flush pending spans without shutting down
await flush();

// Flush and shut down — call before process exit
await shutdown();

shutdown() resets all SDK state so init() can be called again if needed.


bindTemplates(llm, systemTpl, userTpl?, compiledVars?)

Bind prompt templates to a LangChain-compatible LLM so templates are automatically captured on LLM spans managed by frameworks like CrewAI.

import { bindTemplates, PromptTemplate, UserPromptTemplate } from 'neatlogs';

const systemTpl = new PromptTemplate('You are a {{role}} assistant.');
const userTpl = new UserPromptTemplate('Research: {{topic}}');

const boundLlm = bindTemplates(llm, systemTpl, userTpl, { topic: 'AI safety' });
// Pass boundLlm to your framework — template context is injected on every invoke()

registerCrewaiTask(taskId, taskDescription)

Register a CrewAI task for automatic span annotation.

import { registerCrewaiTask } from 'neatlogs';

registerCrewaiTask('research-task', 'Research the latest AI developments');

Supported Instrumentations

Auto-Instrumented (via OpenInference)

These libraries are automatically instrumented when listed in instrumentations:

| Library | Package | Instrumentation | |---------|---------|-----------------| | openai | openai | @arizeai/openinference-instrumentation-openai | | anthropic | @anthropic-ai/sdk | @arizeai/openinference-instrumentation-anthropic | | bedrock | @aws-sdk/client-bedrock-runtime | @arizeai/openinference-instrumentation-bedrock | | langchain | @langchain/core | @arizeai/openinference-instrumentation-langchain | | mcp | @modelcontextprotocol/sdk | @arizeai/openinference-instrumentation-mcp | | beeai | beeai-framework | @arizeai/openinference-instrumentation-beeai | | claude_agent_sdk | @anthropic-ai/claude-agent-sdk | @arizeai/openinference-instrumentation-claude-agent-sdk |

Custom Instrumentors (built into neatlogs)

| Library | Package | Notes | |---------|---------|-------| | google_genai | @google/genai | Custom neatlogs instrumentor | | crewai | crewai | Custom neatlogs instrumentor; auto-loads litellm |

Registry Entries (not yet instrumented in TypeScript)

The following libraries are registered in the instrumentation registry for future support. Passing them to instrumentations will log a debug message and skip gracefully:

cohere, groq, together, vertexai, google_generativeai, mistralai, ollama, watsonx, alephalpha, replicate, sagemaker, huggingface_hub, litellm, langgraph, llamaindex, autogen, haystack, dspy, chromadb, pinecone, weaviate, qdrant, milvus, opensearch, elasticsearch, redis, marqo, instructor, guardrails, google_adk, agno, openai_agents, pydantic_ai, smolagents, strands, pipecat, portkey, promptflow

Configuration

Environment Variables

| Variable | Description | |----------|-------------| | NEATLOGS_API_KEY | API key (fallback when apiKey option is not provided) | | NEATLOGS_DISABLE_EXPORT | Set to true, 1, or yes to disable export |

Programmatic Configuration

All configuration is passed via init() options. See the InitOptions table above.

await init({
  apiKey: process.env.NEATLOGS_API_KEY,
  workflowName: 'my-pipeline',
  sessionId: 'session-123',
  userId: 'user-456',
  tags: ['production', 'v2'],
  metadata: { environment: 'prod' },
  instrumentations: ['openai', 'anthropic'],
  sampleRate: 0.5,
  captureLogs: true,
  debug: true,
});

PII Masking

Global Mask

Apply a mask function to all spans:

await init({
  apiKey: process.env.NEATLOGS_API_KEY,
  mask: (spanData) => {
    // Redact email addresses
    for (const [key, value] of Object.entries(spanData)) {
      if (typeof value === 'string') {
        spanData[key] = value.replace(/[\w.-]+@[\w.-]+/g, '[REDACTED]');
      }
    }
    return spanData;
  },
});

Per-Span Mask

Apply a mask to a specific span:

const sensitive = span({
  kind: 'TOOL',
  name: 'user-lookup',
  mask: (spanData) => {
    delete spanData['input.value'];
    return spanData;
  },
}, async (userId: string) => {
  return await lookupUser(userId);
});

Per-Trace Mask

await trace({
  name: 'sensitive-operation',
  mask: (spanData) => {
    // Return null to drop the span entirely
    return null;
  },
}, async () => {
  // This span will not be exported
});

Server-Side PII Redaction

await init({
  apiKey: process.env.NEATLOGS_API_KEY,
  pii: 'redact',          // or 'hash' or false
  piiEnabled: true,        // override team-level toggle
  piiSpanTypes: ['LLM'],   // only redact LLM spans
});

Examples

See the examples/ directory for complete, runnable examples:

| File | Description | |------|-------------| | basic-openai.ts | Basic OpenAI usage with auto-instrumentation | | prompt-management.ts | PromptTemplate + trace() for prompt versioning | | multi-agent-workflow.ts | Nested spans: WORKFLOW → AGENT → TOOL | | custom-spans.ts | All span kinds: WORKFLOW, CHAIN, AGENT, TOOL, RETRIEVER, EMBEDDING, GUARDRAIL |

Run any example with:

NEATLOGS_API_KEY=your-key npx tsx examples/basic-openai.ts

License

MIT