Export to OpenTelemetry
A self-hosted or bring-your-own-cloud deployment puts the BI Pixie Event Hub in your own Azure subscription. You can read that hub with an OpenTelemetry collector and land BI Pixie engagement events in the observability platform you already run, alongside the rest of your telemetry.
You run the transport, and BI Pixie publishes the semantics: a stable mapping from the BI Pixie event model to OTLP, and a collector configuration that has been run against real events. Nothing leaves your tenant, and BI Pixie opens no new outbound path.
What you need
- A self-hosted or bring-your-own-cloud deployment, so the Event Hub is in your subscription.
- OpenTelemetry Collector Contrib version 0.160.0 or later. The Contrib distribution is required, because the Azure Event Hub receiver is not in the core build.
- A consumer group on the hub that belongs to your collector, and a Listen-only shared access policy.
Give the collector its own consumer group
To give the collector its own consumer group, create a new consumer group on the BI Pixie hub and point the collector at it. The collector cannot share $Default, because BI Pixie's own consumer reads $Default. Two readers on one consumer group take each other's partition leases, and both readers then miss events.
To give the collector access to the hub, create a Listen-only shared access policy for it rather than reusing the namespace's RootManageSharedAccessKey, because the collector only ever reads.
Set the receiver format to raw
When the receiver format is wrong, nothing reaches your backend and nothing reports a problem. The Azure Event Hub receiver defaults to format: azure, which expects Azure diagnostic-log JSON. A BI Pixie event is a flat JSON object, so with the default the receiver reads every event and produces nothing from it. There is no error and no warning. The collector looks healthy and your backend stays empty, which is indistinguishable from a hub with no traffic.
To read BI Pixie events, set format: raw and add the transform from the collector configuration on this page, which parses each event.
Pin the timezone
Each event carries its own timestamp for when the interaction happened. That timestamp has no timezone offset in it. Unless the transform names a timezone, the collector reads the timestamp in whatever timezone the collector host happens to run in. A collector in Chicago then records every event five hours late, and the records look perfectly well formed.
The collector configuration on this page passes "UTC" to the Time function, and the timestamps stay correct only while that argument stays in place. With the "UTC" argument, Timestamp is when the person interacted with the report, and ObservedTimestamp is when your collector read the record, which is what those two fields are for.
What the configuration does with identity
BI Pixie collects viewer user names only when include_user is true in bi_pixie_config.json, and it then keeps the email address, or a masked ID when MASK_USER_NAME is true on the function app. When include_user is false, it counts viewers by a masked IP address.
The collector configuration on this page is private by default, matching the posture of the rest of BI Pixie:
| Field | What happens to it |
|---|---|
| Viewer name | Hashed with SHA256, so per-viewer counts still work and the name does not travel. The name is already masked before it reaches the hub when MASK_USER_NAME is true on the function app. |
| Client IP address | Not mapped at all. |
| Selected filter values | Not mapped at all, because filter selections can carry business data. You can add them deliberately in your own tenant. |
| The raw event body | Cleared, because it carries the viewer name in clear text and would otherwise undo the hash. |
The collector configuration
To use this configuration, fill in your connection string and replace the debug exporter with your own backend.
receivers:
azure_event_hub:
connection: "${env:BIPIXIE_EVENTHUB_CONNECTION}"
group: otel # your own consumer group, never $Default
format: raw # required: the default drops every event silently
processors:
transform/bipixie:
error_mode: propagate
log_statements:
- context: log
statements:
- set(cache["ev"], ParseJSON(Decode(body, "utf-8")))
- set(resource.attributes["service.name"], "bipixie")
- set(resource.attributes["bipixie.license.key"], cache["ev"]["k"])
# "UTC" is required, see Pin the timezone above
- set(time, Time(cache["ev"]["ts"], "%Y-%m-%d %H:%M:%S.%f", "UTC"))
- set(attributes["event.name"], Concat(["bipixie", cache["ev"]["e"]], "."))
- set(attributes["bipixie.project"], cache["ev"]["a"])
- set(attributes["bipixie.report"], cache["ev"]["r"])
- set(attributes["bipixie.page"], cache["ev"]["p"])
- set(attributes["user.name"], SHA256(cache["ev"]["u"])) where cache["ev"]["u"] != nil and cache["ev"]["u"] != ""
- set(attributes["bipixie.bookmark"], cache["ev"]["b"]) where cache["ev"]["b"] != ""
- set(attributes["bipixie.link"], cache["ev"]["ur"]) where cache["ev"]["ur"] != ""
- set(attributes["bipixie.visual.id"], Split(cache["ev"]["d"], ";")[0]) where cache["ev"]["d"] != ""
- set(attributes["bipixie.visual.name"], Split(cache["ev"]["d"], ";")[1]) where cache["ev"]["d"] != ""
- set(attributes["bipixie.visual.type"], cache["ev"]["t"]) where cache["ev"]["t"] != ""
- set(attributes["bipixie.filter.column"], cache["ev"]["fc"]) where cache["ev"]["fc"] != ""
- set(attributes["bipixie.filter.distinct_count"], Int(cache["ev"]["fd"])) where cache["ev"]["fd"] != nil
- set(attributes["bipixie.page.index"], Int(cache["ev"]["pi"])) where cache["ev"]["pi"] != nil
- set(attributes["bipixie.workspace"], cache["ev"]["w"]) where cache["ev"]["w"] != nil
- set(attributes["user_agent.original"], cache["ev"]["user_agent"]) where cache["ev"]["user_agent"] != nil
- set(body, "")
exporters:
debug:
verbosity: detailed
service:
pipelines:
logs:
receivers: [azure_event_hub]
processors: [transform/bipixie]
exporters: [debug] Check that it works
To check the configuration, run the collector with the debug exporter first and open a report that BI Pixie tracks. One record should appear carrying event.name, the report and page names, a hashed viewer name, and a Timestamp that matches when you opened the report rather than when the collector read it.
If the collector reports zero log records, the receiver format is still azure rather than raw, even though the empty result looks like an idle hub.
What this covers
This page covers reading BI Pixie events from the Event Hub, which carries them as a stream.
Events are exported as OTLP log records carrying event.name, which is the OpenTelemetry shape for discrete events. They are not spans. A report interaction has no duration and no parent, so a span would require inventing trace identifiers that mean nothing and would render as a misleading waterfall in your backend.