@coralogix/opentelemetry
v0.5.0
Published
Coralogix extensions for the OpenTelemetry Node SDK, including a transaction sampler for Coralogix APM.
Keywords
Readme
coralogix-opentelemetry-js
Coralogix extensions for the OpenTelemetry Node SDK. This package adds Coralogix-specific behavior on top of a standard OpenTelemetry tracing setup — transaction tagging via a sampler and/or a SpanProcessor, including exclusive self duration on the processor path.
npm install --save @coralogix/opentelemetryRequirements
This package relies on your application's existing OpenTelemetry setup. The following are peerDependencies and must be installed alongside it:
| Package | Version |
| --- | --- |
| @opentelemetry/api | ^1.7.0 |
| @opentelemetry/sdk-trace-base | ^2.8.0 |
The examples below also use @opentelemetry/resources and @opentelemetry/semantic-conventions, which are part of a typical OpenTelemetry Node setup.
Note: This package targets OpenTelemetry JS SDK 2.x. The resource/provider APIs shown below (
resourceFromAttributes,ATTR_SERVICE_NAME) are the 2.x APIs; if you are still on SDK 1.x, adapt the setup accordingly (new Resource(...),SemanticResourceAttributes).
CoralogixTransactionSampler
CoralogixTransactionSampler wraps an existing OpenTelemetry sampler to define, report, and monitor Coralogix transactions. It sets Coralogix transaction attributes on sampled spans and propagates the transaction identity across the trace.
import { CoralogixTransactionSampler } from "@coralogix/opentelemetry";
import { AlwaysOnSampler } from "@opentelemetry/sdk-trace-base";
// Wrap your existing sampler...
const sampler = new CoralogixTransactionSampler(new AlwaysOnSampler());
// ...or omit the argument to default to a ParentBased(AlwaysOn) sampler.
const defaultSampler = new CoralogixTransactionSampler();Pass the sampler to your tracer provider like any other OpenTelemetry sampler:
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
const tracerProvider = new BasicTracerProvider({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: "<your-service-name>",
}),
sampler: new CoralogixTransactionSampler(),
});Supported instrumentation
It works with individual auto-instrumentation and manual instrumentation. The bundled auto-instrumentation method is not supported.
Emitted attributes
When a span starts a transaction, the sampler adds the following attributes:
| Attribute | Description |
| --- | --- |
| cgx.transaction | The transaction name (e.g. GET /users/:id). |
| cgx.transaction.distributed | The distributed transaction name propagated across services. |
| cgx.transaction.root | Whether this span is the root of the transaction. |
TransactionSpanProcessor
TransactionSpanProcessor wraps a SpanExporter to tag Coralogix transactions, stamp exclusive self duration (cgx.transaction.self_duration, seconds) on completed local traces, and record the matching histogram (unit s). It works with any sampler.
How naming works
Transaction membership (new vs inherit, and cgx.transaction.root) is decided on span start. The display name cgx.transaction is stamped only when a completed local trace is finalized for export, using overrideName ?? rootSpan.name. That matters for Express: the HTTP span often starts as GET and is later renamed to GET /myroute by middleware — the exported transaction name is the final root span name.
Harvest defaults (data loss)
By default the processor keeps only the slowest completed local trace(s) for each harvest window:
| Behavior | Default |
| --- | --- |
| Spans kept per local trace | maxNodes: 256 (slowest first; transaction roots always kept) |
| Traces kept until harvest | maxRegularTraces: 1 |
| Harvest period | harvestPeriodMillis: 60_000 (60s) |
Data-loss implication: with maxRegularTraces: 1, every completed local trace that is not the slowest in the current harvest window is not exported as a full waterfall. Losers are reduced to root-only stubs (APM presence) and dropped from the detailed export. Self-duration metrics are still recorded for every completed local trace, including losers.
Set maxRegularTraces: 0 to export every completed trimmed trace immediately (no harvest competition). Setting harvestPeriodMillis: 0 also disables the heap and exports immediately.
import { BasicTracerProvider, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
import { TransactionSpanProcessor } from "@coralogix/opentelemetry";
const tracerProvider = new BasicTracerProvider({
spanProcessors: [
new TransactionSpanProcessor(new ConsoleSpanExporter(), {
// Optional MeterProvider for the self-duration histogram; defaults to the global one.
// maxRegularTraces: 0, // export all completed traces (tests / debugging)
}),
],
});Options
Constructor options win over environment variables. Invalid env values fall back to the default.
| Option | Type | Default | Env var | Meaning |
| --- | --- | --- | --- | --- |
| maxNodes | number | 256 | OTEL_CX_TRANSACTION_MAX_NODES | Max spans kept per completed local trace (slowest first; roots always kept). |
| maxRegularTraces | number | 1 | OTEL_CX_TRANSACTION_MAX_REGULAR_TRACES | Max full traces retained until harvest (slowest-N). 0 = export every completed trimmed trace immediately. Losers become root stubs. |
| harvestPeriodMillis | number | 60000 | OTEL_CX_TRANSACTION_HARVEST_PERIOD_MILLIS | Harvest flush interval. <= 0 exports every completed trace immediately (no heap). |
| completionHoldbackMillis | number | 100 | OTEL_CX_TRANSACTION_COMPLETION_HOLDBACK_MILLIS | After the last live span ends, wait this long before finalize so late siblings can join. 0 = finalize immediately. |
| shutdownIdleWaitMillis | number | 30000 | — | How long shutdown waits for in-flight spans. |
| meterProvider | MeterProvider | global | — | MeterProvider for the self-duration histogram. |
Transactions with Express
To resolve stable, low-cardinality transaction names for Express routes (e.g. GET /users/:id instead of a per-request path), call setExpressApp so the sampler can learn your app's routes and endpoints.
Important: Call
setExpressAppafter all routes and routers have been registered. The sampler reads the Express router stack at the moment you call it, so any routes added afterwards will not be resolved.
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
import { CoralogixTransactionSampler } from "@coralogix/opentelemetry";
import express from "express";
import router from "./router";
const sampler = new CoralogixTransactionSampler();
const tracerProvider = new BasicTracerProvider({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: "<your-service-name>",
}),
sampler,
});
const app = express();
app.use("/", router);
// Register routes first, then hand the app to the sampler.
sampler.setExpressApp(app);
app.listen(3000, () => {
console.log("Server is running");
});Prefixed and nested routers
setExpressApp resolves the full route template, including mount prefixes and arbitrarily nested routers. For example, given:
const orders = express.Router();
orders.get("/orders/:oid", handler);
const api = express.Router();
api.use("/deep", orders);
app.use("/api", api);a request to /api/deep/orders/9 resolves to the transaction GET /api/deep/orders/:oid.
Express version support
Both Express 4 and Express 5 are supported.
Parameters are templatized correctly on both versions, including a parameter used in a mount prefix (app.use("/v/:ver", router)): a request to /v/2/things/abc resolves to GET /v/:ver/things/:t.
Changelog
See CHANGELOG.md for release notes. Note that route-template resolution for prefixed and nested routers is a breaking output change: transaction names for affected apps change, so update any dashboards, alerts, or saved views keyed on the previous names.
License
Licensed under the Apache License 2.0.
