musora-content-services 2.136.2 → 2.136.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/node.js.yml +2 -2
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/services/sanity.js +10 -3
- package/src/services/sync/repositories/practices.ts +30 -2
- package/src/services/sync/store/index.ts +1 -1
- package/src/services/sync/telemetry/index.ts +69 -55
- package/src/services/sync/telemetry/sampling.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [2.136.4](https://github.com/railroadmedia/musora-content-services/compare/v2.136.3...v2.136.4) (2026-02-20)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* better Sentry logging ([#832](https://github.com/railroadmedia/musora-content-services/issues/832)) ([73bd384](https://github.com/railroadmedia/musora-content-services/commit/73bd38473acb631a9f30d559e403e8f4203bb548))
|
|
11
|
+
|
|
12
|
+
### [2.136.3](https://github.com/railroadmedia/musora-content-services/compare/v2.136.2...v2.136.3) (2026-02-20)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* change query ([#831](https://github.com/railroadmedia/musora-content-services/issues/831)) ([8e7d2a2](https://github.com/railroadmedia/musora-content-services/commit/8e7d2a20cecc6bc134664e72b0c30cc067ba0591))
|
|
18
|
+
|
|
5
19
|
### [2.136.2](https://github.com/railroadmedia/musora-content-services/compare/v2.136.1...v2.136.2) (2026-02-19)
|
|
6
20
|
|
|
7
21
|
### [2.136.1](https://github.com/railroadmedia/musora-content-services/compare/v2.136.0...v2.136.1) (2026-02-19)
|
package/package.json
CHANGED
package/src/services/sanity.js
CHANGED
|
@@ -2003,10 +2003,17 @@ export async function fetchScheduledAndNewReleases(
|
|
|
2003
2003
|
const sortOrder = getSortOrder(sort, brand)
|
|
2004
2004
|
|
|
2005
2005
|
const query = `
|
|
2006
|
-
*[
|
|
2007
|
-
|
|
2006
|
+
*[show_in_new_feed == true && (
|
|
2007
|
+
(_type in [${typesString}] && brand == '${brand}' && status in ['published','scheduled'])
|
|
2008
|
+
|| (
|
|
2009
|
+
defined(live_event_start_time)
|
|
2010
|
+
&& (!defined(live_event_end_time) || live_event_end_time >= '${now}' )
|
|
2011
|
+
&& brand == '${brand}'
|
|
2012
|
+
&& status in ['scheduled']
|
|
2013
|
+
)
|
|
2014
|
+
)]
|
|
2008
2015
|
[${start}...${end}]
|
|
2009
|
-
|
|
2016
|
+
| order(${sortOrder}) {
|
|
2010
2017
|
"id": railcontent_id,
|
|
2011
2018
|
title,
|
|
2012
2019
|
"image": thumbnail.asset->url,
|
|
@@ -19,14 +19,42 @@ export default class PracticesRepository extends SyncRepository<Practice> {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async trackAutoPractice(contentId: number, date: string, incrementalDurationSeconds: number, skipPush = true) {
|
|
22
|
-
|
|
22
|
+
this.store.telemetry.log('trackUserPractice:before', {
|
|
23
|
+
now: performance.now(),
|
|
24
|
+
contentId,
|
|
25
|
+
date,
|
|
26
|
+
incrementalDurationSeconds
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const result = await this.upsertOne(Practice.generateAutoId(contentId, date), r => {
|
|
30
|
+
const oldDurationSeconds = r.duration_seconds;
|
|
31
|
+
|
|
23
32
|
r._raw.id = Practice.generateAutoId(contentId, date);
|
|
24
33
|
r.auto = true;
|
|
25
34
|
r.content_id = contentId;
|
|
26
35
|
r.date = date;
|
|
27
36
|
|
|
28
37
|
r.duration_seconds = Math.min((r.duration_seconds || 0) + incrementalDurationSeconds, 59999);
|
|
38
|
+
|
|
39
|
+
this.store.telemetry.log('trackUserPractice:around', {
|
|
40
|
+
now: performance.now(),
|
|
41
|
+
contentId,
|
|
42
|
+
date,
|
|
43
|
+
incrementalDurationSeconds,
|
|
44
|
+
before: oldDurationSeconds,
|
|
45
|
+
after: r.duration_seconds
|
|
46
|
+
})
|
|
29
47
|
}, { skipPush })
|
|
48
|
+
|
|
49
|
+
this.store.telemetry.log('trackUserPractice:after', {
|
|
50
|
+
now: performance.now(),
|
|
51
|
+
contentId,
|
|
52
|
+
date,
|
|
53
|
+
incrementalDurationSeconds,
|
|
54
|
+
after: result.data.duration_seconds
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return result
|
|
30
58
|
}
|
|
31
59
|
|
|
32
60
|
async recordManualPractice(date: string, durationSeconds: number, details: Partial<Pick<Practice, 'title' | 'instrument_id' | 'category_id' | 'thumbnail_url'>> = {}) {
|
|
@@ -49,7 +77,7 @@ export default class PracticesRepository extends SyncRepository<Practice> {
|
|
|
49
77
|
|
|
50
78
|
async updateDetails(id: RecordId, details: Partial<Pick<Practice, 'duration_seconds' | 'title' | 'thumbnail_url' | 'category_id' | 'instrument_id'>>) {
|
|
51
79
|
return await this.updateOneId(id, r => {
|
|
52
|
-
r.duration_seconds = Math.min(details.duration_seconds, 59999)
|
|
80
|
+
r.duration_seconds = details.duration_seconds !== null ? Math.min(details.duration_seconds, 59999) : r.duration_seconds;
|
|
53
81
|
r.title = details.title ?? r.title;
|
|
54
82
|
r.thumbnail_url = details.thumbnail_url ?? r.thumbnail_url;
|
|
55
83
|
r.category_id = details.category_id ?? r.category_id;
|
|
@@ -667,7 +667,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
|
|
|
667
667
|
if (failedResults.length) {
|
|
668
668
|
this.telemetry.warn(
|
|
669
669
|
`[store:${this.model.table}] Push completed with failed records`,
|
|
670
|
-
{
|
|
670
|
+
{ extra: { resultsJSON: JSON.stringify(failedResults) } }
|
|
671
671
|
)
|
|
672
672
|
}
|
|
673
673
|
|
|
@@ -10,12 +10,14 @@ export type SentryLike = {
|
|
|
10
10
|
captureMessage: typeof InjectedSentry.captureMessage
|
|
11
11
|
addBreadcrumb: typeof InjectedSentry.addBreadcrumb
|
|
12
12
|
startSpan: typeof InjectedSentry.startSpan
|
|
13
|
+
logger: typeof InjectedSentry.logger
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export type StartSpanOptions = Parameters<typeof InjectedSentry.startSpan>[0]
|
|
16
17
|
export type Span = InjectedSentry.Span
|
|
17
18
|
|
|
18
19
|
export const SYNC_TELEMETRY_TRACE_PREFIX = 'sync:'
|
|
20
|
+
export const SYNC_TELEMETRY_CONSOLE_PREFIX = '📡 SYNC:'
|
|
19
21
|
|
|
20
22
|
export enum SeverityLevel {
|
|
21
23
|
DEBUG = 0,
|
|
@@ -35,6 +37,13 @@ const severityLevelToSentryLevel: Record<SeverityLevel, 'fatal' | 'error' | 'war
|
|
|
35
37
|
[SeverityLevel.FATAL]: 'fatal',
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
type CaptureOptions = {
|
|
41
|
+
tags?: Record<string, string>
|
|
42
|
+
contexts?: Record<string, Record<string, unknown>>
|
|
43
|
+
extra?: unknown
|
|
44
|
+
}
|
|
45
|
+
type LogAttributes = Record<string, unknown>
|
|
46
|
+
|
|
38
47
|
export class SyncTelemetry {
|
|
39
48
|
private static instance: SyncTelemetry | null = null
|
|
40
49
|
|
|
@@ -58,9 +67,6 @@ export class SyncTelemetry {
|
|
|
58
67
|
|
|
59
68
|
private ignorePatterns: (string | RegExp)[] = []
|
|
60
69
|
|
|
61
|
-
// allows us to know if Sentry shouldn't double-capture a dev-prettified console.error log
|
|
62
|
-
private _ignoreConsole = false
|
|
63
|
-
|
|
64
70
|
constructor(
|
|
65
71
|
userScope: SyncUserScope,
|
|
66
72
|
{
|
|
@@ -81,7 +87,7 @@ export class SyncTelemetry {
|
|
|
81
87
|
this.level = typeof normalizedLevel === 'number' ? normalizedLevel : SeverityLevel.LOG
|
|
82
88
|
this.pretty = typeof pretty !== 'undefined' ? pretty : true
|
|
83
89
|
|
|
84
|
-
watermelonLogger.log = (message: unknown) => this.log(message instanceof Error ? message : ['[Watermelon]', message].join(' '))
|
|
90
|
+
watermelonLogger.log = (message: unknown) => this.log(message instanceof Error ? message.message : ['[Watermelon]', message].join(' '))
|
|
85
91
|
watermelonLogger.warn = (message: unknown) => this.warn(message instanceof Error ? message : ['[Watermelon]', message].join(' '))
|
|
86
92
|
watermelonLogger.error = (message: unknown) => this.error(message instanceof Error ? message : ['[Watermelon]', message].join(' '))
|
|
87
93
|
}
|
|
@@ -100,9 +106,9 @@ export class SyncTelemetry {
|
|
|
100
106
|
let desc = span['_spanId'].slice(0, 4)
|
|
101
107
|
desc += span['_parentSpanId'] ? ` (< ${span['_parentSpanId'].slice(0, 4)})` : ''
|
|
102
108
|
|
|
103
|
-
this.
|
|
109
|
+
this.consoleLog(SeverityLevel.DEBUG, 'info', `[trace:start] ${options.name} (${desc})`)
|
|
104
110
|
const result = callback(span)
|
|
105
|
-
Promise.resolve(result).finally(() => this.
|
|
111
|
+
Promise.resolve(result).finally(() => this.consoleLog(SeverityLevel.DEBUG, 'info', `[trace:end] ${options.name} (${desc})`))
|
|
106
112
|
|
|
107
113
|
return result
|
|
108
114
|
})
|
|
@@ -124,88 +130,79 @@ export class SyncTelemetry {
|
|
|
124
130
|
: undefined
|
|
125
131
|
)
|
|
126
132
|
|
|
127
|
-
this.
|
|
128
|
-
this.error(err instanceof Error ? err.message : String(err))
|
|
129
|
-
this._ignoreConsole = false
|
|
133
|
+
this.consoleLog(SeverityLevel.ERROR, 'error', err)
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return this._ignoreConsole
|
|
136
|
+
debug(message: string, attrs?: LogAttributes) {
|
|
137
|
+
this.logMessage(SeverityLevel.DEBUG, 'info', message, attrs)
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
*/
|
|
140
|
-
ignoreLike(...patterns: (RegExp | string)[]) {
|
|
141
|
-
this.ignorePatterns.push(...patterns)
|
|
140
|
+
info(message: string, attrs?: LogAttributes) {
|
|
141
|
+
this.logMessage(SeverityLevel.INFO, 'info', message, attrs)
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return this.shouldIgnoreMessage(message)
|
|
144
|
+
log(message: string, attrs?: LogAttributes) {
|
|
145
|
+
this.logMessage(SeverityLevel.LOG, 'log', message, attrs)
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return this.shouldIgnoreMessage(exception.message)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return false
|
|
148
|
+
warn(message: unknown, opts?: CaptureOptions) {
|
|
149
|
+
this.captureError(SeverityLevel.WARNING, 'warn', message, opts)
|
|
155
150
|
}
|
|
156
151
|
|
|
157
|
-
|
|
158
|
-
this.
|
|
152
|
+
error(message: unknown, opts?: CaptureOptions) {
|
|
153
|
+
this.captureError(SeverityLevel.ERROR, 'error', message, opts)
|
|
159
154
|
}
|
|
160
155
|
|
|
161
|
-
|
|
162
|
-
this.
|
|
156
|
+
fatal(message: unknown, opts?: CaptureOptions) {
|
|
157
|
+
this.captureError(SeverityLevel.FATAL, 'error', message, opts)
|
|
163
158
|
}
|
|
164
159
|
|
|
165
|
-
log
|
|
166
|
-
this.
|
|
167
|
-
|
|
160
|
+
private logMessage(level: SeverityLevel, consoleMethod: 'info' | 'log', message: string, attrs?: LogAttributes) {
|
|
161
|
+
if (this.level > level) return
|
|
162
|
+
if (this.shouldIgnoreMessage(message)) return
|
|
168
163
|
|
|
169
|
-
|
|
170
|
-
|
|
164
|
+
console[consoleMethod](...this.consoleFormattedMessage(message, attrs))
|
|
165
|
+
const sentryLevel = severityLevelToSentryLevel[level]
|
|
166
|
+
const sentryLogMethod = sentryLevel === 'debug' ? 'debug' : 'info'
|
|
167
|
+
this.Sentry.addBreadcrumb({
|
|
168
|
+
message,
|
|
169
|
+
data: attrs,
|
|
170
|
+
level: sentryLevel,
|
|
171
|
+
})
|
|
172
|
+
this.Sentry.logger[sentryLogMethod](message, attrs)
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
|
|
175
|
+
private captureError(level: SeverityLevel, consoleMethod: 'warn' | 'error', error: unknown, opts: CaptureOptions) {
|
|
176
|
+
if (this.level > level) return
|
|
177
|
+
if (error instanceof Error && this.shouldIgnoreMessage(error.message)) return
|
|
178
|
+
if (typeof error === 'string' && this.shouldIgnoreMessage(error)) return
|
|
176
179
|
|
|
177
|
-
|
|
178
|
-
this.
|
|
180
|
+
console[consoleMethod](...this.consoleFormattedMessage(error, opts))
|
|
181
|
+
this.Sentry.captureException(error, { level: severityLevelToSentryLevel[level] })
|
|
179
182
|
}
|
|
180
183
|
|
|
181
|
-
|
|
184
|
+
private consoleLog(level: SeverityLevel, consoleMethod: 'info' | 'log' | 'warn' | 'error', message: unknown, extra?: any) {
|
|
182
185
|
if (this.level > level || this.shouldIgnoreMessage(message)) return
|
|
183
|
-
this.
|
|
184
|
-
|
|
185
|
-
this._ignoreConsole = false
|
|
186
|
-
|
|
187
|
-
if (skipSentry) return;
|
|
186
|
+
console[consoleMethod](...this.consoleFormattedMessage(message, extra))
|
|
187
|
+
}
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
} else {
|
|
192
|
-
this.Sentry.addBreadcrumb({ message: message instanceof Error ? message.message : String(message), level: severityLevelToSentryLevel[level] })
|
|
193
|
-
}
|
|
189
|
+
static isSyncConsoleMessage(args: unknown[]): boolean {
|
|
190
|
+
return typeof args[0] === 'string' && args[0].startsWith(SYNC_TELEMETRY_CONSOLE_PREFIX)
|
|
194
191
|
}
|
|
195
192
|
|
|
196
|
-
private
|
|
193
|
+
private consoleFormattedMessage(message: unknown, remnant: CaptureOptions | LogAttributes) {
|
|
197
194
|
if (!this.pretty) {
|
|
198
|
-
return [message, ...(
|
|
195
|
+
return [message, ...(remnant ? [remnant] : [])]
|
|
199
196
|
}
|
|
200
197
|
|
|
201
198
|
const date = new Date()
|
|
202
|
-
return [...this.consolePrefix(date), message, ...(
|
|
199
|
+
return [...this.consolePrefix(date), message, ...(remnant ? [remnant] : []), ...this.consoleSuffix(date)]
|
|
203
200
|
}
|
|
204
201
|
|
|
205
202
|
private consolePrefix(date: Date) {
|
|
206
203
|
const now = Math.round(date.getTime() / 1000).toString()
|
|
207
204
|
return [
|
|
208
|
-
|
|
205
|
+
`${SYNC_TELEMETRY_CONSOLE_PREFIX} (%c${now.slice(0, 5)}%c${now.slice(5, 10)})`,
|
|
209
206
|
'color: #ccc',
|
|
210
207
|
'font-weight: bold;',
|
|
211
208
|
]
|
|
@@ -215,6 +212,23 @@ export class SyncTelemetry {
|
|
|
215
212
|
return [` [${date.toLocaleTimeString()}, ${date.getTime()}]`]
|
|
216
213
|
}
|
|
217
214
|
|
|
215
|
+
ignoreLike(...patterns: (RegExp | string)[]) {
|
|
216
|
+
this.ignorePatterns.push(...patterns)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
shouldIgnoreRejection(reason: any) {
|
|
220
|
+
const message = reason instanceof Error ? `${reason.name}: ${reason.message}` : reason
|
|
221
|
+
return this.shouldIgnoreMessage(message)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
shouldIgnoreException(exception: unknown) {
|
|
225
|
+
if (exception instanceof Error) {
|
|
226
|
+
return this.shouldIgnoreMessage(exception.message)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
|
|
218
232
|
private shouldIgnoreMessage(message: any) {
|
|
219
233
|
if (message instanceof Error) message = message.message
|
|
220
234
|
if (typeof message !== 'string') return false
|
|
@@ -6,7 +6,8 @@ type BeforeSendResult<T> = T | PromiseLike<T | null> | null | undefined
|
|
|
6
6
|
type SamplerResult = number | boolean | undefined
|
|
7
7
|
|
|
8
8
|
export const syncSentryBeforeSend = (event: ErrorEvent, hint: EventHint): BeforeSendResult<ErrorEvent> => {
|
|
9
|
-
|
|
9
|
+
const consoleArgs = event.extra?.arguments as unknown[] | undefined
|
|
10
|
+
if (event.logger === 'console' && consoleArgs && SyncTelemetry.isSyncConsoleMessage(consoleArgs)) {
|
|
10
11
|
return null
|
|
11
12
|
}
|
|
12
13
|
|