Uqudo UIregistry

Logger & OpenTelemetry

Structured logging, error capture and OpenTelemetry tracing for a Next.js app, in one registry item.

Drop-in observability. Once installed, server errors, client errors, unhandled rejections and console output are captured as structured JSON, correlated with an OpenTelemetry trace, and written to stdout for the platform collector to pick up.

Unlike the other items in this registry this one is a kit: it installs a library, a provider, both instrumentation hooks and an API route.

Installation

npx shadcn@latest add @uqudo/logger

instrumentation.ts, instrumentation-client.ts and app/api/telemetry/route.ts are written at the project root, not under components/. If your project already has any of them, back them up before installing and merge afterwards.

Usage

import { logger } from "@/lib/observability"

Wrap your root layout once. Global capture is installed before hydration by instrumentation-client.ts; the provider adds the things that need React — identity, Web Vitals and an error boundary.

app/layout.tsx
import { ObservabilityProvider } from "@/components/uqudo/observability-provider"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <ObservabilityProvider>{children}</ObservabilityProvider>
      </body>
    </html>
  )
}

Then log from anywhere — server components, route handlers, client components:

logger.info("Checkout completed", { orderId, amount })
logger.error(error, { event: "checkout-failed", orderId })

// Bind context once and reuse it
const scoped = logger.child({ tenant: "acme" })
scoped.warn("Rate limit approaching", { remaining: 12 })

Configuration

Set these on the deployment. The defaults are safe: logs already reach the backend over stdout, so OTLP log and trace export stay off until the platform has those pipelines.

Prop

Type

Browser events are batched and relayed through POST /api/telemetry, which the item installs for you.

API Reference

logger

import { logger } from "@/lib/observability"

Prop

Type

LogOptions

Anything extra you pass becomes a structured attribute, alongside these recognised fields.

Prop

Type

ObservabilityProvider

Prop

Type

useObservability

Imperative access from client components. Throws outside the provider.

"use client"

import { useObservability } from "@/components/uqudo/observability-provider"

export function SaveButton() {
  const { captureException, identify, flush } = useObservability()
  // ...
}

Prop

Type

ObservabilityErrorReporter

Drop into error.tsx or global-error.tsx to report an error that Next.js has already caught and rendered a fallback for. Reports are deduplicated by the error digest, so Strict Mode does not double-report.

app/global-error.tsx
"use client"

import { ObservabilityErrorReporter } from "@/components/uqudo/observability-provider"

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <ObservabilityErrorReporter error={error} source="global-error" />
        <button onClick={reset}>Try again</button>
      </body>
    </html>
  )
}

Prop

Type

Tracing helpers

Prop

Type

redact, redactHeaders, serializeError, getTracer, addSpanEvent and recordSpanException are exported from the same entry point.

Entry points

lib/observability/index.ts is the full export surface — safe to import from server components, client components, route handlers and the Edge runtime. instrumentation.ts is where the Node-only OpenTelemetry SDK boots.

lib/observability/index.ts
/**
 * Public surface of the observability kit.
 *
 * Safe to import from server components, client components, route handlers
 * and the Edge runtime. Node-only OpenTelemetry setup lives in
 * `./otel-node`, which `instrumentation.ts` imports directly.
 */

export {
  logger,
  captureException,
  addLogSink,
  setGlobalAttributes,
  setUser,
  setBrowserContext,
  getBrowserContext,
  setTraceContextProvider,
  setBrowserSink,
  nativeConsole,
  type Logger,
  type LogOptions,
} from "./logger"

export {
  getClientConfig,
  getServerConfig,
  overrideClientConfig,
  currentRuntime,
  type ClientConfig,
  type ServerConfig,
  type OtlpProtocol,
} from "./config"

export {
  installConsolePatch,
  installConsolePatchIfEnabled,
  isConsolePatched,
} from "./console-patch"

export {
  installClientTransport,
  isTransportInstalled,
  flush as flushClientTelemetry,
} from "./client-transport"

export {
  getTracer,
  getTraceId,
  getSpanId,
  setSpanAttributes,
  addSpanEvent,
  recordSpanException,
  withSpan,
  getPropagationHeaders,
} from "./tracing"

export {
  initPrometheus,
  recordHttpDuration,
  recordAppError,
  renderPrometheusMetrics,
  isScrapeAuthorised,
  getPrometheusRegistry,
} from "./prometheus"
export { startPrometheusOtlpBridge } from "./prometheus-bridge"
export { redact, redactHeaders, redactString, redactUrl, REDACTED } from "./redact"
export { serializeError, safeStringify, flattenAttributes } from "./serialize"

export {
  LOG_LEVELS,
  SEVERITY_NUMBER,
  isLogLevel,
  type BrowserContext,
  type HttpContext,
  type LogAttributes,
  type LogLevel,
  type LogRecord,
  type LogSink,
  type RuntimeName,
  type SerializedError,
  type ServiceContext,
  type TelemetryBatch,
  type TelemetryEvent,
  type TraceContext,
} from "./types"

Reference

The kit ships its own reference at lib/observability/README.md, covering the record schema, the redaction rules and the collector contract.

On this page