Initial commit: AI Photo Organizer (Electron + face-api)
Local-first photo organizer that auto-sorts images by face recognition and EXIF capture date. - Electron app with 3-process split: Main (Node) / UI Renderer (React) / hidden Inference Renderer (face-api + WebGL) - Core pipeline: scan -> face match + EXIF -> path build -> atomic move/copy - Move = copy -> verify -> delete; auto-rename on filename collision - 1st-registered profile = move, others = copy; unmatched -> [미정]/YYYY/MM - EXIF date with mtime fallback - Vitest unit tests (pathBuilder / fileOps / concurrency) all green - electron-builder config for Windows (nsis) + macOS (dmg) - Docs: PRD / DECISIONS / ARCHITECTURE Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { appendFile, mkdir } from 'node:fs/promises'
|
||||
import { dirname } from 'node:path'
|
||||
|
||||
type Level = 'INFO' | 'WARN' | 'ERROR'
|
||||
|
||||
/**
|
||||
* 구조적 로거. 콘솔 + (옵션) 파일에 기록.
|
||||
* 잡 실행 시 setLogFile()로 출력 루트 하위 로그 파일 경로를 지정한다.
|
||||
*/
|
||||
class Logger {
|
||||
private logFile: string | null = null
|
||||
private buffer: string[] = []
|
||||
|
||||
async setLogFile(path: string): Promise<void> {
|
||||
this.logFile = path
|
||||
await mkdir(dirname(path), { recursive: true })
|
||||
}
|
||||
|
||||
private async write(level: Level, msg: string, meta?: unknown): Promise<void> {
|
||||
// new Date() 사용 (Main 프로세스는 일반 Node — 제약 없음)
|
||||
const ts = new Date().toISOString()
|
||||
const metaStr = meta === undefined ? '' : ` ${safeJson(meta)}`
|
||||
const line = `[${ts}] [${level}] ${msg}${metaStr}`
|
||||
|
||||
if (level === 'ERROR') console.error(line)
|
||||
else if (level === 'WARN') console.warn(line)
|
||||
else console.log(line)
|
||||
|
||||
this.buffer.push(line)
|
||||
if (this.logFile) {
|
||||
try {
|
||||
await appendFile(this.logFile, line + '\n', 'utf-8')
|
||||
} catch {
|
||||
// 로그 파일 기록 실패는 치명적이지 않음 — 콘솔 출력은 이미 됨
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info(msg: string, meta?: unknown) {
|
||||
return this.write('INFO', msg, meta)
|
||||
}
|
||||
warn(msg: string, meta?: unknown) {
|
||||
return this.write('WARN', msg, meta)
|
||||
}
|
||||
error(msg: string, meta?: unknown) {
|
||||
return this.write('ERROR', msg, meta)
|
||||
}
|
||||
|
||||
/** 잡 종료 시 버퍼 비우기 */
|
||||
reset(): void {
|
||||
this.buffer = []
|
||||
this.logFile = null
|
||||
}
|
||||
}
|
||||
|
||||
function safeJson(v: unknown): string {
|
||||
try {
|
||||
return JSON.stringify(v)
|
||||
} catch {
|
||||
return String(v)
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger()
|
||||
Reference in New Issue
Block a user