OpenTelemetry (Diagnostic) Integration
The OpenTelemetry integration adds the diagnostic headers required for distributed tracing to every service request leaving the application. This makes it observable which application, organization, model (page), and channel a request was triggered from.
The integration consists of two parts:
- Metadata generation (export/build side): When the application is exported, structured studio metadata (application, model, organization, org group, and tag information) is injected into each QJson's
opt.studiofield. - Header injection (runtime side): Inside the Pipeline
onBeforeRequestevent, this metadata is read viaquick.studioand written to the request headers (diag-nc,diag-fc,diag-ch).
This document explains the setOTel function that runs in the Pipeline onBeforeRequest event, section by section.
The values under quick.studio are resolved at runtime from the metadata written into the QJson's opt.studio field during export. Fields such as parentModel and rootModel are computed at runtime from the page hierarchy (the RenderingComponent chain).
The setOTel Function
The function is defined inside the Pipeline onBeforeRequest event and is invoked before every request is sent. Its purpose is to add the diagnostic headers onto requestObject.headers.
let setOTel = () => {
// ... the sections below ...
};
setOTel();
1. Reading the Studio Metadata
In the first section, the identifying information of the page that triggered the request is read from the quick.studio global object.
let appName = quick.studio.app.name; // Application name
let modelID = quick.studio.model.id; // Model (page) ID
let modelName = quick.studio.model.name; // Model (page) name
let orgGroupName = quick.studio.orgGroupName; // Organization group name
let orgName = quick.studio.org.name; // Organization name
let aciCode = quick.studio.tags['ACICode']; // ACICode value from the studio tags
| Field | Source | Description |
|---|---|---|
appName | quick.studio.app.name | Name of the application making the request. |
modelID | quick.studio.model.id | ID of the model (page) triggering the request. |
modelName | quick.studio.model.name | Name of the model (page) triggering the request. |
orgGroupName | quick.studio.orgGroupName | Organization group the application belongs to. |
orgName | quick.studio.org.name | Organization the application belongs to. |
aciCode | quick.studio.tags['ACICode'] | Application/model diagnostic code (ACICode) read from the studio tags. |
quick.studio.tags holds all tag values defined on studio as { tagLabel: value }. Values such as ACICode and Channel are read from this dictionary.
2. Building the Diagnostic String (otelStr)
The values read above are concatenated according to a fixed template to form a single diagnostic string. This string carries the entire contextual identity of the request in one piece of text.
let otelStr = `${aciCode}.${modelName}.PLATEAUUI.ORGGRP-${orgGroupName}.ORG-${orgName}.APP-${appName}.${modelID}`;
The parts of the template:
| Part | Example | Description |
|---|---|---|
${aciCode} | ABC123 | Starts with the ACICode. |
${modelName} | HomePage | Model (page) name. |
PLATEAUUI | PLATEAUUI | Constant indicating the request comes from the Plateau UI layer. |
ORGGRP-${orgGroupName} | ORGGRP-Finance | Organization group. |
ORG-${orgName} | ORG-AcmeCorp | Organization. |
APP-${appName} | APP-Portal | Application name. |
${modelID} | a1b2c3 | Ends with the model ID. |
Example output:
ABC123.HomePage.PLATEAUUI.ORGGRP-Finance.ORG-AcmeCorp.APP-Portal.a1b2c3
3. Distinguishing Root and Parent Model
A page can be rendered inside another page (for example, via a RenderingComponent). In that case, the "page making the request" and the page that actually initiated this request may differ. To mark that a request came through an integration page, the initiator's information is appended to the diagnostic string.
parentModel→ The page rendered directly under the root page (theqjsonPaththe RenderingComponent currently targets).rootModel→ The outermost (root) page; the top of the page hierarchy.
Both fields are optional — if the current page is not embedded inside another page, they resolve to undefined. For this reason they are read with optional chaining (?.).
// RenderingComponent (parent) — the page directly under the root page:
let parentName = quick.studio.parentModel?.name;
let parentAci = quick.studio.parentModel?.tags?.['ACICode'];
// Root model — the topmost (outermost) page in the hierarchy:
let rootName = quick.studio.rootModel?.name;
let rootAciCode = quick.studio.rootModel?.tags?.['ACICode'];
// The initiator is the parent when present, otherwise the root:
let initiatorName = parentName ? parentName : rootName;
let initiatorAciCode = parentAci ? parentAci : rootAciCode;
The initiator is resolved with a parent-first, root-fallback rule:
- If a page is called directly (not embedded in another page), both
parentModelandrootModelareundefined, so the initiator stays empty and no integration suffix is added. - If a page is embedded through the RenderingComponent chain,
parentModel(the closest enclosing page) is preferred as the initiator; when only the root context is available,rootModel(the outermost page) is used instead.
4. Appending the Integration (INT) Information
If an initiator was resolved (i.e., the page runs embedded inside another page), the initiator's identity is appended to the diagnostic string as integration information.
if (initiatorAciCode && initiatorName) {
otelStr += `.INT-${initiatorName}.INTC-${initiatorAciCode}`;
}
Meaning of the condition:
initiatorAciCodeandinitiatorNamemust be non-empty — both are populated only when a parent or root context actually exists. If the page is running on its own, they areundefinedand the integration suffix is skipped.
The appended parts:
| Part | Description |
|---|---|
INT-${initiatorName} | Name of the initiator page the integration came from. |
INTC-${initiatorAciCode} | ACICode of the initiator page (integration code). |
5. Writing the Diagnostic Headers to the Request
The resulting otelStr is written to the request headers. Thanks to the || otelStr usage, if a header was already filled (for example, by the calling page), it is not overwritten; it is only filled when empty.
requestObject.headers['diag-nc'] = requestObject.headers['diag-nc'] || otelStr;
requestObject.headers['diag-fc'] = requestObject.headers['diag-fc'] || otelStr;
| Header | Description |
|---|---|
diag-nc | Diagnostic "near context" — carries the context currently making the request. |
diag-fc | Diagnostic "full/first context" — carries the originating context of the request. |
The || otelStr pattern preserves the initial context in chained requests (where one page calls another): if the header is already set, the new value is not written, so the originating context is not lost.
6. Writing the Channel Information
Finally, the channel the request came from (for example web, mobile, container) is written to the diag-ch header. The channel is taken first from the container service; if the container does not provide one, the Channel studio tag is used as a fallback.
let channel = quick.studio.tags['Channel'];
let containerChannel = quick.containerServices.extensions?.['getChannel']?.();
requestObject.headers['diag-ch'] = containerChannel || channel;
containerChannel→ The channel obtained from the container services'getChannelextension. This is the primary source. Withextensions?.['getChannel']?.(), if the extensions object or the extension itself is not defined, it safely returnsundefined.channel→ TheChanneltag defined on studio, used as a fallback when the container does not provide a channel.containerChannel || channel→ The container channel takes precedence; otherwise the studio tag is used.
Full Code
let setOTel = () => {
let appName = quick.studio.app.name;
let modelID = quick.studio.model.id;
let modelName = quick.studio.model.name;
let orgGroupName = quick.studio.orgGroupName;
let orgName = quick.studio.org.name;
let aciCode = quick.studio.tags['ACICode'];
// Example diagnostic string
let otelStr = `${aciCode}.${modelName}.PLATEAUUI.ORGGRP-${orgGroupName}.ORG-${orgName}.APP-${appName}.${modelID}`;
// RenderingComponent (parent) — the page directly under the root page:
let parentName = quick.studio.parentModel?.name;
let parentAci = quick.studio.parentModel?.tags?.['ACICode'];
// Root model — the topmost (outermost) page in the hierarchy:
let rootName = quick.studio.rootModel?.name;
let rootAciCode = quick.studio.rootModel?.tags?.['ACICode'];
// The initiator is the parent when present, otherwise the root:
let initiatorName = parentName ? parentName : rootName;
let initiatorAciCode = parentAci ? parentAci : rootAciCode;
// If an initiator was resolved, append the integration information:
if (initiatorAciCode && initiatorName) {
otelStr += `.INT-${initiatorName}.INTC-${initiatorAciCode}`
}
// Do not overwrite if the header is already filled; only fill when empty (preserves the originating context):
requestObject.headers['diag-nc'] = requestObject.headers['diag-nc'] || otelStr;
requestObject.headers['diag-fc'] = requestObject.headers['diag-fc'] || otelStr;
// Channel: container service channel first, studio tag as fallback:
let channel = quick.studio.tags['Channel'];
let containerChannel = quick.containerServices?.extensions?.['getChannel']?.();
requestObject.headers['diag-ch'] = containerChannel || channel;
};
setOTel();
setOTelmust run in the Pipeline onBeforeRequest event; the headers are added before the request is sent.- For
quick.studiovalues to be populated, the studio metadata (opt.studio) must have been injected into the QJsons during application export. - Keep the
|| otelStrpattern; if removed, the originating context is overwritten in chained requests.