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/observabilityfills 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.
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.
| Capability | What you get | |
|---|---|---|
| 🛑 | Error capture | Uncaught exceptions + crash handlers in all five languages |
| 🍞 | Breadcrumbs + scope | Request-scoped user, tags, and a trail of what led to the error |
| 🔐 | PII scrub | Credentials dropped; emails/phones HMAC-hashed per-org — all five SDKs |
| 🔭 | OTel traces + metrics | OTLP/HTTP export with M2M token auth — all five SDKs |
| 🤖 | GenAI telemetry | gen_ai.* semconv helpers everywhere; wrapOpenAI + LangChain integrations |
| 🧱 | React / Next.js | <ErrorBoundary>, useErrorHandler, source-map upload — TypeScript only |
| 🖥️ | Desktop studio | Native logs/errors/metrics client, multi-org, keychain-stored creds |
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:
| TypeScript | Python | Rust | Go | .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 middleware | Hono | FastAPI / Starlette | tower · reqwest | net/http · Fiber · Gin | ASP.NET Core |
| LLM client instrumentation | wrapOpenAI |
LangChain / LangGraph callback | — | — | — |
| Log/session sampling (FNV-1a parity corpus) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Source-map upload | ✅ | n/a | n/a | n/a | n/a |
| React / Next.js bindings | ✅ | n/a | n/a | n/a | n/a |
| Browser: beacon flush + IndexedDB offline queue | ✅ | n/a | n/a | n/a | n/a |
| Published | npm | release-ready, not yet pushed | release-ready, not yet pushed | release-ready, not yet pushed | release-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.
console.log/console.info/console.warn— onlyconsole.erroris 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
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 subpathsPython, 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:
| SDK | Source | Registry status |
|---|---|---|
| TypeScript | packages/core |
|
| Python | python/ (smooai_observability) |
unreleased — not yet on PyPI |
| Rust | rust/observability (smooai-observability) |
unreleased — not yet on crates.io |
| Go | go get github.com/SmooAI/observability/go@main |
no SemVer tag yet — @main resolves via the module proxy |
| .NET | dotnet/ (SmooAI.Observability) |
unreleased — not yet on NuGet |
// next.config.ts
import { withSmooObservability } from '@smooai/observability/next/build';
export default withSmooObservability(
{
/* your config */
},
{
org: 'your-org',
release: process.env.GITHUB_SHA ?? 'dev',
uploadSourcemaps: process.env.CI === 'true',
},
);// instrumentation.ts
export async function register() {
const { Client } = await import('@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';
export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
return (
<html>
<body>
<RootErrorBoundary error={error} resetError={reset} fallback={<YourBrandedError onRetry={reset} />} />
</body>
</html>
);
}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'.
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());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:
from smooai_observability import bootstrap_observability, capture_exception
bootstrap_observability() # reads SMOOAI_OBSERVABILITY_* env vars (never raises)
try:
risky()
except Exception as err:
capture_exception(err, tags={"area": "ingest"})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.
import OpenAI from 'openai';
import { wrapOpenAI } from '@smooai/observability';
// Instruments chat.completions.create — the original client is untouched.
const openai = wrapOpenAI(new OpenAI(), {
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.nameis a straight passthrough on ingest with no fallback — leave it unset and the operation column landsNULL. Always set it.
Parity across the five SDKs:
| SDK | Attribute helper | Message events | Content PII-scrubbed | Framework integration |
|---|---|---|---|---|
| TypeScript | setGenAIAttributes |
recordGenAIMessage |
✅ | ✅ wrapOpenAI — OpenAI Node SDK + compatible APIs |
| Rust | set_gen_ai_attributes |
record_gen_ai_message |
✅ | — |
| Python | set_gen_ai_attributes |
record_gen_ai_message |
✅ | ✅ SmooAICallbackHandler — LangChain / LangGraph |
| Go | SetGenAIAttributes |
RecordGenAIMessage |
✅ | — |
| .NET | GenAIActivity.SetAttributes |
GenAIActivity.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.
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:
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.
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
Full backend architecture: SmooAI/smooai → docs/Architecture/Observability-Architecture.md.
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/.
| Path | What it is | Tests / CI |
|---|---|---|
packages/core |
TypeScript reference SDK — browser + Node entries, /react · /next · /otel · /metrics · /bootstrap subpaths |
vitest, published via changesets |
python/ |
Python SDK — capture, crash hooks, OTel, GenAI, FastAPI + LangChain integrations | pytest lane in pr-checks.yml |
rust/ |
Rust SDK (smooai-observability) — capture, OTel, GenAI, tower + reqwest middleware |
cargo test + clippy lane |
go/ |
Go SDK — capture, OTel, GenAI, net/http + Fiber + Gin middleware | go test lane |
dotnet/ |
.NET SDK (SmooAI.Observability) — capture, OTel, GenAI, ASP.NET Core middleware |
dotnet test lane |
desktop/ |
Observability Studio — Dioxus desktop client | fmt + clippy + test lane; build-desktop.yml bundles 3 OSes on a studio-v* tag |
parity/ |
Shared corpora — sampling/traceparent/settings and PII tokens | both 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.
- TypeScript — strict mode, ESM-only, dual browser/Node entries via package
exportsmap; tsup, turborepo, vitest, changesets - Python 3 —
uv-managed, pytest - Rust — cargo workspace (
rust/SDK,desktop/Dioxus app), clippy-D warnings - Go — stdlib-first module with Fiber/Gin subpackages
- .NET — single
SmooAI.Observabilityproject + xUnit tests
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.
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.
@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.
- 🚀 Observability on the platform — smoo.ai/platform/observability
- 🧰 More open source from Smoo AI — smoo.ai/open-source
- 🧩 Sibling packages — @smooai/logger, @smooai/config, @smooai/fetch, smooth (the
thCLI)
Issues and PRs welcome. Maintained by Brent Rager — email · LinkedIn · BlueSky · TikTok · Instagram.
MIT © Smoo AI, Inc. See LICENSE.
Built by Smoo AI — AI built into every product.
