hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Logging and Observability with @hectoday/http

Why Logging

  • console.log Is Not Enough
  • What to Log
  • Project Setup

Structured Logging

  • JSON Logs
  • Log Levels
  • Building a Logger

Request Logging

  • Request IDs
  • Request-Response Logging
  • Error Logging

Context and Correlation

  • Log Context
  • Child Loggers
  • Correlating Across Services

What to Observe

  • Business Event Logging
  • Performance Logging
  • Health and Metrics

Production

  • Log Output and Transport
  • Sensitive Data
  • Checklist and Capstone

Child Loggers

How child loggers work

The Building a Logger lesson introduced logger.child(context). The Log Context lesson used it to create request-scoped loggers. This lesson explores child loggers in depth — nesting, overriding, and practical patterns.

The inheritance chain

// Application logger (root)
const logger = new Logger({
  context: { service: "book-catalog", env: "production" },
});

// Request logger (child of root)
const requestLogger = logger.child({
  requestId: "req_a1b2c3",
  method: "POST",
  path: "/v2/orders",
});

// Operation logger (child of request)
const orderLogger = requestLogger.child({
  orderId: "order-99",
});

orderLogger.info("processing payment");
// {"service":"book-catalog","env":"production","requestId":"req_a1b2c3","method":"POST","path":"/v2/orders","orderId":"order-99","level":"info","message":"processing payment"}

Each child inherits everything from its parent and adds its own context. The order logger includes service, env, requestId, method, path, AND orderId — all without passing them explicitly.

Scoping to a database operation

route.post("/v2/orders", {
  resolve: async (c) => {
    const log = c.locals.log; // request-scoped logger

    log.info("creating order");

    const order = createOrder(c.input.body);
    const orderLog = log.child({ orderId: order.id });

    orderLog.info("order created", { total: order.total, items: order.items.length });

    try {
      const payment = await chargeCard(order.total, c.input.body.paymentToken);
      orderLog.info("payment processed", { chargeId: payment.id });
    } catch (err) {
      orderLog.error("payment failed", { error: err instanceof Error ? err.message : String(err) });
      throw err;
    }

    orderLog.info("order completed");
    return Response.json(order, { status: 201 });
  },
});

Every log entry for this order includes the orderId. If the payment fails, the error log has the request ID, user ID, AND order ID — everything needed to investigate.

Passing loggers to service functions

Service functions that are called from route handlers can accept a logger parameter:

// src/services/orders.ts
export function processPayment(
  orderId: string,
  amount: number,
  token: string,
  log: Logger,
): PaymentResult {
  const paymentLog = log.child({ operation: "payment", amount });

  paymentLog.info("charging card");
  const result = stripe.charge(amount, token);
  paymentLog.info("card charged", { chargeId: result.id });

  return result;
}

The service function receives a logger that already has the request and order context. It adds operation: "payment" — now every payment log includes the full chain: service → request → order → payment.

When NOT to create a child

Not every function call needs a child logger. Create children when you add meaningful context (orderId, operation type). Do not create children just to pass the logger along — that adds overhead without value.

// Unnecessary: no new context
const childLog = log.child({}); // Don't do this

// Useful: adds orderId context
const orderLog = log.child({ orderId: order.id }); // Do this

Exercises

Exercise 1: Create a three-level logger chain: root → request → order. Log from each level. Verify context accumulates.

Exercise 2: Pass a child logger to a service function. Verify the service’s logs include the request context.

Exercise 3: Trace a failed request by searching for its request ID. Verify all related logs (request, order, payment, error) appear.

Why pass a logger to service functions instead of importing the global logger?

← Log Context Correlating Across Services →

© 2026 hectoday. All rights reserved.