Diagnostics API
The presentation manager provides a way to gather diagnostics (performance metrics, logs, etc.) on a per-request basis. The APIs that should be used to get the diagnostics depend on the requirements and whether diagnostics are needed on the backend or the frontend.
In all cases getting diagnostics consists of two pieces:
- Options - what kind of diagnostics are requested.
- Handler - a function that accepts the diagnostics after the request is fulfilled.
Getting request diagnostics on the frontend
On the frontend side diagnostics can be requested on a per request basis, by supplying diagnostics options through diagnostics
attribute to PresentationManager requests. Resulting diagnostics are then passed to the given handler.
await Presentation.presentation.getElementProperties({
imodel,
elementId,
diagnostics: {
// request version of the backend that handles this request
backendVersion: true,
// supply a callback that'll receive the diagnostics
handler: (diagnostics: ClientDiagnostics) => {
// log the backend version
log(`Backend version: ${diagnostics.backendVersion}`);
},
},
});
Getting request diagnostics on the backend
There are two ways to retrieve diagnostics on the backend - through request parameters or through PresentationManager.
Getting diagnostics on a per-request basis
To get diagnostics on a per request basis, diagnostics options can be supplied through diagnostics
attribute to PresentationManager requests. Resulting diagnostics are then passed to the given handler.
await Presentation.getManager().getElementProperties({
imodel,
elementId,
diagnostics: {
// request performance metrics
perf: true,
// supply a callback that'll receive the diagnostics
handler: (diagnostics: Diagnostics) => {
// log duration of each diagnostics scope
diagnostics.logs && diagnostics.logs.forEach((entry) => {
log(`${entry.scope}: ${entry.duration}`);
});
},
},
});
Getting diagnostics for all requests
It's also possible to set up PresentationManager to retrieve diagnostics of every request made through it. This can be done by supplying diagnostics options, including the handler, when calling Presentation.initialize.
Presentation.initialize({
diagnostics: {
// request performance metrics
perf: true,
// supply a method to capture current request context
requestContextSupplier: getCurrentActivityId,
// supply a callback that'll receive the diagnostics and request context supplied by `requestContextSupplier`
handler: (diagnostics: Diagnostics, currentActivityId?: string) => {
// log duration of each diagnostics scope
diagnostics.logs && diagnostics.logs.forEach((entry) => {
log(`[${currentActivityId}] ${entry.scope}: ${entry.duration}`);
});
},
},
});
// diagnostics of the following requests are captured by the handler supplied to `Presentation.initialize` call
await Presentation.getManager().getElementProperties({ imodel, elementId: id1 });
await Presentation.getManager().getElementProperties({ imodel, elementId: id2 });
This approach also allows the backend to use request diagnostics for telemetry and logging, e.g. in combination with OpenTelemetry. See the Diagnostics and OpenTelemetry section for more details.
Diagnostics and OpenTelemetry
OpenTelemetry is a vendor-neutral standard to collect telemetry data - metrics, logs and traces. The @itwin/presentation-opentelemetry
package provides APIs to easily convert presentation diagnostics objects to OpenTelemetry objects, which makes collecting Presentation-related telemetry much easier.
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
import { context, trace } from "@opentelemetry/api";
import { convertToReadableSpans } from "@itwin/presentation-opentelemetry";
import { Presentation } from "@itwin/presentation-backend";
const traceExporter = new OTLPTraceExporter({
url: "<OpenTelemetry collector's url>",
});
Presentation.initialize({
diagnostics: {
// requesting performance metrics
perf: true,
// a function to capture current context so it can be used when the handler function is called
requestContextSupplier: () => {
// get the parent span that our diagnostics should nest under - it'll be supplied
// as the second argument to the `handler` function
return trace.getSpan(context.active())?.spanContext();
},
// the handler function is called after every request made through the `Presentation` API
handler: (diagnostics, parentSpanContext) => {
// convert diagnostics to OpenTelemetry spans
const spans = convertToReadableSpans(diagnostics, parentSpanContext);
// do export
traceExporter.export(spans, () => {});
},
},
});
Last Updated: 30 November, 2023