diff --git a/Makefile b/Makefile index f7fd892..482032e 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,14 @@ - -GOFILES=$(wildcard *.go) -BACKENDS=$(wildcard ./backend/*.go) -EXAMPLE=./example/main.go -GOBIN=./bin - all: build test +.PHONY: build build: - @echo " > formating file ..." - go fmt $(GOFILES) - go fmt $(BACKENDS) - golangci-lint run $(GOFILES) - golangci-lint run $(BACKENDS) - @rm -rf $(GOBIN) - @-mkdir $(GOBIN) - go build -o $(patsubst %.go, $(GOBIN)/%, $(notdir $(EXAMPLE))) $(EXAMPLE) + @scripts/go-build +.PHONY: test test: @echo " > testing file ..." -.PHONY:clean +.PHONY: clean clean: @echo " > cleaning file ..." - rm -rf $(GOBIN) \ No newline at end of file + rm -rf ./bin/ \ No newline at end of file diff --git a/README.md b/README.md index 9ed86cc..25612e5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## 功能介绍 -[`go-coder/logr`](https://github.com/go-coder/logr) 的一个基础实现方案。`go-coder/logr` 是 [`go-logr/logr`](https://github.com/go-logr/logr) 的一个分支,对于当前实现简单改动就能应用到 `go-logr/logr` 上。 +[`go-logr/logr`](https://github.com/go-logr/logr) 的一个基础实现方案。 ## 设计思路 @@ -17,8 +17,9 @@ + 远程日志:pubsub、 gRPC、HTTP网页 等形式 + AB测试:同时运行新旧版本的程序,自动对比日志不同,从中发现可能存在的问题 ++ 日志归集:将多个不同程序的日志汇总到一个地址进行分析 -## 竞争问题 +## 前端的竞争问题 考虑两种使用场景: @@ -40,7 +41,7 @@ go func() { }() ``` -1. logr.V / logr.WithName / logr.WithFields +1. logr.V / logr.WithName / logr.WithValues 实现的时候,都是在副本中进行的操作,只需要考虑 copy 函数是否有竞争问题即可。 @@ -57,3 +58,7 @@ go func() { ### 结论 logr 通过 log 对象只读的方式避免了竞争问题。所有对 log 进行改变的操作都是在新生成的副本之上进行的,从而保证了不会发生竞争。 + +## 后端竞争问题 + +由后端自行控制,stderr 的竞争问题分析参见 [README.md](./backend/README.md) \ No newline at end of file diff --git a/backend/README.md b/backend/README.md index d7036d8..1333dc9 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,4 +1,10 @@ -# stderr 解决竞争的方案 +# logr 后端设计 + +logr 在设计的时候充分考虑了扩展性,可以添加多种不同的后端方案,方便日志打印和日志归集。只要实现了 [`types.go`](../types/types.go) 中定义的 [`EntryWriter`](../types/types.go#L15) 接口,就可以直接切换后端,而不必担心日志前端的兼容问题。 + +这里,我们默认提供了 `stderr` 的[后端实现](./stderr.go)。 + +## stderr 解决竞争的方案 log 前端部分采用只读类型解决了竞争问题,后端的竞争问题由后端自行解决。这里的解决思路同样是两个:只读、加锁(包括 chan 也是用到了锁)。 @@ -6,6 +12,16 @@ log 前端部分采用只读类型解决了竞争问题,后端的竞争问题 使用 channel 的好处在于直观,所有的 log 请求被组织成了一个队列的形式,所有的日志汇集到了一个 goroutine 中进行处理;可能存在的问题是,当日志请求过多的时候,这个负责处理日志的 goroutine 可能会成为性能瓶颈。 -加锁的话,日志请求在原来的 goroutine 中完成,避免了单个 goroutine 的性能我呢提,但是会影响当前 goroutine 的性能。 +加锁的话,日志请求在原来的 goroutine 中完成,避免了单个 goroutine 的性能问题,但是会影响当前 goroutine 的性能。 我们假定日志组织良好,不会达到 goroutine 性能瓶颈。这样,采用 channel 的方式排队处理请求,把日志处理从普通 goroutine 中提取出来,由专门的 goroutine 来处理,以期提高性能。 + +### 思考1 + +如果使用的是无缓冲chan的话,日志库性能并不高,因为阻塞队列必须每产生一条日志就打印下来,这其实是一个同步操作。带缓冲的chan能够提高性能。(这里其实是假设所有的 goroutine 是在一个线程内部了,被分配到一个核上执行) + +ps: goroutine 是否能够充分利用多核CPU,把打印日志的 goroutine 单独放到另一个核中?这样的话就不存在阻塞问题了,性能会很好。 + +### 思考2 + +单独启动一个 goroutine 负责日志后端打印的话,存在一个问题:当主程序执行完一条打印日志命令后很快结束程序的时候,所有 goroutine 都会被杀死,但是这时候可能日志还没打印完毕,会有丢失日志的情况出现。我们需要一个同步的操作来等待日志打印完毕再结束进程。 diff --git a/bin/main b/bin/main new file mode 100644 index 0000000..3e5d189 Binary files /dev/null and b/bin/main differ diff --git a/example/main.go b/example/main.go index 26b8162..bbe40c1 100644 --- a/example/main.go +++ b/example/main.go @@ -10,7 +10,7 @@ import ( func main() { log.Info("yes I can call it directly") - logr := log.NewLogger(backend.Stderr()).WithName("test").WithFields("key", "value") + logr := log.NewLogger(backend.Stderr()).WithName("test").WithValues("key", "value") logr.V(1).Info("msg", "uint", 112, "int", 211, "nil", nil) var typedNil *int diff --git a/frontend/logr.go b/frontend/logr.go index fda3d4a..06d50d7 100644 --- a/frontend/logr.go +++ b/frontend/logr.go @@ -8,8 +8,8 @@ import ( "runtime/debug" "time" - "github.com/go-coder/logr" "github.com/go-coder/log/types" + "github.com/go-logr/logr" "github.com/spf13/pflag" ) @@ -75,7 +75,7 @@ func (l *rlog) WithName(name string) logr.Logger { return out } -func (l *rlog) WithFields(kvList ...interface{}) logr.Logger { +func (l *rlog) WithValues(kvList ...interface{}) logr.Logger { out := l.clone() out.fields = append(out.fields, kvList...) return out diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..28fc542 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/go-coder/log + +go 1.12 + +require ( + github.com/go-coder/logr v0.1.0 + github.com/go-logr/logr v0.1.0 + github.com/spf13/pflag v1.0.3 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c81fe72 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/go-coder/logr v0.1.0 h1:cKKbRhVJXz38rq0AG/QVIM6Atx14po8nL8It6wgi7+w= +github.com/go-coder/logr v0.1.0/go.mod h1:Wb42XhUxL//12vqkxbdiqPP04fqnODPsXhd87jSiKqw= +github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= diff --git a/logr.go b/logr.go index 924ed7b..0dc8530 100644 --- a/logr.go +++ b/logr.go @@ -3,7 +3,7 @@ package log import ( "github.com/go-coder/log/backend" "github.com/go-coder/log/frontend" - "github.com/go-coder/logr" + "github.com/go-logr/logr" ) var ( @@ -13,7 +13,7 @@ var ( V = syslog.V WithName = syslog.WithName - WithFields = syslog.WithFields + WithValues = syslog.WithValues Info = syslog.Info Error = syslog.Error ) diff --git a/scripts/go-build b/scripts/go-build new file mode 100644 index 0000000..434b6e0 --- /dev/null +++ b/scripts/go-build @@ -0,0 +1,18 @@ +#!/bin/bash + +# Use the Unofficial Bash Strict Mode (Unless You Looove Debugging) | Aaron Maxwell +# http://redsymbol.net/articles/unofficial-bash-strict-mode/ +set -euo pipefail +IFS=$'\n\t' + +main() { + echo " > formating file ..." + for dir in . ./types ./frontend ./backend ./example; do + go fmt $dir/... + golangci-lint run $dir/... + done + echo " > building file ..." + go build -o ./bin/main ./example/main.go +} + +main ${@:-} \ No newline at end of file