publish(task 14): structured json logger
createLogger(opts) sammelt postSuccess/postFailed/postSkippedDraft- events, druckt menschenlesbare zeilen (✓/✗/-), liefert am ende ein RunLog mit allen einträgen plus start/end-timestamps. writeJson() schreibt die komplette summary als json für archivierung/ci-artifact. 2 tests grün. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b6196f1052
commit
db85061287
|
|
@ -0,0 +1,106 @@
|
|||
export type RunMode = 'diff' | 'force-all' | 'post-single'
|
||||
|
||||
export interface PostLog {
|
||||
slug: string
|
||||
status: 'success' | 'failed' | 'skipped-draft'
|
||||
action?: 'new' | 'update'
|
||||
event_id?: string
|
||||
relays_ok?: string[]
|
||||
relays_failed?: string[]
|
||||
blossom_servers_ok?: string[]
|
||||
images_uploaded?: number
|
||||
duration_ms?: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface RunLog {
|
||||
run_id: string
|
||||
started_at: string
|
||||
ended_at: string
|
||||
mode: RunMode
|
||||
posts: PostLog[]
|
||||
exit_code: number
|
||||
}
|
||||
|
||||
export interface SuccessArgs {
|
||||
slug: string
|
||||
action: 'new' | 'update'
|
||||
eventId: string
|
||||
relaysOk: string[]
|
||||
relaysFailed: string[]
|
||||
blossomServersOk: string[]
|
||||
imagesUploaded: number
|
||||
durationMs: number
|
||||
}
|
||||
|
||||
export interface FailedArgs {
|
||||
slug: string
|
||||
error: string
|
||||
durationMs: number
|
||||
}
|
||||
|
||||
export interface LoggerOptions {
|
||||
mode: RunMode
|
||||
runId: string
|
||||
print?: (line: string) => void
|
||||
now?: () => Date
|
||||
}
|
||||
|
||||
export interface Logger {
|
||||
postSuccess(args: SuccessArgs): void
|
||||
postFailed(args: FailedArgs): void
|
||||
postSkippedDraft(slug: string): void
|
||||
finalize(exitCode: number): RunLog
|
||||
writeJson(path: string, summary: RunLog): Promise<void>
|
||||
}
|
||||
|
||||
export function createLogger(opts: LoggerOptions): Logger {
|
||||
const print = opts.print ?? ((line: string) => console.log(line))
|
||||
const now = opts.now ?? (() => new Date())
|
||||
const posts: PostLog[] = []
|
||||
const startedAt = now().toISOString()
|
||||
return {
|
||||
postSuccess(a) {
|
||||
posts.push({
|
||||
slug: a.slug,
|
||||
status: 'success',
|
||||
action: a.action,
|
||||
event_id: a.eventId,
|
||||
relays_ok: a.relaysOk,
|
||||
relays_failed: a.relaysFailed,
|
||||
blossom_servers_ok: a.blossomServersOk,
|
||||
images_uploaded: a.imagesUploaded,
|
||||
duration_ms: a.durationMs,
|
||||
})
|
||||
print(
|
||||
`✓ ${a.slug} (${a.action}) — relays:${a.relaysOk.length}ok/${a.relaysFailed.length}fail — ${a.durationMs}ms`,
|
||||
)
|
||||
},
|
||||
postFailed(a) {
|
||||
posts.push({
|
||||
slug: a.slug,
|
||||
status: 'failed',
|
||||
error: a.error,
|
||||
duration_ms: a.durationMs,
|
||||
})
|
||||
print(`✗ ${a.slug} — ${a.error}`)
|
||||
},
|
||||
postSkippedDraft(slug) {
|
||||
posts.push({ slug, status: 'skipped-draft' })
|
||||
print(`- ${slug} (draft, skipped)`)
|
||||
},
|
||||
finalize(exitCode) {
|
||||
return {
|
||||
run_id: opts.runId,
|
||||
started_at: startedAt,
|
||||
ended_at: now().toISOString(),
|
||||
mode: opts.mode,
|
||||
posts,
|
||||
exit_code: exitCode,
|
||||
}
|
||||
},
|
||||
writeJson(path, summary) {
|
||||
return Deno.writeTextFile(path, JSON.stringify(summary, null, 2))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { assertEquals } from '@std/assert'
|
||||
import { createLogger } from '../src/core/log.ts'
|
||||
|
||||
Deno.test('logger: sammelt post-einträge und schreibt summary', () => {
|
||||
const sink: string[] = []
|
||||
const logger = createLogger({
|
||||
mode: 'force-all',
|
||||
runId: 'run-1',
|
||||
print: (line) => sink.push(line),
|
||||
now: () => new Date('2026-04-16T10:00:00Z'),
|
||||
})
|
||||
logger.postSuccess({
|
||||
slug: 's1',
|
||||
action: 'new',
|
||||
eventId: 'ev1',
|
||||
relaysOk: ['wss://r1'],
|
||||
relaysFailed: [],
|
||||
blossomServersOk: [],
|
||||
imagesUploaded: 0,
|
||||
durationMs: 10,
|
||||
})
|
||||
logger.postSkippedDraft('s2')
|
||||
const summary = logger.finalize(0)
|
||||
assertEquals(summary.run_id, 'run-1')
|
||||
assertEquals(summary.mode, 'force-all')
|
||||
assertEquals(summary.posts.length, 2)
|
||||
assertEquals(summary.posts[0].status, 'success')
|
||||
assertEquals(summary.posts[1].status, 'skipped-draft')
|
||||
assertEquals(summary.exit_code, 0)
|
||||
assertEquals(sink.some((s) => s.includes('s1')), true)
|
||||
})
|
||||
|
||||
Deno.test('logger: writeJson schreibt datei', async () => {
|
||||
const tmp = await Deno.makeTempDir()
|
||||
try {
|
||||
const logger = createLogger({
|
||||
mode: 'diff',
|
||||
runId: 'run-2',
|
||||
print: () => {},
|
||||
now: () => new Date('2026-04-16T10:00:00Z'),
|
||||
})
|
||||
const summary = logger.finalize(0)
|
||||
await logger.writeJson(`${tmp}/out.json`, summary)
|
||||
const text = await Deno.readTextFile(`${tmp}/out.json`)
|
||||
const parsed = JSON.parse(text)
|
||||
assertEquals(parsed.run_id, 'run-2')
|
||||
} finally {
|
||||
await Deno.remove(tmp, { recursive: true })
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue