On this page
Monitor your app with OpenTelemetry and Deno Deploy
Deno Deploy includes built-in OpenTelemetry support that automatically capturestraces for HTTP requests, database queries, and other operations. This tutorialshows how to add custom OpenTelemetry instrumentation to your applications formore detailed observability.
PrerequisitesJump to heading
- AGitHub account
- Deno installedon your local machine
- Access to theDeno Deploy account
- Basic familiarity withOpenTelemetry concepts
Create a basic API applicationJump to heading
First, let's create a simple API server that we'll instrument withOpenTelemetry:
const dataStore: Record<string,string>={};asyncfunctionhandler(req: Request):Promise<Response>{const url=newURL(req.url);// Simulate random latencyawaitnewPromise((resolve)=>setTimeout(resolve, Math.random()*200));try{// Handle product listingif(url.pathname==="/products"&& req.method==="GET"){returnnewResponse(JSON.stringify(Object.values(dataStore)),{ headers:{"Content-Type":"application/json"},});}// Handle product creationif(url.pathname==="/products"&& req.method==="POST"){const data=await req.json();const id= crypto.randomUUID(); dataStore[id]= data;returnnewResponse(JSON.stringify({ id,...data}),{ status:201, headers:{"Content-Type":"application/json"},});}// Handle product retrieval by IDif(url.pathname.startsWith("/products/")&& req.method==="GET"){const id= url.pathname.split("/")[2];const product= dataStore[id];if(!product){returnnewResponse("Product not found",{ status:404});}returnnewResponse(JSON.stringify(product),{ headers:{"Content-Type":"application/json"},});}// Handle root routeif(url.pathname==="/"){returnnewResponse("Product API - Try /products endpoint");}returnnewResponse("Not Found",{ status:404});}catch(error){console.error("Error handling request:", error);returnnewResponse("Internal Server Error",{ status:500});}}console.log("Server running on http://localhost:8000");Deno.serve(handler,{ port:8000});
Save this file and run it locally:
deno run --allow-net main.ts
Test the API with curl or a browser to ensure it works:
# List products (empty at first)curl http://localhost:8000/products# Add a productcurl-X POST http://localhost:8000/products\-H"Content-Type: application/json"\-d'{"name": "Test Product", "price": 19.99}'
Add OpenTelemetry instrumentationJump to heading
Now, let's add custom OpenTelemetry instrumentation to our application. Create anew file calledinstrumented-main.ts
:
import{ trace}from"npm:@opentelemetry/api@1";// Get the OpenTelemetry tracerconst tracer= trace.getTracer("product-api");const dataStore: Record<string,string>={};// Simulate a database operation with custom spanasyncfunctionqueryDatabase( operation:string, data?:unknown,):Promise<unknown>{returnawait tracer.startActiveSpan(`database.${operation}`,async(span)=>{try{// Add attributes to the span for better context span.setAttributes({"db.system":"memory-store","db.operation": operation,});// Simulate database latencyconst delay= Math.random()*100;awaitnewPromise((resolve)=>setTimeout(resolve, delay));// Add latency information to the span span.setAttributes({"db.latency_ms": delay});if(operation==="list"){return Object.values(dataStore);}elseif(operation==="get"){return dataStore[dataasstring];}elseif(operation==="insert"){const id= crypto.randomUUID(); dataStore[id]= dataasstring;return{ id, data};}returnnull;}catch(error){// Record any errors to the span span.recordException(error); span.setStatus({ code: trace.SpanStatusCode.ERROR});throw error;}finally{// End the span when we're done span.end();}});}asyncfunctionhandler(req: Request):Promise<Response>{// Create a parent span for the entire requestreturnawait tracer.startActiveSpan(`${req.method}${newURL(req.url).pathname}`,async(parentSpan)=>{const url=newURL(req.url);// Add request details as span attributes parentSpan.setAttributes({"http.method": req.method,"http.url": req.url,"http.route": url.pathname,});try{// Handle product listingif(url.pathname==="/products"&& req.method==="GET"){const products=awaitqueryDatabase("list");returnnewResponse(JSON.stringify(products),{ headers:{"Content-Type":"application/json"},});}// Handle product creationif(url.pathname==="/products"&& req.method==="POST"){// Create a span for parsing request JSONconst data=await tracer.startActiveSpan("parse.request.body",async(span)=>{try{const result=await req.json();return result;}catch(error){ span.recordException(error); span.setStatus({ code: trace.SpanStatusCode.ERROR});throw error;}finally{ span.end();}},);const result=awaitqueryDatabase("insert", data);returnnewResponse(JSON.stringify(result),{ status:201, headers:{"Content-Type":"application/json"},});}// Handle product retrieval by IDif(url.pathname.startsWith("/products/")&& req.method==="GET"){const id= url.pathname.split("/")[2]; parentSpan.setAttributes({"product.id": id});const product=awaitqueryDatabase("get", id);if(!product){ parentSpan.setAttributes({"error":true,"error.type":"not_found",});returnnewResponse("Product not found",{ status:404});}returnnewResponse(JSON.stringify(product),{ headers:{"Content-Type":"application/json"},});}// Handle root routeif(url.pathname==="/"){returnnewResponse("Product API - Try /products endpoint");} parentSpan.setAttributes({"error":true,"error.type":"not_found"});returnnewResponse("Not Found",{ status:404});}catch(error){console.error("Error handling request:", error);// Record the error in the span parentSpan.recordException(error); parentSpan.setAttributes({"error":true,"error.type": error.name,"error.message": error.message,}); parentSpan.setStatus({ code: trace.SpanStatusCode.ERROR});returnnewResponse("Internal Server Error",{ status:500});}finally{// End the parent span when we're done parentSpan.end();}},);}console.log("Server running with OpenTelemetry instrumentation on http://localhost:8000",);Deno.serve(handler,{ port:8000});
Run the instrumented version locally:
deno run --allow-net instrumented-main.ts
Test the API again with curl to generate some traces.
Create a GitHub repositoryJump to heading
Go toGitHub and create a new repository.
Initialize your local directory as a Git repository:
git initgitadd.git commit-m"Add OpenTelemetry instrumented API"
- Add your GitHub repository as a remote and push your code:
git remoteadd origin https://github.com/your-username/otel-demo-app.gitgit branch-M maingit push-u origin main
Deploy to Deno DeployJump to heading
Navigate toconsole.deno.com
Select your organization or create a new one if needed
Click "+ New App"
Select the GitHub repository you created earlier
Configure the build settings:
- Framework preset: No preset
- Runtime configuration: Dynamic
- Entrypoint:
instrumented-main.ts
Click "Create App" to start the deployment process
Generate sample trafficJump to heading
To generate sample traces and metrics, let's send some traffic to your deployedapplication:
Copy your deployment URL from the Deno Deploy dashboard
Send several requests to different endpoints:
# Store your app URL in a variableAPP_URL=https://your-app-name.your-org-name.deno.net# Get the root routecurl$APP_URL/# List products (empty at first)curl$APP_URL/products# Add some productscurl-X POST$APP_URL/products-H"Content-Type: application/json"-d'{"name": "Laptop", "price": 999.99}'curl-X POST$APP_URL/products-H"Content-Type: application/json"-d'{"name": "Headphones", "price": 129.99}'curl-X POST$APP_URL/products-H"Content-Type: application/json"-d'{"name": "Mouse", "price": 59.99}'# List products againcurl$APP_URL/products# Try to access a non-existent product (will generate an error span)curl$APP_URL/products/nonexistent-id
Explore OpenTelemetry traces and metricsJump to heading
Now let's explore the observability data collected by Deno Deploy:
From your application dashboard, click "Traces" in the sidebar
- You'll see a list of traces for each request to your application
- You can filter traces by HTTP method or status code using the search bar
Select one of your
/products
POST traces to see detailed information:- The parent span for the entire request
- Child spans for database operations
- The span for parsing the request body
Click on individual spans to see their details:
- Duration and timing information
- Attributes you set like
db.operation
anddb.latency_ms
- Any recorded exceptions
Click "Logs" in the sidebar to see console output with trace context:
- Notice how logs emitted during a traced operation are automatically linkedto the trace
- Click "View trace" on a log line to see the associated trace
Click "Metrics" to view application performance metrics:
- HTTP request counts by endpoint
- Error rates
- Response time distributions
🦕 The automatic instrumentation in Deno Deploy combined with your custominstrumentation provides comprehensive visibility into your application'sperformance and behavior.
For more information about OpenTelemetry in Deno, check out these resources: