182 lines
10 KiB
TypeScript
182 lines
10 KiB
TypeScript
// ---------------------------------------------------------------------------
|
|
// Span attribute constants (OTel semantic conventions + AIRI custom)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// GenAI semconv attributes — https://opentelemetry.io/docs/specs/semconv/gen-ai/
|
|
export const GEN_AI_ATTR_OPERATION_NAME = 'gen_ai.operation.name'
|
|
export const GEN_AI_ATTR_REQUEST_MODEL = 'gen_ai.request.model'
|
|
export const GEN_AI_ATTR_RESPONSE_MODEL = 'gen_ai.response.model'
|
|
export const GEN_AI_ATTR_SYSTEM = 'gen_ai.system'
|
|
export const GEN_AI_ATTR_USAGE_INPUT_TOKENS = 'gen_ai.usage.input_tokens'
|
|
export const GEN_AI_ATTR_USAGE_OUTPUT_TOKENS = 'gen_ai.usage.output_tokens'
|
|
|
|
// AIRI custom span attributes
|
|
export const AIRI_ATTR_BILLING_FLUX_CONSUMED = 'airi.billing.flux_consumed'
|
|
export const AIRI_ATTR_GEN_AI_INPUT_MESSAGES = 'airi.gen_ai.input.messages'
|
|
export const AIRI_ATTR_GEN_AI_INPUT_TEXT = 'airi.gen_ai.input.text'
|
|
export const AIRI_ATTR_GEN_AI_OPERATION_KIND = 'airi.gen_ai.operation.kind'
|
|
export const AIRI_ATTR_GEN_AI_OLLAMA_THINK = 'airi.gen_ai.ollama.think'
|
|
export const AIRI_ATTR_GEN_AI_OUTPUT_FULL_TEXT = 'airi.gen_ai.output.full_text'
|
|
export const AIRI_ATTR_GEN_AI_OUTPUT_TEXT = 'airi.gen_ai.output.text'
|
|
export const AIRI_ATTR_GEN_AI_STREAM = 'airi.gen_ai.stream'
|
|
export const AIRI_ATTR_GEN_AI_STREAM_INTERRUPTED = 'airi.gen_ai.stream_interrupted'
|
|
|
|
// AIRI router gateway span attributes (in-process LLM/TTS routing — KTD-3).
|
|
// All prefixed `airi.gen_ai.gateway.*` to stay under the existing `airi.gen_ai.*`
|
|
// namespace already used by `airi.gen_ai.stream_interrupted`.
|
|
export const AIRI_ATTR_GEN_AI_GATEWAY_UPSTREAM_URL = 'airi.gen_ai.gateway.upstream.url'
|
|
export const AIRI_ATTR_GEN_AI_GATEWAY_UPSTREAM_INDEX = 'airi.gen_ai.gateway.upstream.index'
|
|
export const AIRI_ATTR_GEN_AI_GATEWAY_KEY_ID = 'airi.gen_ai.gateway.key.id'
|
|
export const AIRI_ATTR_GEN_AI_GATEWAY_FALLBACK_DEPTH = 'airi.gen_ai.gateway.fallback.depth'
|
|
export const AIRI_ATTR_GEN_AI_GATEWAY_FALLBACK_REASON = 'airi.gen_ai.gateway.fallback.reason'
|
|
|
|
// Server attributes
|
|
export const SERVER_ATTR_ADDRESS = 'server.address'
|
|
export const SERVER_ATTR_PORT = 'server.port'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Metric name constants
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// HTTP — https://opentelemetry.io/docs/specs/semconv/http/http-metrics/
|
|
export const METRIC_HTTP_SERVER_REQUEST_DURATION = 'http.server.request.duration'
|
|
export const METRIC_HTTP_SERVER_ACTIVE_REQUESTS = 'http.server.active_requests'
|
|
|
|
// Auth & user (AIRI custom)
|
|
export const METRIC_AUTH_ATTEMPTS = 'auth.attempts'
|
|
export const METRIC_AUTH_FAILURES = 'auth.failures'
|
|
export const METRIC_USER_REGISTERED = 'user.registered'
|
|
export const METRIC_USER_LOGIN = 'user.login'
|
|
export const METRIC_USER_TOTAL = 'user.total'
|
|
export const METRIC_USER_ACTIVE_SESSIONS = 'user.active_sessions'
|
|
// Distinct users with at least one non-expired session row. Pair with
|
|
// USER_ACTIVE_SESSIONS to detect "session row inflation" (Better Auth
|
|
// creates a new row per sign-in / per OIDC token refresh and never GCs)
|
|
// vs real user growth.
|
|
export const METRIC_USER_DISTINCT_ACTIVE = 'user.distinct_active'
|
|
// Rolling-window distinct active users (DAU / WAU / MAU), sourced from
|
|
// `user.last_seen_at` (touched on sign-in and every OIDC token refresh).
|
|
// Single gauge, observed once per window with a `window="24h"|"7d"|"30d"`
|
|
// attribute. Unlike USER_DISTINCT_ACTIVE (live-session count) this measures
|
|
// activity over a trailing time window, not "currently signed in".
|
|
export const METRIC_USER_ACTIVE_ROLLING = 'user.active_rolling'
|
|
|
|
// Engagement (AIRI custom)
|
|
export const METRIC_CHAT_MESSAGES = 'chat.messages'
|
|
export const METRIC_CHARACTER_CREATED = 'character.created'
|
|
export const METRIC_CHARACTER_DELETED = 'character.deleted'
|
|
export const METRIC_CHARACTER_ENGAGEMENT = 'character.engagement'
|
|
export const METRIC_WS_CONNECTIONS_ACTIVE = 'ws.connections.active'
|
|
export const METRIC_WS_MESSAGES_SENT = 'ws.messages.sent'
|
|
export const METRIC_WS_MESSAGES_RECEIVED = 'ws.messages.received'
|
|
|
|
// Revenue (AIRI custom)
|
|
export const METRIC_STRIPE_CHECKOUT_CREATED = 'stripe.checkout.created'
|
|
export const METRIC_STRIPE_CHECKOUT_COMPLETED = 'stripe.checkout.completed'
|
|
export const METRIC_STRIPE_PAYMENT_FAILED = 'stripe.payment.failed'
|
|
export const METRIC_STRIPE_SUBSCRIPTION_EVENT = 'stripe.subscription.event'
|
|
export const METRIC_STRIPE_EVENTS = 'stripe.events'
|
|
export const METRIC_FLUX_INSUFFICIENT_BALANCE = 'flux.insufficient_balance'
|
|
|
|
// GenAI — https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/
|
|
export const METRIC_GEN_AI_CLIENT_OPERATION_DURATION = 'gen_ai.client.operation.duration'
|
|
export const METRIC_GEN_AI_CLIENT_OPERATION_COUNT = 'gen_ai.client.operation.count'
|
|
export const METRIC_GEN_AI_CLIENT_TOKEN_USAGE_INPUT = 'gen_ai.client.token.usage.input'
|
|
export const METRIC_GEN_AI_CLIENT_TOKEN_USAGE_OUTPUT = 'gen_ai.client.token.usage.output'
|
|
export const METRIC_FLUX_CONSUMED = 'airi.billing.flux.consumed'
|
|
|
|
// AIRI billing — credit/debit visibility beyond raw consumption
|
|
export const METRIC_AIRI_FLUX_CREDITED = 'airi.billing.flux.credited'
|
|
// Streaming-only: token already streamed to user but post-stream debit failed.
|
|
// Real revenue leak — every >0 sample should page. NOT covered by DB latency /
|
|
// HTTP 5xx alerts because the response was 2xx and the catch path is silent.
|
|
export const METRIC_AIRI_FLUX_UNBILLED = 'airi.billing.flux.unbilled'
|
|
export const METRIC_AIRI_TTS_CHARS = 'airi.billing.tts.chars'
|
|
export const METRIC_AIRI_TTS_PREFLIGHT_REJECTIONS = 'airi.billing.tts.preflight_rejections'
|
|
|
|
// AIRI observability — self-monitoring for the metric pipeline
|
|
export const METRIC_AIRI_OBSERVABILITY_READ_ERRORS = 'airi.observability.read_errors'
|
|
|
|
// Product analytics — low-cardinality event volume only. User-level product
|
|
// analytics live in Postgres `product_events`; never add user identifiers to
|
|
// this metric's labels.
|
|
export const METRIC_AIRI_PRODUCT_EVENTS = 'airi.product.events'
|
|
|
|
// AIRI revenue — actual money in (smallest currency unit, e.g. cents)
|
|
export const METRIC_AIRI_STRIPE_REVENUE = 'airi.stripe.revenue'
|
|
|
|
// AIRI email — transactional delivery health
|
|
export const METRIC_AIRI_EMAIL_SEND = 'airi.email.send'
|
|
export const METRIC_AIRI_EMAIL_FAILURES = 'airi.email.failures'
|
|
export const METRIC_AIRI_EMAIL_DURATION = 'airi.email.duration'
|
|
|
|
// AIRI rate limiting — abuse / attack visibility
|
|
export const METRIC_AIRI_RATE_LIMIT_BLOCKED = 'airi.rate_limit.blocked'
|
|
|
|
// AIRI GenAI — stream quality
|
|
export const METRIC_AIRI_GEN_AI_STREAM_INTERRUPTED = 'airi.gen_ai.stream.interrupted'
|
|
export const METRIC_GEN_AI_CLIENT_FIRST_TOKEN_DURATION = 'gen_ai.client.first_token.duration'
|
|
|
|
// AIRI router gateway — in-process LLM/TTS routing fallback / health signals.
|
|
//
|
|
// fallback_count — each retry attempt (per failing key). Labels: provider,
|
|
// from_key, reason. Dashboard sum gives 24h fallback volume.
|
|
// upstream_errors — upstream-side error responses. Labels: provider, status_code.
|
|
// key_exhausted — all keys in a single request failed. Labels: provider.
|
|
// **Primary alert source for "user-facing degradation"**.
|
|
// same_status_exhaustion
|
|
// — all keys failed in one request *with the same status code*.
|
|
// Strong signal of shared-backend (account-level) rate limit
|
|
// that ordinary fallback can't help with. Drives D33's blind
|
|
// spot detection per adversarial review.
|
|
// config_reload — local in-memory configKV cache reloaded. Labels: source
|
|
// (`pubsub` | `ttl` | `manual`), service_instance_id.
|
|
// decrypt_failures — envelope-crypto decryption auth-tag failures. Labels:
|
|
// provider, key_entry_id. >0 = config corruption or rotation
|
|
// misstep.
|
|
// subscriber_state — Pub/Sub subscriber lifecycle transitions. Labels: state
|
|
// (`subscribed` | `reconnecting` | `error` | `closed`).
|
|
// config_write — admin endpoint write events. Labels: result
|
|
// (`success` | `4xx` | `5xx`), actor_email.
|
|
// config_invalid_hmac
|
|
// — Pub/Sub invalidation messages dropped due to HMAC mismatch
|
|
// (forged or replayed). >0 = investigate Redis access.
|
|
export const METRIC_AIRI_GEN_AI_GATEWAY_FALLBACK_COUNT = 'airi.gen_ai.gateway.fallback.count'
|
|
export const METRIC_AIRI_GEN_AI_GATEWAY_UPSTREAM_ERRORS = 'airi.gen_ai.gateway.upstream.errors'
|
|
export const METRIC_AIRI_GEN_AI_GATEWAY_KEY_EXHAUSTED_COUNT = 'airi.gen_ai.gateway.key.exhausted'
|
|
export const METRIC_AIRI_GEN_AI_GATEWAY_SAME_STATUS_EXHAUSTION = 'airi.gen_ai.gateway.same_status_exhaustion'
|
|
export const METRIC_AIRI_GEN_AI_GATEWAY_CONFIG_RELOAD = 'airi.gen_ai.gateway.config.reload'
|
|
export const METRIC_AIRI_GEN_AI_GATEWAY_DECRYPT_FAILURES = 'airi.gen_ai.gateway.decrypt.failures'
|
|
export const METRIC_AIRI_GEN_AI_GATEWAY_SUBSCRIBER_STATE = 'airi.gen_ai.gateway.subscriber_state'
|
|
export const METRIC_AIRI_GEN_AI_GATEWAY_CONFIG_WRITE = 'airi.gen_ai.gateway.config.write'
|
|
export const METRIC_AIRI_GEN_AI_GATEWAY_CONFIG_INVALID_HMAC = 'airi.gen_ai.gateway.config.invalid_hmac'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Canonical gen_ai.system values
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// First emitter of `gen_ai.system` in this repo (see observability-conventions.md).
|
|
// Lock these values now so future adapters / providers don't fork the namespace.
|
|
|
|
export const GEN_AI_SYSTEM_OPENROUTER = 'openrouter'
|
|
export const GEN_AI_SYSTEM_AZURE_SPEECH = 'azure.speech'
|
|
export const GEN_AI_SYSTEM_DASHSCOPE_COSYVOICE = 'dashscope.cosyvoice'
|
|
export const GEN_AI_SYSTEM_VOLCENGINE_TTS = 'volcengine.tts'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function getServerConnectionAttributes(baseUrl: string): Record<string, string | number> {
|
|
const url = new URL(baseUrl)
|
|
const attributes: Record<string, string | number> = {
|
|
[SERVER_ATTR_ADDRESS]: url.hostname,
|
|
}
|
|
|
|
if (url.port) {
|
|
attributes[SERVER_ATTR_PORT] = Number.parseInt(url.port, 10)
|
|
}
|
|
|
|
return attributes
|
|
}
|