Skip to content

fix: replace console.log with structured pino logger in API startup#214

Open
sputnik-mac wants to merge 1 commit intoalexanderwanyoike:devfrom
sputnik-mac:fix/issue-194-structured-logger-startup
Open

fix: replace console.log with structured pino logger in API startup#214
sputnik-mac wants to merge 1 commit intoalexanderwanyoike:devfrom
sputnik-mac:fix/issue-194-structured-logger-startup

Conversation

@sputnik-mac
Copy link
Copy Markdown
Contributor

@sputnik-mac sputnik-mac commented Mar 20, 2026

What

Replaces console.log and console.error in api/src/main.ts with a standalone pino logger instance for the bootstrap phase.

Why

Startup messages (Running database migrations..., Database migrations completed, and the fatal error handler) were using console.log/console.error, producing unstructured plain text. The rest of the API emits structured JSON via nestjs-pino. This inconsistency breaks log aggregation in tools like Datadog, Loki, or CloudWatch.

Changes

  • Added pino as a direct dependency in api/package.json
  • Created a bootstrapLogger instance with { name: "bootstrap" } for pre-NestJS logging
  • Replaced all console.log/console.error calls in bootstrap with structured equivalents

All log output is now consistent structured JSON.

Closes #194

Summary by CodeRabbit

Release Notes

  • Chores
    • Enhanced application startup and error logging infrastructure for improved observability and diagnostics.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

The PR adds pino as a dependency and refactors api/src/main.ts to replace console.log and console.error with structured Pino logging during API startup and migration phases, ensuring consistent JSON-formatted output across all logging operations.

Changes

Cohort / File(s) Summary
Dependencies
api/package.json
Added pino@^9.6.0 as a runtime dependency for structured logging.
Logging Integration
api/src/main.ts
Replaced unstructured console.log/console.error calls with a dedicated bootstrapLogger instance; migration start/completion and error messages now emit structured logs via bootstrapLogger.info() and bootstrapLogger.error().

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 With Pino's hop, the logs now shine,
Structured JSON, perfectly aligned,
No console noise at startup's door,
Just clean, parsed data, nothing more!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: replacing console.log with a pino logger in API startup.
Linked Issues check ✅ Passed The pull request fully addresses issue #194 by adding pino as a dependency, instantiating a bootstrapLogger, and replacing all console statements with structured logging.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing issue #194: dependency addition and bootstrap logging replacement. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can disable the changed files summary in the walkthrough.

Disable the reviews.changed_files_summary setting to disable the changed files summary in the walkthrough.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
api/src/main.ts (1)

4-8: Use env-driven log level for the bootstrap logger.

Line 8 initializes Pino with defaults, so bootstrap logs ignore LOG_LEVEL controls used by the app logger. Consider wiring level from env for consistency.

Proposed change
-const bootstrapLogger = pino({ name: "bootstrap" });
+const bootstrapLogger = pino({
+  name: "bootstrap",
+  level: process.env.LOG_LEVEL || "info",
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/main.ts` around lines 4 - 8, The bootstrap logger is created without
using the environment-controlled log level, so update the pino initialization in
main.ts to read the LOG_LEVEL env var (e.g., process.env.LOG_LEVEL) and pass it
as the level option when calling pino; modify the bootstrapLogger creation (the
pino(...) call) to use that env value (with a sensible default like "info") so
bootstrapLogger respects the same LOG_LEVEL as the rest of the app.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/package.json`:
- Line 57: Change the conflicting dependency so pino-http's peer requirement is
satisfied: update the "pino" dependency in package.json from "^9.6.0" to
"^10.0.0" (or alternatively downgrade "pino-http" to a version compatible with
pino "^9.6.0"), then run yarn install in the api/ directory and commit the
resulting lockfile so CI's yarn install --frozen-lockfile will succeed; target
the "pino" and "pino-http" entries when making the change.

In `@api/src/main.ts`:
- Around line 44-45: Replace the immediate process.exit(1) call after
bootstrapLogger.error to allow pino to flush: set process.exitCode = 1 and
return from the bootstrap/startup flow instead of calling process.exit(1), so
leave the bootstrapLogger.error(...) call intact but replace the terminating
call (process.exit(1)) with assigning process.exitCode = 1 and returning from
the function that runs startup (where main/bootstrap logic executes) to let logs
be flushed.

---

Nitpick comments:
In `@api/src/main.ts`:
- Around line 4-8: The bootstrap logger is created without using the
environment-controlled log level, so update the pino initialization in main.ts
to read the LOG_LEVEL env var (e.g., process.env.LOG_LEVEL) and pass it as the
level option when calling pino; modify the bootstrapLogger creation (the
pino(...) call) to use that env value (with a sensible default like "info") so
bootstrapLogger respects the same LOG_LEVEL as the rest of the app.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 551c1984-bb3f-4167-b5b0-4b03ea5e795c

📥 Commits

Reviewing files that changed from the base of the PR and between 1912551 and f9058de.

📒 Files selected for processing (2)
  • api/package.json
  • api/src/main.ts

"multer": "^2.0.1",
"nats": "^2.29.3",
"nestjs-pino": "^4.5.0",
"pino": "^9.6.0",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the api directory exists and see its structure
ls -la api/ 2>/dev/null | head -20

Repository: alexanderwanyoike/the0

Length of output: 1017


🏁 Script executed:

# Check the current content of api/package.json around line 57
if [ -f "api/package.json" ]; then
  echo "=== api/package.json (lines 50-65) ===" && sed -n '50,65p' api/package.json
else
  echo "api/package.json not found"
fi

Repository: alexanderwanyoike/the0

Length of output: 543


🏁 Script executed:

# Check what lockfiles exist in the api directory
ls -la api/package-lock.json api/yarn.lock 2>&1 | grep -v "cannot access"

Repository: alexanderwanyoike/the0

Length of output: 130


🏁 Script executed:

# Search for CI configuration files and frozen-lockfile references
find . -maxdepth 2 -type f \( -name ".github" -o -name "*.yml" -o -name "*.yaml" \) 2>/dev/null | head -20

Repository: alexanderwanyoike/the0

Length of output: 164


🏁 Script executed:

# Look for CI workflow files
find .github -type f -name "*.yml" -o -name "*.yaml" 2>/dev/null

Repository: alexanderwanyoike/the0

Length of output: 643


🏁 Script executed:

# Check the api.yml workflow file for frozen-lockfile instructions
cat -n .github/workflows/api.yml

Repository: alexanderwanyoike/the0

Length of output: 2694


🏁 Script executed:

# Also check what's actually in the api directory to understand the structure
ls -la api/

Repository: alexanderwanyoike/the0

Length of output: 1017


🏁 Script executed:

# Check if pino is in the yarn.lock file
grep -n "pino" api/yarn.lock | head -20

Repository: alexanderwanyoike/the0

Length of output: 1478


🏁 Script executed:

# Let's also check the full dependency section in package.json to understand the context
head -70 api/package.json | tail -25

Repository: alexanderwanyoike/the0

Length of output: 740


🏁 Script executed:

# Check what version of pino-http requires and whether it conflicts with pino ^9.6.0
grep -A5 "pino-http@\^11.0.0:" api/yarn.lock

Repository: alexanderwanyoike/the0

Length of output: 372


🏁 Script executed:

# Check if there are any other pino entries in the lockfile that could satisfy ^9.6.0
grep "^pino@" api/yarn.lock

Repository: alexanderwanyoike/the0

Length of output: 79


🏁 Script executed:

# Let's get more context on the pino-http dependency and what it requires
grep -B2 -A10 "pino-http@\^11.0.0:" api/yarn.lock

Repository: alexanderwanyoike/the0

Length of output: 497


🏁 Script executed:

# Check if pino-http requires pino ^10.0.0 specifically
grep -A20 "pino-http@\^11.0.0:" api/yarn.lock | grep -A5 "dependencies:"

Repository: alexanderwanyoike/the0

Length of output: 336


🏁 Script executed:

# Let's see if pino ^9.6.0 exists in the lockfile at all
grep "^pino@\^9" api/yarn.lock

Repository: alexanderwanyoike/the0

Length of output: 48


Update the pino version to satisfy existing dependency constraints.

Line 57 adds "pino": "^9.6.0", but pino-http@^11.0.0 (already in dependencies) requires pino@^10.0.0. This version conflict will cause yarn install --frozen-lockfile to fail in CI. Either update pino to ^10.0.0 or downgrade pino-http to a version compatible with pino ^9.6.0, then run yarn install in api/ and commit the updated lockfile.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/package.json` at line 57, Change the conflicting dependency so
pino-http's peer requirement is satisfied: update the "pino" dependency in
package.json from "^9.6.0" to "^10.0.0" (or alternatively downgrade "pino-http"
to a version compatible with pino "^9.6.0"), then run yarn install in the api/
directory and commit the resulting lockfile so CI's yarn install
--frozen-lockfile will succeed; target the "pino" and "pino-http" entries when
making the change.

Comment on lines +44 to 45
bootstrapLogger.error({ err: error }, "Failed to start the0 API");
process.exit(1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and examine lines around 44-45
if [ -f "api/src/main.ts" ]; then
  echo "=== File: api/src/main.ts ===" 
  wc -l api/src/main.ts
  echo "=== Lines 40-50 context ==="
  sed -n '40,50p' api/src/main.ts | cat -n
else
  echo "File api/src/main.ts not found"
  git ls-files | grep "main.ts"
fi

Repository: alexanderwanyoike/the0

Length of output: 367


🏁 Script executed:

# Search for bootstrap function and error handling patterns
rg "bootstrap.*catch|process\.exit" api/src/ -A 3 -B 3

Repository: alexanderwanyoike/the0

Length of output: 2067


🏁 Script executed:

# Check how the logger is initialized to understand pino setup
rg "bootstrapLogger|pino|winston" api/src/main.ts -B 2 -A 2

Repository: alexanderwanyoike/the0

Length of output: 801


🏁 Script executed:

# Check pino configuration and understand default behavior
rg "pino|transport|stream" api/src/main.ts api/src/app.module.ts -B 2 -A 2

Repository: alexanderwanyoike/the0

Length of output: 559


🏁 Script executed:

# Check if there are any pino transport configurations or logging setup
fd -e ts -e js api/src | xargs grep -l "pino\|Logger" | head -10

Repository: alexanderwanyoike/the0

Length of output: 399


🌐 Web query:

pino logger process.exit synchronous stdout flushing Node.js best practices

💡 Result:

In Node.js, when using Pino logger and calling process.exit, logs may not flush to stdout synchronously by default, potentially losing recent entries due to buffering in sonic-boom (Pino's underlying writer). This is a known issue discussed extensively in Pino GitHub issues since v5+. Best practices for synchronous stdout flushing before process.exit: 1. Use pino.destination explicitly (sonic-boom defaults to sync: true for stdout/files): import pino from 'pino'; const dest = pino.destination; // or pino.destination(1) for explicit stdout fd const logger = pino(dest); logger.info('Will flush'); process.exit(1); // Logs guaranteed due to synchronous writes This ensures writes are synchronous (fs.writeSync), preventing loss on abrupt exit. Performance trade-off: slower than async but safe for exit scenarios. 2. For async destinations (sync: false), call logger.flushSync or dest.flushSync before exit: const dest = pino.destination({ sync: false }); const logger = pino(dest); logger.info('Flush me'); dest.flushSync; // Synchronous buffer flush process.exit(1); Note: logger.flush is async (fire-and-forget); use flushSync for blocking guarantee before exit. 3. Use logger.fatal: Automatically sync-flushes as it's designed for pre-exit logging. logger.fatal('Exiting with error'); process.exit(1); // Flushed automatically 4. Avoid transports like pino-pretty in production/exit-critical paths: They run in worker threads, causing flush races (Issue #1705). Use { sync: true } if needed, but prefer raw JSON for stdout. const stream = pino.transport({ target: 'pino-pretty', options: { sync: true } }); const logger = pino(stream); // Still risky on exit; test thoroughly 5. Graceful shutdown handlers (preferred for signals, not direct process.exit): import onExit from 'on-exit-leak-free'; // Used internally by Pino onExit( => { logger.flushSync; process.exit(0); }); Pino registers these automatically in modern Node (v14+), but direct process.exit bypasses 'beforeExit'. Direct process.exit terminates immediately after synchronous code, so synchronous writes/flushes are essential. For high-perf async logging, periodic logger.flush (e.g., every 10s) reduces risk, but sync destination is simplest for exit safety. Test: Modern Pino (v8+) with pino.destination reliably flushes on process.exit(1).

Citations:


Ensure error logs flush before exiting during bootstrap failures.

Line 46 calls process.exit(1) immediately after logging; this can truncate the fatal error log because process.exit() bypasses Node's graceful shutdown and stream flushing. Use process.exitCode = 1 with return to allow pino to flush logs to stdout before termination.

Proposed change
bootstrap().catch((error) => {
  bootstrapLogger.error({ err: error }, "Failed to start the0 API");
-  process.exit(1);
+  process.exitCode = 1;
+  return;
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bootstrapLogger.error({ err: error }, "Failed to start the0 API");
process.exit(1);
bootstrap().catch((error) => {
bootstrapLogger.error({ err: error }, "Failed to start the0 API");
process.exitCode = 1;
return;
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/main.ts` around lines 44 - 45, Replace the immediate process.exit(1)
call after bootstrapLogger.error to allow pino to flush: set process.exitCode =
1 and return from the bootstrap/startup flow instead of calling process.exit(1),
so leave the bootstrapLogger.error(...) call intact but replace the terminating
call (process.exit(1)) with assigning process.exitCode = 1 and returning from
the function that runs startup (where main/bootstrap logic executes) to let logs
be flushed.

@alexanderwanyoike
Copy link
Copy Markdown
Owner

Hey @sputnik-mac, CI is failing because yarn.lock is out of sync. You added pino to package.json but the lockfile wasn't updated. Run yarn install locally and commit the updated yarn.lock.

Also heads up, main.ts has changed on dev since this was opened (JWT_SECRET startup validation was added), so you'll need to rebase onto latest dev as well.

Copy link
Copy Markdown
Owner

@alexanderwanyoike alexanderwanyoike left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a look at the comment I mentioned above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace console.log with structured logger in API startup

2 participants