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