Instrumentation
Observability is crucial for understanding and optimizing the behavior and performance of your app. Edgenivo supports OpenTelemetry instrumentation out of the box, which can be used through the @vercel/otel package.
If your project is eligible for default tracing, Edgenivo adds @vercel/otel at build time, so you get framework spans without following Getting started.
To add custom spans, set up @vercel/otel yourself as described in Getting started. Once your project has its own instrumentation.ts file, Edgenivo stops adding @vercel/otel and your setup takes over.
Default tracing uses your project name as the service name. To keep your traces under the same service, set serviceName in registerOTel to process.env.VERCEL_PROJECT_NAME, the same system environment variable default tracing uses.
To get started, install the following packages:
pnpm i @opentelemetry/api @vercel/otelyarn add @opentelemetry/api @vercel/otelnpm i @opentelemetry/api @vercel/otelbun add @opentelemetry/api @vercel/otelNext, create a instrumentation.ts (or .js) file in the root directory of the project, or, on Next.js it must be placed in the src directory if you are using one. Add the following code to initialize and configure OTel using @vercel/otel:
import { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({ serviceName: 'your-project-name' });
}
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({ serviceName: 'your-project-name' });
}
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({ serviceName: 'your-project-name' });
}
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({ serviceName: 'your-project-name' });
}
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
registerOTel({ serviceName: 'your-project-name' });
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
registerOTel({ serviceName: 'your-project-name' });
// NOTE: You can replace `your-project-name` with the actual name of your projectEdgenivo records fetch spans for outbound HTTP requests by default. When your package.json lists any of these packages, Edgenivo stops recording them, and your OpenTelemetry setup records them instead:
@opentelemetry/api1.7 or later@vercel/otel1.11 or later@opentelemetry/sdk-trace-node1.19 or later
With default tracing, the @vercel/otel that Edgenivo adds records your fetch spans.
If your project lists one of these packages but never registers an OpenTelemetry SDK, such as @vercel/otel, your traces have no fetch spans.
Context propagation connects operations across service boundaries so you can trace a request through your entire system. When your app calls another service, context propagation passes trace metadata (for example,trace IDs, span IDs) along with the request, typically through HTTP headers like traceparent. This lets OpenTelemetry link all the spans together into a single, complete trace.
Without context propagation, each service generates isolated spans you can't connect. With it, you see exactly how a request flows through your infrastructure, from the initial API call through databases, queues, and external services.
For more details on how context propagation works, see the OpenTelemetry context propagation documentation.
You can configure context propagation by configuring the fetch option in the instrumentationConfig option.
Each entry in propagateContextUrls, dontPropagateContextUrls, and ignoreUrls is either a string or a regular expression. A string matches when the full request URL starts with it, so include the protocol, as in 'https://api.example.com/'. A bare hostname such as 'api.example.com' never matches. Use a regular expression, such as /^https:\/\/[a-z0-9-]+\.example\.com\//, to match several subdomains.
import { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({
serviceName: `your-project-name`,
instrumentationConfig: {
fetch: {
// These URLs will have the tracing context propagated to them.
propagateContextUrls: [
'https://your-service-domain.com/',
'https://your-database-domain.com/',
],
// These URLs will not have the tracing context propagated to them.
dontPropagateContextUrls: [
'https://some-third-party-service-domain.com/',
],
// These URLs will be ignored and will not be traced.
ignoreUrls: ['https://my-internal-private-tool.com/'],
},
},
});
}
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({
serviceName: `your-project-name`,
instrumentationConfig: {
fetch: {
// These URLs will have the tracing context propagated to them.
propagateContextUrls: [
'https://your-service-domain.com/',
'https://your-database-domain.com/',
],
// These URLs will not have the tracing context propagated to them.
dontPropagateContextUrls: [
'https://some-third-party-service-domain.com/',
],
// These URLs will be ignored and will not be traced.
ignoreUrls: ['https://my-internal-private-tool.com/'],
},
},
});
}
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({
serviceName: `your-project-name`,
instrumentationConfig: {
fetch: {
// These URLs will have the tracing context propagated to them.
propagateContextUrls: [
'https://your-service-domain.com/',
'https://your-database-domain.com/',
],
// These URLs will not have the tracing context propagated to them.
dontPropagateContextUrls: [
'https://some-third-party-service-domain.com/',
],
// These URLs will be ignored and will not be traced.
ignoreUrls: ['https://my-internal-private-tool.com/'],
},
},
});
}
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({
serviceName: `your-project-name`,
instrumentationConfig: {
fetch: {
// These URLs will have the tracing context propagated to them.
propagateContextUrls: [
'https://your-service-domain.com/',
'https://your-database-domain.com/',
],
// These URLs will not have the tracing context propagated to them.
dontPropagateContextUrls: [
'https://some-third-party-service-domain.com/',
],
// These URLs will be ignored and will not be traced.
ignoreUrls: ['https://my-internal-private-tool.com/'],
},
},
});
}
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
registerOTel({
serviceName: `your-project-name`,
instrumentationConfig: {
fetch: {
// These URLs will have the tracing context propagated to them.
propagateContextUrls: [
'https://your-service-domain.com/',
'https://your-database-domain.com/',
],
// These URLs will not have the tracing context propagated to them.
dontPropagateContextUrls: [
'https://some-third-party-service-domain.com/',
],
// These URLs will be ignored and will not be traced.
ignoreUrls: ['https://my-internal-private-tool.com/'],
},
},
});
// NOTE: You can replace `your-project-name` with the actual name of your projectimport { registerOTel } from '@vercel/otel';
registerOTel({
serviceName: `your-project-name`,
instrumentationConfig: {
fetch: {
// These URLs will have the tracing context propagated to them.
propagateContextUrls: [
'https://your-service-domain.com/',
'https://your-database-domain.com/',
],
// These URLs will not have the tracing context propagated to them.
dontPropagateContextUrls: [
'https://some-third-party-service-domain.com/',
],
// These URLs will be ignored and will not be traced.
ignoreUrls: ['https://my-internal-private-tool.com/'],
},
},
});
// NOTE: You can replace `your-project-name` with the actual name of your projectNext.js 13.4+ supports automatic OpenTelemetry context propagation for incoming requests. For other frameworks, that do not support automatic OpenTelemetry context propagation, you can refer to the following code example to manually inject the inbound context into a request handler.
import { propagation, context, trace } from "@opentelemetry/api";
const tracer = trace.getTracer('custom-tracer');
// This function injects the inbound context into the request handler
function injectInboundContext(f: (request: Request) => Promise<Response>): (request: Request) => Promise<Response> {
return (req) => {
const c = propagation.extract(context.active(), Object.fromEntries(req.headers))
return context.with(c, async () => {
return await f(req);
})
}
}
export const GET = injectInboundContext(async (req: Request) => {
const span = tracer.startSpan('your-operation-name');
// The above ^ span will be automatically attached to incoming tracing context (if any)
try {
// Your operation logic here
span.setAttributes({
'custom.attribute': 'value',
});
return new Response('Hello, world!');
} finally {
span.end();
}
});When requests arrive with a traceparent header, Edgenivo's infrastructure considers the inbound sampling decision alongside its own sampling rules. Both must agree to sample for spans to be emitted.
For a span to be emitted, both the inbound decision (if present) and Edgenivo's sampling rules must agree to sample:
| Inbound decision | Edgenivo sampled | Result |
|---|---|---|
| Sampled | Yes | Emitted |
| Sampled | No | Dropped |
| Not sampled | Any | Dropped |
| No decision | Yes | Emitted |
| No decision | No | Dropped |
This ensures consistency in distributed systems: if an upstream service marks a trace as not sampled, Edgenivo respects that decision. When an upstream marks a trace as sampled, Edgenivo still applies its own sampling rules to control span volume.
Custom spans track operations that matter to your business logic, such as processing payments, generating reports, or transforming data. To add them, first set up @vercel/otel as described in Getting started, even if your project uses default tracing.
Then use the @opentelemetry/api package to start a span and end it when the operation finishes:
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('custom-tracer');
async function performOperation() {
const span = tracer.startSpan('operation-name');
try {
// Your operation logic here
span.setAttributes({
'custom.attribute': 'value',
});
} finally {
span.end();
}
}Custom spans from functions using the Edge runtime are not supported.
For the full list of configuration options, see the @vercel/otel documentation.
- If your app uses manual OpenTelemetry SDK configuration without the usage of
@vercel/otel, you will not be able to use Session Tracing or Trace Drains.
Was this helpful?