From 39fb8208fc479eeb14ec849cf3b8d69a0dfbbb1a Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Mon, 9 Mar 2020 17:23:21 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Logger=20=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FUTURE.md | 9 ++++-- README.md | 2 +- logger.go | 52 ++++++++++++++++++++++++++++++---- logger_handler.go | 2 +- logger_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 125 insertions(+), 12 deletions(-) diff --git a/FUTURE.md b/FUTURE.md index a4e9e91..703b0a1 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -1,11 +1,16 @@ ## ✒ 未来版本的新特性 (Features in future version) -### v0.0.11 +### v0.0.12 * 时间格式化操作中 time.Time.AppendFormat 消耗性能很多,思考优化方案 -### v0.0.10 +### v0.0.11 * 支持日志输出为 Json 形式,通过增加 JSON 日志处理器实现 +### v0.0.10 +* 扩展了 Logger 的方法,可以获取到内部的属性,为日志处理器做准备 +* 支持创建 Logger 对象之后修改它的输出源 writer(这是个之前被遗漏的功能特性哈哈) +* 调整了内部 log 方法的锁机制,使用类似于写时复制的方式释放日志输出的并发性 + ### v0.0.9 * 支持日志输出函数,日志信息可以是一个返回 string 的函数 * 公开 PrefixOf 方法,方便用户自定义处理器的时候获取日志级别字符串 diff --git a/README.md b/README.md index bba335a..c92ae3d 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ $ go test -v ./_examples/benchmarks_test.go -bench=. -benchtime=1s **行为,主要体现在对参数 v interface{} 进行类型检测的逻辑上,而日志输出都是字符串,这一个** **判断是可以省略的,可以减少很多运行时操作时间!v0.0.8 版本开始使用了更有效率的输出方式!** -**3. 经过对 v0.0.8 版本的性能检测,发现时间格式化操作消耗了接近一般的处理时间,** +**3. 经过对 v0.0.8 版本的性能检测,发现时间格式化操作消耗了接近一半的处理时间,** **主要体现在 time.Time.AppendFormat 的调用上。目前正在思考优化方案,或许会在之后的版本中解决!** ### 👥 贡献者 diff --git a/logger.go b/logger.go index 14472d7..922bb69 100644 --- a/logger.go +++ b/logger.go @@ -117,6 +117,13 @@ func (l *Logger) ChangeLevelTo(newLevel LoggerLevel) { l.level = newLevel } +// Level returns the logger level of l. +func (l *Logger) Level() LoggerLevel { + l.mu.RLock() + defer l.mu.RUnlock() + return l.level +} + // EnableFileInfo means every log will contain file info like line number. // However, you should know that this is expensive in time. // So be sure you really need it or keep it disabled. @@ -155,12 +162,24 @@ func (l *Logger) SetHandlers(handlers ...LoggerHandler) bool { return false } + // 先清空原本的日志处理器,再添加新的日志处理器 l.mu.Lock() defer l.mu.Unlock() - l.handlers = handlers + l.handlers = nil + l.handlers = append(l.handlers, handlers...) return true } +// Handlers returns all handlers of l in a copy slice. +func (l *Logger) Handlers() []LoggerHandler { + l.mu.RLock() + defer l.mu.RUnlock() + + // 返回的是日志处理器的副本,防止被非法篡改 + handlers := make([]LoggerHandler, 0, len(l.handlers)) + return append(handlers, l.handlers...) +} + // SetFormatOfTime sets format of time as you want. // Default is "2006-01-02 15:04:05", see DefaultFormatOfTime. func (l *Logger) SetFormatOfTime(formatOfTime string) { @@ -169,8 +188,24 @@ func (l *Logger) SetFormatOfTime(formatOfTime string) { l.formatOfTime = formatOfTime } +// FormatOfTime returns the format of time in l. +func (l *Logger) FormatOfTime() string { + l.mu.RLock() + defer l.mu.RUnlock() + return l.formatOfTime +} + +// ChangeWriterTo changes current writer to newWriter. +func (l *Logger) ChangeWriterTo(newWriter io.Writer) { + l.mu.Lock() + defer l.mu.Unlock() + l.writer = newWriter +} + // Writer returns the writer of l. func (l *Logger) Writer() io.Writer { + l.mu.RLock() + defer l.mu.RUnlock() return l.writer } @@ -183,21 +218,26 @@ func (l *Logger) log(callDepth int, level LoggerLevel, msg string) { // 加上读锁 l.mu.RLock() - defer l.mu.RUnlock() // 以下两种条件直接返回,不记录日志: // 1. 日志处于禁用状态,也就是 l.running = false // 2. 日志记录器的日志级别高于这条记录的日志级别 if !l.running || l.level > level { + l.mu.RUnlock() return } + // 提前释放读锁,后续操作非常消耗时间,可以不用加锁了,彻底释放并发的天性 + // 但是 needFileInfo 的获取需要保证并发安全,就在释放锁之前拷贝一份副本, + // 即使释放锁之后有人修改了这个属性,也和这里无关了,因为在执行这个 log 方法的时间点上 + // 这个属性的值就已经确定了,并且不允许被修改了,这类似于 copy on write 的解决思路。 + // 这个解决并发竞争的方案是否没有问题,需要时间的验证才知道 + needFileInfo := l.needFileInfo + l.mu.RUnlock() + // 如果需要文件信息,对当前的 msg 进行包装 - if l.needFileInfo { - // 提前释放读锁,后续操作非常消耗时间,等获取完文件信息再上读锁 - l.mu.RUnlock() + if needFileInfo { msg = wrapMessageWithFileInfo(callDepth, msg) - l.mu.RLock() } // 处理日志 diff --git a/logger_handler.go b/logger_handler.go index 1195cd3..2b0431f 100644 --- a/logger_handler.go +++ b/logger_handler.go @@ -39,6 +39,6 @@ func (lh LoggerHandler) handle(logger *Logger, level LoggerLevel, now time.Time, // The log handled by this handler will be like "[Info] [2020-03-06 16:10:44] msg". // If you want to customize, just code your own handler, then replace it! func DefaultLoggerHandler(logger *Logger, level LoggerLevel, now time.Time, msg string) bool { - logger.Writer().Write([]byte("[" + PrefixOf(level) + "] [" + now.Format(logger.formatOfTime) + "] " + msg + "\n")) + logger.Writer().Write([]byte("[" + PrefixOf(level) + "] [" + now.Format(logger.FormatOfTime()) + "] " + msg + "\n")) return true } diff --git a/logger_test.go b/logger_test.go index 7d97fe1..3c9a8cb 100644 --- a/logger_test.go +++ b/logger_test.go @@ -21,6 +21,9 @@ package logit import ( "fmt" "os" + "strconv" + "strings" + "sync" "testing" "time" ) @@ -62,6 +65,7 @@ func TestLoggerEnable(t *testing.T) { // 测试日志记录器的级别控制是否可用 func TestLoggerLevel(t *testing.T) { logger := NewLogger(os.Stdout, WarnLevel) + logger.Warn(logger.Level().String()) logger.Info("这条 info 级别的内容可以显示吗?") logger.Error("这条 error 级别的内容可以显示吗?") logger.Warn("这条 warn 级别的内容可以显示吗?") @@ -131,7 +135,71 @@ func TestLoggerAddHandlersAndSetHandlers(t *testing.T) { // 测试更改时间格式化标准的方法 func TestLoggerSetFormatOfTime(t *testing.T) { logger := NewLogger(os.Stdout, InfoLevel) - logger.Info("当前时间格式化信息!") + logger.Info("当前时间格式化信息!" + logger.FormatOfTime()) logger.SetFormatOfTime("2006年01月02日 15点04分05秒") - logger.Info("更改之后的时间格式化信息!") + logger.Info("更改之后的时间格式化信息!" + logger.FormatOfTime()) +} + +// 测试获取日志处理器的方法 +func TestLoggerHandlers(t *testing.T) { + logger := NewStdoutLogger(InfoLevel) + handlers := logger.Handlers() + + // 需要判断地址是否一样,一样说明有问题 + if &handlers[0] == &logger.handlers[0] { + t.Fatal("handlers 获取的数据和底层数据地址一样!这是有问题的!") + } + + // 显示每个日志处理器的地址 + logger.Info("handlers 个数:" + strconv.Itoa(len(handlers))) + logger.InfoFunction(func() string { + builder := strings.Builder{} + for _, handler := range handlers { + builder.WriteString(fmt.Sprintf("%p ", &handler)) + } + return builder.String() + }) + + // 尝试非法篡改日志处理器 + handlers[0] = func(logger *Logger, level LoggerLevel, now time.Time, msg string) bool { + fmt.Println("哈哈哈被非法篡改了!!!") + return false + } + logger.Warn("这条信息需要显示出来!") +} + +// 测试更改写出器的方法 +func TestLoggerChangeWriterTo(t *testing.T) { + logger := NewStdoutLogger(DebugLevel) + fmt.Println(logger.writer) + logger.Debug("哈哈哈") + + file, _ := os.Create("Z:/TestLoggerChangeWriterTo.log") + logger.ChangeWriterTo(file) + fmt.Println(logger.writer) + logger.Debug("哈哈哈文件?") +} + +// 测试并发情况下使用 Logger +func TestLoggerInConcurrency(t *testing.T) { + + logger := NewFileLogger("Z:/TestLoggerInConcurrency.log", DebugLevel) + //logger := NewStdoutLogger(DebugLevel) + + group := sync.WaitGroup{} + for i := 0; i < 100; i++ { + group.Add(1) + go func(num int) { + if num == 30 || num == 60 { + logger.ChangeLevelTo(InfoLevel) + } + logger.Info(strconv.Itoa(num)) + if num == 60 || num == 90 { + logger.ChangeWriterTo(os.Stdout) + } + group.Done() + }(i) + } + + group.Wait() } From ee34d279717060fc9bdb8c51f9e358af72a589dc Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Mon, 9 Mar 2020 21:54:16 +0800 Subject: [PATCH 2/3] =?UTF-8?q?v0.0.10=20=E5=BE=AE=E7=89=B9=E6=80=A7?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=89=88=E6=9C=AC=E9=A2=84=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _examples/logger.go | 7 +++++-- doc.go | 7 +++++-- logit.go | 7 +++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/_examples/logger.go b/_examples/logger.go index c961006..d8738da 100644 --- a/_examples/logger.go +++ b/_examples/logger.go @@ -34,9 +34,9 @@ func main() { // Then you will be easy to log! logger.Debug("this is a debug message!") - logger.Info("this is a info message!") + logger.Info("this is an info message!") logger.Warn("this is a warn message!") - logger.Error("this is a error message!") + logger.Error("this is an error message!") // NewLogger creates a new Logger holder. // The first parameter "os.Stdout" is a writer for logging. @@ -60,4 +60,7 @@ func main() { r := rand.New(rand.NewSource(time.Now().Unix())) return "debug rand int: " + strconv.Itoa(r.Intn(100)) }) + + // If you want to change logger's writer, try this: + logger.ChangeWriterTo(os.Stdout) } diff --git a/doc.go b/doc.go index a19e03f..2de92c1 100644 --- a/doc.go +++ b/doc.go @@ -42,9 +42,9 @@ Package logit provides an easy way to use foundation for your logging operations // Then you will be easy to log! logger.Debug("this is a debug message!") - logger.Info("this is a info message!") + logger.Info("this is an info message!") logger.Warn("this is a warn message!") - logger.Error("this is a error message!") + logger.Error("this is an error message!") // NewLogger creates a new Logger holder. // The first parameter "os.Stdout" is a writer for logging. @@ -69,6 +69,9 @@ Package logit provides an easy way to use foundation for your logging operations return "debug rand int: " + strconv.Itoa(r.Intn(100)) }) + // If you want to change logger's writer, try this: + logger.ChangeWriterTo(os.Stdout) + 3. enable or disable: // Every new Logger is running. diff --git a/logit.go b/logit.go index a767db8..71f9d06 100644 --- a/logit.go +++ b/logit.go @@ -18,6 +18,8 @@ package logit +import "io" + // defaultLogger is a Logger holder for global usage. // Default level is info level. var defaultLogger = NewStdoutLogger(InfoLevel) @@ -122,3 +124,8 @@ func WarnFunction(msgGenerator func() string) { func ErrorFunction(messageGenerator func() string) { defaultLogger.ErrorFunction(messageGenerator) } + +// ChangeWriterTo changes current writer to newWriter. +func ChangeWriterTo(newWriter io.Writer) { + defaultLogger.ChangeWriterTo(newWriter) +} From 4ad01da1769ca6355195a3cc11a48f9a2a1c4766 Mon Sep 17 00:00:00 2001 From: FishGoddess <1149062639@qq.com> Date: Tue, 10 Mar 2020 13:37:07 +0800 Subject: [PATCH 3/3] =?UTF-8?q?v0.0.10=20=E7=89=88=E6=9C=AC=E5=8F=91?= =?UTF-8?q?=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HISTORY.md | 6 ++++++ README.en.md | 2 +- README.md | 2 +- doc.go | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 2a014ab..1aa20ad 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ ## ✒ 历史版本的特性介绍 (Features in old version) +### v0.0.10 +> 此版本发布于 2020-03-10 +* 扩展了 Logger 的方法,可以获取到内部的属性,为日志处理器做准备 +* 支持创建 Logger 对象之后修改它的输出源 writer(这是个之前被遗漏的功能特性哈哈) +* 调整了内部 log 方法的锁机制,使用类似于写时复制的方式释放日志输出的并发性 + ### v0.0.9 > 此版本发布于 2020-03-09 * 支持日志输出函数,日志信息可以是一个返回 string 的函数 diff --git a/README.en.md b/README.en.md index c3f85da..72e8149 100644 --- a/README.en.md +++ b/README.en.md @@ -39,7 +39,7 @@ module your_project_name go 1.14 require ( - github.com/FishGoddess/logit v0.0.9 + github.com/FishGoddess/logit v0.0.10 ) ``` diff --git a/README.md b/README.md index c92ae3d..81b0552 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ module your_project_name go 1.14 require ( - github.com/FishGoddess/logit v0.0.9 + github.com/FishGoddess/logit v0.0.10 ) ``` diff --git a/doc.go b/doc.go index 2de92c1..dbe83ce 100644 --- a/doc.go +++ b/doc.go @@ -170,4 +170,4 @@ Package logit provides an easy way to use foundation for your logging operations package logit // import "github.com/FishGoddess/logit" // Version is the version string representation of the "logit" package. -const Version = "0.0.9" +const Version = "0.0.10"