@neowhale/telemetry
v1.1.4
Published
Error tracking, analytics, and Web Vitals for WhaleTools stores
Maintainers
Readme
Zero dependencies. Works in the browser and on the server.
Install
npm install @neowhale/telemetryPeer dependencies: react >=18 (optional -- only needed for @neowhale/telemetry/react).
Quick Start
Browser (React)
import { WhaleTelemetry, WhaleErrorBoundary } from '@neowhale/telemetry/react'
export default function App() {
return (
<WhaleTelemetry apiKey="wk_live_..." storeId="your-store-uuid">
<WhaleErrorBoundary fallback={<p>Something went wrong.</p>}>
<YourApp />
</WhaleErrorBoundary>
</WhaleTelemetry>
)
}WhaleTelemetry calls init() on mount and destroy() on unmount. In the browser it automatically captures:
- Unhandled errors and promise rejections
- Page views (initial load and SPA navigation)
- Web Vitals (LCP, FID, CLS, FCP)
- Breadcrumbs (clicks, navigation, console warnings/errors, fetch)
Browser (Vanilla)
import { whaletools } from '@neowhale/telemetry'
whaletools.init({ apiKey: 'wk_live_...', storeId: 'your-store-uuid' })
whaletools.track('add_to_cart', { product_id: 'abc', price: 29.99 })
whaletools.captureError(new Error('Payment failed'))Server (Next.js App Router)
import { withTelemetry } from '@neowhale/telemetry/server'
const config = { apiKey: 'wk_live_...', storeId: 'your-store-uuid' }
export const GET = withTelemetry(async (req) => {
return Response.json({ ok: true })
}, config)Server (Manual Error Reporting)
import { reportServerError } from '@neowhale/telemetry/server'
const config = { apiKey: 'wk_live_...', storeId: 'your-store-uuid' }
try {
await riskyOperation()
} catch (err) {
await reportServerError(err as Error, config, { context: 'cron_job' })
}Server (AI/LLM Call Tracking)
import { reportAICall } from '@neowhale/telemetry/server'
const config = { apiKey: 'wk_live_...', storeId: 'your-store-uuid' }
const start = Date.now()
const response = await openai.chat.completions.create({ model: 'gpt-4o', messages })
await reportAICall({
model: 'gpt-4o',
provider: 'openai',
prompt_tokens: response.usage?.prompt_tokens,
completion_tokens: response.usage?.completion_tokens,
duration_ms: Date.now() - start,
status: 'ok',
}, config)Configuration
WhaleToolsConfig (browser)
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | API key (wk_live_... or wk_test_...) |
| storeId | string | required | Store UUID |
| endpoint | string | "https://whale-gateway.fly.dev" | Gateway URL |
| errors | boolean | true | Auto-capture unhandled errors |
| analytics | boolean | true | Auto-capture page views |
| vitals | boolean | true | Collect Web Vitals |
| breadcrumbs | boolean | true | Auto-collect breadcrumbs |
| environment | string | "production" | Environment tag |
| serviceName | string | "store_client" | Service name tag |
| serviceVersion | string | "" | Service version tag |
| flushInterval | number | 5000 | Flush interval in ms |
| flushThreshold | number | 10 | Max queued items before auto-flush |
| debug | boolean | false | Log telemetry to console |
| sampleRate | number | 1 | Analytics sample rate (0--1) |
| beforeSend | (error: ErrorPayload) => ErrorPayload \| false | -- | Mutate or drop errors before send |
ServerConfig (server)
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | API key |
| storeId | string | required | Store UUID |
| endpoint | string | "https://whale-gateway.fly.dev" | Gateway URL |
| serviceName | string | "store_server" | Service name tag |
| environment | string | "production" | Environment tag |
API Reference
Core -- @neowhale/telemetry
The default export provides a pre-initialized whaletools singleton of type WhaleToolsClient.
import { whaletools } from '@neowhale/telemetry'whaletools.init(config: WhaleToolsConfig): void
Initialize the client. Must be called once before other methods. In the browser, automatically installs error handlers, page view tracking, Web Vitals collection, and breadcrumb capture based on config flags.
whaletools.captureError(error: Error | string, extra?: Record<string, unknown>): void
Manually capture an error. Generates a SHA-256 fingerprint, attaches breadcrumbs, and queues for delivery.
whaletools.captureMessage(message: string, severity?: Severity, extra?: Record<string, unknown>): void
Capture a message as an error event. Severity is one of "debug" | "info" | "warning" | "error" | "fatal" (default "info").
whaletools.track(eventName: string, properties?: Record<string, unknown>): void
Track a custom analytics event. Automatically enriched with page_url and page_title. Subject to sampleRate.
whaletools.page(properties?: Record<string, unknown>): void
Track a page view. Enriched with page_url, page_path, and page_title. Called automatically on init and SPA navigation when analytics is enabled.
whaletools.identify(userId: string, traits?: Record<string, unknown>): void
Set user context. Traits such as email and name are attached to subsequent telemetry payloads.
whaletools.trackAICall(call: AICallPayload): void
Track an AI/LLM API call. Automatically calculates total_tokens from prompt_tokens + completion_tokens if omitted.
whaletools.addBreadcrumb(category: string, message: string, data?: Record<string, unknown>): void
Add a manual breadcrumb to the ring buffer (max 25).
whaletools.flush(): void
Force-flush all queued errors, events, vitals, and AI calls to the gateway.
whaletools.destroy(): void
Stop the flush timer, perform a final flush, and reset the initialized state.
addBreadcrumb(category, message, level?, data?) (standalone)
import { addBreadcrumb } from '@neowhale/telemetry'
addBreadcrumb('checkout', 'User entered payment info', 'info', { step: 3 })Add a breadcrumb directly to the global ring buffer without going through the client instance.
React -- @neowhale/telemetry/react
import { WhaleTelemetry, WhaleErrorBoundary } from '@neowhale/telemetry/react'<WhaleTelemetry {...config} />
Provider component. Accepts all WhaleToolsConfig props plus children. Calls whaletools.init() on mount and whaletools.destroy() on unmount.
<WhaleErrorBoundary />
React error boundary that reports caught render errors to WhaleTools.
| Prop | Type | Description |
|---|---|---|
| children | ReactNode | Child components to wrap |
| fallback | ReactNode \| (error: Error) => ReactNode | Fallback UI on error |
| onError | (error: Error, errorInfo: { componentStack: string }) => void | Callback on error |
Server -- @neowhale/telemetry/server
import { withTelemetry, reportServerError, reportAICall, reportAICallBatch } from '@neowhale/telemetry/server'withTelemetry(handler, config): handler
Wraps a route handler (Next.js App Router, etc.) with automatic error capture. Catches unhandled errors, reports them to the gateway, and re-throws so the framework can handle the response.
reportServerError(error: Error, config: ServerConfig, extra?: Record<string, unknown>): Promise<void>
Manually report a server-side error. Use in catch blocks where withTelemetry is not applicable.
reportAICall(call: AICallReport, config: ServerConfig): Promise<void>
Report a single AI/LLM API call. total_tokens is auto-calculated from prompt_tokens + completion_tokens if omitted.
reportAICallBatch(calls: AICallReport[], config: ServerConfig): Promise<void>
Report multiple AI/LLM calls in a single request.
Types
ErrorPayload
interface ErrorPayload {
error_type: string
error_message: string
stack_trace: string
fingerprint: string
severity: 'debug' | 'info' | 'warning' | 'error' | 'fatal'
source_file: string
source_line: number
source_function: string
tags: Record<string, string>
extra: Record<string, unknown>
breadcrumbs: Breadcrumb[]
occurred_at: string
platform: string
}Breadcrumb
interface Breadcrumb {
timestamp: string
category: string
message: string
level: 'debug' | 'info' | 'warning' | 'error'
data?: Record<string, unknown>
}AnalyticsEvent
interface AnalyticsEvent {
event_name: string
properties: Record<string, unknown>
timestamp: string
}WebVital
interface WebVital {
name: 'CLS' | 'FID' | 'LCP' | 'INP' | 'TTFB' | 'FCP'
value: number
rating: 'good' | 'needs-improvement' | 'poor'
timestamp: string
}AICallPayload
interface AICallPayload {
model: string // required
provider?: string
operation?: string
prompt_tokens?: number
completion_tokens?: number
total_tokens?: number
cost?: number
duration_ms?: number
status?: 'ok' | 'error'
error_message?: string
http_status?: number
agent_id?: string
conversation_id?: string
tool_name?: string
parent_span_id?: string
attributes?: Record<string, unknown>
timestamp?: string
}Internals
Transport -- Telemetry is batched and sent via HTTP POST to /v1/stores/{storeId}/telemetry/ingest. On page unload, the client falls back to navigator.sendBeacon to avoid data loss.
Sessions -- visitor_id is persisted in localStorage (wt_vid). session_id (wt_sid) resets after 30 minutes of inactivity.
Fingerprinting -- Errors are deduplicated using a SHA-256 hash of type|message|source. Falls back to djb2 when SubtleCrypto is unavailable.
Breadcrumbs -- Automatically collected categories: ui.click, navigation, console.warn, console.error, fetch. Stored in a ring buffer (max 25 entries) and attached to error payloads.
Web Vitals -- LCP, FID, CLS, and FCP are measured via PerformanceObserver. Ratings follow web.dev thresholds.
License
MIT
