KwadMarket Docs
Best Practices

Structured Logging

Replace console.* with structured, maskable, JSON-in-production logging

Gap severity: HIGH — still open

19 console.* calls remain in backend production source (plan phase 6 / Backend quality §5 tracks the replacement). The convention already forbids them — this page keeps the target pattern.

The cal.diy pattern

packages/lib/logger.ts exports a tslog-based logger:

const logger = new Logger({
  minLevel: parseInt(process.env.NEXT_PUBLIC_LOGGER_LEVEL || "4"),
  maskValuesOfKeys: ["password", "passwordConfirmation", "credentials", "credential"],
  type: IS_PRODUCTION ? "json" : "pretty",  // JSON in prod for log aggregators
  stylePrettyLogs: !IS_PRODUCTION,
});
export default logger;
  • JSON output in production → ingested by Datadog/Axiom/Loki with structured fields
  • Automatically masks sensitive keys (password, credentials)
  • Imported by 82 files across their monorepo

What to implement

Backend (apps/back)

Each service gets its own Nest Logger instance:

import { Injectable, Logger } from "@nestjs/common";

@Injectable()
export class AuthService {
  private readonly logger = new Logger(AuthService.name);

  async login(...) {
    this.logger.log(`Login attempt (userId=${user.id})`);
    // instead of: console.log("login", email)
  }
}

Conventions: log ids, not emails (PII in prod logs); logger.error(message, error.stack) for failures; no emoji. For JSON output in production, configure Nest's built-in logger or add @nestjs/winston:

apps/back/src/main.ts
app.useLogger(
  IS_PRODUCTION
    ? new WinstonLogger({ format: winston.format.json() })
    : new Logger()
);

Frontend (apps/web)

Client-side error paths should surface via toast (the app-wide pattern), not console.error. If a client logger is ever needed, mirror the tslog pattern with maskValuesOfKeys: ["password", "token", "secret"].

CI gate

.github/workflows/ci.yml
- name: No console.log in source
  run: |
    ! grep -rn "console\.\(log\|error\|warn\)" \
      apps/back/src apps/web/app apps/web/features \
      --include="*.ts" --include="*.tsx" \
      | grep -v "seed\|\.spec\.\|\.test\."

ESLint rule

packages/config/eslint.js
"no-console": ["error", { allow: ["warn"] }]

Add both gates after the phase-6 sweep so they enforce, not block.

On this page