Skip to content

Observability

A real-time framework that you cannot observe is a real-time framework you cannot trust. Atmosphere provides three observability pillars: Micrometer metrics for gauges, counters, and timers; OpenTelemetry tracing for distributed request tracing; and health checks for liveness and readiness probes.

AtmosphereMetrics — Micrometer Integration

Section titled “AtmosphereMetrics — Micrometer Integration”

The AtmosphereMetrics class in org.atmosphere.metrics registers gauges, counters, and timers on any Micrometer MeterRegistry. It requires io.micrometer:micrometer-core on the classpath (an optional dependency of atmosphere-runtime).

A single call wires everything up:

AtmosphereMetrics.install(framework, meterRegistry);

This registers a BroadcasterListener and a FrameworkListener on the framework. The method returns the AtmosphereMetrics instance for optional further instrumentation.

MetricTypeDescription
atmosphere.connections.activeGaugeCurrently active connections
atmosphere.connections.totalCounterTotal connections opened
atmosphere.connections.disconnectsCounterTotal disconnects
atmosphere.broadcasters.activeGaugeCurrently active broadcasters
atmosphere.messages.broadcastCounterTotal messages broadcast
atmosphere.messages.deliveredCounterTotal messages delivered to individual resources
atmosphere.broadcast.timerTimerBroadcast completion latency

Call instrumentRoom(room) or instrumentRoomManager(roomManager) for room-level observability:

MetricTypeTagsDescription
atmosphere.rooms.activeGaugeTotal active rooms
atmosphere.rooms.membersGaugeroomCurrent members in a specific room
atmosphere.rooms.messagesCounterroomMessages broadcast in a specific room

Call instrumentCache(uuidBroadcasterCache) to monitor the UUIDBroadcasterCache:

MetricTypeDescription
atmosphere.cache.sizeGaugeTotal cached messages
atmosphere.cache.evictionsFunctionCounterCache evictions
atmosphere.cache.hitsFunctionCounterCache retrieval hits
atmosphere.cache.missesFunctionCounterCache retrieval misses

Call instrumentBackpressure(interceptor) on a BackpressureInterceptor:

MetricTypeDescription
atmosphere.backpressure.dropsFunctionCounterMessages dropped by backpressure
atmosphere.backpressure.disconnectsFunctionCounterClients disconnected by backpressure

The samples/spring-boot-chat/ sample demonstrates wiring metrics into a Spring Boot application:

@Configuration
public class ObservabilityConfig {
private static final Logger logger = LoggerFactory.getLogger(ObservabilityConfig.class);
private final AtmosphereFramework framework;
private final MeterRegistry meterRegistry;
public ObservabilityConfig(AtmosphereFramework framework, MeterRegistry meterRegistry) {
this.framework = framework;
this.meterRegistry = meterRegistry;
}
@EventListener(ApplicationReadyEvent.class)
public void installMetrics() {
AtmosphereMetrics.install(framework, meterRegistry);
logger.info("Atmosphere Micrometer metrics installed — see /actuator/metrics/atmosphere.*");
}
}

The @EventListener(ApplicationReadyEvent.class) ensures the framework is fully initialized before metrics are installed.

With Spring Boot Actuator on the classpath, metrics are available at:

GET /actuator/metrics/atmosphere.connections.active
GET /actuator/metrics/atmosphere.messages.broadcast
GET /actuator/metrics/atmosphere.broadcast.timer

List all Atmosphere metrics:

Terminal window
curl http://localhost:8080/actuator/metrics | jq '.names[] | select(startswith("atmosphere"))'

AtmosphereTracing — OpenTelemetry Integration

Section titled “AtmosphereTracing — OpenTelemetry Integration”

The AtmosphereTracing class in org.atmosphere.metrics is an AtmosphereInterceptorAdapter that creates trace spans for each incoming request. It requires io.opentelemetry:opentelemetry-api on the classpath.

The interceptor creates a SERVER span on inspect() with the following attributes:

AttributeDescription
atmosphere.resource.uuidThe resource UUID
atmosphere.transportTransport type (WEBSOCKET, SSE, LONG_POLLING, etc.)
atmosphere.actionAction result (CONTINUE, SUSPEND)
atmosphere.broadcasterThe broadcaster ID
atmosphere.disconnect.reasonReason for disconnect (client, timeout, application)
atmosphere.roomRoom name (for room operations)

For non-suspended requests, the span ends immediately after postInspect(). For suspended (long-lived) connections, a TracingResourceListener tracks lifecycle events as span events:

  • atmosphere.suspend — resource suspended
  • atmosphere.resume — resource resumed
  • atmosphere.broadcast — message delivered
  • atmosphere.disconnect — resource disconnected
  • atmosphere.close — connection closed

Errors are recorded with span.recordException() and the span status is set to ERROR.

The startRoomSpan(operation, roomName, uuid) method creates internal spans for room operations (join, leave, broadcast). The caller is responsible for ending the span.

Register the interceptor with an OpenTelemetry instance or a Tracer:

OpenTelemetry otel = GlobalOpenTelemetry.get();
framework.interceptor(new AtmosphereTracing(otel));

Or with a custom tracer:

Tracer tracer = otel.getTracer("my-app", "1.0.0");
framework.interceptor(new AtmosphereTracing(tracer));

The samples/spring-boot-otel-chat/ sample demonstrates OpenTelemetry integration with Jaeger.

@Configuration
public class OtelConfig {
private static final Logger logger = LoggerFactory.getLogger(OtelConfig.class);
@Bean
public OpenTelemetry openTelemetry() {
var otel = AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk();
logger.info("OpenTelemetry SDK initialized — AtmosphereTracing will auto-register");
return otel;
}
}

The SDK auto-configures from environment variables:

Environment VariableDefaultDescription
OTEL_EXPORTER_OTLP_ENDPOINThttp://localhost:4317OTLP collector endpoint
OTEL_SERVICE_NAMEatmosphere-otel-chatService name shown in Jaeger

The @ManagedService class has no tracing-specific code. The AtmosphereTracing interceptor is auto-configured:

@ManagedService(path = "/atmosphere/chat", atmosphereConfig = MAX_INACTIVE + "=120000")
public class Chat {
private static final Logger logger = LoggerFactory.getLogger(Chat.class);
@Inject
private AtmosphereResource r;
@Inject
private AtmosphereResourceEvent event;
@Ready
public void onReady() {
logger.info("Browser {} connected — trace span started", r.uuid());
}
@Disconnect
public void onDisconnect() {
if (event.isCancelled()) {
logger.info("Browser {} unexpectedly disconnected", event.getResource().uuid());
} else if (event.isClosedByClient()) {
logger.info("Browser {} closed the connection", event.getResource().uuid());
}
}
@Message
public String onMessage(String message) {
logger.info("Received message — tracing active: {}", message);
return message;
}
}

Start Jaeger and the application:

Terminal window
# Start Jaeger all-in-one
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
jaegertracing/all-in-one:latest
# Run the sample
OTEL_SERVICE_NAME=atmosphere-chat \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
./mvnw spring-boot:run -pl samples/spring-boot-otel-chat

Open Jaeger at http://localhost:16686 and select the atmosphere-chat service to see spans for connect, message, and disconnect events.

The AtmosphereHealth class provides a framework-level health check:

AtmosphereHealth health = new AtmosphereHealth(framework);
Map<String, Object> status = health.check();

The returned map contains:

KeyTypeDescription
statusStringUP or DOWN
versionStringAtmosphere version
connectionsintTotal active connections across all broadcasters
broadcastersintNumber of active broadcasters
handlersintNumber of registered AtmosphereHandlers
interceptorsintNumber of registered interceptors

The isHealthy() method returns true if the framework has not been destroyed.

The atmosphere-spring-boot-starter includes an AtmosphereHealthIndicator that exposes Atmosphere health at /actuator/health:

{
"status": "UP",
"components": {
"atmosphere": {
"status": "UP",
"details": {
"version": "LATEST",
"connections": 42,
"broadcasters": 3,
"handlers": 2,
"interceptors": 5
}
}
}
}

MDCInterceptor populates SLF4J MDC keys on every request so log lines include Atmosphere context:

framework.interceptor(new MDCInterceptor());
KeyValue
atmosphere.uuidUnique resource identifier
atmosphere.transportTransport type
atmosphere.broadcasterBroadcaster ID
%d{HH:mm:ss.SSS} [%thread] %-5level [uuid=%X{atmosphere.uuid} transport=%X{atmosphere.transport}] %logger{36} - %msg%n

MDC keys are automatically included as top-level fields in JSON layouts (logstash-logback-encoder, logback-contrib).

The BackpressureInterceptor protects against slow consumers by limiting the number of pending messages per client:

framework.interceptor(new BackpressureInterceptor());
ParameterDefaultDescription
org.atmosphere.backpressure.highWaterMark1000Max pending messages per client
org.atmosphere.backpressure.policydrop-oldestdrop-oldest, drop-newest, or disconnect
PolicyBehavior
drop-oldestDrops the oldest pending message to make room (default)
drop-newestDrops the incoming message
disconnectDisconnects the slow client
BackpressureInterceptor bp = new BackpressureInterceptor();
bp.pendingCount(uuid); // Pending messages for a client
bp.totalDrops(); // Total messages dropped
bp.totalDisconnects(); // Total clients disconnected

Drop and disconnect counts are also exposed via Micrometer as atmosphere.backpressure.drops and atmosphere.backpressure.disconnects.

The UUIDBroadcasterCache (installed by default with @ManagedService) supports tuning:

ParameterDefaultDescription
org.atmosphere.cache.UUIDBroadcasterCache.maxPerClient1000Max cached messages per client
org.atmosphere.cache.UUIDBroadcasterCache.messageTTL300Per-message TTL in seconds
org.atmosphere.cache.UUIDBroadcasterCache.maxTotal100000Global cache size limit

A ready-to-import Grafana dashboard is available at samples/shared-resources/grafana/atmosphere-dashboard.json. It includes panels for connections, message rates, broadcast latency (p50/p95/p99), room members, cache performance, and backpressure.

  • AtmosphereMetrics.install(framework, meterRegistry) registers Micrometer gauges, counters, and timers for connections, broadcasters, messages, rooms, cache, and backpressure
  • AtmosphereTracing is an interceptor that creates OpenTelemetry spans covering the full request lifecycle with attributes for resource UUID, transport, action, broadcaster, and disconnect reason
  • MDCInterceptor populates SLF4J MDC keys (atmosphere.uuid, atmosphere.transport, atmosphere.broadcaster) on every request for structured logging
  • BackpressureInterceptor protects against slow consumers with configurable policies (drop-oldest, drop-newest, disconnect)
  • UUIDBroadcasterCache supports tuning via maxPerClient, messageTTL, and maxTotal parameters
  • AtmosphereHealth provides a health check snapshot with connection counts, broadcaster counts, and framework status
  • Spring Boot Actuator integration exposes metrics at /actuator/metrics/atmosphere.* and health at /actuator/health
  • The OTel chat sample shows end-to-end tracing with Jaeger
  • A Grafana dashboard is available for out-of-the-box monitoring

Next up: Chapter 19: atmosphere.js Client covers the TypeScript client library.