1
- import { Context , Logger , remove , Schema , Time } from 'koishi'
1
+ import { Context , Dict , Logger , remove , Schema , Time } from 'koishi'
2
2
import { DataService } from '@koishijs/plugin-console'
3
3
import { resolve } from 'path'
4
- import { promises as fsp , mkdirSync , readdirSync } from 'fs'
5
- import { FileHandle } from 'fs/promises'
6
-
7
- const { open, rm } = fsp
4
+ import { mkdirSync , readdirSync } from 'fs'
5
+ import { rm } from 'fs/promises'
6
+ import { FileWriter } from './file'
8
7
9
8
declare module '@koishijs/plugin-console' {
10
9
namespace Console {
@@ -14,10 +13,10 @@ declare module '@koishijs/plugin-console' {
14
13
}
15
14
}
16
15
17
- class LogProvider extends DataService < string [ ] > {
16
+ class LogProvider extends DataService < Logger . Record [ ] > {
18
17
root : string
19
18
date : string
20
- files : number [ ] = [ ]
19
+ files : Dict < number > = { }
21
20
writer : FileWriter
22
21
23
22
constructor ( ctx : Context , private config : LogProvider . Config = { } ) {
@@ -28,55 +27,57 @@ class LogProvider extends DataService<string[]> {
28
27
prod : resolve ( __dirname , '../dist' ) ,
29
28
} )
30
29
31
- this . ctx . on ( 'ready' , ( ) => {
30
+ ctx . on ( 'ready' , ( ) => {
32
31
this . prepareWriter ( )
33
32
this . prepareLogger ( )
34
33
} , true )
34
+
35
+ ctx . on ( 'dispose' , ( ) => {
36
+ this . writer ?. close ( )
37
+ this . writer = null
38
+ } )
35
39
}
36
40
37
41
prepareWriter ( ) {
38
42
this . root = resolve ( this . ctx . baseDir , this . config . root )
39
43
mkdirSync ( this . root , { recursive : true } )
40
44
41
45
for ( const filename of readdirSync ( this . root ) ) {
42
- if ( ! filename . endsWith ( '.log' ) ) continue
43
- this . files . push ( Time . getDateNumber ( new Date ( filename . slice ( 0 , - 4 ) ) , 0 ) )
46
+ const capture = / ^ ( \d { 4 } - \d { 2 } - \d { 2 } ) - ( \d + ) \. l o g $ / . exec ( filename )
47
+ if ( ! capture ) continue
48
+ this . files [ capture [ 1 ] ] = Math . max ( this . files [ capture [ 1 ] ] ?? 0 , + capture [ 2 ] )
44
49
}
45
50
46
- this . createFile ( )
47
-
48
- this . ctx . on ( 'dispose' , ( ) => {
49
- this . writer ?. close ( )
50
- this . writer = null
51
- } )
51
+ const date = new Date ( ) . toISOString ( ) . slice ( 0 , 10 )
52
+ this . createFile ( date , this . files [ date ] ??= 1 )
52
53
}
53
54
54
- createFile ( ) {
55
- this . date = Time . template ( 'yyyy-MM-dd' )
56
- this . writer = new FileWriter ( `${ this . root } /${ this . date } .log` )
55
+ async createFile ( date : string , index : number ) {
56
+ this . writer = new FileWriter ( date , `${ this . root } /${ date } -${ index } .log` )
57
57
58
58
const { maxAge } = this . config
59
59
if ( ! maxAge ) return
60
60
61
- const current = Time . getDateNumber ( new Date ( ) , 0 )
62
- this . files = this . files . filter ( ( date ) => {
63
- if ( date >= current - maxAge ) return true
64
- rm ( `${ this . root } /${ Time . template ( 'yyyy-MM-dd' , Time . fromDateNumber ( date , 0 ) ) } .log` )
65
- } )
61
+ const now = Date . now ( )
62
+ for ( const date in this . files ) {
63
+ if ( now - + new Date ( date ) < maxAge * Time . day ) continue
64
+ for ( let index = 1 ; index <= this . files [ date ] ; ++ index ) {
65
+ await rm ( `${ this . root } /${ date } -${ index } .log` )
66
+ }
67
+ }
66
68
}
67
69
68
70
prepareLogger ( ) {
69
- if ( this . ctx . prologue ) {
70
- for ( const line of this . ctx . prologue ) {
71
- this . printText ( line )
71
+ if ( this . ctx . loader . prolog ) {
72
+ for ( const record of this . ctx . loader . prolog ) {
73
+ this . record ( record )
72
74
}
73
- this . ctx . root . prologue = null
75
+ this . ctx . root . loader . prolog = null
74
76
}
75
77
76
78
const target : Logger . Target = {
77
79
colors : 3 ,
78
- showTime : 'yyyy-MM-dd hh:mm:ss' ,
79
- print : this . printText . bind ( this ) ,
80
+ record : this . record . bind ( this ) ,
80
81
}
81
82
82
83
Logger . targets . push ( target )
@@ -86,13 +87,18 @@ class LogProvider extends DataService<string[]> {
86
87
} )
87
88
}
88
89
89
- printText ( text : string ) {
90
- if ( ! text . startsWith ( this . date ) ) {
90
+ record ( record : Logger . Record ) {
91
+ const date = new Date ( record . timestamp ) . toISOString ( ) . slice ( 0 , 10 )
92
+ if ( this . writer . date !== date ) {
91
93
this . writer . close ( )
92
- this . createFile ( )
94
+ this . createFile ( date , this . files [ date ] = 1 )
95
+ }
96
+ this . writer . write ( record )
97
+ this . patch ( [ record ] )
98
+ if ( this . writer . size >= this . config . maxSize ) {
99
+ this . writer . close ( )
100
+ this . createFile ( date , ++ this . files [ date ] )
93
101
}
94
- this . writer . write ( text )
95
- this . patch ( [ text ] )
96
102
}
97
103
98
104
async get ( ) {
@@ -104,41 +110,17 @@ namespace LogProvider {
104
110
export interface Config {
105
111
root ?: string
106
112
maxAge ?: number
113
+ maxSize ?: number
107
114
}
108
115
109
116
export const Config : Schema < Config > = Schema . object ( {
110
- root : Schema . string ( ) . default ( 'logs' ) . description ( '存放输出日志的本地目录。' ) ,
117
+ root : Schema . path ( {
118
+ filters : [ 'directory' ] ,
119
+ allowCreate : true ,
120
+ } ) . default ( 'data/logs' ) . description ( '存放输出日志的本地目录。' ) ,
111
121
maxAge : Schema . natural ( ) . default ( 30 ) . description ( '日志文件保存的最大天数。' ) ,
122
+ maxSize : Schema . natural ( ) . default ( 1024 * 100 ) . description ( '单个日志文件的最大大小。' ) ,
112
123
} )
113
124
}
114
125
115
126
export default LogProvider
116
-
117
- class FileWriter {
118
- private task : Promise < FileHandle >
119
- private content : string [ ] = [ ]
120
-
121
- constructor ( path : string ) {
122
- this . task = open ( path , 'a+' ) . then ( async ( handle ) => {
123
- const text = await handle . readFile ( 'utf-8' )
124
- if ( text ) this . content = text . split ( / \n (? = \S ) / g)
125
- return handle
126
- } )
127
- }
128
-
129
- async read ( ) {
130
- await this . task
131
- return this . content
132
- }
133
-
134
- async write ( text : string ) {
135
- const handle = await this . task
136
- await handle . write ( text + '\n' )
137
- this . content . push ( text )
138
- }
139
-
140
- async close ( ) {
141
- const handle = await this . task
142
- await handle . close ( )
143
- }
144
- }
0 commit comments