From b4d8b9ad37baa2418ca4724acae5620dc3f94dfe Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Fri, 16 Jan 2026 21:16:47 +0000 Subject: [PATCH] refactor: Add type label for prometheus metrics --- src/backend/ioc.ts | 20 ++++--------------- .../scrobblers/AbstractScrobbleClient.ts | 16 +++++++++------ src/backend/server/api.ts | 8 ++++---- src/backend/sources/AbstractSource.ts | 6 +++++- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/backend/ioc.ts b/src/backend/ioc.ts index 3105246d..4dc24c0c 100644 --- a/src/backend/ioc.ts +++ b/src/backend/ioc.ts @@ -38,34 +38,22 @@ export interface RootOptions { const discovered = new prom.Counter({ name: 'multiscrobbler_source_discovered', help: 'Number of discovered plays for a Source', - labelNames: ['name'] + labelNames: ['name', 'type'] }); - -// const sourceIssues = new prom.Gauge({ -// name: 'multiscrobbler_source_issues', -// help: 'Number of errors/issues with Source', -// labelNames: ['name'] -// }); - const queuedGauge = new prom.Gauge({ name: 'multiscrobbler_client_queued', help: 'Number of queued plays for a Client', - labelNames: ['name'] + labelNames: ['name', 'type'] }); const deadLetterGauge = new prom.Gauge({ name: 'multiscrobbler_client_deadletter', help: 'Number of deadletter plays for a Client', - labelNames: ['name'] + labelNames: ['name', 'type'] }); -// const issuesClientGauge = new prom.Gauge({ -// name: 'multiscrobbler_client_issues', -// help: 'Number of errors/issues with Client', -// labelNames: ['name'] -// }); const scrobbledCounter = new prom.Counter({ name: 'multiscrobbler_client_scrobbled', help: 'Number of discovered plays for a Source', - labelNames: ['name'] + labelNames: ['name', 'type'] }); const createRoot = (options: RootOptions = {logger: loggerDebug}) => { diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index 5182f385..9a697961 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -197,6 +197,10 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i return `${this.type}-${normalizeStr(this.name, {keepSingleWhitespace: false})}`; } + protected getPrometheusLabels() { + return {name: this.getSafeExternalName(), type: this.type}; + } + public notify = async (payload: WebhookPayload) => { this.emitEvent('notify', payload); } @@ -475,7 +479,7 @@ export default abstract class AbstractScrobbleClient extends AbstractComponent i addScrobbledTrack = (playObj: PlayObject, scrobbledPlay: PlayObject) => { this.scrobbledPlayObjs.add({play: playObj, scrobble: scrobbledPlay}); - this.scrobbledCounter.labels({name: this.getSafeExternalId()}).inc(); + this.scrobbledCounter.labels(this.getPrometheusLabels()).inc(); this.lastScrobbledPlayDate = playObj.data.playDate; this.tracksScrobbled++; } @@ -810,7 +814,7 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); this.logger.debug(`Will not scrobble ${buildTrackString(currQueuedPlay.play)} from Source '${currQueuedPlay.source}' because it ${timeFrameValidLog}`); } this.updateQueuedScrobblesCache(); - this.queuedGauge.labels({name: this.getSafeExternalId()}).set(this.queuedScrobbles.length); + this.queuedGauge.labels(this.getPrometheusLabels()).set(this.queuedScrobbles.length); this.emitEvent('scrobbleDequeued', {queuedScrobble: currQueuedPlay}) } await sleep(this.scrobbleSleep); @@ -909,14 +913,14 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); } this.logger.info(`Removed scrobble ${buildTrackString(this.deadLetterScrobbles[index].play)} from queue`, {leaf: 'Dead Letter'}); this.deadLetterScrobbles.splice(index, 1); - this.deadLetterGauge.labels({name: this.getSafeExternalId()}).set(this.deadLetterScrobbles.length); + this.deadLetterGauge.labels(this.getPrometheusLabels()).set(this.deadLetterScrobbles.length); this.updateDeadLetterCache(); } removeDeadLetterScrobbles = () => { this.deadLetterScrobbles = []; this.updateDeadLetterCache(); - this.deadLetterGauge.labels({name: this.getSafeExternalId()}).set(this.deadLetterScrobbles.length); + this.deadLetterGauge.labels(this.getPrometheusLabels()).set(this.deadLetterScrobbles.length); this.logger.info('Removed all scrobbles from queue', {leaf: 'Dead Letter'}); } @@ -934,7 +938,7 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); const queuedPlay = {id: nanoid(), source, play: play} this.emitEvent('scrobbleQueued', {queuedPlay: queuedPlay}); this.queuedScrobbles.push(queuedPlay); - this.queuedGauge.labels({name: this.getSafeExternalId()}).inc(); + this.queuedGauge.labels(this.getPrometheusLabels()).inc(); // this is wasteful but we don't want the processing loop popping out-of-order (by date) scrobbles this.queuedScrobbles.sort((a, b) => sortByOldestPlayDate(a.play, b.play)); } @@ -952,7 +956,7 @@ ${closestMatch.breakdowns.join('\n')}`, {leaf: ['Dupe Check']}); this.deadLetterScrobbles.push(deadData); this.deadLetterScrobbles.sort((a, b) => sortByOldestPlayDate(a.play, b.play)); this.emitEvent('deadLetter', {dead: deadData}); - this.deadLetterGauge.labels({name: this.getSafeExternalId()}).set(this.deadLetterScrobbles.length); + this.deadLetterGauge.labels(this.getPrometheusLabels()).set(this.deadLetterScrobbles.length); this.updateDeadLetterCache(); } diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index 40b4abc1..3498445e 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -514,14 +514,14 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: const issuesClientGauge = new prom.Gauge({ name: 'multiscrobbler_client_issues', help: 'Number of errors/issues with Client', - labelNames: ['name'], + labelNames: ['name', 'type'], async collect() { for(const client of scrobbleClients.clients) { let issues = 0; if(!(await client.isReady())) { issues++; } - this.labels({name: client.getSafeExternalId()}).set(issues); + this.labels({name: client.getSafeExternalName(), type: client.type}).set(issues); } } }); @@ -529,7 +529,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: const sourceIssues = new prom.Gauge({ name: 'multiscrobbler_source_issues', help: 'Number of errors/issues with Source', - labelNames: ['name'], + labelNames: ['name', 'type'], async collect() { for(const source of scrobbleSources.sources) { let issues = 0; @@ -539,7 +539,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: if(source.canPoll && !source.polling) { issues++; } - this.labels({name: source.getSafeExternalId()}).set(issues); + this.labels({name: source.getSafeExternalName(), type: source.type}).set(issues); } } }); diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index 33a95f05..81627e0e 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -125,6 +125,10 @@ export default abstract class AbstractSource extends AbstractComponent implement return `${this.type}-${normalizeStr(this.name, {keepSingleWhitespace: false})}`; } + protected getPrometheusLabels() { + return {name: this.getSafeExternalName(), type: this.type}; + } + getSystemListeningBehavior = (): boolean | undefined => { if(this.supportsManualListening) { return this.config.options !== undefined && 'systemScrobble' in this.config.options ? this.config.options?.systemScrobble : undefined; @@ -155,7 +159,7 @@ export default abstract class AbstractSource extends AbstractComponent implement this.tracksDiscovered++; this.logger.info(`Discovered => ${buildTrackString(play)}`); this.emitEvent('discovered', {play}); - this.discoveredCounter.labels({name: this.getSafeExternalId()}).inc(); + this.discoveredCounter.labels(this.getPrometheusLabels()).inc(); } getFlatRecentlyDiscoveredPlays = (): PlayObject[] => -- 2.51.2