Deno has built in support for OpenTelemetry.
OpenTelemetry is a collection of APIs, SDKs, and tools. Use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help you analyze your software’s performance and behavior.
This integration enables you to monitor your Deno applications using OpenTelemetry observability tooling with instruments like logs, metrics, and traces.
Deno provides the following features:
npm:@opentelemetry/api
package.To enable the OpenTelemetry integration set the environment variable OTEL_DENO=true
:
OTEL_DENO=true deno run my_script.ts
This will automatically collect and export runtime observability data to an OpenTelemetry endpoint at localhost:4318
using Protobuf over HTTP (http/protobuf
).
Tip
If you do not have an OpenTelemetry collector set up yet, you can get started with a local LGTM stack in Docker (Loki (logs), Grafana (dashboard), Tempo (traces), and Prometheus (metrics)) by running the following command:
docker run --name lgtm -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti \
-v "$PWD"/lgtm/grafana:/data/grafana \
-v "$PWD"/lgtm/prometheus:/data/prometheus \
-v "$PWD"/lgtm/loki:/data/loki \
-e GF_PATHS_DATA=/data/grafana \
docker.io/grafana/otel-lgtm:0.8.1
You can then access the Grafana dashboard at http://localhost:3000
with the username admin
and password admin
.
This will automatically collect and export runtime observability data like console.log
, traces for HTTP requests, and metrics for the Deno runtime. Learn more about auto instrumentation.
You can also create your own metrics, traces, and logs using the npm:@opentelemetry/api
package. Learn more about user defined metrics.
Deno automatically collects and exports some observability data to the OTLP endpoint.
This data is exported in the built-in instrumentation scope of the Deno runtime. This scope has the name deno
. The version of the Deno runtime is the version of the deno
instrumentation scope. (e.g. deno:2.1.4
).
Deno automatically creates spans for various operations, such as:
Deno.serve
.fetch
.Deno.serve
Jump to heading#
When you use Deno.serve
to create an HTTP server, a span is created for each incoming request. The span automatically ends when response headers are sent (not when the response body is done sending).
The name of the created span is ${method}
. The span kind is server
.
The following attributes are automatically added to the span on creation:
http.request.method
: The HTTP method of the request.url.full
: The full URL of the request (as would be reported by req.url
).url.scheme
: The scheme of the request URL (e.g. http
or https
).url.path
: The path of the request URL.url.query
: The query string of the request URL.After the request is handled, the following attributes are added:
http.response.status_code
: The status code of the response.Deno does not automatically add a http.route
attribute to the span as the route is not known by the runtime, and instead is determined by the routing logic in a user's handler function. If you want to add a http.route
attribute to the span, you can do so in your handler function using npm:@opentelemetry/api
. In this case you should also update the span name to include the route.
import { trace } from "npm:@opentelemetry/api@1";
const INDEX_ROUTE = new URLPattern({ pathname: "/" });
const BOOK_ROUTE = new URLPattern({ pathname: "/book/:id" });
Deno.serve(async (req) => {
const span = trace.getActiveSpan();
if (INDEX_ROUTE.test(req.url)) {
span.setAttribute("http.route", "/");
span.updateName(`${req.method} /`);
} else if (BOOK_ROUTE.test(req.url)) {
span.setAttribute("http.route", "/book/:id");
span.updateName(`${req.method} /book/:id`);
} else {
return new Response("Not found", { status: 404 });
}
});
fetch
Jump to heading#
When you use fetch
to make an HTTP request, a span is created for the request. The span automatically ends when the response headers are received.
The name of the created span is ${method}
. The span kind is client
.
The following attributes are automatically added to the span on creation:
http.request.method
: The HTTP method of the request.url.full
: The full URL of the request.url.scheme
: The scheme of the request URL.url.path
: The path of the request URL.url.query
: The query string of the request URL.After the response is received, the following attributes are added:
http.status_code
: The status code of the response.The following metrics are automatically collected and exported:
Deno.serve
/ Deno.serveHttp
Jump to heading# http.server.request.duration
Jump to heading#
A histogram of the duration of incoming HTTP requests served with Deno.serve
or Deno.serveHttp
. The time that is measured is from when the request is received to when the response headers are sent. This does not include the time to send the response body. The unit of this metric is seconds. The histogram buckets are [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0]
.
This metric is recorded with the following attributes:
http.request.method
: The HTTP method of the request.url.scheme
: The scheme of the request URL.network.protocol.version
: The version of the HTTP protocol used for the request (e.g. 1.1
or 2
).server.address
: The address that the server is listening on.server.port
: The port that the server is listening on.http.response.status_code
: The status code of the response (if the request has been handled without a fatal error).error.type
: The type of error that occurred (if the request handling was subject to an error).http.server.active_requests
Jump to heading#
A gauge of the number of active requests being handled by Deno.serve
or Deno.serveHttp
at any given time. This is the number of requests that have been received but not yet responded to (where the response headers have not yet been sent). This metric is recorded with the following attributes:
http.request.method
: The HTTP method of the request.url.scheme
: The scheme of the request URL.server.address
: The address that the server is listening on.server.port
: The port that the server is listening on.http.server.request.body.size
Jump to heading#
A histogram of the size of the request body of incoming HTTP requests served with Deno.serve
or Deno.serveHttp
. The unit of this metric is bytes. The histogram buckets are [0, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]
.
This metric is recorded with the following attributes:
http.request.method
: The HTTP method of the request.url.scheme
: The scheme of the request URL.network.protocol.version
: The version of the HTTP protocol used for the request (e.g. 1.1
or 2
).server.address
: The address that the server is listening on.server.port
: The port that the server is listening on.http.response.status_code
: The status code of the response (if the request has been handled without a fatal error).error.type
: The type of error that occurred (if the request handling was subject to an error).http.server.response.body.size
Jump to heading#
A histogram of the size of the response body of incoming HTTP requests served with Deno.serve
or Deno.serveHttp
. The unit of this metric is bytes. The histogram buckets are [0, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]
.
This metric is recorded with the following attributes:
http.request.method
: The HTTP method of the request.url.scheme
: The scheme of the request URL.network.protocol.version
: The version of the HTTP protocol used for the request (e.g. 1.1
or 2
).server.address
: The address that the server is listening on.server.port
: The port that the server is listening on.http.response.status_code
: The status code of the response (if the request has been handled without a fatal error).error.type
: The type of error that occurred (if the request handling was subject to an error).The following logs are automatically collected and exported:
console.*
methods such as console.log
and console.error
.Downloading
logs, and similar.Logs raised from JavaScript code will be exported with the relevant span context, if the log occurred inside of an active span.
console
auto instrumentation can be configured using the OTEL_DENO_CONSOLE
environment variable:
capture
: Logs are emitted to stdout/stderr and are also exported with OpenTelemetry. (default)replace
: Logs are only exported with OpenTelemetry, and not emitted to stdout/stderr.ignore
: Logs are emitted only to stdout/stderr, and will not be exported with OpenTelemetry.In addition to the automatically collected telemetry data, you can also create your own metrics and traces using the npm:@opentelemetry/api
package.
You do not need to configure the npm:@opentelemetry/api
package to use it with Deno. Deno sets up the npm:@opentelemetry/api
package automatically. There is no need to call metrics.setGlobalMeterProvider()
, trace.setGlobalTracerProvider()
, or context.setGlobalContextManager()
. All configuration of resources, exporter settings, etc. is done via environment variables.
Deno works with version 1.x
of the npm:@opentelemetry/api
package. You can either import directly from npm:@opentelemetry/api@1
, or you can install the package locally with deno add
and import from @opentelemetry/api
.
deno add npm:@opentelemetry/api@1
For both traces and metrics, you need to define names for the tracer and meter respectively. If you are instrumenting a library, you should name the tracer or meter after the library (such as my-awesome-lib
). If you are instrumenting an application, you should name the tracer or meter after the application (such as my-app
). The version of the tracer or meter should be set to the version of the library or application.
To create a new span, first import the trace
object from npm:@opentelemetry/api
and create a new tracer:
import { trace } from "npm:@opentelemetry/api@1";
const tracer = trace.getTracer("my-app", "1.0.0");
Then, create a new span using the tracer.startActiveSpan
method and pass a callback function to it. You have to manually end the span by calling the end
method on the span object returned by startActiveSpan
.
function myFunction() {
return tracer.startActiveSpan("myFunction", (span) => {
try {
} catch (error) {
span.recordException(error);
span.setStatus({
code: trace.SpanStatusCode.ERROR,
message: (error as Error).message,
});
throw error;
} finally {
span.end();
}
});
}
span.end()
should be called in a finally
block to ensure that the span is ended even if an error occurs. span.recordException
and span.setStatus
should also be called in a catch
block, to record any errors that occur.
Inside of the callback function, the created span is the "active span". You can get the active span using trace.getActiveSpan()
. The "active span" will be used as the parent span for any spans created (manually, or automatically by the runtime) inside of the callback function (or any functions that are called from the callback function). Learn more about context propagation.
The startActiveSpan
method returns the return value of the callback function.
Spans can have attributes added to them during their lifetime. Attributes are key value pairs that represent structured metadata about the span. Attributes can be added using the setAttribute
and setAttributes
methods on the span object.
span.setAttribute("key", "value");
span.setAttributes({ success: true, "bar.count": 42n, "foo.duration": 123.45 });
Values for attributes can be strings, numbers (floats), bigints (clamped to u64), booleans, or arrays of any of these types. If an attribute value is not one of these types, it will be ignored.
The name of a span can be updated using the updateName
method on the span object.
span.updateName("new name");
The status of a span can be set using the setStatus
method on the span object. The recordException
method can be used to record an exception that occurred during the span's lifetime. recordException
creates an event with the exception stack trace and name and attaches it to the span. recordException
does not set the span status to ERROR
, you must do that manually.
import { SpanStatusCode } from "npm:@opentelemetry/api@1";
span.setStatus({
code: SpanStatusCode.ERROR,
message: "An error occurred",
});
span.recordException(new Error("An error occurred"));
span.setStatus({
code: SpanStatusCode.OK,
});
Spans can also have events and links added to them. Events are points in time that are associated with the span. Links are references to other spans.
span.addEvent("button_clicked", {
id: "submit-button",
action: "submit",
});
span.addEvent("process_completed", { status: "success" }, Date.now());
Events can include optional attributes similar to spans. They are useful for marking significant moments within the span's lifetime without creating separate spans.
Spans can also be created manually with tracer.startSpan
which returns a span object. This method does not set the created span as the active span, so it will not automatically be used as the parent span for any spans created later, or any console.log
calls. A span can manually be set as the active span for a callback, by using the context propagation API.
Both tracer.startActiveSpan
and tracer.startSpan
can take an optional options bag containing any of the following properties:
kind
: The kind of the span. Can be SpanKind.CLIENT
, SpanKind.SERVER
, SpanKind.PRODUCER
, SpanKind.CONSUMER
, or SpanKind.INTERNAL
. Defaults to SpanKind.INTERNAL
.startTime
A Date
object representing the start time of the span, or a number representing the start time in milliseconds since the Unix epoch. If not provided, the current time will be used.attributes
: An object containing attributes to add to the span.links
: An array of links to add to the span.root
: A boolean indicating whether the span should be a root span. If true
, the span will not have a parent span (even if there is an active span).After the options bag, both tracer.startActiveSpan
and tracer.startSpan
can also take a context
object from the context propagation API.
Learn more about the full tracing API in the OpenTelemetry JS API docs.
Metrics Jump to heading#To create a metric, first import the metrics
object from npm:@opentelemetry/api
and create a new meter:
import { metrics } from "npm:@opentelemetry/api@1";
const meter = metrics.getMeter("my-app", "1.0.0");
Then, an instrument can be created from the meter, and used to record values:
const counter = meter.createCounter("my_counter", {
description: "A simple counter",
unit: "1",
});
counter.add(1);
counter.add(2);
Each recording can also have associated attributes:
counter.add(1, { color: "red" });
counter.add(2, { color: "blue" });
Tip
In OpenTelemetry, metric attributes should generally have low cardinality. This means that there should not be too many unique combinations of attribute values. For example, it is probably fine to have an attribute for which continent a user is on, but it would be too high cardinality to have an attribute for the exact latitude and longitude of the user. High cardinality attributes can cause problems with metric storage and exporting, and should be avoided. Use spans and logs for high cardinality data.
There are several types of instruments that can be created with a meter:
Counter: A counter is a monotonically increasing value. Counters can only be positive. They can be used for values that are always increasing, such as the number of requests handled.
UpDownCounter: An up-down counter is a value that can both increase and decrease. Up-down counters can be used for values that can increase and decrease, such as the number of active connections or requests in progress.
Gauge: A gauge is a value that can be set to any value. They are used for values that do not "accumulate" over time, but rather have a specific value at any given time, such as the current temperature.
Histogram: A histogram is a value that is recorded as a distribution of values. Histograms can be used for values that are not just a single number, but a distribution of numbers, such as the response time of a request in milliseconds. Histograms can be used to calculate percentiles, averages, and other statistics. They have a predefined set of boundaries that define the buckets that the values are placed into. By default, the boundaries are [0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0]
.
There are also several types of observable instruments. These instruments do not have a synchronous recording method, but instead return a callback that can be called to record a value. The callback will be called when the OpenTelemetry SDK is ready to record a value, for example just before exporting.
const counter = meter.createObservableCounter("my_counter", {
description: "A simple counter",
unit: "1",
});
counter.addCallback((res) => {
res.observe(1);
res.observe(1, { color: "red" });
});
There are three types of observable instruments:
Learn more about the full metrics API in the OpenTelemetry JS API docs.
Practical Examples Jump to heading#For practical examples of implementing OpenTelemetry in Deno applications, see our tutorials:
In OpenTelemetry, context propagation is the process of passing some context information (such as the current span) from one part of an application to another, without having to pass it explicitly as an argument to every function.
In Deno, context propagation is done using the rules of AsyncContext
, the TC39 proposal for async context propagation. The AsyncContext
API is not yet exposed to users in Deno, but it is used internally to propagate the active span and other context information across asynchronous boundaries.
A quick overview how AsyncContext propagation works:
This means that async context propagation essentially behaves like a global variable that is scoped to the current asynchronous task, and is automatically copied to any new asynchronous tasks that are started from this current task.
The context
API from npm:@opentelemetry/api@1
exposes this functionality to users. It works as follows:
import { context } from "npm:@opentelemetry/api@1";
const currentContext = context.active();
const newContext = currentContext.setValue("id", 1);
console.log(currentContext.getValue("id"));
context.with(newContext, () => {
console.log(context.active().getValue("id"));
function myFunction() {
return context.active().getValue("id");
}
console.log(myFunction());
setTimeout(() => {
console.log(context.active().getValue("id"));
}, 10);
});
console.log(context.active().getValue("id"));
The context API integrates with spans too. For example, to run a function in the context of a specific span, the span can be added to a context, and then the function can be run in that context:
import { context, trace } from "npm:@opentelemetry/api@1";
const tracer = trace.getTracer("my-app", "1.0.0");
const span = tracer.startSpan("myFunction");
const contextWithSpan = trace.setSpan(context.active(), span);
context.with(contextWithSpan, () => {
const activeSpan = trace.getActiveSpan();
console.log(activeSpan === span);
});
span.end();
Learn more about the full context API in the OpenTelemetry JS API docs.
Configuration Jump to heading#The OpenTelemetry integration can be enabled by setting the OTEL_DENO=true
environment variable.
The endpoint and protocol for the OTLP exporter can be configured using the OTEL_EXPORTER_OTLP_ENDPOINT
and OTEL_EXPORTER_OTLP_PROTOCOL
environment variables.
If the endpoint requires authentication, headers can be configured using the OTEL_EXPORTER_OTLP_HEADERS
environment variable.
Endpoint can all be overridden individually for metrics, traces, and logs by using specific environment variables, such as:
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
For more information on headers that can be used to configure the OTLP exporter, see the OpenTelemetry website.
The resource that is associated with the telemetry data can be configured using the OTEL_SERVICE_NAME
and OTEL_RESOURCE_ATTRIBUTES
environment variables. In addition to attributes set via the OTEL_RESOURCE_ATTRIBUTES
environment variable, the following attributes are automatically set:
service.name
: If OTEL_SERVICE_NAME
is not set, the value is set to <unknown_service>
.process.runtime.name
: deno
process.runtime.version
: The version of the Deno runtime.telemetry.sdk.name
: deno-opentelemetry
telemetry.sdk.language
: deno-rust
telemetry.sdk.version
: The version of the Deno runtime, plus the version of the opentelemetry
Rust crate being used by Deno, separated by a -
.Propagators can be configured using the OTEL_PROPAGATORS
environment variable. The default value is tracecontext,baggage
. Multiple propagators can be specified by separating them with commas. Currently supported propagators are:
tracecontext
: W3C Trace Context propagation formatbaggage
: W3C Baggage propagation formatMetric collection frequency can be configured using the OTEL_METRIC_EXPORT_INTERVAL
environment variable. The default value is 60000
milliseconds (60 seconds).
Span exporter batching can be configured using the batch span processor environment variables described in the OpenTelemetry specification.
Log exporter batching can be configured using the batch log record processor environment variables described in the OpenTelemetry specification.
Propagators Jump to heading#Deno supports context propagators which enable automatic propagation of trace context across process boundaries for distributed tracing, allowing you to track requests as they flow through different services.
Propagators are responsible for encoding and decoding context information (like trace and span IDs) into and from carrier formats (like HTTP headers). This enables the trace context to be maintained across service boundaries.
By default, Deno supports the following propagators:
tracecontext
: The W3C Trace Context propagation format, which is the standard way to propagate trace context via HTTP headers.baggage
: The W3C Baggage propagation format, which allows passing key-value pairs across service boundaries.Note
These propagators automatically work with Deno's fetch
API and Deno.serve
, enabling end-to-end tracing across HTTP requests without manual context management.
You can access the propagation API through the @opentelemetry/api
package:
import { context, propagation, trace } from "npm:@opentelemetry/api@1";
function extractContextFromHeaders(headers: Headers) {
const ctx = context.active();
return propagation.extract(ctx, headers);
}
function injectContextIntoHeaders(headers: Headers) {
const ctx = context.active();
propagation.inject(ctx, headers);
return headers;
}
async function tracedFetch(url: string) {
const headers = new Headers();
injectContextIntoHeaders(headers);
return await fetch(url, { headers });
}
Limitations Jump to heading#
While the OpenTelemetry integration for Deno is in development, there are some limitations to be aware of:
OTEL_TRACE_SAMPLER=parentbased_always_on
).console.log
and console.error
) are not supported.http/protobuf
and http/json
protocols are supported for OTLP. Other protocols such as grpc
are not supported.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT
, OTEL_ATTRIBUTE_COUNT_LIMIT
, OTEL_SPAN_EVENT_COUNT_LIMIT
, OTEL_SPAN_LINK_COUNT_LIMIT
, OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT
, and OTEL_LINK_ATTRIBUTE_COUNT_LIMIT
environment variable are not respected for trace spans.OTEL_METRIC_EXPORT_TIMEOUT
environment variable is not respected._OTHER
in the http.request.method
span attribute as per the OpenTelemetry semantic conventions.Deno.serve
does not have an OpenTelemetry status set, and if the handler throws (ie onError
is invoked), the span will not have an error status set and the error will not be attached to the span via event.http.route
attribute to the HTTP client span for fetch
, or to update the span name to include the route.RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4