11// Copyright 2025, Command Line Inc.
22// SPDX-License-Identifier: Apache-2.0
33
4+ import fs from "fs" ;
45import path from "path" ;
56import { format } from "util" ;
67import winston from "winston" ;
78import { getWaveDataDir , isDev } from "./platform" ;
89
910const oldConsoleLog = console . log ;
1011
12+ function findHighestLogNumber ( logsDir : string ) : number {
13+ if ( ! fs . existsSync ( logsDir ) ) {
14+ return 0 ;
15+ }
16+ const files = fs . readdirSync ( logsDir ) ;
17+ let maxNum = 0 ;
18+ for ( const file of files ) {
19+ const match = file . match ( / ^ w a v e a p p \. ( \d + ) \. l o g $ / ) ;
20+ if ( match ) {
21+ const num = parseInt ( match [ 1 ] , 10 ) ;
22+ if ( num > maxNum ) {
23+ maxNum = num ;
24+ }
25+ }
26+ }
27+ return maxNum ;
28+ }
29+
30+ function pruneOldLogs ( logsDir : string ) : { pruned : string [ ] ; error : any } {
31+ if ( ! fs . existsSync ( logsDir ) ) {
32+ return { pruned : [ ] , error : null } ;
33+ }
34+
35+ const files = fs . readdirSync ( logsDir ) ;
36+ const logFiles : { name : string ; num : number } [ ] = [ ] ;
37+
38+ for ( const file of files ) {
39+ const match = file . match ( / ^ w a v e a p p \. ( \d + ) \. l o g $ / ) ;
40+ if ( match ) {
41+ logFiles . push ( { name : file , num : parseInt ( match [ 1 ] , 10 ) } ) ;
42+ }
43+ }
44+
45+ if ( logFiles . length <= 5 ) {
46+ return { pruned : [ ] , error : null } ;
47+ }
48+
49+ logFiles . sort ( ( a , b ) => b . num - a . num ) ;
50+ const toDelete = logFiles . slice ( 5 ) ;
51+ const pruned : string [ ] = [ ] ;
52+ let firstError : any = null ;
53+
54+ for ( const logFile of toDelete ) {
55+ try {
56+ fs . unlinkSync ( path . join ( logsDir , logFile . name ) ) ;
57+ pruned . push ( logFile . name ) ;
58+ } catch ( e ) {
59+ if ( firstError == null ) {
60+ firstError = e ;
61+ }
62+ }
63+ }
64+
65+ return { pruned, error : firstError } ;
66+ }
67+
68+ function rotateLogIfNeeded ( ) : string | null {
69+ const waveDataDir = getWaveDataDir ( ) ;
70+ const logFile = path . join ( waveDataDir , "waveapp.log" ) ;
71+ const logsDir = path . join ( waveDataDir , "logs" ) ;
72+
73+ if ( ! fs . existsSync ( logsDir ) ) {
74+ fs . mkdirSync ( logsDir , { recursive : true } ) ;
75+ }
76+
77+ if ( ! fs . existsSync ( logFile ) ) {
78+ return null ;
79+ }
80+
81+ const stats = fs . statSync ( logFile ) ;
82+ if ( stats . size > 10 * 1024 * 1024 ) {
83+ const nextNum = findHighestLogNumber ( logsDir ) + 1 ;
84+ const rotatedPath = path . join ( logsDir , `waveapp.${ nextNum } .log` ) ;
85+ fs . renameSync ( logFile , rotatedPath ) ;
86+ return rotatedPath ;
87+ }
88+ return null ;
89+ }
90+
91+ let logRotateError : any = null ;
92+ let rotatedPath : string | null = null ;
93+ let prunedFiles : string [ ] = [ ] ;
94+ let pruneError : any = null ;
95+ try {
96+ rotatedPath = rotateLogIfNeeded ( ) ;
97+ const logsDir = path . join ( getWaveDataDir ( ) , "logs" ) ;
98+ const pruneResult = pruneOldLogs ( logsDir ) ;
99+ prunedFiles = pruneResult . pruned ;
100+ pruneError = pruneResult . error ;
101+ } catch ( e ) {
102+ logRotateError = e ;
103+ }
104+
11105const loggerTransports : winston . transport [ ] = [
12106 new winston . transports . File ( { filename : path . join ( getWaveDataDir ( ) , "waveapp.log" ) , level : "info" } ) ,
13107] ;
@@ -23,6 +117,7 @@ const loggerConfig = {
23117 transports : loggerTransports ,
24118} ;
25119const logger = winston . createLogger ( loggerConfig ) ;
120+
26121function log ( ...msg : any [ ] ) {
27122 try {
28123 logger . info ( format ( ...msg ) ) ;
@@ -31,4 +126,17 @@ function log(...msg: any[]) {
31126 }
32127}
33128
129+ if ( logRotateError != null ) {
130+ log ( "error rotating/pruning logs (non-fatal):" , logRotateError ) ;
131+ }
132+ if ( rotatedPath != null ) {
133+ log ( "rotated old log file to:" , rotatedPath ) ;
134+ }
135+ if ( prunedFiles . length > 0 ) {
136+ log ( "pruned old log files:" , prunedFiles . join ( ", " ) ) ;
137+ }
138+ if ( pruneError != null ) {
139+ log ( "error pruning some log files (non-fatal):" , pruneError ) ;
140+ }
141+
34142export { log } ;
0 commit comments