/** * In-memory ring buffer for diagnostic log entries. * * Both the extension host and the webview share access to these entries * via the panel's message bridge; the webview surfaces them in a modal * opened from the IssueDetailPanel. Entries are also mirrored to a VS Code * LogOutputChannel ("Superpowers" in the Output panel) so they remain * visible without the webview and honour the user's log level. * * No filesystem persistence — buffer is capped at the last 500 entries * and lost on extension reload. */ import type { Event, LogOutputChannel } from 'vscode' import { EventEmitter, window } from 'vscode' export type LogLevel = 'info' | 'warn' | 'error' export interface LogEntry { /** Milliseconds since epoch. */ ts: number level: LogLevel /** Short tag identifying the subsystem (e.g. 'webhook', 'gitea'). */ source: string message: string /** Optional extra blob, newline-safe (stack trace, JSON snippet, etc.). */ details?: string } class Logger { private buf: LogEntry[] = [] private readonly cap = 500 private readonly emitter = new EventEmitter() private readonly channel: LogOutputChannel = window.createOutputChannel('Superpowers', { log: true }) readonly onEntry: Event = this.emitter.event /** * Record a new entry. Stamps with the current time, trims the buffer to * the last `cap` entries, fires the change event, and mirrors to the * output channel. */ add(entry: Omit): void { const full: LogEntry = { ...entry, ts: Date.now() } this.buf.push(full) if (this.buf.length > this.cap) this.buf.splice(0, this.buf.length - this.cap) const line = `[${full.source}] ${full.message}` const args = full.details === undefined ? [] : [full.details] if (full.level === 'error') this.channel.error(line, ...args) else if (full.level === 'warn') this.channel.warn(line, ...args) else this.channel.info(line, ...args) this.emitter.fire(full) } /** Returns a copy of the current buffer (newest entry last). */ snapshot(): LogEntry[] { return this.buf.slice() } /** Clears the buffer in place. Webview clearing broadcasts a separate event. */ clear(): void { this.buf = [] } dispose(): void { this.channel.dispose() this.emitter.dispose() } } export const logger = new Logger()