Skip to content

Commit ff8e891

Browse files
DaxServerclaude
andauthored
feat: add consistent uploadId/batchId log prefix on worker path (#114)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 9fc5868 commit ff8e891

6 files changed

Lines changed: 80 additions & 78 deletions

File tree

backend/src/__tests__/uploadClient.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ describe('MediaWikiClient.applySdc', () => {
731731
const apiRequestMock = mock(async () => ({}))
732732
// biome-ignore lint/suspicious/noExplicitAny: overriding private methods for testing
733733
;(client as any).apiRequest = apiRequestMock
734-
await client.applySdc('Photo.jpg', [{ mainsnak: {} }], null, 'summary')
734+
await client.applySdc('Photo.jpg', [{ mainsnak: {} }], null, 'summary', '[1/1]')
735735
expect(apiRequestMock).toHaveBeenCalled()
736736
})
737737

@@ -741,7 +741,9 @@ describe('MediaWikiClient.applySdc', () => {
741741
;(client as any).getCsrfToken = mock(async () => 'token+\\')
742742
// biome-ignore lint/suspicious/noExplicitAny: overriding private methods for testing
743743
;(client as any).apiRequest = mock(async () => ({ error: { info: 'SDC error' } }))
744-
await expect(client.applySdc('Photo.jpg', null, null, 'summary')).rejects.toThrow('SDC error')
744+
await expect(client.applySdc('Photo.jpg', null, null, 'summary', '[1/1]')).rejects.toThrow(
745+
'SDC error',
746+
)
745747
})
746748

747749
it('retries with a fresh token when the wbeditentity request returns badtoken', async () => {
@@ -758,7 +760,7 @@ describe('MediaWikiClient.applySdc', () => {
758760
return {}
759761
})
760762

761-
await client.applySdc('Test.jpg', null, null, 'summary')
763+
await client.applySdc('Test.jpg', null, null, 'summary', '[1/1]')
762764

763765
expect(apiCall).toBe(2)
764766
})
@@ -870,7 +872,7 @@ describe('MediaWikiClient.nullEdit retry', () => {
870872
if (editAttempts <= 2) throw new Error('transient network error')
871873
return {}
872874
})
873-
await expect(client.nullEdit('Photo.jpg')).resolves.toBeUndefined()
875+
await expect(client.nullEdit('Photo.jpg', '[1/1]')).resolves.toBeUndefined()
874876
expect(editAttempts).toBe(3)
875877
} finally {
876878
globalThis.setTimeout = origSetTimeout
@@ -888,7 +890,7 @@ describe('MediaWikiClient.nullEdit retry', () => {
888890
if (params.action === 'query') return makeQueryResponse()
889891
throw new Error('persistent error')
890892
})
891-
await expect(client.nullEdit('Photo.jpg')).rejects.toThrow('persistent error')
893+
await expect(client.nullEdit('Photo.jpg', '[1/1]')).rejects.toThrow('persistent error')
892894
} finally {
893895
globalThis.setTimeout = origSetTimeout
894896
}
@@ -909,7 +911,7 @@ describe('MediaWikiClient.nullEdit retry', () => {
909911
return {}
910912
})
911913

912-
await client.nullEdit('Test.jpg')
914+
await client.nullEdit('Test.jpg', '[1/1]')
913915

914916
expect(apiCall).toBe(2)
915917
})

backend/src/core/logger.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export function elapsed(start: bigint): string {
3232
return formatDuration(Number(process.hrtime.bigint() - start))
3333
}
3434

35+
export function idTag(uploadId: number, batchId: number): string {
36+
return `[${uploadId}/${batchId}]`
37+
}
38+
3539
const requestTimings = new WeakMap<Request, bigint>()
3640

3741
export const elysiaLogger = new Elysia({ name: 'elysia-logger' })

backend/src/handlers/mapillary.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ async function getSequenceIds(sequenceId: string): Promise<string[]> {
108108
return ids
109109
}
110110

111-
async function fetchImagesByIds(imageIds: string[]): Promise<MapillaryImage[]> {
111+
async function fetchImagesByIds(imageIds: string[], tag = ''): Promise<MapillaryImage[]> {
112112
if (imageIds.length === 0) return []
113113

114114
const url = new URL('https://graph.mapillary.com')
@@ -123,7 +123,8 @@ async function fetchImagesByIds(imageIds: string[]): Promise<MapillaryImage[]> {
123123

124124
const data = (await res.json()) as Record<string, MapillaryImage>
125125
const images = Object.values(data)
126-
logger.debug(`[mapillary] fetched ${images.length}/${imageIds.length} images by ids`)
126+
const prefix = tag ? `${tag} ` : ''
127+
logger.debug(`[mapillary] ${prefix}fetched ${images.length}/${imageIds.length} images by ids`)
127128
return images
128129
}
129130

@@ -249,8 +250,8 @@ export class MapillaryHandler {
249250
return getSequenceIds(input)
250251
}
251252

252-
async fetchImagesBatch(imageIds: string[], _input: string): Promise<MediaImage[]> {
253-
const raw = await fetchImagesByIds(imageIds)
253+
async fetchImagesBatch(imageIds: string[], _input: string, tag = ''): Promise<MediaImage[]> {
254+
const raw = await fetchImagesByIds(imageIds, tag)
254255
return raw.map(fromMapillary).filter((i): i is MediaImage => i !== null)
255256
}
256257

backend/src/mediawiki/client.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
SourceCdnError,
88
StorageError,
99
} from '@backend/core/errors'
10-
import { elapsed, logger } from '@backend/core/logger'
10+
import { elapsed, idTag, logger } from '@backend/core/logger'
1111
import { buildAuthHeader } from '@backend/core/oauthClient'
1212
import { withCsrfTokenRetry } from '@backend/mediawiki/tokenRetry'
1313
import type { Redis } from 'ioredis'
@@ -238,7 +238,8 @@ export class MediaWikiClient {
238238
const sha1 = createHash('sha1').update(buffer).digest('hex')
239239
const totalChunks = Math.ceil(buffer.length / CHUNK_SIZE)
240240
const start = process.hrtime.bigint()
241-
logger.info(`[mw] uploading ${filename} (${buffer.length} bytes, ${totalChunks} chunks)`)
241+
const tag = idTag(uploadId, batchId)
242+
logger.info(`[mw] ${tag} uploading ${filename} (${buffer.length} bytes, ${totalChunks} chunks)`)
242243

243244
const duplicates = await this.findDuplicates(sha1)
244245
if (duplicates.length > 0) {
@@ -275,7 +276,7 @@ export class MediaWikiClient {
275276
}
276277

277278
const chunkResponse = await withCsrfTokenRetry(
278-
`[mw] uploadFile chunk ${offset / CHUNK_SIZE + 1}/${totalChunks}`,
279+
`[mw] ${tag} uploadFile chunk ${offset / CHUNK_SIZE + 1}/${totalChunks}`,
279280
() => this.getCsrfToken(),
280281
(t) => this.apiUploadChunk(buildFormData(t)),
281282
token,
@@ -300,10 +301,10 @@ export class MediaWikiClient {
300301
const upload = result.upload as Record<string, unknown>
301302
stashKey = upload.filekey as string
302303
const chunkNum = offset / CHUNK_SIZE + 1
303-
logger.info(`[mw] chunk ${chunkNum}/${totalChunks} uploaded`)
304+
logger.info(`[mw] ${tag} chunk ${chunkNum}/${totalChunks} uploaded`)
304305
}
305306

306-
logger.info(`[mw] final commit for ${filename}`)
307+
logger.info(`[mw] ${tag} final commit for ${filename}`)
307308
let commitResult: Record<string, unknown> | null = null
308309
for (let attempt = 0; attempt <= STASH_RETRY_LIMIT; attempt++) {
309310
const buildFormData = (t: string) => {
@@ -318,7 +319,7 @@ export class MediaWikiClient {
318319
}
319320

320321
const commitResponse = await withCsrfTokenRetry(
321-
`[mw] uploadFile commit (attempt ${attempt + 1})`,
322+
`[mw] ${tag} uploadFile commit (attempt ${attempt + 1})`,
322323
() => this.getCsrfToken(),
323324
(t) => this.apiUploadChunk(buildFormData(t), true),
324325
token,
@@ -329,7 +330,7 @@ export class MediaWikiClient {
329330
if (errorObj) {
330331
if (errorObj.code === 'uploadstash-file-not-found' && attempt < STASH_RETRY_LIMIT) {
331332
logger.warn(
332-
`[mw] stash file not found on attempt ${attempt + 1}, retrying in ${STASH_RETRY_DELAY_MS}ms`,
333+
`[mw] ${tag} stash file not found on attempt ${attempt + 1}, retrying in ${STASH_RETRY_DELAY_MS}ms`,
333334
)
334335
await new Promise((resolve) => setTimeout(resolve, STASH_RETRY_DELAY_MS))
335336
continue
@@ -364,8 +365,8 @@ export class MediaWikiClient {
364365
)
365366
}
366367
logger.warn(
367-
{ uploadId, filename, result: upload.result, warnings },
368-
`[mw] unexpected upload result during commit`,
368+
{ filename, result: upload.result, warnings },
369+
`[mw] ${tag} unexpected upload result during commit`,
369370
)
370371
throw new Error(`Unexpected upload result: ${upload.result}`)
371372
}
@@ -374,7 +375,7 @@ export class MediaWikiClient {
374375
const upload = commitResult.upload as Record<string, unknown>
375376
const imageinfo = upload.imageinfo as Record<string, string>
376377
await redis.del(lockKey)
377-
logger.info(`[mw] uploaded ${filename} | ${elapsed(start)}`)
378+
logger.info(`[mw] ${tag} uploaded ${filename} | ${elapsed(start)}`)
378379
return imageinfo.descriptionurl!
379380
} catch (err) {
380381
await redis.del(lockKey)
@@ -387,13 +388,14 @@ export class MediaWikiClient {
387388
claims: unknown[] | null,
388389
labels: Record<string, { language: string; value: string }> | null,
389390
editSummary: string,
391+
tag: string,
390392
): Promise<void> {
391393
const start = process.hrtime.bigint()
392394
const payload: Record<string, unknown> = {}
393395
if (claims) payload.claims = claims
394396
if (labels) payload.labels = labels
395397
const { result } = await withCsrfTokenRetry(
396-
`[mw] applySdc ${filename}`,
398+
`[mw] ${tag} applySdc ${filename}`,
397399
() => this.getCsrfToken(),
398400
(token) =>
399401
this.apiRequest(
@@ -404,10 +406,10 @@ export class MediaWikiClient {
404406
)
405407
if (result.error)
406408
throw new Error((result.error as Record<string, string>).info ?? 'wbeditentity failed')
407-
logger.info(`[mw] sdc applied to ${filename} | ${elapsed(start)}`)
409+
logger.info(`[mw] ${tag} sdc applied to ${filename} | ${elapsed(start)}`)
408410
}
409411

410-
async nullEdit(filename: string): Promise<void> {
412+
async nullEdit(filename: string, tag: string): Promise<void> {
411413
const start = process.hrtime.bigint()
412414
for (let attempt = 0; attempt <= STASH_RETRY_LIMIT; attempt++) {
413415
try {
@@ -425,7 +427,7 @@ export class MediaWikiClient {
425427
const mainSlot = (slots.main as Record<string, unknown>) ?? {}
426428
const content = (mainSlot.content as string) ?? ''
427429
const { result } = await withCsrfTokenRetry(
428-
`[mw] nullEdit ${filename}`,
430+
`[mw] ${tag} nullEdit ${filename}`,
429431
() => this.getCsrfToken(),
430432
(token) =>
431433
this.apiRequest({ action: 'edit' }, 'POST', {
@@ -438,12 +440,12 @@ export class MediaWikiClient {
438440
)
439441
if (result.error)
440442
throw new Error((result.error as Record<string, string>).info ?? 'null edit failed')
441-
logger.info(`[mw] null edit on ${filename} | ${elapsed(start)}`)
443+
logger.info(`[mw] ${tag} null edit on ${filename} | ${elapsed(start)}`)
442444
return
443445
} catch (err) {
444446
if (attempt < STASH_RETRY_LIMIT) {
445447
logger.warn(
446-
`[mw] null edit attempt ${attempt + 1} failed for ${filename}, retrying in ${STASH_RETRY_DELAY_MS}ms`,
448+
`[mw] ${tag} null edit attempt ${attempt + 1} failed for ${filename}, retrying in ${STASH_RETRY_DELAY_MS}ms`,
447449
)
448450
await new Promise((resolve) => setTimeout(resolve, STASH_RETRY_DELAY_MS))
449451
} else {

backend/src/workers/queue.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { config } from '@backend/config'
2-
import { logger } from '@backend/core/logger'
2+
import { idTag, logger } from '@backend/core/logger'
33
import type { RateLimitInfo } from '@backend/core/rateLimiter'
44
import { Queue } from 'bullmq'
55

@@ -42,7 +42,7 @@ export async function enqueueUpload(data: UploadJobData, delayMs: number): Promi
4242
removeOnFail: { age: 86400 * 7 },
4343
})
4444
logger.info(
45-
`[worker] upload ${data.uploadId} enqueued (job: ${job.id}, batch: ${data.batchId}, delay: ${formatDelayMs(delayMs)})`,
45+
`[worker] ${idTag(data.uploadId, data.batchId)} enqueued (job: ${job.id}, delay: ${formatDelayMs(delayMs)})`,
4646
)
4747
return job.id!
4848
}
@@ -51,6 +51,8 @@ export async function removeUploadJob(jobId: string): Promise<void> {
5151
const job = await getUploadQueue().getJob(jobId)
5252
if (job) {
5353
await job.remove()
54-
logger.info(`[worker] job ${jobId} removed from queue`)
54+
logger.info(
55+
`[worker] ${idTag(job.data.uploadId, job.data.batchId)} job ${jobId} removed from queue`,
56+
)
5557
}
5658
}

0 commit comments

Comments
 (0)