Skip to content

feat: implement Epic 3.5 observability logging#265

Merged
test3207 merged 10 commits intomainfrom
feature/epic-3.5-observability-logging
Dec 24, 2025
Merged

feat: implement Epic 3.5 observability logging#265
test3207 merged 10 commits intomainfrom
feature/epic-3.5-observability-logging

Conversation

@test3207
Copy link
Owner

@test3207 test3207 commented Dec 23, 2025

Summary

Implements Epic 3.5 structured logging and observability foundation for M3W.

Changes

Backend (Part of #249)

  • Trace Middleware: Added trace-middleware.ts for X-Trace-Id header handling
  • Log Collection Endpoint: POST /api/logs receives frontend logs and extracts client IP/User-Agent
  • Logger Enhancement: Updated logger.ts with createLogger(c) factory for per-request loggers
  • Pino Integration: Structured JSON logs ready for Grafana Alloy ingestion

Note: Backend route migration to new logger format will be done in a separate PR.

Frontend (#250) ✅

  • Logger SDK: New logger-client.ts with Trace-based API pattern
    • startTrace(page) returns Trace instance
    • LogOptions.traceId field for external traceId injection
    • Auto-collect client context (language, timezone, screen, networkType, isOnline)
  • API Logging: Unified pattern in router.ts
    • Simplified helper: log = trace || logger
    • Log success (info) and failure (error) for all API calls
    • Auto-generate or reuse traceId from Trace instance
  • Business Operation Logging: Added logs to key pages
    • Upload form: success/failure tracking with counts
    • Library CRUD: create/delete operations
    • Playlist CRUD: create/delete with default playlist checks
    • Song deletion: LibraryDetailPage with full context
    • Playlist song operations: remove and reorder with index tracking

Test Coverage

  • ✅ Backend: All 56 tests passing
  • ✅ Frontend: All 411 tests passing
  • ✅ TypeScript: No errors in both workspaces

Related Issues

Closes #250
Part of #249 (backend route migration pending)

Part of Epic 3.5 (Production Readiness)

Testing

\\�ash

Backend tests

cd backend && npm test

Frontend tests

cd frontend && npm test

Type check

npx tsc --noEmit
\\

Next Steps

- Backend: structured logging with Pino and trace middleware
  - Add trace-middleware.ts for X-Trace-Id header handling
  - Add /api/logs endpoint for frontend log collection
  - Extract client IP and User-Agent from headers
  - Update logger.ts with createLogger(c) factory

- Frontend: unified logging SDK with Trace pattern
  - Add logger-client.ts with Trace-based API
  - LogOptions supports external traceId injection
  - Auto-collect client context (language, timezone, screen, network)
  - Buffer logs and batch send to backend

- API logging: unified pattern in router.ts
  - Simplified log helper: log = trace || logger
  - Log success (info) and failure (error) for all API calls
  - Auto-generate or reuse traceId from Trace instance

- Business operation logging in pages:
  - Upload: success/failure tracking with counts
  - Library CRUD: create/delete with error handling
  - Playlist CRUD: create/delete with default check
  - Song deletion: LibraryDetailPage with context
  - Playlist song operations: remove and reorder

Related: #249 (Backend), #250 (Frontend), Epic 3.5
Copilot AI review requested due to automatic review settings December 23, 2025 13:04
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements Epic 3.5's observability logging foundation with structured logging infrastructure for both backend (Pino with trace middleware, log collection endpoint) and frontend (new trace-based logger API with buffering and remote submission). The implementation adds comprehensive logging across business operations including uploads, library/playlist CRUD, and player operations.

Key changes:

  • Backend: Trace middleware, enhanced logger with createLogger(c) factory, POST /api/logs endpoint for frontend log collection
  • Frontend: Complete logger-client.ts rewrite with Trace API, auto-context collection, and batched submission
  • Application-wide: Systematic logging migration to new structured format [Component][method] with { raw: {...} } pattern

Reviewed changes

Copilot reviewed 63 out of 64 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
backend/src/lib/trace-middleware.ts New middleware to extract/generate X-Trace-Id for request correlation
backend/src/lib/logger.ts Enhanced Pino logger with createLogger(c) factory for structured logging
backend/src/routes/logs.ts New endpoint to receive frontend logs with validation schema
frontend/src/lib/logger-client.ts Complete rewrite with Trace API, buffering, and remote submission
frontend/src/lib/api/router.ts Added X-Trace-Id header injection and trace option support
frontend/src/stores/* Updated all stores to new logging format with raw data wrapping
frontend/src/pages/* Added business operation logging (upload, CRUD operations)
frontend/src/lib/* Updated utilities and services to new logging format
frontend/src/components/ui/dropdown-menu.tsx Unrelated Tailwind CSS updates (contains critical typo)
Files not reviewed (1)
  • backend/package-lock.json: Language not supported

- Fix logger.error signature: pass Error objects instead of strings
- Make backend schema fields optional to match frontend LogOptions
- Revert dropdown-menu CSS to Tailwind v4 syntax (Copilot was wrong)
@test3207
Copy link
Owner Author

Copilot Review Responses

All issues have been addressed in commit ee636e6:

1. Schema inconsistency (logger-client.ts): ✅ Made col1, col2, and source optional in backend schema to match frontend LogOptions.

2-6. logger.error signature issues: ✅ Fixed all calls to pass Error objects instead of strings or undefined:

  • SignInPage: Wrapped string in Error object
  • AuthCallbackPage: Created proper Error objects with messages
  • playlistStore: Changed to Error object
  • playerStore: Corrected parameter order
  • event-handlers: Created Error object (state.error doesn't exist)

7. CSS syntax (dropdown-menu.tsx): ℹ️ This is not a typo. max-h-(--var) is the correct Tailwind CSS v4 syntax, replacing the old max-h-[var(--var)]. See IDE hint in the file confirming this.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • backend/package-lock.json: Language not supported

@test3207
Copy link
Owner Author

Re: SignInPage.tsx line 51

You're right. The errorCode from OAuth callback is a controlled error state (e.g., 'access_denied'), not an exception. Passing undefined as the error parameter is more accurate here since there's no actual Error object to log.

Fixed to: logger.error('GitHub OAuth failed', undefined, { errorCode })

@test3207
Copy link
Owner Author

Re: dropdown-menu.tsx line 66

This is Tailwind CSS v4 syntax, not an error:

  1. max-h-(--radix-dropdown-menu-content-available-height): Correct v4 syntax for CSS custom properties. The parentheses syntax is the new v4 way to reference CSS variables directly.

  2. min-w-32: Correct. min-w-32 = 8rem (128px) in Tailwind v4. Line 48 also uses min-w-32 consistently.

These are intentional v4 upgrades, not bugs. See Tailwind CSS v4 docs for the new arbitrary value syntax.

@test3207
Copy link
Owner Author

Re: dropdown-menu.tsx line 85

You're correct. The data-[disabled] syntax is missing the colon. Should be data-[disabled]: to properly apply disabled state styling.

Will fix this.

@test3207
Copy link
Owner Author

CORRECTION: Re: dropdown-menu.tsx line 85

Wait - checking Tailwind CSS v4 docs. The syntax data-disabled: (without brackets) might be the new v4 data attribute variant syntax. Let me verify this is intentional v4 syntax before making changes.

If data-disabled: is valid v4 syntax (similar to how max-h-() is), then this is correct and no fix needed.

@test3207
Copy link
Owner Author

Re: OfflineSettings.tsx line 246

Checking if min-w-25 is valid Tailwind v4 syntax. In v4, arbitrary spacing scale might support this. Will verify against v4 docs before changing.

If min-w-25 is not supported in v4, will change to min-w-[100px].

@test3207
Copy link
Owner Author

Re: backend/src/routes/logs.ts line 46

Good catch. The source field is optional in the schema but accessed directly in the logging code. However, checking the actual usage:

  • Line 99-100 access log.source directly in the context object
  • If source is undefined, it will be logged as undefined in the JSON

This is actually safe - Pino will serialize undefined as null in JSON. But for clarity, we could add optional chaining or make source required.

Current behavior is acceptable (logs undefined as null), but if you prefer explicit handling, I can add: source: log.source ?? 'unknown'

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • backend/package-lock.json: Language not supported

- Import useAuthStore in logger-client.ts
- Add getUserId() method to collect current user ID
- Include userId in LogEntry interface and submission
- Remove unnecessary window.__authStore exposure
@test3207
Copy link
Owner Author

Addressed all 6 Copilot review issues in commit 86c5a36 + upcoming commit:

  1. persistence.ts lines 74, 108, 148: Copilot suggested moving errors to 3rd parameter, but logger.warn() doesn't have an err parameter (only logger.error() does). Kept errors in raw field with String() conversion, which is the correct pattern for warn().

  2. backend logs.ts line 100: Fixed source field to use conditional spread (...(source && { source })) for consistency.

  3. backend logs.ts line 27: Fixed raw field schema to accept both object and string (z.union([z.record(...), z.string()])) since frontend sends JSON.stringify(options.raw).

  4. cache-sync-service.ts line 163: Replaced double type assertion with { raw: stats } for proper type safety.

- backend: use conditional spread for optional source field
- backend: allow raw field to accept string or object in schema
- frontend: keep warn() errors in raw field (warn has no err param)
- frontend: replace double type assertion with raw field in cache-sync-service
Copilot AI review requested due to automatic review settings December 24, 2025 04:00
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • backend/package-lock.json: Language not supported

- Change PlaylistDetailPage error → warn (store handles, no throw)
- Remove all meaningless 'traceId: undefined' (18 occurrences)
- traceId is optional, no need to explicitly pass undefined
@test3207
Copy link
Owner Author

Copilot Review Response:

Issue 1 (logs.ts auth/rate limiting): Rate limiting should be implemented at infrastructure layer (K8s Gateway). Will add runtime env vars to control log collection (disabled by default). Tracked in separate issue for Epic 3.6.

Issue 2 (playlistStore.ts line 317): Intentional. This is a business logic error (Favorites not found), not a caught exception. Creating Error inline provides stack trace for debugging. This is acceptable practice.

Issues 3 & 4 (PlaylistDetailPage undefined params): Fixed in commit 7305538. Changed to logger.warn() since store layer handles errors without throwing. Also removed all 18 occurrences of meaningless 'traceId: undefined' across codebase.

Issue 5 (privacy concerns): Valid for multi-region deployment. Will add user consent checkbox during login flow. Created issue to track implementation.

@test3207
Copy link
Owner Author

Re: PlaylistDetailPage.tsx line 244 (undefined params)

Fixed in commit 7305538. Changed to logger.warn() since store layer handles errors without throwing. Also removed all 18 occurrences of meaningless 'traceId: undefined' across codebase.

@test3207
Copy link
Owner Author

Re: logger-client.ts line 75 (privacy concerns)

Valid concern for multi-region deployment. Created issue #266 to track implementation:

  • Add user consent checkbox during login
  • Store consent preference in user profile
  • Frontend/backend respect consent flag
  • Document telemetry data in privacy policy
  • Provide opt-out in Settings

For self-hosted deployments, users control their data. But we should still provide transparency and choice.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • backend/package-lock.json: Language not supported

- Fix raw field usage in 4 locations (player, events, StorageManager, cache-sw-bridge)
- Change ENABLE_REMOTE_LOGGING from compile-time to runtime injection via window.__ENABLE_REMOTE_LOGGING__
- Remove unnecessary user-facing UI controls (logging should not be user-controlled)
- Add proper Error object wrapping for string errors
- Add type guards for void/primitive payload in EventBus
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 64 out of 65 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • backend/package-lock.json: Language not supported

- Backend: Add ENABLE_REMOTE_LOGGING env var in logger.ts (reserved for future use)
- Frontend: Support two-layer config: runtime injection (prod) + env var (dev)
- Update .env.example files with clear documentation
- Enable convenient local testing without modifying runtime injection code
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 66 out of 67 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • backend/package-lock.json: Language not supported

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 66 out of 67 changed files in this pull request and generated 7 comments.

Files not reviewed (1)
  • backend/package-lock.json: Language not supported

- logger.warn doesn't have error parameter, only logger.error does
- Place error objects in raw field without String() wrapping to preserve error information
- Fixes 3 logger.warn calls in persistence.ts
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 66 out of 67 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • backend/package-lock.json: Language not supported

@github-actions
Copy link
Contributor

Coverage after merging feature/epic-3.5-observability-logging into main will be

60.62%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
100%100%100%100%
src/lib
   events.ts100%100%100%100%
   logger-client.ts40.67%33.33%48.39%46.49%129, 136–138, 138, 138–139, 145, 145, 145–146, 146, 146–147, 149, 171, 176, 182–183, 185, 190, 190, 190, 192, 192, 192, 194, 196, 196, 196–199, 199, 199, 205, 205, 205–207, 209, 221, 224–225, 225, 225–226, 228, 228, 228, 228–229, 229, 229–230, 234, 236, 242, 242, 246, 246–247, 247–248, 248–249, 249–250, 250–251, 251, 258–259, 259, 259–260, 265, 265, 265, 267–269, 275, 275, 275–276, 279, 298–299, 306–307, 310–311, 316, 318–319, 324, 326–327, 346, 346, 346–349, 353, 361, 361, 361, 365, 372, 375–376, 376, 376, 382–383, 383, 383, 389–390, 390, 390, 395, 395, 395, 397–398, 401, 401, 404, 407–408, 41, 411, 411, 46, 46–47, 47, 47, 51, 55
   shared.ts100%100%100%100%
src/lib/api
   config.ts75%62.50%100%85.71%17, 22, 22–23
   multi-region.ts2.21%0%0%3.81%103–104, 104, 104, 106, 109–110, 110, 110, 114, 116–119, 121–122, 126, 126, 126, 126, 126–127, 129–130, 130, 130, 132–133, 143, 145–146, 148–150, 154, 156, 156, 156, 158–159, 161, 172, 172, 172–173, 176, 179, 179, 179–180, 182–183, 187, 189, 189, 189–191, 191, 191–194, 196, 203, 203, 203, 203, 203–205, 208, 208, 208–209, 211–212, 216–218, 220, 220, 220–223, 229–230, 233, 240, 240, 240, 244, 244, 244–245, 247, 255, 266, 266, 266, 269, 269, 269–271, 275, 277, 279–281, 283, 283, 283, 283, 283–285, 289–290, 292, 296, 304–305, 50, 50, 50, 52, 56, 56, 56–57, 57, 57–59, 61, 65, 65, 65, 68, 68, 68, 68, 68, 70, 77, 85, 85, 85, 87–89, 89, 89, 91–92, 92, 92, 94
src/lib/audio
   media-session.ts74.84%90.91%71.43%65.12%107–108, 126, 137–139, 146–148, 150, 157–159, 161, 169–171, 173, 180–182, 184, 200, 214, 228, 241, 249–250, 263, 44–45, 78, 88–89, 95
   queue.ts86.15%82.54%92%86.92%210–211, 223, 225, 225, 225–226, 228, 249–252, 303, 313, 313, 313–314, 316, 37–38, 54–55, 72, 76–77
src/lib/cache
   metadata-cache.ts1%0%0%1.47%101–103, 106, 106, 108, 108, 123–124, 127–129, 131, 131, 131–133, 136–137, 139–140, 153–154, 157, 160, 163, 170–171, 173–174, 183–186, 188–189, 201–204, 204, 204–205, 207, 207, 207–209, 212, 214–215, 24–25, 25, 25, 32, 40–42, 44–45, 54–56, 58–59, 68–70, 72–73, 82–84, 86–87, 97–99
   response-cache.ts0%0%0%0%100, 32–33, 33, 33–34, 38–39, 39, 39, 41, 44, 46–47, 50, 50, 50, 50, 50–51, 54, 57, 57, 57, 57, 59, 59, 59–61, 61, 61–62, 64, 67, 67, 67–69, 69, 69–70, 72, 75, 75, 75–77, 77, 77, 79, 79, 79, 79, 79–81, 83–84, 84, 84, 86, 89, 92, 97
src/lib/offline-proxy/routes
   libraries.ts42.23%24.32%57.14%51.69%127–128, 213, 253, 266–268, 271, 273, 273, 273–274, 283, 283, 283, 283, 283–284, 293–294, 296, 296, 296–297, 301, 304–305, 308, 310, 310, 310, 312, 316, 316–317, 317, 321, 325, 328, 331, 331, 331–332, 343, 346, 349, 349, 349, 349, 349–350, 352, 357, 361, 364, 366, 369, 369–370, 370–371, 371–372, 372–373, 373–374, 374, 374, 374–375, 375–376, 376, 378, 378, 378, 378, 381, 381, 393–394, 397, 402, 402, 402–404, 404, 410, 420–421, 427–430, 430, 430, 432, 434, 434, 434, 434, 434–435, 445, 448, 450, 455, 66, 95, 95–96
   player.ts47.26%35%57.14%55.70%102, 138, 150–151, 154, 159, 161, 165, 167, 167, 167–169, 171, 171, 171–172, 180, 180–181, 181, 195, 200–201,

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 66 out of 67 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • backend/package-lock.json: Language not supported

@test3207 test3207 merged commit c4d9bb0 into main Dec 24, 2025
10 checks passed
@test3207 test3207 deleted the feature/epic-3.5-observability-logging branch December 24, 2025 12:47
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.

Frontend error logging SDK

1 participant