Documentation/titan/Observability matrix
In this section

Observability matrix

Operating a Titan app means knowing what to grep for and what to alert on. This page lists — per module — the events emitted, notable log keys, and metric/health signals you can rely on.

All claims here are verified against module source. If a row is empty, the module is deliberately quiet at that layer.

At-a-glance

official · @omnitron-dev/titan-*

The Metrics surface column distinguishes metrics actively pushed into titan-metrics from stats you read on demand (pull — a getStats() / getMetrics() / stats() accessor).

ModuleEmits eventsNotable logsMetrics surfaceHealth indicator
titan-auth—JWT verify failures——
titan-cache—L2 fallback / errorshit/miss/evict (pull: getStats())—
titan-database—slow query, retries—yes (via titan-health)
titan-discoverydiscovery:eventregister, heartbeat, dereg——
titan-eventsproxydispatch errors——
titan-health—indicator failures—n/a (itself)
titan-lock—failure-tracker windows——
titan-metrics—quiet by designprocess/system collectors—
titan-notificationsnotifications.* (via titan-events)send + DLQ—yes
titan-pmmany (below)spawn / crash / restartprocess statsyes (via titan-health)
titan-ratelimit—reject reasonsallow/deny (pull: getStats())—
titan-redis—reconnect / errors—yes (via titan-health)
titan-scheduler—job start / finishruns/failures (pull: getMetrics())—
titan-telemetry-relay—WAL eventspull: stats()—

built-in · @omnitron-dev/titan

ModuleChange notificationsNotable logsPushes metrics
configonChange() callbackreload success/fail—
logger—(the logger itself)—

Per-module reference

titan-pm — the most chatty module

official · @omnitron-dev/titan-pm

Process manager is the loudest module by design — every supervised process / pool / worker raises events you can subscribe to.

EventArgsMeaning
process:spawnprocessInfoChild process started
process:readyprocessInfoChild responded to ready handshake
process:stopprocessInfoChild exited cleanly
process:crashprocessInfo, errorChild died unexpectedly
child:startednameSupervised child entry started
child:stoppednameSupervised child stopped
child:start-failedname, errorInitial start raised before ready
child:crashname, errorSupervised child crashed mid-run
child:restartname, countChild restarted; count = total restarts
pool:initialized{ size, class }Worker pool warmed up
pool:scaled{ from, to, class }Pool grew / shrank
pool:drained{ class }Pool finished draining queued work
pool:destroyed{ class }Pool released
pool:memory{ workerId, rssMB }Memory limit exceeded
worker:spawned{ workerId, class }Pool worker forked
worker:shutdown{ workerId, class }Pool worker reaped
worker:unhealthy{ workerId }Worker missed a ping
worker:unresponsive{ workerId }Worker still silent after retries
worker:replaced{ workerId }Unresponsive worker replaced
request:queued{ poolClass, queueSize }Pool saturated; work queued
circuitbreaker:open—Breaker tripped
circuitbreaker:halfopen—Probe window started
circuitbreaker:close—Breaker reset
escalatename, errorRestart limit exceeded; failure escalated upstream
shutdown—Supervisor shutdown
exitinfoProcess exited
health:changeprocessId, IHealthStatusPer-process health flipped
health:criticalprocessId, IHealthStatusPer-process health became critical

Subscribe with the standard event emitter API:

pmService.on('process:crash', (info, error) => {
  logger.error({ pid: info.id, error }, 'child crashed');
});

titan-notifications

official · @omnitron-dev/titan-notifications

Lifecycle events are emitted through titan-events under the NOTIFICATIONS_EVENTS topic names (only when an EventsService is available). Selected topics:

Topic constantEvent nameMeaning
NOTIFICATION_SENTnotifications.notification.sentSend completed
NOTIFICATION_FAILEDnotifications.notification.failedSend failed
CHANNEL_DELIVERY_SUCCESSnotifications.channel.delivery.successPer-channel delivery ok
CHANNEL_DELIVERY_FAILEDnotifications.channel.delivery.failedPer-channel delivery failed
DLQ_MESSAGE_ADDEDnotifications.dlq.message.addedMessage routed to DLQ

Dead-lettered messages are also inspectable on the service via getDLQStats() / getDLQMessages(options?) and requeued with requeueFromDLQ(count?).

Health: the module registers NotificationsHealthIndicator (NOTIFICATIONS_HEALTH) — automatically picked up by titan-health if both modules are loaded.

titan-discovery

official · @omnitron-dev/titan-discovery

EventPayloadMeaning
discovery:event{ type, node, service?, ts }Node added / removed / updated

type is one of node.added, node.removed, node.updated.

Notable logs (all under the service's own logger namespace):

LevelPattern
infoNode 'X' registered, DiscoveryService started
infoInitiating graceful shutdown for node 'X'
warnHeartbeat attempt failed
errorAll N heartbeat attempts failed
errorFailed to publish discovery event
debugReceived discovery event, Cleaned up inactive nodes

titan-events

official · @omnitron-dev/titan-events

titan-events is the event bus; it doesn't emit framework-level events of its own. Per-emitter introspection is available via EventHistoryService if enableHistory: true.

titan-scheduler

official · @omnitron-dev/titan-scheduler

Doesn't expose a hot event stream — IJobListener is the extension point. Implement one to observe job execution:

class MyJobListener implements IJobListener {
  onJobStart?(job, context)         { /* ... */ }
  onJobComplete?(job, result)       { /* ... */ }
  onJobError?(job, error, context)  { /* ... */ }
  onJobRetry?(job, attempt, error)  { /* ... */ }
  onJobCancelled?(job, reason?)     { /* ... */ }
}

All hooks are optional. Register listeners via the listeners module option or the SCHEDULER_LISTENERS_TOKEN.

titan-cache

official · @omnitron-dev/titan-cache

Cache hit/miss/eviction metrics are exposed through the @Cached decorator metadata and the CacheService.getStats() API.

const stats = cache.getStats();
// { hits, misses, evictions, size, hitRate, ... }

No native event stream — wrap calls if you need per-key observability.

titan-lock

official · @omnitron-dev/titan-lock

Uses the framework's FailureTracker primitive: instead of logging every failure, it collapses repeated failures of the same operation into windowed "X failing" warnings. This keeps logs quiet when Redis briefly hiccups.

LevelPattern
warn[DistributedLock] X started failing
debug[DistributedLock] X still failing
info[DistributedLock] X recovered

titan-database

official · @omnitron-dev/titan-database

LevelPattern
warnSlow query (over the configured threshold)
warnTransient error retried (via withRetry)
errorPool exhaustion / connection lost
infoMigration applied

The module exposes DatabaseHealthIndicator for k8s probes.

titan-redis

official · @omnitron-dev/titan-redis

Inherits ioredis's event model (connect, ready, error, close, reconnecting, end). The manager logs reconnect attempts and surfaces a RedisHealthIndicator via titan-health.

titan-auth

official · @omnitron-dev/titan-auth

Quiet by design — token-verification details should not be logged at info level (PII/tokens). Failures log at warn with error.code populated; successful verifications are silent.

titan-ratelimit

official · @omnitron-dev/titan-ratelimit

Statistics are pull-based:

const stats = rate.getStats();
// { totalChecks, totalAllowed, totalDenied, activeKeys, byTier? }

Denied requests are not logged automatically (volume would be high) — count them via metrics.recordTyped('counter', ...) from the caller if you need to.

titan-metrics

official · @omnitron-dev/titan-metrics

MetricsService.getStorageStatus() reports durability, queue samples/bytes, completed writes, retry failures, known losses and retention/callback errors. Background errors also reach onError; explicit write/flush failures reject. Omnitron records rate-limited structured failures and exposes the same reading through daemon health, CLI and Console. Normal successful flushes stay quiet.

The process collector (collection: { process: true }) records, for the daemon's own process: heap_used_bytes, heap_total_bytes, rss_bytes, external_bytes, cpu_percent, uptime_seconds. The child/orchestrator collector (system: true) additionally records memory_bytes, rpc_requests_total, rpc_errors_total, and app_status per supervised app. Event-loop lag is not collected by the built-in collector.

titan-telemetry-relay

official · @omnitron-dev/titan-telemetry-relay

Exposes a pull-based stats() snapshot — { totalEmitted, totalSent, totalFailed, totalReceived, transportConnected, buffer, wal } — where buffer ({ size, totalPushed, totalDropped, totalFlushed }) and wal ({ segments, totalSize, totalWritten, currentSegment }) carry the queue-depth and throughput signals. Surface these through your metrics module at the leader node; the relay does not push them itself.

titan-health

official · @omnitron-dev/titan-health

LevelPattern
errorIndicator threw during check
warnIndicator returned degraded
debugProbe cache hit / refresh

Health results carry per-check timing in their own result objects; the module does not push check latency into titan-metrics.

Built-in modules

config

built-in · @omnitron-dev/titan/module/config

Change notifications are delivered through a callback subscription, not a named event-bus topic:

const unsubscribe = config.onChange((event: IConfigChangeEvent) => {
  // event: { path, oldValue, newValue, source, timestamp }
});

Notable logs:

  • info — Configuration reloaded due to file change (after a watched source changes)

  • error — Failed to reload configuration

logger

built-in · @omnitron-dev/titan/module/logger

The logger doesn't log about itself except at fatal failures (e.g., transport open failed at boot). Use createNullLogger() in tests when you want absolute silence.

Cross-cutting recommendations

Subscribe instead of poll

The pm/discovery event streams are hot — subscribing is cheaper and more responsive than polling state. For per-process autoscaling or alerting, hook the events:

pm.on('pool:memory',      handleMemorySpike);
pm.on('worker:unresponsive', handleWorkerHang);
pm.on('circuitbreaker:open', notifyOncall);

Tie metrics to events

For dashboards, route key events through titan-metrics:

pm.on('process:crash', (info, error) => {
  metrics.recordTyped('counter', 'pm.process.crash.total',
    { class: info.processName }, 1);
});

Alert thresholds — common starting points

SignalSuggested alert
rss_bytes growth rate> +50 MB/min sustained
cpu_percentsustained near 100 for 2 min
pool:scaled to/from limitrepeated within 5 min
worker:replaced count> 3 in 5 min for same pool
circuitbreaker:openany open lasting > 30 s
app_status flips to 0any supervised app offline
Health degraded/unhealthysustained > 1 probe window

Don't log per-call by default

Several modules (titan-ratelimit, titan-cache, titan-auth) intentionally stay quiet at info level. Per-call logging at request rate is a recipe for disk/SIEM saturation. Use counters and sampled debug logging instead.

See also