Skip to content

feat(server): migrate to pino for asynchronous structured logging (fi…#881

Merged
Sachinchaurasiya360 merged 5 commits into
Sachinchaurasiya360:mainfrom
ManoharKonala:feat/pino-logging
Jun 1, 2026
Merged

feat(server): migrate to pino for asynchronous structured logging (fi…#881
Sachinchaurasiya360 merged 5 commits into
Sachinchaurasiya360:mainfrom
ManoharKonala:feat/pino-logging

Conversation

@ManoharKonala

@ManoharKonala ManoharKonala commented May 31, 2026

Copy link
Copy Markdown
Contributor

Resolves #880

This PR migrates the backend's centralized logging utility from a synchronous console.log wrapper to Pino, a high-performance, asynchronous JSON logger.

Why
Currently, the backend relies heavily on console.log, console.warn, and console.error. In a high-traffic Node.js environment, writing to process.stdout synchronously blocks the event loop, causing severe latency spikes during heavy I/O operations (like bulk scraping, cron processing, or massive API traffic). Furthermore, unstructured text logs are extremely difficult to index, parse, and filter reliably in production log aggregators (e.g., Datadog, AWS CloudWatch, ELK).

What Changed
Pino Engine: Refactored server/src/utils/logger.ts to instantiate a root Pino logger. This replaces the old fmt() wrapper while keeping the exact same createLogger(moduleName) interface so that no existing call sites across the codebase break.
Graceful Developer Experience: Integrated pino-pretty as a dev dependency. When NODE_ENV !== "production", logs are automatically piped through pino-pretty to provide human-readable, colorized terminal output with accurate timestamps.
Production Readiness: In production, the logger falls back to emitting raw, incredibly fast JSON lines ({"level":30,"time":...}), ensuring optimal performance and seamless integration with observability stacks.
Index Cleanup: Upgraded critical startup and shutdown logs in server/src/index.ts to use the new structured logger instead of direct console.* calls.

Testing / Verification
Verified compilation success (npx tsc --noEmit) with no TypeScript signature errors.
Passed all 106 backend unit tests successfully.
Confirmed startup, database connections, and cron initializations log gracefully in development mode.

Next Steps / Scope
This PR intentionally limits the refactor to index.ts and logger.ts to keep the diff reviewable. Future tech-debt tasks can iteratively replace any remaining raw console.log calls across other services.

Summary by CodeRabbit

  • New Features

    • Introduced structured logging for improved server diagnostics and startup/shutdown messages.
  • Refactor

    • Consolidated copy-to-clipboard into a reusable CopyButton component.
    • Resolved merge conflicts and simplified an event handler.
    • Removed unused UI icon assets.
  • Chores

    • Added runtime logging support and related package adjustments.

@github-actions github-actions Bot added enhancement New feature or request gssoc quality:clean Clean and well-structured contribution level:critical High-impact or critical changes labels May 31, 2026
@github-actions github-actions Bot added the gssoc:approved Approved for GSSoC scoring label May 31, 2026
@github-actions

Copy link
Copy Markdown

Hi @ManoharKonala, thanks for contributing to InternHack! 🎉

I have automatically:

  • 👤 Assigned this PR to you.
  • 🏷️ Applied the gssoc:approved label.

Our workflows will now analyze your changes to classify:

  • 📈 PR Difficulty: level:*
  • 🧩 PR Type: type:*
  • 🌟 PR Quality: quality:*

Tip

Ensure your PR description references the issue it resolves (e.g. Closes #123). This allows the bot to inherit any additional labels from that issue!

Happy coding! 🚀

@github-actions github-actions Bot added the type:feature New feature implementation label May 31, 2026
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5d2dd700-4d97-49dd-90ca-7d6c574d0249

📥 Commits

Reviewing files that changed from the base of the PR and between 210602a and b990bac.

⛔ Files ignored due to path filters (1)
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • .env.example
  • server/.env.example
  • server/package.json
  • server/src/module/recruiter/recruiter.service.ts

📝 Walkthrough

Walkthrough

Backend logging is migrated to pino with a root logger and child loggers; server index and one service are updated to use the structured logger. Frontend: local clipboard handlers and copy icon imports are removed in favor of a shared CopyButton, merge-conflict markers resolved, and an ESLint hook suppression added.

Changes

Logging Infrastructure Migration

Layer / File(s) Summary
Pino root logger and createLogger
server/src/utils/logger.ts, server/package.json
Adds pino and pino-pretty, creates a root Pino logger configured by LOG_LEVEL/NODE_ENV, and implements createLogger returning child loggers that normalize errors and merge optional metadata.
Server index and shutdown integration
server/src/index.ts
Initializes module logger and replaces console.log/error usages with logger.info/error in process handlers, app startup, AI init failure handling, and shutdown hooks.
Recruiter service error logging tweak
server/src/module/recruiter/recruiter.service.ts
Adjusts logger.error invocation to pass the caught err as a separate argument to the logger in the email-send failure handler.

Frontend clipboard consolidation and small fixes

Layer / File(s) Summary
Replace local copy handlers with CopyButton
client/src/module/student/ats/CoverLetterPage.tsx, client/src/module/student/ats/LatexResumeEditor.tsx
Removes local clipboard handlers, copied state, and Copy icon imports; binds shared CopyButton to cover letter/LaTeX content.
RepoDiscovery conflict resolution and lint suppression
client/src/module/student/opensource/RepoDiscoveryPage.tsx, client/src/module/student/sql/SqlExercisePage.tsx
Resolves merge-conflict around useRecentlyViewedRepos destructuring, simplifies trending toggle handler, and adds an eslint-disable-next-line react-hooks/exhaustive-deps comment before a useEffect.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

type:performance, level:advanced

Suggested reviewers

  • Sachinchaurasiya360

Poem

🐰 I hop through logs both clear and neat,
Pino hums and keeps the beat,
CopyButton shares a single cheer,
Conflicts cleared, the code runs clear,
A joyful hop — carrot treat! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes in CoverLetterPage.tsx, LatexResumeEditor.tsx, RepoDiscoveryPage.tsx, and SqlExercisePage.tsx (refactoring Copy handlers and resolving merge conflicts) appear unrelated to Pino logging migration, suggesting possible merge artifact or scope drift. Clarify whether client-side changes were intentional or inadvertently included; if unrelated to #880, move them to a separate PR to maintain focused scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: migrating backend logging to Pino for asynchronous structured logging, directly matching the primary objective of this PR.
Description check ✅ Passed The description is comprehensive, covering motivation, implementation details, testing, and scope; it aligns well with the template sections and linked issue requirements.
Linked Issues check ✅ Passed The PR successfully implements all key requirements from issue #880: installing pino/pino-pretty [package.json], updating logger.ts with root Pino and child loggers [logger.ts], preserving createLogger interface, adding pino-pretty dev transport, and refactoring index.ts console calls [index.ts].

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/src/utils/logger.ts`:
- Around line 35-42: The logger.error method is flattening Error instances to {
message, stack } which strips other fields (e.g., Prisma's code/cause); update
the error handling in the error(msg: string, error?: unknown, meta?: unknown)
function so that when error is an Error it is passed as the original object
under the key err (i.e., { err: error }) instead of { err: { message, stack } },
while keeping the existing payload merge with meta and calling
logger.error(payload, msg); this preserves Pino's error serializer and
downstream checks like err?.code in milestone.service.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2fb7cb3-57d4-4566-a88d-684fb1aed52f

📥 Commits

Reviewing files that changed from the base of the PR and between 04a086b and ed51e11.

⛔ Files ignored due to path filters (1)
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • server/package.json
  • server/src/index.ts
  • server/src/utils/logger.ts

Comment on lines 35 to +42
error(msg: string, error?: unknown, meta?: unknown) {
const errInfo = error instanceof Error
? { message: error.message, stack: error.stack }
: error;
console.error(fmt("ERROR", module, msg, meta ?? errInfo));
const errInfo =
error instanceof Error
? { err: { message: error.message, stack: error.stack } }
: { err: error };

const payload = meta ? { ...errInfo, ...(meta as object) } : errInfo;
logger.error(payload, msg);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In pino v10, when calling logger.error({ err: someError }, "msg"), does Pino preserve the Error's type/name, stack, and custom enumerable fields compared with manually flattening it to { message, stack }?

💡 Result:

Yes. In Pino, when you call logger.error({ err: someError }, "msg") (where the error is placed under the special top-level key "err"), Pino applies its standard error serializer and serializes the Error into a structured object that includes the Error’s constructor name as type, the message, and the stack, and it also includes any additional enumerable properties attached to the Error. The std error serializer returns an object of the form: - type: constructor name (Error’s name) - message: the supplied error message - stack: the stack when the error was generated - raw: the original Error kept non-enumerable for subsequent serializers - any additional enumerable properties that the original Error had [1] So compared with manually flattening to { message, stack }: - Pino preserves Error’s type/name via type (constructor name) [1] - Pino preserves stack via stack [1] - Pino preserves custom enumerable fields attached to the Error by copying them into the serialized output [1] Note: Pino’s built-in behavior is tied to using the error property name "err" (or the configured errorKey). The core docs state the standard error serializer is used when logging errors under that key (and also when passing an Error instance directly as the first argument) [2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant section of logger.ts
file="server/src/utils/logger.ts"
if [ -f "$file" ]; then
  echo "== $file =="
  nl -ba "$file" | sed -n '1,200p' | sed -n '25,90p'
else
  echo "Missing file: $file"
fi

# Find call sites of logger.error(payload, msg) or of logger.error wrapper
# Search for ".error(" usages in server/src that use this logger util.
rg -n --hidden --glob 'server/src/**/*.{ts,js}' "logger\.error\(" server/src/utils/logger.ts server/src | head -n 50 || true

# Search for the wrapper signature error(msg, error?, meta?)
rg -n --hidden --glob 'server/src/**/*.{ts,js}' "error\\(msg: string, error\\?: unknown, meta\\?: unknown\\)" -S server/src || true

# Search for milestone.service.ts lines around 131-171 to see example errors
target="server/src/module/milestone/milestone.service.ts"
if [ -f "$target" ]; then
  echo "== $target =="
  nl -ba "$target" | sed -n '110,210p'
else
  echo "Missing file: $target"
fi

Repository: Sachinchaurasiya360/InternHack

Length of output: 168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="server/src/utils/logger.ts"
if [ -f "$file" ]; then
  echo "== $file =="
  nl -ba "$file" | sed -n '25,90p'
else
  echo "Missing file: $file"
fi

rg -n --hidden --glob 'server/src/**/*.{ts,js}' "logger\.error\(" server/src/utils/logger.ts server/src | head -n 80 || true

rg -n --hidden --glob 'server/src/**/*.{ts,js}' "error\\(msg: string, error\\?: unknown, meta\\?: unknown\\)" -S server/src || true

target="server/src/module/milestone/milestone.service.ts"
if [ -f "$target" ]; then
  echo "== $target =="
  nl -ba "$target" | sed -n '120,200p'
else
  echo "Missing file: $target"
fi

Repository: Sachinchaurasiya360/InternHack

Length of output: 168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="server/src/utils/logger.ts"
if [ -f "$file" ]; then
  echo "== $file =="
  nl -ba "$file" | sed -n '25,90p'
else
  echo "Missing file: $file"
fi

rg -n --hidden --glob 'server/src/**/*.{ts,js}' "logger\.error\(" server/src/utils/logger.ts server/src | head -n 80 || true

rg -n --hidden --glob 'server/src/**/*.{ts,js}' "error\\(msg: string, error\\?: unknown, meta\\?: unknown\\)" -S server/src || true

target="server/src/module/milestone/milestone.service.ts"
if [ -f "$target" ]; then
  echo "== $target =="
  nl -ba "$target" | sed -n '120,200p'
else
  echo "Missing file: $target"
fi

Repository: Sachinchaurasiya360/InternHack

Length of output: 168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="server/src/utils/logger.ts"

echo "== file exists? =="
ls -l "$file" || true
echo "== wc -l =="
wc -l "$file" || true

echo "== snippet (lines 1-120) =="
awk '{printf "%d\t%s\n", NR, $0}' "$file" | sed -n '1,120p'

echo "== snippet (lines 25-70) =="
awk '{printf "%d\t%s\n", NR, $0}' "$file" | sed -n '25,70p'

echo "== find wrapper signature error(msg: string, error?, meta?) =="
rg -n "error\\(msg: string, error\\?: unknown, meta\\?: unknown\\)" -S "$file" || true

echo "== search for calls to milestone.service.ts around 120-200 =="
target="server/src/module/milestone/milestone.service.ts"
ls -l "$target" || true
wc -l "$target" || true
awk '{printf "%d\t%s\n", NR, $0}' "$target" | sed -n '110,200p'

echo "== find usages of logger.error(...) in server/src =="
rg -n --hidden --glob 'server/src/**/*.{ts,js}' "logger\\.error\\(" server/src | head -n 80 || true

Repository: Sachinchaurasiya360/InternHack

Length of output: 13455


Preserve full Error objects when logging (don’t flatten to { message, stack }).
server/src/utils/logger.ts currently converts Error instances into { err: { message, stack } }, so Pino’s built-in err error serializer can’t retain fields like Prisma’s code/cause (e.g., server/src/module/milestone/milestone.service.ts checks err?.code). Pass the original error under err instead.

Suggested fix
     error(msg: string, error?: unknown, meta?: unknown) {
-      const errInfo =
-        error instanceof Error
-          ? { err: { message: error.message, stack: error.stack } }
-          : { err: error };
-          
-      const payload = meta ? { ...errInfo, ...(meta as object) } : errInfo;
-      logger.error(payload, msg);
+      const errInfo = error === undefined ? {} : { err: error };
+      const payload = meta
+        ? { ...errInfo, ...(meta as Record<string, unknown>) }
+        : errInfo;
+
+      return Object.keys(payload).length > 0
+        ? logger.error(payload, msg)
+        : logger.error(msg);
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/src/utils/logger.ts` around lines 35 - 42, The logger.error method is
flattening Error instances to { message, stack } which strips other fields
(e.g., Prisma's code/cause); update the error handling in the error(msg: string,
error?: unknown, meta?: unknown) function so that when error is an Error it is
passed as the original object under the key err (i.e., { err: error }) instead
of { err: { message, stack } }, while keeping the existing payload merge with
meta and calling logger.error(payload, msg); this preserves Pino's error
serializer and downstream checks like err?.code in milestone.service.ts.

@Sachinchaurasiya360 Sachinchaurasiya360 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review

Verdict: Approve with minor notes

Issues

**1. must be a **
It is listed under dependencies but is only used when NODE_ENV !== 'production'. Move it to devDependencies — it drags 15+ transitive packages into the production bundle for no reason.

2. logger.error signature change is a silent breaking change
The old signature was error(msg, error?, meta?). The new implementation wraps the error under { err: { message, stack } } before passing to pino. Any existing call site passing (msg, error) will now serialize differently. Please grep all createLogger consumers and verify no call site is affected before merging.

3. Module prefix lost from log messages
The old logger embedded [module] in the message string. It is now a structured module field on the JSON object (which is better for structured logging), but anyone grepping raw logs for [Redis] or [AI] will silently miss results. Worth a note in the PR description.

4. LOG_LEVEL env var is undocumented
It is consumed in logger.ts but not present in .env.example or any README. Add it.

5. Remaining console.* calls in services
The PR description says scope is intentionally limited to index.ts and logger.ts — that is fine, but please link a follow-up issue so the remaining raw console calls across services are tracked as debt.

@Sachinchaurasiya360 Sachinchaurasiya360 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review

Verdict: Approve with minor notes

Issues

1. pino-pretty must be a devDependency
It is listed under dependencies but is only used when NODE_ENV !== 'production'. Move it to devDependencies — it drags 15+ transitive packages into the production bundle for no reason.

2. logger.error signature change is a silent breaking change
The old signature was error(msg, error?, meta?). The new implementation wraps the error under { err: { message, stack } } before passing to pino. Any existing call site passing (msg, error) will now serialize differently. Grep all createLogger consumers and verify no call site is affected before merging.

3. Module prefix lost from log messages
The old logger embedded [module] in the message string. It is now a structured module field on the JSON object (better for structured logging), but anyone grepping raw logs for [Redis] or [AI] will silently miss results. Worth noting in the PR description.

4. LOG_LEVEL env var is undocumented
Consumed in logger.ts but absent from .env.example and any README. Add it.

5. Remaining console.* calls in services
Scope intentionally limited to index.ts and logger.ts — that is fine, but please link a follow-up issue so the remaining raw console calls across services are tracked as debt.

@Sachinchaurasiya360

Copy link
Copy Markdown
Owner

fix the lint

konal added 3 commits May 31, 2026 23:43
- Move pino-pretty to devDependencies

- Fix logger.error call site passing meta as error

- Document LOG_LEVEL in .env.example files
@github-actions github-actions Bot added the type:security Security-related fixes or improvements label May 31, 2026
@ManoharKonala

ManoharKonala commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

@Sachinchaurasiya360
Moved pino-pretty to devDependencies so it doesn't bloat the prod bundle.
Good catch on the logger.error signature! I checked all the usages across the codebase and found only one spot in recruiter.service.ts that was passing an object as the second arg. I fixed it to pass the actual error object. Everything else was already fine.
Added a note to the PR description about the [module] prefix changing to a JSON field.
Added LOG_LEVEL=info to both .env.example files.
I just opened a follow-up issue to clean up the rest of the raw console.* calls: #891
Let me know if anything else stands out

@Sachinchaurasiya360 Sachinchaurasiya360 merged commit a6611c3 into Sachinchaurasiya360:main Jun 1, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request gssoc:approved Approved for GSSoC scoring gssoc level:critical High-impact or critical changes quality:clean Clean and well-structured contribution type:feature New feature implementation type:security Security-related fixes or improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ENHANCEMENT]: Migrate backend to use Pino for asynchronous structured JSON logging

2 participants