Closed liwnn closed 1 year ago
提交了一版到这个分支,估计最近会合并到master和release: https://github.com/lesismal/nbio/commit/5d0c6a2daac7c3d356df8015e7e676467114bd9c
可以先用这个分支测试,或者应用层自己先封装个结构体实现 Logger :
type UserLogger struct {
fsetLevel func(lvl int)
fdebug func(format string, v ...interface{})
finfo func(format string, v ...interface{})
fwarn func(format string, v ...interface{})
ferror func(format string, v ...interface{})
}
然后初始化把这个logger字段设置为zaplog的func,setLevel设置个空函数 func(int) {},这样应该也是可以的
或者把Output
设置为你们的writer也可以,但nbio部分的日志还是nbio自己的格式:
logging.Output = yourLogWriter
或者把
Output
设置为你们的writer也可以,但nbio部分的日志还是nbio自己的格式:logging.Output = yourLogWriter
ok,先用这种方式
不好意思,之前漏看了logger接口。参考了zap、grpc这些库的logger接口:
Debugf(format string, v ...interface{})
Debug(v ...interface{})
带format的函数名都会有加上f。可以自己重新定义适配nbio,不过我觉得还是跟这些库适配下比较好。您评估下,觉得没必要就给close掉
我前面两层楼说的方法,都可以用户层自己进行适配实现的。
没有必要向其他库看齐,否则又有一个其他知名的库跟你提到的库不一样,我得怎么看齐呢?
用户自己能适配的就自己适配下,不能指望基础设施都向用户需求看齐,否则标准库的很多东西得天天接需求改造。
有些基础设施的库,直接用标准库的 log,但标准库 log 也跟 zap 这些不一样。
另一个知名库 zerolog 还有这样用法: log.Info().Msg("hello world")
logrus 有 log.SetLevel(log.WarnLevel)
不能只以你们自己项目的习惯用法来要求基础设施啊!
兄弟,我有点后悔今天对 Logger 的修改啊,不能随便听你们的需求!
下个版本我revert回去
:joy::joy:
哈哈,把你带沟里去了。我看了下logrus,他也是这样的
func (logger *Logger) Debug(args ...interface{}) {
logger.Log(DebugLevel, args...)
}
func (logger *Logger) Debugf(format string, args ...interface{}) {
logger.Logf(DebugLevel, format, args...)
}
go的标准库log:
func Printf(format string, v ...any) {
感觉这是大家的一个默认规则,带f的都有format。我觉可以像大多数库看齐
有些基础设施的库,直接用标准库的 log,但标准库 log 也跟 zap 这些不一样。
另一个知名库 zerolog 还有这样用法:
log.Info().Msg("hello world")
logrus 有
log.SetLevel(log.WarnLevel)
不能只以你们自己项目的习惯用法来要求基础设施啊!
兄弟,我有点后悔今天对 Logger 的修改啊,不能随便听你们的需求!
下个版本我revert回去
😂😂
这个我就不太认同的,logrus带level,是因为它是日志库的具体实现。同样的zap、包括nbio默认库都需要有setlevel函数。但是logging是一个抽象接口,只需要能打印日志就行。具体的是setlevel或者有些日志需要flush,logging就不管了
这个其实影响不大,我觉的像大多数知名库看齐,方便即插即用。可以适配,这个就先关了
感觉这是大家的一个默认规则,带f的都有format。我觉可以像大多数库看齐
这个我就不太认同的,logrus带level,是因为它是日志库的具体实现。同样的zap、包括nbio默认库都需要有setlevel函数。但是logging是一个抽象接口,只需要能打印日志就行。具体的是setlevel或者有些日志需要flush,logging就不管了
需求不一样,nbio的需求是打印自己这一层的日志,一些用处不大的debug日志之类的,方便用户通过 SetLevel 屏蔽,所以才内部区分成 Debug/Info 这些级别。format 是为了 nbio 自己日志的方便。
而其他那些专业日志库,是面向日志这种“业务”本身,所以它们要考虑性能、结构化等各种,提供给用户的接口也需要你说的所谓的标准,但这不适用于 nbio。
就像标准库有些地方也会有日志,但并不会向你说的这些日志库看齐、连 Level 都不区分的。
web框架echo里提供了Debug/Debugf进行区分: https://github.com/labstack/gommon/blob/master/log/log.go#L157
我个人觉得基础库的日志格式,还是带 format 的更简洁明了些。 nbio里的日志不是特别多,把 f 省去了,即使区分 Debug和Debugf的实现,也是调用 Debugf。 然后用户还是要来适配 Debugf、还是带format。 但这些都可以通过我上面说的两种方案来实现,想与其他库结构化之类的格式兼容,就自己Wrap个中间层实现Logger定义设置下,如果不需要就 SetOutput 就可以了
如果与其他库适配,虽然方便即插即用了,但不带format、日志就不那么好看了。 如果用 Printf,就与我上面说的方便用户屏蔽不需要的Debug日志之类的冲突。
所以下个版本我再把SetLevel弄回来
应用层该做适配的,由应用层自己实施,没什么难度。
但是我觉得,只是把Debug改名成Debugf,就可以适配大多数日志库,而不用让使用者自己去适配。整理是利大于弊的
结构化日志也并不是所有项目的需求。 上了ELK之类的日志系统,如果遇到有业务故障需要编码通过结构化日志来自动处理,这种场景下,结构化日志优势,但前提也是编写日志代码有较好得设计规范和实现。 但更多的项目,甚至是在用ELK这些的,还是在用format为主的日志,包括上了ELK这些的,很多还是format了的、只是全文搜索关键字比较方便。 绝大多数项目对业务可用性要求没那么高、或者说不是每一个模块对业务可用性都那么高,对结构化日志的要求就更低了,因为单就可读性来讲,结构化的比format的要差一些。 还有基础设施类的,网关、代理,,大家都是format这种
我的意思是保持用带format的形式。但是函数名改成Debugf、Infof、Warnf、Errof。这样可以直接适配其他日志
type Logger interface {
Debugf(format string, v ...interface{})
Infof(format string, v ...interface{})
Warnf(format string, v ...interface{})
Errorf(format string, v ...interface{})
}
不是说去掉format,感觉说的不是一个频道
这样仅仅把函数名字改下,就可以同大多数日志库一致。而且成本也很低
不是说去掉format,感觉说的不是一个频道
"但是我觉得,只是把Debug改名成Debugf,就可以适配大多数日志库,而不用让使用者自己去适配。整理是利大于弊的“ 你说这句的时候我正在编辑这楼下面的那段,并不是回复你这一楼的,而是接着我前面楼层
这样仅仅把函数名字改下,就可以同大多数日志库一致。而且成本也很低
这就和旧版本不兼容性了,别人的项目也有设置 Logger 替换 nbio 这个默认 Logger 的,修改了别人更新nbio就不兼容了。不能只考虑你的需求啊
还是前面说的那样,你可以自己Wrap一个logger实现我这几个蹩脚的接口就可以了
这样仅仅把函数名字改下,就可以同大多数日志库一致。而且成本也很低
这就和旧版本不兼容性了,别人的项目也有设置 Logger 替换 nbio 这个默认 Logger 的,修改了别人更新nbio就不兼容了。不能只考虑你的需求啊
还是前面说的那样,你可以自己Wrap一个logger实现我这几个蹩脚的接口就可以了
好的,兼容性确实是个问题,那就我自己适配
好的,兼容性确实是个问题,那就我自己适配
很多基础设施,是没有统一标准的,比如你同时依赖多个基础设施,这几个设施依赖了不同的日志实现,很可能这几个日志实现不一样,那只能应用层自己做兼容、不可能要求每个基础设施都去按照你们项目习惯使用的那个日志库去实现。
还有比如对net.Conn的实现,一些网络协议时没有实现net.Conn的比如websocket、QUIC。 所以我另一个项目arpc里,为了支持多种协议做transport层,去自己封装一层来实现net.Conn: https://github.com/lesismal/arpc/tree/master/extension/protocol
还有比如这个例子: https://github.com/lesismal/nbio/issues/263
gin、echo的 ResponseWriter,因为它们要考虑 SEO 所以支持 template 为主,所以都没有支持 sendfile 这种 zero copy ,但对静态资源服务就性能不友好了,而且各个框架各自实现都不一样,只能应用层按各个框架各自方式来处理它们
中间层才是王道
睡了睡了,晚安了
晚安,感谢大佬解答。希望nbio越来越火
我又想了下,或许可以这样实现老版本兼容:
nbio自己先添加一个中间层的 thirdLogger:
// third_logger.go
package logging
type thirdLogger struct {
setLevel func(l int)
debug func(format string, v ...interface{})
info func(format string, v ...interface{})
warn func(format string, v ...interface{})
err func(format string, v ...interface{})
}
// SetLevel sets logs priority.
func (l *thirdLogger) SetLevel(lvl int) {
l.setLevel(lvl)
}
// Debug uses fmt.Printf to log a message at LevelDebug.
func (l *thirdLogger) Debug(format string, v ...interface{}) {
l.debug(format, v...)
}
// Info uses fmt.Printf to log a message at LevelInfo.
func (l *thirdLogger) Info(format string, v ...interface{}) {
l.info(format, v...)
}
// Warn uses fmt.Printf to log a message at LevelWarn.
func (l *thirdLogger) Warn(format string, v ...interface{}) {
l.warn(format, v...)
}
// Error uses fmt.Printf to log a message at LevelWarn.
func (l *thirdLogger) Error(format string, v ...interface{}) {
l.err(format, v...)
}
SetLogger 方法里面做兼容:
// Logger defines log interface.
type Logger interface {
SetLevel(l int)
Debug(format string, v ...interface{})
Info(format string, v ...interface{})
Warn(format string, v ...interface{})
Error(format string, v ...interface{})
}
// SetLogger sets default logger.
func SetLogger(v interface{}) {
if l, ok := v.(Logger); ok {
DefaultLogger = l
return
}
l := &thirdLogger{}
if i, ok := v.(interface {
Debugf(format string, v ...interface{})
Infof(format string, v ...interface{})
Warnf(format string, v ...interface{})
Errorf(format string, v ...interface{})
}); ok {
l.debug = i.Debugf
l.info = i.Infof
l.warn = i.Warnf
l.err = i.Errorf
} else {
ilogger := &logger{level: LevelInfo}
l.debug = ilogger.Debug
l.info = ilogger.Info
l.warn = ilogger.Warn
l.err = ilogger.Error
l.setLevel = ilogger.SetLevel
}
if i, ok := v.(interface {
SetLevel(l int)
}); ok {
l.setLevel = i.SetLevel
}
DefaultLogger = l
}
这样即使三方库使用了 SetLogger,参数类型时 interface{} 了也能兼容到别人的调用,也能把别人的这些 f 系列函数的 Logger 接口设置进来
type Logger interface {
Debugf(format string, v ...interface{})
Infof(format string, v ...interface{})
Warnf(format string, v ...interface{})
Errorf(format string, v ...interface{})
}
完整的: logging.zip
还是算了,这样的话代码就太奇怪了
ok, ok。确实不好改,得项目开始就定义好。
https://github.com/grpc/grpc-go/blob/master/grpclog/loggerv2.go grpc的做法,加了个V2。然后把原先的标记为Deprecated
不改了,应用层自己适配下,其他人有这样做的: https://github.com/inaneverb/ekaweb/blob/master/framework/nbio/logging_bridge.go
grpc那个V2,如果用户想设置自己的logger还是要实现 LoggerV2,这里有个 V(l int) bool
未必每个logger框架都有、用户还是要自己适配:
https://github.com/grpc/grpc-go/blob/master/grpclog/loggerv2.go#L66
echo框架的 Logger 也是有 SetLevel 的: https://github.com/labstack/echo/blob/master/log.go#L17
按照你们习惯使用的日志库,如果你们使用 echo 框架那是不是也需要让它去改一下 Logger 定义? 实际上没必要的,应用层自己来适配吧,别用几家的定义去约束其他家的定义。
ok, ok.我已经做了适配了,就是突然想到的
gin的一些日志更霸道,根本不给你自己搞格式的机会,只能output: https://github.com/gin-gonic/gin/blob/master/debug.go#L50 https://github.com/gin-gonic/gin/blob/master/logger.go#L267
标准库不是slog嘛,只能等标准库的slog越来越证明成熟适合生产后,大家统一换到slog就好了,否则日志这玩意一家一个样子,没有完全一样的。 标准库的 log.Printf 自动换行的,那还要 log.Println 也是完全多余啊(省了format性能倒是略好)。。。所以我就没打算遵循标准库原来的 log 或者其他日志库。我这一组不带f,纯是自己只有一种日志需要、带上 f 的名字太丑,就粗暴这样搞了,留给用户可配置的方式了用户想咋弄都行的
对,等标准库 slog 666 了,我换到 slog 去,哈哈哈
主要是现在有个问题,不同的库都有自己的logging接口。有的Infof带格式,有的Info带格式,适配起来有些麻烦。只能等个能统一标准的
这事只能等标准库了,任何非官方都难做到一统江湖。
fasthttp这个最简洁不能再简洁了。。 https://github.com/valyala/fasthttp/blob/master/server.go#L856
休息了,晚安
这事只能等标准库了,任何非官方都难做到一统江湖。
fasthttp这个最简洁不能再简洁了。。 https://github.com/valyala/fasthttp/blob/master/server.go#L856
确实需要标准库,现在只能舍弃一部分日志行号了。因为带f跟不带f的不兼容。写两个文件可读性又差,代码行还可以接受。
确实需要标准库,现在只能舍弃一部分日志行号了。因为带f跟不带f的不兼容。写两个文件可读性又差,代码行还可以接受。
行号不至于啊,日志库一般都支持设置stack depth的,zap的我看了下,弄个单独的 sugar 给nbio,这个sugar用 AddCallerSkip
就可以了:
https://github.com/uber-go/zap/issues/1070#issuecomment-1078681368
测试文件名 log.go
,完整代码:
// log.go
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"time"
"github.com/lesismal/nbio/logging"
"github.com/lesismal/nbio/nbhttp"
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
// setup nbio logger
nbioSugar := logger.Sugar().WithOptions(zap.AddCallerSkip(2))
logging.SetLogger(newNBIOLogger(nbioSugar))
defer nbioSugar.Sync()
// Your sugar logger
sugar := logger.Sugar()
defer sugar.Sync()
sugar.Infof("App log 111")
mux := &http.ServeMux{}
mux.HandleFunc("/now", onTime)
engine := nbhttp.NewEngine(nbhttp.Config{
Network: "tcp",
Addrs: []string{"localhost:8080"},
Handler: mux,
})
err := engine.Start()
if err != nil {
fmt.Printf("nbio.Start failed: %v\n", err)
return
}
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
<-interrupt
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
engine.Shutdown(ctx)
}
func onTime(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(time.Now().Format("20060102 15:04:05")))
}
type nbioLogger struct {
sugar *zap.SugaredLogger
}
func (logger *nbioLogger) SetLevel(_ int) {
// Do nothing. Nbio package does not call this method.
}
func (logger *nbioLogger) Debug(format string, v ...interface{}) {
logger.sugar.Debugf(format, v...)
}
func (logger *nbioLogger) Info(format string, v ...interface{}) {
logger.sugar.Infof(format, v...)
}
func (logger *nbioLogger) Warn(format string, v ...interface{}) {
logger.sugar.Warnf(format, v...)
}
func (logger *nbioLogger) Error(format string, v ...interface{}) {
logger.sugar.Errorf(format, v...)
}
func newNBIOLogger(sugar *zap.SugaredLogger) *nbioLogger {
return &nbioLogger{sugar: sugar}
}
output:
{"level":"info","ts":1684896533.5934606,"caller":"log/log.go:28","msg":"App log 111"}
{"level":"info","ts":1684896533.593972,"caller":"nbio@v1.3.16/engine_std.go:102","msg":"NBIO[NB] start"}
{"level":"info","ts":1684896533.6130466,"caller":"nbhttp/engine.go:378","msg":"Serve HTTP On: [tcp@127.0.0.1:8080]"}
如果不支持设置栈深度,那三方日志库就不能被封装日志接口了、文件都得错乱,所以这应该是各个知名日志库的基本功能。
如果不支持设置栈深度,那三方日志库就不能被封装日志接口了、文件都得错乱,所以这应该是各个知名日志库的基本功能。
因为还有另外的库也用了zaplog,它兼容zaplog,而nbio包了一层,所以深度不一样的。不过您这么一说我知道怎么搞了,我再包一层适配另外的库,让它们深度一样就解决了。
因为还有另外的库也用了zaplog,它兼容zaplog,而nbio包了一层,所以深度不一样的。不过您这么一说我知道怎么搞了,我再包一层适配另外的库,让它们深度一样就解决了。
不同的库做适配,用不同的sugarlogger,每个sugarlogger设置自己的深度就可以了 我示例代码里,Your sugar就是zap默认的深度,nbioSugar就是深度+2(包装的这个Logger一层、nbio.logging.Info这些包函数一层,一共是2) 你跑下我的示例代码,看下output,nbio里的日志是打印了正常的文件行号的,app自己的sugar也是打印了正确的行号的:
/ setup nbio logger // 给nbio单独一个sugar、深度+2
nbioSugar := logger.Sugar().WithOptions(zap.AddCallerSkip(2))
logging.SetLogger(newNBIOLogger(nbioSugar))
defer nbioSugar.Sync()
// Your sugar logger // 你们项目自己的sugar,不用更改深度
sugar := logger.Sugar()
defer sugar.Sync()
// Other sugar logger ,如果也需要适配其他库,再弄个sugar给它就可以了
// ...
output:
// 这个是测试代码的正确文件行号
{"level":"info","ts":1684896533.5934606,"caller":"log/log.go:28","msg":"App log 111"}
// 这两个都是nbio正确的文件行号
{"level":"info","ts":1684896533.593972,"caller":"nbio@v1.3.16/engine_std.go:102","msg":"NBIO[NB] start"}
{"level":"info","ts":1684896533.6130466,"caller":"nbhttp/engine.go:378","msg":"Serve HTTP On: [tcp@127.0.0.1:8080]"}
你先看下我的示例代码就懂了
明白了,感谢
好嘞,不客气
想把nbio的logger接入zaplog,代码如下
但是zaplogger没有实现SetLevel(lvl int)接口,所以没法接入。看下能否去掉SetLevel接口?
日志级别应该是日志实现者的职责,不应该强制设置。因为日志级别的字段类型不同,也很难统一设置。