Logging and Debugging

By Syncfusion®, Inc. · Last updated

What to log, a custom logger with the finish event, request IDs, Morgan formats and custom tokens, error logging, the DEBUG environment variable, debugging routes, middleware order and async handlers, and health checks.

What you will learn

  • Log method, URL, status and time for every request
  • Add request IDs to logs and answers
  • Use Morgan formats and custom tokens
  • Use DEBUG output
  • Debug routes, middleware order and async handlers

Logging and Debugging Introduction

A running server is a black box unless it tells you what it does.

  • Log = a line your server writes about something that happened.
  • Request log = one line per request: method, URL, status and time.
  • Debugging = finding out why the server does something you did not expect.
  • Logs answer "what happened?". Debugging answers "why?".
  • Good logs never contain passwords, tokens or other secrets.

Simple lesson: Log every request, and log errors with enough detail to fix them.

A Real-World Example

The canteen keeps an order register.

  1. Each order gets a line: time, token number, item, and "served" or "cancelled".
  2. A parent complains: "My child waited 30 minutes."
  3. The manager finds token 41 in the register: ordered 12:01, served 12:31.
  4. The register never lists the child's lunch card PIN.

The register is your request log. The token number is a request ID.

In this lesson, you will learn:

  1. What to put in a request log.
  2. How to write logger, timing and request-ID middleware.
  3. How to use Morgan, the popular logging package.
  4. How to log errors safely.
  5. How to use Express's DEBUG output.
  6. How to debug routes, middleware order and async handlers.

What to Log

  • Method and URL: GET /api/menu?veg=true.
  • Status code: 200, 404, 500.
  • Response time: how many milliseconds the request took.
  • Request ID: a unique ID to connect log lines for one request.
  • Never: passwords, tokens, cookies, Authorization headers or full card numbers.
  • Logs often end up in shared tools, so treat them as semi-public.

Simple lesson: Method, URL, status, time and ID. Never secrets.

A Custom Logger

The status is only known at the end of a request. So the logger must wait.

  • Record the start time when the request arrives.
  • Listen for the response's finish event.
  • When it fires, the status and time are known. Write the line.
  • Call next() straight away, so the request carries on.
  • performance.now() gives a precise time in milliseconds.
  • /slow shows about 300 ms. /missing shows 404.
  • If you logged before next(), you would not know the status yet.

Easy way to remember: arrive, start the clock. Finish, write the line.

Simple lesson: Start the clock on arrival, write the line on finish.

Request IDs

Request ID = a unique label for one request.

  • Create it with crypto.randomUUID().
  • If a proxy already sent X-Request-Id, reuse it, so both logs match.
  • Store it on req.id, and send it back in the X-Request-Id header.
  • Put it in every log line and every error answer.
  • A user can then say "my request ID was 3f2a..." and you find it instantly.
  • The second request keeps the ID the proxy sent.
  • The login log shows the user, but the password is left out.

Never log req.body or req.headers whole. They often hold passwords, tokens and cookies.

Simple lesson: One ID per request, in the header, in the logs and in error answers.

Logging with Morgan

Morgan = a ready-made request logger. Install it with npm install morgan.

  • morgan("dev") = short, coloured lines for development.
  • morgan("tiny") = the smallest useful line.
  • morgan("combined") = the standard Apache format, common in production.
  • Custom format strings: ":method :url :status :response-time ms".
  • Custom tokens: morgan.token("id", (req) => req.id), then use :id.
  • skip = a function that hides some requests, such as health checks.
  • /health is not logged, because of skip.
  • Change the format to "combined" and run again to see a production-style line.

Big apps often log JSON lines with a package such as pino. Tools can then search by status, ID or URL. The ideas are the same as here.

Simple lesson: Morgan writes request lines for you. Add tokens for your own fields.

Logging Errors

  • Log every 500 with the request ID, the message and the stack.
  • Log 4xx errors more briefly, or not at all. They are the client's mistake.
  • Send the request ID to the client in the error answer.
  • Keep stacks in the log, out of the answer. See Error Handling.

Simple lesson: The same request ID in the answer and the log connects the two.

Express DEBUG Output

Express can print its own internal steps.

  • Set the DEBUG environment variable before starting.
  • DEBUG=express:* shows every Express namespace.
  • DEBUG=express:router shows only routing. DEBUG=* shows every package.
  • Namespace = the label before each debug line, such as express:application.
  • On Windows PowerShell: $env:DEBUG="express:*"; node server.js.
  • The terminal shows each setting and each request being dispatched.
  • The real output has more lines. In this lab, you see a shortened version.

Simple lesson: DEBUG=express:* shows what Express itself is doing.

Debugging Common Problems

When something does not work, check in this order:

  1. Is the request arriving? A global logger at the top answers this.
  2. Does a route match? Check the method, the path and req.path. Print req.route?.path in the handler.
  3. Is middleware in the right order? Add a console.log in each one and watch the order.
  4. Does every path end? Every branch must call next() or send a response.
  5. Is an async call awaited? A missing await sends answers too early.
  6. Use console.log for quick checks, and node --inspect with Chrome DevTools for breakpoints.
  • /api/menu gives 404. The log shows the request arrived, so the path is wrong.
  • /api/price/2 logs price is Promise... and answers {"price":{}}. Add await.
  • /api/menu/2 works, so the server itself is fine.
  • Fix both bugs and run again.

Simple lesson: Log the request, check the match, check the order, check the await.

Health Checks

Health check = a tiny route that says "the server is alive".

  • Load balancers and hosting platforms call it every few seconds.
  • It usually answers 200 with { "status": "ok" }.
  • It can also check the database and answer 503 when something is down.
  • Skip it in request logs, or it floods them.

Simple lesson: /health lets machines check your server. Keep it fast and quiet.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Logging before next()The status is known at onceWait for finishEvery line shows 200
Logging req.body on loginIt helps debuggingIt contains the passwordPasswords stored in logs
No request IDThe time is enoughMany requests share a timeYou cannot match a complaint to a log
Logger registered after routesOrder does not matterRegister it firstSome requests are never logged
Logging health checksMore logs are betterSkip themReal problems get buried
Stack traces in answersIt helps usersStacks belong in logsInternals leak
Missing awaitAsync values arrive by themselvesawait the promise{} or undefined answers

Check Yourself

Exercises

What to Remember

  • Log method, URL, status, time and a request ID. Never secrets.
  • Write the log line on finish, when the status is known.
  • Request IDs connect the client's answer to your logs.
  • Morgan handles request logs. Custom tokens add your own fields.
  • DEBUG=express:* shows Express's own steps.
  • Debug in order: arrival, route match, middleware order, ending, await.

Next: Express Security. You will protect your server with headers, CORS, rate limits and safe secrets.