Skip to main content
The @packages/observability package provides comprehensive observability tools for Answer Overflow, including error tracking with Sentry, telemetry with Axiom, and OpenTelemetry integration.

Installation

npm install @packages/observability

Overview

This package provides:
  • Sentry integration for error tracking
  • Axiom integration for logs, metrics, and traces
  • OpenTelemetry for distributed tracing
  • Effect integration for functional error handling
  • Convex-specific observability utilities

Sentry Error Tracking

Initialization

import { initSentry } from "@packages/observability/sentry";

initSentry({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV || "development",
  release: process.env.VERCEL_GIT_COMMIT_SHA,
  sampleRate: 0.25,
  tracesSampleRate: 0.1,
  profilesSampleRate: 0.1,
});
SentryConfig
object
Sentry configuration
dsn
string | undefined
required
Sentry DSN (Data Source Name)
environment
string
required
Environment name (e.g., “production”, “development”)
release
string
Release version or commit SHA
sampleRate
number
Error sampling rate (0-1, default: 0.25)
tracesSampleRate
number
Traces sampling rate (0-1, default: 0.1)
profilesSampleRate
number
Profiles sampling rate (0-1, default: 0.1)

Capturing Exceptions

import { captureException } from "@packages/observability/sentry";

try {
  // Risky operation
  await processUserData(userId);
} catch (error) {
  captureException(error, {
    tags: {
      module: "user-processing",
      userId,
    },
    extra: {
      userData: { id: userId },
    },
    user: {
      id: userId,
      username: "john_doe",
    },
  });
}
captureException
function
Capture an exception to Sentry
error
unknown
required
The error to capture
context.tags
Record<string, string>
Tags for filtering/grouping errors
context.extra
Record<string, unknown>
Additional context data
context.user
object
User information

Capturing Messages

import { captureMessage } from "@packages/observability/sentry";

captureMessage("User completed onboarding", "info");
captureMessage("Rate limit exceeded", "warning");
captureMessage("Critical system failure", "error");

User Context

import { setUser, setContext, setTag } from "@packages/observability/sentry";

// Set user context
setUser({
  id: "user_123",
  username: "john_doe",
});

// Clear user context
setUser(null);

// Set custom context
setContext("server", {
  serverId: "123456789",
  serverName: "My Server",
});

// Set tags
setTag("feature", "chat");

Effect Integration

import {
  tapErrorCauseToSentry,
  captureEffectCause,
} from "@packages/observability/sentry-effect";
import { Effect } from "effect";

const riskyOperation = Effect.gen(function* () {
  // Operation that might fail
}).pipe(
  tapErrorCauseToSentry({
    tags: { operation: "risky" },
  })
);

// Or manually capture
const result = await Effect.runPromise(
  riskyOperation.pipe(
    Effect.catchAllCause((cause) => {
      captureEffectCause(cause, {
        tags: { severity: "high" },
      });
      return Effect.fail(cause);
    })
  )
);

Sentry Layer (Effect)

import { createSentryLayer } from "@packages/observability/sentry";
import { Layer } from "effect";

const SentryLayer = createSentryLayer({
  dsn: process.env.SENTRY_DSN,
  environment: "production",
});

const program = Effect.gen(function* () {
  // Your program
}).pipe(Effect.provide(SentryLayer));

Axiom Telemetry

Initialization

import { createAxiomLayer } from "@packages/observability/axiom";
import { Effect, Layer } from "effect";
import * as Duration from "effect/Duration";

const AxiomLayer = createAxiomLayer({
  apiToken: process.env.AXIOM_API_TOKEN!,
  tracesDataset: "traces",
  logsDataset: "logs",
  metricsDataset: "metrics",
  serviceName: "answer-overflow",
  serviceVersion: "1.0.0",
  environment: "production",
  tracesExportInterval: Duration.seconds(5),
  logsExportInterval: Duration.seconds(1),
  metricsExportInterval: Duration.seconds(10),
});
AxiomConfig
object
Axiom configuration
apiToken
string
required
Axiom API token
tracesDataset
string
required
Dataset name for traces
logsDataset
string
required
Dataset name for logs
metricsDataset
string
required
Dataset name for metrics
serviceName
string
required
Service name for telemetry
serviceVersion
string
Service version
environment
string
Environment (default: “production”)
domain
string
Axiom domain (default: “api.axiom.co”)
tracesExportInterval
Duration
How often to export traces
logsExportInterval
Duration
How often to export logs
metricsExportInterval
Duration
How often to export metrics

Using Axiom Layer

import { Effect } from "effect";

const program = Effect.gen(function* () {
  yield* Effect.log("Starting operation");
  
  // Your application logic
  const result = yield* processData();
  
  yield* Effect.log("Operation completed");
  
  return result;
}).pipe(Effect.provide(AxiomLayer));

await Effect.runPromise(program);

OpenTelemetry

Effect OpenTelemetry

import { createOtelLayer } from "@packages/observability/effect-otel";
import * as Duration from "effect/Duration";

const OtelLayer = createOtelLayer({
  url: "https://api.axiom.co/v1/traces",
  headers: {
    Authorization: `Bearer ${process.env.AXIOM_API_TOKEN}`,
    "X-Axiom-Dataset": "traces",
  },
  serviceName: "answer-overflow",
  exportInterval: Duration.seconds(5),
});

const program = Effect.gen(function* () {
  // Automatically traced
  yield* Effect.log("Processing request");
  const result = yield* fetchData();
  return result;
}).pipe(Effect.provide(OtelLayer));

Convex OpenTelemetry

For Convex-specific telemetry:
import { createConvexOtelLayer } from "@packages/observability/convex-effect-otel";

const ConvexOtelLayer = createConvexOtelLayer({
  endpoint: process.env.OTEL_ENDPOINT!,
  headers: {
    Authorization: `Bearer ${process.env.OTEL_TOKEN}`,
  },
});

Complete Setup Example

import { initSentry, createSentryLayer } from "@packages/observability/sentry";
import { createAxiomLayer } from "@packages/observability/axiom";
import { Effect, Layer } from "effect";

// Initialize Sentry (non-Effect code)
initSentry({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV!,
});

// Create Effect layers
const SentryLayer = createSentryLayer({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV!,
});

const AxiomLayer = createAxiomLayer({
  apiToken: process.env.AXIOM_API_TOKEN!,
  tracesDataset: "traces",
  logsDataset: "logs",
  metricsDataset: "metrics",
  serviceName: "answer-overflow",
  environment: process.env.NODE_ENV!,
});

// Combine layers
const ObservabilityLayer = Layer.mergeAll(
  SentryLayer,
  AxiomLayer
);

// Use in your application
const app = Effect.gen(function* () {
  yield* Effect.log("Application started");
  
  try {
    yield* runServer();
  } catch (error) {
    yield* Effect.logError("Server error", error);
  }
}).pipe(Effect.provide(ObservabilityLayer));

await Effect.runPromise(app);

Best Practices

  1. Initialize early: Set up observability at app startup
  2. Use structured logging: Include context in logs
  3. Tag appropriately: Use tags for filtering and grouping
  4. Sample strategically: Adjust sample rates for production
  5. Capture user context: Add user info to error reports
  6. Flush on shutdown: Call flush() before process exits

Exports

./sentry
module
Sentry error tracking utilities
./sentry-effect
module
Sentry integration for Effect
./axiom
module
Axiom telemetry integration
./effect-otel
module
Effect OpenTelemetry integration
./convex-effect-otel
module
Convex-specific OpenTelemetry

Dependencies

  • @sentry/node - Sentry SDK for Node.js
  • @sentry/opentelemetry - Sentry OpenTelemetry integration
  • @effect/opentelemetry - Effect OpenTelemetry integration
  • @opentelemetry/sdk-node - OpenTelemetry SDK
  • effect - Effect library