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