Skip to main content

EVA Client telemetry

The main error tracking and performance tool we use for our EVA applications is sentry.io.

We require the use of the following integrations/features:

  • error capture: console plugin
  • navigation: url routing (sentry/browser or platform specific)
  • performance: transaction tracing

Default Tags#

Sentry works with the concept of tags that are added to events sent to their platform. These tags provide the context we need for filtering and reporting. The tags will allow us to pinpoint topics like customers, specific organization unit or a specific sequence of calls. To ensure consistent information across our clients we will dictate a minimum set of tags that must be implemented by all EVA applications with the same names and values.

These are tags Sentry provides standard or through the required configuration:

  • sentry project (app)
  • sentry environment (prod, other, etc)
  • OS / Patch levels, device properties

NOTE: Do not confuse the sentry environment with the EVA environment. See customer manager section below for that information.

From current user#

The currently logged in (or anonymous default user) provides an important amount of context. The following tags must be provided from the EVA.Core.GetCurrentUserResponse:

  • countryId: CurrentCountryID
  • language: CurrentLanguageID
  • organizationUnitId: CurrentOrganizationID
  • organizationUnitType: CurrentOrganizationUnitType
  • organizationUnit: CurrentOrganizationName

From current stations#

Our sales focused applications work using a selected stations. This station identifier is also important for legal reasons. The station ID can be gotten from the EVA.Core.ListStationsForOrganizationUnit response of which one will be selected as being current in the application state.

  • stationId: EVA.Core.StationDto.ID from Application state

From EVA Customer manager#

The EVA Customer Manger is our premiere source of customer and environment configuration. Our CI and build setup always generates an eva-config.json file with the configuration used for the build. This file needs to be read and its values provided as tags on each transaction.

Example:

{  "name": "prod",  "customerName": "The customer",  "endpoint": "https://api.customer.prod.eva-online.cloud"}
  • configName: name
  • configCustomerName: customerName
  • configEndpoint: endpoint

Optional tags#

There are tags which are more specific to certain types of applications. Below is the list of these tags.

  • platform: things electron, cordova, capacitor, etc

Error capture#

Using integrations: https://docs.sentry.io/platforms/javascript/configuration/integrations/plugin/#captureconsole

Set to track console message of level Error.

Navigation#

This will be a platform specific integration. For web apps it will be the browser router. This will provide basic page load times and page impression details.

See the docs for more details: https://docs.sentry.io/platforms/javascript/performance/instrumentation/automatic-instrumentation/#beforenavigate

The application will need to adjust the url patterns for better tracking. For instance urls like /order/123/details and /order/456/details/ should both be tracked as /order/<digits>/details to remain comparable.

Performance#

Using tracing: https://docs.sentry.io/platforms/javascript/performance/

Analysing performance issues is complicated and it is vital to be able to layers like the network, hardware and software. Sentry collects timing information in transactions which contain 1 or more spans (operations).

We will primarily be focused on 2 types of transactions:

  • Service transaction
  • Application transaction

Service transactions#

These are setup for lower level service tracking and direct correlation to server side logging. Each fetch request made using our SDK will set a header called EVA-App-ContextID which provides a unique identifier to trace a unique instance of a service call. A second header (EVA-App-ContextChainID) can be present if the call is part of a sequence of service which we refer to as a service chain.

For service transaction tracking we will use the following naming patterns:

  • Transaction: SVC: [Service name] (ex. SVC: GetUserTaskCount)
  • Operation name: [Service context] (ex. getApplicationConfiguration:a36a5109-4b36-48f8-8ae3-dcb4913d0b4c )

The following tags will also need to be provided:

  • endpointUrl (required)
  • chainContext (optional)

Depending on which SDK you use there will be some sort of hooks available that are called both before and after each fetch request. These hooks should provide access to the values needed for service transaction tracking. Please consult the documentation of the specific SDK you are using.

This is how a service transaction could look in sentry:

Sentry service tracking

And the details of a specific service call:

Sentry service tracking details

@springtree/eva-sdk-core-service implementation#

The core service package provides supports for interceptors. A request and response interceptor can be setup to capture all fetch requests and responses for Sentry. Below is an example implementation

import {  Interceptor,  IRequestInterceptor,  IResponseInterceptor,} from '@springtree/eva-sdk-core-service';import urlParse from 'url-parse';
/** * Helper class that is used to intercept fetch requests and response * for Sentry performance tracking purposes * * @export * @class EvaSentryServiceInterceptor */export class EvaSentryServiceInterceptor extends Interceptor {
  /**   * Creates an instance of the EvaSentryServiceInterceptor interceptor   */  constructor() {    super('EVA:SentryServiceInterceptor');  }
  /**   * The request interceptor implementation.   * If you return a fetch Response it will prevent the service call from going out   *   * @type {IRequestInterceptor}   */  public requestInterceptor: IRequestInterceptor = {    hook: async (request) => {      // Split the target url for the service name and fetch context information      // from the request headers      //      const targetUrl = urlParse(request.url as string);      const serviceName = targetUrl.pathname.split('/').pop();      const serviceContext = request.headers.get('EVA-App-ContextID');      const chainContext = request.headers.get('EVA-App-ContextChainID');
      // Insert Sentry tracking start transaction code here
      // Return the unmodified request      //      return request;    },  }
  /**   * The response interceptor implementation.   *   * @type {IResponseInterceptor}   */   public responseInterceptor: IResponseInterceptor = {    hook: async (request: Request, options, response: Response) => {      // Split the target url for the service name and fetch context information      // from the request headers      //      const targetUrl = urlParse(request.url as string);      const serviceName = targetUrl.pathname.split('/').pop();      const serviceContext = request.headers.get('EVA-App-ContextID');      const chainContext = request.headers.get('EVA-App-ContextChainID');
      // Insert Sentry tracking end transaction code here
      // Return the unmodified response      //      return response;    },  }}
// This will register and enable the interceptor with the EvaService class//const evaSentryServiceInterceptor = new EvaSentryServiceInterceptor();evaSentryServiceInterceptor.activate();

eva-sdk-redux implementation#

For our aging redux SDK there are 2 hooks available in the settings which will be called before and after each fetch request. These callbacks contain all the required details needed to track the service transactions. You should only need to set these up once for generic tracking of all services as transactions.

Below is an excerpt from the SDK code showing the method signatures

class Settings {  /**    * Callback done before each fetch call    *    */  public beforeFetch?: (    params: {serviceContext: string, chainContext?: string, url: string, serviceName: string}  ) => void;
  /**    * Callback done after each fetch call    *    */  public afterFetch?: (    params: {serviceContext: string, chainContext?: string, url: string, serviceName: string}  ) => void;}

Application transactions#

The application level performance tracking will differ more per application. The main sales processes you want tracking might be comprised of multiple components in one application or multiple working in unison in another. Also more platform specific performance metrics can be added like frames per seconds tracking for limited hardware (mobile/embedded) devices.

For application transaction tracking we will use the following naming patterns:

  • Transaction: APP: [Component/provider/etc]
  • Operation name: [Custom method or component name]