@devfellowship/observability
Browser observability SDK for React apps. Provides OpenTelemetry tracing, metrics, Sentry error tracking, Web Vitals, fetch instrumentation, error boundaries, and React Query error reporting — all via a single provider wrapper.
Install
npm install @devfellowship/observability
Peer dependencies
react>= 18@tanstack/react-query>= 5 (optional — enables automatic query error reporting)
Quick start
Wrap your app with <ObservabilityProvider>:
import { ObservabilityProvider } from "@devfellowship/observability";
function App() {
return (
<ObservabilityProvider>
<YourApp />
</ObservabilityProvider>
);
}
With zero props, the provider reads configuration from Vite environment variables:
| Env var | Purpose |
|---|---|
VITE_APP_NAME | Service name for traces/metrics |
VITE_APP_VERSION | Service version |
VITE_OTEL_API_KEY | API key sent as x-api-key header |
VITE_SENTRY_DSN | Sentry DSN (enables error tracking. Session replay is OFF.) |
VITE_GIT_SHA | Git SHA of the build. Becomes the Sentry release. Falls back to VITE_COMMIT_SHA, then <app>@<version>. |
VITE_OTEL_ENABLED | Set to "true" to enable (auto-enabled in production) |
Explicit configuration
All settings can be passed as props, overriding env vars:
<ObservabilityProvider
serviceName="my-app"
serviceVersion="1.2.0"
collectorUrl="https://otel.devfellowship.com"
apiKey="your-api-key"
sentryDsn="https://[email protected]/yyy"
environment="production"
enabled={true}
enableWebVitals={true}
enableQueryTracking={true}
enableFetchTracing={true}
>
<App />
</ObservabilityProvider>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
serviceName | string | VITE_APP_NAME or "unknown" | Service name for OTel resource |
serviceVersion | string | VITE_APP_VERSION or "0.0.0" | Service version |
collectorUrl | string | "https://otel.devfellowship.com" | OTel collector base URL |
apiKey | string | VITE_OTEL_API_KEY | API key header value |
sentryDsn | string | VITE_SENTRY_DSN | Sentry DSN (omit to disable Sentry) |
environment | string | "production" / "development" | Environment label |
enabled | boolean | true in production | Master kill-switch |
enableWebVitals | boolean | true | Capture LCP, CLS, INP, TTFB |
enableQueryTracking | boolean | true | Report React Query errors as spans |
enableFetchTracing | boolean | true | Auto-trace all fetch() calls |
Utilities
Track custom events
import { trackEvent } from "@devfellowship/observability";
trackEvent("button_click", { "button.id": "checkout", page: "/cart" });
Set user context
import { setUserId, setUserRole } from "@devfellowship/observability";
// After authentication
setUserId(user.id);
setUserRole(user.role);
Sentry helpers
import { captureError, setSentryUser } from "@devfellowship/observability";
captureError(new Error("something broke"), { component: "Checkout" });
setSentryUser(user.id, user.role);
Error boundary
import { ErrorBoundary } from "@devfellowship/observability";
<ErrorBoundary fallback={<p>Something went wrong</p>}>
<RiskyComponent />
</ErrorBoundary>
What's included
- OTel traces —
BatchSpanProcessor→ OTLP/HTTP exporter - OTel metrics —
PeriodicExportingMetricReader→ OTLP/HTTP exporter - Fetch instrumentation — auto-creates spans for all
fetch()calls (collector URLs filtered) - Web Vitals — LCP, CLS, INP, TTFB reported as OTel histogram metrics
- Sentry — error tracking and browser tracing, with scrubbing (see Privacy). No session replay.
- Error boundary — catches React component errors, reports to both OTel and Sentry
- React Query integration — subscribes to query cache, creates error spans on failures
- Resource attributes — browser, OS, device type, session ID, viewport, connection type, route
Privacy (Sentry)
Since 2.2.0 the Sentry client is built to leak no personal data (plan 20261101-fleet-observability-error-loop, T1):
- No session replay. No replay integration, and both replay sample rates are 0. Do not add it back without a new privacy decision.
- Anonymous user. The Sentry user is a random id per browser (
localStoragekeydfl_obs_anon_id).setSentryUser(id, role)keeps the signature but ignoresid: no account id, email or name is sent. - Scrubbing.
beforeSend,beforeSendTransactionandbeforeBreadcrumbremove emails, URL query strings and fragments, JWTs, bearer tokens, key=value secrets and IPv4 addresses from messages, request data, headers, breadcrumbs and span data. Cookies,Authorizationand similar headers are dropped. Seesrc/lib/scrub.ts. - Fail closed, never throw. The three hooks use
scrubEventOrDropandscrubBreadcrumbOrDrop. A getter-only property (seen 2026-11-04) is read and written safely. When a value needs scrubbing and its property cannot take the new value, the event or crumb is dropped, aconsole.warnnames the key (never the value), and nothing is sent half scrubbed. A hook that threw was reported by the SDK as a new error and lost the transaction. - IP address.
sendDefaultPiiisfalse; with it the SDK setsinfer_ip: "never". There is noscrubIPAddressesSDK option in@sentry/react10.x. "Prevent Storing of IP Addresses" is a Sentry project setting and must be on in the project (dfl-react). The SDK never sends an IP, and a test checks it. - Release. Set
VITE_GIT_SHA(or thereleaseprop) at build time, so a release changes at each deploy.
Tests: npm test (unit, with fixtures). Real-browser proof of "no replay envelope": PLAYWRIGHT_PATH=<path to playwright/index.mjs> npm run proof:browser.
Development
npm install
npm run build # builds with tsup (ESM + .d.ts)
Publishing
npm version patch # or minor/major
npm publish
License
MIT