Distributed tracing on New Relic using Open Telemetry.

First, create a new project using a tool such as **electron-vite**. You can do this by running the command npm create @quick-start/electron@latest. If you're familiar with the Vite ecosystem, you'll find this straightforward. If not, simply answer all questions with 'Yes'. Let's proceed...

Next, you need to install Open Telemetry Dependencies.

  "@opentelemetry/api": "^1.9.0",
  "@opentelemetry/auto-instrumentations-node": "^0.47.1",
  "@opentelemetry/exporter-trace-otlp-http": "^0.52.0",
  "@opentelemetry/instrumentation": "^0.52.0",
  "@opentelemetry/resources": "^1.25.0",
  "@opentelemetry/semantic-conventions": "^1.25.0",

Now, create a .env file to establish two values in your variables.

VITE_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://otlp.nr-data.net:4318/v1/traces
VITE_OTEL_EXPORTER_OTLP_API_KEY="your-ingest-license-new-relic-api-key"

Now, create a folder named instrumentations. Inside it, create a file named registerAppInstrumentations.js and follow the code below:

import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { Resource } from '@opentelemetry/resources'
import {
  SEMRESATTRS_SERVICE_NAME,
  SEMRESATTRS_SERVICE_VERSION,
  SEMRESATTRS_OS_VERSION
} from '@opentelemetry/semantic-conventions'
import { NodeTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { registerInstrumentations } from '@opentelemetry/instrumentation'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'

export const registerAppInstrumentations = (appVersion: string, operatingSystem: string, appName: string): void => {
  registerInstrumentations({
    instrumentations: [
      getNodeAutoInstrumentations()
    ]
  })
  
  const resource = Resource.default().merge(
    new Resource({
      [SEMRESATTRS_SERVICE_NAME]: NEWRELIC_APP_NAME,
      [SEMRESATTRS_SERVICE_VERSION]: appVersion,
      [SEMRESATTRS_OS_VERSION]: operatingSystem
    })
  )
  
  const provider = new NodeTracerProvider({
    resource: resource
  })
  
   const otlpTraceExporter = new OTLPTraceExporter({
    url: import.meta.env.VITE_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
    headers: {
      "api-key": import.meta.env.VITE_OTEL_EXPORTER_OTLP_API_KEY,
    },
  });
  
  const processor = import.meta.env.DEV
    ? new SimpleSpanProcessor(otlpTraceExporter)
    : new BatchSpanProcessor(otlpTraceExporter)
    
  provider.addSpanProcessor(processor)
  provider.register()
}

Now, let's incorporate the functions we created into the main process of the Electron app. Follow the code below:

import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import { trace, SpanStatusCode } from '@opentelemetry/api'
import icon from '../../resources/icon.png?asset'
import { name } from '../../package.json'
import { registerAppInstrumentations } from '../instrumentations/registerAppInstrumentations'

app.whenReady().then(() => {
  const appVersion = app.getVersion()
  const operatingSystem = process.platform;
  const appName = name;

  registerAppInstrumentations(appVersion, operatingSystem, appName)
  
  electronApp.setAppUserModelId('com.electron')
  app.on('browser-window-created', (_, window) => {
    optimizer.watchWindowShortcuts(window)
  })

  ipcMain.on('ping', async () => {
    await trace.getTracer("ping").startActiveSpan('fetchPingPong', async (span) => {
      try {
        span.setAttribute('ping', 'pong')
        
        await new Promise((resolve) => setTimeout(resolve, 1000))
        
        span.addEvent('ping', { pong: 'ping' })
        
      } catch (error: any) {
        span.recordException(error)
        span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
        console.error(error)
      } finally {
        span.end()
      }
    })
  })

  createWindow()

  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

You can see the full code on my GitHub.