AnnouncementsIntroducing Arkoren

    Menu

    Monitoring

    Propagate trace context through notifications and interpret broadcast spans and metrics without recording message contents.

    What's on this page
    1. Connect producer and consumer traces through message metadata.
    2. Carry bounded application metadata without treating it as routing or authorization.
    3. Interpret broadcast spans and metrics as attempts and deliveries, not unique effects.
    4. Protect broker data and verify instrumentation with recording fakes.
    On this page

    An order request can publish one notification that several services handle later. Trace propagation connects those deliveries to the publication; broadcast spans and metrics show transport calls and listener execution. They do not prove that every intended recipient received the notification.

    Broadcasting carries trace context in metadata, a string map beside the application payload. Telemetry can record routing names and error classes without recording payloads, metadata values, or error text. Broker readers can still read the message body, including that metadata.

    Connecting Telemetry

    By default, publishers and consumers resolve the shared telemetry handle at each operation. Its initial no-op records and propagates nothing. If your application configures that shared handle, facade construction needs no telemetry override.

    For an explicit application handle, configure both sides. With an existing app and configured broadcast driver:

    import "arkoren.dev/core/service/broadcast"
    
    publisher := broadcast.NewPublisher(driver, func(sender *broadcast.PublisherBuilder) {
    	sender.Telemetry(app.Telemetry())
    })
    consumer := broadcast.NewConsumer(driver, func(receiver *broadcast.ConsumerBuilder) {
    	receiver.Telemetry(app.Telemetry())
    })

    The publisher injects context using the telemetry driver’s propagator; the consumer extracts it before running listeners. Both sides need compatible propagation. Export configuration belongs in the telemetry guide. Instrumentation does not add retention, retries, or different listener routing.

    Message Metadata

    The typed Publisher.Publish operation has no metadata parameter. It allocates a fresh propagation map when enabled telemetry injects values, such as W3C traceparent and tracestate. Typed listeners receive only their decoded payload; pattern listeners can inspect the whole envelope.

    If you integrate directly with a driver, you can supply application metadata yourself. This intentionally bypasses facade JSON encoding and producer telemetry. With a configured driver, ctx, and broadcast imported, send valid payload JSON plus a nonsecret correlation identifier:

    message := broadcast.Message{
    	Name:    "order.created",
    	Payload: []byte(`{"order_id":"o-1042"}`),
    	Metadata: map[string]string{
    		"correlation-id": "order-import-7",
    	},
    }
    err := driver.Publish(ctx, message)

    A receiving pattern listener sees message.Metadata["correlation-id"] as order-import-7. Metadata does not select listeners or authorize the message. To observe that field with an existing application logger, allowlist a nonsecret value and treat values from external publishers as untrusted:

    unsubscribe, err := consumer.ListenPattern("order.#",
    	func(ctx context.Context, message broadcast.Message) error {
    		logger.InfoContext(ctx, "order notification",
    			"correlation", message.Metadata["correlation-id"])
    		return nil
    	})
    if err != nil {
    	return err
    }
    defer unsubscribe()

    Missing metadata permits map lookup and yields an empty string. Do not log a field merely because it is metadata. Keep payload bytes and the metadata map read-only because matching listeners share them.

    Drivers preserve metadata within this portable round-trip guarantee:

    Limit Value
    Entries At most 16.
    Key 1–64 bytes from A-Za-z0-9._-, case-sensitive.
    Value Valid UTF-8.
    Combined Size At most 4096 bytes of keys and values together.

    The constants are MaxMetadataEntries, MaxMetadataKeyLength, and MaxMetadataSize in broadcast. Beyond these limits a driver may reject a message. Nil and empty metadata are equivalent. A message published without metadata arrives with a nil map; existing messages without metadata remain decodable.

    External drivers carry metadata in the JSON envelope body, not MQTT user properties, AMQP headers, or SNS propagation attributes. Those protocols’ header limits therefore do not define this map’s limits; broker message-size limits still apply.

    Reading Spans

    A publish that reaches the driver records producer span send order.created. A valid message routed by the consumer records process order.created around all matching listeners. When propagation is present and extracted, the process span is a child of the send span in the same trace. Listener contexts carry that span, allowing context-aware logs and downstream calls to join the trace.

    Three independent recipients can therefore produce three child process spans. Redelivery adds more process spans; a process span is not an exactly-once receipt. A valid unmatched delivery also records processing even though no listener effect occurs.

    Attribute Meaning
    messaging.system Driver system, or an explicit override.
    messaging.operation.name send or process.
    messaging.operation.type send or process; spans only.
    messaging.destination.name Logical broadcast name.
    messaging.message.id Nonempty delivery ID; spans only.
    error.type Error class on failure.

    Driver systems are memory, redis, nats, rabbitmq, mqtt, aws_sns, and aws_sqs. SNS publication followed by SQS consumption correctly shows different systems on the two sides. PublisherBuilder.System and ConsumerBuilder.System override the driver name. If neither supplies one, the value is _OTHER. Broadcast spans do not include queue job or attempt attributes.

    Invalid publication names and payload-encoding errors occur before send instrumentation. Invalid delivery names, or broker-driver rejection of malformed or routing-mismatched envelopes, occur before facade process instrumentation. They produce no corresponding facade span or metric. Use provider diagnostics for transport drops; absence of a process span alone does not identify the cause.

    Reading Metrics

    All four instruments use scope arkoren.dev/core/service/broadcast, distinct from queues even though their metric names are shared:

    Metric What It Measures
    messaging.client.sent.messages Publish attempts that reach the driver, including failures.
    messaging.client.consumed.messages Valid routed deliveries, including failures and repeats.
    messaging.client.operation.duration Publish-call duration, in seconds.
    messaging.process.duration All matching listeners’ processing duration per delivery, in seconds.

    Metric attributes are messaging.system, messaging.operation.name, messaging.destination.name, and error.type on failure. Counts are neither unique application events nor successful recipient effects. One facade call with an internal RabbitMQ publication retry still produces one send observation.

    A failed publish or listener delivery marks its span as erroneous and records an error class, never the error text. Cancellation and deadline errors use context.Canceled and context.DeadlineExceeded. Otherwise classification follows the single-cause unwrap chain; a multi-cause error has no single root and uses its aggregate type. Listener failure events provide original errors to your application when you need sensitive diagnostics.

    Protecting Data

    Recorded broadcast names, systems, durations, error classes, and optional message IDs are still operational data. Keep names stable and nonsecret: do not embed customer identifiers or credentials in routing names. Many distinct names also create metric cardinality; use bounded metric retention when publishers can generate many destinations.

    The facade does not record payloads, metadata values, error messages, or panic stack traces into its telemetry. That boundary does not sanitize your logs, event observers, native SDK errors, or custom spans. Nor does it encrypt trace context inside broker messages. Restrict broker access and use transport encryption; never put credentials in metadata. See Telemetry Privacy for deployment-wide controls.

    Testing Signals

    Use telemetrytest with the broadcast recording fake to check instrumentation without a broker. In a test with t *testing.T, import the two fake packages and configure both facades with the same recorder:

    import (
    	"arkoren.dev/core/service/broadcast/broadcasttest"
    	"arkoren.dev/core/service/telemetry/telemetrytest"
    )
    
    signals := telemetrytest.New()
    fake := broadcasttest.New()
    recorder := signals.Telemetry(t)
    publisher := broadcast.NewPublisher(fake, func(sender *broadcast.PublisherBuilder) {
    	sender.Telemetry(recorder)
    })
    consumer := broadcast.NewConsumer(fake, func(receiver *broadcast.ConsumerBuilder) {
    	receiver.Telemetry(recorder)
    })

    With the order topic, publish and route one notification, then assert the two observations:

    if err := publisher.Publish(t.Context(), OrderCreatedTopic, OrderCreated{OrderID: "o-1042"}); err != nil {
    	t.Fatal(err)
    }
    if err := fake.Drain(t.Context(), consumer); err != nil {
    	t.Fatal(err)
    }
    signals.AssertSpan(t, "send order.created")
    signals.AssertSpan(t, "process order.created")

    No listener is needed to record a valid routed delivery; register one separately when checking its effect. The fake has no system name, so its spans report _OTHER unless overridden. These assertions prove instrumentation ran, not network propagation, parentage, or recipient success. See Capture Telemetry for inspecting recorded spans and metrics more precisely.

    For every function and option, see the broadcast API and telemetry API.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive