# Metrics & tracing
Two related but very different stories on the Java side:
- Metrics: yes, built in. moleculer-java has a full Micrometer (opens new window)-based
metrics subsystem (
services.moleculer.metrics). It is off by default; turn it on and the broker records request, event, transit, transporter, circuit-breaker, cacher, retry and timeout metrics, and you can add your own. Despite that, there is no$node.metricsaction — you read metrics through the registry, not through the$node.*introspection service. - Tracing: no native support. There is no built-in tracer and no Jaeger / Zipkin / OpenTelemetry integration. See Tracing for what to do instead.
# Enabling metrics
Metrics are collected by a MetricMiddleware that the broker installs only when
metricsEnabled is true. The registry implementation is DefaultMetrics (a Micrometer
CompositeMeterRegistry); a default instance is already in the config, so the minimum is one flag:
ServiceBroker broker = ServiceBroker.builder()
.nodeID("java-node")
.transporter(new NatsTransporter("nats://localhost:4222"))
.metricsEnabled(true) // installs the MetricMiddleware
.build();
To configure reporting or add JVM gauges, build your own DefaultMetrics and hand it to the broker:
DefaultMetrics metrics = new DefaultMetrics();
metrics.startConsoleReporter(); // periodic console dump (needs io.dropwizard.metrics:metrics-core)
metrics.addProcessorMetrics(); // optional JVM/system gauges
metrics.addJvmMemoryMetrics();
ServiceBroker broker = ServiceBroker.builder()
.nodeID("java-node")
.transporter(new NatsTransporter("nats://localhost:4222"))
.metricsEnabled(true)
.metrics(metrics) // your configured registry
.build();
The equivalent in Node.js is metrics: { enabled: true, reporter: ... } — same idea, wired in code.
# What is collected
With metrics enabled, the broker records these metric families (Micrometer counters, gauges and timers); a few representative names:
| Family | Example metrics |
|---|---|
| Requests | moleculer.request.total, moleculer.request.active, moleculer.request.error.total, moleculer.request.time |
| Events | moleculer.event.emit.total, moleculer.event.broadcast.total, moleculer.event.received.time |
| Transit | moleculer.transit.requests.active, moleculer.transit.connected |
| Transporter | moleculer.transporter.packets.sent.total, moleculer.transporter.packets.received.bytes |
| Circuit breaker | moleculer.circuit-breaker.opened.active, moleculer.circuit-breaker.opened.total |
| Cacher / retry / timeout | cache get/set/del/clean counts, retry attempts, request timeouts |
You can also bind standard JVM/system meters on the DefaultMetrics instance:
addProcessorMetrics() (CPU), addJvmMemoryMetrics(), addJvmGcMetrics(), addJvmThreadMetrics(),
addClassLoaderMetrics(), and addExecutorServiceMetrics(...).
# Reporting / exporting
DefaultMetrics ships with Dropwizard-backed reporters (each needs
io.dropwizard.metrics:metrics-core on the classpath):
metrics.startConsoleReporter(); // every minute, to stdout
metrics.startSlf4jReporter(); // every minute, via SLF4J
metrics.startCsvReporter("./metrics"); // every second, to .csv files in a directory
For push/pull backends, give DefaultMetrics any Micrometer MeterRegistry (add the matching
micrometer-registry-* dependency — e.g. JMX, New Relic, Datadog):
// Example: expose metrics over JMX (needs io.micrometer:micrometer-registry-jmx)
MeterRegistry jmx = new JmxMeterRegistry(JmxConfig.DEFAULT, Clock.SYSTEM);
DefaultMetrics metrics = new DefaultMetrics(jmx); // Datadog / New Relic registries plug in the same way
# Custom metrics
Reach the registry with broker.getConfig().getMetrics() and record your own measurements through the
Metrics interface — a counter, a gauge, and a stoppable timer:
import java.time.Duration;
import services.moleculer.metrics.Metrics;
import services.moleculer.metrics.StoppableTimer;
Metrics metrics = broker.getConfig().getMetrics();
// counter (name, description, delta, optional tags...)
metrics.increment("my.orders.total", "Number of orders", 1, "channel", "web");
// gauge (name, description, value, optional tags...)
metrics.set("my.queue.size", "Pending items", queue.size());
// timer — call stop() when the work finishes
StoppableTimer timer = metrics.timer("my.job.time", "Job duration", Duration.ofMinutes(5));
try {
doWork();
} finally {
timer.stop();
}
# Tracing
There is no native distributed tracing in moleculer-java: no built-in tracer, and no Jaeger, Zipkin or OpenTelemetry exporter. If you need traces, choose one of these:
- Propagate context yourself. Every
Contextalready carriesrequestID(stable across the whole call chain),parentID, the calllevel, and a per-callid. Pass a trace/span id throughmeta(which rides along across language boundaries — see Metadata) and stitch the spans together in your backend. - Instrument at the application layer. Wrap actions in a
Middlewarethat starts and stops spans with an external tracer (e.g. an OpenTelemetry or Micrometer-Tracing SDK), or rely on your APM agent's bytecode instrumentation. TherequestID/parentID/levelfields give you the parent-child links to build a trace.
In a hybrid cluster, only the metadata travels between languages, not a trace context object — agree on a
metakey (e.g.traceId) on both the Java and Node.js sides if you correlate across the boundary.