惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AWS News Blog
AWS News Blog
T
Tenable Blog
Project Zero
Project Zero
T
The Exploit Database - CXSecurity.com
L
LINUX DO - 热门话题
T
Threat Research - Cisco Blogs
T
Threatpost
Security Latest
Security Latest
C
Cisco Blogs
L
Lohrmann on Cybersecurity
S
Security @ Cisco Blogs
Google Online Security Blog
Google Online Security Blog
NISL@THU
NISL@THU
AI
AI
V
Vulnerabilities – Threatpost
Google DeepMind News
Google DeepMind News
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Last Watchdog
The Last Watchdog
G
GRAHAM CLULEY
Cloudbric
Cloudbric
H
Hackread – Cybersecurity News, Data Breaches, AI and More
H
Hacker News: Front Page
U
Unit 42
A
Arctic Wolf
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
MyScale Blog
MyScale Blog
O
OpenAI News
Scott Helme
Scott Helme
V2EX - 技术
V2EX - 技术
P
Proofpoint News Feed
博客园 - 叶小钗
Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
V
Visual Studio Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Cyberwarzone
Cyberwarzone
博客园 - 【当耐特】
H
Heimdal Security Blog
S
Schneier on Security
阮一峰的网络日志
阮一峰的网络日志
Help Net Security
Help Net Security
D
DataBreaches.Net
Y
Y Combinator Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
TaoSecurity Blog
TaoSecurity Blog
K
Kaspersky official blog
N
News and Events Feed by Topic
WordPress大学
WordPress大学
P
Palo Alto Networks Blog

Deno

Deno 2.8 | Deno Claw Patrol: an open-source security firewall for agents | Deno Fresh 2.3: Zero JS by default, View Transitions, and Temporal support | Deno Deno 2.7: Temporal API, Windows ARM, and npm overrides | Deno Build a dinosaur runner game with Deno, pt. 6 | Deno Build a dinosaur runner game with Deno, pt. 5 | Deno Deno Deploy is Generally Available | Deno Introducing Deno Sandbox | Deno Build a dinosaur runner game with Deno, pt. 4 | Deno Build a dinosaur runner game with Deno, pt. 3 | Deno Build a dinosaur runner game with Deno, pt. 2 | Deno React / Next.js Denial-of-Service Vulnerability: Deno Deploy users protected | Deno Deno 2.6: dx is the new npx | Deno Build a dinosaur runner game with Deno, pt. 1 | Deno React Server Functions / Next.js Vulnerability: Deno Deploy users protected | Deno My highlights from the new Deno Deploy | Deno Deno's Other Open Source Projects | Deno How Deno protects against npm exploits | Deno Help Us Raise $200k to Free JavaScript from Oracle | Deno Deno 2.5: Permissions in the config file | Deno Fresh 2.0 Graduates to Beta, Adds Vite Support | Deno Deno 2.4: deno bundle is back | Deno JavaScript™ Trademark Update | Deno What's coming to JavaScript | Deno A brief history of JavaScript | Deno Reports of Deno's Demise Have Been Greatly Exaggerated | Deno An Update on Fresh | Deno How Plaid migrated 100 services to a new database platform 5x faster with Deno | Deno Deno 2.3: Improved deno compile, local npm packages, and more | Deno Add JSR packages with pnpm and Yarn | Deno Exploring Art with TypeScript, Jupyter, Polars, and Observable Plot | Deno Deno v Oracle Update 3: Fighting the JavaScript Trademark | Deno Build a custom RAG AI agent in TypeScript and Jupyter | Deno How to get deep traces in your Node.js backend with OTel and Deno | Deno toranoana.deno #20 登録受付中(2025年3月14日) | Deno Node just added TypeScript support. What does that mean for Deno? | Deno The Dino 🦕, the Llama 🦙, and the Whale 🐋 | Deno Publish a lint rule, get a prize | Deno Deno 2.2: OpenTelemetry, Lint Plugins, node:sqlite | Deno If you're not using npm specifiers, you're doing it wrong | Deno How Deno's documentation is evolving | Deno Oracle justified its JavaScript trademark with Node.js—now it wants that ignored | Deno Introducing the JSR open governance board | Deno Intro to Wasm in Deno | Deno Announcing OpenAI on JSR | Deno Deno in 2024 | Deno Goodbye WinterCG, welcome WinterTC | Deno Build a SolidJS app with Deno | Deno Run your Next.js SSR app on Deno Deploy | Deno Solve Advent of Code 2024 with Deno and Win Prizes! | Deno Deno v. Oracle: Canceling the JavaScript Trademark | Deno Deno 2.1: Wasm Imports and other enhancements | Deno Build a Typesafe API with tRPC and Deno | Deno Self-contained Executable Programs with Deno Compile | Deno Build a Database App with Drizzle ORM and Deno | Deno Introducing your new JavaScript package manager: Deno | Deno Announcing Growthbook on JSR | Deno Build an Astro site with Deno | Deno How to convert CommonJS to ESM | Deno Announcing Deno 2 | Deno The Final Touches: What’s New In v2.0.0-rc.10 | Deno Announcing Stable V8 Bindings for Rust | Deno Deno 2.0 Release Candidate | Deno Secure, efficient private npm registries with Cloudsmith and Deno | Deno Painting the Plane as We Fly It: Designing JSR | Deno Introducing Web Cache API support on Deno Deploy | Deno Deno 1.46: The Last 1.x Release | Deno Protect your cloud spend with new Deno Deploy spend limits | Deno What we got wrong about HTTP imports | Deno Benchmarking AWS Lambda Cold Starts Across JavaScript Runtimes | Deno Announcing Supabase on JSR | Deno Deno 1.45: Workspace and Monorepo Support | Deno Introducing KV Backup for Deno Subhosting | Deno A Gentle Intro to TypeScript | Deno Announcing Hono on JSR | Deno How We Made the Deno Language Server Ten Times Faster | Deno How the Guardian uses Deno to audit accessibility and performance across their 2.7 million articles | Deno Introducing More Flexible Domain Association for Deno Subhosting | Deno The stabilization process of the Standard Library has begun | Deno Deno 1.44: Private npm registries, improved Node.js compat, and performance boosts | Deno How we built a secure, performant, multi-tenant cloud platform to run untrusted code | Deno The Deno Standard Library is now available on JSR | Deno How to document your JavaScript package | Deno Your Low Code Solution Needs an Escape Hatch | Deno Deno 1.43: Improved Language Server performance | Deno How Slack used Deno to save months of engineering effort in launching their new platform | Deno JSR Is Not Another Package Manager | Deno Announcing the Hookdeck SDK on JSR | Deno Announcing the Neon Serverless Driver on JSR | Deno An intro to TSConfig for JavaScript Developers | Deno How we built JSR | Deno How Netlify used Deno Subhosting to build a successful edge functions product | Deno Introducing Simpler Project Creation in Deno Deploy | Deno Deno 1.42: Better dependency management with JSR | Deno Introducing deployctl, the command line interface for Deno Deploy | Deno Introducing JSR - the JavaScript Registry | Deno How to add Monaco to a Next.js app and securely run untrusted user code | Deno Survey Results and Roadmap | Deno Deno 1.41: smaller deno compile binaries | Deno Webhooks suck, but here are alternatives | Deno
Zero-config Debugging with Deno and OpenTelemetry | Deno
2025-04-10 · via Deno

Debugging in production can be far more difficult and complex than local development — reasons range from limited visibility because of a lack of a step-by-step debugging, to needing to trace across distributed systems, and more. But fortunately there are actions to level up your debugging that you can take today.

Old man yells at cloud

Debugging issues in production.

Before we talk about how to improve debugging, what makes for “good” debugging? Good debugging requires good data. To help understand issues quickly, you should know:

  • What happened?
  • Who triggered it?
  • How long did it take?
  • When did it start?
  • Which request is it a part of?

In this blog post, we’ll go over the three pillars of observability — logs, traces, and metrics — and how they work together to answer important questions when debugging that will help you actually solve real problems. We’ll also show how you can instrument your existing server-side code by running it in Deno without any additional code.

  • The issue with logs
  • Traces: the who, what, when, and duration
  • Tracing across multiple services
  • Metrics: when to investigate an issue?
  • What’s next
Or watch the 20 min video which includes all of the demos in this post.

The issue with logs

Too often, we’re presented with logs like this:

[http] GET /api/book/1
[http] GET /api/book/2
[db] SELECT * FROM books WHERE id = 1
[db] SELECT * FROM books WHERE id = 2
error: Uncaught (in promise) PostgresError: 'name' column is null
  throw new PostgresError(`'name' column is null`);

    at query (file:/
    at queryBook (file:/
    at handleRequest (file:/
    at file:/

Does book 1 or book 2 have a null name field? 🤔

Without context, you can’t tell, because there are logs from two different parallel requests being interleaved here.

There is however a way out: if we associate a request ID with each log, then we can determine which book had the null name field:

[req:1] [http] GET /api/book/1
[req:2] [http] GET /api/book/2
[req:1] [db] SELECT * FROM books WHERE id = 1
[req:2] [db] SELECT * FROM books WHERE id = 2
[req:1] error: Uncaught (in promise) PostgresError: 'name' column is null
  throw new PostgresError(`'name' column is null`);

    at query (file:/
    at queryBook (file:/
    at handleRequest (file:/
    at file:/

Now, we’re able to immediately understand the problem causing the error: book 1 is missing a name.

But augmenting our logging to include a unique request ID can be a hassle and difficult to maintain. In our code, we’d have to generate and pass around requestId to all downstream functions that might console.log:

 // ...

 app.get("/api/book/:id", async (req, res) => {
+  const requestId = Math.random();
+  console.log(`[req:${requestId}] [http] GET /api/book/${req.params.id}`);
-  console.log(`[http] GET /api/book/${req.params.id}`);
   const id = req.params.id;
   const book = await getBookFromDatabase(requestId, id);
   res.send(book);
 });

+ async function getBookFromDatabase(requestId: number, bookId: string) {
+   console.log(`[req:${requestId}] [db] getBookFromDatabase ${bookId}`);
-   console.log(`[db] getBookFromDatabase ${bookId}`);
    const book = books.get(bookId);
    if (book === undefined) throw new Error("No book found.");
    if (!book.author) throw new Error("Book is missing author.");
    return book;
  },
To associate logs with a single request, we’d have to pass around a unique requestId, as well as include it in each console.log, making the code difficult to maintain and scale.

That’s where Deno’s built-in OpenTelemetry support comes in - you simply run the program with Deno, and all your console.log ’s automatically have unique request IDs associated with them that Deno assigns automatically for every incoming HTTP request:

Luca showing a demo of logs captured automatically with Deno and OTel.

Learn more about OpenTelemetry with Deno in our documentation.

Traces: the who, what, when, and duration

Beyond the logs, oftentimes we want to answer questions such as:

Which caller passed the wrong arguments to this function?

Which one of the two call sites called this database query?

In order to answer that, we don’t just need to know what request an operation is a part of, but we also need to know the parent operation. Here, “operation” simply means something we want to track, such as an HTTP request or a database call.

In the world of telemetry, what we’re looking for are traces. They let us know who triggered what, when, and how long it took. This can then be displayed nicely in a graphical format, like this:

Example of trace

This trace, for instance, represents a single GET request, which triggered an authorization check, two database queries, etc. You’ll notice that in this trace you can see:

  • which code triggered an operation
  • how long each operation took
  • what operations block other operations (e.g. the authorization check blocks the database queries)

Here, this lets us know that these two database queries took the longest time.

In a trace, each operation is called a span. A span records how long the operation took, when it started, who triggered it, and which trace it is a part of.

In order to have each span nested correctly under it’s parent within the trace, we need a way to track the parent. This can be done by assigning each operation a span ID, and when we start a new operation (span), we can figure out what the current span ID is and use that as the parent span.

Parent operations and their spans

The child operation, database call, shares the same parent span ID as the span ID of its parent operation, HTTP request.

Out of the box, Deno will automatically create a span for each incoming HTTP request. This means that if you run your server with Deno, you’ll get a trace for each incoming HTTP request, and each trace will have a span for the request. This is done automatically, and you don’t need to do anything to configure it.

In a simple web app, that means you get a trace with a single span for every HTTP request:

An example of a trace with spans

Deno also creates spans for outgoing HTTP requests automatically. This means that if you make an outgoing HTTP request, Deno will automatically create a span for that request, and it will be a child of the span for the incoming HTTP request.

But we can improve our traces further by adding more spans, for example for the database operation. We can implement that by starting a span before we make the database call, and ending it after the database call is complete. To make this easier, I like using a little utility function that wraps the startActiveSpan function from the OpenTelemetry API. This utility function automatically handles the span lifecycle for us, including setting the status code and recording exceptions.

util.ts

import { Span, SpanStatusCode, trace } from "@opentelemetry/api";

const tracer = trace.getTracer("api");

export function withSpan<Args extends unknown[], T>(
  name: string,
  cb: (span: Span, ...args: Args) => Promise<T>,
): (...args: Args) => Promise<T> {
  return (...args: Args) => {
    return tracer.startActiveSpan(name, async (span) => {
      try {
        return await cb(span, ...args);
      } catch (err) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
        span.recordException(err as Error);
        throw err;
      } finally {
        span.end();
      }
    });
  };
}

We can then use this utility function to wrap our getBookFromDatabase function:

db.ts

import { withSpan } from "./utils.ts";

export const getBookFromDatabase = withSpan(
  "getBookFromDatabase",
  async function getBookFromDatabase(span, bookId: string) {
    span.setAttribute("book_id", bookId);
    console.log(`[db] getBookFromDatabase ${bookId}`);
    await new Promise((resolve) => setTimeout(resolve, 300));
    const book = books.get(bookId);
    if (book === undefined) throw new Error("No book found.");
    if (!book.author) throw new Error("Book is missing author.");
    return book;
  },
);

Now, when we send another cURL and view that request’s trace, we can see two spans:

Multiple spans in a trace

We’re now able to track all the operations triggered from a single HTTP request.

Here’s an end-to-end demo:

Luca showing traces captured automatically with Deno and OTel.

Tracing across multiple services

Debugging can get particularly difficult when dealing with multiple services, like in a microservices system. Tracing can help here too though, because traces can span across multiple services. All this requires is that you propagate the “parent operation” information from one service to another when making a request. This is called propagation.

This is made simple using W3C spec tracecontext, a standard format representing the trace information to be propagated between services.

You could implement this yourself with a function like this on the sending side, where you manually inject the header into the request:

import { trace } from "npm:@opentelemetry/api";

export function fetchWithTrace(req: Request): Promise<Response> {
  const span = trace.getActiveSpan();
  if (!span) return fetch(req);
  const spanContext = span.spanContext();
  const traceparent = `00-${spanContext.traceId}-${spanContext.spanId}-01`;
  req.headers.set("traceparent", traceparent);
  return fetch(req);
}


const resp = await fetchWithTrace(new Request("https://example.com"));

You also have to then read back the traceparent header on the receiving side and create a new span with the traceparent information. This is a lot of work, and it’s easy to get wrong. You also have to remember to do this for every outgoing request, and every incoming request.

With Deno, you actually don’t have to do any of this. Deno automatically propagates the trace information for you between services. It can automatically inject trace information into outbound requests, and it can automatically read trace information from incoming requests.

Let’s create a new main.ts that simply calls the API server:

const resp = await fetch("https://localhost:8000/api/book/1");
const book = await resp.json();
console.log(book);

Now, run it with OTEL enabled:

$ deno run -A --unstable-otel main.ts
{ id: "1", title: "The Great Gatsby", author: "F. Scott Fitzgerald" }

When we check Grafana for our traces, you’ll notice that the latest trace does not have the service name book-api. That’s because the trace originated from a new service.

A trace across multiple services

Clicking on that fetcher trace will show us three spans: one green and two purple. Deno has automatically tracked a single trace across multiple services.

A trace across multiple services

For more complex microservice architectures, you could imagine having deep nests of spans across multiple services.

Here’s a full demo from Luca showing how Deno can automatically collect traces across multiple services:

Luca showing the traces collected automatically across multiple services with Deno and OTel.

Metrics: when to investigate an issue?

You probably don’t want to wait until your customer runs into an issue to let you know to fix something. The best way to get ahead of your customers when it comes to investigating production issues is through metrics (or more specifically, setting alert thresholds with metrics).

Metrics are aggregate data that show you how frequently something is happening. For instance, a common setup would be to trigger an alert when there are some percentage of requests with a 500 status code (for example 1% or 0.1%).

An example of metrics of requests with various status codes

An example of metrics of requests with various status codes.

Running your program with Deno will automatically collect metrics:

Luca showing a demo of the automatically captured metrics from Deno and OTel.

What’s next

In this post, we covered the three pillars of OpenTelemetry:

  • Logs: what happened, when, and which request is it a part of?
  • Traces: what happened, who triggered it, when did it happen, how long did it take, and which request is it a part of?
  • Metrics: How frequently did something happen?

We also showed that Deno’s built-in OpenTelemetry support will automatically collect all of this information without any additional implementation or config. (You can also run your Node projects with Deno and automatically get this level of observability.) When running your program in Deno, you’ll get:

  • log correlation with trace IDs
  • one trace per HTTP request
  • spans for incoming and outgoing HTTP requests
  • request count metrics (by status code)
  • response latency (by status code)
  • automatic propagation

with much more to come.

Deno makes writing code simple. But we know that writing code is only half of the battle. The other half is debugging and maintaining that code. This is why debugging, especially in production, is now such a core part of the Deno runtime.

🚨️ Learn more about Deno 2.2 🚨️

and more!