LuxoAILuxoAIdocs
Welcome
InvoiceAgentInternalSupplyAgentBetaLearnAgentBeta
OverviewAgent Corev0.11.0StableObservabilityv0.6.0StableLuxo Assistantv0.6.0BetaFeedback UIv0.2.0BetaLuxo MLScaffold
API Console
All systemsv0.8.1
LuxoAILuxoAIdocs
WelcomeApiObservability

@luxoai-dev/observability · API Reference

Observability.

Stablev0.6.0

The shared signal layer for every Luxo service. Health + readiness contracts, Prometheus metrics with sensible buckets, redacted JSON logging, LLM telemetry tied to gateway events, feedback recording, monitoring-token-protected routes, service metric snapshots, and Next.js / Node convenience exports.

At a glance
Package
@luxoai-dev/observability
Version
v0.6.0
Modules
. · /next · /node
Default registry
defaultMetrics: MetricsRegistry
Pairs with
agent-core /llm onEvent

Core module

Imported from @luxoai-dev/observability. Service identity, health payloads, logging, metrics primitives, and LLM telemetry helpers.

import { ... } from "@luxoai-dev/observability";
initObservabilitygetObservabilityConfigbuildHealthPayloadcreateHealthResponsecreateLoggerMetricsRegistrydefaultMetricscreateLLMCallTrackercreateLLMGatewayTelemetryHandlerrecordLLMStartrecordLLMEndrecordLLMCallrecordFeedbackrecordLuxoActionObservabilityConfigLoggerLLMCallRecord
Function

initObservability

Stable
function initObservability(
  config: Partial<ObservabilityConfig>,
): ObservabilityConfig
Set the process-wide observability identity (service, version, environment, node, OTLP endpoint). Idempotent — call once at boot. Subsequent reads via getObservabilityConfig() see the merged result.
Parameters
ParamTypeRequiredDescription
config.serviceNamestringOptionalLogical service name reported on every log/metric label.
config.versionstringOptionalBuild version. Defaults from APP_VERSION env.
config.environmentstringOptionalproduction | staging | dev. Defaults from NODE_ENV.
config.componentstringOptionalProcess component (api, worker, scheduler...).
config.nodeNamestringOptionalHostname or pod name.
config.otlpEndpointstringOptionalOTLP collector URL for off-process forwarding.

Returns

ObservabilityConfig

example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
// app/instrumentation.ts (Next.js) or server.ts entry
import { initObservability } from "@luxoai-dev/observability";

initObservability({
  serviceName: "support-assistant",
  version: process.env.APP_VERSION ?? "local",
  environment: process.env.NODE_ENV,
  component: "api",
  nodeName: process.env.HOSTNAME,
});
Function

buildHealthPayload

Stable
function buildHealthPayload(
  options?: HealthRouteOptions,
): Promise<HealthPayload>
Run all health checks in parallel, collect results, and return the canonical payload. Status is 'down' if any check throws or returns ok: false.
Parameters
ParamTypeRequiredDescription
options.checksRecord<string, HealthCheck>OptionalNamed checks. Each is async; throw or return { ok: false, details } to fail.
options.servicestringOptionalOverride the configured service name.
options.versionstringOptionalOverride the configured version.

Returns

HealthPayload

example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
const payload = await buildHealthPayload({
  service: "support-assistant",
  checks: {
    db: async () => ({ ok: await pingDb() }),
    upstreamLLM: async () => ({ ok: await pingProvider() }),
  },
});

response
  .status(payload.status === "ok" ? 200 : 503)
  .json(payload);
Function

createHealthResponse

Stable
function createHealthResponse(
  options?: HealthRouteOptions,
): Promise<Response>
Same as buildHealthPayload but wraps the result in a Web Response with the right status code and content-type. The Next.js helpers in /next call this internally.
Function

createLogger

Stable
function createLogger(
  baseContext?: LogContext,
  options?: LoggerOptions,
): Logger
Structured JSON logger that auto-injects service/version/env from observability config. Redacts a default set of sensitive keys (authorization, cookie, password, secret, ...) plus any keys you add. Since v0.6.0 redact keys are matched case-insensitively in a consistent way (e.g. 'bearerToken' matches 'bearertoken', 'BEARERTOKEN'); exact-match semantics are preserved, so keys that only contain a sensitive substring (e.g. 'secret_question', 'token_expiry') are still kept.
Parameters
ParamTypeRequiredDescription
baseContextLogContextOptionalDefault fields merged into every log entry (e.g. { component: 'gateway' }).
options.minLevel'debug'|'info'|'warn'|'error'OptionalLowest level to emit. Defaults to 'info'.
options.redactKeysstring[]OptionalAdditional keys to redact in nested objects.
options.sink(entry) => voidOptionalReplace console.log with a custom sink (file, ndjson stream, etc.).

Returns

Logger

Object with debug/info/warn/error and child(context) for context inheritance.
example.tsTypeScriptExample
1
2
3
4
5
const log = createLogger({ component: "support-gateway" });

log.info("triage.requested", { ticketId: "TKT-3042", actor: actor.userId });
// {"timestamp":"...","level":"info","event":"triage.requested",
//  "service.name":"support-assistant","component":"support-gateway",...}
Class

MetricsRegistry

Stable
class MetricsRegistry {
  counter(name: string, help: string, baseLabels?: MetricLabels): Counter;
  gauge(name: string, help: string, baseLabels?: MetricLabels): Gauge;
  histogram(name: string, help: string, buckets?: number[], baseLabels?: MetricLabels): Histogram;
  renderPrometheus(): string;
}
In-process Prometheus-text metrics. The package exposes defaultMetrics (a singleton) — use that unless you have a reason to isolate registries (e.g. a worker pool with per-worker metrics).
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
12
const requests = defaultMetrics.counter(
  "support_triage_requests_total",
  "Triage requests by outcome.",
);
requests.inc({ outcome: "approved" });

const latency = defaultMetrics.histogram(
  "support_triage_latency_seconds",
  "Triage latency in seconds.",
  [0.1, 0.25, 0.5, 1, 2, 5, 10],
);
latency.observe(elapsed / 1000, { provider: "openai" });
Constant

defaultMetrics

Stable
const defaultMetrics: MetricsRegistry
Process-wide registry. The /next createMetricsRoute() and the LLM telemetry helpers default to this. You can pass a custom registry to any helper that accepts options.metrics.
Function

createLLMGatewayTelemetryHandler

Stable
function createLLMGatewayTelemetryHandler(
  identity?: LLMTelemetryIdentity,
  options?: LLMTelemetryOptions,
): (event: LLMGatewayTelemetryEvent) => void
Drop-in handler for the gateway’s onEvent hook. Increments the right counters, observes latency histograms, manages an in-flight gauge, and writes structured logs — all keyed by feature / provider / model / outcome. Since v0.6.0 the handler also passes errorMessage, toolsRequested, and toolsUsed through to the log entry — useful for auditing tool-using calls and the actual upstream error string (not just the type).
Parameters
ParamTypeRequiredDescription
identity.serviceNamestringOptionalOverride service label per handler.
identity.environmentstringOptionalOverride environment label.
identity.componentstringOptionalOverride component label.
identity.nodeNamestringOptionalOverride node label.
options.metricsMetricsRegistryOptionalCustom registry. Defaults to defaultMetrics.
options.loggerLoggerOptionalCustom logger. Defaults to a child of createLogger().

Returns

(event) => void

Pass directly to createLuxoGateway({ onEvent: ... }).
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
12
import { createLLMGatewayTelemetryHandler } from "@luxoai-dev/observability";

const onEvent = createLLMGatewayTelemetryHandler({
  serviceName: "support-assistant",
  component: "gateway",
});

const gateway = createLuxoGateway({
  adapters,
  routes,
  onEvent,
});
Function

createLLMCallTracker

Stable
function createLLMCallTracker(
  input: LLMCallStart,
  options?: LLMTelemetryOptions,
): (record: Omit<LLMCallRecord, keyof LLMCallLabels>) => void
Lower-level helper when you call providers directly (without the gateway). Emits the start gauge increment immediately and returns a 'finish' function — call it exactly once on success or failure.
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const finish = createLLMCallTracker({
  serviceName: "support-assistant",
  feature: "support.triage",
  provider: "openai",
  model: "gpt-4.1-mini",
  requestId: crypto.randomUUID(),
});

try {
  const start = Date.now();
  const result = await callProviderDirectly();
  finish({ outcome: "success", latencyMs: Date.now() - start });
} catch (error) {
  finish({ outcome: "error", errorType: classifyError(error) });
  throw error;
}
Function

recordLLMStart

Stable
function recordLLMStart(input: LLMCallStart, options?: LLMTelemetryOptions): void
Increment the luxo_llm_inflight gauge and write a 'llm.call.started' log line. Pair with recordLLMEnd to keep gauges accurate.
Function

recordLLMEnd

Stable
function recordLLMEnd(input: LLMCallStart, options?: LLMTelemetryOptions): void
Decrement luxo_llm_inflight when the call finishes.
Function

recordLLMCall

Stable
function recordLLMCall(input: LLMCallRecord, options?: LLMTelemetryOptions): void
Bump luxo_llm_requests_total + observe latency on luxo_llm_latency_seconds. Outcome must be one of "success" | "error" | "policy_blocked". As of v0.7.0, when toolsRequested or toolsUsed are present on the record, this also bumps luxo_llm_tool_use_total with one series per tool and role: "requested" | "used" — gives dashboards both "which tools the gateway exposed" and "which tools the model actually invoked".
Function

recordFeedback

Stable
function recordFeedback(input: FeedbackRecord, options?: FeedbackTelemetryOptions): void
Record human-feedback metrics (luxo_feedback_total counter and a per-score gauge). Pair with @luxoai-dev/agent-core/feedback's validateFeedback to keep validation and metrics aligned.
Function

recordLuxoAction

since v0.7Beta
function recordLuxoAction(
  input: LuxoActionRecord,
  options?: LuxoActionTelemetryOptions,
): void
Record Luxo chat action telemetry: one counter series per { actionId, status, surface, service_name }. Emits a structured luxo.action.proposed / luxo.action.executed log line in addition to bumping luxo_action_total. Wire "proposed" from the chat route's onActionsProposed hook and "executed" from the action beacon route. action_id labels come from each app's bounded registry vocabulary — cardinality stays controlled.
Parameters
ParamTypeRequiredDescription
input.actionIdstringRequiredAction id from your LUXO_ACTION_REGISTRY (e.g. 'simulator.loadBell').
input.status'proposed' | 'executed'Required'proposed' = LLM emitted the directive (server). 'executed' = user clicked the chip (client beacon).
input.surfacestringOptionalSurface key (e.g. 'review', 'editor'). Surface label in dashboards.
input.serviceNamestringOptionalFalls back to getObservabilityConfig().serviceName when omitted.
input.conversationIdstringOptionalEchoed into the log line for join with chat history.
input.requestIdstringOptionalLLM gateway requestId — joins to audit_log + feedback row.
input.userIdstringOptionalTenant-scoped actor id; echoed into the log line, not used as a metric label.
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { recordLuxoAction } from "@luxoai-dev/observability";

// Server side, inside createLuxoChatRoute onActionsProposed hook:
recordLuxoAction({
  actionId: "circuit.add-gate",
  status: "proposed",
  surface: "simulator",
  conversationId,
  requestId,
});

// Server side, inside createLuxoActionBeaconRoute onRecorded:
recordLuxoAction({
  actionId: payload.actionId,
  status: "executed",
  surface: payload.surface,
  conversationId: payload.conversationId,
  userId: auth?.userId,
});
interface

ObservabilityConfig

interface ObservabilityConfig {
  serviceName: string;
  version?: string;
  environment?: string;
  component?: string;
  nodeName?: string;
  otlpEndpoint?: string;
}
Process-wide identity. Set with initObservability; read with getObservabilityConfig.
interface

Logger

interface Logger {
  debug(event: string, fields?: LogContext): void;
  info(event: string, fields?: LogContext): void;
  warn(event: string, fields?: LogContext): void;
  error(event: string, fields?: LogContext): void;
  child(context: LogContext): Logger;
}
Structured-event logger. The first argument is an event name (snake_case.dot.style); the second is a context bag. JSON-encoded and redacted before being sent to the sink.
interface

HealthPayload

interface HealthPayload {
  status: "ok" | "down";
  service: string;
  version?: string;
  timestamp: string;
  checks: Record<string, { ok: boolean; details?: unknown }>;
}
interface

LLMCallRecord

interface LLMCallRecord extends LLMCallLabels {
  outcome: "success" | "error" | "policy_blocked";
  latencyMs?: number;
  errorType?: string;
  policyRule?: string;
  requestId?: string;
  traceId?: string;
}
Complete record passed to recordLLMCall when an LLM call finishes.

Next.js helpers · /next

Route-handler factories that wrap the core helpers in Web Response. Use these in app/api routes.

import { ... } from "@luxoai-dev/observability/next";
createHealthRoutecreateReadyRoutecreateMetricsRoutecreateMonitoringAuthorizermetricsResponse
Function

createHealthRoute

Stable
function createHealthRoute(options?: HealthRouteOptions): () => Promise<Response>
Returns an async GET handler that runs the configured checks and returns the canonical health payload with the right status code (200 / 503).
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
// app/api/health/route.ts
import { createHealthRoute } from "@luxoai-dev/observability/next";

export const GET = createHealthRoute({
  service: "support-assistant",
  version: process.env.APP_VERSION ?? "local",
  checks: {
    db: async () => ({ ok: await pingDb() }),
  },
});
Function

createReadyRoute

Stable
function createReadyRoute(options?: HealthRouteOptions): () => Promise<Response>
Same shape as createHealthRoute but conventionally for /api/ready (deeper checks: warm caches, recent migrations, etc.). Mount on a separate route from /api/health for orchestrators that distinguish liveness from readiness.
Function

createMetricsRoute

Stable
function createMetricsRoute(registry?: MetricsRegistry): () => Response
Returns a Prometheus-text response with cache-control: no-store. Defaults to defaultMetrics.
example.tsTypeScriptExample
1
2
// app/api/metrics/route.ts
export { createMetricsRoute as GET } from "@luxoai-dev/observability/next";
Function

createMonitoringAuthorizer

since v0.6Stable
function createMonitoringAuthorizer(
  options?: MonitoringAuthorizerOptions,
): (request: Request) => Response | null
Bearer-token check for protected metrics routes. Returns null on success and a 401 Response when the Authorization: Bearer ... header is missing or wrong. Absorbs the isMonitoringAuthorized helper every consumer hand-rolled. Reads MONITORING_TOKEN from process.env by default; pass token explicitly for tests. In development the dev-mode fallback allows unauthenticated reads so a local Prometheus can scrape.
Parameters
ParamTypeRequiredDescription
options.tokenstringOptionalOverride the token. Defaults to process.env.MONITORING_TOKEN.
options.allowInDevbooleanOptionalAllow unauthenticated reads when NODE_ENV !== 'production'. Defaults to true.
options.envFlagstringOptionalEnv var consulted for the token. Defaults to 'MONITORING_TOKEN'.

Returns

(request) => Response | null

example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// app/api/metrics/route.ts
import {
  createMetricsRoute,
  createMonitoringAuthorizer,
  metricsResponse,
} from "@luxoai-dev/observability/next";

const authorize = createMonitoringAuthorizer();
const metrics = createMetricsRoute();

export async function GET(request: Request) {
  const denied = authorize(request);
  if (denied) return denied;
  return metrics();
}
Function

metricsResponse

since v0.6Stable
function metricsResponse(body: string): Response
Convenience wrapper that returns a Prometheus-text Response with content-type: text/plain; version=0.0.4 and cache-control: no-store. Use this when you compose a custom metrics body (e.g. concatenating buildServiceMetricsSnapshot with defaultMetrics.renderPrometheus()) instead of returning the registry directly.
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
12
// app/api/metrics/route.ts (custom composition)
import { defaultMetrics } from "@luxoai-dev/observability";
import { metricsResponse } from "@luxoai-dev/observability/next";
import { buildServiceMetricsSnapshot } from "@luxoai-dev/observability/node";

export function GET() {
  const snapshot = buildServiceMetricsSnapshot({
    serviceName: "support-assistant",
    dependencies: { openai: !!process.env.OPENAI_API_KEY },
  });
  return metricsResponse(snapshot + defaultMetrics.renderPrometheus());
}

Node helpers · /node

Process-level helpers that don't fit the Next.js Response shape. Use these in long-lived workers, schedulers, or plain Node servers.

import { ... } from "@luxoai-dev/observability/node";
initNodeObservabilitystartProcessMetricsbuildServiceMetricsSnapshot
Function

initNodeObservability

Stable
function initNodeObservability(
  config: Partial<ObservabilityConfig>,
): ObservabilityConfig
Calls initObservability, logs an 'observability.initialized' line, and starts the default process-metrics sampler. Use as the single boot call in plain Node entry points.
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
// server.ts
import { initNodeObservability } from "@luxoai-dev/observability/node";

initNodeObservability({
  serviceName: "support-worker",
  version: process.env.APP_VERSION ?? "local",
  component: "worker",
});
Function

startProcessMetrics

Stable
function startProcessMetrics(
  registry?: MetricsRegistry,
  intervalMs?: number,
): ProcessMetricsHandle
Sample memoryUsage() and uptime every intervalMs (default 10s) and write to process_memory_bytes (with kind: rss/heap_used/heap_total) and process_uptime_seconds. Returns a handle with stop().
Parameters
ParamTypeRequiredDescription
registryMetricsRegistryOptionalDefaults to defaultMetrics.
intervalMsnumberOptionalSampling cadence. Defaults to 10000.
Function

buildServiceMetricsSnapshot

since v0.6Stable
function buildServiceMetricsSnapshot(
  input: ServiceMetricsSnapshotInput,
): string
Pull-based service metrics for /api/metrics routes that want an up-to-the-second snapshot without waiting for the next startProcessMetrics tick. Emits app_info (service, version, environment, node), app_dependency_configured (one series per declared dependency), and standard Node.js memory / CPU / event-loop gauges. Concatenate with defaultMetrics.renderPrometheus() — no parallel registry needed.
Parameters
ParamTypeRequiredDescription
input.serviceNamestringRequiredService name label on app_info.
input.versionstringOptionalService version. Defaults to process.env.APP_VERSION.
input.environmentstringOptionalEnvironment label. Defaults to process.env.NODE_ENV.
input.nodeNamestringOptionalHostname / pod label. Defaults to process.env.NODE_NAME or os.hostname().
input.dependenciesRecord<string, boolean>OptionalConfigured-or-not flags rendered as app_dependency_configured{name=…}.

Returns

string

Prometheus-text body. Concatenate with other rendered registries.
example.tsTypeScriptExample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { buildServiceMetricsSnapshot } from "@luxoai-dev/observability/node";
import { defaultMetrics } from "@luxoai-dev/observability";
import { metricsResponse } from "@luxoai-dev/observability/next";

export function GET() {
  const snapshot = buildServiceMetricsSnapshot({
    serviceName: "support-assistant",
    dependencies: {
      openai: Boolean(process.env.OPENAI_API_KEY),
      supabase: Boolean(process.env.SUPABASE_URL),
    },
  });
  return metricsResponse(snapshot + defaultMetrics.renderPrometheus());
}

OTLP forwarding

The package emits Prometheus text and structured JSON locally. To ship to a central collector, scrape /api/metrics with Prometheus or wire an OTel agent sidecar that scrapes locally and forwards to CENTRAL_OTEL_ENDPOINT. See monitoring.luxoai.org for the LuxoAI control plane.