From 406a3fc443bddefbd7ea56d37355cd53f7c492da Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Sat, 6 Jun 2020 15:27:39 +0800 Subject: [PATCH 01/18] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=81=94=E7=B3=BB?= =?UTF-8?q?=E9=82=AE=E7=AE=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _examples/basic.go | 2 +- _examples/benchmarks_test.go | 2 +- _examples/config_file.go | 2 +- _examples/handler.go | 2 +- _examples/level_and_disable.go | 2 +- _examples/log_to_file.go | 2 +- _examples/logger.go | 2 +- _examples/writer.go | 2 +- config.go | 2 +- config_test.go | 2 +- doc.go | 2 +- encoder.go | 2 +- encoder_test.go | 2 +- handler.go | 2 +- handler_extension.go | 2 +- handler_extension_test.go | 2 +- level.go | 2 +- level_based_handler.go | 2 +- level_based_handler_test.go | 2 +- level_shielded_handler.go | 2 +- level_shielded_handler_test.go | 2 +- log.go | 2 +- logger.go | 2 +- logger_test.go | 2 +- logit.go | 2 +- writer/doc.go | 2 +- writer/duration_rolling_file.go | 2 +- writer/duration_rolling_file_test.go | 2 +- writer/size_rolling_file.go | 2 +- writer/size_rolling_file_test.go | 2 +- writer/writer.go | 2 +- 31 files changed, 31 insertions(+), 31 deletions(-) diff --git a/_examples/basic.go b/_examples/basic.go index fd7b221..30b7243 100644 --- a/_examples/basic.go +++ b/_examples/basic.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/02/29 21:59:13 package main diff --git a/_examples/benchmarks_test.go b/_examples/benchmarks_test.go index ee81f0b..1d2b6b6 100644 --- a/_examples/benchmarks_test.go +++ b/_examples/benchmarks_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/02 20:51:29 package main diff --git a/_examples/config_file.go b/_examples/config_file.go index 41507e6..f56e1c9 100644 --- a/_examples/config_file.go +++ b/_examples/config_file.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/30 14:21:28 package main diff --git a/_examples/handler.go b/_examples/handler.go index 7977e16..081e054 100644 --- a/_examples/handler.go +++ b/_examples/handler.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/06 16:01:00 package main diff --git a/_examples/level_and_disable.go b/_examples/level_and_disable.go index 85c2ee5..3357bc1 100644 --- a/_examples/level_and_disable.go +++ b/_examples/level_and_disable.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/01 15:10:19 package main diff --git a/_examples/log_to_file.go b/_examples/log_to_file.go index d8fb38d..d87f76a 100644 --- a/_examples/log_to_file.go +++ b/_examples/log_to_file.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/03 23:39:39 package main diff --git a/_examples/logger.go b/_examples/logger.go index ef40a68..8ee1ac9 100644 --- a/_examples/logger.go +++ b/_examples/logger.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/01 14:51:46 package main diff --git a/_examples/writer.go b/_examples/writer.go index e1feb91..da66198 100644 --- a/_examples/writer.go +++ b/_examples/writer.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/05 17:30:21 package main diff --git a/config.go b/config.go index aff0741..66707ad 100644 --- a/config.go +++ b/config.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/25 23:06:59 package logit diff --git a/config_test.go b/config_test.go index 59ab0b2..71b17c1 100644 --- a/config_test.go +++ b/config_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/29 22:59:14 package logit diff --git a/doc.go b/doc.go index 314738a..946287d 100644 --- a/doc.go +++ b/doc.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/02/29 15:41:09 /* diff --git a/encoder.go b/encoder.go index 9dea8d5..8537ea6 100644 --- a/encoder.go +++ b/encoder.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/04/14 21:06:56 package logit diff --git a/encoder_test.go b/encoder_test.go index fb87af3..6ea0106 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/04/24 16:43:49 package logit diff --git a/handler.go b/handler.go index 350ab3b..71f14c2 100644 --- a/handler.go +++ b/handler.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/06 13:36:28 package logit diff --git a/handler_extension.go b/handler_extension.go index 295d920..872c04f 100644 --- a/handler_extension.go +++ b/handler_extension.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/04/24 23:37:56 package logit diff --git a/handler_extension_test.go b/handler_extension_test.go index 9954671..20cf991 100644 --- a/handler_extension_test.go +++ b/handler_extension_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/03 22:48:34 package logit diff --git a/level.go b/level.go index 2dc0e6d..1ebba3a 100644 --- a/level.go +++ b/level.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/01 14:18:33 package logit diff --git a/level_based_handler.go b/level_based_handler.go index aa7b449..defab8b 100644 --- a/level_based_handler.go +++ b/level_based_handler.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/04/27 22:44:04 package logit diff --git a/level_based_handler_test.go b/level_based_handler_test.go index 3ae8bdc..64e003c 100644 --- a/level_based_handler_test.go +++ b/level_based_handler_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/04/27 23:40:12 package logit diff --git a/level_shielded_handler.go b/level_shielded_handler.go index 1f59e8f..8887bf5 100644 --- a/level_shielded_handler.go +++ b/level_shielded_handler.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/05/27 20:50:41 package logit diff --git a/level_shielded_handler_test.go b/level_shielded_handler_test.go index 2a24bb9..c6db0c0 100644 --- a/level_shielded_handler_test.go +++ b/level_shielded_handler_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/05/27 21:10:13 package logit diff --git a/log.go b/log.go index 54ebc9b..9075d63 100644 --- a/log.go +++ b/log.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/25 22:01:26 package logit diff --git a/logger.go b/logger.go index 0ec3df4..a9787fe 100644 --- a/logger.go +++ b/logger.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/02/29 15:39:02 package logit diff --git a/logger_test.go b/logger_test.go index bb9d1ab..45f91c8 100644 --- a/logger_test.go +++ b/logger_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/02/29 16:41:41 package logit diff --git a/logit.go b/logit.go index b5fda03..9d0b6c6 100644 --- a/logit.go +++ b/logit.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/02/29 22:55:31 package logit diff --git a/writer/doc.go b/writer/doc.go index d834691..970793c 100644 --- a/writer/doc.go +++ b/writer/doc.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/03 14:59:08 /* diff --git a/writer/duration_rolling_file.go b/writer/duration_rolling_file.go index 8f27919..6d55160 100644 --- a/writer/duration_rolling_file.go +++ b/writer/duration_rolling_file.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/03 14:58:21 package writer diff --git a/writer/duration_rolling_file_test.go b/writer/duration_rolling_file_test.go index 8d9ee1d..775cbd3 100644 --- a/writer/duration_rolling_file_test.go +++ b/writer/duration_rolling_file_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/03 16:01:38 package writer diff --git a/writer/size_rolling_file.go b/writer/size_rolling_file.go index 6138d9d..b51c498 100644 --- a/writer/size_rolling_file.go +++ b/writer/size_rolling_file.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/05 00:11:50 package writer diff --git a/writer/size_rolling_file_test.go b/writer/size_rolling_file_test.go index 25cdd80..216dde5 100644 --- a/writer/size_rolling_file_test.go +++ b/writer/size_rolling_file_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/05 16:10:31 package writer diff --git a/writer/writer.go b/writer/writer.go index aa178a9..f8d1a72 100644 --- a/writer/writer.go +++ b/writer/writer.go @@ -13,7 +13,7 @@ // limitations under the License. // // Author: FishGoddess -// Email: fishinlove@163.com +// Email: fishgoddess@qq.com // Created at 2020/03/03 15:10:45 package writer From cb7a1504de2a8470da1040883a559efd3285bd54 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Mon, 8 Jun 2020 17:57:11 +0800 Subject: [PATCH 02/18] =?UTF-8?q?=E5=8F=AF=E5=8F=98=E9=95=BF=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E5=88=97=E8=A1=A8=E7=9A=84=E7=89=B9=E6=80=A7=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E5=AE=8C=E6=88=90=EF=BC=8C=E5=BE=85=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=92=8C=E6=80=A7=E8=83=BD=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FUTURE.md | 10 ++++++++- logger.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/FUTURE.md b/FUTURE.md index 4d8385d..7053ee7 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -1,7 +1,15 @@ ## ✒ 未来版本的新特性 (Features in future version) +### v0.2.7 +* 加入日志存活个数的特性 + +### v0.2.6 +* 加入日志存活天数的特性 + ### v0.2.5 -* 修复配置文件中出现转义字符导致解析出错的问题 +* 加入之前被移除的特性 - 可变长参数列表的日志输出支持,主要可以使用格式化字符串进行多参数传递 +* ~~修复配置文件中出现转义字符导致解析出错的问题~~ + > 取消这个特性是因为,配置文件是用户写的,如果存在转义字符的问题,用户自行做转义会更适合一点 ### v0.2.4 * 新增屏蔽某个日志级别的日志处理器 diff --git a/logger.go b/logger.go index a9787fe..513eda9 100644 --- a/logger.go +++ b/logger.go @@ -19,6 +19,7 @@ package logit import ( + "fmt" "io" "os" "runtime" @@ -345,3 +346,64 @@ func (l *Logger) WarnFunc(msgGenerator func() string) { func (l *Logger) ErrorFunc(msgGenerator func() string) { l.log(callDepth, ErrorLevel, msgGenerator()) } + +// generateMessage generates a message from format and params. +func generateMessage(format string, params ...interface{}) string { + return fmt.Sprintf(format, params...) +} + +// Debugf will output msg as a debug message. +// The msg is the return value of generateMessage. +// This is a way to output a long log made from many variables. +// The msgFormat is the same as format in fmt.Printf, so you can use +// all format it supports, such as '%d'. +// msgParams is the params msgFormat needs, and it is variable-length, so +// you can add all your params here. +// You should know that this way to output msg is the most expensive way in time, +// but it's still faster than other logging libs. If you care about performance, +// than you should think about it, and if you don't, just use it without thinking. +func (l *Logger) Debugf(msgFormat string, msgParams ...interface{}) { + l.log(callDepth, DebugLevel, generateMessage(msgFormat, msgParams)) +} + +// Infof will output msg as an info message. +// The msg is the return value of generateMessage. +// This is a way to output a long log made from many variables. +// The msgFormat is the same as format in fmt.Printf, so you can use +// all format it supports, such as '%d'. +// msgParams is the params msgFormat needs, and it is variable-length, so +// you can add all your params here. +// You should know that this way to output msg is the most expensive way in time, +// but it's still faster than other logging libs. If you care about performance, +// than you should think about it, and if you don't, just use it without thinking. +func (l *Logger) Infof(msgFormat string, msgParams ...interface{}) { + l.log(callDepth, InfoLevel, generateMessage(msgFormat, msgParams)) +} + +// Warnf will output msg as a warn message. +// The msg is the return value of generateMessage. +// This is a way to output a long log made from many variables. +// The msgFormat is the same as format in fmt.Printf, so you can use +// all format it supports, such as '%d'. +// msgParams is the params msgFormat needs, and it is variable-length, so +// you can add all your params here. +// You should know that this way to output msg is the most expensive way in time, +// but it's still faster than other logging libs. If you care about performance, +// than you should think about it, and if you don't, just use it without thinking. +func (l *Logger) Warnf(msgFormat string, msgParams ...interface{}) { + l.log(callDepth, WarnLevel, generateMessage(msgFormat, msgParams)) +} + +// Errorf will output msg as an error message. +// The msg is the return value of generateMessage. +// This is the better way to output a long log made from many variables. +// The msgFormat is the same as format in fmt.Printf, so you can use +// all format it supports, such as '%d'. +// msgParams is the params msgFormat needs, and it is variable-length, so +// you can add all your params here. +// You should know that this way to output msg is the most expensive way in time, +// but it's still faster than other logging libs. If you care about performance, +// than you should think about it, and if you don't, just use it without thinking. +func (l *Logger) Errorf(msgFormat string, msgParams ...interface{}) { + l.log(callDepth, ErrorLevel, generateMessage(msgFormat, msgParams)) +} From 740e5a1ce083e2ffd5ea86fe6c4d7cd57350bbeb Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Mon, 8 Jun 2020 23:59:44 +0800 Subject: [PATCH 03/18] =?UTF-8?q?=E5=8F=AF=E5=8F=98=E9=95=BF=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E5=88=97=E8=A1=A8=E7=89=B9=E6=80=A7=E5=AE=8C=E6=88=90?= =?UTF-8?q?=EF=BC=8C=E5=88=9D=E6=AD=A5=E4=BD=BF=E7=94=A8=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FUTURE.md | 2 +- HISTORY.md | 4 ++ README.en.md | 38 +++++++++---- README.md | 37 ++++++++---- _examples/basic.go | 6 ++ _examples/config/logit-config-template.conf | 2 +- .../config/logit-config-template.en.conf | 2 +- doc.go | 8 ++- logger.go | 8 +-- logger_test.go | 10 ++++ logit.go | 56 +++++++++++++++++++ 11 files changed, 141 insertions(+), 32 deletions(-) diff --git a/FUTURE.md b/FUTURE.md index 7053ee7..0c700e8 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -6,7 +6,7 @@ ### v0.2.6 * 加入日志存活天数的特性 -### v0.2.5 +### v0.2.5-alpha * 加入之前被移除的特性 - 可变长参数列表的日志输出支持,主要可以使用格式化字符串进行多参数传递 * ~~修复配置文件中出现转义字符导致解析出错的问题~~ > 取消这个特性是因为,配置文件是用户写的,如果存在转义字符的问题,用户自行做转义会更适合一点 diff --git a/HISTORY.md b/HISTORY.md index 01b3bbf..07bd76e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ ## ✒ 历史版本的特性介绍 (Features in old version) +### v0.2.5-alpha +* 加入之前被移除的特性 - 可变长参数列表的日志输出支持,主要可以使用格式化字符串进行多参数传递 +> 此版本发布于 2020-06-08 + ### v0.2.4 > 此版本发布于 2020-05-27 * 新增屏蔽某个日志级别的日志处理器 diff --git a/README.en.md b/README.en.md index ce8500e..fd09dc3 100644 --- a/README.en.md +++ b/README.en.md @@ -46,7 +46,7 @@ module your_project_name go 1.14 require ( - github.com/FishGoddess/logit v0.2.4 + github.com/FishGoddess/logit v0.2.5-alpha ) ``` @@ -62,15 +62,15 @@ logit has no more external dependencies. package main import ( - "math/rand" - "strconv" - "time" - - "github.com/FishGoddess/logit" + "math/rand" + "strconv" + "time" + + "github.com/FishGoddess/logit" ) func main() { - + // Log messages with four levels. logit.Debug("I am a debug message!") logit.Info("I am an info message!") @@ -92,6 +92,12 @@ func main() { return "debug rand int: " + strconv.Itoa(r.Intn(100)) }) + // Or you can use formatting method like this: + logit.Debugf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Infof("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Warnf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Errorf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + // If a config file "logit.conf" in "./", then logit will load it automatically. // This is more convenience to use config file and logger. } @@ -119,12 +125,12 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=10s > Benchmark file:[_examples/benchmarks_test.go](./_examples/benchmarks_test.go) -| test case | times ran (large is better) | ns/op (small is better) | features | extension | +| test case | times ran (large is better) | ns/op (small is better) | B/op | allocs/op | | -----------|--------|-------------|-------------|-------------| -| **logit** | **6429907** | **1855 ns/op** | powerful | high | -| golog | 3361483 | 3589 ns/op | easy | common | -| zap | 2971119 | 4066 ns/op | complex | normal | -| logrus | 1553419 | 7869 ns/op | normal | normal | +| **logit** | **6429907** | **1855 ns/op** | 384 B/op | 8 allocs/op | +| golog | 3361483 | 3589 ns/op | 712 B/op | 24 allocs/op | +| zap | 2971119 | 4066 ns/op | 448 B/op | 16 allocs/op | +| logrus | 1553419 | 7869 ns/op | 1633 B/op | 52 allocs/op | > Environment:I7-6700HQ CPU @ 2.6 GHZ, 16 GB RAM @@ -143,6 +149,14 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=10s **reduce the times of time format. However, is it worth to replace time format operation with concurrent competition?** **The answer is no, so we cancel this mechanism in v0.1.1-alpha and higher versions.** +**4. You should know that some APIs like Debugf can't reach high performance as the same as others because of reflection, ** +**however, their performances are not as bad as we think: ** + +| test case | times ran (large is better) | ns/op (small is better) | B/op | allocs/op | +| -----------|--------|-------------|-------------|-------------| +| logit | **6429907** | **1855 ns/op** | 384 B/op | 8 allocs/op | +| **logit-reflection ** | **5288931** | **2334 ns/op** | 424 B/op | 12 allocs/op | + ### 👥 Contributing If you find that something is not working as expected please open an _**issue**_. diff --git a/README.md b/README.md index d1637ec..4a01509 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ module your_project_name go 1.14 require ( - github.com/FishGoddess/logit v0.2.4 + github.com/FishGoddess/logit v0.2.5-alpha ) ``` @@ -63,15 +63,15 @@ logit 没有任何其他额外的依赖,纯使用 [Golang 标准库](https://g package main import ( - "math/rand" - "strconv" - "time" - - "github.com/FishGoddess/logit" + "math/rand" + "strconv" + "time" + + "github.com/FishGoddess/logit" ) func main() { - + // Log messages with four levels. logit.Debug("I am a debug message!") logit.Info("I am an info message!") @@ -93,6 +93,12 @@ func main() { return "debug rand int: " + strconv.Itoa(r.Intn(100)) }) + // Or you can use formatting method like this: + logit.Debugf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Infof("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Warnf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Errorf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + // If a config file "logit.conf" in "./", then logit will load it automatically. // This is more convenience to use config file and logger. } @@ -120,12 +126,12 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=10s > 测试文件:[_examples/benchmarks_test.go](./_examples/benchmarks_test.go) -| 测试 | 单位时间内运行次数 (越大越好) | 每个操作消耗时间 (越小越好) | 功能性 | 扩展性 | +| 测试 | 单位时间内运行次数 (越大越好) | 每个操作消耗时间 (越小越好) | B/op (越小越好) | allocs/op (越小越好) | | -----------|--------|-------------|-------------|-------------| -| **logit** | **6429907** | **1855 ns/op** | 强大 | 高 | -| golog | 3361483 | 3589 ns/op | 简单 | 一般 | -| zap | 2971119 | 4066 ns/op | 复杂 | 正常 | -| logrus | 1553419 | 7869 ns/op | 正常 | 正常 | +| **logit** | **6429907** | **1855 ns/op** | 384 B/op | 8 allocs/op | +| golog | 3361483 | 3589 ns/op | 712 B/op | 24 allocs/op | +| zap | 2971119 | 4066 ns/op | 448 B/op | 16 allocs/op | +| logrus | 1553419 | 7869 ns/op | 1633 B/op | 52 allocs/op | > 测试环境:I7-6700HQ CPU @ 2.6 GHZ,16 GB RAM @@ -144,6 +150,13 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=10s **目前存在一个疑惑就是使用并发竞争去换取时间格式化的性能消耗究竟值不值得?** **答案是不值得,我们在 v0.1.1-alpha 及更高版本中取消了这个时间缓存机制。** +**4. 值得注意的是,Debugf 一类带格式化的 API 性能达不到这个水平,因为还是使用了反射技术,但是性能依旧是不差的:** + +| 测试 | 单位时间内运行次数 (越大越好) | 每个操作消耗时间 (越小越好) | B/op (越小越好) | allocs/op (越小越好) | +| -----------|--------|-------------|-------------|-------------| +| logit | **6429907** | **1855 ns/op** | 384 B/op | 8 allocs/op | +| **logit-使用反射技术 ** | **5288931** | **2334 ns/op** | 424 B/op | 12 allocs/op | + ### 👥 贡献者 如果您觉得 logit 缺少您需要的功能,请不要犹豫,马上参与进来,发起一个 _**issue**_。 diff --git a/_examples/basic.go b/_examples/basic.go index 30b7243..74b5947 100644 --- a/_examples/basic.go +++ b/_examples/basic.go @@ -48,6 +48,12 @@ func main() { return "debug rand int: " + strconv.Itoa(r.Intn(100)) }) + // Or you can use formatting method like this: + logit.Debugf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Infof("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Warnf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Errorf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + // If a config file "logit.conf" in "./", then logit will load it automatically. // This is more convenience to use config file and logger. } diff --git a/_examples/config/logit-config-template.conf b/_examples/config/logit-config-template.conf index cde9d76..5cedf25 100644 --- a/_examples/config/logit-config-template.conf +++ b/_examples/config/logit-config-template.conf @@ -1,4 +1,4 @@ -# logit 配置文件的模板 v0.2.4 +# logit 配置文件的模板 v0.2.5-alpha # 注意:以 # 开头的是注释,不参与配置文件的解析,注释必须是单独的一行,不能写在属性的后面 # 语法是基于 Json 并作了一些改动使其更适合做配置文件,所以要注意双引号和逗号这些格式 # 下面所有涉及目录路径的都使用 / 或者 \\ 而不能是 \,否则会造成配置解析出错,这意味着特殊字符需要自行转义 diff --git a/_examples/config/logit-config-template.en.conf b/_examples/config/logit-config-template.en.conf index b529f32..2424708 100644 --- a/_examples/config/logit-config-template.en.conf +++ b/_examples/config/logit-config-template.en.conf @@ -1,4 +1,4 @@ -# logit config template v0.2.4 +# logit config template v0.2.5-alpha # Notice that starting with # is comment, and it must be a new line # Grammar is based on Json, but adds more features to let it become more configured and easy-to-read # You should always use / or \\ instead of \, because some special characters should be escaped diff --git a/doc.go b/doc.go index 946287d..6525f5b 100644 --- a/doc.go +++ b/doc.go @@ -42,6 +42,12 @@ Package logit provides an easy way to use foundation for your logging operations return "debug rand int: " + strconv.Itoa(r.Intn(100)) }) + // Or you can use formatting method like this: + logit.Debugf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Infof("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Warnf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + logit.Errorf("This is a debug msg with %d params: %s, %s", 2, "msgFormat", "msgParams") + // If a config file "logit.conf" in "./", then logit will load it automatically. // This is more convenience to use config file and logger. @@ -201,5 +207,5 @@ package logit // import "github.com/FishGoddess/logit" const ( // Version is the version string representation of logit. - Version = "v0.2.4" + Version = "v0.2.5-alpha" ) diff --git a/logger.go b/logger.go index 513eda9..2b038a5 100644 --- a/logger.go +++ b/logger.go @@ -363,7 +363,7 @@ func generateMessage(format string, params ...interface{}) string { // but it's still faster than other logging libs. If you care about performance, // than you should think about it, and if you don't, just use it without thinking. func (l *Logger) Debugf(msgFormat string, msgParams ...interface{}) { - l.log(callDepth, DebugLevel, generateMessage(msgFormat, msgParams)) + l.log(callDepth, DebugLevel, generateMessage(msgFormat, msgParams...)) } // Infof will output msg as an info message. @@ -377,7 +377,7 @@ func (l *Logger) Debugf(msgFormat string, msgParams ...interface{}) { // but it's still faster than other logging libs. If you care about performance, // than you should think about it, and if you don't, just use it without thinking. func (l *Logger) Infof(msgFormat string, msgParams ...interface{}) { - l.log(callDepth, InfoLevel, generateMessage(msgFormat, msgParams)) + l.log(callDepth, InfoLevel, generateMessage(msgFormat, msgParams...)) } // Warnf will output msg as a warn message. @@ -391,7 +391,7 @@ func (l *Logger) Infof(msgFormat string, msgParams ...interface{}) { // but it's still faster than other logging libs. If you care about performance, // than you should think about it, and if you don't, just use it without thinking. func (l *Logger) Warnf(msgFormat string, msgParams ...interface{}) { - l.log(callDepth, WarnLevel, generateMessage(msgFormat, msgParams)) + l.log(callDepth, WarnLevel, generateMessage(msgFormat, msgParams...)) } // Errorf will output msg as an error message. @@ -405,5 +405,5 @@ func (l *Logger) Warnf(msgFormat string, msgParams ...interface{}) { // but it's still faster than other logging libs. If you care about performance, // than you should think about it, and if you don't, just use it without thinking. func (l *Logger) Errorf(msgFormat string, msgParams ...interface{}) { - l.log(callDepth, ErrorLevel, generateMessage(msgFormat, msgParams)) + l.log(callDepth, ErrorLevel, generateMessage(msgFormat, msgParams...)) } diff --git a/logger_test.go b/logger_test.go index 45f91c8..15ff109 100644 --- a/logger_test.go +++ b/logger_test.go @@ -232,3 +232,13 @@ func TestLoggerLogFunction(t *testing.T) { // test escaping logger.Info(`test "double quotes"\t\b \u0003 \u0019 !!!!`) } + +// 测试带格式化的日志输出方法 +func TestLoggerOutputWithFormat(t *testing.T) { + logger := NewLogger(DebugLevel, NewStandardHandler(os.Stdout, TextEncoder(), DefaultTimeFormat)) + logger.EnableFileInfo() + logger.Debugf("Debugf... %d %s %.3f", 123, "幸福呢", 123.123456) + logger.Infof("Infof... %d %s %.3f", 123, "幸福呢", 123.123456) + logger.Warnf("Warnf... %d %s %.3f", 123, "幸福呢", 123.123456) + logger.Errorf("Errorf... %d %s %.3f", 123, "幸福呢", 123.123456) +} diff --git a/logit.go b/logit.go index 9d0b6c6..20f08a1 100644 --- a/logit.go +++ b/logit.go @@ -106,3 +106,59 @@ func WarnFunc(msgGenerator func() string) { func ErrorFunc(msgGenerator func() string) { globalLogger.log(callDepthOfGlobalLogger, ErrorLevel, msgGenerator()) } + +// Debugf will output msg as a debug message. +// The msg is the return value of generateMessage. +// This is a way to output a long log made from many variables. +// The msgFormat is the same as format in fmt.Printf, so you can use +// all format it supports, such as '%d'. +// msgParams is the params msgFormat needs, and it is variable-length, so +// you can add all your params here. +// You should know that this way to output msg is the most expensive way in time, +// but it's still faster than other logging libs. If you care about performance, +// than you should think about it, and if you don't, just use it without thinking. +func Debugf(msgFormat string, msgParams ...interface{}) { + globalLogger.log(callDepth, DebugLevel, generateMessage(msgFormat, msgParams...)) +} + +// Infof will output msg as an info message. +// The msg is the return value of generateMessage. +// This is a way to output a long log made from many variables. +// The msgFormat is the same as format in fmt.Printf, so you can use +// all format it supports, such as '%d'. +// msgParams is the params msgFormat needs, and it is variable-length, so +// you can add all your params here. +// You should know that this way to output msg is the most expensive way in time, +// but it's still faster than other logging libs. If you care about performance, +// than you should think about it, and if you don't, just use it without thinking. +func Infof(msgFormat string, msgParams ...interface{}) { + globalLogger.log(callDepth, InfoLevel, generateMessage(msgFormat, msgParams...)) +} + +// Warnf will output msg as a warn message. +// The msg is the return value of generateMessage. +// This is a way to output a long log made from many variables. +// The msgFormat is the same as format in fmt.Printf, so you can use +// all format it supports, such as '%d'. +// msgParams is the params msgFormat needs, and it is variable-length, so +// you can add all your params here. +// You should know that this way to output msg is the most expensive way in time, +// but it's still faster than other logging libs. If you care about performance, +// than you should think about it, and if you don't, just use it without thinking. +func Warnf(msgFormat string, msgParams ...interface{}) { + globalLogger.log(callDepth, WarnLevel, generateMessage(msgFormat, msgParams...)) +} + +// Errorf will output msg as an error message. +// The msg is the return value of generateMessage. +// This is the better way to output a long log made from many variables. +// The msgFormat is the same as format in fmt.Printf, so you can use +// all format it supports, such as '%d'. +// msgParams is the params msgFormat needs, and it is variable-length, so +// you can add all your params here. +// You should know that this way to output msg is the most expensive way in time, +// but it's still faster than other logging libs. If you care about performance, +// than you should think about it, and if you don't, just use it without thinking. +func Errorf(msgFormat string, msgParams ...interface{}) { + globalLogger.log(callDepth, ErrorLevel, generateMessage(msgFormat, msgParams...)) +} From 20ec3380c6d9a66bd4bb3e0cae55dc2df8a802c9 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Tue, 9 Jun 2020 00:06:59 +0800 Subject: [PATCH 04/18] =?UTF-8?q?v0.2.5-alpha=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E9=A2=84=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 07bd76e..c2c0153 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,8 +1,8 @@ ## ✒ 历史版本的特性介绍 (Features in old version) ### v0.2.5-alpha -* 加入之前被移除的特性 - 可变长参数列表的日志输出支持,主要可以使用格式化字符串进行多参数传递 > 此版本发布于 2020-06-08 +* 加入之前被移除的特性 - 可变长参数列表的日志输出支持,主要可以使用格式化字符串进行多参数传递 ### v0.2.4 > 此版本发布于 2020-05-27 From e184d2504085b22f3d4e7e03122e494fae2ade22 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Tue, 9 Jun 2020 00:24:08 +0800 Subject: [PATCH 05/18] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20readme=20=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E6=B5=8B=E8=AF=95=E7=BB=93=E6=9E=9C=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.en.md | 6 +++--- README.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.en.md b/README.en.md index fd09dc3..c757cd4 100644 --- a/README.en.md +++ b/README.en.md @@ -127,7 +127,7 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=10s | test case | times ran (large is better) | ns/op (small is better) | B/op | allocs/op | | -----------|--------|-------------|-------------|-------------| -| **logit** | **6429907** | **1855 ns/op** | 384 B/op | 8 allocs/op | +| **logit** | **6429907** | **1855 ns/op** | **384 B/op** | **8 allocs/op** | | golog | 3361483 | 3589 ns/op | 712 B/op | 24 allocs/op | | zap | 2971119 | 4066 ns/op | 448 B/op | 16 allocs/op | | logrus | 1553419 | 7869 ns/op | 1633 B/op | 52 allocs/op | @@ -154,8 +154,8 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=10s | test case | times ran (large is better) | ns/op (small is better) | B/op | allocs/op | | -----------|--------|-------------|-------------|-------------| -| logit | **6429907** | **1855 ns/op** | 384 B/op | 8 allocs/op | -| **logit-reflection ** | **5288931** | **2334 ns/op** | 424 B/op | 12 allocs/op | +| logit | 6429907 | 1855 ns/op | 384 B/op | 8 allocs/op | +| **logit-reflection** | **5288931** | **2334 ns/op** | **424 B/op** | **12 allocs/op** | ### 👥 Contributing diff --git a/README.md b/README.md index 4a01509..69eed18 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=10s | 测试 | 单位时间内运行次数 (越大越好) | 每个操作消耗时间 (越小越好) | B/op (越小越好) | allocs/op (越小越好) | | -----------|--------|-------------|-------------|-------------| -| **logit** | **6429907** | **1855 ns/op** | 384 B/op | 8 allocs/op | +| **logit** | **6429907** | **1855 ns/op** | **384 B/op** | **8 allocs/op** | | golog | 3361483 | 3589 ns/op | 712 B/op | 24 allocs/op | | zap | 2971119 | 4066 ns/op | 448 B/op | 16 allocs/op | | logrus | 1553419 | 7869 ns/op | 1633 B/op | 52 allocs/op | @@ -154,8 +154,8 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=10s | 测试 | 单位时间内运行次数 (越大越好) | 每个操作消耗时间 (越小越好) | B/op (越小越好) | allocs/op (越小越好) | | -----------|--------|-------------|-------------|-------------| -| logit | **6429907** | **1855 ns/op** | 384 B/op | 8 allocs/op | -| **logit-使用反射技术 ** | **5288931** | **2334 ns/op** | 424 B/op | 12 allocs/op | +| logit | 6429907 | 1855 ns/op | 384 B/op | 8 allocs/op | +| **logit-使用反射技术** | **5288931** | **2334 ns/op** | **424 B/op** | **12 allocs/op** | ### 👥 贡献者 From 9fde45be7ed66e9640281242a94f981c1e448514 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Tue, 9 Jun 2020 00:27:12 +0800 Subject: [PATCH 06/18] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20readme=20=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E6=B5=8B=E8=AF=95=E7=BB=93=E6=9E=9C=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.en.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.en.md b/README.en.md index c757cd4..6f87142 100644 --- a/README.en.md +++ b/README.en.md @@ -149,8 +149,8 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=10s **reduce the times of time format. However, is it worth to replace time format operation with concurrent competition?** **The answer is no, so we cancel this mechanism in v0.1.1-alpha and higher versions.** -**4. You should know that some APIs like Debugf can't reach high performance as the same as others because of reflection, ** -**however, their performances are not as bad as we think: ** +**4. You should know that some APIs like Debugf can't reach high performance as the same as others because of reflection,** +**however, their performances are not as bad as we think:** | test case | times ran (large is better) | ns/op (small is better) | B/op | allocs/op | | -----------|--------|-------------|-------------|-------------| From 25477ab488196da9743c36b5f1c8d0c73e1584a6 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Sat, 13 Jun 2020 02:10:43 +0800 Subject: [PATCH 07/18] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=AD=98=E6=B4=BB=E5=A4=A9=E6=95=B0=E7=9A=84?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=92=8C=E7=AE=80=E5=8D=95=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- writer/name_generator.go | 42 ++++++++++++++++++++ writer/rolling_hook.go | 75 ++++++++++++++++++++++++++++++++++++ writer/rolling_hook_test.go | 77 +++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 writer/name_generator.go create mode 100644 writer/rolling_hook.go create mode 100644 writer/rolling_hook_test.go diff --git a/writer/name_generator.go b/writer/name_generator.go new file mode 100644 index 0000000..4727530 --- /dev/null +++ b/writer/name_generator.go @@ -0,0 +1,42 @@ +// Copyright 2020 Ye Zi Jie. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: FishGoddess +// Email: fishgoddess@qq.com +// Created at 2020/06/11 21:15:37 + +package writer + +import ( + "math/rand" + "path/filepath" + "strconv" + "time" +) + +type NameGenerator func(string, time.Time) string + +func (ng NameGenerator) NextName(directory string, now time.Time) string { + return ng(directory, now) +} + +func DefaultNameGenerator() func(directory string, now time.Time) string { + // TODO 考虑使用原子计数器替换随机数 + // 这个 Seed 方法最好不要并发执行,但是这个方法有可能会被并发执行,这是个隐患 + rand.Seed(time.Now().Unix()) + return func(directory string, now time.Time) string { + name := now.Format("20060102-150405") + "-" + strconv.Itoa(rand.Intn(1000)) + SuffixOfLogFile + return filepath.Join(directory, name) + } +} diff --git a/writer/rolling_hook.go b/writer/rolling_hook.go new file mode 100644 index 0000000..58c2aa3 --- /dev/null +++ b/writer/rolling_hook.go @@ -0,0 +1,75 @@ +// Copyright 2020 Ye Zi Jie. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: FishGoddess +// Email: fishgoddess@qq.com +// Created at 2020/06/12 22:08:31 + +package writer + +import ( + "io/ioutil" + "os" + "path/filepath" + "time" +) + +type RollingHook interface { + BeforeRolling() + AfterRolling() +} + +// ============================= default rolling hook ============================= +type DefaultRollingHook struct{} + +func (drh *DefaultRollingHook) BeforeRolling() { + // Do nothing... +} + +func (drh *DefaultRollingHook) AfterRolling() { + // Do nothing... +} + +// ============================= life rolling hook ============================= + +type LifeBasedRollingHook struct { + *DefaultRollingHook + + life time.Duration + + directory string +} + +func NewLifeBasedRollingHook(life time.Duration, directory string) *LifeBasedRollingHook { + return &LifeBasedRollingHook{ + DefaultRollingHook: &DefaultRollingHook{}, + life: life, + directory: directory, + } +} + +func (lrh *LifeBasedRollingHook) AfterRolling() { + + fileInfos, err := ioutil.ReadDir(lrh.directory) + if err != nil { + return + } + + now := time.Now() + for _, file := range fileInfos { + if !file.IsDir() && now.Sub(file.ModTime()) >= lrh.life { + os.Remove(filepath.Join(lrh.directory, file.Name())) + } + } +} diff --git a/writer/rolling_hook_test.go b/writer/rolling_hook_test.go new file mode 100644 index 0000000..7568390 --- /dev/null +++ b/writer/rolling_hook_test.go @@ -0,0 +1,77 @@ +// Copyright 2020 Ye Zi Jie. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: FishGoddess +// Email: fishgoddess@qq.com +// Created at 2020/06/13 00:07:00 + +package writer + +import ( + "io/ioutil" + "testing" + "time" +) + +func TestLifeRollingHookAfterRolling(t *testing.T) { + + // 创建测试文件夹 + directory, err := ioutil.TempDir("", "TestLifeRollingHookAfterRolling_*") + if err != nil { + t.Fatal(err) + } + + t.Log(directory) + + // 创建测试文件 + for i := 0; i < 3; i++ { + file, err := ioutil.TempFile(directory, "test_file_*.log") + if err != nil { + t.Fatal(err) + } + file.Close() + + _, err = ioutil.TempDir(directory, "test_directory_*") + if err != nil { + t.Fatal(err) + } + + time.Sleep(2 * time.Second) + } + + // 判断测试文件是否准备成功 + checkFileCountInDirectory := func(count int) { + fileInfos, err := ioutil.ReadDir(directory) + t.Log("count:", count) + t.Log("len:", len(fileInfos)) + if err != nil || len(fileInfos) != count { + t.Fatal(err, len(fileInfos)) + } + } + + // 创建基于生命周期的滚动钩子器 + rollingHook := NewLifeBasedRollingHook(4*time.Second, directory) + + // 开始测试 + // 判断滚动之前的文件数量是否正确 + checkFileCountInDirectory(6) + for i := 1; i <= 3; i++ { + rollingHook.AfterRolling() + checkFileCountInDirectory(6 - i) + time.Sleep(2 * time.Second) + } + + rollingHook.AfterRolling() + checkFileCountInDirectory(3) +} From 60350f60014f6f0b52d5b2818b234f35ba0490f3 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Sat, 13 Jun 2020 15:57:26 +0800 Subject: [PATCH 08/18] =?UTF-8?q?=E5=AF=B9=20writer=20=E5=8C=85=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E9=87=8D=E6=9E=84=EF=BC=8C=E5=B9=B6=E6=94=B9=E5=90=8D?= =?UTF-8?q?=E4=B8=BA=20files=20=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.en.md | 2 +- README.md | 2 +- _examples/benchmarks_test.go | 10 +- _examples/{writer.go => files.go} | 31 +++-- _examples/log_to_file.go | 16 +-- doc.go | 14 +-- files/doc.go | 61 ++++++++++ {writer => files}/duration_rolling_file.go | 44 ++++--- files/duration_rolling_file_test.go | 123 +++++++++++++++++++ {writer => files}/name_generator.go | 10 +- {writer => files}/rolling_hook.go | 6 +- {writer => files}/rolling_hook_test.go | 2 +- {writer => files}/size_rolling_file.go | 53 +++++++-- files/size_rolling_file_test.go | 131 +++++++++++++++++++++ {writer => files}/writer.go | 34 ++---- handler_extension.go | 16 +-- handler_extension_test.go | 4 +- writer/doc.go | 45 ------- writer/duration_rolling_file_test.go | 68 ----------- writer/size_rolling_file_test.go | 68 ----------- 20 files changed, 461 insertions(+), 279 deletions(-) rename _examples/{writer.go => files.go} (52%) create mode 100644 files/doc.go rename {writer => files}/duration_rolling_file.go (80%) create mode 100644 files/duration_rolling_file_test.go rename {writer => files}/name_generator.go (81%) rename {writer => files}/rolling_hook.go (94%) rename {writer => files}/rolling_hook_test.go (99%) rename {writer => files}/size_rolling_file.go (84%) create mode 100644 files/size_rolling_file_test.go rename {writer => files}/writer.go (60%) delete mode 100644 writer/doc.go delete mode 100644 writer/duration_rolling_file_test.go delete mode 100644 writer/size_rolling_file_test.go diff --git a/README.en.md b/README.en.md index 6f87142..0b66656 100644 --- a/README.en.md +++ b/README.en.md @@ -110,7 +110,7 @@ func main() { * [level_and_disable](./_examples/level_and_disable.go) * [config_file](./_examples/config_file.go) * [handler](./_examples/handler.go) -* [writer](./_examples/writer.go) +* [files](./_examples/files.go) * [log_to_file](./_examples/log_to_file.go) _Check more examples in [_examples](./_examples)._ diff --git a/README.md b/README.md index 69eed18..cc59a94 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ func main() { * [level_and_disable](./_examples/level_and_disable.go) * [config_file](./_examples/config_file.go) * [handler](./_examples/handler.go) -* [writer](./_examples/writer.go) +* [files](./_examples/files.go) * [log_to_file](./_examples/log_to_file.go) _更多使用案例请查看 [_examples](./_examples) 目录。_ diff --git a/_examples/benchmarks_test.go b/_examples/benchmarks_test.go index 1d2b6b6..4831494 100644 --- a/_examples/benchmarks_test.go +++ b/_examples/benchmarks_test.go @@ -23,7 +23,7 @@ import ( //"time" "github.com/FishGoddess/logit" - //"github.com/FishGoddess/logit/writer" + //"github.com/FishGoddess/logit/files" //"github.com/kataras/golog" //"github.com/sirupsen/logrus" //"go.uber.org/zap" @@ -172,7 +172,7 @@ func BenchmarkLogitLogger(b *testing.B) { //// 测试 logit 文件日志记录器的速度 //func BenchmarkLogitFile(b *testing.B) { // -// file, _ := writer.NewFile("D:/BenchmarkLogitFile.log") +// file, _ := files.CreateFileOf("D:/BenchmarkLogitFile.log") // logger := logit.NewLogger(logit.DebugLevel, logit.NewStandardHandler(file, logit.TextEncoder(), timeFormat)) // // // 测试用的日志任务 @@ -195,7 +195,7 @@ func BenchmarkLogitLogger(b *testing.B) { //func BenchmarkGologFile(b *testing.B) { // // logger := golog.New() -// file, _ := writer.NewFile("D:/BenchmarkGologFile.log") +// file, _ := files.CreateFileOf("D:/BenchmarkGologFile.log") // logger.SetOutput(file) // logger.SetLevel("debug") // logger.SetTimeFormat(timeFormat) @@ -226,7 +226,7 @@ func BenchmarkLogitLogger(b *testing.B) { // enc.AppendString(t.Format(timeFormat)) // } // encoder := zapcore.NewConsoleEncoder(config) -// file, _ := writer.NewFile("D:/BenchmarkZapFile.log") +// file, _ := files.CreateFileOf("D:/BenchmarkZapFile.log") // writeSyncer := zapcore.AddSync(file) // core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) // logger := zap.New(core) @@ -253,7 +253,7 @@ func BenchmarkLogitLogger(b *testing.B) { //func BenchmarkLogrusFile(b *testing.B) { // // logger := logrus.New() -// file, _ := writer.NewFile("D:/BenchmarkLogrusFile.log") +// file, _ := files.CreateFileOf("D:/BenchmarkLogrusFile.log") // logger.SetOutput(file) // logger.SetLevel(logrus.DebugLevel) // logger.SetFormatter(&logrus.TextFormatter{ diff --git a/_examples/writer.go b/_examples/files.go similarity index 52% rename from _examples/writer.go rename to _examples/files.go index da66198..9ae4736 100644 --- a/_examples/writer.go +++ b/_examples/files.go @@ -19,30 +19,47 @@ package main import ( + "path/filepath" "time" - "github.com/FishGoddess/logit/writer" + "github.com/FishGoddess/logit/files" ) func main() { // 1. DurationRollingFile is a time sensitive file. - durationRollingFile := writer.NewDurationRollingFile(24*time.Hour, func(now time.Time) string { - return "D:/" + now.Format("20060102-150405") + ".txt" - }) + durationRollingFile := files.NewDurationRollingFile("D:/", 24*time.Hour) defer durationRollingFile.Close() // You can use it like using io.Writer! durationRollingFile.Write([]byte("durationRollingFile!")) + // If you want to change the way generating file name, try this: + durationRollingFile.SetNameGenerator(func(directory string, now time.Time) string { + // directory is the directory stores all file rolled before. + // now is the time calling this method. + return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) + }) + + // What's more, you can add a hook in rolling process, see RollingHook. + //durationRollingFile.SetRollingHook(xxx) + // ================================================================================= // 2. SizeRollingFile is a file size sensitive file. - sizeRollingFile := writer.NewSizeRollingFile(64*writer.KB, func(now time.Time) string { - return "D:/" + now.Format("20060102150405.000") + ".txt" - }) + sizeRollingFile := files.NewSizeRollingFile("D:/", 64*files.KB) defer sizeRollingFile.Close() // You can use it like using io.Writer! sizeRollingFile.Write([]byte("sizeRollingFile!")) + + // If you want to change the way generating file name, try this: + sizeRollingFile.SetNameGenerator(func(directory string, now time.Time) string { + // directory is the directory stores all file rolled before. + // now is the time calling this method. + return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) + }) + + // What's more, you can add a hook in rolling process, see RollingHook. + //sizeRollingFile.SetRollingHook(xxx) } diff --git a/_examples/log_to_file.go b/_examples/log_to_file.go index d87f76a..1e5bd1b 100644 --- a/_examples/log_to_file.go +++ b/_examples/log_to_file.go @@ -22,7 +22,7 @@ import ( "time" "github.com/FishGoddess/logit" - "github.com/FishGoddess/logit/writer" + "github.com/FishGoddess/logit/files" ) func main() { @@ -34,20 +34,20 @@ func main() { // NewDurationRollingLogger creates a duration rolling logger with given duration. // You should appoint a directory to store all log files generated in this time. - // Notice that duration must not less than minDuration (generally time.Second), see writer.minDuration. - // Also, default filename of log file is like "20200304-145246-45.log", see writer.NewFilename. + // Notice that duration must not less than minDuration (generally time.Second), see files.minDuration. + // Also, default filename of log file is like "20200304-145246-45.log", see files.NewFilename. // If you want to appoint another filename, check this and do it by this way. - // See writer.NewDurationRollingFile (it is an implement of io.writer). + // See files.NewDurationRollingFile (it is an implement of io.writer). logger = logit.NewLogger(logit.DebugLevel, logit.NewDurationRollingHandler(24*time.Hour, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) logger.Info("Rolling!!!") // NewSizeRollingLogger creates a file size rolling logger with given limitedSize. // You should appoint a directory to store all log files generated in this time. - // Notice that limitedSize must not less than minLimitedSize (generally 64 KB), see writer.minLimitedSize. - // Check writer.KB, writer.MB, writer.GB to know what unit you gonna to use. + // Notice that limitedSize must not less than minLimitedSize (generally 64 KB), see files.minLimitedSize. + // Check files.KB, files.MB, files.GB to know what unit you gonna to use. // Also, default filename of log file is like "20200304-145246-45.log", see nextFilename. // If you want to appoint another filename, check this and do it by this way. - // See writer.NewSizeRollingFile (it is an implement of io.writer). - logger = logit.NewLogger(logit.DebugLevel, logit.NewSizeRollingHandler(64*writer.KB, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) + // See files.NewSizeRollingFile (it is an implement of io.writer). + logger = logit.NewLogger(logit.DebugLevel, logit.NewSizeRollingHandler(64*files.KB, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) logger.Info("file size???") } diff --git a/doc.go b/doc.go index 6525f5b..0eb1330 100644 --- a/doc.go +++ b/doc.go @@ -116,21 +116,21 @@ Package logit provides an easy way to use foundation for your logging operations // NewDurationRollingLogger creates a duration rolling logger with given duration. // You should appoint a directory to store all log files generated in this time. - // Notice that duration must not less than minDuration (generally time.Second), see writer.minDuration. - // Also, default filename of log file is like "20200304-145246-45.log", see writer.NewFilename. + // Notice that duration must not less than minDuration (generally time.Second), see files.minDuration. + // Also, default filename of log file is like "20200304-145246-45.log", see files.NewFilename. // If you want to appoint another filename, check this and do it by this way. - // See writer.NewDurationRollingFile (it is an implement of io.writer). + // See files.NewDurationRollingFile (it is an implement of io.writer). logger = logit.NewLogger(logit.DebugLevel, logit.NewDurationRollingHandler(24*time.Hour, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) logger.Info("Rolling!!!") // NewSizeRollingLogger creates a file size rolling logger with given limitedSize. // You should appoint a directory to store all log files generated in this time. - // Notice that limitedSize must not less than minLimitedSize (generally 64 KB), see writer.minLimitedSize. - // Check writer.KB, writer.MB, writer.GB to know what unit you gonna to use. + // Notice that limitedSize must not less than minLimitedSize (generally 64 KB), see files.minLimitedSize. + // Check files.KB, files.MB, files.GB to know what unit you gonna to use. // Also, default filename of log file is like "20200304-145246-45.log", see nextFilename. // If you want to appoint another filename, check this and do it by this way. - // See writer.NewSizeRollingFile (it is an implement of io.writer). - logger = logit.NewLogger(logit.DebugLevel, logit.NewSizeRollingHandler(64*writer.KB, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) + // See files.NewSizeRollingFile (it is an implement of io.writer). + logger = logit.NewLogger(logit.DebugLevel, logit.NewSizeRollingHandler(64*files.KB, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) logger.Info("file size???") 5. handler: diff --git a/files/doc.go b/files/doc.go new file mode 100644 index 0000000..1e8a9ad --- /dev/null +++ b/files/doc.go @@ -0,0 +1,61 @@ +// Copyright 2020 Ye Zi Jie. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: FishGoddess +// Email: fishgoddess@qq.com +// Created at 2020/03/03 14:59:08 + +/* +Package files provides some writers to extend your logger. + +1. DurationRollingFile: + + // DurationRollingFile is a time sensitive file. + durationRollingFile := files.NewDurationRollingFile("D:/", 24*time.Hour) + defer durationRollingFile.Close() + + // You can use it like using io.Writer! + durationRollingFile.Write([]byte("durationRollingFile!")) + + // If you want to change the way generating file name, try this: + durationRollingFile.SetNameGenerator(func(directory string, now time.Time) string { + // directory is the directory stores all file rolled before. + // now is the time calling this method. + return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) + }) + + // What's more, you can add a hook in rolling process, see RollingHook. + //durationRollingFile.SetRollingHook(xxx) + +2. SizeRollingFile: + + // SizeRollingFile is a file size sensitive file. + sizeRollingFile := files.NewSizeRollingFile("D:/", 64*files.KB) + defer sizeRollingFile.Close() + + // You can use it like using io.Writer! + sizeRollingFile.Write([]byte("sizeRollingFile!")) + + // If you want to change the way generating file name, try this: + sizeRollingFile.SetNameGenerator(func(directory string, now time.Time) string { + // directory is the directory stores all file rolled before. + // now is the time calling this method. + return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) + }) + + // What's more, you can add a hook in rolling process, see RollingHook. + //sizeRollingFile.SetRollingHook(xxx) + +*/ +package files // import "github.com/FishGoddess/logit/files" diff --git a/writer/duration_rolling_file.go b/files/duration_rolling_file.go similarity index 80% rename from writer/duration_rolling_file.go rename to files/duration_rolling_file.go index 6d55160..55b5a5f 100644 --- a/writer/duration_rolling_file.go +++ b/files/duration_rolling_file.go @@ -16,7 +16,7 @@ // Email: fishgoddess@qq.com // Created at 2020/03/03 14:58:21 -package writer +package files import ( "errors" @@ -39,6 +39,8 @@ type DurationRollingFile struct { // file points the writer which will be used this moment. file *os.File + directory string + // lastTime is the created time of current file above. lastTime time.Time @@ -48,11 +50,9 @@ type DurationRollingFile struct { // larger than minDuration for some safe considerations. See minDuration. duration time.Duration - // nextFilename is a function for generating next file name. - // Every times rolling to next file will call it first. - // now is the time of calling this function, also the - // created time of next file. - nextFilename func(now time.Time) string + nameGenerator NameGenerator + + rollingHook RollingHook // mu is a lock for safe concurrency. mu sync.Mutex @@ -70,21 +70,19 @@ const ( // Every times rolling to next file will call nextFilename first. // now is the created time of next file. Notice that duration's min value // is one second. See minDuration. -func NewDurationRollingFile(duration time.Duration, nextFilename func(now time.Time) string) *DurationRollingFile { +func NewDurationRollingFile(directory string, duration time.Duration) *DurationRollingFile { // 防止时间间隔太小导致滚动文件时 IO 的疯狂蠕动 if duration < minDuration { panic(errors.New("Duration is smaller than " + minDuration.String() + "\n")) } - // 获取当前时间,并生成第一个文件 - file, now := generateFirstFile(nextFilename) return &DurationRollingFile{ - file: file, - lastTime: now, - duration: duration, - nextFilename: nextFilename, - mu: sync.Mutex{}, + directory: directory, + duration: duration, + nameGenerator: DefaultNameGenerator(), + rollingHook: NewDefaultRollingHook(), + mu: sync.Mutex{}, } } @@ -92,7 +90,7 @@ func NewDurationRollingFile(duration time.Duration, nextFilename func(now time.T func (drf *DurationRollingFile) rollingToNextFile(now time.Time) { // 如果创建新文件发生错误,就继续使用当前的文件,等到下一次时间间隔再重试 - newFile, err := NewFile(drf.nextFilename(now)) + newFile, err := CreateFileOf(drf.nameGenerator.NextName(drf.directory, now)) if err != nil { return } @@ -106,8 +104,10 @@ func (drf *DurationRollingFile) rollingToNextFile(now time.Time) { // ensureFileIsCorrect ensures drf is writing to a correct file this moment. func (drf *DurationRollingFile) ensureFileIsCorrect() { now := time.Now() - if now.Sub(drf.lastTime) >= drf.duration { + if drf.file == nil || now.Sub(drf.lastTime) >= drf.duration { + drf.rollingHook.BeforeRolling() drf.rollingToNextFile(now) + drf.rollingHook.AfterRolling() } } @@ -130,3 +130,15 @@ func (drf *DurationRollingFile) Close() error { defer drf.mu.Unlock() return drf.file.Close() } + +func (drf *DurationRollingFile) SetNameGenerator(nameGenerator NameGenerator) { + drf.mu.Lock() + defer drf.mu.Unlock() + drf.nameGenerator = nameGenerator +} + +func (drf *DurationRollingFile) SetRollingHook(rollingHook RollingHook) { + drf.mu.Lock() + defer drf.mu.Unlock() + drf.rollingHook = rollingHook +} diff --git a/files/duration_rolling_file_test.go b/files/duration_rolling_file_test.go new file mode 100644 index 0000000..a553569 --- /dev/null +++ b/files/duration_rolling_file_test.go @@ -0,0 +1,123 @@ +// Copyright 2020 Ye Zi Jie. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: FishGoddess +// Email: fishgoddess@qq.com +// Created at 2020/03/03 16:01:38 + +package files + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" +) + +// 测试时间间隔滚动文件 +func TestNewDurationRollingFile(t *testing.T) { + + defer func() { + if err := recover(); err == nil { + t.Errorf("时间间隔大小限制测试出现问题!") + } + }() + + // 创建临时测试文件夹 + root, err := ioutil.TempDir("", "TestNewDurationRollingFile_") + if err != nil { + t.Fatal(err) + } + + file := NewDurationRollingFile(root, time.Second) + defer file.Close() + + for i := 0; i < 5; i++ { + file.Write([]byte("测试")) + time.Sleep(666 * time.Millisecond) + } + + dir, err := os.Open(root) + if err != nil { + t.Fatal("获取测试文件夹失败!") + } + + fileInfos, err := dir.Readdir(0) + if err != nil { + t.Fatal("获取测试文件夹信息失败!") + } + + // 如果创建的文件数不符合,直接报错 + if len(fileInfos) != 3 { + t.Fatal("文件滚动出现问题!") + } + + file.Close() + file = NewDurationRollingFile("", 999*time.Millisecond) + file.SetNameGenerator(func(directory string, now time.Time) string { + return "" + }) +} + +// 测试名字生成器的设置方法 +func TestDurationRollingFileSetNameGenerator(t *testing.T) { + + dir, err := ioutil.TempDir("", "TestDurationRollingFileSetNameGenerator_") + if err != nil { + t.Fatal(err) + } + + // 创建文件,并写入内容 + file := NewDurationRollingFile(dir, 2*time.Second) + defer file.Close() + file.Write([]byte("hello!")) + + // 更换命名器,等待滚动时间到了之后,再次写入内容 + file.SetNameGenerator(func(directory string, now time.Time) string { + return filepath.Join(directory, now.Format("2006年01月02日的15点04分05秒产生的文件.log")) + }) + time.Sleep(2 * time.Second) + file.Write([]byte("hi!")) +} + +// 测试使用的 rolling hook +type testDurationRollingFileRollingHook struct{} + +func (tdrfrh *testDurationRollingFileRollingHook) BeforeRolling() { + fmt.Println("before rolling...") +} + +func (tdrfrh *testDurationRollingFileRollingHook) AfterRolling() { + fmt.Println("after rolling...") +} + +// 测试名字生成器的设置方法 +func TestDurationRollingFileSetRollingHook(t *testing.T) { + + dir, err := ioutil.TempDir("", "TestDurationRollingFileSetRollingHook_") + if err != nil { + t.Fatal(err) + } + + file := NewDurationRollingFile(dir, 2*time.Second) + defer file.Close() + + file.Write([]byte("hello!")) + file.SetRollingHook(&testDurationRollingFileRollingHook{}) + + time.Sleep(2 * time.Second) + file.Write([]byte("hi!")) +} diff --git a/writer/name_generator.go b/files/name_generator.go similarity index 81% rename from writer/name_generator.go rename to files/name_generator.go index 4727530..6691e18 100644 --- a/writer/name_generator.go +++ b/files/name_generator.go @@ -16,7 +16,7 @@ // Email: fishgoddess@qq.com // Created at 2020/06/11 21:15:37 -package writer +package files import ( "math/rand" @@ -32,11 +32,13 @@ func (ng NameGenerator) NextName(directory string, now time.Time) string { } func DefaultNameGenerator() func(directory string, now time.Time) string { - // TODO 考虑使用原子计数器替换随机数 + // 考虑使用原子计数器替换随机数 // 这个 Seed 方法最好不要并发执行,但是这个方法有可能会被并发执行,这是个隐患 - rand.Seed(time.Now().Unix()) + // 在测试阶段就已经出现了随机数重复的情况,导致一个文件被写入多个文件的内容 + // https://github.com/FishGoddess/logit/issues/7 + rand.Seed(time.Now().UnixNano()) return func(directory string, now time.Time) string { - name := now.Format("20060102-150405") + "-" + strconv.Itoa(rand.Intn(1000)) + SuffixOfLogFile + name := now.Format("20060102-150405") + "-" + strconv.Itoa(rand.Intn(10000)) + SuffixOfLogFile return filepath.Join(directory, name) } } diff --git a/writer/rolling_hook.go b/files/rolling_hook.go similarity index 94% rename from writer/rolling_hook.go rename to files/rolling_hook.go index 58c2aa3..61b0e92 100644 --- a/writer/rolling_hook.go +++ b/files/rolling_hook.go @@ -16,7 +16,7 @@ // Email: fishgoddess@qq.com // Created at 2020/06/12 22:08:31 -package writer +package files import ( "io/ioutil" @@ -33,6 +33,10 @@ type RollingHook interface { // ============================= default rolling hook ============================= type DefaultRollingHook struct{} +func NewDefaultRollingHook() *DefaultRollingHook { + return &DefaultRollingHook{} +} + func (drh *DefaultRollingHook) BeforeRolling() { // Do nothing... } diff --git a/writer/rolling_hook_test.go b/files/rolling_hook_test.go similarity index 99% rename from writer/rolling_hook_test.go rename to files/rolling_hook_test.go index 7568390..fe49daa 100644 --- a/writer/rolling_hook_test.go +++ b/files/rolling_hook_test.go @@ -16,7 +16,7 @@ // Email: fishgoddess@qq.com // Created at 2020/06/13 00:07:00 -package writer +package files import ( "io/ioutil" diff --git a/writer/size_rolling_file.go b/files/size_rolling_file.go similarity index 84% rename from writer/size_rolling_file.go rename to files/size_rolling_file.go index b51c498..b7e40aa 100644 --- a/writer/size_rolling_file.go +++ b/files/size_rolling_file.go @@ -16,7 +16,7 @@ // Email: fishgoddess@qq.com // Created at 2020/03/05 00:11:50 -package writer +package files import ( "errors" @@ -40,6 +40,8 @@ type SizeRollingFile struct { // file points the writer which will be used this moment. file *os.File + directory string + // limitedSize is the limited size of this file. // File will roll to next file if its size has reached to limitedSize. // This field should be always larger than minLimitedSize for some safe considerations. @@ -68,6 +70,10 @@ type SizeRollingFile struct { // created time of next file. nextFilename func(now time.Time) string + nameGenerator NameGenerator + + rollingHook RollingHook + // mu is a lock for safe concurrency. mu sync.Mutex } @@ -84,29 +90,34 @@ const ( // Every times rolling to next file will call nextFilename first. // now is the created time of next file. Notice that limitedSize's min value // is 64 KB (64 * 1024 bytes). See minLimitedSize. -func NewSizeRollingFile(limitedSize int64, nextFilename func(now time.Time) string) *SizeRollingFile { +func NewSizeRollingFile(directory string, limitedSize int64) *SizeRollingFile { // 防止文件限制尺寸太小导致滚动文件时 IO 的疯狂蠕动 if limitedSize < minLimitedSize { panic(errors.New("LimitedSize is smaller than " + strconv.FormatUint(uint64(minLimitedSize)>>10, 10) + " KB!\n")) } - // 获取当前时间,并生成第一个文件 - file, _ := generateFirstFile(nextFilename) return &SizeRollingFile{ - file: file, - limitedSize: limitedSize, - currentSize: 0, - nextFilename: nextFilename, - mu: sync.Mutex{}, + directory: directory, + limitedSize: limitedSize, + currentSize: 0, + nameGenerator: DefaultNameGenerator(), + rollingHook: NewDefaultRollingHook(), + mu: sync.Mutex{}, } } +func (srf *SizeRollingFile) doRollingTask() { + srf.rollingHook.BeforeRolling() + srf.rollingToNextFile(time.Now()) + srf.rollingHook.AfterRolling() +} + // rollingToNextFile will roll to next file for srf. func (srf *SizeRollingFile) rollingToNextFile(now time.Time) { // 如果创建新文件发生错误,就继续使用当前的文件,等到下一次时间间隔再重试 - newFile, err := NewFile(srf.nextFilename(now)) + newFile, err := CreateFileOf(srf.nameGenerator.NextName(srf.directory, now)) if err != nil { return } @@ -119,6 +130,14 @@ func (srf *SizeRollingFile) rollingToNextFile(now time.Time) { // ensureFileIsCorrect ensures srf is writing to a correct file this moment. func (srf *SizeRollingFile) ensureFileIsCorrect() { + + // file 为 nil,进行初始化 + if srf.file == nil { + srf.doRollingTask() + return + } + + // 判断文件大小是否超过限制值 if srf.currentSize >= srf.limitedSize { // 这时候还不能确定 currentSize 是正确的,需要通过系统调用查询文件真实大小 @@ -128,7 +147,7 @@ func (srf *SizeRollingFile) ensureFileIsCorrect() { // 1. err != nil,获取文件真实大小失败,选择相信 currentSize // 2. 真实文件大小确实大于 limitedSize if err != nil || fileInfo.Size() >= srf.limitedSize { - srf.rollingToNextFile(time.Now()) + srf.doRollingTask() return } @@ -163,3 +182,15 @@ func (srf *SizeRollingFile) Close() error { defer srf.mu.Unlock() return srf.file.Close() } + +func (srf *SizeRollingFile) SetNameGenerator(nameGenerator NameGenerator) { + srf.mu.Lock() + defer srf.mu.Unlock() + srf.nameGenerator = nameGenerator +} + +func (srf *SizeRollingFile) SetRollingHook(rollingHook RollingHook) { + srf.mu.Lock() + defer srf.mu.Unlock() + srf.rollingHook = rollingHook +} diff --git a/files/size_rolling_file_test.go b/files/size_rolling_file_test.go new file mode 100644 index 0000000..dc1e31a --- /dev/null +++ b/files/size_rolling_file_test.go @@ -0,0 +1,131 @@ +// Copyright 2020 Ye Zi Jie. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: FishGoddess +// Email: fishgoddess@qq.com +// Created at 2020/03/05 16:10:31 + +package files + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" +) + +// 测试创建根据文件大小滚动的文件类型 +func TestNewSizeRollingFile(t *testing.T) { + + defer func() { + if err := recover(); err == nil { + t.Errorf("文件大小限制测试出现问题!") + } + }() + + // 创建临时测试文件夹 + root, err := ioutil.TempDir(os.TempDir(), "TestNewSizeRollingFile_") + if err != nil { + t.Fatal(err) + } + + file := NewSizeRollingFile(root, 64*KB) + defer file.Close() + + b := make([]byte, 1024) + for i := 0; i < 1024; i++ { + file.Write(b) + } + + dir, err := os.Open(root) + if err != nil { + t.Fatal("获取测试文件夹失败!") + } + + fileInfos, err := dir.Readdir(0) + if err != nil { + t.Fatal("获取测试文件夹信息失败!") + } + + // 如果创建的文件数不符合,直接报错 + if len(fileInfos) != 16 { + t.Fatal("文件滚动出现问题!") + } + + file.Close() + file = NewSizeRollingFile("", 0) + file.SetNameGenerator(func(directory string, now time.Time) string { + return "" + }) +} + +// 测试名字生成器的设置方法 +func TestSizeRollingFileSetNameGenerator(t *testing.T) { + + dir, err := ioutil.TempDir("", "TestSizeRollingFileSetNameGenerator_") + if err != nil { + t.Fatal(err) + } + + // 创建文件,并写入内容 + file := NewSizeRollingFile(dir, 64*KB) + defer file.Close() + for i := 0; i < 10000; i++ { + file.Write([]byte(" hello! ")) + } + + // 更换命名器,等待滚动时间到了之后,再次写入内容 + file.SetNameGenerator(func(directory string, now time.Time) string { + return filepath.Join(directory, now.Format("2006年01月02日的15点04分05秒产生的文件.log")) + }) + time.Sleep(2 * time.Second) + for i := 0; i < 10000; i++ { + file.Write([]byte(" hi!! ")) + } +} + +// 测试使用的 rolling hook +type testSizeRollingFileRollingHook struct{} + +func (tdrfrh *testSizeRollingFileRollingHook) BeforeRolling() { + fmt.Println("before rolling...") +} + +func (tdrfrh *testSizeRollingFileRollingHook) AfterRolling() { + fmt.Println("after rolling...") +} + +// 测试名字生成器的设置方法 +func TestSizeRollingFileSetRollingHook(t *testing.T) { + + dir, err := ioutil.TempDir("", "TestSizeRollingFileSetRollingHook_") + if err != nil { + t.Fatal(err) + } + + file := NewSizeRollingFile(dir, 64*KB) + defer file.Close() + + for i := 0; i < 10000; i++ { + file.Write([]byte(" hello! ")) + } + file.SetRollingHook(&testSizeRollingFileRollingHook{}) + + time.Sleep(2 * time.Second) + for i := 0; i < 10000; i++ { + file.Write([]byte(" hi!! ")) + } +} diff --git a/writer/writer.go b/files/writer.go similarity index 60% rename from writer/writer.go rename to files/writer.go index f8d1a72..fcc3256 100644 --- a/writer/writer.go +++ b/files/writer.go @@ -16,14 +16,10 @@ // Email: fishgoddess@qq.com // Created at 2020/03/03 15:10:45 -package writer +package files import ( - "math/rand" "os" - "path/filepath" - "strconv" - "time" ) const ( @@ -40,31 +36,17 @@ const ( SuffixOfLogFile = ".log" ) -// NextFilename creates a time-relative filename with given now time. -// Also, it uses random number to ensure this filename is available. -// The filename will be like "20200304-145246-45.log". -// Notice that directory stores all log files generated in this time. -func NextFilename(directory string) func(now time.Time) string { - rand.Seed(time.Now().UnixNano()) - return func(now time.Time) string { - name := now.Format("20060102-150405") + "-" + strconv.Itoa(rand.Intn(1000)) + SuffixOfLogFile - return filepath.Join(directory, name) - } -} - // NewFile creates a new file with given filePath. // Return a new File or an error if failed. // Notice that the permission of new file is 0644, which means rw-rw-r-- in unix-like os. +// Deprecated. It will be removed in next version and you can use CreateFileOf instead. func NewFile(filePath string) (*os.File, error) { - return os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0664) + return CreateFileOf(filePath) } -// generateFirstFile creates the first file with given nextFilename function. -func generateFirstFile(nextFilename func(now time.Time) string) (*os.File, time.Time) { - now := time.Now() - file, err := NewFile(nextFilename(now)) - if err != nil { - panic(err) - } - return file, now +// CreateFileOf creates a new file with given filePath. +// Return a new File or an error if failed. +// Notice that the permission of new file is 0644, which means rw-rw-r-- in unix-like os. +func CreateFileOf(filePath string) (*os.File, error) { + return os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0664) } diff --git a/handler_extension.go b/handler_extension.go index 872c04f..6779f1a 100644 --- a/handler_extension.go +++ b/handler_extension.go @@ -19,7 +19,7 @@ package logit import ( - "github.com/FishGoddess/logit/writer" + "github.com/FishGoddess/logit/files" "os" "strconv" "strings" @@ -108,7 +108,7 @@ func registerConsoleHandler() { // func registerFileHandler() { RegisterHandler("file", func(params map[string]interface{}) Handler { - path := pathOf(params, "./logit-"+strconv.FormatInt(time.Now().Unix(), 10)+writer.SuffixOfLogFile) + path := pathOf(params, "./logit-"+strconv.FormatInt(time.Now().Unix(), 10)+files.SuffixOfLogFile) encoder, timeFormat := encoderAndTimeFormatOf(params, TextEncoder(), DefaultTimeFormat) return NewFileHandler(path, encoder, timeFormat) }) @@ -207,7 +207,7 @@ func registerSizeRollingHandler() { // 滚动的文件大小,单位是 MB,默认是 64 MB limit, directory := limitAndDirectoryOf(params, 64, "./") encoder, timeFormat := encoderAndTimeFormatOf(params, TextEncoder(), DefaultTimeFormat) - return NewSizeRollingHandler(int64(limit)*writer.MB, directory, encoder, timeFormat) + return NewSizeRollingHandler(int64(limit)*files.MB, directory, encoder, timeFormat) }) } @@ -282,7 +282,7 @@ func NewConsoleHandler(encoder Encoder, timeFormat string) Handler { // If the file of this path doesn't exist, a new file will be created. // See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. func NewFileHandler(path string, encoder Encoder, timeFormat string) Handler { - file, err := writer.NewFile(path) + file, err := files.CreateFileOf(path) if err != nil { panic(err) } @@ -294,9 +294,9 @@ func NewFileHandler(path string, encoder Encoder, timeFormat string) Handler { // each duration has its own log file. Also you can point a directory // to be used to store all created log files. // See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. -// See writer.NewDurationRollingFile. +// See files.NewDurationRollingFile. func NewDurationRollingHandler(limit time.Duration, directory string, encoder Encoder, timeFormat string) Handler { - file := writer.NewDurationRollingFile(limit, writer.NextFilename(directory)) + file := files.NewDurationRollingFile(directory, limit) return NewStandardHandler(file, encoder, timeFormat) } @@ -305,8 +305,8 @@ func NewDurationRollingHandler(limit time.Duration, directory string, encoder En // and the log file will switch to a new one after reaching to max size. // Also you can point a directory to be used to store all created log files. // See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. -// See writer.NewSizeRollingFile. +// See files.NewSizeRollingFile. func NewSizeRollingHandler(limit int64, directory string, encoder Encoder, timeFormat string) Handler { - file := writer.NewSizeRollingFile(limit, writer.NextFilename(directory)) + file := files.NewSizeRollingFile(directory, limit) return NewStandardHandler(file, encoder, timeFormat) } diff --git a/handler_extension_test.go b/handler_extension_test.go index 20cf991..4e58fb1 100644 --- a/handler_extension_test.go +++ b/handler_extension_test.go @@ -25,7 +25,7 @@ import ( "testing" "time" - "github.com/FishGoddess/logit/writer" + "github.com/FishGoddess/logit/files" ) // 测试创建文件日志处理器 @@ -58,7 +58,7 @@ func TestNewDurationRollingHandler(t *testing.T) { // 测试按照文件大小自动划分日志文件的日志处理器 func TestNewSizeRollingHandler(t *testing.T) { - logger := NewLogger(DebugLevel, NewSizeRollingHandler(64*writer.KB, os.TempDir(), TextEncoder(), "")) + logger := NewLogger(DebugLevel, NewSizeRollingHandler(64*files.KB, os.TempDir(), TextEncoder(), "")) for i := 0; i < 2000; i++ { logger.Debug("debug...") logger.Info("info...") diff --git a/writer/doc.go b/writer/doc.go deleted file mode 100644 index 970793c..0000000 --- a/writer/doc.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020 Ye Zi Jie. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Author: FishGoddess -// Email: fishgoddess@qq.com -// Created at 2020/03/03 14:59:08 - -/* -Package writer provides some writers to extend your logger. - -1. DurationRollingFile: - - // DurationRollingFile is a time sensitive file. - file := NewDurationRollingFile(24*time.Hour, func(now time.Time) string { - return "D:/" + now.Format("20060102-150405") + ".txt" - }) - defer file.Close() - - // You can use it like using os.File! - file.Write([]byte("Hello!")) - -2. SizeRollingFile: - - // SizeRollingFile is a file size sensitive file. - file := NewSizeRollingFile(64*KB, func (now time.Time) string { - return "D:/" + now.Format("20060102150405.000") + ".txt" - }) - defer file.Close() - - // You can use it like using os.File! - file.Write([]byte("Hello!")) - -*/ -package writer // import "github.com/FishGoddess/logit/writer" diff --git a/writer/duration_rolling_file_test.go b/writer/duration_rolling_file_test.go deleted file mode 100644 index 775cbd3..0000000 --- a/writer/duration_rolling_file_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2020 Ye Zi Jie. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Author: FishGoddess -// Email: fishgoddess@qq.com -// Created at 2020/03/03 16:01:38 - -package writer - -import ( - "os" - "path/filepath" - "testing" - "time" -) - -// 测试时间间隔滚动文件 -func TestNewDurationRollingFile(t *testing.T) { - - defer func() { - if err := recover(); err == nil { - t.Errorf("时间间隔大小限制测试出现问题!") - } - }() - - root := filepath.Join(os.TempDir(), "TestNewDurationRollingFile") - os.RemoveAll(root) // 先删除现有文件夹 - os.Mkdir(root, 0666) // 再创建测试文件夹 - - file := NewDurationRollingFile(time.Second, NextFilename(root)) - defer file.Close() - - for i := 0; i < 5; i++ { - file.Write([]byte("测试")) - time.Sleep(666 * time.Millisecond) - } - - dir, err := os.Open(root) - if err != nil { - t.Fatal("获取测试文件夹失败!") - } - - fileInfos, err := dir.Readdir(0) - if err != nil { - t.Fatal("获取测试文件夹信息失败!") - } - - // 如果创建的文件数不符合,直接报错 - if len(fileInfos) != 3 { - t.Fatal("文件滚动出现问题!") - } - - file.Close() - file = NewDurationRollingFile(999*time.Millisecond, func(now time.Time) string { - return "" - }) -} diff --git a/writer/size_rolling_file_test.go b/writer/size_rolling_file_test.go deleted file mode 100644 index 216dde5..0000000 --- a/writer/size_rolling_file_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2020 Ye Zi Jie. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Author: FishGoddess -// Email: fishgoddess@qq.com -// Created at 2020/03/05 16:10:31 - -package writer - -import ( - "os" - "path/filepath" - "testing" - "time" -) - -// 测试创建根据文件大小滚动的文件类型 -func TestNewSizeRollingFile(t *testing.T) { - - defer func() { - if err := recover(); err == nil { - t.Errorf("文件大小限制测试出现问题!") - } - }() - - root := filepath.Join(os.TempDir(), "TestNewSizeRollingFile") - os.RemoveAll(root) // 先删除现有文件夹 - os.Mkdir(root, 0666) // 再创建测试文件夹 - - file := NewSizeRollingFile(64*KB, NextFilename(root)) - defer file.Close() - - b := make([]byte, 1024) - for i := 0; i < 1024; i++ { - file.Write(b) - } - - dir, err := os.Open(root) - if err != nil { - t.Fatal("获取测试文件夹失败!") - } - - fileInfos, err := dir.Readdir(0) - if err != nil { - t.Fatal("获取测试文件夹信息失败!") - } - - // 如果创建的文件数不符合,直接报错 - if len(fileInfos) != 16 { - t.Fatal("文件滚动出现问题!") - } - - file.Close() - file = NewSizeRollingFile(0, func(now time.Time) string { - return "" - }) -} From c9e2296543b47f39e781eaa64ac0e4e78e6af480 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Sat, 13 Jun 2020 16:11:09 +0800 Subject: [PATCH 09/18] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86=20Ha?= =?UTF-8?q?ndler=20=E7=9A=84=E5=8F=82=E6=95=B0=E5=88=97=E8=A1=A8=E9=A1=BA?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FUTURE.md | 7 ++++++- README.en.md | 2 +- README.md | 2 +- _examples/config/logit-config-template.conf | 18 +++++++++--------- _examples/config/logit-config-template.en.conf | 18 +++++++++--------- _examples/files.go | 4 ++-- _examples/log_to_file.go | 4 ++-- doc.go | 6 +++--- files/doc.go | 4 ++-- files/writer.go | 2 +- handler_extension.go | 8 ++++---- handler_extension_test.go | 4 ++-- 12 files changed, 42 insertions(+), 37 deletions(-) diff --git a/FUTURE.md b/FUTURE.md index 0c700e8..d600b58 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -3,8 +3,13 @@ ### v0.2.7 * 加入日志存活个数的特性 -### v0.2.6 +### v0.2.6-alpha +* 对 writer 包进行重构,改名为 files 包 +* 废弃了原 writer 包的 NewFile 方法,并使用同包下的 CreateFileOf 代替 +* 引入 NameGenerator 和 RollingHook 两个组件 * 加入日志存活天数的特性 +* 修改 NewDurationRollingHandler 的参数顺序 +* 修改 NewSizeRollingHandler 的参数顺序 ### v0.2.5-alpha * 加入之前被移除的特性 - 可变长参数列表的日志输出支持,主要可以使用格式化字符串进行多参数传递 diff --git a/README.en.md b/README.en.md index 0b66656..a7fce1e 100644 --- a/README.en.md +++ b/README.en.md @@ -46,7 +46,7 @@ module your_project_name go 1.14 require ( - github.com/FishGoddess/logit v0.2.5-alpha + github.com/FishGoddess/logit v0.2.6-alpha ) ``` diff --git a/README.md b/README.md index cc59a94..0717608 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ module your_project_name go 1.14 require ( - github.com/FishGoddess/logit v0.2.5-alpha + github.com/FishGoddess/logit v0.2.6-alpha ) ``` diff --git a/_examples/config/logit-config-template.conf b/_examples/config/logit-config-template.conf index 5cedf25..5201a35 100644 --- a/_examples/config/logit-config-template.conf +++ b/_examples/config/logit-config-template.conf @@ -1,4 +1,4 @@ -# logit 配置文件的模板 v0.2.5-alpha +# logit 配置文件的模板 v0.2.6-alpha # 注意:以 # 开头的是注释,不参与配置文件的解析,注释必须是单独的一行,不能写在属性的后面 # 语法是基于 Json 并作了一些改动使其更适合做配置文件,所以要注意双引号和逗号这些格式 # 下面所有涉及目录路径的都使用 / 或者 \\ 而不能是 \,否则会造成配置解析出错,这意味着特殊字符需要自行转义 @@ -58,15 +58,15 @@ # duration 日志处理器 # 下面是该日志处理器支持的所有参数 "duration": { + # 日志存储的文件夹 + # 因为会有很多的日志文件需要存储,所以这里配置的是文件夹,如果不配置的话,默认是 ./ + "directory": "D:/", + # 滚动阈值,时间间隔,单位是秒 # 假设 10:00:00 创建了日志文件,limit 配置的是 60,则当时间为 10:01:00 的时候,就会滚动到新的日志文件 # 如果不配置的话,默认是一天 "limit": 60, - # 日志存储的文件夹 - # 因为会有很多的日志文件需要存储,所以这里配置的是文件夹,如果不配置的话,默认是 ./ - "directory": "D:/", - # 日志编码器,可选值有 text,json # text: 使用普通文本形式编码日志,通常是 `[info] [2020-04-24 13:14:15] xxx` 这样的形式 # json: 使用 Json 形式编码日志,通常是 `{"level":"info", "time":"2020-04-24 13:14:15", "msg":"xxx"}` 这样的形式 @@ -82,15 +82,15 @@ # size 日志处理器 # 下面是该日志处理器支持的所有参数 "size": { + # 日志存储的文件夹 + # 因为会有很多的日志文件需要存储,所以这里配置的是文件夹,如果不配置的话,默认是 ./ + "directory": "D:/", + # 滚动阈值,最大日志文件大小,单位是 MB # 假设 limit 配置的是 16,则当日志文件大小为 16 MB 的时候,就会滚动到新的日志文件 # 如果不配置的话,默认是 64 MB "limit": 16, - # 日志存储的文件夹 - # 因为会有很多的日志文件需要存储,所以这里配置的是文件夹,如果不配置的话,默认是 ./ - "directory": "D:/", - # 日志编码器,可选值有 text,json # text: 使用普通文本形式编码日志,通常是 `[info] [2020-04-24 13:14:15] xxx` 这样的形式 # json: 使用 Json 形式编码日志,通常是 `{"level":"info", "time":"2020-04-24 13:14:15", "msg":"xxx"}` 这样的形式 diff --git a/_examples/config/logit-config-template.en.conf b/_examples/config/logit-config-template.en.conf index 2424708..69b84f2 100644 --- a/_examples/config/logit-config-template.en.conf +++ b/_examples/config/logit-config-template.en.conf @@ -1,4 +1,4 @@ -# logit config template v0.2.5-alpha +# logit config template v0.2.6-alpha # Notice that starting with # is comment, and it must be a new line # Grammar is based on Json, but adds more features to let it become more configured and easy-to-read # You should always use / or \\ instead of \, because some special characters should be escaped @@ -61,16 +61,16 @@ # duration handler # These are all supported params "duration": { + # The destination folder stores all log files created + # default is ./ + "directory": "D:/", + # Duration, the unit is second # Assume that a log file was created on 10:00:00, and limit is 60, then when the time # is 10:01:00, a new log file will be created and roll to it # Default is one day "limit": 60, - # The destination folder stores all log files created - # default is ./ - "directory": "D:/", - # Log encoder, all valid values are text, json # text: Encode log to plain text, such as `[info] [2020-04-24 13:14:15] xxx` # json: Encode log to Json, such as `{"level":"info", "time":"2020-04-24 13:14:15", "msg":"xxx"}` @@ -86,16 +86,16 @@ # size handler # These are all supported params "size": { + # The destination folder stores all log files created + # default is ./ + "directory": "D:/", + # Max size of log file, the unit is MB # Assume that limit is 16, then when the size of log file is 16 MB, a new # log file will be created and roll to it # Default is 64 MB "limit": 16, - # The destination folder stores all log files created - # default is ./ - "directory": "D:/", - # Log encoder, all valid values are text, json # text: Encode log to plain text, such as `[info] [2020-04-24 13:14:15] xxx` # json: Encode log to Json, such as `{"level":"info", "time":"2020-04-24 13:14:15", "msg":"xxx"}` diff --git a/_examples/files.go b/_examples/files.go index 9ae4736..d7c6b0c 100644 --- a/_examples/files.go +++ b/_examples/files.go @@ -41,7 +41,7 @@ func main() { return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) }) - // What's more, you can add a hook in rolling process, see RollingHook. + // What's more, you can add a hook in rolling process, see files.RollingHook. //durationRollingFile.SetRollingHook(xxx) // ================================================================================= @@ -60,6 +60,6 @@ func main() { return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) }) - // What's more, you can add a hook in rolling process, see RollingHook. + // What's more, you can add a hook in rolling process, see files.RollingHook. //sizeRollingFile.SetRollingHook(xxx) } diff --git a/_examples/log_to_file.go b/_examples/log_to_file.go index 1e5bd1b..f930946 100644 --- a/_examples/log_to_file.go +++ b/_examples/log_to_file.go @@ -38,7 +38,7 @@ func main() { // Also, default filename of log file is like "20200304-145246-45.log", see files.NewFilename. // If you want to appoint another filename, check this and do it by this way. // See files.NewDurationRollingFile (it is an implement of io.writer). - logger = logit.NewLogger(logit.DebugLevel, logit.NewDurationRollingHandler(24*time.Hour, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) + logger = logit.NewLogger(logit.DebugLevel, logit.NewDurationRollingHandler("D:/", 24*time.Hour, logit.TextEncoder(), logit.DefaultTimeFormat)) logger.Info("Rolling!!!") // NewSizeRollingLogger creates a file size rolling logger with given limitedSize. @@ -48,6 +48,6 @@ func main() { // Also, default filename of log file is like "20200304-145246-45.log", see nextFilename. // If you want to appoint another filename, check this and do it by this way. // See files.NewSizeRollingFile (it is an implement of io.writer). - logger = logit.NewLogger(logit.DebugLevel, logit.NewSizeRollingHandler(64*files.KB, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) + logger = logit.NewLogger(logit.DebugLevel, logit.NewSizeRollingHandler("D:/", 64*files.KB, logit.TextEncoder(), logit.DefaultTimeFormat)) logger.Info("file size???") } diff --git a/doc.go b/doc.go index 0eb1330..25da1d2 100644 --- a/doc.go +++ b/doc.go @@ -120,7 +120,7 @@ Package logit provides an easy way to use foundation for your logging operations // Also, default filename of log file is like "20200304-145246-45.log", see files.NewFilename. // If you want to appoint another filename, check this and do it by this way. // See files.NewDurationRollingFile (it is an implement of io.writer). - logger = logit.NewLogger(logit.DebugLevel, logit.NewDurationRollingHandler(24*time.Hour, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) + logger = logit.NewLogger(logit.DebugLevel, logit.NewDurationRollingHandler("D:/", 24*time.Hour, logit.TextEncoder(), logit.DefaultTimeFormat)) logger.Info("Rolling!!!") // NewSizeRollingLogger creates a file size rolling logger with given limitedSize. @@ -130,7 +130,7 @@ Package logit provides an easy way to use foundation for your logging operations // Also, default filename of log file is like "20200304-145246-45.log", see nextFilename. // If you want to appoint another filename, check this and do it by this way. // See files.NewSizeRollingFile (it is an implement of io.writer). - logger = logit.NewLogger(logit.DebugLevel, logit.NewSizeRollingHandler(64*files.KB, "D:/", logit.TextEncoder(), logit.DefaultTimeFormat)) + logger = logit.NewLogger(logit.DebugLevel, logit.NewSizeRollingHandler("D:/", 64*files.KB, logit.TextEncoder(), logit.DefaultTimeFormat)) logger.Info("file size???") 5. handler: @@ -207,5 +207,5 @@ package logit // import "github.com/FishGoddess/logit" const ( // Version is the version string representation of logit. - Version = "v0.2.5-alpha" + Version = "v0.2.6-alpha" ) diff --git a/files/doc.go b/files/doc.go index 1e8a9ad..ee9b055 100644 --- a/files/doc.go +++ b/files/doc.go @@ -35,7 +35,7 @@ Package files provides some writers to extend your logger. return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) }) - // What's more, you can add a hook in rolling process, see RollingHook. + // What's more, you can add a hook in rolling process, see files.RollingHook. //durationRollingFile.SetRollingHook(xxx) 2. SizeRollingFile: @@ -54,7 +54,7 @@ Package files provides some writers to extend your logger. return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) }) - // What's more, you can add a hook in rolling process, see RollingHook. + // What's more, you can add a hook in rolling process, see files.RollingHook. //sizeRollingFile.SetRollingHook(xxx) */ diff --git a/files/writer.go b/files/writer.go index fcc3256..7ea896b 100644 --- a/files/writer.go +++ b/files/writer.go @@ -39,7 +39,7 @@ const ( // NewFile creates a new file with given filePath. // Return a new File or an error if failed. // Notice that the permission of new file is 0644, which means rw-rw-r-- in unix-like os. -// Deprecated. It will be removed in next version and you can use CreateFileOf instead. +// Deprecated. It will be removed in next version and you can use files.CreateFileOf instead. func NewFile(filePath string) (*os.File, error) { return CreateFileOf(filePath) } diff --git a/handler_extension.go b/handler_extension.go index 6779f1a..4a854b5 100644 --- a/handler_extension.go +++ b/handler_extension.go @@ -159,7 +159,7 @@ func registerDurationRollingHandler() { // 滚动的时间间隔,单位是秒,默认是 1 天 limit, directory := limitAndDirectoryOf(params, 24*60*60, "./") encoder, timeFormat := encoderAndTimeFormatOf(params, TextEncoder(), DefaultTimeFormat) - return NewDurationRollingHandler(time.Duration(limit)*time.Second, directory, encoder, timeFormat) + return NewDurationRollingHandler(directory, time.Duration(limit)*time.Second, encoder, timeFormat) }) } @@ -207,7 +207,7 @@ func registerSizeRollingHandler() { // 滚动的文件大小,单位是 MB,默认是 64 MB limit, directory := limitAndDirectoryOf(params, 64, "./") encoder, timeFormat := encoderAndTimeFormatOf(params, TextEncoder(), DefaultTimeFormat) - return NewSizeRollingHandler(int64(limit)*files.MB, directory, encoder, timeFormat) + return NewSizeRollingHandler(directory, int64(limit)*files.MB, encoder, timeFormat) }) } @@ -295,7 +295,7 @@ func NewFileHandler(path string, encoder Encoder, timeFormat string) Handler { // to be used to store all created log files. // See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. // See files.NewDurationRollingFile. -func NewDurationRollingHandler(limit time.Duration, directory string, encoder Encoder, timeFormat string) Handler { +func NewDurationRollingHandler(directory string, limit time.Duration, encoder Encoder, timeFormat string) Handler { file := files.NewDurationRollingFile(directory, limit) return NewStandardHandler(file, encoder, timeFormat) } @@ -306,7 +306,7 @@ func NewDurationRollingHandler(limit time.Duration, directory string, encoder En // Also you can point a directory to be used to store all created log files. // See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. // See files.NewSizeRollingFile. -func NewSizeRollingHandler(limit int64, directory string, encoder Encoder, timeFormat string) Handler { +func NewSizeRollingHandler(directory string, limit int64, encoder Encoder, timeFormat string) Handler { file := files.NewSizeRollingFile(directory, limit) return NewStandardHandler(file, encoder, timeFormat) } diff --git a/handler_extension_test.go b/handler_extension_test.go index 4e58fb1..2b1ab5e 100644 --- a/handler_extension_test.go +++ b/handler_extension_test.go @@ -48,7 +48,7 @@ func TestNewFileHandler(t *testing.T) { // 测试创建随时间间隔滚动的文件日志处理器 func TestNewDurationRollingHandler(t *testing.T) { - logger := NewLogger(DebugLevel, NewDurationRollingHandler(time.Second, os.TempDir(), TextEncoder(), "")) + logger := NewLogger(DebugLevel, NewDurationRollingHandler(os.TempDir(), time.Second, TextEncoder(), "")) for i := 0; i < 5; i++ { logger.Info("1. info!!!!!!!! " + strconv.FormatInt(time.Now().Unix(), 10)) time.Sleep(time.Second) @@ -58,7 +58,7 @@ func TestNewDurationRollingHandler(t *testing.T) { // 测试按照文件大小自动划分日志文件的日志处理器 func TestNewSizeRollingHandler(t *testing.T) { - logger := NewLogger(DebugLevel, NewSizeRollingHandler(64*files.KB, os.TempDir(), TextEncoder(), "")) + logger := NewLogger(DebugLevel, NewSizeRollingHandler(os.TempDir(), 64*files.KB, TextEncoder(), "")) for i := 0; i < 2000; i++ { logger.Debug("debug...") logger.Info("info...") From b15fb27319ed378b64d31f3a43922f704e665759 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Sun, 14 Jun 2020 14:39:57 +0800 Subject: [PATCH 10/18] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/logit-config-template.en.conf | 2 +- files/duration_rolling_file.go | 23 ++++++++++++++----- files/{writer.go => files.go} | 8 ------- files/name_generator.go | 14 +++++++++-- files/rolling_hook.go | 20 ++++++++++++++++ files/rolling_hook_test.go | 1 + files/size_rolling_file.go | 18 ++++++++++++--- logit.go | 2 +- 8 files changed, 67 insertions(+), 21 deletions(-) rename files/{writer.go => files.go} (78%) diff --git a/_examples/config/logit-config-template.en.conf b/_examples/config/logit-config-template.en.conf index 69b84f2..0c85efe 100644 --- a/_examples/config/logit-config-template.en.conf +++ b/_examples/config/logit-config-template.en.conf @@ -20,7 +20,7 @@ # At lease add one handler or it will panic "handlers": { # Notice that what params can be set here is dependent to what handler you use - # Also, every params you set here will be injected into newHandler in logit.RegisterHandler as kv pair form + # Also, every param you set here will be injected into newHandler in logit.RegisterHandler as kv pair form # Config file is only responsible for injection, so handlers have responsibility to decide how to use these params # Different handler may have different params, check document of using handler to know about more information diff --git a/files/duration_rolling_file.go b/files/duration_rolling_file.go index 55b5a5f..b65e8c2 100644 --- a/files/duration_rolling_file.go +++ b/files/duration_rolling_file.go @@ -39,6 +39,7 @@ type DurationRollingFile struct { // file points the writer which will be used this moment. file *os.File + // directory is the target storing all created files. directory string // lastTime is the created time of current file above. @@ -50,12 +51,20 @@ type DurationRollingFile struct { // larger than minDuration for some safe considerations. See minDuration. duration time.Duration + // nameGenerator is for generating the name of every created file. + // You can customize your format of filename by implementing this function. + // Default is DefaultNameGenerator(). nameGenerator NameGenerator + // rollingHook is a hook that will be invoked in rolling process. + // This interface has two method: BeforeRolling and AfterRolling. + // BeforeRolling will be called before rolling to next file. + // AfterRolling will be called after rolling to next file. + // Default is DefaultRollingHook, and it will do nothing when rolling to next file. rollingHook RollingHook // mu is a lock for safe concurrency. - mu sync.Mutex + mu *sync.Mutex } const ( @@ -82,7 +91,7 @@ func NewDurationRollingFile(directory string, duration time.Duration) *DurationR duration: duration, nameGenerator: DefaultNameGenerator(), rollingHook: NewDefaultRollingHook(), - mu: sync.Mutex{}, + mu: &sync.Mutex{}, } } @@ -131,14 +140,16 @@ func (drf *DurationRollingFile) Close() error { return drf.file.Close() } -func (drf *DurationRollingFile) SetNameGenerator(nameGenerator NameGenerator) { +// SetNameGenerator replaces drf.nameGenerator to newNameGenerator. +func (drf *DurationRollingFile) SetNameGenerator(newNameGenerator NameGenerator) { drf.mu.Lock() defer drf.mu.Unlock() - drf.nameGenerator = nameGenerator + drf.nameGenerator = newNameGenerator } -func (drf *DurationRollingFile) SetRollingHook(rollingHook RollingHook) { +// SetRollingHook replaces drf.rollingHook to newRollingHook. +func (drf *DurationRollingFile) SetRollingHook(newRollingHook RollingHook) { drf.mu.Lock() defer drf.mu.Unlock() - drf.rollingHook = rollingHook + drf.rollingHook = newRollingHook } diff --git a/files/writer.go b/files/files.go similarity index 78% rename from files/writer.go rename to files/files.go index 7ea896b..34a85ad 100644 --- a/files/writer.go +++ b/files/files.go @@ -36,14 +36,6 @@ const ( SuffixOfLogFile = ".log" ) -// NewFile creates a new file with given filePath. -// Return a new File or an error if failed. -// Notice that the permission of new file is 0644, which means rw-rw-r-- in unix-like os. -// Deprecated. It will be removed in next version and you can use files.CreateFileOf instead. -func NewFile(filePath string) (*os.File, error) { - return CreateFileOf(filePath) -} - // CreateFileOf creates a new file with given filePath. // Return a new File or an error if failed. // Notice that the permission of new file is 0644, which means rw-rw-r-- in unix-like os. diff --git a/files/name_generator.go b/files/name_generator.go index 6691e18..6c3ccff 100644 --- a/files/name_generator.go +++ b/files/name_generator.go @@ -25,20 +25,30 @@ import ( "time" ) +// NameGenerator is the type for generating the name of every created file. +// You can customize your format of filename by implementing this function. +// The two parameters string and time.Time is useful. The string parameter is the directory +// stores all files created in this time and the time.Time parameter is the time of current moment. type NameGenerator func(string, time.Time) string +// NextName is for code-readable. +// Directory stores all files created in this time and now is the time of current moment. func (ng NameGenerator) NextName(directory string, now time.Time) string { return ng(directory, now) } +// DefaultNameGenerator returns a name generator that creates a time-relative filename +// with given now time. Also, it uses random number to ensure this filename is available. +// The filename will be like "20200304-145246-45.log". +// Notice that directory stores all files created in this time and now is the time of current moment. func DefaultNameGenerator() func(directory string, now time.Time) string { // 考虑使用原子计数器替换随机数 // 这个 Seed 方法最好不要并发执行,但是这个方法有可能会被并发执行,这是个隐患 // 在测试阶段就已经出现了随机数重复的情况,导致一个文件被写入多个文件的内容 - // https://github.com/FishGoddess/logit/issues/7 + // issue: https://github.com/FishGoddess/logit/issues/7 rand.Seed(time.Now().UnixNano()) return func(directory string, now time.Time) string { - name := now.Format("20060102-150405") + "-" + strconv.Itoa(rand.Intn(10000)) + SuffixOfLogFile + name := now.Format("20060102-150405") + "-" + strconv.Itoa(rand.Intn(1000000)) + SuffixOfLogFile return filepath.Join(directory, name) } } diff --git a/files/rolling_hook.go b/files/rolling_hook.go index 61b0e92..9ea5214 100644 --- a/files/rolling_hook.go +++ b/files/rolling_hook.go @@ -25,36 +25,54 @@ import ( "time" ) +// RollingHook is a hook that will be invoked in rolling process. +// This interface has two method: BeforeRolling and AfterRolling. +// BeforeRolling will be called before rolling to next file. +// AfterRolling will be called after rolling to next file. +// You can do your job in both of methods. type RollingHook interface { BeforeRolling() AfterRolling() } // ============================= default rolling hook ============================= + +// DefaultRollingHook is default rolling hook, which will do nothing on rolling to next file. type DefaultRollingHook struct{} +// NewDefaultRollingHook returns a default rolling hook. func NewDefaultRollingHook() *DefaultRollingHook { return &DefaultRollingHook{} } +// BeforeRolling does nothing when rolling to next file. func (drh *DefaultRollingHook) BeforeRolling() { // Do nothing... } +// AfterRolling does nothing when rolling to next file. func (drh *DefaultRollingHook) AfterRolling() { // Do nothing... } // ============================= life rolling hook ============================= +// LifeBasedRollingHook is a life based rolling hook. +// It will tag a life on every file and if life runs out, this file will be removed. type LifeBasedRollingHook struct { + + // DefaultRollingHook has default implement and BeforeRolling is reserved. *DefaultRollingHook + // life is the life of every file. life time.Duration + // directory is the target storing all files need to monitor. directory string } +// NewLifeBasedRollingHook returns a LifeBasedRollingHook holder. +// life is the life of every file and directory is the target storing all files need to monitor. func NewLifeBasedRollingHook(life time.Duration, directory string) *LifeBasedRollingHook { return &LifeBasedRollingHook{ DefaultRollingHook: &DefaultRollingHook{}, @@ -63,6 +81,8 @@ func NewLifeBasedRollingHook(life time.Duration, directory string) *LifeBasedRol } } +// AfterRolling checks life of all files and removes the dead files. +// Remember, it will do nothing if this directory could not be read and modified. func (lrh *LifeBasedRollingHook) AfterRolling() { fileInfos, err := ioutil.ReadDir(lrh.directory) diff --git a/files/rolling_hook_test.go b/files/rolling_hook_test.go index fe49daa..213fb60 100644 --- a/files/rolling_hook_test.go +++ b/files/rolling_hook_test.go @@ -24,6 +24,7 @@ import ( "time" ) +// 测试 LifeRollingHook 的 AfterRolling 方法 func TestLifeRollingHookAfterRolling(t *testing.T) { // 创建测试文件夹 diff --git a/files/size_rolling_file.go b/files/size_rolling_file.go index b7e40aa..73890de 100644 --- a/files/size_rolling_file.go +++ b/files/size_rolling_file.go @@ -40,12 +40,13 @@ type SizeRollingFile struct { // file points the writer which will be used this moment. file *os.File + // directory is the target storing all created files. directory string // limitedSize is the limited size of this file. // File will roll to next file if its size has reached to limitedSize. // This field should be always larger than minLimitedSize for some safe considerations. - // Notice that it doesn't mean every files must be this size due to our performance optimization + // Notice that it doesn't mean every file must be this size due to our performance optimization // scheme. Generally it equals to file size, however, it will not equal to file size // if someone modified this file. See currentSize. limitedSize int64 @@ -70,12 +71,20 @@ type SizeRollingFile struct { // created time of next file. nextFilename func(now time.Time) string + // nameGenerator is for generating the name of every created file. + // You can customize your format of filename by implementing this function. + // Default is DefaultNameGenerator(). nameGenerator NameGenerator + // rollingHook is a hook that will be invoked in rolling process. + // This interface has two method: BeforeRolling and AfterRolling. + // BeforeRolling will be called before rolling to next file. + // AfterRolling will be called after rolling to next file. + // Default is DefaultRollingHook, and it will do nothing when rolling to next file. rollingHook RollingHook // mu is a lock for safe concurrency. - mu sync.Mutex + mu *sync.Mutex } const ( @@ -103,10 +112,11 @@ func NewSizeRollingFile(directory string, limitedSize int64) *SizeRollingFile { currentSize: 0, nameGenerator: DefaultNameGenerator(), rollingHook: NewDefaultRollingHook(), - mu: sync.Mutex{}, + mu: &sync.Mutex{}, } } +// doRollingTask does the whole task of rolling process. func (srf *SizeRollingFile) doRollingTask() { srf.rollingHook.BeforeRolling() srf.rollingToNextFile(time.Now()) @@ -183,12 +193,14 @@ func (srf *SizeRollingFile) Close() error { return srf.file.Close() } +// SetNameGenerator replaces srf.nameGenerator to newNameGenerator. func (srf *SizeRollingFile) SetNameGenerator(nameGenerator NameGenerator) { srf.mu.Lock() defer srf.mu.Unlock() srf.nameGenerator = nameGenerator } +// SetRollingHook replaces srf.rollingHook to newRollingHook. func (srf *SizeRollingFile) SetRollingHook(rollingHook RollingHook) { srf.mu.Lock() defer srf.mu.Unlock() diff --git a/logit.go b/logit.go index 20f08a1..ada58a0 100644 --- a/logit.go +++ b/logit.go @@ -38,7 +38,7 @@ func init() { } // newGlobalLogger returns a logger for global usage. -// Notice that it will try to load "./logit.conf" first, but a logger for console +// Notice that it will try to load "./logit.conf" first, but a logger to console // will be created if failed. func newGlobalLogger() *Logger { file, err := os.Open("./logit.conf") From b5fd7def5cd36dcaf4164faff29bb48ed9881982 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Sun, 14 Jun 2020 23:50:52 +0800 Subject: [PATCH 11/18] =?UTF-8?q?=E5=AE=8C=E5=96=84=20NameGenerator=20?= =?UTF-8?q?=E5=92=8C=20RollingHook=20=E4=B8=A4=E4=B8=AA=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- files/name_generator.go | 47 +++++++++++++ files/name_generator_test.go | 66 +++++++++++++++++++ files/rolling_hook.go | 123 +++++++++++++++++++++++++++++++++-- files/rolling_hook_test.go | 64 +++++++++++++++++- handler.go | 8 +-- 5 files changed, 297 insertions(+), 11 deletions(-) create mode 100644 files/name_generator_test.go diff --git a/files/name_generator.go b/files/name_generator.go index 6c3ccff..747fa38 100644 --- a/files/name_generator.go +++ b/files/name_generator.go @@ -19,12 +19,28 @@ package files import ( + "errors" + "fmt" "math/rand" + "os" "path/filepath" "strconv" + "sync" "time" ) +var ( + // nameGenerators stores all nameGenerators registered. + // mutexOfNameGenerators is for concurrency. + nameGenerators = map[string]NameGenerator{ + "default": DefaultNameGenerator(), + } + mutexOfNameGenerators = &sync.RWMutex{} + + // NameGeneratorIsExistedError is an error happening on repeating nameGenerator name. + NameGeneratorIsExistedError = errors.New("the name of nameGenerator you want to register already exists! May be you should give it an another name") +) + // NameGenerator is the type for generating the name of every created file. // You can customize your format of filename by implementing this function. // The two parameters string and time.Time is useful. The string parameter is the directory @@ -37,6 +53,37 @@ func (ng NameGenerator) NextName(directory string, now time.Time) string { return ng(directory, now) } +// RegisterNameGenerator registers your nameGenerator to logit so that you can use them in config file. +// Return an error if the name is existed, and you should change another name for your nameGenerator. +func RegisterNameGenerator(name string, nameGenerator NameGenerator) error { + mutexOfNameGenerators.Lock() + defer mutexOfNameGenerators.Unlock() + + if _, ok := nameGenerators[name]; ok { + return NameGeneratorIsExistedError + } + nameGenerators[name] = nameGenerator + return nil +} + +// nameGeneratorOf returns nameGenerator whose name is given name. +// Notice that we use tips+exit mechanism to check the name. +// This is a more convenient way to use nameGenerator (we think). +// so if the nameGenerator doesn't exist, a tip will be printed and +// the program will exit with status code 10. +func nameGeneratorOf(name string) NameGenerator { + mutexOfNameGenerators.RLock() + defer mutexOfNameGenerators.RUnlock() + nameGenerator, ok := nameGenerators[name] + if !ok { + fmt.Fprintf(os.Stderr, "Error: The nameGenerator \"%s\" doesn't exist! Please change it to another nameGenerator.\n", name) + os.Exit(10) + } + return nameGenerator +} + +// ================================== default name generator ================================== + // DefaultNameGenerator returns a name generator that creates a time-relative filename // with given now time. Also, it uses random number to ensure this filename is available. // The filename will be like "20200304-145246-45.log". diff --git a/files/name_generator_test.go b/files/name_generator_test.go new file mode 100644 index 0000000..8b6e1cb --- /dev/null +++ b/files/name_generator_test.go @@ -0,0 +1,66 @@ +// Copyright 2020 Ye Zi Jie. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Author: FishGoddess +// Email: fishgoddess@qq.com +// Created at 2020/06/14 23:11:04 + +package files + +import ( + "testing" + "time" +) + +// 测试注册 nameGenerator 的方法 +func TestRegisterNameGenerator(t *testing.T) { + + // default 已经存在,测试是否报错 + err := RegisterNameGenerator("default", func(directory string, now time.Time) string { + return "" + }) + if err == nil { + t.Fatal(err) + } + + // test 不存在,测试是否报错 + err = RegisterNameGenerator("TestRegisterNameGenerator", func(directory string, now time.Time) string { + return "test" + }) + if err != nil { + t.Fatal(err) + } + + nameGenerator := nameGeneratorOf("TestRegisterNameGenerator") + if nameGenerator.NextName("", time.Now()) != "test" { + t.Fatal("注册可能失败了,获取也可能是失败了...") + } +} + +// 测试 nameGeneratorOf 方法 +func TestNameGeneratorOf(t *testing.T) { + + // 先注册,再获取 + err := RegisterNameGenerator("TestNameGeneratorOf", func(directory string, now time.Time) string { + return "test" + }) + if err != nil { + t.Fatal(err) + } + + nameGenerator := nameGeneratorOf("TestNameGeneratorOf") + if nameGenerator.NextName("", time.Now()) != "test" { + t.Fatal("注册可能失败了,获取也可能是失败了...") + } +} diff --git a/files/rolling_hook.go b/files/rolling_hook.go index 9ea5214..3c6de17 100644 --- a/files/rolling_hook.go +++ b/files/rolling_hook.go @@ -19,12 +19,28 @@ package files import ( + "errors" + "fmt" "io/ioutil" "os" "path/filepath" + "sync" "time" ) +var ( + // rollingHooks stores all rollingHooks registered. + // mutexOfRollingHooks is for concurrency. + rollingHooks = map[string]func(params map[string]interface{}) RollingHook{ + "default": newDefaultRollingHookFunc, + "life": newLifeBasedRollingHookFunc, + } + mutexOfRollingHooks = &sync.RWMutex{} + + // RollingHookIsExistedError is an error happening on repeating rollingHook name. + RollingHookIsExistedError = errors.New("the name of rollingHook you want to register already exists! May be you should give it an another name") +) + // RollingHook is a hook that will be invoked in rolling process. // This interface has two method: BeforeRolling and AfterRolling. // BeforeRolling will be called before rolling to next file. @@ -35,6 +51,101 @@ type RollingHook interface { AfterRolling() } +// RegisterRollingHook registers your rollingHook to logit so that you can use them in config file. +// Return an error if the name is existed, and you should change another name for your rollingHook. +// Notice that newRollingHook has a parameter called params, which will be injected into newRollingHook +// by logit automatically. Different rollingHook may have different params, so what params should +// be injected into newRollingHook is dependent to specific rollingHook. Actually, this params is a +// mapping of config file. All params you write in config file will be injected here. +// For example, your config file is like this: +// +// "rollingHook": { +// "myRollingHook": { +// "db": "127.0.0.1:3306", +// "user": "me", +// "password": "you guess?", +// "maxConnections": 1024 +// } +// } +// +// Then a map[string]interface{} { +// "db": "127.0.0.1:3306", +// "user": "me", +// "password": "you guess?", +// "maxConnections": 1024 +// } will be injected to params. +// +// So you can use these params written in config file. +func RegisterRollingHook(name string, newRollingHook func(params map[string]interface{}) RollingHook) error { + mutexOfRollingHooks.Lock() + defer mutexOfRollingHooks.Unlock() + if _, ok := rollingHooks[name]; ok { + return RollingHookIsExistedError + } + rollingHooks[name] = newRollingHook + return nil +} + +// rollingHookOf returns rollingHook whose name is given name. +// Notice that we use tips+exit mechanism to check the name. +// This is a more convenient way to use rollingHook (we think). +// so if the rollingHook doesn't exist, a tip will be printed and +// the program will exit with status code 11. +func rollingHookOf(name string, params map[string]interface{}) RollingHook { + mutexOfRollingHooks.RLock() + defer mutexOfRollingHooks.RUnlock() + newRollingHook, ok := rollingHooks[name] + if !ok { + fmt.Fprintf(os.Stderr, "Error: The rollingHook \"%s\" doesn't exist! Please change it to another rollingHook.\n", name) + os.Exit(11) + } + return newRollingHook(params) +} + +// ================================ for code-readable ================================ + +// newDefaultRollingHookFunc creates a default rolling hook with given params. +// After registering to logit, you can use it in config file, try this: +// +// "rollingHook": { +// "default": {} +// } +// +func newDefaultRollingHookFunc(params map[string]interface{}) RollingHook { + return NewDefaultRollingHook() +} + +// newLifeBasedRollingHookFunc creates a life based rolling hook with params. +// After registering to logit, you can use it in config file, try this: +// +// "rollingHook": { +// "life": { +// "limit": 60, +// "directory": "D:/logs" +// } +// } +// +// Notice that the unit of limit is second, and default value of limit is one day. +// Directory is the target storing all files, and it is a necessary parameter. +func newLifeBasedRollingHookFunc(params map[string]interface{}) RollingHook { + + // limit 是寿命,单位是秒,默认值是一天 + limit := 24 * 60 * 60 + if param, ok := params["limit"]; ok { + limit = int(param.(float64)) + } + + // directory 是要监控的文件目录,一般配置的就是几个 rollingFile 中的 directory,否则没有意义 + // 另外,这个值是必须的,不然就没法正常工作 + directory, ok := params["directory"] + if !ok { + fmt.Fprintln(os.Stderr, "Error: The rollingHook life misses a necessary parameter \"directory\"!") + os.Exit(12) + } + + return NewLifeBasedRollingHook(directory.(string), time.Duration(limit)*time.Second) +} + // ============================= default rolling hook ============================= // DefaultRollingHook is default rolling hook, which will do nothing on rolling to next file. @@ -55,7 +166,7 @@ func (drh *DefaultRollingHook) AfterRolling() { // Do nothing... } -// ============================= life rolling hook ============================= +// ============================= life based rolling hook ============================= // LifeBasedRollingHook is a life based rolling hook. // It will tag a life on every file and if life runs out, this file will be removed. @@ -64,20 +175,20 @@ type LifeBasedRollingHook struct { // DefaultRollingHook has default implement and BeforeRolling is reserved. *DefaultRollingHook - // life is the life of every file. - life time.Duration - // directory is the target storing all files need to monitor. directory string + + // life is the life of every file. + life time.Duration } // NewLifeBasedRollingHook returns a LifeBasedRollingHook holder. // life is the life of every file and directory is the target storing all files need to monitor. -func NewLifeBasedRollingHook(life time.Duration, directory string) *LifeBasedRollingHook { +func NewLifeBasedRollingHook(directory string, life time.Duration) *LifeBasedRollingHook { return &LifeBasedRollingHook{ DefaultRollingHook: &DefaultRollingHook{}, - life: life, directory: directory, + life: life, } } diff --git a/files/rolling_hook_test.go b/files/rolling_hook_test.go index 213fb60..36b71e8 100644 --- a/files/rolling_hook_test.go +++ b/files/rolling_hook_test.go @@ -19,6 +19,7 @@ package files import ( + "fmt" "io/ioutil" "testing" "time" @@ -62,7 +63,7 @@ func TestLifeRollingHookAfterRolling(t *testing.T) { } // 创建基于生命周期的滚动钩子器 - rollingHook := NewLifeBasedRollingHook(4*time.Second, directory) + rollingHook := NewLifeBasedRollingHook(directory, 4*time.Second) // 开始测试 // 判断滚动之前的文件数量是否正确 @@ -76,3 +77,64 @@ func TestLifeRollingHookAfterRolling(t *testing.T) { rollingHook.AfterRolling() checkFileCountInDirectory(3) } + +// 测试用的 rollingHook +type testRollingHook struct{} + +func (trh *testRollingHook) BeforeRolling() { + fmt.Println("testRollingHook.BeforeRolling()...") +} + +func (trh *testRollingHook) AfterRolling() { + fmt.Println("testRollingHook.AfterRolling()...") +} + +// 测试注册 rollingHook 的方法 +func TestRegisterRollingHook(t *testing.T) { + + // default 已经存在,测试是否报错 + err := RegisterRollingHook("default", func(params map[string]interface{}) RollingHook { + return &testRollingHook{} + }) + if err == nil { + t.Fatal("name 为 default 的 rollingHook 已经存在,本来要报错的,但是没有报错") + } + + // life 已经存在,测试是否报错 + err = RegisterRollingHook("life", func(params map[string]interface{}) RollingHook { + return &testRollingHook{} + }) + if err == nil { + t.Fatal("name 为 life 的 rollingHook 已经存在,本来要报错的,但是没有报错") + } + + // test 不存在,测试是否报错 + err = RegisterRollingHook("TestRegisterRollingHook", func(params map[string]interface{}) RollingHook { + return &testRollingHook{} + }) + if err != nil { + t.Fatal("name 为 test 的 rollingHook 不存在,不应该报错的,但是报错了") + } + + rollingHook := rollingHookOf("TestRegisterRollingHook", map[string]interface{}{}) + rollingHook.BeforeRolling() + rollingHook.AfterRolling() + + //rollingHookOf("noExist", map[string]interface{}{}) +} + +// 测试 rollingHookOf 方法 +func TestRollingHookOf(t *testing.T) { + + // 先注册,再获取 + err := RegisterRollingHook("TestRollingHookOf", func(params map[string]interface{}) RollingHook { + return &testRollingHook{} + }) + if err != nil { + t.Fatal(err) + } + + rollingHook := rollingHookOf("TestRollingHookOf", map[string]interface{}{}) + rollingHook.BeforeRolling() + rollingHook.AfterRolling() +} diff --git a/handler.go b/handler.go index 71f14c2..ade8a30 100644 --- a/handler.go +++ b/handler.go @@ -32,12 +32,12 @@ const ( ) var ( - // handlers store all handlers registered. + // handlers stores all handlers registered. // mutexOfHandlers is for concurrency. handlers = map[string]func(params map[string]interface{}) Handler{} mutexOfHandlers = &sync.RWMutex{} - // HandlerIsExistedError is an error happens on repeating handler name. + // HandlerIsExistedError is an error happening on repeating handler name. HandlerIsExistedError = errors.New("the name of handler you want to register already exists! May be you should give it an another name") ) @@ -55,7 +55,7 @@ type Handler interface { Handle(log *Log) bool } -// RegisterHandler registers your handler to logit so that you can use them easily. +// RegisterHandler registers your handler to logit so that you can use them in config file. // Return an error if the name is existed, and you should change another name for your handler. // Notice that newHandler has a parameter called params, which will be injected into newHandler // by logit automatically. Different handler may have different params, so what params should @@ -64,7 +64,7 @@ type Handler interface { // For example, your config file is like this: // // "handlers": { -// "my-handler": { +// "myHandler": { // "db": "127.0.0.1:3306", // "user": "me", // "password": "you guess?", From 7d7faebbdc3c72e0c282460facd3c3768d1a4123 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Tue, 23 Jun 2020 17:19:01 +0800 Subject: [PATCH 12/18] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E9=80=82=E9=85=8D?= =?UTF-8?q?=E6=96=B0=E7=9A=84=20rolling=20file=20=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FUTURE.md | 3 ++ handler_extension.go | 65 +++++++++++++++++++++++++++++++++++++-- handler_extension_test.go | 12 ++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/FUTURE.md b/FUTURE.md index d600b58..7eaec33 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -1,5 +1,8 @@ ## ✒ 未来版本的新特性 (Features in future version) +### v0.2.8 +* 尝试整理包结构,精简 logit 包下的 API + ### v0.2.7 * 加入日志存活个数的特性 diff --git a/handler_extension.go b/handler_extension.go index 4a854b5..eca5f3f 100644 --- a/handler_extension.go +++ b/handler_extension.go @@ -296,8 +296,7 @@ func NewFileHandler(path string, encoder Encoder, timeFormat string) Handler { // See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. // See files.NewDurationRollingFile. func NewDurationRollingHandler(directory string, limit time.Duration, encoder Encoder, timeFormat string) Handler { - file := files.NewDurationRollingFile(directory, limit) - return NewStandardHandler(file, encoder, timeFormat) + return NewDurationRollingHandlerWithOptions(directory, limit, encoder, timeFormat, RollingHandlerOptions{}) } // NewSizeRollingHandler returns a handler which uses @@ -307,6 +306,68 @@ func NewDurationRollingHandler(directory string, limit time.Duration, encoder En // See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. // See files.NewSizeRollingFile. func NewSizeRollingHandler(directory string, limit int64, encoder Encoder, timeFormat string) Handler { + return NewSizeRollingHandlerWithOptions(directory, limit, encoder, timeFormat, RollingHandlerOptions{}) +} + +// RollingHandlerOptions includes two options for creating duration rolling handler +// and size rolling handler. NameGenerator is for generating the name of created files, +// and rollingHook is a hook which will be triggered on rolling to next file. +// See files.NameGenerator and files.RollingHook. +type RollingHandlerOptions struct { + nameGenerator files.NameGenerator + rollingHook files.RollingHook +} + +// filledDurationRollingFileWithOptions fills file with non-nil options. +func filledDurationRollingFileWithOptions(file *files.DurationRollingFile, options RollingHandlerOptions) { + if options.nameGenerator != nil { + file.SetNameGenerator(options.nameGenerator) + } + if options.rollingHook != nil { + file.SetRollingHook(options.rollingHook) + } +} + +// filledSizeRollingFileWithOptions fills file with non-nil options. +func filledSizeRollingFileWithOptions(file *files.SizeRollingFile, options RollingHandlerOptions) { + if options.nameGenerator != nil { + file.SetNameGenerator(options.nameGenerator) + } + if options.rollingHook != nil { + file.SetRollingHook(options.rollingHook) + } +} + +// NewDurationRollingHandlerWithOptions returns a handler which uses +// a duration rolling file to write logs. The limit is duration, and +// each duration has its own log file. Also you can point a directory +// to be used to store all created log files. Notice that you should +// point an options object which includes nameGenerator and rollingHook. +// NameGenerator is for generating the name of created files, and rollingHook +// is a hook which will be triggered on rolling to next file. However, +// both of them is not necessary, so if one of them is nil then this "one" +// option will be ignored. See logit.RollingHandlerOptions. +// See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. +// See files.NewDurationRollingFile. +func NewDurationRollingHandlerWithOptions(directory string, limit time.Duration, encoder Encoder, timeFormat string, options RollingHandlerOptions) Handler { + file := files.NewDurationRollingFile(directory, limit) + filledDurationRollingFileWithOptions(file, options) + return NewStandardHandler(file, encoder, timeFormat) +} + +// NewSizeRollingHandlerWithOptions returns a handler which uses +// a size rolling file to write logs. The limit is the max size of log file, +// and the log file will switch to a new one after reaching to max size. +// Also you can point a directory to be used to store all created log files. +// Notice that you should point an options object which includes nameGenerator +// and rollingHook. NameGenerator is for generating the name of created files, +// and rollingHook is a hook which will be triggered on rolling to next file. +// However, both of them is not necessary, so if one of them is nil then this "one" +// option will be ignored. See logit.RollingHandlerOptions. +// See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. +// See files.NewSizeRollingFile. +func NewSizeRollingHandlerWithOptions(directory string, limit int64, encoder Encoder, timeFormat string, options RollingHandlerOptions) Handler { file := files.NewSizeRollingFile(directory, limit) + filledSizeRollingFileWithOptions(file, options) return NewStandardHandler(file, encoder, timeFormat) } diff --git a/handler_extension_test.go b/handler_extension_test.go index 2b1ab5e..0da2bfd 100644 --- a/handler_extension_test.go +++ b/handler_extension_test.go @@ -66,3 +66,15 @@ func TestNewSizeRollingHandler(t *testing.T) { logger.Error("error...") } } + +// 测试创建空的 RollingHandlerOptions +func TestRollingHandlerOptions(t *testing.T) { + options := RollingHandlerOptions{} + t.Log(options) + if options.nameGenerator != nil { + t.Fatal("nameGenerator 应该是 nil 的,结果不是。。。") + } + if options.rollingHook != nil { + t.Fatal("rollingHook 应该是 nil 的,结果不是。。。") + } +} From 4f5d0bee815f5a7244a88707c99fb21ab746fa95 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Tue, 23 Jun 2020 17:46:32 +0800 Subject: [PATCH 13/18] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=B8=A4=E4=B8=AA=20ro?= =?UTF-8?q?lling=20handler=EF=BC=8C=E5=BE=85=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- files/name_generator.go | 4 ++-- files/name_generator_test.go | 4 ++-- files/rolling_hook.go | 4 ++-- files/rolling_hook_test.go | 4 ++-- handler_extension.go | 33 +++++++++++++++++++++++++++++++-- 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/files/name_generator.go b/files/name_generator.go index 747fa38..7299d77 100644 --- a/files/name_generator.go +++ b/files/name_generator.go @@ -66,12 +66,12 @@ func RegisterNameGenerator(name string, nameGenerator NameGenerator) error { return nil } -// nameGeneratorOf returns nameGenerator whose name is given name. +// NameGeneratorOf returns nameGenerator whose name is given name. // Notice that we use tips+exit mechanism to check the name. // This is a more convenient way to use nameGenerator (we think). // so if the nameGenerator doesn't exist, a tip will be printed and // the program will exit with status code 10. -func nameGeneratorOf(name string) NameGenerator { +func NameGeneratorOf(name string) NameGenerator { mutexOfNameGenerators.RLock() defer mutexOfNameGenerators.RUnlock() nameGenerator, ok := nameGenerators[name] diff --git a/files/name_generator_test.go b/files/name_generator_test.go index 8b6e1cb..dfe4b66 100644 --- a/files/name_generator_test.go +++ b/files/name_generator_test.go @@ -42,7 +42,7 @@ func TestRegisterNameGenerator(t *testing.T) { t.Fatal(err) } - nameGenerator := nameGeneratorOf("TestRegisterNameGenerator") + nameGenerator := NameGeneratorOf("TestRegisterNameGenerator") if nameGenerator.NextName("", time.Now()) != "test" { t.Fatal("注册可能失败了,获取也可能是失败了...") } @@ -59,7 +59,7 @@ func TestNameGeneratorOf(t *testing.T) { t.Fatal(err) } - nameGenerator := nameGeneratorOf("TestNameGeneratorOf") + nameGenerator := NameGeneratorOf("TestNameGeneratorOf") if nameGenerator.NextName("", time.Now()) != "test" { t.Fatal("注册可能失败了,获取也可能是失败了...") } diff --git a/files/rolling_hook.go b/files/rolling_hook.go index 3c6de17..43c5393 100644 --- a/files/rolling_hook.go +++ b/files/rolling_hook.go @@ -86,12 +86,12 @@ func RegisterRollingHook(name string, newRollingHook func(params map[string]inte return nil } -// rollingHookOf returns rollingHook whose name is given name. +// RollingHookOf returns rollingHook whose name is given name. // Notice that we use tips+exit mechanism to check the name. // This is a more convenient way to use rollingHook (we think). // so if the rollingHook doesn't exist, a tip will be printed and // the program will exit with status code 11. -func rollingHookOf(name string, params map[string]interface{}) RollingHook { +func RollingHookOf(name string, params map[string]interface{}) RollingHook { mutexOfRollingHooks.RLock() defer mutexOfRollingHooks.RUnlock() newRollingHook, ok := rollingHooks[name] diff --git a/files/rolling_hook_test.go b/files/rolling_hook_test.go index 36b71e8..c86f7ad 100644 --- a/files/rolling_hook_test.go +++ b/files/rolling_hook_test.go @@ -116,7 +116,7 @@ func TestRegisterRollingHook(t *testing.T) { t.Fatal("name 为 test 的 rollingHook 不存在,不应该报错的,但是报错了") } - rollingHook := rollingHookOf("TestRegisterRollingHook", map[string]interface{}{}) + rollingHook := RollingHookOf("TestRegisterRollingHook", map[string]interface{}{}) rollingHook.BeforeRolling() rollingHook.AfterRolling() @@ -134,7 +134,7 @@ func TestRollingHookOf(t *testing.T) { t.Fatal(err) } - rollingHook := rollingHookOf("TestRollingHookOf", map[string]interface{}{}) + rollingHook := RollingHookOf("TestRollingHookOf", map[string]interface{}{}) rollingHook.BeforeRolling() rollingHook.AfterRolling() } diff --git a/handler_extension.go b/handler_extension.go index eca5f3f..b79da3e 100644 --- a/handler_extension.go +++ b/handler_extension.go @@ -159,7 +159,8 @@ func registerDurationRollingHandler() { // 滚动的时间间隔,单位是秒,默认是 1 天 limit, directory := limitAndDirectoryOf(params, 24*60*60, "./") encoder, timeFormat := encoderAndTimeFormatOf(params, TextEncoder(), DefaultTimeFormat) - return NewDurationRollingHandler(directory, time.Duration(limit)*time.Second, encoder, timeFormat) + options := rollingHandlerOptionsOf(params, files.DefaultNameGenerator(), files.NewDefaultRollingHook()) + return NewDurationRollingHandlerWithOptions(directory, time.Duration(limit)*time.Second, encoder, timeFormat, options) }) } @@ -207,7 +208,8 @@ func registerSizeRollingHandler() { // 滚动的文件大小,单位是 MB,默认是 64 MB limit, directory := limitAndDirectoryOf(params, 64, "./") encoder, timeFormat := encoderAndTimeFormatOf(params, TextEncoder(), DefaultTimeFormat) - return NewSizeRollingHandler(directory, int64(limit)*files.MB, encoder, timeFormat) + options := rollingHandlerOptionsOf(params, files.DefaultNameGenerator(), files.NewDefaultRollingHook()) + return NewSizeRollingHandlerWithOptions(directory, int64(limit)*files.MB, encoder, timeFormat, options) }) } @@ -268,6 +270,33 @@ func limitAndDirectoryOf(params map[string]interface{}, defaultLimit int, defaul return limit, directory } +// rollingHandlerOptionsOf returns rolling handler options in this params. +// defaultNameGenerator and defaultRollingHook will be used if you don't set to params. +func rollingHandlerOptionsOf(params map[string]interface{}, defaultNameGenerator files.NameGenerator, defaultRollingHook files.RollingHook) RollingHandlerOptions { + + // 获取名字生成器配置 + nameGenerator := defaultNameGenerator + if param, ok := params["nameGenerator"]; ok { + nameGenerator = files.NameGeneratorOf(param.(string)) + } + + // 获取滚动钩子配置 + rollingHook := defaultRollingHook + if _, ok := params["rollingHook"]; ok { + param := params["rollingHook"].(map[string]map[string]interface{}) + for rollingHookName, rollingHookParams := range param { + rollingHook = files.RollingHookOf(rollingHookName, rollingHookParams) + break + } + } + + // 返回配置结果 + return RollingHandlerOptions{ + nameGenerator: nameGenerator, + rollingHook: rollingHook, + } +} + // =============================== for public users =============================== // NewConsoleHandler returns a handler for console. From 34d4db77979b4b274185584dbbcd8d96b16a0b8c Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Tue, 23 Jun 2020 22:54:27 +0800 Subject: [PATCH 14/18] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=9A=84=E6=97=B6?= =?UTF-8?q?=E5=80=99=E5=8F=91=E7=8E=B0=E4=BA=86=E4=B8=80=E4=BA=9B=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handler_extension.go | 4 ++-- handler_extension_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/handler_extension.go b/handler_extension.go index b79da3e..e02eb12 100644 --- a/handler_extension.go +++ b/handler_extension.go @@ -185,7 +185,7 @@ func registerDurationRollingHandler() { // use your layout to format time, try this: // // "handlers": { -// "duration": { +// "size": { // "limit": 16, // "directory": "D:/logs", // "timeFormat": "2006-01-02" @@ -195,7 +195,7 @@ func registerDurationRollingHandler() { // Want a Json string? Try this: // // "handlers":{ -// "duration":{ +// "size":{ // "limit": 16, // "encoder": "json", // "directory": "D:/logs", diff --git a/handler_extension_test.go b/handler_extension_test.go index 0da2bfd..a36aba8 100644 --- a/handler_extension_test.go +++ b/handler_extension_test.go @@ -19,6 +19,7 @@ package logit import ( + "io/ioutil" "os" "path/filepath" "strconv" @@ -78,3 +79,37 @@ func TestRollingHandlerOptions(t *testing.T) { t.Fatal("rollingHook 应该是 nil 的,结果不是。。。") } } + +func TestNewDurationRollingHandlerWithOptions(t *testing.T) { + + // 准备测试环境 + testDirectory, err := ioutil.TempDir("", "TestNewDurationRollingHandlerWithOptions_*") + if err != nil { + t.Fatal(err) + } + + // 创建日志处理器 + handler := NewDurationRollingHandlerWithOptions(testDirectory, time.Second, TextEncoder(), "", RollingHandlerOptions{ + nameGenerator: func(directory string, now time.Time) string { + return filepath.Join(directory, now.Format("2006年01月02日-15点04分05秒.log")) + }, + rollingHook: files.NewLifeBasedRollingHook(testDirectory, 500*time.Millisecond), + }) + + // TODO 这个测试通不过,发现 rollingHook 中的 now.Sub(file.ModTime()) 是负数。。。 + // TODO 这个 RollingHook 机制需要取消掉,因为涉及到太多原本的代码了,考虑使用定时任务解决 + logger := NewLogger(DebugLevel, handler) + logger.Info("1") + time.Sleep(900 * time.Millisecond) + logger.Info("2") + time.Sleep(1 * time.Second) + logger.Info("3") + time.Sleep(1200 * time.Millisecond) + logger.Info("4") + + infos, err := ioutil.ReadDir(testDirectory) + if err != nil || len(infos) != 2 { + t.Log("len(infos) == 2 ? ", len(infos) == 2) + t.Fatal(err) + } +} From 22a0d9de10f507357bf39da8e9e4e6cd7b6b11e4 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Wed, 24 Jun 2020 14:51:42 +0800 Subject: [PATCH 15/18] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=BA=86=20RollingHook?= =?UTF-8?q?=20=E7=BB=84=E4=BB=B6=EF=BC=8C=E8=80=83=E8=99=91=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=9B=B4=E5=A5=BD=E7=9A=84=E6=96=B9=E5=BC=8F=E5=8E=BB?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=96=B0=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _examples/files.go | 6 - files/doc.go | 6 - files/duration_rolling_file.go | 17 --- files/duration_rolling_file_test.go | 34 +---- files/rolling_hook.go | 210 ---------------------------- files/rolling_hook_test.go | 140 ------------------- files/size_rolling_file.go | 32 +---- files/size_rolling_file_test.go | 38 +---- handler_extension.go | 98 +------------ handler_extension_test.go | 47 ------- 10 files changed, 10 insertions(+), 618 deletions(-) delete mode 100644 files/rolling_hook.go delete mode 100644 files/rolling_hook_test.go diff --git a/_examples/files.go b/_examples/files.go index d7c6b0c..482ba21 100644 --- a/_examples/files.go +++ b/_examples/files.go @@ -41,9 +41,6 @@ func main() { return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) }) - // What's more, you can add a hook in rolling process, see files.RollingHook. - //durationRollingFile.SetRollingHook(xxx) - // ================================================================================= // 2. SizeRollingFile is a file size sensitive file. @@ -59,7 +56,4 @@ func main() { // now is the time calling this method. return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) }) - - // What's more, you can add a hook in rolling process, see files.RollingHook. - //sizeRollingFile.SetRollingHook(xxx) } diff --git a/files/doc.go b/files/doc.go index ee9b055..6d0d2f4 100644 --- a/files/doc.go +++ b/files/doc.go @@ -35,9 +35,6 @@ Package files provides some writers to extend your logger. return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) }) - // What's more, you can add a hook in rolling process, see files.RollingHook. - //durationRollingFile.SetRollingHook(xxx) - 2. SizeRollingFile: // SizeRollingFile is a file size sensitive file. @@ -54,8 +51,5 @@ Package files provides some writers to extend your logger. return filepath.Join(directory, now.Format("2006-01-02-15-04-05.log")) }) - // What's more, you can add a hook in rolling process, see files.RollingHook. - //sizeRollingFile.SetRollingHook(xxx) - */ package files // import "github.com/FishGoddess/logit/files" diff --git a/files/duration_rolling_file.go b/files/duration_rolling_file.go index b65e8c2..d9554af 100644 --- a/files/duration_rolling_file.go +++ b/files/duration_rolling_file.go @@ -56,13 +56,6 @@ type DurationRollingFile struct { // Default is DefaultNameGenerator(). nameGenerator NameGenerator - // rollingHook is a hook that will be invoked in rolling process. - // This interface has two method: BeforeRolling and AfterRolling. - // BeforeRolling will be called before rolling to next file. - // AfterRolling will be called after rolling to next file. - // Default is DefaultRollingHook, and it will do nothing when rolling to next file. - rollingHook RollingHook - // mu is a lock for safe concurrency. mu *sync.Mutex } @@ -90,7 +83,6 @@ func NewDurationRollingFile(directory string, duration time.Duration) *DurationR directory: directory, duration: duration, nameGenerator: DefaultNameGenerator(), - rollingHook: NewDefaultRollingHook(), mu: &sync.Mutex{}, } } @@ -114,9 +106,7 @@ func (drf *DurationRollingFile) rollingToNextFile(now time.Time) { func (drf *DurationRollingFile) ensureFileIsCorrect() { now := time.Now() if drf.file == nil || now.Sub(drf.lastTime) >= drf.duration { - drf.rollingHook.BeforeRolling() drf.rollingToNextFile(now) - drf.rollingHook.AfterRolling() } } @@ -146,10 +136,3 @@ func (drf *DurationRollingFile) SetNameGenerator(newNameGenerator NameGenerator) defer drf.mu.Unlock() drf.nameGenerator = newNameGenerator } - -// SetRollingHook replaces drf.rollingHook to newRollingHook. -func (drf *DurationRollingFile) SetRollingHook(newRollingHook RollingHook) { - drf.mu.Lock() - defer drf.mu.Unlock() - drf.rollingHook = newRollingHook -} diff --git a/files/duration_rolling_file_test.go b/files/duration_rolling_file_test.go index a553569..76a4241 100644 --- a/files/duration_rolling_file_test.go +++ b/files/duration_rolling_file_test.go @@ -19,7 +19,6 @@ package files import ( - "fmt" "io/ioutil" "os" "path/filepath" @@ -37,7 +36,7 @@ func TestNewDurationRollingFile(t *testing.T) { }() // 创建临时测试文件夹 - root, err := ioutil.TempDir("", "TestNewDurationRollingFile_") + root, err := ioutil.TempDir("", "TestNewDurationRollingFile_*") if err != nil { t.Fatal(err) } @@ -75,7 +74,7 @@ func TestNewDurationRollingFile(t *testing.T) { // 测试名字生成器的设置方法 func TestDurationRollingFileSetNameGenerator(t *testing.T) { - dir, err := ioutil.TempDir("", "TestDurationRollingFileSetNameGenerator_") + dir, err := ioutil.TempDir("", "TestDurationRollingFileSetNameGenerator_*") if err != nil { t.Fatal(err) } @@ -92,32 +91,3 @@ func TestDurationRollingFileSetNameGenerator(t *testing.T) { time.Sleep(2 * time.Second) file.Write([]byte("hi!")) } - -// 测试使用的 rolling hook -type testDurationRollingFileRollingHook struct{} - -func (tdrfrh *testDurationRollingFileRollingHook) BeforeRolling() { - fmt.Println("before rolling...") -} - -func (tdrfrh *testDurationRollingFileRollingHook) AfterRolling() { - fmt.Println("after rolling...") -} - -// 测试名字生成器的设置方法 -func TestDurationRollingFileSetRollingHook(t *testing.T) { - - dir, err := ioutil.TempDir("", "TestDurationRollingFileSetRollingHook_") - if err != nil { - t.Fatal(err) - } - - file := NewDurationRollingFile(dir, 2*time.Second) - defer file.Close() - - file.Write([]byte("hello!")) - file.SetRollingHook(&testDurationRollingFileRollingHook{}) - - time.Sleep(2 * time.Second) - file.Write([]byte("hi!")) -} diff --git a/files/rolling_hook.go b/files/rolling_hook.go deleted file mode 100644 index 43c5393..0000000 --- a/files/rolling_hook.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2020 Ye Zi Jie. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Author: FishGoddess -// Email: fishgoddess@qq.com -// Created at 2020/06/12 22:08:31 - -package files - -import ( - "errors" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sync" - "time" -) - -var ( - // rollingHooks stores all rollingHooks registered. - // mutexOfRollingHooks is for concurrency. - rollingHooks = map[string]func(params map[string]interface{}) RollingHook{ - "default": newDefaultRollingHookFunc, - "life": newLifeBasedRollingHookFunc, - } - mutexOfRollingHooks = &sync.RWMutex{} - - // RollingHookIsExistedError is an error happening on repeating rollingHook name. - RollingHookIsExistedError = errors.New("the name of rollingHook you want to register already exists! May be you should give it an another name") -) - -// RollingHook is a hook that will be invoked in rolling process. -// This interface has two method: BeforeRolling and AfterRolling. -// BeforeRolling will be called before rolling to next file. -// AfterRolling will be called after rolling to next file. -// You can do your job in both of methods. -type RollingHook interface { - BeforeRolling() - AfterRolling() -} - -// RegisterRollingHook registers your rollingHook to logit so that you can use them in config file. -// Return an error if the name is existed, and you should change another name for your rollingHook. -// Notice that newRollingHook has a parameter called params, which will be injected into newRollingHook -// by logit automatically. Different rollingHook may have different params, so what params should -// be injected into newRollingHook is dependent to specific rollingHook. Actually, this params is a -// mapping of config file. All params you write in config file will be injected here. -// For example, your config file is like this: -// -// "rollingHook": { -// "myRollingHook": { -// "db": "127.0.0.1:3306", -// "user": "me", -// "password": "you guess?", -// "maxConnections": 1024 -// } -// } -// -// Then a map[string]interface{} { -// "db": "127.0.0.1:3306", -// "user": "me", -// "password": "you guess?", -// "maxConnections": 1024 -// } will be injected to params. -// -// So you can use these params written in config file. -func RegisterRollingHook(name string, newRollingHook func(params map[string]interface{}) RollingHook) error { - mutexOfRollingHooks.Lock() - defer mutexOfRollingHooks.Unlock() - if _, ok := rollingHooks[name]; ok { - return RollingHookIsExistedError - } - rollingHooks[name] = newRollingHook - return nil -} - -// RollingHookOf returns rollingHook whose name is given name. -// Notice that we use tips+exit mechanism to check the name. -// This is a more convenient way to use rollingHook (we think). -// so if the rollingHook doesn't exist, a tip will be printed and -// the program will exit with status code 11. -func RollingHookOf(name string, params map[string]interface{}) RollingHook { - mutexOfRollingHooks.RLock() - defer mutexOfRollingHooks.RUnlock() - newRollingHook, ok := rollingHooks[name] - if !ok { - fmt.Fprintf(os.Stderr, "Error: The rollingHook \"%s\" doesn't exist! Please change it to another rollingHook.\n", name) - os.Exit(11) - } - return newRollingHook(params) -} - -// ================================ for code-readable ================================ - -// newDefaultRollingHookFunc creates a default rolling hook with given params. -// After registering to logit, you can use it in config file, try this: -// -// "rollingHook": { -// "default": {} -// } -// -func newDefaultRollingHookFunc(params map[string]interface{}) RollingHook { - return NewDefaultRollingHook() -} - -// newLifeBasedRollingHookFunc creates a life based rolling hook with params. -// After registering to logit, you can use it in config file, try this: -// -// "rollingHook": { -// "life": { -// "limit": 60, -// "directory": "D:/logs" -// } -// } -// -// Notice that the unit of limit is second, and default value of limit is one day. -// Directory is the target storing all files, and it is a necessary parameter. -func newLifeBasedRollingHookFunc(params map[string]interface{}) RollingHook { - - // limit 是寿命,单位是秒,默认值是一天 - limit := 24 * 60 * 60 - if param, ok := params["limit"]; ok { - limit = int(param.(float64)) - } - - // directory 是要监控的文件目录,一般配置的就是几个 rollingFile 中的 directory,否则没有意义 - // 另外,这个值是必须的,不然就没法正常工作 - directory, ok := params["directory"] - if !ok { - fmt.Fprintln(os.Stderr, "Error: The rollingHook life misses a necessary parameter \"directory\"!") - os.Exit(12) - } - - return NewLifeBasedRollingHook(directory.(string), time.Duration(limit)*time.Second) -} - -// ============================= default rolling hook ============================= - -// DefaultRollingHook is default rolling hook, which will do nothing on rolling to next file. -type DefaultRollingHook struct{} - -// NewDefaultRollingHook returns a default rolling hook. -func NewDefaultRollingHook() *DefaultRollingHook { - return &DefaultRollingHook{} -} - -// BeforeRolling does nothing when rolling to next file. -func (drh *DefaultRollingHook) BeforeRolling() { - // Do nothing... -} - -// AfterRolling does nothing when rolling to next file. -func (drh *DefaultRollingHook) AfterRolling() { - // Do nothing... -} - -// ============================= life based rolling hook ============================= - -// LifeBasedRollingHook is a life based rolling hook. -// It will tag a life on every file and if life runs out, this file will be removed. -type LifeBasedRollingHook struct { - - // DefaultRollingHook has default implement and BeforeRolling is reserved. - *DefaultRollingHook - - // directory is the target storing all files need to monitor. - directory string - - // life is the life of every file. - life time.Duration -} - -// NewLifeBasedRollingHook returns a LifeBasedRollingHook holder. -// life is the life of every file and directory is the target storing all files need to monitor. -func NewLifeBasedRollingHook(directory string, life time.Duration) *LifeBasedRollingHook { - return &LifeBasedRollingHook{ - DefaultRollingHook: &DefaultRollingHook{}, - directory: directory, - life: life, - } -} - -// AfterRolling checks life of all files and removes the dead files. -// Remember, it will do nothing if this directory could not be read and modified. -func (lrh *LifeBasedRollingHook) AfterRolling() { - - fileInfos, err := ioutil.ReadDir(lrh.directory) - if err != nil { - return - } - - now := time.Now() - for _, file := range fileInfos { - if !file.IsDir() && now.Sub(file.ModTime()) >= lrh.life { - os.Remove(filepath.Join(lrh.directory, file.Name())) - } - } -} diff --git a/files/rolling_hook_test.go b/files/rolling_hook_test.go deleted file mode 100644 index c86f7ad..0000000 --- a/files/rolling_hook_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2020 Ye Zi Jie. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Author: FishGoddess -// Email: fishgoddess@qq.com -// Created at 2020/06/13 00:07:00 - -package files - -import ( - "fmt" - "io/ioutil" - "testing" - "time" -) - -// 测试 LifeRollingHook 的 AfterRolling 方法 -func TestLifeRollingHookAfterRolling(t *testing.T) { - - // 创建测试文件夹 - directory, err := ioutil.TempDir("", "TestLifeRollingHookAfterRolling_*") - if err != nil { - t.Fatal(err) - } - - t.Log(directory) - - // 创建测试文件 - for i := 0; i < 3; i++ { - file, err := ioutil.TempFile(directory, "test_file_*.log") - if err != nil { - t.Fatal(err) - } - file.Close() - - _, err = ioutil.TempDir(directory, "test_directory_*") - if err != nil { - t.Fatal(err) - } - - time.Sleep(2 * time.Second) - } - - // 判断测试文件是否准备成功 - checkFileCountInDirectory := func(count int) { - fileInfos, err := ioutil.ReadDir(directory) - t.Log("count:", count) - t.Log("len:", len(fileInfos)) - if err != nil || len(fileInfos) != count { - t.Fatal(err, len(fileInfos)) - } - } - - // 创建基于生命周期的滚动钩子器 - rollingHook := NewLifeBasedRollingHook(directory, 4*time.Second) - - // 开始测试 - // 判断滚动之前的文件数量是否正确 - checkFileCountInDirectory(6) - for i := 1; i <= 3; i++ { - rollingHook.AfterRolling() - checkFileCountInDirectory(6 - i) - time.Sleep(2 * time.Second) - } - - rollingHook.AfterRolling() - checkFileCountInDirectory(3) -} - -// 测试用的 rollingHook -type testRollingHook struct{} - -func (trh *testRollingHook) BeforeRolling() { - fmt.Println("testRollingHook.BeforeRolling()...") -} - -func (trh *testRollingHook) AfterRolling() { - fmt.Println("testRollingHook.AfterRolling()...") -} - -// 测试注册 rollingHook 的方法 -func TestRegisterRollingHook(t *testing.T) { - - // default 已经存在,测试是否报错 - err := RegisterRollingHook("default", func(params map[string]interface{}) RollingHook { - return &testRollingHook{} - }) - if err == nil { - t.Fatal("name 为 default 的 rollingHook 已经存在,本来要报错的,但是没有报错") - } - - // life 已经存在,测试是否报错 - err = RegisterRollingHook("life", func(params map[string]interface{}) RollingHook { - return &testRollingHook{} - }) - if err == nil { - t.Fatal("name 为 life 的 rollingHook 已经存在,本来要报错的,但是没有报错") - } - - // test 不存在,测试是否报错 - err = RegisterRollingHook("TestRegisterRollingHook", func(params map[string]interface{}) RollingHook { - return &testRollingHook{} - }) - if err != nil { - t.Fatal("name 为 test 的 rollingHook 不存在,不应该报错的,但是报错了") - } - - rollingHook := RollingHookOf("TestRegisterRollingHook", map[string]interface{}{}) - rollingHook.BeforeRolling() - rollingHook.AfterRolling() - - //rollingHookOf("noExist", map[string]interface{}{}) -} - -// 测试 rollingHookOf 方法 -func TestRollingHookOf(t *testing.T) { - - // 先注册,再获取 - err := RegisterRollingHook("TestRollingHookOf", func(params map[string]interface{}) RollingHook { - return &testRollingHook{} - }) - if err != nil { - t.Fatal(err) - } - - rollingHook := RollingHookOf("TestRollingHookOf", map[string]interface{}{}) - rollingHook.BeforeRolling() - rollingHook.AfterRolling() -} diff --git a/files/size_rolling_file.go b/files/size_rolling_file.go index 73890de..b221722 100644 --- a/files/size_rolling_file.go +++ b/files/size_rolling_file.go @@ -65,24 +65,11 @@ type SizeRollingFile struct { // won't waste the time we spent on file.Stat() ^_^. currentSize int64 - // nextFilename is a function for generating next file name. - // Every times rolling to next file will call it first. - // now is the time of calling this function, also the - // created time of next file. - nextFilename func(now time.Time) string - // nameGenerator is for generating the name of every created file. // You can customize your format of filename by implementing this function. // Default is DefaultNameGenerator(). nameGenerator NameGenerator - // rollingHook is a hook that will be invoked in rolling process. - // This interface has two method: BeforeRolling and AfterRolling. - // BeforeRolling will be called before rolling to next file. - // AfterRolling will be called after rolling to next file. - // Default is DefaultRollingHook, and it will do nothing when rolling to next file. - rollingHook RollingHook - // mu is a lock for safe concurrency. mu *sync.Mutex } @@ -111,18 +98,10 @@ func NewSizeRollingFile(directory string, limitedSize int64) *SizeRollingFile { limitedSize: limitedSize, currentSize: 0, nameGenerator: DefaultNameGenerator(), - rollingHook: NewDefaultRollingHook(), mu: &sync.Mutex{}, } } -// doRollingTask does the whole task of rolling process. -func (srf *SizeRollingFile) doRollingTask() { - srf.rollingHook.BeforeRolling() - srf.rollingToNextFile(time.Now()) - srf.rollingHook.AfterRolling() -} - // rollingToNextFile will roll to next file for srf. func (srf *SizeRollingFile) rollingToNextFile(now time.Time) { @@ -143,7 +122,7 @@ func (srf *SizeRollingFile) ensureFileIsCorrect() { // file 为 nil,进行初始化 if srf.file == nil { - srf.doRollingTask() + srf.rollingToNextFile(time.Now()) return } @@ -157,7 +136,7 @@ func (srf *SizeRollingFile) ensureFileIsCorrect() { // 1. err != nil,获取文件真实大小失败,选择相信 currentSize // 2. 真实文件大小确实大于 limitedSize if err != nil || fileInfo.Size() >= srf.limitedSize { - srf.doRollingTask() + srf.rollingToNextFile(time.Now()) return } @@ -199,10 +178,3 @@ func (srf *SizeRollingFile) SetNameGenerator(nameGenerator NameGenerator) { defer srf.mu.Unlock() srf.nameGenerator = nameGenerator } - -// SetRollingHook replaces srf.rollingHook to newRollingHook. -func (srf *SizeRollingFile) SetRollingHook(rollingHook RollingHook) { - srf.mu.Lock() - defer srf.mu.Unlock() - srf.rollingHook = rollingHook -} diff --git a/files/size_rolling_file_test.go b/files/size_rolling_file_test.go index dc1e31a..7481505 100644 --- a/files/size_rolling_file_test.go +++ b/files/size_rolling_file_test.go @@ -19,7 +19,6 @@ package files import ( - "fmt" "io/ioutil" "os" "path/filepath" @@ -37,7 +36,7 @@ func TestNewSizeRollingFile(t *testing.T) { }() // 创建临时测试文件夹 - root, err := ioutil.TempDir(os.TempDir(), "TestNewSizeRollingFile_") + root, err := ioutil.TempDir("", "TestNewSizeRollingFile_*") if err != nil { t.Fatal(err) } @@ -75,7 +74,7 @@ func TestNewSizeRollingFile(t *testing.T) { // 测试名字生成器的设置方法 func TestSizeRollingFileSetNameGenerator(t *testing.T) { - dir, err := ioutil.TempDir("", "TestSizeRollingFileSetNameGenerator_") + dir, err := ioutil.TempDir("", "TestSizeRollingFileSetNameGenerator_*") if err != nil { t.Fatal(err) } @@ -96,36 +95,3 @@ func TestSizeRollingFileSetNameGenerator(t *testing.T) { file.Write([]byte(" hi!! ")) } } - -// 测试使用的 rolling hook -type testSizeRollingFileRollingHook struct{} - -func (tdrfrh *testSizeRollingFileRollingHook) BeforeRolling() { - fmt.Println("before rolling...") -} - -func (tdrfrh *testSizeRollingFileRollingHook) AfterRolling() { - fmt.Println("after rolling...") -} - -// 测试名字生成器的设置方法 -func TestSizeRollingFileSetRollingHook(t *testing.T) { - - dir, err := ioutil.TempDir("", "TestSizeRollingFileSetRollingHook_") - if err != nil { - t.Fatal(err) - } - - file := NewSizeRollingFile(dir, 64*KB) - defer file.Close() - - for i := 0; i < 10000; i++ { - file.Write([]byte(" hello! ")) - } - file.SetRollingHook(&testSizeRollingFileRollingHook{}) - - time.Sleep(2 * time.Second) - for i := 0; i < 10000; i++ { - file.Write([]byte(" hi!! ")) - } -} diff --git a/handler_extension.go b/handler_extension.go index e02eb12..7179278 100644 --- a/handler_extension.go +++ b/handler_extension.go @@ -159,8 +159,7 @@ func registerDurationRollingHandler() { // 滚动的时间间隔,单位是秒,默认是 1 天 limit, directory := limitAndDirectoryOf(params, 24*60*60, "./") encoder, timeFormat := encoderAndTimeFormatOf(params, TextEncoder(), DefaultTimeFormat) - options := rollingHandlerOptionsOf(params, files.DefaultNameGenerator(), files.NewDefaultRollingHook()) - return NewDurationRollingHandlerWithOptions(directory, time.Duration(limit)*time.Second, encoder, timeFormat, options) + return NewDurationRollingHandler(directory, time.Duration(limit)*time.Second, encoder, timeFormat) }) } @@ -208,8 +207,7 @@ func registerSizeRollingHandler() { // 滚动的文件大小,单位是 MB,默认是 64 MB limit, directory := limitAndDirectoryOf(params, 64, "./") encoder, timeFormat := encoderAndTimeFormatOf(params, TextEncoder(), DefaultTimeFormat) - options := rollingHandlerOptionsOf(params, files.DefaultNameGenerator(), files.NewDefaultRollingHook()) - return NewSizeRollingHandlerWithOptions(directory, int64(limit)*files.MB, encoder, timeFormat, options) + return NewSizeRollingHandler(directory, int64(limit)*files.MB, encoder, timeFormat) }) } @@ -270,33 +268,6 @@ func limitAndDirectoryOf(params map[string]interface{}, defaultLimit int, defaul return limit, directory } -// rollingHandlerOptionsOf returns rolling handler options in this params. -// defaultNameGenerator and defaultRollingHook will be used if you don't set to params. -func rollingHandlerOptionsOf(params map[string]interface{}, defaultNameGenerator files.NameGenerator, defaultRollingHook files.RollingHook) RollingHandlerOptions { - - // 获取名字生成器配置 - nameGenerator := defaultNameGenerator - if param, ok := params["nameGenerator"]; ok { - nameGenerator = files.NameGeneratorOf(param.(string)) - } - - // 获取滚动钩子配置 - rollingHook := defaultRollingHook - if _, ok := params["rollingHook"]; ok { - param := params["rollingHook"].(map[string]map[string]interface{}) - for rollingHookName, rollingHookParams := range param { - rollingHook = files.RollingHookOf(rollingHookName, rollingHookParams) - break - } - } - - // 返回配置结果 - return RollingHandlerOptions{ - nameGenerator: nameGenerator, - rollingHook: rollingHook, - } -} - // =============================== for public users =============================== // NewConsoleHandler returns a handler for console. @@ -325,78 +296,17 @@ func NewFileHandler(path string, encoder Encoder, timeFormat string) Handler { // See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. // See files.NewDurationRollingFile. func NewDurationRollingHandler(directory string, limit time.Duration, encoder Encoder, timeFormat string) Handler { - return NewDurationRollingHandlerWithOptions(directory, limit, encoder, timeFormat, RollingHandlerOptions{}) -} - -// NewSizeRollingHandler returns a handler which uses -// a size rolling file to write logs. The limit is the max size of log file, -// and the log file will switch to a new one after reaching to max size. -// Also you can point a directory to be used to store all created log files. -// See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. -// See files.NewSizeRollingFile. -func NewSizeRollingHandler(directory string, limit int64, encoder Encoder, timeFormat string) Handler { - return NewSizeRollingHandlerWithOptions(directory, limit, encoder, timeFormat, RollingHandlerOptions{}) -} - -// RollingHandlerOptions includes two options for creating duration rolling handler -// and size rolling handler. NameGenerator is for generating the name of created files, -// and rollingHook is a hook which will be triggered on rolling to next file. -// See files.NameGenerator and files.RollingHook. -type RollingHandlerOptions struct { - nameGenerator files.NameGenerator - rollingHook files.RollingHook -} - -// filledDurationRollingFileWithOptions fills file with non-nil options. -func filledDurationRollingFileWithOptions(file *files.DurationRollingFile, options RollingHandlerOptions) { - if options.nameGenerator != nil { - file.SetNameGenerator(options.nameGenerator) - } - if options.rollingHook != nil { - file.SetRollingHook(options.rollingHook) - } -} - -// filledSizeRollingFileWithOptions fills file with non-nil options. -func filledSizeRollingFileWithOptions(file *files.SizeRollingFile, options RollingHandlerOptions) { - if options.nameGenerator != nil { - file.SetNameGenerator(options.nameGenerator) - } - if options.rollingHook != nil { - file.SetRollingHook(options.rollingHook) - } -} - -// NewDurationRollingHandlerWithOptions returns a handler which uses -// a duration rolling file to write logs. The limit is duration, and -// each duration has its own log file. Also you can point a directory -// to be used to store all created log files. Notice that you should -// point an options object which includes nameGenerator and rollingHook. -// NameGenerator is for generating the name of created files, and rollingHook -// is a hook which will be triggered on rolling to next file. However, -// both of them is not necessary, so if one of them is nil then this "one" -// option will be ignored. See logit.RollingHandlerOptions. -// See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. -// See files.NewDurationRollingFile. -func NewDurationRollingHandlerWithOptions(directory string, limit time.Duration, encoder Encoder, timeFormat string, options RollingHandlerOptions) Handler { file := files.NewDurationRollingFile(directory, limit) - filledDurationRollingFileWithOptions(file, options) return NewStandardHandler(file, encoder, timeFormat) } -// NewSizeRollingHandlerWithOptions returns a handler which uses +// NewSizeRollingHandler returns a handler which uses // a size rolling file to write logs. The limit is the max size of log file, // and the log file will switch to a new one after reaching to max size. // Also you can point a directory to be used to store all created log files. -// Notice that you should point an options object which includes nameGenerator -// and rollingHook. NameGenerator is for generating the name of created files, -// and rollingHook is a hook which will be triggered on rolling to next file. -// However, both of them is not necessary, so if one of them is nil then this "one" -// option will be ignored. See logit.RollingHandlerOptions. // See logit.Encoder, logit.TextEncoder, logit.JsonEncoder. // See files.NewSizeRollingFile. -func NewSizeRollingHandlerWithOptions(directory string, limit int64, encoder Encoder, timeFormat string, options RollingHandlerOptions) Handler { +func NewSizeRollingHandler(directory string, limit int64, encoder Encoder, timeFormat string) Handler { file := files.NewSizeRollingFile(directory, limit) - filledSizeRollingFileWithOptions(file, options) return NewStandardHandler(file, encoder, timeFormat) } diff --git a/handler_extension_test.go b/handler_extension_test.go index a36aba8..2b1ab5e 100644 --- a/handler_extension_test.go +++ b/handler_extension_test.go @@ -19,7 +19,6 @@ package logit import ( - "io/ioutil" "os" "path/filepath" "strconv" @@ -67,49 +66,3 @@ func TestNewSizeRollingHandler(t *testing.T) { logger.Error("error...") } } - -// 测试创建空的 RollingHandlerOptions -func TestRollingHandlerOptions(t *testing.T) { - options := RollingHandlerOptions{} - t.Log(options) - if options.nameGenerator != nil { - t.Fatal("nameGenerator 应该是 nil 的,结果不是。。。") - } - if options.rollingHook != nil { - t.Fatal("rollingHook 应该是 nil 的,结果不是。。。") - } -} - -func TestNewDurationRollingHandlerWithOptions(t *testing.T) { - - // 准备测试环境 - testDirectory, err := ioutil.TempDir("", "TestNewDurationRollingHandlerWithOptions_*") - if err != nil { - t.Fatal(err) - } - - // 创建日志处理器 - handler := NewDurationRollingHandlerWithOptions(testDirectory, time.Second, TextEncoder(), "", RollingHandlerOptions{ - nameGenerator: func(directory string, now time.Time) string { - return filepath.Join(directory, now.Format("2006年01月02日-15点04分05秒.log")) - }, - rollingHook: files.NewLifeBasedRollingHook(testDirectory, 500*time.Millisecond), - }) - - // TODO 这个测试通不过,发现 rollingHook 中的 now.Sub(file.ModTime()) 是负数。。。 - // TODO 这个 RollingHook 机制需要取消掉,因为涉及到太多原本的代码了,考虑使用定时任务解决 - logger := NewLogger(DebugLevel, handler) - logger.Info("1") - time.Sleep(900 * time.Millisecond) - logger.Info("2") - time.Sleep(1 * time.Second) - logger.Info("3") - time.Sleep(1200 * time.Millisecond) - logger.Info("4") - - infos, err := ioutil.ReadDir(testDirectory) - if err != nil || len(infos) != 2 { - t.Log("len(infos) == 2 ? ", len(infos) == 2) - t.Fatal(err) - } -} From 9fb7dbaa023ff918d0a84a2a65556b3aaf475340 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Wed, 24 Jun 2020 22:04:43 +0800 Subject: [PATCH 16/18] =?UTF-8?q?=E5=87=86=E5=A4=87=E5=8F=91=E5=B8=83=20v0?= =?UTF-8?q?.2.6-alpha=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FUTURE.md | 18 ++++++++++++------ HISTORY.md | 10 +++++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/FUTURE.md b/FUTURE.md index 7eaec33..ec40d43 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -1,23 +1,29 @@ -## ✒ 未来版本的新特性 (Features in future version) +## ✒ 未来版本的新特性 (Features in future versions) -### v0.2.8 +### v0.2.9 * 尝试整理包结构,精简 logit 包下的 API -### v0.2.7 +### v0.2.8 +* 加入日志存活天数的特性 * 加入日志存活个数的特性 +### v0.2.7 +* 修复 DefaultNameGenerator 生成文件名可能重复的 bug + ### v0.2.6-alpha * 对 writer 包进行重构,改名为 files 包 * 废弃了原 writer 包的 NewFile 方法,并使用同包下的 CreateFileOf 代替 -* 引入 NameGenerator 和 RollingHook 两个组件 -* 加入日志存活天数的特性 +* 引入 NameGenerator 组件 * 修改 NewDurationRollingHandler 的参数顺序 * 修改 NewSizeRollingHandler 的参数顺序 +* ~~引入 RollinHook 组件~~ + > 取消这个特性是因为,它的代码入侵太严重了,并且会使代码设计变得很复杂,使用体验也会变差。 + > 为了这样一个扩展特性要去改动核心特性的代码,有些喧宾夺主了,所以最后取消了这个组件。 ### v0.2.5-alpha * 加入之前被移除的特性 - 可变长参数列表的日志输出支持,主要可以使用格式化字符串进行多参数传递 * ~~修复配置文件中出现转义字符导致解析出错的问题~~ - > 取消这个特性是因为,配置文件是用户写的,如果存在转义字符的问题,用户自行做转义会更适合一点 + > 取消这个特性是因为,配置文件是用户写的,如果存在转义字符的问题,用户自行做转义会更适合一点。 ### v0.2.4 * 新增屏蔽某个日志级别的日志处理器 diff --git a/HISTORY.md b/HISTORY.md index c2c0153..7ff565f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,12 @@ -## ✒ 历史版本的特性介绍 (Features in old version) +## ✒ 历史版本的特性介绍 (Features in old versions) + +### v0.2.6-alpha +> 此版本发布于 2020-06-24 +* 对 writer 包进行重构,改名为 files 包 +* 废弃了原 writer 包的 NewFile 方法,并使用同包下的 CreateFileOf 代替 +* 引入 NameGenerator 组件 +* 修改 NewDurationRollingHandler 的参数顺序 +* 修改 NewSizeRollingHandler 的参数顺序 ### v0.2.5-alpha > 此版本发布于 2020-06-08 From 2d93242cad4ea7569499ed537cfe98b06fda5dee Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Thu, 25 Jun 2020 00:47:25 +0800 Subject: [PATCH 17/18] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20DefaultNameGenerator?= =?UTF-8?q?=20=E5=8F=AF=E8=83=BD=E4=BA=A7=E7=94=9F=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FUTURE.md | 2 +- HISTORY.md | 4 + README.en.md | 2 +- README.md | 2 +- _examples/config/logit-config-template.conf | 2 +- .../config/logit-config-template.en.conf | 2 +- doc.go | 48 +++++----- files/name_generator.go | 66 +++---------- files/name_generator_test.go | 95 ++++++++++--------- 9 files changed, 98 insertions(+), 125 deletions(-) diff --git a/FUTURE.md b/FUTURE.md index ec40d43..8600813 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -8,7 +8,7 @@ * 加入日志存活个数的特性 ### v0.2.7 -* 修复 DefaultNameGenerator 生成文件名可能重复的 bug +* 修复了 DefaultNameGenerator 可能产生重复文件名的 bug ### v0.2.6-alpha * 对 writer 包进行重构,改名为 files 包 diff --git a/HISTORY.md b/HISTORY.md index 7ff565f..dc99e33 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ ## ✒ 历史版本的特性介绍 (Features in old versions) +### v0.2.7 +> 此版本发布于 2020-06-25 +* 修复了 DefaultNameGenerator 可能产生重复文件名的 bug + ### v0.2.6-alpha > 此版本发布于 2020-06-24 * 对 writer 包进行重构,改名为 files 包 diff --git a/README.en.md b/README.en.md index a7fce1e..0ee23b3 100644 --- a/README.en.md +++ b/README.en.md @@ -46,7 +46,7 @@ module your_project_name go 1.14 require ( - github.com/FishGoddess/logit v0.2.6-alpha + github.com/FishGoddess/logit v0.2.7 ) ``` diff --git a/README.md b/README.md index 0717608..d816c75 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ module your_project_name go 1.14 require ( - github.com/FishGoddess/logit v0.2.6-alpha + github.com/FishGoddess/logit v0.2.7 ) ``` diff --git a/_examples/config/logit-config-template.conf b/_examples/config/logit-config-template.conf index 5201a35..52e50c2 100644 --- a/_examples/config/logit-config-template.conf +++ b/_examples/config/logit-config-template.conf @@ -1,4 +1,4 @@ -# logit 配置文件的模板 v0.2.6-alpha +# logit 配置文件的模板 v0.2.7 # 注意:以 # 开头的是注释,不参与配置文件的解析,注释必须是单独的一行,不能写在属性的后面 # 语法是基于 Json 并作了一些改动使其更适合做配置文件,所以要注意双引号和逗号这些格式 # 下面所有涉及目录路径的都使用 / 或者 \\ 而不能是 \,否则会造成配置解析出错,这意味着特殊字符需要自行转义 diff --git a/_examples/config/logit-config-template.en.conf b/_examples/config/logit-config-template.en.conf index 0c85efe..6d60e12 100644 --- a/_examples/config/logit-config-template.en.conf +++ b/_examples/config/logit-config-template.en.conf @@ -1,4 +1,4 @@ -# logit config template v0.2.6-alpha +# logit config template v0.2.7 # Notice that starting with # is comment, and it must be a new line # Grammar is based on Json, but adds more features to let it become more configured and easy-to-read # You should always use / or \\ instead of \, because some special characters should be escaped diff --git a/doc.go b/doc.go index 25da1d2..f867e6d 100644 --- a/doc.go +++ b/doc.go @@ -90,21 +90,25 @@ Package logit provides an easy way to use foundation for your logging operations 3. level_and_disable: + // Use logit.Debug method to output a debug level message. + // Also, Info/Warn/Error method is available. logit.Debug("Default logger level is debug.") - // Change logger level to info level. - // So debug log will be ignored. - logit.ChangeLevelTo(logit.InfoLevel) + // Change logger level to info level, so logs in debug level will be ignored. + // Notice that logit has blocked some methods for more refreshing method list. + // If you want to use some higher level methods, you should call logit.Me() to + // get the fully functional logger, then call what you want to call. + logit.Me().ChangeLevelTo(logit.InfoLevel) logit.Debug("You never see me!") // In particular, you can change level to OffLevel to disable the logger. // So the info message next line will not be logged! - level := logit.ChangeLevelTo(logit.OffLevel) + level := logit.Me().ChangeLevelTo(logit.OffLevel) logit.Info("I will not be logged!") // Enable the Logger. // The info message next line will be logged again! - logit.ChangeLevelTo(level) + logit.Me().ChangeLevelTo(level) logit.Info("I am running again!") 4. log to file: @@ -135,23 +139,23 @@ Package logit provides an easy way to use foundation for your logging operations 5. handler: - type myHandler struct{} + type myHandler struct{} - // Customize your own handler. - func (mh *myHandler) Handle(log *logit.Log) bool { - os.Stdout.Write([]byte("myHandler: ")) - os.Stdout.Write(logit.TextEncoder().Encode(log, "")) // Try `os.Stdout.WriteString(log.Msg())` ? - return true - } + // Customize your own handler. + func (mh *myHandler) Handle(log *logit.Log) bool { + os.Stdout.Write([]byte("myHandler: ")) + os.Stdout.Write(logit.TextEncoder().Encode(log, "")) // Try `os.Stdout.WriteString(log.Msg())` ? + return true + } - func init() { - // We recommend you to register your handler to logit, so that - // you can use your handler in config file. - // See logit.RegisterHandler. - logit.RegisterHandler("myHandler", func(params map[string]interface{}) logit.Handler { - return &myHandler{} - }) - } + func init() { + // We recommend you to register your handler to logit, so that + // you can use your handler in config file. + // See logit.RegisterHandler. + logit.RegisterHandler("myHandler", func(params map[string]interface{}) logit.Handler { + return &myHandler{} + }) + } // Create a logger holder with a console handler. logger := logit.NewLogger(logit.DebugLevel, logit.NewConsoleHandler(logit.TextEncoder(), logit.DefaultTimeFormat)) @@ -192,7 +196,7 @@ Package logit provides an easy way to use foundation for your logging operations // } // } // - logger := logit.NewLoggerFrom("./logger.conf") + logger := logit.NewLoggerFromPath("./logger.conf") logger.Info("I am working!") logger.Info("My level is " + logger.Level().String()) fmt.Println("fmt ==============================================") @@ -207,5 +211,5 @@ package logit // import "github.com/FishGoddess/logit" const ( // Version is the version string representation of logit. - Version = "v0.2.6-alpha" + Version = "v0.2.7" ) diff --git a/files/name_generator.go b/files/name_generator.go index 7299d77..9567e79 100644 --- a/files/name_generator.go +++ b/files/name_generator.go @@ -19,28 +19,14 @@ package files import ( - "errors" - "fmt" + "math" "math/rand" - "os" "path/filepath" "strconv" - "sync" + "sync/atomic" "time" ) -var ( - // nameGenerators stores all nameGenerators registered. - // mutexOfNameGenerators is for concurrency. - nameGenerators = map[string]NameGenerator{ - "default": DefaultNameGenerator(), - } - mutexOfNameGenerators = &sync.RWMutex{} - - // NameGeneratorIsExistedError is an error happening on repeating nameGenerator name. - NameGeneratorIsExistedError = errors.New("the name of nameGenerator you want to register already exists! May be you should give it an another name") -) - // NameGenerator is the type for generating the name of every created file. // You can customize your format of filename by implementing this function. // The two parameters string and time.Time is useful. The string parameter is the directory @@ -53,49 +39,27 @@ func (ng NameGenerator) NextName(directory string, now time.Time) string { return ng(directory, now) } -// RegisterNameGenerator registers your nameGenerator to logit so that you can use them in config file. -// Return an error if the name is existed, and you should change another name for your nameGenerator. -func RegisterNameGenerator(name string, nameGenerator NameGenerator) error { - mutexOfNameGenerators.Lock() - defer mutexOfNameGenerators.Unlock() - - if _, ok := nameGenerators[name]; ok { - return NameGeneratorIsExistedError - } - nameGenerators[name] = nameGenerator - return nil -} - -// NameGeneratorOf returns nameGenerator whose name is given name. -// Notice that we use tips+exit mechanism to check the name. -// This is a more convenient way to use nameGenerator (we think). -// so if the nameGenerator doesn't exist, a tip will be printed and -// the program will exit with status code 10. -func NameGeneratorOf(name string) NameGenerator { - mutexOfNameGenerators.RLock() - defer mutexOfNameGenerators.RUnlock() - nameGenerator, ok := nameGenerators[name] - if !ok { - fmt.Fprintf(os.Stderr, "Error: The nameGenerator \"%s\" doesn't exist! Please change it to another nameGenerator.\n", name) - os.Exit(10) - } - return nameGenerator -} - // ================================== default name generator ================================== +var ( + // For DefaultNameGenerator. + defaultNameGeneratorRandom = rand.New(rand.NewSource(time.Now().Unix())) + defaultNameGeneratorCounter = int64(0) +) + // DefaultNameGenerator returns a name generator that creates a time-relative filename // with given now time. Also, it uses random number to ensure this filename is available. // The filename will be like "20200304-145246-45.log". // Notice that directory stores all files created in this time and now is the time of current moment. -func DefaultNameGenerator() func(directory string, now time.Time) string { - // 考虑使用原子计数器替换随机数 - // 这个 Seed 方法最好不要并发执行,但是这个方法有可能会被并发执行,这是个隐患 - // 在测试阶段就已经出现了随机数重复的情况,导致一个文件被写入多个文件的内容 +func DefaultNameGenerator() NameGenerator { + // v0.2.7 版本中加入了原子计数器机制,配合随机数生成唯一性更高的名字 + // 原本在这个方法中调用了 rand.Seed 方法,这个方法最好不要并发执行,但这里有可能会被并发执行,这是个隐患 + // 在测试阶段就已经出现了随机数重复的情况,导致一个文件被写入多个文件的内容,所以需要进行修复 // issue: https://github.com/FishGoddess/logit/issues/7 - rand.Seed(time.Now().UnixNano()) return func(directory string, now time.Time) string { - name := now.Format("20060102-150405") + "-" + strconv.Itoa(rand.Intn(1000000)) + SuffixOfLogFile + atomic.CompareAndSwapInt64(&defaultNameGeneratorCounter, math.MaxInt64-128, 0) + seq := strconv.FormatInt(atomic.AddInt64(&defaultNameGeneratorCounter, int64(1)), 10) + name := now.Format("20060102-150405") + "-" + seq + strconv.Itoa(defaultNameGeneratorRandom.Int()) + SuffixOfLogFile return filepath.Join(directory, name) } } diff --git a/files/name_generator_test.go b/files/name_generator_test.go index dfe4b66..dcc12a5 100644 --- a/files/name_generator_test.go +++ b/files/name_generator_test.go @@ -1,66 +1,67 @@ -// Copyright 2020 Ye Zi Jie. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// // Author: FishGoddess // Email: fishgoddess@qq.com -// Created at 2020/06/14 23:11:04 +// Created at 2020/06/24 23:05:51 package files import ( + "fmt" + "sync" "testing" "time" ) -// 测试注册 nameGenerator 的方法 -func TestRegisterNameGenerator(t *testing.T) { +// 测试 DefaultNameGenerator 是否会产生重复名字 +func TestDefaultNameGenerator(t *testing.T) { - // default 已经存在,测试是否报错 - err := RegisterNameGenerator("default", func(directory string, now time.Time) string { - return "" - }) - if err == nil { - t.Fatal(err) - } + // 简单看看生成的名字 + nameGenerator1 := DefaultNameGenerator() + nameGenerator2 := DefaultNameGenerator() + fmt.Printf("%p %p\n", nameGenerator1, nameGenerator2) + fmt.Println(nameGenerator1.NextName("", time.Now()), nameGenerator2.NextName("", time.Now())) - // test 不存在,测试是否报错 - err = RegisterNameGenerator("TestRegisterNameGenerator", func(directory string, now time.Time) string { - return "test" - }) - if err != nil { - t.Fatal(err) - } + // 并发生成一批名字,并判断唯一性 + nameQueue := make(chan string, 64) + defer close(nameQueue) - nameGenerator := NameGeneratorOf("TestRegisterNameGenerator") - if nameGenerator.NextName("", time.Now()) != "test" { - t.Fatal("注册可能失败了,获取也可能是失败了...") + times := 128 + concurrency := 8 + group := sync.WaitGroup{} + group.Add(concurrency) + for i := 0; i < concurrency; i++ { + go func(id int) { + if id&1 == 0 { + for i := 0; i < times; i++ { + nameQueue <- nameGenerator1.NextName("", time.Now()) + } + } else { + for i := 0; i < times; i++ { + nameQueue <- nameGenerator2.NextName("", time.Now()) + } + } + group.Done() + }(i) } -} -// 测试 nameGeneratorOf 方法 -func TestNameGeneratorOf(t *testing.T) { - - // 先注册,再获取 - err := RegisterNameGenerator("TestNameGeneratorOf", func(directory string, now time.Time) string { - return "test" - }) - if err != nil { - t.Fatal(err) + countMap := map[string]int{} + done := false + for !done { + select { + case name := <-nameQueue: + //fmt.Println(name) + if _, ok := countMap[name]; ok { + t.Fatalf("name [%s] 重复了!\n", name) + } + countMap[name] = 1 + case <-time.After(time.Second): + done = true + break + } } - nameGenerator := NameGeneratorOf("TestNameGeneratorOf") - if nameGenerator.NextName("", time.Now()) != "test" { - t.Fatal("注册可能失败了,获取也可能是失败了...") + if len(countMap) != concurrency*times { + t.Fatal("countMap 数量不对,说明有 name 重复了!") } + + group.Wait() } From 9e75cc01c53be36b0fd52b692f0590b8dbee062b Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Thu, 25 Jun 2020 01:00:10 +0800 Subject: [PATCH 18/18] =?UTF-8?q?v0.2.7=20=E7=89=88=E6=9C=AC=E5=87=86?= =?UTF-8?q?=E5=A4=87=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.en.md | 12 +----------- README.md | 12 +----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/README.en.md b/README.en.md index 0ee23b3..88ecfe4 100644 --- a/README.en.md +++ b/README.en.md @@ -30,15 +30,11 @@ _Check [HISTORY.md](./HISTORY.md) and [FUTURE.md](./FUTURE.md) to know about mor ### 🚀 Installation -The only requirement is the [Golang Programming Language](https://golang.org). - -> Go modules - ```bash $ go get -u github.com/FishGoddess/logit ``` -Or edit your project's go.mod file and execute _**go build**_. +> Or edit your project's go.mod file if you are using Go modules. ```bash module your_project_name @@ -50,12 +46,6 @@ require ( ) ``` -> Go path - -```bash -$ go get -u github.com/FishGoddess/logit -``` - logit has no more external dependencies. ```go diff --git a/README.md b/README.md index d816c75..b4e420b 100644 --- a/README.md +++ b/README.md @@ -31,15 +31,11 @@ _历史版本的特性请查看 [HISTORY.md](./HISTORY.md)。未来版本的新 ### 🚀 安装方式 -唯一需要的依赖就是 [Golang 运行环境](https://golang.org). - -> Go modules - ```bash $ go get -u github.com/FishGoddess/logit ``` -您也可以直接编辑 go.mod 文件,然后执行 _**go build**_。 +> 如果是 Go modules 的项目,您还可以直接编辑 go.mod 文件。 ```bash module your_project_name @@ -51,12 +47,6 @@ require ( ) ``` -> Go path - -```bash -$ go get -u github.com/FishGoddess/logit -``` - logit 没有任何其他额外的依赖,纯使用 [Golang 标准库](https://golang.org) 完成。 ```go