neatlogs 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 neatlogs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,571 @@
1
+ # neatlogs
2
+
3
+ OpenTelemetry-native observability for LLM applications — TypeScript SDK.
4
+
5
+ Automatically trace LLM calls, agent workflows, tool invocations, and retrieval pipelines. Ship production-ready observability with a few lines of code.
6
+
7
+ ## Quick Start
8
+
9
+ ```typescript
10
+ import { init, span, shutdown } from 'neatlogs';
11
+ import OpenAI from 'openai';
12
+
13
+ async function main() {
14
+ // 1. Initialize the SDK
15
+ await init({
16
+ apiKey: process.env.NEATLOGS_API_KEY,
17
+ instrumentations: ['openai'],
18
+ });
19
+
20
+ // 2. Create your LLM client AFTER init()
21
+ const client = new OpenAI();
22
+
23
+ // 3. Wrap functions with span() for observability
24
+ const myWorkflow = span({ kind: 'WORKFLOW', name: 'qa-bot' }, async (query: string) => {
25
+ const res = await client.chat.completions.create({
26
+ model: 'gpt-4o',
27
+ messages: [{ role: 'user', content: query }],
28
+ });
29
+ return res.choices[0].message.content;
30
+ });
31
+
32
+ const answer = await myWorkflow('What is TypeScript?');
33
+ console.log(answer);
34
+
35
+ await shutdown();
36
+ }
37
+
38
+ main().catch(console.error);
39
+ ```
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ npm install neatlogs
45
+ ```
46
+
47
+ For auto-instrumentation of specific LLM providers, install the corresponding peer dependency:
48
+
49
+ ```bash
50
+ # OpenAI
51
+ npm install @arizeai/openinference-instrumentation-openai
52
+
53
+ # Anthropic
54
+ npm install @arizeai/openinference-instrumentation-anthropic
55
+
56
+ # AWS Bedrock
57
+ npm install @arizeai/openinference-instrumentation-bedrock
58
+
59
+ # LangChain
60
+ npm install @arizeai/openinference-instrumentation-langchain
61
+
62
+ # MCP (Model Context Protocol)
63
+ npm install @arizeai/openinference-instrumentation-mcp
64
+
65
+ # BeeAI
66
+ npm install @arizeai/openinference-instrumentation-beeai
67
+
68
+ # Claude Agent SDK
69
+ npm install @arizeai/openinference-instrumentation-claude-agent-sdk
70
+
71
+ # Google GenAI (@google/genai)
72
+ npm install @google/genai
73
+ ```
74
+
75
+ ## Core Concepts
76
+
77
+ | Function | Purpose |
78
+ |----------|---------|
79
+ | `init()` | Initialize the SDK — sets up OTel providers, exporters, and instrumentation |
80
+ | `span()` | Wrap a function with observability — captures inputs, outputs, timing, and errors |
81
+ | `trace()` | Create a manual span with prompt template tracking and multi-turn session support |
82
+ | `log()` | Capture timestamped log steps within the active trace |
83
+ | `shutdown()` | Flush all pending data and shut down the SDK gracefully |
84
+
85
+ ### Important: Initialization Order
86
+
87
+ `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.
88
+
89
+ ```typescript
90
+ // ✅ Correct
91
+ await init({ instrumentations: ['openai'] });
92
+ const client = new OpenAI(); // patched
93
+
94
+ // ❌ Wrong — client created before patching
95
+ const client = new OpenAI(); // NOT patched
96
+ await init({ instrumentations: ['openai'] });
97
+ ```
98
+
99
+ ### Important: No Top-Level Await
100
+
101
+ Always wrap your code in an `async function main()` pattern:
102
+
103
+ ```typescript
104
+ async function main() {
105
+ await init({ ... });
106
+ // ... your code
107
+ await shutdown();
108
+ }
109
+
110
+ main().catch(console.error);
111
+ ```
112
+
113
+ ## API Reference
114
+
115
+ ### `init(options?)`
116
+
117
+ Initialize the Neatlogs SDK. Returns `Promise<void>`.
118
+
119
+ ```typescript
120
+ await init({
121
+ apiKey: process.env.NEATLOGS_API_KEY,
122
+ instrumentations: ['openai', 'anthropic'],
123
+ debug: true,
124
+ });
125
+ ```
126
+
127
+ #### `InitOptions`
128
+
129
+ | Option | Type | Default | Description |
130
+ |--------|------|---------|-------------|
131
+ | `apiKey` | `string` | `process.env.NEATLOGS_API_KEY` | Neatlogs API key. Export disabled if not set. |
132
+ | `baseUrl` | `string` | `'https://app.neatlogs.com'` | Base URL for the Neatlogs API. |
133
+ | `workflowName` | `string` | Derived from `process.argv[1]` | Name of the workflow being traced. |
134
+ | `sessionId` | `string` | — | Explicit session ID for grouping traces. |
135
+ | `autoSession` | `boolean` | `false` | Auto-generate a session ID if none provided. |
136
+ | `userId` | `string` | — | User identifier for the session. |
137
+ | `tags` | `string[]` | — | Tags attached to all spans. |
138
+ | `metadata` | `Record<string, any>` | — | Custom metadata attached to all spans. |
139
+ | `debug` | `boolean` | `false` | Enable debug logging. |
140
+ | `disableExport` | `boolean` | `false` | Disable export to Neatlogs backend. |
141
+ | `instrumentations` | `string[]` | — | Libraries to auto-instrument (e.g., `['openai']`). |
142
+ | `mask` | `MaskFunction` | — | Global mask function applied to all spans. |
143
+ | `sampleRate` | `number` | `1.0` | Sampling rate (0.0 to 1.0). |
144
+ | `captureLogs` | `boolean` | `false` | Capture log records via OTel LoggerProvider. |
145
+ | `traceContent` | `boolean` | `true` | Capture input/output content on spans. |
146
+ | `pii` | `'redact' &#124; 'hash' &#124; false` | — | PII detection mode. |
147
+ | `endpoint` | `string` | `'https://staging-cloud.neatlogs.com/api/data/v4/batch'` | Backend endpoint URL. |
148
+ | `batchSize` | `number` | `100` | Maximum spans per export batch. |
149
+ | `flushInterval` | `number` | `5` | Seconds between batch flushes. |
150
+ | `piiEnabled` | `boolean` | — | Override team-level PII redaction toggle. |
151
+ | `piiSpanTypes` | `string[]` | — | Override which span types have server-side PII redaction. |
152
+
153
+ ---
154
+
155
+ ### `span(options, fn)`
156
+
157
+ Wrap a function with OpenTelemetry span instrumentation. Returns a new function with the same signature that automatically creates a span when called.
158
+
159
+ ```typescript
160
+ const myFn = span({ kind: 'WORKFLOW', name: 'my-workflow' }, async (input: string) => {
161
+ return await process(input);
162
+ });
163
+
164
+ const result = await myFn('hello');
165
+ ```
166
+
167
+ 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.
168
+
169
+ #### `SpanOptions`
170
+
171
+ | Option | Type | Default | Description |
172
+ |--------|------|---------|-------------|
173
+ | `kind` | `SpanKind` | — | **Required.** The kind of span. |
174
+ | `name` | `string` | Function name | Custom name for the span. |
175
+ | `captureInput` | `boolean` | `true` | Capture function input. |
176
+ | `captureOutput` | `boolean` | `true` | Capture function output. |
177
+ | `captureStdout` | `boolean` | `false` | Capture stdout during execution. |
178
+ | `tags` | `string[]` | — | Tags for this span. |
179
+ | `metadata` | `Record<string, any>` | — | Custom metadata for this span. |
180
+ | `mask` | `MaskFunction` | — | Per-span mask function. |
181
+ | `internal` | `boolean` | — | Mark span as internal (not user-facing). |
182
+ | `role` | `string` | — | Agent role (for `kind: 'AGENT'`). |
183
+ | `goal` | `string` | — | Agent goal (for `kind: 'AGENT'`). |
184
+ | `toolName` | `string` | — | Tool name (for `kind: 'TOOL'`). |
185
+ | `parameters` | `Record<string, any>` | — | Tool parameters schema (for `kind: 'TOOL'`). |
186
+ | `model` | `string` | — | Embedding model name (for `kind: 'EMBEDDING'`). |
187
+ | `dimension` | `number` | — | Embedding dimension (for `kind: 'EMBEDDING'`). |
188
+
189
+ #### `SpanKind` Values
190
+
191
+ | Kind | Use For |
192
+ |------|---------|
193
+ | `WORKFLOW` | Top-level orchestration / pipelines |
194
+ | `AGENT` | Autonomous agents with roles and goals |
195
+ | `CHAIN` | Sequential processing steps |
196
+ | `TOOL` | External tool calls (APIs, databases, etc.) |
197
+ | `RETRIEVER` | Document / vector retrieval |
198
+ | `EMBEDDING` | Vector embedding operations |
199
+ | `MCP_TOOL` | Model Context Protocol tool calls |
200
+ | `GUARDRAIL` | Safety checks and content filters |
201
+
202
+ ---
203
+
204
+ ### `Span()` Decorator
205
+
206
+ TC39 Stage 3 class-method decorator for instrumenting class methods.
207
+
208
+ ```typescript
209
+ class MyAgent {
210
+ @Span({ kind: 'AGENT', role: 'researcher' })
211
+ async run(query: string) {
212
+ // automatically traced
213
+ return await this.search(query);
214
+ }
215
+
216
+ @Span({ kind: 'TOOL', name: 'web-search' })
217
+ async search(query: string) {
218
+ return { results: ['...'] };
219
+ }
220
+ }
221
+ ```
222
+
223
+ > **Note:** Requires TypeScript 5.0+ with `"experimentalDecorators": false` (the new TC39 Stage 3 decorators, not legacy decorators).
224
+
225
+ ---
226
+
227
+ ### `trace(options, fn)`
228
+
229
+ Create a manual span that runs a callback. Unlike `span()`, which wraps a reusable function, `trace()` executes inline and is ideal for:
230
+
231
+ - **Prompt template tracking** — associate `PromptTemplate` instances with spans
232
+ - **Multi-turn sessions** — automatically creates root traces when `sessionId` is set
233
+ - **Grouping operations** — wrap a block of code in an ad-hoc span
234
+
235
+ ```typescript
236
+ const result = await trace({
237
+ name: 'llm-call',
238
+ promptTemplate: myTemplate,
239
+ }, async (activeSpan) => {
240
+ const rendered = myTemplate.compile({ name: 'world' });
241
+ return await callLLM(rendered);
242
+ });
243
+ ```
244
+
245
+ #### `TraceOptions`
246
+
247
+ | Option | Type | Default | Description |
248
+ |--------|------|---------|-------------|
249
+ | `name` | `string` | — | **Required.** Name for the trace span. |
250
+ | `kind` | `SpanKind` | `'CHAIN'` | Span kind. |
251
+ | `promptTemplate` | `string &#124; PromptTemplate` | — | Prompt template to track. |
252
+ | `promptVariables` | `Record<string, any>` | — | Prompt variables for the template. |
253
+ | `userPromptTemplate` | `string &#124; UserPromptTemplate` | — | User prompt template. |
254
+ | `userPromptVariables` | `Record<string, any>` | — | User prompt variables. |
255
+ | `version` | `string` | — | Prompt version identifier. |
256
+ | `captureStdout` | `boolean` | `false` | Capture stdout during execution. |
257
+ | `mask` | `MaskFunction` | — | Per-trace mask function. |
258
+ | `attributes` | `Record<string, any>` | — | Custom attributes on the span. |
259
+ | `tags` | `string[]` | — | Tags for this trace. |
260
+ | `metadata` | `Record<string, any>` | — | Custom metadata. |
261
+
262
+ #### `span()` vs `trace()`
263
+
264
+ | | `span()` | `trace()` |
265
+ |---|----------|-----------|
266
+ | Pattern | Higher-order function wrapper | Inline callback |
267
+ | Reuse | Returns a reusable function | Executes immediately |
268
+ | Prompt tracking | No | Yes — `promptTemplate`, `promptVariables` |
269
+ | Session-aware | No | Yes — creates root traces for multi-turn sessions |
270
+ | Best for | Wrapping functions/methods | Ad-hoc tracing blocks, prompt versioning |
271
+
272
+ ---
273
+
274
+ ### `log(template, options?)`
275
+
276
+ Capture a timestamped log step within the current trace. Uses `{key}` placeholders for template variables.
277
+
278
+ ```typescript
279
+ log('Processing query: {query}', { query: 'What is TypeScript?' });
280
+ log('Retrieved {count} documents in {ms}ms', { count: 5, ms: 120 });
281
+ log('Classification result', { category: 'technical', level: 'debug' });
282
+ ```
283
+
284
+ Requires `captureLogs: true` in `init()`. Log records are emitted as OTel `LogRecord`s associated with the active span.
285
+
286
+ 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.
287
+
288
+ ---
289
+
290
+ ### `PromptTemplate` / `UserPromptTemplate`
291
+
292
+ Template classes for prompt versioning with `{{variable}}` placeholders. When used with `trace()`, variables are automatically captured on the span for prompt tracking.
293
+
294
+ ```typescript
295
+ // String template
296
+ const systemPrompt = new PromptTemplate(
297
+ 'You are a {{role}} assistant specializing in {{topic}}.'
298
+ );
299
+
300
+ // Message array template
301
+ const chatPrompt = new PromptTemplate([
302
+ { role: 'system', content: 'You are a {{role}} assistant.' },
303
+ { role: 'user', content: '{{question}}' },
304
+ ]);
305
+
306
+ // Compile with variables
307
+ const rendered = systemPrompt.compile({ role: 'helpful', topic: 'TypeScript' });
308
+ // => 'You are a helpful assistant specializing in TypeScript.'
309
+
310
+ // Access template metadata
311
+ systemPrompt.variables; // ['role', 'topic']
312
+ systemPrompt.template; // raw template string
313
+ ```
314
+
315
+ `UserPromptTemplate` is identical but stores context separately — use it for the user/human turn in multi-template setups:
316
+
317
+ ```typescript
318
+ const systemTpl = new PromptTemplate('You are a {{role}} assistant.');
319
+ const userTpl = new UserPromptTemplate('{{question}}');
320
+
321
+ await trace({
322
+ name: 'qa',
323
+ promptTemplate: systemTpl,
324
+ userPromptTemplate: userTpl,
325
+ }, async () => {
326
+ const system = systemTpl.compile({ role: 'helpful' });
327
+ const user = userTpl.compile({ question: 'What is TypeScript?' });
328
+ // Variables from both templates are captured on the span
329
+ });
330
+ ```
331
+
332
+ ---
333
+
334
+ ### `PromptClient`
335
+
336
+ Server-side prompt management for storing, versioning, and retrieving prompts from the Neatlogs backend.
337
+
338
+ ```typescript
339
+ import { PromptClient } from 'neatlogs';
340
+
341
+ const client = new PromptClient({
342
+ baseUrl: 'https://app.neatlogs.com',
343
+ apiKey: process.env.NEATLOGS_API_KEY!,
344
+ });
345
+
346
+ // Create a prompt
347
+ const prompt = await client.createPrompt({
348
+ name: 'qa-system',
349
+ content: 'You are a {{role}} assistant for {{company}}.',
350
+ labels: ['production'],
351
+ });
352
+
353
+ // Fetch by name (returns latest version)
354
+ const handle = await client.getPrompt('qa-system');
355
+
356
+ // Fetch by label or version
357
+ const prod = await client.getPrompt('qa-system', { label: 'production' });
358
+ const v2 = await client.getPrompt('qa-system', { version: 2 });
359
+
360
+ // Compile with variables
361
+ const rendered = handle.compile({ role: 'helpful', company: 'Acme' });
362
+
363
+ // Compile as message array
364
+ const messages = handle.compileMessages({ role: 'helpful', company: 'Acme' });
365
+
366
+ // List all prompts
367
+ const all = await client.listPrompts();
368
+
369
+ // Update prompt content
370
+ await client.updatePrompt('qa-system', { content: 'Updated: {{role}} for {{company}}.' });
371
+
372
+ // Save a new version
373
+ await client.saveAsVersion('qa-system', { label: 'v2' });
374
+
375
+ // Delete a prompt
376
+ await client.deletePrompt('qa-system');
377
+ ```
378
+
379
+ Module-level convenience functions are also available after `init()`:
380
+
381
+ ```typescript
382
+ import { init, getPrompt, fetchPrompt, listPrompts, createPrompt, updatePrompt, saveAsVersion, deletePrompt, removeTag } from 'neatlogs';
383
+
384
+ await init({ apiKey: process.env.NEATLOGS_API_KEY });
385
+
386
+ const handle = await getPrompt('my-prompt');
387
+ const rendered = handle.compile({ name: 'world' });
388
+ ```
389
+
390
+ ---
391
+
392
+ ### `flush()` / `shutdown()`
393
+
394
+ ```typescript
395
+ // Flush pending spans without shutting down
396
+ await flush();
397
+
398
+ // Flush and shut down — call before process exit
399
+ await shutdown();
400
+ ```
401
+
402
+ `shutdown()` resets all SDK state so `init()` can be called again if needed.
403
+
404
+ ---
405
+
406
+ ### `bindTemplates(llm, systemTpl, userTpl?, compiledVars?)`
407
+
408
+ Bind prompt templates to a LangChain-compatible LLM so templates are automatically captured on LLM spans managed by frameworks like CrewAI.
409
+
410
+ ```typescript
411
+ import { bindTemplates, PromptTemplate, UserPromptTemplate } from 'neatlogs';
412
+
413
+ const systemTpl = new PromptTemplate('You are a {{role}} assistant.');
414
+ const userTpl = new UserPromptTemplate('Research: {{topic}}');
415
+
416
+ const boundLlm = bindTemplates(llm, systemTpl, userTpl, { topic: 'AI safety' });
417
+ // Pass boundLlm to your framework — template context is injected on every invoke()
418
+ ```
419
+
420
+ ---
421
+
422
+ ### `registerCrewaiTask(taskId, taskDescription)`
423
+
424
+ Register a CrewAI task for automatic span annotation.
425
+
426
+ ```typescript
427
+ import { registerCrewaiTask } from 'neatlogs';
428
+
429
+ registerCrewaiTask('research-task', 'Research the latest AI developments');
430
+ ```
431
+
432
+ ## Supported Instrumentations
433
+
434
+ ### Auto-Instrumented (via OpenInference)
435
+
436
+ These libraries are automatically instrumented when listed in `instrumentations`:
437
+
438
+ | Library | Package | Instrumentation |
439
+ |---------|---------|-----------------|
440
+ | `openai` | `openai` | `@arizeai/openinference-instrumentation-openai` |
441
+ | `anthropic` | `@anthropic-ai/sdk` | `@arizeai/openinference-instrumentation-anthropic` |
442
+ | `bedrock` | `@aws-sdk/client-bedrock-runtime` | `@arizeai/openinference-instrumentation-bedrock` |
443
+ | `langchain` | `@langchain/core` | `@arizeai/openinference-instrumentation-langchain` |
444
+ | `mcp` | `@modelcontextprotocol/sdk` | `@arizeai/openinference-instrumentation-mcp` |
445
+ | `beeai` | `beeai-framework` | `@arizeai/openinference-instrumentation-beeai` |
446
+ | `claude_agent_sdk` | `@anthropic-ai/claude-agent-sdk` | `@arizeai/openinference-instrumentation-claude-agent-sdk` |
447
+
448
+ ### Custom Instrumentors (built into neatlogs)
449
+
450
+ | Library | Package | Notes |
451
+ |---------|---------|-------|
452
+ | `google_genai` | `@google/genai` | Custom neatlogs instrumentor |
453
+ | `crewai` | `crewai` | Custom neatlogs instrumentor; auto-loads `litellm` |
454
+
455
+ ### Registry Entries (not yet instrumented in TypeScript)
456
+
457
+ The following libraries are registered in the instrumentation registry for future support. Passing them to `instrumentations` will log a debug message and skip gracefully:
458
+
459
+ `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`
460
+
461
+ ## Configuration
462
+
463
+ ### Environment Variables
464
+
465
+ | Variable | Description |
466
+ |----------|-------------|
467
+ | `NEATLOGS_API_KEY` | API key (fallback when `apiKey` option is not provided) |
468
+ | `NEATLOGS_DISABLE_EXPORT` | Set to `true`, `1`, or `yes` to disable export |
469
+
470
+ ### Programmatic Configuration
471
+
472
+ All configuration is passed via `init()` options. See the [InitOptions table](#initoptions) above.
473
+
474
+ ```typescript
475
+ await init({
476
+ apiKey: process.env.NEATLOGS_API_KEY,
477
+ workflowName: 'my-pipeline',
478
+ sessionId: 'session-123',
479
+ userId: 'user-456',
480
+ tags: ['production', 'v2'],
481
+ metadata: { environment: 'prod' },
482
+ instrumentations: ['openai', 'anthropic'],
483
+ sampleRate: 0.5,
484
+ captureLogs: true,
485
+ debug: true,
486
+ });
487
+ ```
488
+
489
+ ## PII Masking
490
+
491
+ ### Global Mask
492
+
493
+ Apply a mask function to all spans:
494
+
495
+ ```typescript
496
+ await init({
497
+ apiKey: process.env.NEATLOGS_API_KEY,
498
+ mask: (spanData) => {
499
+ // Redact email addresses
500
+ for (const [key, value] of Object.entries(spanData)) {
501
+ if (typeof value === 'string') {
502
+ spanData[key] = value.replace(/[\w.-]+@[\w.-]+/g, '[REDACTED]');
503
+ }
504
+ }
505
+ return spanData;
506
+ },
507
+ });
508
+ ```
509
+
510
+ ### Per-Span Mask
511
+
512
+ Apply a mask to a specific span:
513
+
514
+ ```typescript
515
+ const sensitive = span({
516
+ kind: 'TOOL',
517
+ name: 'user-lookup',
518
+ mask: (spanData) => {
519
+ delete spanData['input.value'];
520
+ return spanData;
521
+ },
522
+ }, async (userId: string) => {
523
+ return await lookupUser(userId);
524
+ });
525
+ ```
526
+
527
+ ### Per-Trace Mask
528
+
529
+ ```typescript
530
+ await trace({
531
+ name: 'sensitive-operation',
532
+ mask: (spanData) => {
533
+ // Return null to drop the span entirely
534
+ return null;
535
+ },
536
+ }, async () => {
537
+ // This span will not be exported
538
+ });
539
+ ```
540
+
541
+ ### Server-Side PII Redaction
542
+
543
+ ```typescript
544
+ await init({
545
+ apiKey: process.env.NEATLOGS_API_KEY,
546
+ pii: 'redact', // or 'hash' or false
547
+ piiEnabled: true, // override team-level toggle
548
+ piiSpanTypes: ['LLM'], // only redact LLM spans
549
+ });
550
+ ```
551
+
552
+ ## Examples
553
+
554
+ See the [`examples/`](./examples/) directory for complete, runnable examples:
555
+
556
+ | File | Description |
557
+ |------|-------------|
558
+ | [`basic-openai.ts`](./examples/basic-openai.ts) | Basic OpenAI usage with auto-instrumentation |
559
+ | [`prompt-management.ts`](./examples/prompt-management.ts) | PromptTemplate + trace() for prompt versioning |
560
+ | [`multi-agent-workflow.ts`](./examples/multi-agent-workflow.ts) | Nested spans: WORKFLOW → AGENT → TOOL |
561
+ | [`custom-spans.ts`](./examples/custom-spans.ts) | All span kinds: WORKFLOW, CHAIN, AGENT, TOOL, RETRIEVER, EMBEDDING, GUARDRAIL |
562
+
563
+ Run any example with:
564
+
565
+ ```bash
566
+ NEATLOGS_API_KEY=your-key npx tsx examples/basic-openai.ts
567
+ ```
568
+
569
+ ## License
570
+
571
+ MIT