For the complete documentation index, see llms.txt. Markdown versions are available by appending .md to documentation URLs.

Monitor EMQX with OpenTelemetry Metrics, Traces, Logs

SigNoz Cloud - This page applies to SigNoz Cloud editions.
Self-Host - This page applies to self-hosted SigNoz editions.

Overview

EMQX is an MQTT broker. It moves messages between publishers and subscribers. EMQX also has an OpenTelemetry exporter built in. Point that exporter at SigNoz, and every node sends its telemetry over OTLP. You do not need an SDK or a scrape target.

All three signals arrive under one service name, emqx by default. Set OTEL_SERVICE_NAME to change it.

You collect three signals here:

  • Metrics: connection, session, subscription, packet, and message counters, plus BEAM VM gauges for CPU, memory, and run queue.
  • Traces: per-message spans that follow a publish from the broker to each subscriber. The spans join the publisher's trace when the client sends a traceparent.
  • Logs: broker log events shaped to the OpenTelemetry log data model.

Prerequisites

Ensure you have:

  • EMQX 6.0 or later. Earlier releases cannot attach the ingestion key, so route those through a Collector.
  • An instance of SigNoz (either Cloud or Self-Hosted)
  • Outbound network access from every EMQX node to your SigNoz ingestion endpoint on port 443
  • MQTT 5.0 publishers, if you want broker spans joined to your application's traces

Send EMQX Telemetry to SigNoz

Step 1: Configure the exporter and start EMQX

EMQX exports over OTLP/gRPC only. Every snippet below uses the gRPC endpoint on port 443, not the OTLP/HTTP endpoint used elsewhere in these docs.

Pick where EMQX runs:

Append the block below to /etc/emqx/emqx.conf. Leave your existing node and cluster sections in place:

/etc/emqx/emqx.conf
opentelemetry {
  exporter {
    endpoint = "https://ingest.<region>.signoz.cloud:443"
    ssl_options {
      enable = true
      verify = verify_peer
      cacertfile = "/etc/ssl/certs/ca-certificates.crt"
    }
    headers { "signoz-ingestion-key" = "<your-ingestion-key>" }
  }
  metrics { enable = true, interval = "10s" }
  traces { enable = true, filter { trace_all = true } }
  logs { enable = true, level = warning }
}

Verify these values:

Quote the header name. signoz-ingestion-key contains hyphens, which HOCON reads as part of a longer path unless the key is wrapped in double quotes.

verify = verify_peer makes EMQX validate the SigNoz certificate. Without it the exporter encrypts the connection but accepts any certificate, because ssl_options.verify defaults to verify_none. Point cacertfile at your system trust store: /etc/ssl/certs/ca-certificates.crt on Debian and Ubuntu, /etc/pki/tls/certs/ca-bundle.crt on RHEL and Fedora. EMQX ships its own cacert.pem demo CA, which cannot verify a public certificate.

The service name is not a HOCON key. To change it from the default emqx, set OTEL_SERVICE_NAME in the environment file that the EMQX unit reads, such as /etc/emqx/emqx.env:

/etc/emqx/emqx.env
OTEL_SERVICE_NAME=<service-name>

Restart the broker, then watch its log:

sudo systemctl restart emqx
sudo journalctl -u emqx -f

A connected exporter writes no log lines of its own. Repeated [grpc_client] connect to ... failed or opentelemetry_exporter failed to export lines mean the broker cannot reach the endpoint. Expect a few during startup, before EMQX resolves the endpoint and retries.

Step 2: Produce MQTT traffic

Counters stay at zero until clients connect. No spans exist until a client publishes a message. Generate a message with MQTTX CLI, or use any client you already have.

Subscribe in one terminal:

mqttx sub -h <emqx-host> -p 1883 -t 't/trace/test' -q 1

Set <emqx-host> to the host or service name that serves the MQTT listener on port 1883.

Publish in another terminal. Pass a traceparent so that the broker joins your existing trace:

mqttx pub -h <emqx-host> -p 1883 -t 't/trace/test' -q 1 -m 'hello' \
  -up "traceparent: 00-cce3a024ca134a7cb4b41e048e8d98de-cef47eaa4ebc3fae-01"

EMQX exports spans in batches. The default interval is 5 seconds.

Validate

Every signal arrives with the resource attribute service.name = <service-name>, and service.instance.id holds the node name. Filter on both in Metrics, Traces, and Logs.

Verify these values:

  • <service-name>: the value of OTEL_SERVICE_NAME, or emqx when you leave it unset

The Metrics Summary tab lists every metric the broker exports.

EMQX metrics listed in the SigNoz Metrics Summary tab
EMQX metrics in SigNoz, filtered by service.name = emqx

Each published message produces one process_message span, plus one send_published_message span for every subscriber. When the publisher sends a traceparent, the spans join your application's trace.

EMQX broker spans in the SigNoz Traces Explorer
process_message and send_published_message spans in SigNoz, filtered by service.name = emqx

Restart a node or force an authentication failure to produce a fresh log entry.

EMQX broker logs in the SigNoz Logs Explorer
EMQX broker startup logs in SigNoz, filtered by service.name = emqx

Data Collected

Metrics

EMQX 6.3.0 exports 142 metrics. Names are dotted and carry no emqx prefix, so scope every query by service.name to avoid mixing them with metrics from other sources.

FamilyExamplesWhat it covers
Connections and sessionsconnections.count, live_connections.count, sessions.count, channels.countConnected clients and live sessions per node
Messagesmessages.received, messages.sent, messages.delivered, messages.ackedBroker throughput by direction and QoS
Packetspackets.received, packets.publish.received, packets.connect, packets.puback.missedMQTT packet counters, including protocol errors
Dropped messagesmessages.dropped.no_subscribers, delivery.dropped.queue_full, delivery.dropped.expiredWhy the broker discarded a message
Subscriptions and routingsubscriptions.count, subscriptions.shared.count, topics.count, retained.countSize of the subscription and topic tables
Authentication and authorizationauthentication.success, authentication.failure, authorization.deny, client.bannedClient access outcomes
Node runtimecpu.use, run.queue, used.memory, total.memory, node.runningBEAM VM pressure and cluster membership

Some gauges are cluster-wide and arrive with the same value from every node: topics.count, routes.count, retained.count, subscriptions.shared.count, durable_subscriptions.count, cluster_sessions.count, and node.running. Take the maximum across nodes for these, not the sum. Counts such as connections.count, sessions.count, and subscriptions.count are node-local, so sum those.

Spans

SpanWhen EMQX records itAttributes
process_messageStarts when EMQX parses a PUBLISH packet. Ends when EMQX dispatches the message locally or forwards it to other nodes. One per message.messaging.destination.name, messaging.client_id
send_published_messageStarts when the connection process of a subscriber takes the message. Ends when the packet reaches the socket. One per subscriber.messaging.destination.name, messaging.client_id

Logs

Log records carry the same resource attributes as the other two signals, plus Erlang context such as pid, file, line, and mfa. Bodies are strings for plain messages and structured maps for EMQX's own event reports.

Troubleshooting

Nothing arrives in SigNoz

  • Read the broker log for [grpc_client] connect to ... failed or opentelemetry_exporter failed to export.
  • Make sure that the endpoint scheme matches your intent. EMQX turns TLS on by itself for an https:// endpoint, but it does not verify the server certificate unless you set verify = verify_peer.
  • Make sure that the ingestion key header name is quoted. Without quotes, HOCON reads signoz-ingestion-key as a nested path and the header breaks.
  • Make sure that the node can reach the endpoint: curl -v https://ingest.<region>.signoz.cloud:443.
  • Make sure that at least one signal is enabled. All three default to enable = false.

TLS handshake fails after you enable verification

The log shows failed to export with {tls_alert,{unknown_ca,...}}. EMQX cannot build a trust chain to the SigNoz certificate.

  • Make sure that cacertfile points at your system trust store, not at EMQX's bundled cacert.pem. The bundled file is a demo CA and verifies nothing public.
  • Make sure that the path exists inside the container or host that runs EMQX.
  • Leave server_name_indication unset. A value that does not match the endpoint host fails the same way.

EMQX rejects the opentelemetry configuration

The build has no exporter. Run emqx ctl status and compare the version against the table in Prerequisites. Open source builds from 5.8.3 to 5.8.9 exclude the exporter.

Collect metrics through the Prometheus endpoint instead. On these builds prometheus.enable_basic_auth defaults to false, so the endpoint answers unauthenticated scrapes. Add this receiver to your OpenTelemetry Collector configuration. Reference it from a metrics pipeline:

config.yaml
receivers:
  prometheus/emqx:
    config:
      scrape_configs:
        - job_name: emqx
          scrape_interval: 30s
          metrics_path: /api/v5/prometheus/stats
          static_configs:
            - targets: ["<emqx-host>:18083"]

EMQX names these metrics emqx_*, so they do not match the names in Data Collected. This path carries no traces and no logs.

Port 18083 also serves the EMQX Dashboard, so keep it off the public network and reachable only from the Collector.

No logs appear

opentelemetry.logs.level cannot be more verbose than the broker's own handlers. If log.console.level is warning, the broker exports only warnings and above, even when you set the OpenTelemetry level to info. Raise both levels, or leave the default.

Keep logs.level = warning in production. At info, EMQX exports Erlang supervisor progress reports, which are large and constant.

Filtering on severity returns nothing

severity_text holds values such as SEVERITY_NUMBER_WARN, not WARN. Filter on severity_number, or copy the exact value from a record in the Logs Explorer.

Some published messages produce no spans

Trace context travels in MQTT user properties, which exist only in MQTT 5.0. A 3.1.1 publisher sends no traceparent, so EMQX skips the message. Set traces.filter.trace_all = true to trace it anyway. EMQX then generates a fresh trace ID.

Metric names collide with other services

cpu.use, run.queue, used.memory, and similar names carry no vendor prefix. Filter by service.name = <service-name>, or rename them in a Collector with the metricstransform processor.

A distinct OTEL_SERVICE_NAME per cluster keeps two EMQX deployments apart in the same workspace.

Export through an OpenTelemetry Collector

Send EMQX telemetry to a Collector first for any of these reasons:

  • You run EMQX 5.x, whose exporter cannot attach the SigNoz ingestion key. The Collector adds it.
  • You want to filter or rename metrics before they reach SigNoz.
  • You want to send the data to more than one backend.
  • The broker cannot connect to your SigNoz instance directly.

Point EMQX at the gRPC port of the Collector, where <collector-host> is the host that runs it. Remove the TLS and header settings, because the Collector now holds the ingestion key:

/etc/emqx/emqx.conf
opentelemetry {
  exporter { endpoint = "http://<collector-host>:4317" }
  metrics { enable = true, interval = "10s" }
  traces { enable = true, filter { trace_all = true } }
  logs { enable = true, level = warning }
}

Then add an OTLP receiver to your existing Collector configuration. Reference it from your metrics, traces, and logs pipelines:

config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

Next Steps

Get Help

If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack. If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.

Is this page helpful

Last updatedSeptember 08, 2026

Edit on GitHub