How to improve your console.logs without additional code



This content originally appeared on DEV Community and was authored by Andy Jiang

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:///tmp/main.ts:29:9)
    at queryBook (file:///tmp/main.ts:25:3)
    at handleRequest (file:///tmp/main.ts:21:3)
    at file:///tmp/main.ts:18:1

So, does book 1 or book 2 have a null name field? 🤔

Without context, these logs are noisy and difficult to parse. However, 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:///tmp/main.ts:29:9)
    at queryBook (file:///tmp/main.ts:25:3)
    at handleRequest (file:///tmp/main.ts:21:3)
    at file:///tmp/main.ts:18:1

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

We could manually pass a dynamically generated requestId across our functions so that they’re included in each console.log:

// Express app

export async function handle(req: Request): Promise<Response> {
  const requestId = Math.random();

  console.log(`[req:${requestId}] [http] ${req.method} ${req.url}`);

  const url = new URL(req.url);
  const bookId = url.pathname.split("/")[2];
  const data = await getData(requestId, bookId);

  return Response.json(data);
}

async function getData(requestId: number, bookId: string): Promise<Book> {
  console.log(`[req:${requestId}] [db] get book ${bookId}`);
  try {
    return await db.selectFrom("books").where("id", "=", bookId).first();
  } catch (err) {
    console.error(`[req:${requestId}] ${err}`);
    throw err;
  }
}

But that is annoying and difficult to scale/maintain.

Instead, running the program with Deno (and it’s built-in OpenTelemetry support) will automatically apply unique request IDs to each log:

$ deno run -A --unstable-otel app.js

And in our Grafana Loki, we can see that while our logs don’t include the request ID in the logs themselves:

Structured logging with Deno

Clicking on a single log will show us a trace_id:

Trace id in log

And when we click on the trace_id, we will see all the logs associated with that specific request:

All logs associated with a single request

Now it is much easier to parse the logs and know how to identify problems in our code.


Watch the full 20 minute talk on zero config debugging with Deno and OpenTelemetry.

Learn more about getting deep traces in Node.js with Deno, OpenTelemetry support in Deno, and Deno v2.2.


This content originally appeared on DEV Community and was authored by Andy Jiang