@@ -33,10 +33,11 @@ import (
3333var Stdout io.Writer = os .Stdout
3434
3535func init () {
36+ RegisterPlugin [GroupAppender ]("Group" , PluginTypeAppender )
3637 RegisterPlugin [DiscardAppender ]("Discard" , PluginTypeAppender )
3738 RegisterPlugin [ConsoleAppender ]("Console" , PluginTypeAppender )
3839 RegisterPlugin [FileAppender ]("File" , PluginTypeAppender )
39- RegisterPlugin [GroupAppender ]("Group " , PluginTypeAppender )
40+ RegisterPlugin [RollingFileAppender ]("RotateFile " , PluginTypeAppender )
4041
4142 // register built-in converters
4243 RegisterConverter (ParseRotateStrategy )
6364 _ Appender = (* GroupAppender )(nil )
6465 _ Appender = (* DiscardAppender )(nil )
6566 _ Appender = (* ConsoleAppender )(nil )
66- _ Appender = (* FileAppender )(nil )
67+ _ Appender = (* RollingFileAppender )(nil )
6768)
6869
6970// AppenderBase provides common configuration fields for all appenders.
@@ -145,6 +146,50 @@ func (c *ConsoleAppender) Write(b []byte) {
145146 _ , _ = Stdout .Write (b )
146147}
147148
149+ // FileAppender writes formatted log events to a specified file.
150+ type FileAppender struct {
151+ AppenderBase
152+ Layout Layout `PluginElement:"Layout,default=TextLayout"`
153+ FileDir string `PluginAttribute:"fileDir,default=./logs"`
154+ FileName string `PluginAttribute:"fileName"`
155+
156+ file * os.File
157+ }
158+
159+ func (c * FileAppender ) ConcurrentSafe () bool { return true }
160+
161+ // Start opens the log file for appending.
162+ func (c * FileAppender ) Start () error {
163+ const fileFlag = os .O_WRONLY | os .O_CREATE | os .O_APPEND
164+ fileName := filepath .Join (c .FileDir , c .FileName )
165+ f , err := os .OpenFile (fileName , fileFlag , 0644 )
166+ if err != nil {
167+ return err
168+ }
169+ c .file = f
170+ return nil
171+ }
172+
173+ // Append formats the log event and writes it to the file.
174+ func (c * FileAppender ) Append (e * Event ) {
175+ if c .EnableLevel (e .Level ) {
176+ c .Write (c .Layout .ToBytes (e ))
177+ }
178+ }
179+
180+ // Write writes a byte slice directly to the file.
181+ func (c * FileAppender ) Write (b []byte ) {
182+ _ , _ = c .file .Write (b )
183+ }
184+
185+ // Stop flushes and closes the file.
186+ func (c * FileAppender ) Stop () {
187+ if c .file != nil {
188+ _ = c .file .Sync ()
189+ _ = c .file .Close ()
190+ }
191+ }
192+
148193var rotateStrategyRegistry = map [string ]RollingPolicy {}
149194
150195// RollingPolicy defines the interface for log rotation strategies.
@@ -188,7 +233,7 @@ func ParseRotateStrategy(name string) (RollingPolicy, error) {
188233 return s , nil
189234}
190235
191- // FileAppender allows **multiple goroutines** to call Write()
236+ // RollingFileAppender allows **multiple goroutines** to call Write()
192237// safely, at the cost of slightly higher overhead and potential
193238// (acceptable) log loss during rotation.
194239//
@@ -202,7 +247,7 @@ func ParseRotateStrategy(name string) (RollingPolicy, error) {
202247// - During Stop(), concurrent writes may also be lost.
203248// - If zero log loss is required, use AsyncRotateFileWriter
204249// with a dedicated logging goroutine instead.
205- type FileAppender struct {
250+ type RollingFileAppender struct {
206251 AppenderBase
207252 Layout Layout `PluginElement:"Layout,default=TextLayout"`
208253 FileDir string `PluginAttribute:"fileDir,default=./logs"`
@@ -216,7 +261,7 @@ type FileAppender struct {
216261}
217262
218263// Start opens the initial log file.
219- func (c * FileAppender ) Start () error {
264+ func (c * RollingFileAppender ) Start () error {
220265 now := time .Now ()
221266 filePath , file , err := c .createFile (now )
222267 if err != nil {
@@ -229,15 +274,15 @@ func (c *FileAppender) Start() error {
229274
230275// Write writes bytes to the current log file.
231276// May lose a few writes during rotation or Stop().
232- func (c * FileAppender ) Write (b []byte ) {
277+ func (c * RollingFileAppender ) Write (b []byte ) {
233278 c .rotate ()
234279 if file := c .file .Load (); file != nil {
235280 _ , _ = file .Write (b )
236281 }
237282}
238283
239284// Stop flushes and closes the current file.
240- func (c * FileAppender ) Stop () {
285+ func (c * RollingFileAppender ) Stop () {
241286 c .rotate ()
242287 if file := c .file .Swap (nil ); file != nil {
243288 _ = file .Sync ()
@@ -249,7 +294,7 @@ func (c *FileAppender) Stop() {
249294// If so, it closes the old file, opens a new one, and triggers cleanup.
250295// Risk: If file creation fails during rotation, new logs will be lost
251296// until the issue is resolved.
252- func (c * FileAppender ) rotate () {
297+ func (c * RollingFileAppender ) rotate () {
253298 now := time .Now ()
254299 nowTime := c .RollingPolicy .Time (now )
255300 if nowTime <= c .currTime .Load () {
@@ -285,15 +330,15 @@ func (c *FileAppender) rotate() {
285330 go c .clearExpiredFiles ()
286331}
287332
288- func (c * FileAppender ) ConcurrentSafe () bool { return true }
333+ func (c * RollingFileAppender ) ConcurrentSafe () bool { return true }
289334
290- func (c * FileAppender ) Append (e * Event ) {
335+ func (c * RollingFileAppender ) Append (e * Event ) {
291336 panic (util .ErrForbiddenMethod )
292337}
293338
294339// createFile creates or opens the current log file for appending.
295340// The application is responsible for ensuring the directory exists.
296- func (c * FileAppender ) createFile (t time.Time ) (string , * os.File , error ) {
341+ func (c * RollingFileAppender ) createFile (t time.Time ) (string , * os.File , error ) {
297342 fileName := c .FileName + "." + c .RollingPolicy .Format (t )
298343 filePath := filepath .Join (c .FileDir , fileName )
299344 const fileFlag = os .O_CREATE | os .O_WRONLY | os .O_APPEND
@@ -305,7 +350,7 @@ func (c *FileAppender) createFile(t time.Time) (string, *os.File, error) {
305350}
306351
307352// clearExpiredFiles removes expired log files.
308- func (c * FileAppender ) clearExpiredFiles () {
353+ func (c * RollingFileAppender ) clearExpiredFiles () {
309354 expiration := time .Now ().Add (- time .Duration (c .ClearHours ) * time .Hour )
310355 entries , _ := os .ReadDir (c .FileDir )
311356 for _ , entry := range entries {
0 commit comments