1- import { LOG_LEVEL } from "./config.js" ;
1+ import { appendFileSync , existsSync , mkdirSync , renameSync , statSync } from "node:fs" ;
2+ import { join } from "node:path" ;
3+ import { LOG_LEVEL , CLAWUI_DB_DIR } from "./config.js" ;
24
35type LogLevel = "debug" | "info" | "warn" | "error" ;
46
@@ -16,6 +18,57 @@ const LEVEL_LABELS: Record<LogLevel, string> = {
1618 error : "ERROR" ,
1719} ;
1820
21+ // ─── File logging ────────────────────────────────────────────
22+
23+ const LOG_DIR = join ( CLAWUI_DB_DIR , "logs" ) ;
24+ const LOG_FILE = join ( LOG_DIR , "server.log" ) ;
25+ const MAX_LOG_SIZE = 10 * 1024 * 1024 ; // 10 MB
26+ const MAX_ROTATED_FILES = 5 ;
27+
28+ let fileLoggingEnabled = false ;
29+
30+ try {
31+ if ( ! existsSync ( LOG_DIR ) ) {
32+ mkdirSync ( LOG_DIR , { recursive : true } ) ;
33+ }
34+ fileLoggingEnabled = true ;
35+ } catch {
36+ // If we can't create the log dir, fall back to console-only
37+ console . warn ( `[logger] Could not create log directory ${ LOG_DIR } , file logging disabled` ) ;
38+ }
39+
40+ function rotateIfNeeded ( ) : void {
41+ try {
42+ if ( ! existsSync ( LOG_FILE ) ) return ;
43+ const stat = statSync ( LOG_FILE ) ;
44+ if ( stat . size < MAX_LOG_SIZE ) return ;
45+
46+ // Rotate: server.log.4 → delete, server.log.3 → .4, ... server.log → .1
47+ for ( let i = MAX_ROTATED_FILES - 1 ; i >= 1 ; i -- ) {
48+ const from = `${ LOG_FILE } .${ i } ` ;
49+ const to = `${ LOG_FILE } .${ i + 1 } ` ;
50+ if ( existsSync ( from ) ) {
51+ renameSync ( from , to ) ;
52+ }
53+ }
54+ renameSync ( LOG_FILE , `${ LOG_FILE } .1` ) ;
55+ } catch {
56+ // Rotation failure is non-fatal
57+ }
58+ }
59+
60+ function writeToFile ( formatted : string ) : void {
61+ if ( ! fileLoggingEnabled ) return ;
62+ try {
63+ rotateIfNeeded ( ) ;
64+ appendFileSync ( LOG_FILE , formatted + "\n" ) ;
65+ } catch {
66+ // File write failure is non-fatal
67+ }
68+ }
69+
70+ // ─── Logger ──────────────────────────────────────────────────
71+
1972function shouldLog ( level : LogLevel ) : boolean {
2073 const threshold = LEVEL_ORDER [ LOG_LEVEL as LogLevel ] ?? LEVEL_ORDER . info ;
2174 return LEVEL_ORDER [ level ] >= threshold ;
@@ -25,6 +78,16 @@ function formatMessage(level: LogLevel, module: string, msg: string): string {
2578 return `[${ new Date ( ) . toISOString ( ) } ] [${ LEVEL_LABELS [ level ] } ] [${ module } ] ${ msg } ` ;
2679}
2780
81+ function formatArgs ( msg : string , args : unknown [ ] ) : string {
82+ if ( args . length === 0 ) return msg ;
83+ // Simple %s substitution like console.log does
84+ let result = msg ;
85+ for ( const arg of args ) {
86+ result = result . replace ( "%s" , String ( arg ) ) ;
87+ }
88+ return result ;
89+ }
90+
2891export interface Logger {
2992 debug : ( msg : string , ...args : unknown [ ] ) => void ;
3093 info : ( msg : string , ...args : unknown [ ] ) => void ;
@@ -35,16 +98,32 @@ export interface Logger {
3598export function createLogger ( module : string ) : Logger {
3699 return {
37100 debug ( msg : string , ...args : unknown [ ] ) {
38- if ( shouldLog ( "debug" ) ) console . debug ( formatMessage ( "debug" , module , msg ) , ...args ) ;
101+ if ( shouldLog ( "debug" ) ) {
102+ const formatted = formatMessage ( "debug" , module , formatArgs ( msg , args ) ) ;
103+ console . debug ( formatted ) ;
104+ writeToFile ( formatted ) ;
105+ }
39106 } ,
40107 info ( msg : string , ...args : unknown [ ] ) {
41- if ( shouldLog ( "info" ) ) console . log ( formatMessage ( "info" , module , msg ) , ...args ) ;
108+ if ( shouldLog ( "info" ) ) {
109+ const formatted = formatMessage ( "info" , module , formatArgs ( msg , args ) ) ;
110+ console . log ( formatted ) ;
111+ writeToFile ( formatted ) ;
112+ }
42113 } ,
43114 warn ( msg : string , ...args : unknown [ ] ) {
44- if ( shouldLog ( "warn" ) ) console . warn ( formatMessage ( "warn" , module , msg ) , ...args ) ;
115+ if ( shouldLog ( "warn" ) ) {
116+ const formatted = formatMessage ( "warn" , module , formatArgs ( msg , args ) ) ;
117+ console . warn ( formatted ) ;
118+ writeToFile ( formatted ) ;
119+ }
45120 } ,
46121 error ( msg : string , ...args : unknown [ ] ) {
47- if ( shouldLog ( "error" ) ) console . error ( formatMessage ( "error" , module , msg ) , ...args ) ;
122+ if ( shouldLog ( "error" ) ) {
123+ const formatted = formatMessage ( "error" , module , formatArgs ( msg , args ) ) ;
124+ console . error ( formatted ) ;
125+ writeToFile ( formatted ) ;
126+ }
48127 } ,
49128 } ;
50129}
0 commit comments