Repository files navigation

@smooai/observability — Error capture and grouping, your backend only.

npmSmoo AIlicense

CIdownloadsTypeScript · Python · Rust · Go · .NETone ingest contract

What it is · Feature tour · Install · Usage · SDK status · Architecture · Studio · Platform


The error-tracking platform we wished was already in our stack. You ship a deploy; somewhere out there a webpack chunk is 404'ing for one user and your sign-in page is silently broken. Your error boundary console.errors into the void, and your only signal is the support ticket that arrives forty minutes later. @smooai/observability fills that gap: automatic capture, breadcrumbs, PII scrubbing, OpenTelemetry traces + metrics, and GenAI telemetry — with SDKs in five languages speaking one ingest contract, your events going to your Smoo backend only. Plus a native desktop studio to read it all.

What is this?

A monorepo of observability SDKs — TypeScript (the reference, on npm), Python, Rust, Go, and .NET (complete and CI-tested, in-repo) — plus a native Dioxus desktop client. Every SDK captures errors with breadcrumbs and scoped context, scrubs PII before anything leaves the process, exports OpenTelemetry traces and metrics over OTLP with M2M auth, and POSTs error events to the same ingest endpoint (POST /webhooks/observability/{org_id}/{token}). The heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

✨ Feature tour

CapabilityWhat you get
🛑Error captureUncaught exceptions + crash handlers in all five languages
🍞Breadcrumbs + scopeRequest-scoped user, tags, and a trail of what led to the error
🔐PII scrubCredentials dropped; emails/phones HMAC-hashed per-org — all five SDKs
🔭OTel traces + metricsOTLP/HTTP export with M2M token auth — all five SDKs
🤖GenAI telemetrygen_ai.* semconv helpers everywhere; wrapOpenAI + LangChain integrations
🧱React / Next.js<ErrorBoundary>, useErrorHandler, source-map upload — TypeScript only
🖥️Desktop studioNative logs/errors/metrics client, multi-org, keychain-stored creds

Error capture — every language

Every SDK ships the same core: captureException (+ each runtime's global crash hooks), breadcrumbs, a request/task-scoped context that doesn't leak across requests, a batched retrying webhook transport, PII scrubbing, and OTLP trace + metric export. What differs per language is the framework glue:

TypeScriptPythonRustGo.NET
Error capture + crash handlers
Breadcrumbs + scoped context
Batched webhook transport
PII scrub + per-org HMAC hashing
OTel traces + metrics (OTLP, M2M auth)
GenAI gen_ai.* helpers
HTTP middlewareHonoFastAPI / Starlettetower · reqwestnet/http · Fiber · GinASP.NET Core
LLM client instrumentationwrapOpenAILangChain / LangGraph callback
Log/session sampling (FNV-1a parity corpus)
Source-map uploadn/an/an/an/a
React / Next.js bindingsn/an/an/an/a
Browser: beacon flush + IndexedDB offline queuen/an/an/an/a
Publishednpmrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushed

The four non-npm SDKs are release-ready but deliberately unpublished: manifests, metadata, dry runs and the publish.yml gates are all in place, and the names are free on crates.io, PyPI and NuGet — but a first publish to those registries is irreversible, so it stays a human decision. See RELEASING.md for the one command each.

Browser extras (TypeScript only): window.onerror / unhandledrejection / console.error taps, fetch/XHR/click/navigation breadcrumbs, release tagging with the git sha, navigator.sendBeacon flush at pagehide, and an IndexedDB offline queue that retries on focus.

What does NOT get captured

  • console.log / console.info / console.warn — only console.error is tapped, and that's opt-out
  • HTTP request bodies — only method, path, status, and duration appear in breadcrumbs
  • Credentials matching the PII scrub regex — dropped outright, never hashed
  • Raw emails / phones / street addresses — replaced by a keyed per-org hash, never stored in the clear

📦 Install

TypeScript is the published SDK — React and Next.js bindings are subpath exports of the same package, not separate installs:

pnpm add @smooai/observability # core — plus /react, /next, /node, /otel, /metrics subpaths

Python, Rust, Go, and .NET are complete and CI-tested, but not yet on their registries (PyPI / crates.io / NuGet publishing is set up in publish.yml and lands with the first language tag). Until then, use them from source:

SDKSourceRegistry status
TypeScriptpackages/corenpm
Pythonpython/ (smooai_observability)unreleased — not yet on PyPI
Rustrust/observability (smooai-observability)unreleased — not yet on crates.io
Gogo get github.com/SmooAI/observability/go@mainno SemVer tag yet — @main resolves via the module proxy
.NETdotnet/ (SmooAI.Observability)unreleased — not yet on NuGet

🚀 Usage

Next.js

// next.config.tsimport{withSmooObservability}from'@smooai/observability/next/build';exportdefaultwithSmooObservability({/* your config */},{org: 'your-org',release: process.env.GITHUB_SHA??'dev',uploadSourcemaps: process.env.CI==='true',},);
// instrumentation.tsexportasyncfunctionregister(){const{ Client }=awaitimport('@smooai/observability');Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE,release: process.env.GITHUB_SHA??'dev',});}
// app/global-error.tsx'use client';import{RootErrorBoundary}from'@smooai/observability/next';exportdefaultfunctionGlobalError({ error, reset }: {error: Error&{digest?: string};reset: ()=>void}){return(<html><body><RootErrorBoundaryerror={error}resetError={reset}fallback={<YourBrandedErroronRetry={reset}/>}/></body></html>);}

Browser SPA

import{Client}from'@smooai/observability';Client.init({dsn: process.env.SMOO_OBSERVABILITY_DSN!,environment: 'production',release: import.meta.env.VITE_GIT_SHA,});Client.setUser({id: 'user_abc',orgId: 'org_xyz'});

React bindings live at the /react subpath — import { ErrorBoundary, useErrorHandler } from '@smooai/observability/react'.

Node / Hono

import{Client,observabilityMiddleware}from'@smooai/observability/node';Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE!,release: process.env.LAMBDA_FUNCTION_VERSION??'dev',});app.use('*',observabilityMiddleware());

Python / Rust / Go / .NET

Same shape, native idioms — each sub-README has the full walkthrough: python/ (FastAPI middleware, LangChain callback, crash hooks), rust/ (tower + reqwest middleware), go/ (net/http, Fiber, Gin), dotnet/ (ASP.NET Core middleware). A taste of Python:

fromsmooai_observabilityimportbootstrap_observability, capture_exceptionbootstrap_observability() # reads SMOOAI_OBSERVABILITY_* env vars (never raises)try:
risky()
exceptExceptionaserr:
capture_exception(err, tags={"area": "ingest"})

🤖 GenAI telemetry (gen_ai.*)

LLM and agent spans carry the OTel GenAI semantic conventions, so any semconv-aware backend reads them — Smoo's LLM dashboard routes on gen_ai.system alone.

importOpenAIfrom'openai';import{wrapOpenAI}from'@smooai/observability';// Instruments chat.completions.create — the original client is untouched.constopenai=wrapOpenAI(newOpenAI(),{conversationId: conversation.id,// Providers don't return a price. Supply one and the cost column fills in.costUsd: ({ inputTokens =0, outputTokens =0})=>inputTokens*2.5e-6+outputTokens*1e-5,});

The same wrapper covers Groq, Together, Fireworks, DeepSeek, Azure OpenAI, and any OpenAI-compatible gateway — pass { system: 'groq' } so spans attribute to the real provider. Prompt and completion content is off by default; { recordContent: true } records it as gen_ai.*.message span events, PII-scrubbed on the way out.

For hand-rolled calls, set the attributes directly:

import{setGenAIAttributes,recordGenAIMessage}from'@smooai/observability';setGenAIAttributes(span,{system: 'anthropic',operationName: 'chat',requestModel: 'claude-opus-4-7',usageInputTokens: 812,usageOutputTokens: 96});

gen_ai.operation.name is a straight passthrough on ingest with no fallback — leave it unset and the operation column lands NULL. Always set it.

Parity across the five SDKs:

SDKAttribute helperMessage eventsContent PII-scrubbedFramework integration
TypeScriptsetGenAIAttributesrecordGenAIMessagewrapOpenAI — OpenAI Node SDK + compatible APIs
Rustset_gen_ai_attributesrecord_gen_ai_message
Pythonset_gen_ai_attributesrecord_gen_ai_messageSmooAICallbackHandler — LangChain / LangGraph
GoSetGenAIAttributesRecordGenAIMessage
.NETGenAIActivity.SetAttributesGenAIActivity.RecordMessage

Known divergences: none in the attribute or event shape. All five emit gen_ai.tool.names as a string array and all five PII-scrub recorded message content. Two divergences that used to be listed here are closed: Rust emitted gen_ai.tool.names comma-joined (a tool name containing a comma silently became two tools, and a Rust service's spans could not be filtered by tool), and only TypeScript scrubbed message content (prompts and tool arguments are the most PII-dense payload the SDK touches). Each fix ships with a span-level test in its own language.

What still differs is only the framework glue — the wrapOpenAI and LangChain columns above — which is a matter of which ecosystems have an integration written, not of the wire contract.

📐 Cross-language parity, honestly

parity/sampling-corpus.json pins 170 vectors for the FNV-1a session sampler, level normalization, W3C traceparent parse/format, and settings resolution. All five SDKs implement it and all five CI lanes load that same file — a language that cannot reproduce a vector fails its build:

SDKLoader
TypeScriptpackages/core/src/__tests__/parity-corpus.test.ts
Rustrust/observability/tests/parity_corpus.rs
Pythonpython/tests/test_parity_corpus.py
Gogo/parity_corpus_test.go
.NETdotnet/tests/.../ParityCorpusTests.cs

parity/** is a path-filter trigger for every language lane, so touching the corpus re-runs all five.

The PII token — the [email:02ea437f] handle that replaces a personal identifier — has its own shared corpus, parity/pii-corpus.json, loaded by the same five lanes. It pins the HMAC message framing, the per-org salt, the per-kind normalization, and the no-key redaction fallback.

📖 Architecture

The SDK is intentionally thin. It captures, batches, redacts credentials, hashes personal identifiers, and POSTs to a Smoo ingest endpoint. All of the heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
SDKS["5 SDKs<br/>TS · Python · Rust · Go · .NET<br/>capture · scope · scrub · batch"]
SDKS -->|"errors: POST /webhooks/observability/{org}/{token}"| INGEST[("Smoo platform<br/>group · symbolicate · alert")]
SDKS -->|"traces + metrics: OTLP/HTTP<br/>M2M token auth"| INGEST
STUDIO["Observability Studio<br/>desktop (Dioxus)"] -->|"reads api.smoo.ai<br/>M2M client_credentials"| INGEST
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class SDKS warm
class INGEST,STUDIO teal
Loading

Full backend architecture: SmooAI/smooai → docs/Architecture/Observability-Architecture.md.

🖥️ Observability Studio (desktop)

desktop/ is a native desktop client for the whole stack — logs, errors, and metrics from api.smoo.ai, multi-org with credentials in your OS keychain, Cmd+K org/view switching. Built with Dioxus on the shared @smooai/ui design system. Unsigned bundles for macOS / Linux / Windows ship from the studio-v* GitHub Releases; or cargo run --release -p observability-studio-app from desktop/.

🗂️ Five SDKs, one contract

PathWhat it isTests / CI
packages/coreTypeScript reference SDK — browser + Node entries, /react · /next · /otel · /metrics · /bootstrap subpathsvitest, published via changesets
python/Python SDK — capture, crash hooks, OTel, GenAI, FastAPI + LangChain integrationspytest lane in pr-checks.yml
rust/Rust SDK (smooai-observability) — capture, OTel, GenAI, tower + reqwest middlewarecargo test + clippy lane
go/Go SDK — capture, OTel, GenAI, net/http + Fiber + Gin middlewarego test lane
dotnet/.NET SDK (SmooAI.Observability) — capture, OTel, GenAI, ASP.NET Core middlewaredotnet test lane
desktop/Observability Studio — Dioxus desktop clientfmt + clippy + test lane; build-desktop.yml bundles 3 OSes on a studio-v* tag
parity/Shared corpora — sampling/traceparent/settings and PII tokensboth loaded by all five language lanes

Every language runs typecheck/lint/format/test in its own pr-checks.yml lane on every PR that touches it.

📖 Built with

  • TypeScript — strict mode, ESM-only, dual browser/Node entries via package exports map; tsup, turborepo, vitest, changesets
  • Python 3uv-managed, pytest
  • Rust — cargo workspace (rust/ SDK, desktop/ Dioxus app), clippy -D warnings
  • Go — stdlib-first module with Fiber/Gin subpackages
  • .NET — single SmooAI.Observability project + xUnit tests

📖 Privacy & telemetry

This SDK is opinionated about privacy:

  • We never capture form bodies, request bodies, or response bodies by default
  • We never capture cookies
  • We never send anything to a third-party service — your events go to your Smoo backend only
  • PII scrubbing is enabled by default and can be tuned per-tenant. Personal identifiers are hashed with HMAC-SHA256 under a key you supply (SMOOAI_OBSERVABILITY_PII_HASH_KEY), salted by org id — identical across the TypeScript, Rust, Go, Python and .NET SDKs. With no key configured they are fully redacted, never hashed under a guessable one.

📖 Status

The TypeScript SDK is live on npm and in production across the Smoo platform. The Python, Rust, Go, and .NET SDKs are feature-complete and CI-tested in-repo but not yet published to PyPI / crates.io / NuGet — the publish workflow (publish.yml) is tag-triggered and no language tag has shipped yet. The desktop studio ships unsigned bundles from studio-v* releases. Backend ingest, fingerprint grouping, and dashboards live in the SmooAI/smooai monorepo under SMOODEV-1067.

🧩 Part of Smoo AI {#part-of-smoo-ai}

@smooai/observability is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Issues and PRs welcome. Maintained by Brent Rager — email · LinkedIn · BlueSky · TikTok · Instagram.

📄 License

MIT © Smoo AI, Inc. See LICENSE.

(back to top)


Built by Smoo AI — AI built into every product.

About

Sentry-like error tracking SDK for the Smoo AI platform — browser, Node, React, and Next.js. The open-core companion to the hosted error + metrics dashboards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

@smooai/observability — Error capture and grouping, your backend only.

npmSmoo AIlicense

CIdownloadsTypeScript · Python · Rust · Go · .NETone ingest contract

What it is · Feature tour · Install · Usage · SDK status · Architecture · Studio · Platform


The error-tracking platform we wished was already in our stack. You ship a deploy; somewhere out there a webpack chunk is 404'ing for one user and your sign-in page is silently broken. Your error boundary console.errors into the void, and your only signal is the support ticket that arrives forty minutes later. @smooai/observability fills that gap: automatic capture, breadcrumbs, PII scrubbing, OpenTelemetry traces + metrics, and GenAI telemetry — with SDKs in five languages speaking one ingest contract, your events going to your Smoo backend only. Plus a native desktop studio to read it all.

What is this?

A monorepo of observability SDKs — TypeScript (the reference, on npm), Python, Rust, Go, and .NET (complete and CI-tested, in-repo) — plus a native Dioxus desktop client. Every SDK captures errors with breadcrumbs and scoped context, scrubs PII before anything leaves the process, exports OpenTelemetry traces and metrics over OTLP with M2M auth, and POSTs error events to the same ingest endpoint (POST /webhooks/observability/{org_id}/{token}). The heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

✨ Feature tour

CapabilityWhat you get
🛑Error captureUncaught exceptions + crash handlers in all five languages
🍞Breadcrumbs + scopeRequest-scoped user, tags, and a trail of what led to the error
🔐PII scrubCredentials dropped; emails/phones HMAC-hashed per-org — all five SDKs
🔭OTel traces + metricsOTLP/HTTP export with M2M token auth — all five SDKs
🤖GenAI telemetrygen_ai.* semconv helpers everywhere; wrapOpenAI + LangChain integrations
🧱React / Next.js<ErrorBoundary>, useErrorHandler, source-map upload — TypeScript only
🖥️Desktop studioNative logs/errors/metrics client, multi-org, keychain-stored creds

Error capture — every language

Every SDK ships the same core: captureException (+ each runtime's global crash hooks), breadcrumbs, a request/task-scoped context that doesn't leak across requests, a batched retrying webhook transport, PII scrubbing, and OTLP trace + metric export. What differs per language is the framework glue:

TypeScriptPythonRustGo.NET
Error capture + crash handlers
Breadcrumbs + scoped context
Batched webhook transport
PII scrub + per-org HMAC hashing
OTel traces + metrics (OTLP, M2M auth)
GenAI gen_ai.* helpers
HTTP middlewareHonoFastAPI / Starlettetower · reqwestnet/http · Fiber · GinASP.NET Core
LLM client instrumentationwrapOpenAILangChain / LangGraph callback
Log/session sampling (FNV-1a parity corpus)
Source-map uploadn/an/an/an/a
React / Next.js bindingsn/an/an/an/a
Browser: beacon flush + IndexedDB offline queuen/an/an/an/a
Publishednpmrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushed

The four non-npm SDKs are release-ready but deliberately unpublished: manifests, metadata, dry runs and the publish.yml gates are all in place, and the names are free on crates.io, PyPI and NuGet — but a first publish to those registries is irreversible, so it stays a human decision. See RELEASING.md for the one command each.

Browser extras (TypeScript only): window.onerror / unhandledrejection / console.error taps, fetch/XHR/click/navigation breadcrumbs, release tagging with the git sha, navigator.sendBeacon flush at pagehide, and an IndexedDB offline queue that retries on focus.

What does NOT get captured

  • console.log / console.info / console.warn — only console.error is tapped, and that's opt-out
  • HTTP request bodies — only method, path, status, and duration appear in breadcrumbs
  • Credentials matching the PII scrub regex — dropped outright, never hashed
  • Raw emails / phones / street addresses — replaced by a keyed per-org hash, never stored in the clear

📦 Install

TypeScript is the published SDK — React and Next.js bindings are subpath exports of the same package, not separate installs:

pnpm add @smooai/observability # core — plus /react, /next, /node, /otel, /metrics subpaths

Python, Rust, Go, and .NET are complete and CI-tested, but not yet on their registries (PyPI / crates.io / NuGet publishing is set up in publish.yml and lands with the first language tag). Until then, use them from source:

SDKSourceRegistry status
TypeScriptpackages/corenpm
Pythonpython/ (smooai_observability)unreleased — not yet on PyPI
Rustrust/observability (smooai-observability)unreleased — not yet on crates.io
Gogo get github.com/SmooAI/observability/go@mainno SemVer tag yet — @main resolves via the module proxy
.NETdotnet/ (SmooAI.Observability)unreleased — not yet on NuGet

🚀 Usage

Next.js

// next.config.tsimport{withSmooObservability}from'@smooai/observability/next/build';exportdefaultwithSmooObservability({/* your config */},{org: 'your-org',release: process.env.GITHUB_SHA??'dev',uploadSourcemaps: process.env.CI==='true',},);
// instrumentation.tsexportasyncfunctionregister(){const{ Client }=awaitimport('@smooai/observability');Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE,release: process.env.GITHUB_SHA??'dev',});}
// app/global-error.tsx'use client';import{RootErrorBoundary}from'@smooai/observability/next';exportdefaultfunctionGlobalError({ error, reset }: {error: Error&{digest?: string};reset: ()=>void}){return(<html><body><RootErrorBoundaryerror={error}resetError={reset}fallback={<YourBrandedErroronRetry={reset}/>}/></body></html>);}

Browser SPA

import{Client}from'@smooai/observability';Client.init({dsn: process.env.SMOO_OBSERVABILITY_DSN!,environment: 'production',release: import.meta.env.VITE_GIT_SHA,});Client.setUser({id: 'user_abc',orgId: 'org_xyz'});

React bindings live at the /react subpath — import { ErrorBoundary, useErrorHandler } from '@smooai/observability/react'.

Node / Hono

import{Client,observabilityMiddleware}from'@smooai/observability/node';Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE!,release: process.env.LAMBDA_FUNCTION_VERSION??'dev',});app.use('*',observabilityMiddleware());

Python / Rust / Go / .NET

Same shape, native idioms — each sub-README has the full walkthrough: python/ (FastAPI middleware, LangChain callback, crash hooks), rust/ (tower + reqwest middleware), go/ (net/http, Fiber, Gin), dotnet/ (ASP.NET Core middleware). A taste of Python:

fromsmooai_observabilityimportbootstrap_observability, capture_exceptionbootstrap_observability() # reads SMOOAI_OBSERVABILITY_* env vars (never raises)try:
risky()
exceptExceptionaserr:
capture_exception(err, tags={"area": "ingest"})

🤖 GenAI telemetry (gen_ai.*)

LLM and agent spans carry the OTel GenAI semantic conventions, so any semconv-aware backend reads them — Smoo's LLM dashboard routes on gen_ai.system alone.

importOpenAIfrom'openai';import{wrapOpenAI}from'@smooai/observability';// Instruments chat.completions.create — the original client is untouched.constopenai=wrapOpenAI(newOpenAI(),{conversationId: conversation.id,// Providers don't return a price. Supply one and the cost column fills in.costUsd: ({ inputTokens =0, outputTokens =0})=>inputTokens*2.5e-6+outputTokens*1e-5,});

The same wrapper covers Groq, Together, Fireworks, DeepSeek, Azure OpenAI, and any OpenAI-compatible gateway — pass { system: 'groq' } so spans attribute to the real provider. Prompt and completion content is off by default; { recordContent: true } records it as gen_ai.*.message span events, PII-scrubbed on the way out.

For hand-rolled calls, set the attributes directly:

import{setGenAIAttributes,recordGenAIMessage}from'@smooai/observability';setGenAIAttributes(span,{system: 'anthropic',operationName: 'chat',requestModel: 'claude-opus-4-7',usageInputTokens: 812,usageOutputTokens: 96});

gen_ai.operation.name is a straight passthrough on ingest with no fallback — leave it unset and the operation column lands NULL. Always set it.

Parity across the five SDKs:

SDKAttribute helperMessage eventsContent PII-scrubbedFramework integration
TypeScriptsetGenAIAttributesrecordGenAIMessagewrapOpenAI — OpenAI Node SDK + compatible APIs
Rustset_gen_ai_attributesrecord_gen_ai_message
Pythonset_gen_ai_attributesrecord_gen_ai_messageSmooAICallbackHandler — LangChain / LangGraph
GoSetGenAIAttributesRecordGenAIMessage
.NETGenAIActivity.SetAttributesGenAIActivity.RecordMessage

Known divergences: none in the attribute or event shape. All five emit gen_ai.tool.names as a string array and all five PII-scrub recorded message content. Two divergences that used to be listed here are closed: Rust emitted gen_ai.tool.names comma-joined (a tool name containing a comma silently became two tools, and a Rust service's spans could not be filtered by tool), and only TypeScript scrubbed message content (prompts and tool arguments are the most PII-dense payload the SDK touches). Each fix ships with a span-level test in its own language.

What still differs is only the framework glue — the wrapOpenAI and LangChain columns above — which is a matter of which ecosystems have an integration written, not of the wire contract.

📐 Cross-language parity, honestly

parity/sampling-corpus.json pins 170 vectors for the FNV-1a session sampler, level normalization, W3C traceparent parse/format, and settings resolution. All five SDKs implement it and all five CI lanes load that same file — a language that cannot reproduce a vector fails its build:

SDKLoader
TypeScriptpackages/core/src/__tests__/parity-corpus.test.ts
Rustrust/observability/tests/parity_corpus.rs
Pythonpython/tests/test_parity_corpus.py
Gogo/parity_corpus_test.go
.NETdotnet/tests/.../ParityCorpusTests.cs

parity/** is a path-filter trigger for every language lane, so touching the corpus re-runs all five.

The PII token — the [email:02ea437f] handle that replaces a personal identifier — has its own shared corpus, parity/pii-corpus.json, loaded by the same five lanes. It pins the HMAC message framing, the per-org salt, the per-kind normalization, and the no-key redaction fallback.

📖 Architecture

The SDK is intentionally thin. It captures, batches, redacts credentials, hashes personal identifiers, and POSTs to a Smoo ingest endpoint. All of the heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
SDKS["5 SDKs<br/>TS · Python · Rust · Go · .NET<br/>capture · scope · scrub · batch"]
SDKS -->|"errors: POST /webhooks/observability/{org}/{token}"| INGEST[("Smoo platform<br/>group · symbolicate · alert")]
SDKS -->|"traces + metrics: OTLP/HTTP<br/>M2M token auth"| INGEST
STUDIO["Observability Studio<br/>desktop (Dioxus)"] -->|"reads api.smoo.ai<br/>M2M client_credentials"| INGEST
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class SDKS warm
class INGEST,STUDIO teal
Loading

Full backend architecture: SmooAI/smooai → docs/Architecture/Observability-Architecture.md.

🖥️ Observability Studio (desktop)

desktop/ is a native desktop client for the whole stack — logs, errors, and metrics from api.smoo.ai, multi-org with credentials in your OS keychain, Cmd+K org/view switching. Built with Dioxus on the shared @smooai/ui design system. Unsigned bundles for macOS / Linux / Windows ship from the studio-v* GitHub Releases; or cargo run --release -p observability-studio-app from desktop/.

🗂️ Five SDKs, one contract

PathWhat it isTests / CI
packages/coreTypeScript reference SDK — browser + Node entries, /react · /next · /otel · /metrics · /bootstrap subpathsvitest, published via changesets
python/Python SDK — capture, crash hooks, OTel, GenAI, FastAPI + LangChain integrationspytest lane in pr-checks.yml
rust/Rust SDK (smooai-observability) — capture, OTel, GenAI, tower + reqwest middlewarecargo test + clippy lane
go/Go SDK — capture, OTel, GenAI, net/http + Fiber + Gin middlewarego test lane
dotnet/.NET SDK (SmooAI.Observability) — capture, OTel, GenAI, ASP.NET Core middlewaredotnet test lane
desktop/Observability Studio — Dioxus desktop clientfmt + clippy + test lane; build-desktop.yml bundles 3 OSes on a studio-v* tag
parity/Shared corpora — sampling/traceparent/settings and PII tokensboth loaded by all five language lanes

Every language runs typecheck/lint/format/test in its own pr-checks.yml lane on every PR that touches it.

📖 Built with

  • TypeScript — strict mode, ESM-only, dual browser/Node entries via package exports map; tsup, turborepo, vitest, changesets
  • Python 3uv-managed, pytest
  • Rust — cargo workspace (rust/ SDK, desktop/ Dioxus app), clippy -D warnings
  • Go — stdlib-first module with Fiber/Gin subpackages
  • .NET — single SmooAI.Observability project + xUnit tests

📖 Privacy & telemetry

This SDK is opinionated about privacy:

  • We never capture form bodies, request bodies, or response bodies by default
  • We never capture cookies
  • We never send anything to a third-party service — your events go to your Smoo backend only
  • PII scrubbing is enabled by default and can be tuned per-tenant. Personal identifiers are hashed with HMAC-SHA256 under a key you supply (SMOOAI_OBSERVABILITY_PII_HASH_KEY), salted by org id — identical across the TypeScript, Rust, Go, Python and .NET SDKs. With no key configured they are fully redacted, never hashed under a guessable one.

📖 Status

The TypeScript SDK is live on npm and in production across the Smoo platform. The Python, Rust, Go, and .NET SDKs are feature-complete and CI-tested in-repo but not yet published to PyPI / crates.io / NuGet — the publish workflow (publish.yml) is tag-triggered and no language tag has shipped yet. The desktop studio ships unsigned bundles from studio-v* releases. Backend ingest, fingerprint grouping, and dashboards live in the SmooAI/smooai monorepo under SMOODEV-1067.

🧩 Part of Smoo AI {#part-of-smoo-ai}

@smooai/observability is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Issues and PRs welcome. Maintained by Brent Rager — email · LinkedIn · BlueSky · TikTok · Instagram.

📄 License

MIT © Smoo AI, Inc. See LICENSE.

(back to top)


Built by Smoo AI — AI built into every product.

About

Sentry-like error tracking SDK for the Smoo AI platform — browser, Node, React, and Next.js. The open-core companion to the hosted error + metrics dashboards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@smooai/observability — Error capture and grouping, your backend only.

npmSmoo AIlicense

CIdownloadsTypeScript · Python · Rust · Go · .NETone ingest contract

What it is · Feature tour · Install · Usage · SDK status · Architecture · Studio · Platform


The error-tracking platform we wished was already in our stack. You ship a deploy; somewhere out there a webpack chunk is 404'ing for one user and your sign-in page is silently broken. Your error boundary console.errors into the void, and your only signal is the support ticket that arrives forty minutes later. @smooai/observability fills that gap: automatic capture, breadcrumbs, PII scrubbing, OpenTelemetry traces + metrics, and GenAI telemetry — with SDKs in five languages speaking one ingest contract, your events going to your Smoo backend only. Plus a native desktop studio to read it all.

What is this?

A monorepo of observability SDKs — TypeScript (the reference, on npm), Python, Rust, Go, and .NET (complete and CI-tested, in-repo) — plus a native Dioxus desktop client. Every SDK captures errors with breadcrumbs and scoped context, scrubs PII before anything leaves the process, exports OpenTelemetry traces and metrics over OTLP with M2M auth, and POSTs error events to the same ingest endpoint (POST /webhooks/observability/{org_id}/{token}). The heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

✨ Feature tour

CapabilityWhat you get
🛑Error captureUncaught exceptions + crash handlers in all five languages
🍞Breadcrumbs + scopeRequest-scoped user, tags, and a trail of what led to the error
🔐PII scrubCredentials dropped; emails/phones HMAC-hashed per-org — all five SDKs
🔭OTel traces + metricsOTLP/HTTP export with M2M token auth — all five SDKs
🤖GenAI telemetrygen_ai.* semconv helpers everywhere; wrapOpenAI + LangChain integrations
🧱React / Next.js<ErrorBoundary>, useErrorHandler, source-map upload — TypeScript only
🖥️Desktop studioNative logs/errors/metrics client, multi-org, keychain-stored creds

Error capture — every language

Every SDK ships the same core: captureException (+ each runtime's global crash hooks), breadcrumbs, a request/task-scoped context that doesn't leak across requests, a batched retrying webhook transport, PII scrubbing, and OTLP trace + metric export. What differs per language is the framework glue:

TypeScriptPythonRustGo.NET
Error capture + crash handlers
Breadcrumbs + scoped context
Batched webhook transport
PII scrub + per-org HMAC hashing
OTel traces + metrics (OTLP, M2M auth)
GenAI gen_ai.* helpers
HTTP middlewareHonoFastAPI / Starlettetower · reqwestnet/http · Fiber · GinASP.NET Core
LLM client instrumentationwrapOpenAILangChain / LangGraph callback
Log/session sampling (FNV-1a parity corpus)
Source-map uploadn/an/an/an/a
React / Next.js bindingsn/an/an/an/a
Browser: beacon flush + IndexedDB offline queuen/an/an/an/a
Publishednpmrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushed

The four non-npm SDKs are release-ready but deliberately unpublished: manifests, metadata, dry runs and the publish.yml gates are all in place, and the names are free on crates.io, PyPI and NuGet — but a first publish to those registries is irreversible, so it stays a human decision. See RELEASING.md for the one command each.

Browser extras (TypeScript only): window.onerror / unhandledrejection / console.error taps, fetch/XHR/click/navigation breadcrumbs, release tagging with the git sha, navigator.sendBeacon flush at pagehide, and an IndexedDB offline queue that retries on focus.

What does NOT get captured

  • console.log / console.info / console.warn — only console.error is tapped, and that's opt-out
  • HTTP request bodies — only method, path, status, and duration appear in breadcrumbs
  • Credentials matching the PII scrub regex — dropped outright, never hashed
  • Raw emails / phones / street addresses — replaced by a keyed per-org hash, never stored in the clear

📦 Install

TypeScript is the published SDK — React and Next.js bindings are subpath exports of the same package, not separate installs:

pnpm add @smooai/observability # core — plus /react, /next, /node, /otel, /metrics subpaths

Python, Rust, Go, and .NET are complete and CI-tested, but not yet on their registries (PyPI / crates.io / NuGet publishing is set up in publish.yml and lands with the first language tag). Until then, use them from source:

SDKSourceRegistry status
TypeScriptpackages/corenpm
Pythonpython/ (smooai_observability)unreleased — not yet on PyPI
Rustrust/observability (smooai-observability)unreleased — not yet on crates.io
Gogo get github.com/SmooAI/observability/go@mainno SemVer tag yet — @main resolves via the module proxy
.NETdotnet/ (SmooAI.Observability)unreleased — not yet on NuGet

🚀 Usage

Next.js

// next.config.tsimport{withSmooObservability}from'@smooai/observability/next/build';exportdefaultwithSmooObservability({/* your config */},{org: 'your-org',release: process.env.GITHUB_SHA??'dev',uploadSourcemaps: process.env.CI==='true',},);
// instrumentation.tsexportasyncfunctionregister(){const{ Client }=awaitimport('@smooai/observability');Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE,release: process.env.GITHUB_SHA??'dev',});}
// app/global-error.tsx'use client';import{RootErrorBoundary}from'@smooai/observability/next';exportdefaultfunctionGlobalError({ error, reset }: {error: Error&{digest?: string};reset: ()=>void}){return(<html><body><RootErrorBoundaryerror={error}resetError={reset}fallback={<YourBrandedErroronRetry={reset}/>}/></body></html>);}

Browser SPA

import{Client}from'@smooai/observability';Client.init({dsn: process.env.SMOO_OBSERVABILITY_DSN!,environment: 'production',release: import.meta.env.VITE_GIT_SHA,});Client.setUser({id: 'user_abc',orgId: 'org_xyz'});

React bindings live at the /react subpath — import { ErrorBoundary, useErrorHandler } from '@smooai/observability/react'.

Node / Hono

import{Client,observabilityMiddleware}from'@smooai/observability/node';Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE!,release: process.env.LAMBDA_FUNCTION_VERSION??'dev',});app.use('*',observabilityMiddleware());

Python / Rust / Go / .NET

Same shape, native idioms — each sub-README has the full walkthrough: python/ (FastAPI middleware, LangChain callback, crash hooks), rust/ (tower + reqwest middleware), go/ (net/http, Fiber, Gin), dotnet/ (ASP.NET Core middleware). A taste of Python:

fromsmooai_observabilityimportbootstrap_observability, capture_exceptionbootstrap_observability() # reads SMOOAI_OBSERVABILITY_* env vars (never raises)try:
risky()
exceptExceptionaserr:
capture_exception(err, tags={"area": "ingest"})

🤖 GenAI telemetry (gen_ai.*)

LLM and agent spans carry the OTel GenAI semantic conventions, so any semconv-aware backend reads them — Smoo's LLM dashboard routes on gen_ai.system alone.

importOpenAIfrom'openai';import{wrapOpenAI}from'@smooai/observability';// Instruments chat.completions.create — the original client is untouched.constopenai=wrapOpenAI(newOpenAI(),{conversationId: conversation.id,// Providers don't return a price. Supply one and the cost column fills in.costUsd: ({ inputTokens =0, outputTokens =0})=>inputTokens*2.5e-6+outputTokens*1e-5,});

The same wrapper covers Groq, Together, Fireworks, DeepSeek, Azure OpenAI, and any OpenAI-compatible gateway — pass { system: 'groq' } so spans attribute to the real provider. Prompt and completion content is off by default; { recordContent: true } records it as gen_ai.*.message span events, PII-scrubbed on the way out.

For hand-rolled calls, set the attributes directly:

import{setGenAIAttributes,recordGenAIMessage}from'@smooai/observability';setGenAIAttributes(span,{system: 'anthropic',operationName: 'chat',requestModel: 'claude-opus-4-7',usageInputTokens: 812,usageOutputTokens: 96});

gen_ai.operation.name is a straight passthrough on ingest with no fallback — leave it unset and the operation column lands NULL. Always set it.

Parity across the five SDKs:

SDKAttribute helperMessage eventsContent PII-scrubbedFramework integration
TypeScriptsetGenAIAttributesrecordGenAIMessagewrapOpenAI — OpenAI Node SDK + compatible APIs
Rustset_gen_ai_attributesrecord_gen_ai_message
Pythonset_gen_ai_attributesrecord_gen_ai_messageSmooAICallbackHandler — LangChain / LangGraph
GoSetGenAIAttributesRecordGenAIMessage
.NETGenAIActivity.SetAttributesGenAIActivity.RecordMessage

Known divergences: none in the attribute or event shape. All five emit gen_ai.tool.names as a string array and all five PII-scrub recorded message content. Two divergences that used to be listed here are closed: Rust emitted gen_ai.tool.names comma-joined (a tool name containing a comma silently became two tools, and a Rust service's spans could not be filtered by tool), and only TypeScript scrubbed message content (prompts and tool arguments are the most PII-dense payload the SDK touches). Each fix ships with a span-level test in its own language.

What still differs is only the framework glue — the wrapOpenAI and LangChain columns above — which is a matter of which ecosystems have an integration written, not of the wire contract.

📐 Cross-language parity, honestly

parity/sampling-corpus.json pins 170 vectors for the FNV-1a session sampler, level normalization, W3C traceparent parse/format, and settings resolution. All five SDKs implement it and all five CI lanes load that same file — a language that cannot reproduce a vector fails its build:

SDKLoader
TypeScriptpackages/core/src/__tests__/parity-corpus.test.ts
Rustrust/observability/tests/parity_corpus.rs
Pythonpython/tests/test_parity_corpus.py
Gogo/parity_corpus_test.go
.NETdotnet/tests/.../ParityCorpusTests.cs

parity/** is a path-filter trigger for every language lane, so touching the corpus re-runs all five.

The PII token — the [email:02ea437f] handle that replaces a personal identifier — has its own shared corpus, parity/pii-corpus.json, loaded by the same five lanes. It pins the HMAC message framing, the per-org salt, the per-kind normalization, and the no-key redaction fallback.

📖 Architecture

The SDK is intentionally thin. It captures, batches, redacts credentials, hashes personal identifiers, and POSTs to a Smoo ingest endpoint. All of the heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
SDKS["5 SDKs<br/>TS · Python · Rust · Go · .NET<br/>capture · scope · scrub · batch"]
SDKS -->|"errors: POST /webhooks/observability/{org}/{token}"| INGEST[("Smoo platform<br/>group · symbolicate · alert")]
SDKS -->|"traces + metrics: OTLP/HTTP<br/>M2M token auth"| INGEST
STUDIO["Observability Studio<br/>desktop (Dioxus)"] -->|"reads api.smoo.ai<br/>M2M client_credentials"| INGEST
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class SDKS warm
class INGEST,STUDIO teal
Loading

Full backend architecture: SmooAI/smooai → docs/Architecture/Observability-Architecture.md.

🖥️ Observability Studio (desktop)

desktop/ is a native desktop client for the whole stack — logs, errors, and metrics from api.smoo.ai, multi-org with credentials in your OS keychain, Cmd+K org/view switching. Built with Dioxus on the shared @smooai/ui design system. Unsigned bundles for macOS / Linux / Windows ship from the studio-v* GitHub Releases; or cargo run --release -p observability-studio-app from desktop/.

🗂️ Five SDKs, one contract

PathWhat it isTests / CI
packages/coreTypeScript reference SDK — browser + Node entries, /react · /next · /otel · /metrics · /bootstrap subpathsvitest, published via changesets
python/Python SDK — capture, crash hooks, OTel, GenAI, FastAPI + LangChain integrationspytest lane in pr-checks.yml
rust/Rust SDK (smooai-observability) — capture, OTel, GenAI, tower + reqwest middlewarecargo test + clippy lane
go/Go SDK — capture, OTel, GenAI, net/http + Fiber + Gin middlewarego test lane
dotnet/.NET SDK (SmooAI.Observability) — capture, OTel, GenAI, ASP.NET Core middlewaredotnet test lane
desktop/Observability Studio — Dioxus desktop clientfmt + clippy + test lane; build-desktop.yml bundles 3 OSes on a studio-v* tag
parity/Shared corpora — sampling/traceparent/settings and PII tokensboth loaded by all five language lanes

Every language runs typecheck/lint/format/test in its own pr-checks.yml lane on every PR that touches it.

📖 Built with

  • TypeScript — strict mode, ESM-only, dual browser/Node entries via package exports map; tsup, turborepo, vitest, changesets
  • Python 3uv-managed, pytest
  • Rust — cargo workspace (rust/ SDK, desktop/ Dioxus app), clippy -D warnings
  • Go — stdlib-first module with Fiber/Gin subpackages
  • .NET — single SmooAI.Observability project + xUnit tests

📖 Privacy & telemetry

This SDK is opinionated about privacy:

  • We never capture form bodies, request bodies, or response bodies by default
  • We never capture cookies
  • We never send anything to a third-party service — your events go to your Smoo backend only
  • PII scrubbing is enabled by default and can be tuned per-tenant. Personal identifiers are hashed with HMAC-SHA256 under a key you supply (SMOOAI_OBSERVABILITY_PII_HASH_KEY), salted by org id — identical across the TypeScript, Rust, Go, Python and .NET SDKs. With no key configured they are fully redacted, never hashed under a guessable one.

📖 Status

The TypeScript SDK is live on npm and in production across the Smoo platform. The Python, Rust, Go, and .NET SDKs are feature-complete and CI-tested in-repo but not yet published to PyPI / crates.io / NuGet — the publish workflow (publish.yml) is tag-triggered and no language tag has shipped yet. The desktop studio ships unsigned bundles from studio-v* releases. Backend ingest, fingerprint grouping, and dashboards live in the SmooAI/smooai monorepo under SMOODEV-1067.

🧩 Part of Smoo AI {#part-of-smoo-ai}

@smooai/observability is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Issues and PRs welcome. Maintained by Brent Rager — email · LinkedIn · BlueSky · TikTok · Instagram.

📄 License

MIT © Smoo AI, Inc. See LICENSE.

(back to top)


Built by Smoo AI — AI built into every product.

About

Sentry-like error tracking SDK for the Smoo AI platform — browser, Node, React, and Next.js. The open-core companion to the hosted error + metrics dashboards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@smooai/observability — Error capture and grouping, your backend only.

npmSmoo AIlicense

CIdownloadsTypeScript · Python · Rust · Go · .NETone ingest contract

What it is · Feature tour · Install · Usage · SDK status · Architecture · Studio · Platform


The error-tracking platform we wished was already in our stack. You ship a deploy; somewhere out there a webpack chunk is 404'ing for one user and your sign-in page is silently broken. Your error boundary console.errors into the void, and your only signal is the support ticket that arrives forty minutes later. @smooai/observability fills that gap: automatic capture, breadcrumbs, PII scrubbing, OpenTelemetry traces + metrics, and GenAI telemetry — with SDKs in five languages speaking one ingest contract, your events going to your Smoo backend only. Plus a native desktop studio to read it all.

What is this?

A monorepo of observability SDKs — TypeScript (the reference, on npm), Python, Rust, Go, and .NET (complete and CI-tested, in-repo) — plus a native Dioxus desktop client. Every SDK captures errors with breadcrumbs and scoped context, scrubs PII before anything leaves the process, exports OpenTelemetry traces and metrics over OTLP with M2M auth, and POSTs error events to the same ingest endpoint (POST /webhooks/observability/{org_id}/{token}). The heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

✨ Feature tour

CapabilityWhat you get
🛑Error captureUncaught exceptions + crash handlers in all five languages
🍞Breadcrumbs + scopeRequest-scoped user, tags, and a trail of what led to the error
🔐PII scrubCredentials dropped; emails/phones HMAC-hashed per-org — all five SDKs
🔭OTel traces + metricsOTLP/HTTP export with M2M token auth — all five SDKs
🤖GenAI telemetrygen_ai.* semconv helpers everywhere; wrapOpenAI + LangChain integrations
🧱React / Next.js<ErrorBoundary>, useErrorHandler, source-map upload — TypeScript only
🖥️Desktop studioNative logs/errors/metrics client, multi-org, keychain-stored creds

Error capture — every language

Every SDK ships the same core: captureException (+ each runtime's global crash hooks), breadcrumbs, a request/task-scoped context that doesn't leak across requests, a batched retrying webhook transport, PII scrubbing, and OTLP trace + metric export. What differs per language is the framework glue:

TypeScriptPythonRustGo.NET
Error capture + crash handlers
Breadcrumbs + scoped context
Batched webhook transport
PII scrub + per-org HMAC hashing
OTel traces + metrics (OTLP, M2M auth)
GenAI gen_ai.* helpers
HTTP middlewareHonoFastAPI / Starlettetower · reqwestnet/http · Fiber · GinASP.NET Core
LLM client instrumentationwrapOpenAILangChain / LangGraph callback
Log/session sampling (FNV-1a parity corpus)
Source-map uploadn/an/an/an/a
React / Next.js bindingsn/an/an/an/a
Browser: beacon flush + IndexedDB offline queuen/an/an/an/a
Publishednpmrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushed

The four non-npm SDKs are release-ready but deliberately unpublished: manifests, metadata, dry runs and the publish.yml gates are all in place, and the names are free on crates.io, PyPI and NuGet — but a first publish to those registries is irreversible, so it stays a human decision. See RELEASING.md for the one command each.

Browser extras (TypeScript only): window.onerror / unhandledrejection / console.error taps, fetch/XHR/click/navigation breadcrumbs, release tagging with the git sha, navigator.sendBeacon flush at pagehide, and an IndexedDB offline queue that retries on focus.

What does NOT get captured

  • console.log / console.info / console.warn — only console.error is tapped, and that's opt-out
  • HTTP request bodies — only method, path, status, and duration appear in breadcrumbs
  • Credentials matching the PII scrub regex — dropped outright, never hashed
  • Raw emails / phones / street addresses — replaced by a keyed per-org hash, never stored in the clear

📦 Install

TypeScript is the published SDK — React and Next.js bindings are subpath exports of the same package, not separate installs:

pnpm add @smooai/observability # core — plus /react, /next, /node, /otel, /metrics subpaths

Python, Rust, Go, and .NET are complete and CI-tested, but not yet on their registries (PyPI / crates.io / NuGet publishing is set up in publish.yml and lands with the first language tag). Until then, use them from source:

SDKSourceRegistry status
TypeScriptpackages/corenpm
Pythonpython/ (smooai_observability)unreleased — not yet on PyPI
Rustrust/observability (smooai-observability)unreleased — not yet on crates.io
Gogo get github.com/SmooAI/observability/go@mainno SemVer tag yet — @main resolves via the module proxy
.NETdotnet/ (SmooAI.Observability)unreleased — not yet on NuGet

🚀 Usage

Next.js

// next.config.tsimport{withSmooObservability}from'@smooai/observability/next/build';exportdefaultwithSmooObservability({/* your config */},{org: 'your-org',release: process.env.GITHUB_SHA??'dev',uploadSourcemaps: process.env.CI==='true',},);
// instrumentation.tsexportasyncfunctionregister(){const{ Client }=awaitimport('@smooai/observability');Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE,release: process.env.GITHUB_SHA??'dev',});}
// app/global-error.tsx'use client';import{RootErrorBoundary}from'@smooai/observability/next';exportdefaultfunctionGlobalError({ error, reset }: {error: Error&{digest?: string};reset: ()=>void}){return(<html><body><RootErrorBoundaryerror={error}resetError={reset}fallback={<YourBrandedErroronRetry={reset}/>}/></body></html>);}

Browser SPA

import{Client}from'@smooai/observability';Client.init({dsn: process.env.SMOO_OBSERVABILITY_DSN!,environment: 'production',release: import.meta.env.VITE_GIT_SHA,});Client.setUser({id: 'user_abc',orgId: 'org_xyz'});

React bindings live at the /react subpath — import { ErrorBoundary, useErrorHandler } from '@smooai/observability/react'.

Node / Hono

import{Client,observabilityMiddleware}from'@smooai/observability/node';Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE!,release: process.env.LAMBDA_FUNCTION_VERSION??'dev',});app.use('*',observabilityMiddleware());

Python / Rust / Go / .NET

Same shape, native idioms — each sub-README has the full walkthrough: python/ (FastAPI middleware, LangChain callback, crash hooks), rust/ (tower + reqwest middleware), go/ (net/http, Fiber, Gin), dotnet/ (ASP.NET Core middleware). A taste of Python:

fromsmooai_observabilityimportbootstrap_observability, capture_exceptionbootstrap_observability() # reads SMOOAI_OBSERVABILITY_* env vars (never raises)try:
risky()
exceptExceptionaserr:
capture_exception(err, tags={"area": "ingest"})

🤖 GenAI telemetry (gen_ai.*)

LLM and agent spans carry the OTel GenAI semantic conventions, so any semconv-aware backend reads them — Smoo's LLM dashboard routes on gen_ai.system alone.

importOpenAIfrom'openai';import{wrapOpenAI}from'@smooai/observability';// Instruments chat.completions.create — the original client is untouched.constopenai=wrapOpenAI(newOpenAI(),{conversationId: conversation.id,// Providers don't return a price. Supply one and the cost column fills in.costUsd: ({ inputTokens =0, outputTokens =0})=>inputTokens*2.5e-6+outputTokens*1e-5,});

The same wrapper covers Groq, Together, Fireworks, DeepSeek, Azure OpenAI, and any OpenAI-compatible gateway — pass { system: 'groq' } so spans attribute to the real provider. Prompt and completion content is off by default; { recordContent: true } records it as gen_ai.*.message span events, PII-scrubbed on the way out.

For hand-rolled calls, set the attributes directly:

import{setGenAIAttributes,recordGenAIMessage}from'@smooai/observability';setGenAIAttributes(span,{system: 'anthropic',operationName: 'chat',requestModel: 'claude-opus-4-7',usageInputTokens: 812,usageOutputTokens: 96});

gen_ai.operation.name is a straight passthrough on ingest with no fallback — leave it unset and the operation column lands NULL. Always set it.

Parity across the five SDKs:

SDKAttribute helperMessage eventsContent PII-scrubbedFramework integration
TypeScriptsetGenAIAttributesrecordGenAIMessagewrapOpenAI — OpenAI Node SDK + compatible APIs
Rustset_gen_ai_attributesrecord_gen_ai_message
Pythonset_gen_ai_attributesrecord_gen_ai_messageSmooAICallbackHandler — LangChain / LangGraph
GoSetGenAIAttributesRecordGenAIMessage
.NETGenAIActivity.SetAttributesGenAIActivity.RecordMessage

Known divergences: none in the attribute or event shape. All five emit gen_ai.tool.names as a string array and all five PII-scrub recorded message content. Two divergences that used to be listed here are closed: Rust emitted gen_ai.tool.names comma-joined (a tool name containing a comma silently became two tools, and a Rust service's spans could not be filtered by tool), and only TypeScript scrubbed message content (prompts and tool arguments are the most PII-dense payload the SDK touches). Each fix ships with a span-level test in its own language.

What still differs is only the framework glue — the wrapOpenAI and LangChain columns above — which is a matter of which ecosystems have an integration written, not of the wire contract.

📐 Cross-language parity, honestly

parity/sampling-corpus.json pins 170 vectors for the FNV-1a session sampler, level normalization, W3C traceparent parse/format, and settings resolution. All five SDKs implement it and all five CI lanes load that same file — a language that cannot reproduce a vector fails its build:

SDKLoader
TypeScriptpackages/core/src/__tests__/parity-corpus.test.ts
Rustrust/observability/tests/parity_corpus.rs
Pythonpython/tests/test_parity_corpus.py
Gogo/parity_corpus_test.go
.NETdotnet/tests/.../ParityCorpusTests.cs

parity/** is a path-filter trigger for every language lane, so touching the corpus re-runs all five.

The PII token — the [email:02ea437f] handle that replaces a personal identifier — has its own shared corpus, parity/pii-corpus.json, loaded by the same five lanes. It pins the HMAC message framing, the per-org salt, the per-kind normalization, and the no-key redaction fallback.

📖 Architecture

The SDK is intentionally thin. It captures, batches, redacts credentials, hashes personal identifiers, and POSTs to a Smoo ingest endpoint. All of the heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
SDKS["5 SDKs<br/>TS · Python · Rust · Go · .NET<br/>capture · scope · scrub · batch"]
SDKS -->|"errors: POST /webhooks/observability/{org}/{token}"| INGEST[("Smoo platform<br/>group · symbolicate · alert")]
SDKS -->|"traces + metrics: OTLP/HTTP<br/>M2M token auth"| INGEST
STUDIO["Observability Studio<br/>desktop (Dioxus)"] -->|"reads api.smoo.ai<br/>M2M client_credentials"| INGEST
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class SDKS warm
class INGEST,STUDIO teal
Loading

Full backend architecture: SmooAI/smooai → docs/Architecture/Observability-Architecture.md.

🖥️ Observability Studio (desktop)

desktop/ is a native desktop client for the whole stack — logs, errors, and metrics from api.smoo.ai, multi-org with credentials in your OS keychain, Cmd+K org/view switching. Built with Dioxus on the shared @smooai/ui design system. Unsigned bundles for macOS / Linux / Windows ship from the studio-v* GitHub Releases; or cargo run --release -p observability-studio-app from desktop/.

🗂️ Five SDKs, one contract

PathWhat it isTests / CI
packages/coreTypeScript reference SDK — browser + Node entries, /react · /next · /otel · /metrics · /bootstrap subpathsvitest, published via changesets
python/Python SDK — capture, crash hooks, OTel, GenAI, FastAPI + LangChain integrationspytest lane in pr-checks.yml
rust/Rust SDK (smooai-observability) — capture, OTel, GenAI, tower + reqwest middlewarecargo test + clippy lane
go/Go SDK — capture, OTel, GenAI, net/http + Fiber + Gin middlewarego test lane
dotnet/.NET SDK (SmooAI.Observability) — capture, OTel, GenAI, ASP.NET Core middlewaredotnet test lane
desktop/Observability Studio — Dioxus desktop clientfmt + clippy + test lane; build-desktop.yml bundles 3 OSes on a studio-v* tag
parity/Shared corpora — sampling/traceparent/settings and PII tokensboth loaded by all five language lanes

Every language runs typecheck/lint/format/test in its own pr-checks.yml lane on every PR that touches it.

📖 Built with

  • TypeScript — strict mode, ESM-only, dual browser/Node entries via package exports map; tsup, turborepo, vitest, changesets
  • Python 3uv-managed, pytest
  • Rust — cargo workspace (rust/ SDK, desktop/ Dioxus app), clippy -D warnings
  • Go — stdlib-first module with Fiber/Gin subpackages
  • .NET — single SmooAI.Observability project + xUnit tests

📖 Privacy & telemetry

This SDK is opinionated about privacy:

  • We never capture form bodies, request bodies, or response bodies by default
  • We never capture cookies
  • We never send anything to a third-party service — your events go to your Smoo backend only
  • PII scrubbing is enabled by default and can be tuned per-tenant. Personal identifiers are hashed with HMAC-SHA256 under a key you supply (SMOOAI_OBSERVABILITY_PII_HASH_KEY), salted by org id — identical across the TypeScript, Rust, Go, Python and .NET SDKs. With no key configured they are fully redacted, never hashed under a guessable one.

📖 Status

The TypeScript SDK is live on npm and in production across the Smoo platform. The Python, Rust, Go, and .NET SDKs are feature-complete and CI-tested in-repo but not yet published to PyPI / crates.io / NuGet — the publish workflow (publish.yml) is tag-triggered and no language tag has shipped yet. The desktop studio ships unsigned bundles from studio-v* releases. Backend ingest, fingerprint grouping, and dashboards live in the SmooAI/smooai monorepo under SMOODEV-1067.

🧩 Part of Smoo AI {#part-of-smoo-ai}

@smooai/observability is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Issues and PRs welcome. Maintained by Brent Rager — email · LinkedIn · BlueSky · TikTok · Instagram.

📄 License

MIT © Smoo AI, Inc. See LICENSE.

(back to top)


Built by Smoo AI — AI built into every product.

About

Sentry-like error tracking SDK for the Smoo AI platform — browser, Node, React, and Next.js. The open-core companion to the hosted error + metrics dashboards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

@smooai/observability — Error capture and grouping, your backend only.

npmSmoo AIlicense

CIdownloadsTypeScript · Python · Rust · Go · .NETone ingest contract

What it is · Feature tour · Install · Usage · SDK status · Architecture · Studio · Platform


The error-tracking platform we wished was already in our stack. You ship a deploy; somewhere out there a webpack chunk is 404'ing for one user and your sign-in page is silently broken. Your error boundary console.errors into the void, and your only signal is the support ticket that arrives forty minutes later. @smooai/observability fills that gap: automatic capture, breadcrumbs, PII scrubbing, OpenTelemetry traces + metrics, and GenAI telemetry — with SDKs in five languages speaking one ingest contract, your events going to your Smoo backend only. Plus a native desktop studio to read it all.

What is this?

A monorepo of observability SDKs — TypeScript (the reference, on npm), Python, Rust, Go, and .NET (complete and CI-tested, in-repo) — plus a native Dioxus desktop client. Every SDK captures errors with breadcrumbs and scoped context, scrubs PII before anything leaves the process, exports OpenTelemetry traces and metrics over OTLP with M2M auth, and POSTs error events to the same ingest endpoint (POST /webhooks/observability/{org_id}/{token}). The heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

✨ Feature tour

CapabilityWhat you get
🛑Error captureUncaught exceptions + crash handlers in all five languages
🍞Breadcrumbs + scopeRequest-scoped user, tags, and a trail of what led to the error
🔐PII scrubCredentials dropped; emails/phones HMAC-hashed per-org — all five SDKs
🔭OTel traces + metricsOTLP/HTTP export with M2M token auth — all five SDKs
🤖GenAI telemetrygen_ai.* semconv helpers everywhere; wrapOpenAI + LangChain integrations
🧱React / Next.js<ErrorBoundary>, useErrorHandler, source-map upload — TypeScript only
🖥️Desktop studioNative logs/errors/metrics client, multi-org, keychain-stored creds

Error capture — every language

Every SDK ships the same core: captureException (+ each runtime's global crash hooks), breadcrumbs, a request/task-scoped context that doesn't leak across requests, a batched retrying webhook transport, PII scrubbing, and OTLP trace + metric export. What differs per language is the framework glue:

TypeScriptPythonRustGo.NET
Error capture + crash handlers
Breadcrumbs + scoped context
Batched webhook transport
PII scrub + per-org HMAC hashing
OTel traces + metrics (OTLP, M2M auth)
GenAI gen_ai.* helpers
HTTP middlewareHonoFastAPI / Starlettetower · reqwestnet/http · Fiber · GinASP.NET Core
LLM client instrumentationwrapOpenAILangChain / LangGraph callback
Log/session sampling (FNV-1a parity corpus)
Source-map uploadn/an/an/an/a
React / Next.js bindingsn/an/an/an/a
Browser: beacon flush + IndexedDB offline queuen/an/an/an/a
Publishednpmrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushed

The four non-npm SDKs are release-ready but deliberately unpublished: manifests, metadata, dry runs and the publish.yml gates are all in place, and the names are free on crates.io, PyPI and NuGet — but a first publish to those registries is irreversible, so it stays a human decision. See RELEASING.md for the one command each.

Browser extras (TypeScript only): window.onerror / unhandledrejection / console.error taps, fetch/XHR/click/navigation breadcrumbs, release tagging with the git sha, navigator.sendBeacon flush at pagehide, and an IndexedDB offline queue that retries on focus.

What does NOT get captured

  • console.log / console.info / console.warn — only console.error is tapped, and that's opt-out
  • HTTP request bodies — only method, path, status, and duration appear in breadcrumbs
  • Credentials matching the PII scrub regex — dropped outright, never hashed
  • Raw emails / phones / street addresses — replaced by a keyed per-org hash, never stored in the clear

📦 Install

TypeScript is the published SDK — React and Next.js bindings are subpath exports of the same package, not separate installs:

pnpm add @smooai/observability # core — plus /react, /next, /node, /otel, /metrics subpaths

Python, Rust, Go, and .NET are complete and CI-tested, but not yet on their registries (PyPI / crates.io / NuGet publishing is set up in publish.yml and lands with the first language tag). Until then, use them from source:

SDKSourceRegistry status
TypeScriptpackages/corenpm
Pythonpython/ (smooai_observability)unreleased — not yet on PyPI
Rustrust/observability (smooai-observability)unreleased — not yet on crates.io
Gogo get github.com/SmooAI/observability/go@mainno SemVer tag yet — @main resolves via the module proxy
.NETdotnet/ (SmooAI.Observability)unreleased — not yet on NuGet

🚀 Usage

Next.js

// next.config.tsimport{withSmooObservability}from'@smooai/observability/next/build';exportdefaultwithSmooObservability({/* your config */},{org: 'your-org',release: process.env.GITHUB_SHA??'dev',uploadSourcemaps: process.env.CI==='true',},);
// instrumentation.tsexportasyncfunctionregister(){const{ Client }=awaitimport('@smooai/observability');Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE,release: process.env.GITHUB_SHA??'dev',});}
// app/global-error.tsx'use client';import{RootErrorBoundary}from'@smooai/observability/next';exportdefaultfunctionGlobalError({ error, reset }: {error: Error&{digest?: string};reset: ()=>void}){return(<html><body><RootErrorBoundaryerror={error}resetError={reset}fallback={<YourBrandedErroronRetry={reset}/>}/></body></html>);}

Browser SPA

import{Client}from'@smooai/observability';Client.init({dsn: process.env.SMOO_OBSERVABILITY_DSN!,environment: 'production',release: import.meta.env.VITE_GIT_SHA,});Client.setUser({id: 'user_abc',orgId: 'org_xyz'});

React bindings live at the /react subpath — import { ErrorBoundary, useErrorHandler } from '@smooai/observability/react'.

Node / Hono

import{Client,observabilityMiddleware}from'@smooai/observability/node';Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE!,release: process.env.LAMBDA_FUNCTION_VERSION??'dev',});app.use('*',observabilityMiddleware());

Python / Rust / Go / .NET

Same shape, native idioms — each sub-README has the full walkthrough: python/ (FastAPI middleware, LangChain callback, crash hooks), rust/ (tower + reqwest middleware), go/ (net/http, Fiber, Gin), dotnet/ (ASP.NET Core middleware). A taste of Python:

fromsmooai_observabilityimportbootstrap_observability, capture_exceptionbootstrap_observability() # reads SMOOAI_OBSERVABILITY_* env vars (never raises)try:
risky()
exceptExceptionaserr:
capture_exception(err, tags={"area": "ingest"})

🤖 GenAI telemetry (gen_ai.*)

LLM and agent spans carry the OTel GenAI semantic conventions, so any semconv-aware backend reads them — Smoo's LLM dashboard routes on gen_ai.system alone.

importOpenAIfrom'openai';import{wrapOpenAI}from'@smooai/observability';// Instruments chat.completions.create — the original client is untouched.constopenai=wrapOpenAI(newOpenAI(),{conversationId: conversation.id,// Providers don't return a price. Supply one and the cost column fills in.costUsd: ({ inputTokens =0, outputTokens =0})=>inputTokens*2.5e-6+outputTokens*1e-5,});

The same wrapper covers Groq, Together, Fireworks, DeepSeek, Azure OpenAI, and any OpenAI-compatible gateway — pass { system: 'groq' } so spans attribute to the real provider. Prompt and completion content is off by default; { recordContent: true } records it as gen_ai.*.message span events, PII-scrubbed on the way out.

For hand-rolled calls, set the attributes directly:

import{setGenAIAttributes,recordGenAIMessage}from'@smooai/observability';setGenAIAttributes(span,{system: 'anthropic',operationName: 'chat',requestModel: 'claude-opus-4-7',usageInputTokens: 812,usageOutputTokens: 96});

gen_ai.operation.name is a straight passthrough on ingest with no fallback — leave it unset and the operation column lands NULL. Always set it.

Parity across the five SDKs:

SDKAttribute helperMessage eventsContent PII-scrubbedFramework integration
TypeScriptsetGenAIAttributesrecordGenAIMessagewrapOpenAI — OpenAI Node SDK + compatible APIs
Rustset_gen_ai_attributesrecord_gen_ai_message
Pythonset_gen_ai_attributesrecord_gen_ai_messageSmooAICallbackHandler — LangChain / LangGraph
GoSetGenAIAttributesRecordGenAIMessage
.NETGenAIActivity.SetAttributesGenAIActivity.RecordMessage

Known divergences: none in the attribute or event shape. All five emit gen_ai.tool.names as a string array and all five PII-scrub recorded message content. Two divergences that used to be listed here are closed: Rust emitted gen_ai.tool.names comma-joined (a tool name containing a comma silently became two tools, and a Rust service's spans could not be filtered by tool), and only TypeScript scrubbed message content (prompts and tool arguments are the most PII-dense payload the SDK touches). Each fix ships with a span-level test in its own language.

What still differs is only the framework glue — the wrapOpenAI and LangChain columns above — which is a matter of which ecosystems have an integration written, not of the wire contract.

📐 Cross-language parity, honestly

parity/sampling-corpus.json pins 170 vectors for the FNV-1a session sampler, level normalization, W3C traceparent parse/format, and settings resolution. All five SDKs implement it and all five CI lanes load that same file — a language that cannot reproduce a vector fails its build:

SDKLoader
TypeScriptpackages/core/src/__tests__/parity-corpus.test.ts
Rustrust/observability/tests/parity_corpus.rs
Pythonpython/tests/test_parity_corpus.py
Gogo/parity_corpus_test.go
.NETdotnet/tests/.../ParityCorpusTests.cs

parity/** is a path-filter trigger for every language lane, so touching the corpus re-runs all five.

The PII token — the [email:02ea437f] handle that replaces a personal identifier — has its own shared corpus, parity/pii-corpus.json, loaded by the same five lanes. It pins the HMAC message framing, the per-org salt, the per-kind normalization, and the no-key redaction fallback.

📖 Architecture

The SDK is intentionally thin. It captures, batches, redacts credentials, hashes personal identifiers, and POSTs to a Smoo ingest endpoint. All of the heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
SDKS["5 SDKs<br/>TS · Python · Rust · Go · .NET<br/>capture · scope · scrub · batch"]
SDKS -->|"errors: POST /webhooks/observability/{org}/{token}"| INGEST[("Smoo platform<br/>group · symbolicate · alert")]
SDKS -->|"traces + metrics: OTLP/HTTP<br/>M2M token auth"| INGEST
STUDIO["Observability Studio<br/>desktop (Dioxus)"] -->|"reads api.smoo.ai<br/>M2M client_credentials"| INGEST
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class SDKS warm
class INGEST,STUDIO teal
Loading

Full backend architecture: SmooAI/smooai → docs/Architecture/Observability-Architecture.md.

🖥️ Observability Studio (desktop)

desktop/ is a native desktop client for the whole stack — logs, errors, and metrics from api.smoo.ai, multi-org with credentials in your OS keychain, Cmd+K org/view switching. Built with Dioxus on the shared @smooai/ui design system. Unsigned bundles for macOS / Linux / Windows ship from the studio-v* GitHub Releases; or cargo run --release -p observability-studio-app from desktop/.

🗂️ Five SDKs, one contract

PathWhat it isTests / CI
packages/coreTypeScript reference SDK — browser + Node entries, /react · /next · /otel · /metrics · /bootstrap subpathsvitest, published via changesets
python/Python SDK — capture, crash hooks, OTel, GenAI, FastAPI + LangChain integrationspytest lane in pr-checks.yml
rust/Rust SDK (smooai-observability) — capture, OTel, GenAI, tower + reqwest middlewarecargo test + clippy lane
go/Go SDK — capture, OTel, GenAI, net/http + Fiber + Gin middlewarego test lane
dotnet/.NET SDK (SmooAI.Observability) — capture, OTel, GenAI, ASP.NET Core middlewaredotnet test lane
desktop/Observability Studio — Dioxus desktop clientfmt + clippy + test lane; build-desktop.yml bundles 3 OSes on a studio-v* tag
parity/Shared corpora — sampling/traceparent/settings and PII tokensboth loaded by all five language lanes

Every language runs typecheck/lint/format/test in its own pr-checks.yml lane on every PR that touches it.

📖 Built with

  • TypeScript — strict mode, ESM-only, dual browser/Node entries via package exports map; tsup, turborepo, vitest, changesets
  • Python 3uv-managed, pytest
  • Rust — cargo workspace (rust/ SDK, desktop/ Dioxus app), clippy -D warnings
  • Go — stdlib-first module with Fiber/Gin subpackages
  • .NET — single SmooAI.Observability project + xUnit tests

📖 Privacy & telemetry

This SDK is opinionated about privacy:

  • We never capture form bodies, request bodies, or response bodies by default
  • We never capture cookies
  • We never send anything to a third-party service — your events go to your Smoo backend only
  • PII scrubbing is enabled by default and can be tuned per-tenant. Personal identifiers are hashed with HMAC-SHA256 under a key you supply (SMOOAI_OBSERVABILITY_PII_HASH_KEY), salted by org id — identical across the TypeScript, Rust, Go, Python and .NET SDKs. With no key configured they are fully redacted, never hashed under a guessable one.

📖 Status

The TypeScript SDK is live on npm and in production across the Smoo platform. The Python, Rust, Go, and .NET SDKs are feature-complete and CI-tested in-repo but not yet published to PyPI / crates.io / NuGet — the publish workflow (publish.yml) is tag-triggered and no language tag has shipped yet. The desktop studio ships unsigned bundles from studio-v* releases. Backend ingest, fingerprint grouping, and dashboards live in the SmooAI/smooai monorepo under SMOODEV-1067.

🧩 Part of Smoo AI {#part-of-smoo-ai}

@smooai/observability is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Issues and PRs welcome. Maintained by Brent Rager — email · LinkedIn · BlueSky · TikTok · Instagram.

📄 License

MIT © Smoo AI, Inc. See LICENSE.

(back to top)


Built by Smoo AI — AI built into every product.

About

Sentry-like error tracking SDK for the Smoo AI platform — browser, Node, React, and Next.js. The open-core companion to the hosted error + metrics dashboards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@smooai/observability — Error capture and grouping, your backend only.

npmSmoo AIlicense

CIdownloadsTypeScript · Python · Rust · Go · .NETone ingest contract

What it is · Feature tour · Install · Usage · SDK status · Architecture · Studio · Platform


The error-tracking platform we wished was already in our stack. You ship a deploy; somewhere out there a webpack chunk is 404'ing for one user and your sign-in page is silently broken. Your error boundary console.errors into the void, and your only signal is the support ticket that arrives forty minutes later. @smooai/observability fills that gap: automatic capture, breadcrumbs, PII scrubbing, OpenTelemetry traces + metrics, and GenAI telemetry — with SDKs in five languages speaking one ingest contract, your events going to your Smoo backend only. Plus a native desktop studio to read it all.

What is this?

A monorepo of observability SDKs — TypeScript (the reference, on npm), Python, Rust, Go, and .NET (complete and CI-tested, in-repo) — plus a native Dioxus desktop client. Every SDK captures errors with breadcrumbs and scoped context, scrubs PII before anything leaves the process, exports OpenTelemetry traces and metrics over OTLP with M2M auth, and POSTs error events to the same ingest endpoint (POST /webhooks/observability/{org_id}/{token}). The heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

✨ Feature tour

CapabilityWhat you get
🛑Error captureUncaught exceptions + crash handlers in all five languages
🍞Breadcrumbs + scopeRequest-scoped user, tags, and a trail of what led to the error
🔐PII scrubCredentials dropped; emails/phones HMAC-hashed per-org — all five SDKs
🔭OTel traces + metricsOTLP/HTTP export with M2M token auth — all five SDKs
🤖GenAI telemetrygen_ai.* semconv helpers everywhere; wrapOpenAI + LangChain integrations
🧱React / Next.js<ErrorBoundary>, useErrorHandler, source-map upload — TypeScript only
🖥️Desktop studioNative logs/errors/metrics client, multi-org, keychain-stored creds

Error capture — every language

Every SDK ships the same core: captureException (+ each runtime's global crash hooks), breadcrumbs, a request/task-scoped context that doesn't leak across requests, a batched retrying webhook transport, PII scrubbing, and OTLP trace + metric export. What differs per language is the framework glue:

TypeScriptPythonRustGo.NET
Error capture + crash handlers
Breadcrumbs + scoped context
Batched webhook transport
PII scrub + per-org HMAC hashing
OTel traces + metrics (OTLP, M2M auth)
GenAI gen_ai.* helpers
HTTP middlewareHonoFastAPI / Starlettetower · reqwestnet/http · Fiber · GinASP.NET Core
LLM client instrumentationwrapOpenAILangChain / LangGraph callback
Log/session sampling (FNV-1a parity corpus)
Source-map uploadn/an/an/an/a
React / Next.js bindingsn/an/an/an/a
Browser: beacon flush + IndexedDB offline queuen/an/an/an/a
Publishednpmrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushed

The four non-npm SDKs are release-ready but deliberately unpublished: manifests, metadata, dry runs and the publish.yml gates are all in place, and the names are free on crates.io, PyPI and NuGet — but a first publish to those registries is irreversible, so it stays a human decision. See RELEASING.md for the one command each.

Browser extras (TypeScript only): window.onerror / unhandledrejection / console.error taps, fetch/XHR/click/navigation breadcrumbs, release tagging with the git sha, navigator.sendBeacon flush at pagehide, and an IndexedDB offline queue that retries on focus.

What does NOT get captured

  • console.log / console.info / console.warn — only console.error is tapped, and that's opt-out
  • HTTP request bodies — only method, path, status, and duration appear in breadcrumbs
  • Credentials matching the PII scrub regex — dropped outright, never hashed
  • Raw emails / phones / street addresses — replaced by a keyed per-org hash, never stored in the clear

📦 Install

TypeScript is the published SDK — React and Next.js bindings are subpath exports of the same package, not separate installs:

pnpm add @smooai/observability # core — plus /react, /next, /node, /otel, /metrics subpaths

Python, Rust, Go, and .NET are complete and CI-tested, but not yet on their registries (PyPI / crates.io / NuGet publishing is set up in publish.yml and lands with the first language tag). Until then, use them from source:

SDKSourceRegistry status
TypeScriptpackages/corenpm
Pythonpython/ (smooai_observability)unreleased — not yet on PyPI
Rustrust/observability (smooai-observability)unreleased — not yet on crates.io
Gogo get github.com/SmooAI/observability/go@mainno SemVer tag yet — @main resolves via the module proxy
.NETdotnet/ (SmooAI.Observability)unreleased — not yet on NuGet

🚀 Usage

Next.js

// next.config.tsimport{withSmooObservability}from'@smooai/observability/next/build';exportdefaultwithSmooObservability({/* your config */},{org: 'your-org',release: process.env.GITHUB_SHA??'dev',uploadSourcemaps: process.env.CI==='true',},);
// instrumentation.tsexportasyncfunctionregister(){const{ Client }=awaitimport('@smooai/observability');Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE,release: process.env.GITHUB_SHA??'dev',});}
// app/global-error.tsx'use client';import{RootErrorBoundary}from'@smooai/observability/next';exportdefaultfunctionGlobalError({ error, reset }: {error: Error&{digest?: string};reset: ()=>void}){return(<html><body><RootErrorBoundaryerror={error}resetError={reset}fallback={<YourBrandedErroronRetry={reset}/>}/></body></html>);}

Browser SPA

import{Client}from'@smooai/observability';Client.init({dsn: process.env.SMOO_OBSERVABILITY_DSN!,environment: 'production',release: import.meta.env.VITE_GIT_SHA,});Client.setUser({id: 'user_abc',orgId: 'org_xyz'});

React bindings live at the /react subpath — import { ErrorBoundary, useErrorHandler } from '@smooai/observability/react'.

Node / Hono

import{Client,observabilityMiddleware}from'@smooai/observability/node';Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE!,release: process.env.LAMBDA_FUNCTION_VERSION??'dev',});app.use('*',observabilityMiddleware());

Python / Rust / Go / .NET

Same shape, native idioms — each sub-README has the full walkthrough: python/ (FastAPI middleware, LangChain callback, crash hooks), rust/ (tower + reqwest middleware), go/ (net/http, Fiber, Gin), dotnet/ (ASP.NET Core middleware). A taste of Python:

fromsmooai_observabilityimportbootstrap_observability, capture_exceptionbootstrap_observability() # reads SMOOAI_OBSERVABILITY_* env vars (never raises)try:
risky()
exceptExceptionaserr:
capture_exception(err, tags={"area": "ingest"})

🤖 GenAI telemetry (gen_ai.*)

LLM and agent spans carry the OTel GenAI semantic conventions, so any semconv-aware backend reads them — Smoo's LLM dashboard routes on gen_ai.system alone.

importOpenAIfrom'openai';import{wrapOpenAI}from'@smooai/observability';// Instruments chat.completions.create — the original client is untouched.constopenai=wrapOpenAI(newOpenAI(),{conversationId: conversation.id,// Providers don't return a price. Supply one and the cost column fills in.costUsd: ({ inputTokens =0, outputTokens =0})=>inputTokens*2.5e-6+outputTokens*1e-5,});

The same wrapper covers Groq, Together, Fireworks, DeepSeek, Azure OpenAI, and any OpenAI-compatible gateway — pass { system: 'groq' } so spans attribute to the real provider. Prompt and completion content is off by default; { recordContent: true } records it as gen_ai.*.message span events, PII-scrubbed on the way out.

For hand-rolled calls, set the attributes directly:

import{setGenAIAttributes,recordGenAIMessage}from'@smooai/observability';setGenAIAttributes(span,{system: 'anthropic',operationName: 'chat',requestModel: 'claude-opus-4-7',usageInputTokens: 812,usageOutputTokens: 96});

gen_ai.operation.name is a straight passthrough on ingest with no fallback — leave it unset and the operation column lands NULL. Always set it.

Parity across the five SDKs:

SDKAttribute helperMessage eventsContent PII-scrubbedFramework integration
TypeScriptsetGenAIAttributesrecordGenAIMessagewrapOpenAI — OpenAI Node SDK + compatible APIs
Rustset_gen_ai_attributesrecord_gen_ai_message
Pythonset_gen_ai_attributesrecord_gen_ai_messageSmooAICallbackHandler — LangChain / LangGraph
GoSetGenAIAttributesRecordGenAIMessage
.NETGenAIActivity.SetAttributesGenAIActivity.RecordMessage

Known divergences: none in the attribute or event shape. All five emit gen_ai.tool.names as a string array and all five PII-scrub recorded message content. Two divergences that used to be listed here are closed: Rust emitted gen_ai.tool.names comma-joined (a tool name containing a comma silently became two tools, and a Rust service's spans could not be filtered by tool), and only TypeScript scrubbed message content (prompts and tool arguments are the most PII-dense payload the SDK touches). Each fix ships with a span-level test in its own language.

What still differs is only the framework glue — the wrapOpenAI and LangChain columns above — which is a matter of which ecosystems have an integration written, not of the wire contract.

📐 Cross-language parity, honestly

parity/sampling-corpus.json pins 170 vectors for the FNV-1a session sampler, level normalization, W3C traceparent parse/format, and settings resolution. All five SDKs implement it and all five CI lanes load that same file — a language that cannot reproduce a vector fails its build:

SDKLoader
TypeScriptpackages/core/src/__tests__/parity-corpus.test.ts
Rustrust/observability/tests/parity_corpus.rs
Pythonpython/tests/test_parity_corpus.py
Gogo/parity_corpus_test.go
.NETdotnet/tests/.../ParityCorpusTests.cs

parity/** is a path-filter trigger for every language lane, so touching the corpus re-runs all five.

The PII token — the [email:02ea437f] handle that replaces a personal identifier — has its own shared corpus, parity/pii-corpus.json, loaded by the same five lanes. It pins the HMAC message framing, the per-org salt, the per-kind normalization, and the no-key redaction fallback.

📖 Architecture

The SDK is intentionally thin. It captures, batches, redacts credentials, hashes personal identifiers, and POSTs to a Smoo ingest endpoint. All of the heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
SDKS["5 SDKs<br/>TS · Python · Rust · Go · .NET<br/>capture · scope · scrub · batch"]
SDKS -->|"errors: POST /webhooks/observability/{org}/{token}"| INGEST[("Smoo platform<br/>group · symbolicate · alert")]
SDKS -->|"traces + metrics: OTLP/HTTP<br/>M2M token auth"| INGEST
STUDIO["Observability Studio<br/>desktop (Dioxus)"] -->|"reads api.smoo.ai<br/>M2M client_credentials"| INGEST
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class SDKS warm
class INGEST,STUDIO teal
Loading

Full backend architecture: SmooAI/smooai → docs/Architecture/Observability-Architecture.md.

🖥️ Observability Studio (desktop)

desktop/ is a native desktop client for the whole stack — logs, errors, and metrics from api.smoo.ai, multi-org with credentials in your OS keychain, Cmd+K org/view switching. Built with Dioxus on the shared @smooai/ui design system. Unsigned bundles for macOS / Linux / Windows ship from the studio-v* GitHub Releases; or cargo run --release -p observability-studio-app from desktop/.

🗂️ Five SDKs, one contract

PathWhat it isTests / CI
packages/coreTypeScript reference SDK — browser + Node entries, /react · /next · /otel · /metrics · /bootstrap subpathsvitest, published via changesets
python/Python SDK — capture, crash hooks, OTel, GenAI, FastAPI + LangChain integrationspytest lane in pr-checks.yml
rust/Rust SDK (smooai-observability) — capture, OTel, GenAI, tower + reqwest middlewarecargo test + clippy lane
go/Go SDK — capture, OTel, GenAI, net/http + Fiber + Gin middlewarego test lane
dotnet/.NET SDK (SmooAI.Observability) — capture, OTel, GenAI, ASP.NET Core middlewaredotnet test lane
desktop/Observability Studio — Dioxus desktop clientfmt + clippy + test lane; build-desktop.yml bundles 3 OSes on a studio-v* tag
parity/Shared corpora — sampling/traceparent/settings and PII tokensboth loaded by all five language lanes

Every language runs typecheck/lint/format/test in its own pr-checks.yml lane on every PR that touches it.

📖 Built with

  • TypeScript — strict mode, ESM-only, dual browser/Node entries via package exports map; tsup, turborepo, vitest, changesets
  • Python 3uv-managed, pytest
  • Rust — cargo workspace (rust/ SDK, desktop/ Dioxus app), clippy -D warnings
  • Go — stdlib-first module with Fiber/Gin subpackages
  • .NET — single SmooAI.Observability project + xUnit tests

📖 Privacy & telemetry

This SDK is opinionated about privacy:

  • We never capture form bodies, request bodies, or response bodies by default
  • We never capture cookies
  • We never send anything to a third-party service — your events go to your Smoo backend only
  • PII scrubbing is enabled by default and can be tuned per-tenant. Personal identifiers are hashed with HMAC-SHA256 under a key you supply (SMOOAI_OBSERVABILITY_PII_HASH_KEY), salted by org id — identical across the TypeScript, Rust, Go, Python and .NET SDKs. With no key configured they are fully redacted, never hashed under a guessable one.

📖 Status

The TypeScript SDK is live on npm and in production across the Smoo platform. The Python, Rust, Go, and .NET SDKs are feature-complete and CI-tested in-repo but not yet published to PyPI / crates.io / NuGet — the publish workflow (publish.yml) is tag-triggered and no language tag has shipped yet. The desktop studio ships unsigned bundles from studio-v* releases. Backend ingest, fingerprint grouping, and dashboards live in the SmooAI/smooai monorepo under SMOODEV-1067.

🧩 Part of Smoo AI {#part-of-smoo-ai}

@smooai/observability is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Issues and PRs welcome. Maintained by Brent Rager — email · LinkedIn · BlueSky · TikTok · Instagram.

📄 License

MIT © Smoo AI, Inc. See LICENSE.

(back to top)


Built by Smoo AI — AI built into every product.

About

Sentry-like error tracking SDK for the Smoo AI platform — browser, Node, React, and Next.js. The open-core companion to the hosted error + metrics dashboards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@smooai/observability — Error capture and grouping, your backend only.

npmSmoo AIlicense

CIdownloadsTypeScript · Python · Rust · Go · .NETone ingest contract

What it is · Feature tour · Install · Usage · SDK status · Architecture · Studio · Platform


The error-tracking platform we wished was already in our stack. You ship a deploy; somewhere out there a webpack chunk is 404'ing for one user and your sign-in page is silently broken. Your error boundary console.errors into the void, and your only signal is the support ticket that arrives forty minutes later. @smooai/observability fills that gap: automatic capture, breadcrumbs, PII scrubbing, OpenTelemetry traces + metrics, and GenAI telemetry — with SDKs in five languages speaking one ingest contract, your events going to your Smoo backend only. Plus a native desktop studio to read it all.

What is this?

A monorepo of observability SDKs — TypeScript (the reference, on npm), Python, Rust, Go, and .NET (complete and CI-tested, in-repo) — plus a native Dioxus desktop client. Every SDK captures errors with breadcrumbs and scoped context, scrubs PII before anything leaves the process, exports OpenTelemetry traces and metrics over OTLP with M2M auth, and POSTs error events to the same ingest endpoint (POST /webhooks/observability/{org_id}/{token}). The heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

✨ Feature tour

CapabilityWhat you get
🛑Error captureUncaught exceptions + crash handlers in all five languages
🍞Breadcrumbs + scopeRequest-scoped user, tags, and a trail of what led to the error
🔐PII scrubCredentials dropped; emails/phones HMAC-hashed per-org — all five SDKs
🔭OTel traces + metricsOTLP/HTTP export with M2M token auth — all five SDKs
🤖GenAI telemetrygen_ai.* semconv helpers everywhere; wrapOpenAI + LangChain integrations
🧱React / Next.js<ErrorBoundary>, useErrorHandler, source-map upload — TypeScript only
🖥️Desktop studioNative logs/errors/metrics client, multi-org, keychain-stored creds

Error capture — every language

Every SDK ships the same core: captureException (+ each runtime's global crash hooks), breadcrumbs, a request/task-scoped context that doesn't leak across requests, a batched retrying webhook transport, PII scrubbing, and OTLP trace + metric export. What differs per language is the framework glue:

TypeScriptPythonRustGo.NET
Error capture + crash handlers
Breadcrumbs + scoped context
Batched webhook transport
PII scrub + per-org HMAC hashing
OTel traces + metrics (OTLP, M2M auth)
GenAI gen_ai.* helpers
HTTP middlewareHonoFastAPI / Starlettetower · reqwestnet/http · Fiber · GinASP.NET Core
LLM client instrumentationwrapOpenAILangChain / LangGraph callback
Log/session sampling (FNV-1a parity corpus)
Source-map uploadn/an/an/an/a
React / Next.js bindingsn/an/an/an/a
Browser: beacon flush + IndexedDB offline queuen/an/an/an/a
Publishednpmrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushed

The four non-npm SDKs are release-ready but deliberately unpublished: manifests, metadata, dry runs and the publish.yml gates are all in place, and the names are free on crates.io, PyPI and NuGet — but a first publish to those registries is irreversible, so it stays a human decision. See RELEASING.md for the one command each.

Browser extras (TypeScript only): window.onerror / unhandledrejection / console.error taps, fetch/XHR/click/navigation breadcrumbs, release tagging with the git sha, navigator.sendBeacon flush at pagehide, and an IndexedDB offline queue that retries on focus.

What does NOT get captured

  • console.log / console.info / console.warn — only console.error is tapped, and that's opt-out
  • HTTP request bodies — only method, path, status, and duration appear in breadcrumbs
  • Credentials matching the PII scrub regex — dropped outright, never hashed
  • Raw emails / phones / street addresses — replaced by a keyed per-org hash, never stored in the clear

📦 Install

TypeScript is the published SDK — React and Next.js bindings are subpath exports of the same package, not separate installs:

pnpm add @smooai/observability # core — plus /react, /next, /node, /otel, /metrics subpaths

Python, Rust, Go, and .NET are complete and CI-tested, but not yet on their registries (PyPI / crates.io / NuGet publishing is set up in publish.yml and lands with the first language tag). Until then, use them from source:

SDKSourceRegistry status
TypeScriptpackages/corenpm
Pythonpython/ (smooai_observability)unreleased — not yet on PyPI
Rustrust/observability (smooai-observability)unreleased — not yet on crates.io
Gogo get github.com/SmooAI/observability/go@mainno SemVer tag yet — @main resolves via the module proxy
.NETdotnet/ (SmooAI.Observability)unreleased — not yet on NuGet

🚀 Usage

Next.js

// next.config.tsimport{withSmooObservability}from'@smooai/observability/next/build';exportdefaultwithSmooObservability({/* your config */},{org: 'your-org',release: process.env.GITHUB_SHA??'dev',uploadSourcemaps: process.env.CI==='true',},);
// instrumentation.tsexportasyncfunctionregister(){const{ Client }=awaitimport('@smooai/observability');Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE,release: process.env.GITHUB_SHA??'dev',});}
// app/global-error.tsx'use client';import{RootErrorBoundary}from'@smooai/observability/next';exportdefaultfunctionGlobalError({ error, reset }: {error: Error&{digest?: string};reset: ()=>void}){return(<html><body><RootErrorBoundaryerror={error}resetError={reset}fallback={<YourBrandedErroronRetry={reset}/>}/></body></html>);}

Browser SPA

import{Client}from'@smooai/observability';Client.init({dsn: process.env.SMOO_OBSERVABILITY_DSN!,environment: 'production',release: import.meta.env.VITE_GIT_SHA,});Client.setUser({id: 'user_abc',orgId: 'org_xyz'});

React bindings live at the /react subpath — import { ErrorBoundary, useErrorHandler } from '@smooai/observability/react'.

Node / Hono

import{Client,observabilityMiddleware}from'@smooai/observability/node';Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE!,release: process.env.LAMBDA_FUNCTION_VERSION??'dev',});app.use('*',observabilityMiddleware());

Python / Rust / Go / .NET

Same shape, native idioms — each sub-README has the full walkthrough: python/ (FastAPI middleware, LangChain callback, crash hooks), rust/ (tower + reqwest middleware), go/ (net/http, Fiber, Gin), dotnet/ (ASP.NET Core middleware). A taste of Python:

fromsmooai_observabilityimportbootstrap_observability, capture_exceptionbootstrap_observability() # reads SMOOAI_OBSERVABILITY_* env vars (never raises)try:
risky()
exceptExceptionaserr:
capture_exception(err, tags={"area": "ingest"})

🤖 GenAI telemetry (gen_ai.*)

LLM and agent spans carry the OTel GenAI semantic conventions, so any semconv-aware backend reads them — Smoo's LLM dashboard routes on gen_ai.system alone.

importOpenAIfrom'openai';import{wrapOpenAI}from'@smooai/observability';// Instruments chat.completions.create — the original client is untouched.constopenai=wrapOpenAI(newOpenAI(),{conversationId: conversation.id,// Providers don't return a price. Supply one and the cost column fills in.costUsd: ({ inputTokens =0, outputTokens =0})=>inputTokens*2.5e-6+outputTokens*1e-5,});

The same wrapper covers Groq, Together, Fireworks, DeepSeek, Azure OpenAI, and any OpenAI-compatible gateway — pass { system: 'groq' } so spans attribute to the real provider. Prompt and completion content is off by default; { recordContent: true } records it as gen_ai.*.message span events, PII-scrubbed on the way out.

For hand-rolled calls, set the attributes directly:

import{setGenAIAttributes,recordGenAIMessage}from'@smooai/observability';setGenAIAttributes(span,{system: 'anthropic',operationName: 'chat',requestModel: 'claude-opus-4-7',usageInputTokens: 812,usageOutputTokens: 96});

gen_ai.operation.name is a straight passthrough on ingest with no fallback — leave it unset and the operation column lands NULL. Always set it.

Parity across the five SDKs:

SDKAttribute helperMessage eventsContent PII-scrubbedFramework integration
TypeScriptsetGenAIAttributesrecordGenAIMessagewrapOpenAI — OpenAI Node SDK + compatible APIs
Rustset_gen_ai_attributesrecord_gen_ai_message
Pythonset_gen_ai_attributesrecord_gen_ai_messageSmooAICallbackHandler — LangChain / LangGraph
GoSetGenAIAttributesRecordGenAIMessage
.NETGenAIActivity.SetAttributesGenAIActivity.RecordMessage

Known divergences: none in the attribute or event shape. All five emit gen_ai.tool.names as a string array and all five PII-scrub recorded message content. Two divergences that used to be listed here are closed: Rust emitted gen_ai.tool.names comma-joined (a tool name containing a comma silently became two tools, and a Rust service's spans could not be filtered by tool), and only TypeScript scrubbed message content (prompts and tool arguments are the most PII-dense payload the SDK touches). Each fix ships with a span-level test in its own language.

What still differs is only the framework glue — the wrapOpenAI and LangChain columns above — which is a matter of which ecosystems have an integration written, not of the wire contract.

📐 Cross-language parity, honestly

parity/sampling-corpus.json pins 170 vectors for the FNV-1a session sampler, level normalization, W3C traceparent parse/format, and settings resolution. All five SDKs implement it and all five CI lanes load that same file — a language that cannot reproduce a vector fails its build:

SDKLoader
TypeScriptpackages/core/src/__tests__/parity-corpus.test.ts
Rustrust/observability/tests/parity_corpus.rs
Pythonpython/tests/test_parity_corpus.py
Gogo/parity_corpus_test.go
.NETdotnet/tests/.../ParityCorpusTests.cs

parity/** is a path-filter trigger for every language lane, so touching the corpus re-runs all five.

The PII token — the [email:02ea437f] handle that replaces a personal identifier — has its own shared corpus, parity/pii-corpus.json, loaded by the same five lanes. It pins the HMAC message framing, the per-org salt, the per-kind normalization, and the no-key redaction fallback.

📖 Architecture

The SDK is intentionally thin. It captures, batches, redacts credentials, hashes personal identifiers, and POSTs to a Smoo ingest endpoint. All of the heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
SDKS["5 SDKs<br/>TS · Python · Rust · Go · .NET<br/>capture · scope · scrub · batch"]
SDKS -->|"errors: POST /webhooks/observability/{org}/{token}"| INGEST[("Smoo platform<br/>group · symbolicate · alert")]
SDKS -->|"traces + metrics: OTLP/HTTP<br/>M2M token auth"| INGEST
STUDIO["Observability Studio<br/>desktop (Dioxus)"] -->|"reads api.smoo.ai<br/>M2M client_credentials"| INGEST
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class SDKS warm
class INGEST,STUDIO teal
Loading

Full backend architecture: SmooAI/smooai → docs/Architecture/Observability-Architecture.md.

🖥️ Observability Studio (desktop)

desktop/ is a native desktop client for the whole stack — logs, errors, and metrics from api.smoo.ai, multi-org with credentials in your OS keychain, Cmd+K org/view switching. Built with Dioxus on the shared @smooai/ui design system. Unsigned bundles for macOS / Linux / Windows ship from the studio-v* GitHub Releases; or cargo run --release -p observability-studio-app from desktop/.

🗂️ Five SDKs, one contract

PathWhat it isTests / CI
packages/coreTypeScript reference SDK — browser + Node entries, /react · /next · /otel · /metrics · /bootstrap subpathsvitest, published via changesets
python/Python SDK — capture, crash hooks, OTel, GenAI, FastAPI + LangChain integrationspytest lane in pr-checks.yml
rust/Rust SDK (smooai-observability) — capture, OTel, GenAI, tower + reqwest middlewarecargo test + clippy lane
go/Go SDK — capture, OTel, GenAI, net/http + Fiber + Gin middlewarego test lane
dotnet/.NET SDK (SmooAI.Observability) — capture, OTel, GenAI, ASP.NET Core middlewaredotnet test lane
desktop/Observability Studio — Dioxus desktop clientfmt + clippy + test lane; build-desktop.yml bundles 3 OSes on a studio-v* tag
parity/Shared corpora — sampling/traceparent/settings and PII tokensboth loaded by all five language lanes

Every language runs typecheck/lint/format/test in its own pr-checks.yml lane on every PR that touches it.

📖 Built with

  • TypeScript — strict mode, ESM-only, dual browser/Node entries via package exports map; tsup, turborepo, vitest, changesets
  • Python 3uv-managed, pytest
  • Rust — cargo workspace (rust/ SDK, desktop/ Dioxus app), clippy -D warnings
  • Go — stdlib-first module with Fiber/Gin subpackages
  • .NET — single SmooAI.Observability project + xUnit tests

📖 Privacy & telemetry

This SDK is opinionated about privacy:

  • We never capture form bodies, request bodies, or response bodies by default
  • We never capture cookies
  • We never send anything to a third-party service — your events go to your Smoo backend only
  • PII scrubbing is enabled by default and can be tuned per-tenant. Personal identifiers are hashed with HMAC-SHA256 under a key you supply (SMOOAI_OBSERVABILITY_PII_HASH_KEY), salted by org id — identical across the TypeScript, Rust, Go, Python and .NET SDKs. With no key configured they are fully redacted, never hashed under a guessable one.

📖 Status

The TypeScript SDK is live on npm and in production across the Smoo platform. The Python, Rust, Go, and .NET SDKs are feature-complete and CI-tested in-repo but not yet published to PyPI / crates.io / NuGet — the publish workflow (publish.yml) is tag-triggered and no language tag has shipped yet. The desktop studio ships unsigned bundles from studio-v* releases. Backend ingest, fingerprint grouping, and dashboards live in the SmooAI/smooai monorepo under SMOODEV-1067.

🧩 Part of Smoo AI {#part-of-smoo-ai}

@smooai/observability is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Issues and PRs welcome. Maintained by Brent Rager — email · LinkedIn · BlueSky · TikTok · Instagram.

📄 License

MIT © Smoo AI, Inc. See LICENSE.

(back to top)


Built by Smoo AI — AI built into every product.

About

Sentry-like error tracking SDK for the Smoo AI platform — browser, Node, React, and Next.js. The open-core companion to the hosted error + metrics dashboards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

@smooai/observability — Error capture and grouping, your backend only.

npmSmoo AIlicense

CIdownloadsTypeScript · Python · Rust · Go · .NETone ingest contract

What it is · Feature tour · Install · Usage · SDK status · Architecture · Studio · Platform


The error-tracking platform we wished was already in our stack. You ship a deploy; somewhere out there a webpack chunk is 404'ing for one user and your sign-in page is silently broken. Your error boundary console.errors into the void, and your only signal is the support ticket that arrives forty minutes later. @smooai/observability fills that gap: automatic capture, breadcrumbs, PII scrubbing, OpenTelemetry traces + metrics, and GenAI telemetry — with SDKs in five languages speaking one ingest contract, your events going to your Smoo backend only. Plus a native desktop studio to read it all.

What is this?

A monorepo of observability SDKs — TypeScript (the reference, on npm), Python, Rust, Go, and .NET (complete and CI-tested, in-repo) — plus a native Dioxus desktop client. Every SDK captures errors with breadcrumbs and scoped context, scrubs PII before anything leaves the process, exports OpenTelemetry traces and metrics over OTLP with M2M auth, and POSTs error events to the same ingest endpoint (POST /webhooks/observability/{org_id}/{token}). The heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

✨ Feature tour

CapabilityWhat you get
🛑Error captureUncaught exceptions + crash handlers in all five languages
🍞Breadcrumbs + scopeRequest-scoped user, tags, and a trail of what led to the error
🔐PII scrubCredentials dropped; emails/phones HMAC-hashed per-org — all five SDKs
🔭OTel traces + metricsOTLP/HTTP export with M2M token auth — all five SDKs
🤖GenAI telemetrygen_ai.* semconv helpers everywhere; wrapOpenAI + LangChain integrations
🧱React / Next.js<ErrorBoundary>, useErrorHandler, source-map upload — TypeScript only
🖥️Desktop studioNative logs/errors/metrics client, multi-org, keychain-stored creds

Error capture — every language

Every SDK ships the same core: captureException (+ each runtime's global crash hooks), breadcrumbs, a request/task-scoped context that doesn't leak across requests, a batched retrying webhook transport, PII scrubbing, and OTLP trace + metric export. What differs per language is the framework glue:

TypeScriptPythonRustGo.NET
Error capture + crash handlers
Breadcrumbs + scoped context
Batched webhook transport
PII scrub + per-org HMAC hashing
OTel traces + metrics (OTLP, M2M auth)
GenAI gen_ai.* helpers
HTTP middlewareHonoFastAPI / Starlettetower · reqwestnet/http · Fiber · GinASP.NET Core
LLM client instrumentationwrapOpenAILangChain / LangGraph callback
Log/session sampling (FNV-1a parity corpus)
Source-map uploadn/an/an/an/a
React / Next.js bindingsn/an/an/an/a
Browser: beacon flush + IndexedDB offline queuen/an/an/an/a
Publishednpmrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushedrelease-ready, not yet pushed

The four non-npm SDKs are release-ready but deliberately unpublished: manifests, metadata, dry runs and the publish.yml gates are all in place, and the names are free on crates.io, PyPI and NuGet — but a first publish to those registries is irreversible, so it stays a human decision. See RELEASING.md for the one command each.

Browser extras (TypeScript only): window.onerror / unhandledrejection / console.error taps, fetch/XHR/click/navigation breadcrumbs, release tagging with the git sha, navigator.sendBeacon flush at pagehide, and an IndexedDB offline queue that retries on focus.

What does NOT get captured

  • console.log / console.info / console.warn — only console.error is tapped, and that's opt-out
  • HTTP request bodies — only method, path, status, and duration appear in breadcrumbs
  • Credentials matching the PII scrub regex — dropped outright, never hashed
  • Raw emails / phones / street addresses — replaced by a keyed per-org hash, never stored in the clear

📦 Install

TypeScript is the published SDK — React and Next.js bindings are subpath exports of the same package, not separate installs:

pnpm add @smooai/observability # core — plus /react, /next, /node, /otel, /metrics subpaths

Python, Rust, Go, and .NET are complete and CI-tested, but not yet on their registries (PyPI / crates.io / NuGet publishing is set up in publish.yml and lands with the first language tag). Until then, use them from source:

SDKSourceRegistry status
TypeScriptpackages/corenpm
Pythonpython/ (smooai_observability)unreleased — not yet on PyPI
Rustrust/observability (smooai-observability)unreleased — not yet on crates.io
Gogo get github.com/SmooAI/observability/go@mainno SemVer tag yet — @main resolves via the module proxy
.NETdotnet/ (SmooAI.Observability)unreleased — not yet on NuGet

🚀 Usage

Next.js

// next.config.tsimport{withSmooObservability}from'@smooai/observability/next/build';exportdefaultwithSmooObservability({/* your config */},{org: 'your-org',release: process.env.GITHUB_SHA??'dev',uploadSourcemaps: process.env.CI==='true',},);
// instrumentation.tsexportasyncfunctionregister(){const{ Client }=awaitimport('@smooai/observability');Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE,release: process.env.GITHUB_SHA??'dev',});}
// app/global-error.tsx'use client';import{RootErrorBoundary}from'@smooai/observability/next';exportdefaultfunctionGlobalError({ error, reset }: {error: Error&{digest?: string};reset: ()=>void}){return(<html><body><RootErrorBoundaryerror={error}resetError={reset}fallback={<YourBrandedErroronRetry={reset}/>}/></body></html>);}

Browser SPA

import{Client}from'@smooai/observability';Client.init({dsn: process.env.SMOO_OBSERVABILITY_DSN!,environment: 'production',release: import.meta.env.VITE_GIT_SHA,});Client.setUser({id: 'user_abc',orgId: 'org_xyz'});

React bindings live at the /react subpath — import { ErrorBoundary, useErrorHandler } from '@smooai/observability/react'.

Node / Hono

import{Client,observabilityMiddleware}from'@smooai/observability/node';Client.init({dsn: process.env.OBSERVABILITY_INGEST_URL!,environment: process.env.STAGE!,release: process.env.LAMBDA_FUNCTION_VERSION??'dev',});app.use('*',observabilityMiddleware());

Python / Rust / Go / .NET

Same shape, native idioms — each sub-README has the full walkthrough: python/ (FastAPI middleware, LangChain callback, crash hooks), rust/ (tower + reqwest middleware), go/ (net/http, Fiber, Gin), dotnet/ (ASP.NET Core middleware). A taste of Python:

fromsmooai_observabilityimportbootstrap_observability, capture_exceptionbootstrap_observability() # reads SMOOAI_OBSERVABILITY_* env vars (never raises)try:
risky()
exceptExceptionaserr:
capture_exception(err, tags={"area": "ingest"})

🤖 GenAI telemetry (gen_ai.*)

LLM and agent spans carry the OTel GenAI semantic conventions, so any semconv-aware backend reads them — Smoo's LLM dashboard routes on gen_ai.system alone.

importOpenAIfrom'openai';import{wrapOpenAI}from'@smooai/observability';// Instruments chat.completions.create — the original client is untouched.constopenai=wrapOpenAI(newOpenAI(),{conversationId: conversation.id,// Providers don't return a price. Supply one and the cost column fills in.costUsd: ({ inputTokens =0, outputTokens =0})=>inputTokens*2.5e-6+outputTokens*1e-5,});

The same wrapper covers Groq, Together, Fireworks, DeepSeek, Azure OpenAI, and any OpenAI-compatible gateway — pass { system: 'groq' } so spans attribute to the real provider. Prompt and completion content is off by default; { recordContent: true } records it as gen_ai.*.message span events, PII-scrubbed on the way out.

For hand-rolled calls, set the attributes directly:

import{setGenAIAttributes,recordGenAIMessage}from'@smooai/observability';setGenAIAttributes(span,{system: 'anthropic',operationName: 'chat',requestModel: 'claude-opus-4-7',usageInputTokens: 812,usageOutputTokens: 96});

gen_ai.operation.name is a straight passthrough on ingest with no fallback — leave it unset and the operation column lands NULL. Always set it.

Parity across the five SDKs:

SDKAttribute helperMessage eventsContent PII-scrubbedFramework integration
TypeScriptsetGenAIAttributesrecordGenAIMessagewrapOpenAI — OpenAI Node SDK + compatible APIs
Rustset_gen_ai_attributesrecord_gen_ai_message
Pythonset_gen_ai_attributesrecord_gen_ai_messageSmooAICallbackHandler — LangChain / LangGraph
GoSetGenAIAttributesRecordGenAIMessage
.NETGenAIActivity.SetAttributesGenAIActivity.RecordMessage

Known divergences: none in the attribute or event shape. All five emit gen_ai.tool.names as a string array and all five PII-scrub recorded message content. Two divergences that used to be listed here are closed: Rust emitted gen_ai.tool.names comma-joined (a tool name containing a comma silently became two tools, and a Rust service's spans could not be filtered by tool), and only TypeScript scrubbed message content (prompts and tool arguments are the most PII-dense payload the SDK touches). Each fix ships with a span-level test in its own language.

What still differs is only the framework glue — the wrapOpenAI and LangChain columns above — which is a matter of which ecosystems have an integration written, not of the wire contract.

📐 Cross-language parity, honestly

parity/sampling-corpus.json pins 170 vectors for the FNV-1a session sampler, level normalization, W3C traceparent parse/format, and settings resolution. All five SDKs implement it and all five CI lanes load that same file — a language that cannot reproduce a vector fails its build:

SDKLoader
TypeScriptpackages/core/src/__tests__/parity-corpus.test.ts
Rustrust/observability/tests/parity_corpus.rs
Pythonpython/tests/test_parity_corpus.py
Gogo/parity_corpus_test.go
.NETdotnet/tests/.../ParityCorpusTests.cs

parity/** is a path-filter trigger for every language lane, so touching the corpus re-runs all five.

The PII token — the [email:02ea437f] handle that replaces a personal identifier — has its own shared corpus, parity/pii-corpus.json, loaded by the same five lanes. It pins the HMAC message framing, the per-org salt, the per-kind normalization, and the no-key redaction fallback.

📖 Architecture

The SDK is intentionally thin. It captures, batches, redacts credentials, hashes personal identifiers, and POSTs to a Smoo ingest endpoint. All of the heavy lifting — fingerprint grouping, source-map symbolication, dashboards, alerts, retention — lives in the Smoo platform.

%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
SDKS["5 SDKs<br/>TS · Python · Rust · Go · .NET<br/>capture · scope · scrub · batch"]
SDKS -->|"errors: POST /webhooks/observability/{org}/{token}"| INGEST[("Smoo platform<br/>group · symbolicate · alert")]
SDKS -->|"traces + metrics: OTLP/HTTP<br/>M2M token auth"| INGEST
STUDIO["Observability Studio<br/>desktop (Dioxus)"] -->|"reads api.smoo.ai<br/>M2M client_credentials"| INGEST
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class SDKS warm
class INGEST,STUDIO teal
Loading

Full backend architecture: SmooAI/smooai → docs/Architecture/Observability-Architecture.md.

🖥️ Observability Studio (desktop)

desktop/ is a native desktop client for the whole stack — logs, errors, and metrics from api.smoo.ai, multi-org with credentials in your OS keychain, Cmd+K org/view switching. Built with Dioxus on the shared @smooai/ui design system. Unsigned bundles for macOS / Linux / Windows ship from the studio-v* GitHub Releases; or cargo run --release -p observability-studio-app from desktop/.

🗂️ Five SDKs, one contract

PathWhat it isTests / CI
packages/coreTypeScript reference SDK — browser + Node entries, /react · /next · /otel · /metrics · /bootstrap subpathsvitest, published via changesets
python/Python SDK — capture, crash hooks, OTel, GenAI, FastAPI + LangChain integrationspytest lane in pr-checks.yml
rust/Rust SDK (smooai-observability) — capture, OTel, GenAI, tower + reqwest middlewarecargo test + clippy lane
go/Go SDK — capture, OTel, GenAI, net/http + Fiber + Gin middlewarego test lane
dotnet/.NET SDK (SmooAI.Observability) — capture, OTel, GenAI, ASP.NET Core middlewaredotnet test lane
desktop/Observability Studio — Dioxus desktop clientfmt + clippy + test lane; build-desktop.yml bundles 3 OSes on a studio-v* tag
parity/Shared corpora — sampling/traceparent/settings and PII tokensboth loaded by all five language lanes

Every language runs typecheck/lint/format/test in its own pr-checks.yml lane on every PR that touches it.

📖 Built with

  • TypeScript — strict mode, ESM-only, dual browser/Node entries via package exports map; tsup, turborepo, vitest, changesets
  • Python 3uv-managed, pytest
  • Rust — cargo workspace (rust/ SDK, desktop/ Dioxus app), clippy -D warnings
  • Go — stdlib-first module with Fiber/Gin subpackages
  • .NET — single SmooAI.Observability project + xUnit tests

📖 Privacy & telemetry

This SDK is opinionated about privacy:

  • We never capture form bodies, request bodies, or response bodies by default
  • We never capture cookies
  • We never send anything to a third-party service — your events go to your Smoo backend only
  • PII scrubbing is enabled by default and can be tuned per-tenant. Personal identifiers are hashed with HMAC-SHA256 under a key you supply (SMOOAI_OBSERVABILITY_PII_HASH_KEY), salted by org id — identical across the TypeScript, Rust, Go, Python and .NET SDKs. With no key configured they are fully redacted, never hashed under a guessable one.

📖 Status

The TypeScript SDK is live on npm and in production across the Smoo platform. The Python, Rust, Go, and .NET SDKs are feature-complete and CI-tested in-repo but not yet published to PyPI / crates.io / NuGet — the publish workflow (publish.yml) is tag-triggered and no language tag has shipped yet. The desktop studio ships unsigned bundles from studio-v* releases. Backend ingest, fingerprint grouping, and dashboards live in the SmooAI/smooai monorepo under SMOODEV-1067.

🧩 Part of Smoo AI {#part-of-smoo-ai}

@smooai/observability is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Issues and PRs welcome. Maintained by Brent Rager — email · LinkedIn · BlueSky · TikTok · Instagram.

📄 License

MIT © Smoo AI, Inc. See LICENSE.

(back to top)


Built by Smoo AI — AI built into every product.

About

Sentry-like error tracking SDK for the Smoo AI platform — browser, Node, React, and Next.js. The open-core companion to the hosted error + metrics dashboards.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages