Skip to content

feat: add some sentry spans#83

Merged
roitium merged 3 commits into
devfrom
feat/add-sentry-transactions
Nov 9, 2025
Merged

feat: add some sentry spans#83
roitium merged 3 commits into
devfrom
feat/add-sentry-transactions

Conversation

@roitium
Copy link
Copy Markdown
Collaborator

@roitium roitium commented Nov 9, 2025

Summary by CodeRabbit

  • Chores
    • Enhanced application monitoring and diagnostics across the app (added runtime tracing/profiling).
    • Adjusted tracing/sample-rate settings to reduce captured trace volume.
    • Minor internal exports to support improved service wiring.
    • Bumped app versionCode for the next release.

@roitium roitium added the enhancement New feature or request label Nov 9, 2025
@roitium roitium added this to BBPlayer Nov 9, 2025
@safedep
Copy link
Copy Markdown

safedep Bot commented Nov 9, 2025

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

Package Details
Package Malware Vulnerability Risky License Report
icon @types/he @ ^1.2.3
package.json
ok icon
ok icon
ok icon
🔗
icon @types/he @ 1.2.3
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗

This report is generated by SafeDep Github App

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 9, 2025

Walkthrough

Adds Sentry instrumentation across the app: wraps React Lyrics component with Sentry.withProfiler and inlines its fetch/sync logic; and instruments many facade, service, and store async flows with Sentry.startSpan. Also lowers Sentry tracing sample rate and adds a couple of ArtistService methods and a trackService export.

Changes

Cohort / File(s) Summary
Sentry config
src/lib/config/sentry.ts
Reduced tracing sample rate from 0.7 to 0.3.
Player UI
src/features/player/components/PlayerLyrics.tsx
Wrapped memoized Lyrics with Sentry.withProfiler; inlined lyrics data-fetching (smartFetchLyrics, LyricSync) and consolidated render flow; preserved pending/error/lyrics UI and offset controls.
Player store
src/hooks/stores/usePlayerStore.ts
Added Sentry.startSpan tracing around major actions (e.g., _finalizeAndRecordCurrentPlay, togglePlay, skipToTrack, addToQueue, removeTrack, seekTo, resetStore, patchAudio) and attached span attributes.
Facades
src/lib/facades/bilibili.ts, src/lib/facades/playlist.ts, src/lib/facades/sync.ts
Wrapped key facade methods with Sentry.startSpan (fetchRemotePlaylistMetadata, duplicatePlaylist, updateTrackLocalPlaylists, batchAddTracksToLocalPlaylist, sync methods) and added span attributes for inputs/metrics.
Services — artist
src/lib/services/artistService.ts
Instrumented methods with nested spans; added findOrCreateArtist and findOrCreateManyRemoteArtists public methods (with validation and bulk logic).
Services — download & lyrics
src/lib/services/downloadService.ts, src/lib/services/lyricService.ts
Wrapped download and lyric flows (getDownloadUrl, start, delete, getBestMatchedLyrics, smartFetchLyrics, saveLyricsToFile, fetchLyrics, migrateFromOldFormat, clearAllLyrics, etc.) in Sentry.startSpan with nested IO/DB/http spans.
Services — playlist & track
src/lib/services/playlistService.ts, src/lib/services/trackService.ts
Instrumented PlaylistService and TrackService methods with spans for function and DB operations; added/exported trackService instance from TrackService module.
Services — update
src/lib/services/updateService.ts
Wrapped fetchLatestRelease and checkForAppUpdate with spans and recorded manifest/HTTP/version attributes.
Package manifest
package.json
Bumped versionCode 75→76 and added @types/he to dependencies/devDependencies.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant UI as Lyrics Component (React)
    participant SentryUI as Sentry.withProfiler
    participant Store as Player Store / Services
    participant Sentry as Sentry.startSpan
    participant DB as DB/IO/HTTP

    UI->>SentryUI: render Lyrics (memoized)
    SentryUI->>UI: execute component body
    UI->>Store: call smartFetchLyrics / LyricSync
    Store->>Sentry: startSpan("LyricService.smartFetchLyrics")
    Sentry->>DB: read cache / HTTP fetch / parse
    DB-->>Sentry: result
    Sentry-->>Store: return lyrics
    alt has lyrics
        UI->>UI: render lyrics list / offset control
    else pending/error
        UI->>UI: render pending or error view
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Wide surface: many files across services, facades, stores, and UI with heterogeneous changes.
  • Look closely at: span lifecycles and error propagation (try/catch placement), preservation of original return semantics, new ArtistService methods, and the added trackService export.

Possibly related PRs

  • feat: performance improve #69 — modifies PlayerLyrics.tsx rendering/refactor; overlaps with this PR’s Lyrics component restructuring.
  • feat: lyrics style #70 — refactors the same Lyrics component (wrapping/memoization changes); likely touches the same code paths.
  • feat: ToView as a playlist #71 — changes player store functions (_finalizeAndRecordCurrentPlay, skipToTrack); strongly related to store instrumentation here.

Poem

🐰 I hopped through spans both wide and small,

Wrapped lyrics, traces—I saw them all.
From fetch to DB and render light,
I traced each hop through day and night.
A rabbit’s hop, now logged just right. 🎵

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main objective of the PR: adding Sentry spans for observability across multiple service and component files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-sentry-transactions

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 951f253 and 1dcd51d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • package.json (2 hunks)
🔇 Additional comments (2)
package.json (2)

5-5: Version code bump is routine.

The versionCode increment from 75 to 76 is standard practice for releases.


126-126: The review comment is incorrect—he package is actively used in the codebase.

The he package is imported (import { decode } from 'he') in src/app/playlist/remote/search-result/global/[query].tsx, confirming that adding @types/he to devDependencies is necessary and appropriate. This is not an orphaned dependency.

Likely an incorrect or invalid review comment.


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.

@roitium roitium moved this to In review in BBPlayer Nov 9, 2025
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: 5

🧹 Nitpick comments (1)
src/lib/services/artistService.ts (1)

379-479: Excellent bulk operation implementation!

This new method implements the bulk find-or-create pattern correctly using onConflictDoNothing() followed by a query to retrieve all artists. The logic is sound and the instrumentation provides good visibility into batch sizes and found counts.

Consider removing two redundant checks for cleaner code:

  1. Line 410: The check if (payloads.length > 0) is redundant since you've already returned early at line 395 when the array is empty.

  2. Lines 460-464: The check fullArtists.length !== payloads.length will never be true because map() always returns an array of the same length as the input (or throws during iteration, which happens at line 456).

Apply this diff to remove the redundancies:

-			if (payloads.length > 0) {
-				await Sentry.startSpan(
-					{ name: 'db:insert:many:artists', op: 'db' },
-					() =>
-						this.db
-							.insert(schema.artists)
-							.values(
-								payloads.map(
-									(p) =>
-										({
-											name: p.name,
-											source: p.source,
-											remoteId: p.remoteId,
-											avatarUrl: p.avatarUrl,
-											signature: p.signature,
-										}) satisfies CreateArtistPayload,
-								),
-							)
-							.onConflictDoNothing(),
-				)
-			}
+			await Sentry.startSpan(
+				{ name: 'db:insert:many:artists', op: 'db' },
+				() =>
+					this.db
+						.insert(schema.artists)
+						.values(
+							payloads.map(
+								(p) =>
+									({
+										name: p.name,
+										source: p.source,
+										remoteId: p.remoteId,
+										avatarUrl: p.avatarUrl,
+										signature: p.signature,
+									}) satisfies CreateArtistPayload,
+							),
+						)
+						.onConflictDoNothing(),
+			)

 			const findConditions = payloads.map((p) =>
 				and(
 					eq(schema.artists.source, p.source),
 					eq(schema.artists.remoteId, p.remoteId!),
 				),
 			)

 			const allArtists = await Sentry.startSpan(
 				{ name: 'db:query:many:artists', op: 'db' },
 				() =>
 					this.db.query.artists.findMany({
 						where: or(...findConditions),
 					}),
 			)
 			span?.setAttribute('found.count', allArtists.length)

 			const fullArtists = payloads.map((p) => {
 				const existing = allArtists.find(
 					(a) =>
 						`${a.source}::${a.remoteId}` === `${p.source}::${p.remoteId}`,
 				)
 				if (existing) {
 					return existing
 				}
 				throw new DatabaseError(
 					`批量查找或创建 artists 后数据不一致,未找到 artist: ${p.source}::${p.remoteId}`,
 				)
 			})
-			if (fullArtists.length !== payloads.length) {
-				throw new DatabaseError(
-					'创建或查找 artists 后数据不一致,部分 artist 未能成功写入或查询。',
-				)
-			}

 			const finalResultMap = new Map(
 				fullArtists.map((artist) => [artist.remoteId!, artist]),
 			)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb0967b and 951f253.

📒 Files selected for processing (12)
  • src/features/player/components/PlayerLyrics.tsx (2 hunks)
  • src/hooks/stores/usePlayerStore.ts (6 hunks)
  • src/lib/config/sentry.ts (1 hunks)
  • src/lib/facades/bilibili.ts (2 hunks)
  • src/lib/facades/playlist.ts (4 hunks)
  • src/lib/facades/sync.ts (6 hunks)
  • src/lib/services/artistService.ts (9 hunks)
  • src/lib/services/downloadService.ts (4 hunks)
  • src/lib/services/lyricService.ts (3 hunks)
  • src/lib/services/playlistService.ts (18 hunks)
  • src/lib/services/trackService.ts (13 hunks)
  • src/lib/services/updateService.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
src/lib/facades/sync.ts (5)
src/lib/errors/facade.ts (2)
  • createSyncTaskAlreadyRunningError (22-27)
  • createFacadeError (29-39)
src/lib/api/bilibili/utils.ts (2)
  • bv2av (10-25)
  • av2bv (32-51)
src/types/core/media.ts (1)
  • BilibiliTrack (38-54)
src/utils/set.ts (1)
  • diffSets (7-32)
src/lib/services/genKey.ts (1)
  • generateUniqueTrackKey (10-38)
src/lib/services/trackService.ts (6)
src/lib/errors/service.ts (3)
  • createValidationError (70-75)
  • createTrackNotFound (27-32)
  • createNotImplementedError (77-79)
src/lib/services/genKey.ts (1)
  • generateUniqueTrackKey (10-38)
src/types/services/track.ts (3)
  • BilibiliMetadataPayload (1-7)
  • CreateTrackPayloadBase (13-18)
  • CreateBilibiliTrackPayload (20-23)
src/lib/errors/index.ts (1)
  • ServiceError (15-15)
src/types/core/media.ts (2)
  • PlayRecord (12-16)
  • Track (63-63)
src/lib/db/schema.ts (1)
  • bilibiliMetadata (138-155)
src/lib/services/lyricService.ts (5)
src/lib/errors/index.ts (2)
  • FileSystemError (38-38)
  • DataParsingError (37-37)
src/types/player/lyrics.ts (2)
  • ParsedLrc (18-24)
  • LyricSearchResult (26-32)
src/utils/error-handling.ts (1)
  • toastAndLogError (11-41)
src/types/core/media.ts (1)
  • BilibiliTrack (38-54)
src/lib/api/bilibili/api.ts (1)
  • bilibiliApi (824-824)
src/hooks/stores/usePlayerStore.ts (4)
src/utils/log.ts (2)
  • flatErrorMessage (111-129)
  • reportErrorToSentry (138-164)
src/lib/config/queryClient.ts (1)
  • queryClient (9-45)
src/hooks/queries/db/track.ts (1)
  • trackKeys (11-32)
src/utils/error-handling.ts (1)
  • toastAndLogError (11-41)
src/lib/services/artistService.ts (5)
src/types/services/artist.ts (2)
  • CreateArtistPayload (1-7)
  • UpdateArtistPayload (9-13)
src/lib/errors/service.ts (2)
  • createValidationError (70-75)
  • createArtistNotFound (34-42)
src/lib/errors/index.ts (1)
  • ServiceError (15-15)
src/types/core/media.ts (1)
  • Track (63-63)
src/lib/db/schema.ts (1)
  • artists (13-49)
src/features/player/components/PlayerLyrics.tsx (7)
src/types/player/lyrics.ts (1)
  • LyricLine (3-16)
src/hooks/stores/useAppStore.ts (1)
  • useAppStore (44-166)
src/hooks/queries/lyrics/index.ts (2)
  • useSmartFetchLyrics (14-36)
  • lyricsQueryKeys (6-12)
src/features/player/hooks/useLyricSync.ts (1)
  • useLyricSync (9-146)
src/lib/config/queryClient.ts (1)
  • queryClient (9-45)
src/utils/error-handling.ts (1)
  • toastAndLogError (11-41)
src/hooks/stores/useModalStore.ts (1)
  • useModalStore (29-72)
src/lib/services/downloadService.ts (4)
src/lib/errors/service.ts (2)
  • createNotImplementedError (77-79)
  • createServiceError (15-25)
src/hooks/stores/useAppStore.ts (2)
  • useAppStore (44-166)
  • serializeCookieObject (36-42)
src/utils/log.ts (1)
  • flatErrorMessage (111-129)
src/hooks/stores/usePlayerStore.ts (1)
  • usePlayerStore (47-852)
src/lib/facades/playlist.ts (2)
src/lib/errors/service.ts (1)
  • createValidationError (70-75)
src/lib/errors/facade.ts (1)
  • createFacadeError (29-39)
src/lib/services/playlistService.ts (5)
src/types/services/playlist.ts (2)
  • CreatePlaylistPayload (1-8)
  • UpdatePlaylistPayload (10-14)
src/lib/errors/service.ts (3)
  • createPlaylistNotFound (44-53)
  • createTrackNotInPlaylist (55-68)
  • createValidationError (70-75)
src/lib/errors/index.ts (1)
  • ServiceError (15-15)
src/lib/db/schema.ts (1)
  • playlists (86-115)
src/types/core/media.ts (1)
  • Track (63-63)
src/lib/facades/bilibili.ts (2)
src/lib/errors/facade.ts (1)
  • createFacadeError (29-39)
src/lib/api/bilibili/utils.ts (1)
  • av2bv (32-51)
🔇 Additional comments (8)
src/lib/services/artistService.ts (8)

1-1: Excellent instrumentation approach!

The Sentry import and comprehensive tracing pattern throughout this service is well-structured. The consistent use of Sentry.startSpan with meaningful operation names (function, db) and span attributes will provide valuable performance insights.


45-76: Well-instrumented method with clear span hierarchy.

The nested spans (function → db:insert:artist) provide excellent granularity for performance monitoring. The source attribute will be useful for filtering traces.


84-151: New method implementation looks solid!

The find-or-create pattern is correctly implemented with appropriate validation. Excellent use of span.setAttribute('artistAlreadyExists', ...) to track cache hits vs. misses—this will be valuable for performance analysis.


159-212: LGTM!

The instrumentation correctly traces both the existence check and the update operation. The error handling properly distinguishes between ArtistNotFound and database errors.


219-263: LGTM!

Consistent with updateArtist pattern—validates existence before deletion with proper tracing throughout.


270-313: LGTM!

The track.count span attribute will be useful for monitoring query result sizes. Proper error handling for formatting failures.


319-339: LGTM!

Clean implementation with andTee to set the artist.count attribute—nice use of the neverthrow API.


346-371: LGTM!

Straightforward implementation with appropriate tracing.

Comment thread src/lib/services/downloadService.ts
Comment thread src/lib/services/downloadService.ts
Comment thread src/lib/services/downloadService.ts
Comment on lines +132 to +135
Sentry.startSpan({ name: 'io:file:write', op: 'io' }, () =>
lyricFile.write(JSON.stringify(lyrics)),
)
return okAsync(lyrics)
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

Await the span-wrapped lyric writes

Line 132 and Line 141 start Sentry.startSpan calls that wrap lyricFile.write(...), but the returned promise is never awaited nor converted into a ResultAsync. As a result, smartFetchLyrics now reports success even if the write fails, and the rejection is lost outside of the pipeline. Please propagate the promise so IO failures surface again.

@@
-							.andThen((lyrics) => {
-								logger.info('自动搜索最佳匹配的歌词完成')
-								Sentry.startSpan({ name: 'io:file:write', op: 'io' }, () =>
-									lyricFile.write(JSON.stringify(lyrics)),
-								)
-								return okAsync(lyrics)
-							})
+							.andThen((lyrics) => {
+								logger.info('自动搜索最佳匹配的歌词完成')
+								return ResultAsync.fromPromise(
+									Sentry.startSpan(
+										{ name: 'io:file:write', op: 'io' },
+										() => lyricFile.write(JSON.stringify(lyrics)),
+									),
+									(e) =>
+										new FileSystemError('写入歌词缓存失败', {
+											cause: e,
+											data: { filePath: lyricFile.uri },
+										}),
+								).map(() => lyrics)
+							})
@@
-					return this.getBestMatchedLyrics(track).andThen((lyrics) => {
-						logger.info('自动搜索最佳匹配的歌词完成')
-						Sentry.startSpan({ name: 'io:file:write', op: 'io' }, () =>
-							lyricFile.write(JSON.stringify(lyrics)),
-						)
-						return okAsync(lyrics)
-					})
+					return this.getBestMatchedLyrics(track).andThen((lyrics) => {
+						logger.info('自动搜索最佳匹配的歌词完成')
+						return ResultAsync.fromPromise(
+							Sentry.startSpan(
+								{ name: 'io:file:write', op: 'io' },
+								() => lyricFile.write(JSON.stringify(lyrics)),
+							),
+							(e) =>
+								new FileSystemError('写入歌词缓存失败', {
+									cause: e,
+									data: { filePath: lyricFile.uri },
+								}),
+						).map(() => lyrics)
+					})

Also applies to: 140-143

🤖 Prompt for AI Agents
In src/lib/services/lyricService.ts around lines 132-135 (and similarly
140-143), the Sentry.startSpan wrapping lyricFile.write returns a promise that
is not awaited, so write failures are swallowed; modify the code to await the
span-wrapped write (or wrap it in a ResultAsync) and propagate rejections—i.e.,
call await Sentry.startSpan(..., () => lyricFile.write(JSON.stringify(lyrics)))
(or return/chain a ResultAsync from the write) and ensure any thrown error is
returned as an Err/ rejected promise so smartFetchLyrics reflects IO failures.

Comment thread src/lib/services/lyricService.ts
@roitium roitium merged commit 5c3094c into dev Nov 9, 2025
4 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in BBPlayer Nov 9, 2025
@roitium roitium deleted the feat/add-sentry-transactions branch December 14, 2025 07:13
@coderabbitai coderabbitai Bot mentioned this pull request Jan 6, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Feb 13, 2026
@coderabbitai coderabbitai Bot mentioned this pull request May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant