Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Important
This feature is in Private Preview. To try it, reach out to your Azure Databricks contact.
Use this reference to implement an event producer or write queries over App Analytics events. It defines schema version 1, its OpenTelemetry Protocol (OTLP) encoding, and the processing steps needed before aggregation.
For a first-event example, see Get started with App Analytics. For the dashboard and its additional app-access data, see What is App Analytics?.
Event schema
Each event is an OpenTelemetry log record stored in the app's otel_logs table in Unity Catalog. Use the configured table name, including any prefix. The table also contains other log records, so select analytics events by their schema version and event type.
Payload examples
Select an event type to see its payload. Each example is one OTLP log record, placed in resourceLogs[].scopeLogs[].logRecords[] when exported. The quickstart shows that request wrapper.
The IDs and timestamp below are illustrative. Generate a unique ID for each new event and use its actual occurrence time. The examples omit the optional session_id.
Action
Record a completed report export. The optional format property describes the export without changing the event name.
{
"timeUnixNano": "1790164800000000000",
"eventName": "report_exported",
"attributes": [
{ "key": "databricks.app.analytics.schema.version", "value": { "intValue": "1" } },
{ "key": "databricks.app.analytics.event.id", "value": { "stringValue": "d6c34165-7f4a-467c-b671-50ae495bcb14" } },
{ "key": "databricks.app.analytics.event.type", "value": { "stringValue": "action" } },
{ "key": "databricks.app.analytics.event.name", "value": { "stringValue": "report_exported" } },
{ "key": "databricks.app.analytics.properties.format", "value": { "stringValue": "csv" } }
]
}
Page view
Record a visit to /reports. The event type and event name are both page_view.
{
"timeUnixNano": "1790164800000000000",
"eventName": "page_view",
"attributes": [
{ "key": "databricks.app.analytics.schema.version", "value": { "intValue": "1" } },
{ "key": "databricks.app.analytics.event.id", "value": { "stringValue": "caafc8e3-5f19-4f22-934b-09d1f8bf8e63" } },
{ "key": "databricks.app.analytics.event.type", "value": { "stringValue": "page_view" } },
{ "key": "databricks.app.analytics.event.name", "value": { "stringValue": "page_view" } },
{ "key": "databricks.app.analytics.page.path", "value": { "stringValue": "/reports" } }
]
}
Web Vital
Record a Largest Contentful Paint (LCP) measurement of 1,800 milliseconds. This value has a good rating.
{
"timeUnixNano": "1790164800000000000",
"eventName": "lcp",
"attributes": [
{ "key": "databricks.app.analytics.schema.version", "value": { "intValue": "1" } },
{ "key": "databricks.app.analytics.event.id", "value": { "stringValue": "a23eecb7-b6c1-47c8-92ab-e2856f6ac804" } },
{ "key": "databricks.app.analytics.event.type", "value": { "stringValue": "web_vital" } },
{ "key": "databricks.app.analytics.event.name", "value": { "stringValue": "lcp" } },
{ "key": "databricks.app.analytics.page.path", "value": { "stringValue": "/reports" } },
{ "key": "databricks.app.analytics.web_vital.value", "value": { "doubleValue": 1800 } },
{ "key": "databricks.app.analytics.web_vital.unit", "value": { "stringValue": "ms" } },
{ "key": "databricks.app.analytics.web_vital.delta", "value": { "doubleValue": 1800 } },
{
"key": "databricks.app.analytics.web_vital.sample_id",
"value": { "stringValue": "30990ea0-3ec1-4a93-98b9-7c9abfe0e2d8" }
},
{ "key": "databricks.app.analytics.web_vital.rating", "value": { "stringValue": "good" } }
]
}
Reports for the same measurement share a web_vital.sample_id. Each new report has its own event.id. See Web Vital rules.
Common fields
The following logical fields apply across event types. Their OTLP attribute names appear in the examples above.
| Field | Type | Requirement | Description |
|---|---|---|---|
schema_version |
INT |
Required | Major contract version. Use 1. |
event_id |
STRING |
Required | Globally unique, opaque identifier. Preserve it when retrying an event. |
event_type |
STRING |
Required | action, page_view, or web_vital. |
event_name |
STRING |
Required | A low-cardinality name within the event type. |
occurred_at |
TIMESTAMP |
Required | When the event occurred, not when it was ingested. |
session_id |
STRING |
Optional | An opaque identifier for an analytics activity window. It doesn't identify a person. |
page_path |
STRING |
Optional | A sanitized concrete app path, such as /orders/42. |
properties |
Object | Optional | Application-defined scalar properties. |
session_id is optional in the event schema. Session-based metrics use the records that supply it. Don't manufacture an identity or use an empty string to represent an absent session.
Required strings must be nonempty and must not contain whitespace, credentials, or personal data. When an optional field is present, it must conform to its field rules. An absent optional attribute reads as null in the logical schema.
Event types
| Event type | Event name | Additional fields |
|---|---|---|
action |
An application-defined name, such as report_exported. |
Optional properties describe the action. |
page_view |
page_view |
An optional page_path identifies the visited route. |
web_vital |
lcp, inp, cls, fcp, or ttfb |
The Web Vital fields defined in Web Vital rules. |
A captured action describes what the producer recorded. For example, a click can record intent without proving that the operation succeeded.
OTLP encoding and table fields
Analytics attributes use the databricks.app.analytics. namespace. The following table maps the logical fields to OTLP and the telemetry table:
| Logical field | OTLP source | otel_logs source |
|---|---|---|
occurred_at |
timeUnixNano |
time |
schema_version |
databricks.app.analytics.schema.version |
attributes:["databricks.app.analytics.schema.version"] |
event_id |
databricks.app.analytics.event.id |
attributes:["databricks.app.analytics.event.id"] |
event_type |
databricks.app.analytics.event.type |
attributes:["databricks.app.analytics.event.type"] |
event_name |
databricks.app.analytics.event.name |
attributes:["databricks.app.analytics.event.name"] |
session_id |
databricks.app.analytics.session.id |
attributes:["databricks.app.analytics.session.id"] |
page_path |
databricks.app.analytics.page.path |
attributes:["databricks.app.analytics.page.path"] |
properties.<key> |
databricks.app.analytics.properties.<key> |
The corresponding key in attributes. |
web_vital_<field> |
databricks.app.analytics.web_vital.<field> |
The corresponding key in attributes. |
Use the following OTLP JSON encodings:
- Timestamps: A decimal string of Unix nanoseconds in
timeUnixNano. - Integers: A decimal-string
intValue, such as{ "intValue": "1" }. - Doubles: A numeric
doubleValue, such as{ "doubleValue": 1.25 }. - Strings:
stringValue. - Booleans:
boolValue.
Set the log record's eventName to the same value as the analytics event_name attribute. The instrumentation scope.name identifies the producer, but isn't a selector for analytics records.
The telemetry table can retain a single-field doubleValue wrapper for numeric values, including zero. Decode that wrapper before validating numeric fields. Validate the original value's type before casting it, rather than accepting a numeric-looking string as a number.
The physical date column is available for partition pruning. It isn't part of the logical event schema.
Field constraints
Sanitize fields before export. Validation and projection in a query don't remove raw data already stored in the telemetry table.
Action names and properties
Action names contain 1 to 128 characters. They begin with a lowercase letter and use lowercase letters, digits, dots, and underscores. Dots and underscores separate alphanumeric segments. The grammar is ^[a-z][a-z0-9]*([._][a-z0-9]+)*$.
Use stable names instead of embedding IDs, email addresses, or user-entered text. Put varying details in properties. For example, use report_exported with a format property rather than a different action name for every format.
Property keys follow the same grammar. Don't use reserved namespaces or sensitive segments, including analytics, identity, session, telemetry, url, user, users, email, password, passwd, secret, token, authorization, cookie, username, ip, credential, api_key, private_key, html, dom, selector, selectors, stack, stacktrace, text, input, or referrer. These restrictions also apply to action names.
Properties can contain scalar strings, finite numbers, and booleans. Arrays, nested objects, null values, credentials, and personal data aren't permitted.
| Limit | Value |
|---|---|
| Properties per event | 50 |
| Property-key length | 128 characters |
| String-value length | 1,024 characters |
Query projections omit properties that don't meet these constraints. Producers remain responsible for the meaning of the values they send. Syntactic checks alone can't establish that a value is non-personal.
Page paths
A page_path begins with / and contains the concrete application path. Remove query strings and fragments, and sanitize sensitive path segments. Don't send credentials, personal data, or user-entered text in a path.
An invalid path is omitted from the query projection. A page view without a path remains valid, but doesn't contribute to route-level aggregations.
Sessions
An analytics session belongs to a top-level browser context. When a producer supplies a session ID, keep it stable across client-side navigation and reloads, and renew it after 30 minutes without a recorded analytics event.
Session IDs must be opaque and must not derive from user identities, email addresses, IP addresses, request IDs, job IDs, or access tokens. Backend events preserve the originating session when one is available. Replayed events preserve their original session and event time.
A session measures an activity window, not a unique person. Viewer metrics in the product can use separate app-access data.
Web Vital rules
Web Vital events add the following fields. Encode value and delta as OTLP doubleValue attributes, and the remaining fields as stringValue attributes. Web Vital-specific fields must be absent on actions and page views.
| Field | Requirement | Description |
|---|---|---|
web_vital_value |
Required | Finite, nonnegative cumulative value for the sample. |
web_vital_unit |
Required | ms or score, determined by the metric. |
web_vital_delta |
Required | Finite, nonnegative change since the previous report. |
web_vital_sample_id |
Required | Globally unique identifier shared by reports for one sample. |
web_vital_rating |
Required | good, needs_improvement, or poor, derived from the value and metric thresholds. |
web_vital_navigation_type |
Optional | navigate, reload, back_forward, back_forward_cache, prerender, or restore. |
The following table defines the units and thresholds:
| Metric | Name | Unit | Good | Poor |
|---|---|---|---|---|
lcp |
Largest Contentful Paint (LCP) | ms |
≤ 2,500 | > 4,000 |
inp |
Interaction to Next Paint (INP) | ms |
≤ 200 | > 500 |
cls |
Cumulative Layout Shift (CLS) | score |
≤ 0.1 | > 0.25 |
fcp |
First Contentful Paint (FCP) | ms |
≤ 1,800 | > 3,000 |
ttfb |
Time to First Byte (TTFB) | ms |
≤ 800 | > 1,800 |
LCP, INP, and CLS are Core Web Vitals. FCP and TTFB provide additional performance measurements. A value is good at or below the Good threshold, poor above the Poor threshold, and needs_improvement between them. A mismatched unit or rating makes the record invalid.
A sample can produce multiple reports. Web Vitals describe document navigation. A client-side route change can produce a page view without producing a new Web Vital sample. A server can forward a browser measurement, but server timings aren't a substitute for that measurement.
Validation order
Process records in the following order:
- Select supported schema versions and event types. For this schema, select version
1andaction,page_view, orweb_vital. - Decode the typed attributes and require valid
event_id,event_name, andoccurred_atvalues. - Validate event-type fields, names, units, ratings, and field constraints. Exclude records with invalid required fields and project only permitted optional data.
- Detect conflicting event IDs and deduplicate identical deliveries.
- Select the analysis interval and any path or event filters.
- For Web Vitals, reduce repeated reports to one report per sample.
- Aggregate the resulting events or samples.
Keep validation separate from ingestion diagnostics. Finding a row in otel_logs doesn't establish that it conforms to the event schema.
Deduplication and sample reduction
Identical deliveries of an event count as one event. If an event_id is reused for different logical event content, exclude every record in that conflict set and report the conflict as a data-quality issue. Don't choose an arbitrary delivery from the set.
Compare normalized logical content, including optional fields and properties. Use a deterministic representation of properties so that key ordering doesn't create false conflicts. Transport metadata isn't part of the logical event content. Retries preserve the original event timestamp.
Check identity across the retained deliveries for each candidate event ID before applying the analysis time range. Filtering raw deliveries first can hide conflicting content outside that range.
After deduplication and selection of the analysis interval, retain the latest report per web_vital_sample_id. Order reports by occurred_at, breaking ties with event_id, both descending. Calculate percentiles after this reduction.
Metric definitions
Time intervals include start_time and exclude end_time. Time buckets use Coordinated Universal Time (UTC).
| Metric | Definition |
|---|---|
| Page views | Number of deduplicated page_view events. |
| Sessions | Number of distinct, non-null session_id values among the selected analytics events. |
| Tracked actions | Number of deduplicated action events. |
| Pages per session | Page views divided by sessions. No value when the session count is zero. |
| Top actions | Actions grouped by event_name. |
| Top pages | Page views grouped by page_path, excluding missing paths. |
| Web Vital p75 | Nearest-rank 75th percentile after event deduplication and sample reduction. |
| Web Vital sample size | Number of distinct samples after reduction. |
| Latest event | Greatest occurred_at among the selected analytics events. |
For N ordered samples, nearest-rank p75 uses the value at position ceil(0.75 × N), with one-based indexing. Treat fewer than 30 Web Vital samples as indicative rather than conclusive evidence of a trend.
This schema doesn't define user identity, device, browser, or geography dimensions. Product surfaces can display additional metrics from other sources. In particular, the dashboard's unique viewers aren't computed by counting analytics sessions.
Example: calculate Web Vital p75 from validated events
The following query demonstrates collision exclusion, deduplication, sample reduction, and exact percentile calculation. It consumes a normalized dataset, not the raw otel_logs table.
Prepare the input
Create an input view in your own processing pipeline that applies steps 1 through 3 of Validation order. The view must contain the following data:
- One row per delivery that passes validation, before deduplication or time-range filtering.
- The typed logical fields, including
event_id,occurred_at,event_type,event_name,web_vital_value, andweb_vital_sample_id. - A non-null
logical_contentstring containing a deterministic representation of all normalized event fields and properties. Represent absent optional fields consistently and preserve the original event time.
Scope the input to the app you are analyzing. Include retained deliveries of all supported event types so that reuse of an event ID across types is detected. The input must enforce all required fields, numeric types and ranges, units, ratings, and event-type constraints described above.
Important
This input view is not created by enabling app telemetry. It is a prerequisite supplied by your processing pipeline. Casting a few raw attributes isn't a substitute for this validation step.
Aggregate the validated input
Replace <catalog>.<schema>.<validated-events-view> with your input view. Set :start_time and :end_time as timestamp parameters in the SQL editor.
WITH event_id_quality AS (
SELECT event_id, COUNT(DISTINCT logical_content) AS content_count
FROM <catalog>.<schema>.<validated-events-view>
GROUP BY event_id
),
deduplicated AS (
SELECT events.*
FROM <catalog>.<schema>.<validated-events-view> AS events
JOIN event_id_quality AS quality USING (event_id)
WHERE quality.content_count = 1
QUALIFY ROW_NUMBER() OVER (
PARTITION BY event_id ORDER BY occurred_at
) = 1
),
samples AS (
SELECT *
FROM deduplicated
WHERE event_type = 'web_vital'
AND occurred_at >= :start_time
AND occurred_at < :end_time
QUALIFY ROW_NUMBER() OVER (
PARTITION BY web_vital_sample_id ORDER BY occurred_at DESC, event_id DESC
) = 1
)
SELECT
event_name,
COUNT(*) AS sample_size,
PERCENTILE_DISC(0.75) WITHIN GROUP (ORDER BY web_vital_value) AS p75
FROM samples
GROUP BY event_name
ORDER BY event_name;
PERCENTILE_DISC returns the observed value at the requested rank rather than an approximation. The query excludes conflicting event IDs before selecting the time range. Track IDs whose content_count exceeds one separately as data-quality errors.
For example, three reduced LCP samples with values of 100, 200, and 300 milliseconds produce sample_size = 3 and p75 = 300. Identical redeliveries don't increase that sample count. An event ID with conflicting normalized content contributes no record.
Versioning
schema_version identifies the major contract version independently of producer package versions.
Adding optional fields or application-defined action names is backward compatible. Removing required fields or changing their types, meanings, identifier scope, or session semantics requires a major version change. Changes to deduplication, sample reduction, or event-type semantics also require a new major version.
Consumers ignore unsupported versions and event types instead of interpreting them as actions.