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

Sensitive Data

The logging security problem

Logs are stored, searched, and sometimes shared. If a log entry contains a password, anyone with log access has the password. If it contains a JWT, anyone can impersonate the user. If it contains a credit card number, you violate PCI compliance.

The What to Log lesson listed what NOT to log. This lesson builds redaction — automatically removing or masking sensitive data from log entries.

What must never appear in logs

Passwords — Not even hashed. Log “login attempt for user alice”, not “login attempt with password s3cret”.

Authentication tokens — JWTs, session IDs, API keys. Log “token validated for user-42”, not the actual token.

Credit card numbers — PCI-DSS requires it. Log “payment processed for $29.99”, not the card number.

Personal Identifiable Information (PII) — Email addresses, phone numbers, physical addresses. Some regulations (GDPR) restrict logging PII.

Request/response bodies — Bodies can contain any of the above. Do not log bodies by default.

A redaction function

const SENSITIVE_KEYS = new Set([
  "password",
  "confirmPassword",
  "currentPassword",
  "newPassword",
  "token",
  "accessToken",
  "refreshToken",
  "sessionId",
  "apiKey",
  "authorization",
  "creditCard",
  "cardNumber",
  "cvv",
  "ssn",
  "secret",
  "privateKey",
]);

export function redact(obj: Record<string, unknown>): Record<string, unknown> {
  const result: Record<string, unknown> = {};

  for (const [key, value] of Object.entries(obj)) {
    if (SENSITIVE_KEYS.has(key.toLowerCase())) {
      result[key] = "[REDACTED]";
    } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
      result[key] = redact(value as Record<string, unknown>);
    } else {
      result[key] = value;
    }
  }

  return result;
}
redact({ username: "alice", password: "s3cret", token: "eyJ..." });
// { username: "alice", password: "[REDACTED]", token: "[REDACTED]" }

redact({ user: { email: "[email protected]", apiKey: "sk_live_..." } });
// { user: { email: "[email protected]", apiKey: "[REDACTED]" } }

Integrating redaction into the logger

export class Logger {
  private log(level: Level, message: string, context: Record<string, unknown> = {}): void {
    if (LEVELS[level] < this.minLevel) return;

    const entry = {
      timestamp: new Date().toISOString(),
      level,
      message,
      ...this.baseContext,
      ...redact(context), // ← Redact context before serializing
    };

    const output = JSON.stringify(entry);
    // ... write to stdout/stderr
  }
}

Now every log call automatically redacts sensitive fields. A developer who accidentally logs { userId: "u1", password: "s3cret" } gets { userId: "u1", password: "[REDACTED]" } in the output.

Masking instead of redacting

Sometimes you want to show part of a value — enough to identify it, not enough to use it:

function mask(value: string, visibleChars: number = 4): string {
  if (value.length <= visibleChars) return "****";
  return "****" + value.slice(-visibleChars);
}

mask("sk_live_abc123def456"); // "****f456"
mask("[email protected]"); // "****e.com" (still somewhat PII though)

Use masking for values where partial visibility helps debugging (last 4 digits of a card, last 4 chars of an API key). Use full redaction for passwords and tokens.

Preventing body logging

The Request-Response Logging lesson mentioned: do not log request bodies by default. Enforce this:

// DANGEROUS — body might contain passwords
logger.info("request received", { body: requestBody }); // DON'T

// SAFE — log only the fields you need
logger.info("request received", {
  method: request.method,
  path: url.pathname,
  contentLength: request.headers.get("content-length"),
});

If you must log a body for debugging, use the redaction function:

if (process.env.LOG_BODIES === "true") {
  logger.debug("request body", { body: redact(requestBody) });
}

[!NOTE] The Web Security Fundamentals course covered sensitive data exposure. Logging is one of the most common sources of accidental exposure. Redaction is your safety net.

Exercises

Exercise 1: Build the redact function. Test with objects containing password, token, and apiKey fields.

Exercise 2: Integrate redaction into the Logger class. Log an object with sensitive fields. Verify they are redacted in the output.

Exercise 3: Add a mask function for partial visibility. Test with API keys and card numbers.

Why redact sensitive data automatically in the logger instead of relying on developers to exclude it?

← Log Output and Transport Checklist and Capstone →

© 2026 hectoday. All rights reserved.