Skip to content

feat: Implement structured logging with NestJS Pino and a global exce…#559

Open
PaulicStudios wants to merge 2 commits intodevfrom
548-add-logging-to-api
Open

feat: Implement structured logging with NestJS Pino and a global exce…#559
PaulicStudios wants to merge 2 commits intodevfrom
548-add-logging-to-api

Conversation

@PaulicStudios
Copy link
Member

@PaulicStudios PaulicStudios commented Feb 21, 2026

…ption filter.

Summary by CodeRabbit

  • Bug Fixes

    • Unified and improved error handling and responses across the API.
  • Chores

    • Updated core framework dependencies to latest patches.
    • Integrated structured request/response logging and pretty-printing for easier monitoring.
  • Changes

    • Queue-related endpoints now require caller identity to be provided, ensuring clearer audit and authorization checks.

@PaulicStudios PaulicStudios requested a review from Peu77 February 21, 2026 18:23
@PaulicStudios PaulicStudios self-assigned this Feb 21, 2026
@PaulicStudios PaulicStudios linked an issue Feb 21, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

Adds structured logging (nestjs-pino/pino) and a global AllExceptionsFilter; integrates logger instances across multiple controllers and updates main/app module to use the new logger and filters. No public API signatures were removed; a few controller method signatures were extended (team queue methods).

Changes

Cohort / File(s) Summary
Dependencies & App Bootstrap
api/package.json, api/src/app.module.ts, api/src/main.ts
Added nestjs-pino, pino, pino-http, and pino-pretty (dev); configured LoggerModule.forRoot in AppModule and wired app logger via app.useLogger in main/bootstrap.
Global Exception Handling
api/src/common/AllExceptionsFilter.ts, api/src/common/TypeormExceptionFilter.ts
Added AllExceptionsFilter implementing uniform HTTP responses and status-aware logging; enhanced TypeormExceptionFilter to log EntityNotFoundError with request context.
Auth Controller Logging
api/src/auth/auth.controller.ts
Added Logger field and logging calls for OAuth flows (github_login, fortytwo_link); replaced console.error with structured logger and enhanced error logging.
Event Controller Logging
api/src/event/event.controller.ts
Added Logger and numerous structured logs for event actions (create, join, lock/unlock, admin changes, starter templates) at authorization/action points.
Match Controller Logging
api/src/match/match.controller.ts
Replaced logger declaration with private readonly logger = new Logger(MatchController.name) and added logs for match lifecycle actions (start matches, reveal, cleanup).
Team Controller: Logging & Signature Changes
api/src/team/team.controller.ts
Added Logger and logs for team actions; updated joinQueue and leaveQueue method signatures to accept userId parameter alongside team.
User Controllers & Module
api/src/user/user.controller.ts, api/src/user/user.events.controller.ts, api/src/user/user.module.ts, api/src/user/social-account.service.ts
Added Logger and a searchUsers log; minor formatting/whitespace/import reflow in events controller, module, and social-account service (no signature changes except logging additions).
DTOs & Constants Formatting
api/src/constants.ts, api/src/event/dtos/createEventDto.ts, api/src/event/dtos/updateEventSettingsDto.ts
Small formatting/whitespace adjustments and trailing newline changes only.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant NestApp as NestJS App
    participant Controller as Controller (Auth/Event/Team/Match/User)
    participant Service as Service Layer
    participant DB as Database
    participant Logger as Pino Logger
    participant Filter as AllExceptionsFilter

    Client->>NestApp: HTTP request
    NestApp->>Controller: route handler
    Controller->>Logger: log attempt_{action} (userId, metadata)
    Controller->>Service: perform action
    Service->>DB: query/update
    DB-->>Service: result
    Service-->>Controller: result
    Controller-->>Client: response
    alt exception thrown
        Controller->>Filter: exception propagated
        Filter->>Logger: log 4xx/5xx with context
        Filter-->>Client: uniform error response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Peu77

"I hopped through logs with tiny paws and glee,
I marked each login, match, and team-degree,
With pino lights and filters brave and bright,
The rabbit notes each error, wrong or right,
And munches carrots while the traces flee." 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objectives of the pull request: implementing structured logging with NestJS Pino and adding a global exception filter, which are the core changes across the codebase.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 548-add-logging-to-api

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.

Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/src/team/team.controller.ts (1)

272-286: ⚠️ Potential issue | 🟡 Minor

joinQueue and leaveQueue log entries are missing userId, making them unattributable.

Every other mutating endpoint in this controller (and across the PR) logs which user performed the action. Queue operations are important audit events that should include the actor.

♻️ Proposed fix
  `@UseGuards`(JwtAuthGuard, MyTeamGuards)
  `@Put`(`event/:${EVENT_ID_PARAM}/queue/join`)
- async joinQueue(`@Team`() team: TeamEntity) {
+ async joinQueue(`@Team`() team: TeamEntity, `@UserId`() userId: string) {
    if (team.inQueue)
      throw new BadRequestException("You are already in the queue.");

    if (!(await this.eventService.hasEventStartedForTeam(team.id)))
      throw new BadRequestException("The event has not started yet.");

    this.logger.log({
      action: "join_queue",
      teamId: team.id,
+     userId,
    });

    return this.teamService.joinQueue(team.id);
  }

  `@UseGuards`(JwtAuthGuard, MyTeamGuards)
  `@Put`(`event/:${EVENT_ID_PARAM}/queue/leave`)
- async leaveQueue(`@Team`() team: TeamEntity) {
+ async leaveQueue(`@Team`() team: TeamEntity, `@UserId`() userId: string) {
    if (!team.inQueue)
      throw new BadRequestException("You are not in the queue.");

    this.logger.log({
      action: "leave_queue",
      teamId: team.id,
+     userId,
    });

    return this.teamService.leaveQueue(team.id);
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/team/team.controller.ts` around lines 272 - 286, The joinQueue (and
the corresponding leaveQueue) log is missing the actor; update joinQueue(`@Team`()
team: TeamEntity) and leaveQueue to include the authenticated user's id in the
logger payload by accepting the user from the auth context (e.g. add a parameter
to the handler to get request.user or a `@User`() user with user.id provided by
JwtAuthGuard) and add userId: user.id to the this.logger.log calls before
calling teamService.joinQueue/teamService.leaveQueue so the audit entry includes
the actor.
🧹 Nitpick comments (4)
api/src/match/match.controller.ts (1)

27-27: Inconsistent logger declaration compared to other controllers.

Other controllers in this PR (e.g., TeamController, EventController, UserController) declare the logger as:

private readonly logger = new Logger(ControllerName.name);

This file uses a string literal and omits readonly:

private logger = new Logger("MatchController");
♻️ Align with the rest of the codebase
-  private logger = new Logger("MatchController");
+  private readonly logger = new Logger(MatchController.name);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/match/match.controller.ts` at line 27, Update the logger declaration
in MatchController to match other controllers: change the property declaration
from a non-readonly string-based logger to a readonly logger using the class
name (i.e., replace the existing private logger = new Logger("MatchController")
with a private readonly logger = new Logger(MatchController.name)) so it aligns
with TeamController/EventController/UserController patterns and prevents
accidental reassignment.
api/src/common/TypeormExceptionFilter.ts (1)

25-30: Consider whether exception.message in the response leaks internal details.

The error: exception.message field may expose TypeORM internals (e.g., entity/table names, query fragments) to API consumers. The AllExceptionsFilter uses a generic message for non-HTTP exceptions. You could align this by removing or generalizing the error field.

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

In `@api/src/common/TypeormExceptionFilter.ts` around lines 25 - 30, The response
currently returns potentially sensitive internals via error: exception.message
in the TypeormExceptionFilter when sending HttpStatus.NOT_FOUND; change the JSON
payload to avoid leaking internals by removing the error field or replacing it
with a generic value (e.g., "Not Found" or "Resource not found") so it matches
the AllExceptionsFilter behavior, and log the full exception.message privately
using the filter's logger (e.g., inside the TypeormExceptionFilter.catch
handler) for debugging instead of returning it to the client.
api/src/common/AllExceptionsFilter.ts (1)

42-51: Logging stack traces for 4xx errors is likely too noisy.

Every BadRequestException, UnauthorizedException, NotFoundException, etc. will dump a full stack trace as a warning. In production, these are frequent and expected — the stack trace adds little diagnostic value for client errors while significantly increasing log volume.

Consider logging only the message for 4xx and reserving stack traces for 5xx:

♻️ Reduce 4xx log verbosity
     } else {
       // 4xx errors
-      if (exception instanceof Error) {
-        this.logger.warn(
-          `HTTP Exception: ${exception.message}`,
-          exception.stack,
-        );
-      } else {
-        this.logger.warn(`HTTP Exception: ${exception}`);
-      }
+      this.logger.warn(
+        `HTTP Exception: ${exception instanceof Error ? exception.message : exception}`,
+      );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/common/AllExceptionsFilter.ts` around lines 42 - 51, The current 4xx
branch in AllExceptionsFilter logs full stack traces for client errors; change
the logic to detect HTTP status (use the HttpException/getStatus() or existing
status extraction used earlier in the filter) and for statuses < 500 log only
the exception message via this.logger.warn(`HTTP Exception: ${message}`) without
the stack, reserving stack logging (this.logger.warn/this.logger.error with
exception.stack) only for status >= 500 or non-HTTP errors; update the block
that checks "exception instanceof Error" to consult the HTTP status and
conditionally include the stack trace accordingly.
api/src/event/event.controller.ts (1)

97-103: Logs fire before the service call — they record intent, not confirmed outcome.

If eventService.createEvent(...) (or any of the other service calls in this controller) throws, the log entry has already been emitted indicating the action succeeded. This pattern is repeated across every mutating endpoint (join_event, lock_event, unlock_event, etc.) and in team.controller.ts as well.

Either move the log after the await (for async calls) or name the action attempt_* to make the intent semantics explicit.

♻️ Example fix for one call site
-    this.logger.log({
-      action: "create_event",
-      userId,
-      eventName: createEventDto.name,
-    });
-
-    return this.eventService.createEvent(
+    const result = await this.eventService.createEvent(
       userId,
       createEventDto.name,
       // ...
     );
+
+    this.logger.log({
+      action: "create_event",
+      userId,
+      eventName: createEventDto.name,
+    });
+
+    return result;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/event/event.controller.ts` around lines 97 - 103, Logs are being
emitted before the service call so they record intent not outcome; for each
mutating controller method (e.g., createEvent in event.controller.ts, joinEvent,
lockEvent, unlockEvent, and corresponding methods in team.controller.ts) move
the this.logger.log call to after the await this.eventService.* (or await
this.teamService.*) so it runs only on successful completion, or alternatively
change the action name to "attempt_*" (e.g., "attempt_create_event") to indicate
intent; update all occurrences consistently and ensure any early-return/error
paths do not produce a success-style log.
🤖 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/src/auth/auth.controller.ts`:
- Around line 120-122: The catch block currently calls logger.error("Error in
FortyTwo callback:", e) which misuses NestJS Logger.error signature and risks
leaking sensitive data; update the catch to (1) detect BadRequestException vs
other errors (check instanceof BadRequestException) and extract a safe, minimal
message for logs, (2) pass a string stack trace (e.stack || String(e)) as the
second argument to logger.error rather than the raw Error object, and (3) avoid
serializing the whole Error (do not pass e directly) so sensitive OAuth response
bodies/tokens are not logged; reference the catch(e) block and the logger.error
call in auth.controller.ts and preserve BadRequestException message handling
while ensuring only safe strings are logged.

In `@api/src/common/AllExceptionsFilter.ts`:
- Around line 17-19: AllExceptionsFilter's catch currently assumes an HTTP
context; add a guard that checks the incoming ArgumentsHost type and returns
early for non-HTTP contexts so HTTP-specific calls aren't invoked. In the catch
method (AllExceptionsFilter.catch), before calling host.switchToHttp() or using
this.httpAdapter.getRequestUrl()/reply(), check host.getType && host.getType()
=== 'http' (or equivalent isHttp check) and skip the HTTP response
construction/this.httpAdapter.reply() when false; leave logging/exception
handling for non-HTTP contexts unchanged.

---

Outside diff comments:
In `@api/src/team/team.controller.ts`:
- Around line 272-286: The joinQueue (and the corresponding leaveQueue) log is
missing the actor; update joinQueue(`@Team`() team: TeamEntity) and leaveQueue to
include the authenticated user's id in the logger payload by accepting the user
from the auth context (e.g. add a parameter to the handler to get request.user
or a `@User`() user with user.id provided by JwtAuthGuard) and add userId: user.id
to the this.logger.log calls before calling
teamService.joinQueue/teamService.leaveQueue so the audit entry includes the
actor.

---

Nitpick comments:
In `@api/src/common/AllExceptionsFilter.ts`:
- Around line 42-51: The current 4xx branch in AllExceptionsFilter logs full
stack traces for client errors; change the logic to detect HTTP status (use the
HttpException/getStatus() or existing status extraction used earlier in the
filter) and for statuses < 500 log only the exception message via
this.logger.warn(`HTTP Exception: ${message}`) without the stack, reserving
stack logging (this.logger.warn/this.logger.error with exception.stack) only for
status >= 500 or non-HTTP errors; update the block that checks "exception
instanceof Error" to consult the HTTP status and conditionally include the stack
trace accordingly.

In `@api/src/common/TypeormExceptionFilter.ts`:
- Around line 25-30: The response currently returns potentially sensitive
internals via error: exception.message in the TypeormExceptionFilter when
sending HttpStatus.NOT_FOUND; change the JSON payload to avoid leaking internals
by removing the error field or replacing it with a generic value (e.g., "Not
Found" or "Resource not found") so it matches the AllExceptionsFilter behavior,
and log the full exception.message privately using the filter's logger (e.g.,
inside the TypeormExceptionFilter.catch handler) for debugging instead of
returning it to the client.

In `@api/src/event/event.controller.ts`:
- Around line 97-103: Logs are being emitted before the service call so they
record intent not outcome; for each mutating controller method (e.g.,
createEvent in event.controller.ts, joinEvent, lockEvent, unlockEvent, and
corresponding methods in team.controller.ts) move the this.logger.log call to
after the await this.eventService.* (or await this.teamService.*) so it runs
only on successful completion, or alternatively change the action name to
"attempt_*" (e.g., "attempt_create_event") to indicate intent; update all
occurrences consistently and ensure any early-return/error paths do not produce
a success-style log.

In `@api/src/match/match.controller.ts`:
- Line 27: Update the logger declaration in MatchController to match other
controllers: change the property declaration from a non-readonly string-based
logger to a readonly logger using the class name (i.e., replace the existing
private logger = new Logger("MatchController") with a private readonly logger =
new Logger(MatchController.name)) so it aligns with
TeamController/EventController/UserController patterns and prevents accidental
reassignment.

…lers and filters, including prefixing action logs with "attempt_".
Copy link

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/src/auth/auth.controller.ts (1)

120-141: ⚠️ Potential issue | 🟠 Major

Remove redundant logging in catch blocks or adjust AllExceptionsFilter to skip logging for 4xx errors.

The controller logs BadRequestException at ERROR level and then throws it, but AllExceptionsFilter catches it and logs again at WARN level. This results in duplicate log entries for the same error event. Either remove the logging from these catch blocks and rely on the filter, or configure the filter to skip logging for client errors (4xx) and only log server errors (5xx).

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

In `@api/src/auth/auth.controller.ts` around lines 120 - 141, The controller
currently logs BadRequestException and other errors inside the FortyTwo callback
catch block (see the catch handling that checks instanceof BadRequestException,
constructs errorMessage/errorStack, calls this.logger.error, then throws), which
duplicates logging done by AllExceptionsFilter; remove the this.logger.error
calls in this catch block and simply rethrow the appropriate exceptions
(preserve the existing throw e and throw new BadRequestException(...) behavior),
or alternatively update AllExceptionsFilter to skip logging for 4xx errors by
checking exception.getStatus() < 500 before logging; choose one approach and
apply consistently so client (4xx) errors are logged only once.
🧹 Nitpick comments (1)
api/src/match/match.controller.ts (1)

22-27: Move logger declaration before the constructor for consistency.

The field is declared after the constructor body, which is syntactically valid but inconsistent with auth.controller.ts (where logger precedes the constructor) and the general TypeScript class-field convention.

♻️ Suggested reorder
 `@Controller`("match")
 export class MatchController {
+  private readonly logger = new Logger(MatchController.name);
+
   constructor(
     private readonly matchService: MatchService,
     private readonly eventService: EventService,
   ) {}
-
-  private readonly logger = new Logger(MatchController.name);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/src/match/match.controller.ts` around lines 22 - 27, Move the private
readonly logger field declaration to appear before the constructor in the
MatchController class to match the class-field ordering used elsewhere (e.g.,
auth.controller.ts); locate the logger defined as "private readonly logger = new
Logger(MatchController.name)" and place it above the constructor that injects
MatchService and EventService so fields are declared before dependency-injecting
constructor parameters.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@api/src/auth/auth.controller.ts`:
- Around line 120-141: The controller currently logs BadRequestException and
other errors inside the FortyTwo callback catch block (see the catch handling
that checks instanceof BadRequestException, constructs errorMessage/errorStack,
calls this.logger.error, then throws), which duplicates logging done by
AllExceptionsFilter; remove the this.logger.error calls in this catch block and
simply rethrow the appropriate exceptions (preserve the existing throw e and
throw new BadRequestException(...) behavior), or alternatively update
AllExceptionsFilter to skip logging for 4xx errors by checking
exception.getStatus() < 500 before logging; choose one approach and apply
consistently so client (4xx) errors are logged only once.

---

Nitpick comments:
In `@api/src/match/match.controller.ts`:
- Around line 22-27: Move the private readonly logger field declaration to
appear before the constructor in the MatchController class to match the
class-field ordering used elsewhere (e.g., auth.controller.ts); locate the
logger defined as "private readonly logger = new Logger(MatchController.name)"
and place it above the constructor that injects MatchService and EventService so
fields are declared before dependency-injecting constructor parameters.

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.

add logging to api

1 participant