logging

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2022 License: MIT Imports: 6 Imported by: 6

README

Logging

尝试提供一套易用的日志门面API接口。

设计特性

  • 可用易用
  • 非vendor绑定。仅定义接口和数据struct,内置 Nop (什么也不输出)。
  • 方便适配。提供一个可用参考实现:zap-logging

安装

TBD

使用方法

TBD

适配方法

TBD

设计考量

职责分离

API设计只管调用方如何使用日志框架:可能使用哪些方法输出哪些内容, 能输出何种等级日志(但不关心如何设置可输出日志等级)。

API层不关心输出哪些固定字段、输出何种格式 、输出到哪里等。 这些由提供适配的 vendor 关心。 vendor 要负责日志系统的配置初始化、设置logger 日志等级、日志输出格式、日志输出目标文件等。

kratos,or not kratos

整个 yimi-go 项目是为了实现一个方便部署到 service-mesh 环境的微服务框架。 在设计上大量的借鉴其他开源 kit 框架。

kratos 是优秀的框架项目,其代码实现是很有参考价值。其实现基本处处体现着简洁。 但在日志部分,我觉得其在设计上有些过犹不及,过于简洁,只提供了一个简单的接口供适配:

// Logger is a logger interface.
type Logger interface {
	Log(level Level, keyvals ...interface{}) error
}

同时提供一个 Helper 来提供方便使用的 Info、Error 等。

这种设计有以下几个问题:

  • 同时暴露两个接口,调用方可能使用任意一个。 咋一看,这不会是什么问题,但到需要日志携带 caller 信息时,问题就来了。 vendor 适配时难以确定需要 skip 多少 stack 层级来计算 caller 和 stacktrace。 为了解决这个问题,kratos 贴心的提供了 CallerValuer 构造器函数, 方便调用者提供 caller 信息。但问题并没完美解决,还带来了以下问题:
    • 是否需要计算 caller 无法在运行时通过配置控制,因为已经在代码里写死了。
    • 开发者必须关心 vendor,vendor 锁定。 开发者必须在开发阶段确定使用哪个 vendor 的适配,才能准确计算 caller depth, 且必须保证之后不能替换 vendor。
  • Log 接口的 Log 方法使用了变长参数 keyvals 。这个参数提供要输出的日志结构键值对。 如果提供的键值对不成对,可能产生错位,需要人工检查(可靠性?)或打warn日志(为时晚矣?) 或panic(Oh no!)。
  • 项目其他功能模块直接使用 Helper,而 Helper 是 struct ,无法变更实现, 对测试 mock 也不友好。经典的设计模式、最佳工程实践都告诉我们要基于接口而非实现编程, Kratos 项目基于 Helper 使用日志框架也算是个反模式了。

Kratos 提供了一系列工程的最佳实践,尤其是在服务性可用性方面,log 模块成为了整个项目的一大败笔。

当然,以上一些问题也可能不是问题,毕竟代码是要人来写的,通过行政手段管好人不犯错就可以了😅。

标准库 logger?

项目一开始计划提供标准库 log 的接口抽象,毕竟标准库提供的 logger 是个 struct, 存在难以 mock 等问题。但经过考虑还是放弃了对标准库 logger 的支持:

  • 标准库的功能实在过于简单,或者说简陋。 以本人愚见,只有 prints 方法在比较简单的场景下有些可用价值。 标准库logger 不支持结构化输出,要输出结构化,需要大量代码组装结构化数据再序列化,使用复杂。
  • 其 caller 计算是在内部实现的。外部完全无法介入。 一旦需要将其封装到工具函数/方法里,就需要类似 kratos caller valuer 的方案了。
  • 标准库logger 提供了危险的 panic 和更危险的 fatal 类方法。没有特殊的理由,业务不应主动 panic。 即使要 panic 也必须妥善的 recover 处理,否则导致进程不断退出重启,就是 bug、事故了。 fatal 类方法更是没有 recover 机会。 通常只有启动 init 阶段检查启动条件不满足时才会 panic,且此时需要返回特定进程退出码时才会使用 fatal/exit。 相比将 panic/fatal 能力提供到 logger API 里,建议真正需要panic/fatal的时候使用 error 级别日志 + 手动 panic / os.Exit。
  • 如果日志框架提供了更全功能的日志接口时,谁还会用提供的标准化库 logger 接口呢?🤔
    • 当将其抽象出一个 stdlogger 接口时,对外需要暴露两个接口:stdLogger、logger。
      • 如果两个接口不相关,适配层没有动力实现 stdLogger。 标准库接口功能比较简单,使用这个接口开发都的基本都是确定自己的日志需求已经可以被这个API满足了。 那么 vendor 实现这个接口时如果功能比标准库有加强就会打破开发者的预期; 如果不加强,为什么不直接使用标准库的 logger struct?
      • 所以只能把 stdLogger 作为 logger的内嵌接口。 那么应该暴露哪个接口给使用方呢?
        • 如果框架的 getLogger 的函数/方法返回 stdLogger,那么使用方需要 assert 以获取全功能的 Logger。 一方面如前面所述,如果调用方是要用标准库相同的 API,何必使用这个框架? 另一方面,如果调用方是为了使用全功能的 Logger ,那代码里大量的 assert 会很麻烦、很蠢。
        • 如果 getLogger 提供 logger ,那么 stdLogger 估计永远用不到。 标准库的 Logger 也一定实现不了全功能 Logger 接口。

不支持 glog 系的 V 方法

glog 支持 按 module 控制日志输出和 V 方法分级输出。但是:

  • 按 module 控制只能按 package 最后路径部分控制,控制精度有问题。
  • 按V方法分级输出会有过度输出的问题。对于长时间运行的服务端应用来说,日志量放大可能导致服务可用性问题。

而且这两种方法基本都是以启动参数的方式来设置,不适用动态调整。 yimi-go 最终目标是要支持 service-mash 的云原生应用,应该是日志配置文件化、版本化。 通过文件系统监听可以实现配置的动态重载。 通过按 logger name 配置日志等级,可以解决 glog 按文件目录 module 控制精度问题。

不支持 panic 和 fatal 类日志方法

在不支持标准库 logger 部分已有说明,这里不再赘述。

其他风格问题考虑

  • 之前有读到过一篇博文,其表述了一个观点:日志应取消 Warn 级别,因为不是Error所以没有人会看,并导致日志级别错乱。 初读之时大为赞同,但细想,用错日志等级还是程序员的问题。取消 Warn 级别还是不现实。 我可以不用,但你不能没有啊!而且 Warn 日志也远没有 panic / fatal 那样的"负"作用。 另外,使用日志收集系统和 metric 系统,是可以基于日志等级做报警系统的。
  • 有些日志框架API提供了获取 Leveled Logger 再 print 的方式调用。 实现框架时也考虑过这样,因为这样可以提供比较简洁的API界面。但实现使用上代码会冗长不少, 当前日志接口的设计是要易用易接入,与设计方向相背,故放弃。

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	LevelName = map[Level]string{
		DebugLevel: "DEBUG",
		InfoLevel:  "INFO",
		WarnLevel:  "WARN",
		ErrorLevel: "ERROR",
		OffLevel:   "OFF",
	}
	LevelValue = map[string]Level{
		"DEBUG": DebugLevel,
		"INFO":  InfoLevel,
		"WARN":  WarnLevel,
		"ERROR": ErrorLevel,
		"OFF":   OffLevel,
	}
)

Functions

func NewContext

func NewContext(ctx context.Context, field ...Field) context.Context

NewContext wraps fields into a new context and return it.

Types

type Factory

type Factory interface {
	// Logger returns a Logger by name, which may be cached.
	// It should never return nil.
	Logger(name string) Logger
}

Factory is a Factory that produces Logger by name.

func GetFactory

func GetFactory() Factory

GetFactory returns the registered Factory. It should never return nil. By default, a nop Logger Factory will be returned.

For any production projects, a vendor provided Factory should be registered first via SwapFactory before first calling GetFactory

func NewNopLoggerFactory

func NewNopLoggerFactory() Factory

NewNopLoggerFactory returns a Factory that create Loggers that print nothing and enabled none Levels.

func SwapFactory

func SwapFactory(factory Factory) Factory

SwapFactory registers new Factory, and returns the origin Factory.

For any production projects, a vendor provided Factory should be registered first via SwapFactory before first calling GetFactory

type Field

type Field interface {
	// Key is field key.
	Key() string
	// Type is field type.
	Type() FieldType
	// Value is field value.
	Value() any
}

Field is logging field.

func Any

func Any(key string, value any) Field

Any creates a Field of UnknownType value.

func Binary

func Binary(key string, value []byte) Field

Binary creates a Field of BinaryType value.

func Bool

func Bool(key string, value bool) Field

Bool creates a Field of BoolType value.

func Boolp

func Boolp(key string, value *bool) Field

Boolp creates a Field of BoolType value if value is not nil, or a Field of UnknownType value if value is nil.

func Complex128

func Complex128(key string, value complex128) Field

Complex128 creates a Field of Complex128Type value.

func Complex128p

func Complex128p(key string, value *complex128) Field

Complex128p creates a Field of Complex128Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Complex64

func Complex64(key string, value complex64) Field

Complex64 creates a Field of Complex64Type value.

func Complex64p

func Complex64p(key string, value *complex64) Field

Complex64p creates a Field of Complex64Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Duration

func Duration(key string, value time.Duration) Field

Duration creates a Field of DurationType value.

func Durationp

func Durationp(key string, value *time.Duration) Field

Durationp creates a Field of DurationType value if value is not nil, or a Field of UnknownType value if value is nil.

func Error

func Error(err error) Field

Error creates a Field of ErrorType value, with key "error".

func Float32

func Float32(key string, value float32) Field

Float32 creates a Field of Float32Type value.

func Float32p

func Float32p(key string, value *float32) Field

Float32p creates a Field of Float32Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Float64

func Float64(key string, value float64) Field

Float64 creates a Field of Float64Type value.

func Float64p

func Float64p(key string, value *float64) Field

Float64p creates a Field of Float64Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Int

func Int(key string, value int) Field

Int creates a Field of Int64Type value.

func Int16

func Int16(key string, value int16) Field

Int16 creates a Field of Int16Type value.

func Int16p

func Int16p(key string, value *int16) Field

Int16p creates a Field of Int16Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Int32

func Int32(key string, value int32) Field

Int32 creates a Field of Int32Type value.

func Int32p

func Int32p(key string, value *int32) Field

Int32p creates a Field of Int32Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Int64

func Int64(key string, value int64) Field

Int64 creates a Field of Int64Type value.

func Int64p

func Int64p(key string, value *int64) Field

Int64p creates a Field of Int64Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Int8

func Int8(key string, value int8) Field

Int8 creates a Field of Int8Type value.

func Int8p

func Int8p(key string, value *int8) Field

Int8p creates a Field of Int8Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Intp

func Intp(key string, value *int) Field

Intp creates a Field of Int64Type value if value is not nil, or a Field of UnknownType value if value is nil.

func NamedError

func NamedError(key string, err error) Field

NamedError create a Field of ErrorType value.

func Stack

func Stack(key string) Field

Stack create a Field that captures stacktrace of the current goroutine.

func StackSkip

func StackSkip(key string, skip int) Field

StackSkip create a Field that captures stacktrace of the current goroutine, skipping the given number of frames from the top of the stacktrace.

func String

func String(key string, value string) Field

String creates a Field of StringType value.

func Stringer

func Stringer(key string, value fmt.Stringer) Field

Stringer creates a Field of StringerType value.

func Stringp

func Stringp(key string, value *string) Field

Stringp creates a Field of StringType value if value is not nil, or a Field of UnknownType value if value is nil.

func Time

func Time(key string, value time.Time) Field

Time creates a Field of TimeType value.

func Timep

func Timep(key string, value *time.Time) Field

Timep creates a Field of TimeType value if value is not nil, or a Field of UnknownType value if value is nil.

func Uint

func Uint(key string, value uint) Field

Uint creates a Field of Uint64Type value.

func Uint16

func Uint16(key string, value uint16) Field

Uint16 creates a Field of Uint16Type value.

func Uint16p

func Uint16p(key string, value *uint16) Field

Uint16p creates a Field of Uint16Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Uint32

func Uint32(key string, value uint32) Field

Uint32 creates a Field of Uint32Type value.

func Uint32p

func Uint32p(key string, value *uint32) Field

Uint32p creates a Field of Uint32Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Uint64

func Uint64(key string, value uint64) Field

Uint64 creates a Field of Uint64Type value.

func Uint64p

func Uint64p(key string, value *uint64) Field

Uint64p creates a Field of Uint64Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Uint8

func Uint8(key string, value uint8) Field

Uint8 creates a Field of Uint8Type value.

func Uint8p

func Uint8p(key string, value *uint8) Field

Uint8p creates a Field of Uint8Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Uintp

func Uintp(key string, value *uint) Field

Uintp creates a Field of Uint64Type value if value is not nil, or a Field of UnknownType value if value is nil.

func Uintptr

func Uintptr(key string, value uintptr) Field

Uintptr creates a Field of UintptrType value.

func Uintptrp

func Uintptrp(key string, value *uintptr) Field

Uintptrp creates a Field of UintptrType value if value is not nil, or a Field of UnknownType value if value is nil.

type FieldType

type FieldType uint8
const (
	// UnknownType is the default field type.
	UnknownType FieldType = iota
	// BinaryType indicates that the field carries an opaque binary blob.
	BinaryType
	// BoolType indicates that the field carries a bool.
	BoolType
	// Complex128Type indicates that the field carries a complex128.
	Complex128Type
	// Complex64Type indicates that the field carries a complex128.
	Complex64Type
	// DurationType indicates that the field carries a time.Duration.
	DurationType
	// Float64Type indicates that the field carries a float64.
	Float64Type
	// Float32Type indicates that the field carries a float32.
	Float32Type
	// Int64Type indicates that the field carries an int64.
	Int64Type
	// Int32Type indicates that the field carries an int32.
	Int32Type
	// Int16Type indicates that the field carries an int16.
	Int16Type
	// Int8Type indicates that the field carries an int8.
	Int8Type
	// StringType indicates that the field carries a string.
	StringType
	// TimeType indicates that the field carries a time.Time.
	TimeType
	// Uint64Type indicates that the field carries an uint64.
	Uint64Type
	// Uint32Type indicates that the field carries an uint32.
	Uint32Type
	// Uint16Type indicates that the field carries an uint16.
	Uint16Type
	// Uint8Type indicates that the field carries an uint8.
	Uint8Type
	// UintptrType indicates that the field carries an uintptr.
	UintptrType
	// StringerType indicates that the field carries a fmt.Stringer.
	StringerType
	// ErrorType indicates that the field carries an error.
	ErrorType
	// StackType indicates that the field captures stacktrace of the current goroutine.
	StackType
)

type Level

type Level int32

Level is a log level.

const (
	// DebugLevel is a debug log level.
	DebugLevel Level = iota - 1
	// InfoLevel is an info log level, the default level.
	InfoLevel
	// WarnLevel is a warn log level.
	WarnLevel
	// ErrorLevel is a error log level.
	ErrorLevel
	// OffLevel is a log level that prevent logging.
	OffLevel
)

func (Level) Enabled

func (l Level) Enabled(proba Level) bool

Enabled Each concrete Level value implements a static LevelEnabler which returns true for itself and all higher logging levels, except OffLevel. For example WarnLevel.Enabled() will return true for WarnLevel and ErrorLevel but return false for InfoLevel and DebugLevel, and OffLevel.

func (Level) MarshalYAML

func (l Level) MarshalYAML() (any, error)

func (Level) String

func (l Level) String() string

func (*Level) UnmarshalYAML

func (l *Level) UnmarshalYAML(value *yaml.Node) error

type LevelEnabler

type LevelEnabler interface {
	Enabled(Level) bool
}

LevelEnabler decides whether a given logging level is enabled when logging a message.

type Logger

type Logger interface {
	// LevelEnabler enforce a Logger provide a Enabled method.
	LevelEnabler

	// Debug outputs a log at DebugLevel if the Logger enabled DebugLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the Print method does.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Debug(v ...any)
	// Debugln outputs a log at DebugLevel if the Logger enabled DebugLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the fmt.Println method does but without an ending new line.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Debugln(v ...any)
	// Debugf outputs a log at DebugLevel if the Logger enabled DebugLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the fmt.Printf method does.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Debugf(format string, v ...any)
	// Debugw outputs a log at DebugLevel if the Logger enabled DebugLevel.
	//
	// This method allow you log extra Fields to output.
	//
	// For fields with same name, usually, the later one override the former and builtin ones.
	// But a vendor may change this behavior by merge them.
	// Read the references of the vendor you chose about that.
	//
	// If you want format you msg field, do it before you call this method.
	// If formatting is expensive, you'd better call Enabled method before do that.
	Debugw(message string, field ...Field)

	// Info outputs a log at InfoLevel if the Logger enabled InfoLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the Print method does.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Info(v ...any)
	// Infoln outputs a log at InfoLevel if the Logger enabled InfoLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the fmt.Println method does but without an ending new line.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Infoln(v ...any)
	// Infof outputs a log at InfoLevel if the Logger enabled InfoLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the fmt.Printf method does.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Infof(format string, v ...any)
	// Infow outputs a log at InfoLevel if the Logger enabled InfoLevel.
	//
	// This method allow you log extra Fields to output.
	//
	// For fields with same name, usually, the later one override the former and builtin ones.
	// But a vendor may change this behavior by merge them.
	// Read the references of the vendor you chose about that.
	//
	// If you want format you msg field, do it before you call this method.
	// If formatting is expensive, you'd better call Enabled method before do that.
	Infow(message string, field ...Field)

	// Warn outputs a log at WarnLevel if the Logger enabled WarnLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the Print method does.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Warn(v ...any)
	// Warnln outputs a log at WarnLevel if the Logger enabled WarnLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the fmt.Println method does but without an ending new line.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Warnln(v ...any)
	// Warnf outputs a log at WarnLevel if the Logger enabled WarnLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the fmt.Printf method does.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Warnf(format string, v ...any)
	// Warnw outputs a log at WarnLevel if the Logger enabled WarnLevel.
	//
	// This method allow you log extra Fields to output.
	//
	// For fields with same name, usually, the later one override the former and builtin ones.
	// But a vendor may change this behavior by merge them.
	// Read the references of the vendor you chose about that.
	//
	// If you want format you msg field, do it before you call this method.
	// If formatting is expensive, you'd better call Enabled method before do that.
	Warnw(message string, field ...Field)

	// Error outputs a log at ErrorLevel if the Logger enabled ErrorLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the Print method does.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Error(v ...any)
	// Errorln outputs a log at ErrorLevel if the Logger enabled ErrorLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the fmt.Println method does but without an ending new line.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Errorln(v ...any)
	// Errorf outputs a log at ErrorLevel if the Logger enabled ErrorLevel.
	//
	// The parameters would be used to forming the log msg field
	// via formatting like the fmt.Printf method does.
	//
	// If formatting is expensive, you'd better call Enabled method before do that.
	Errorf(format string, v ...any)
	// Errorw outputs a log at ErrorLevel if the Logger enabled ErrorLevel.
	//
	// This method allow you log extra Fields to output.
	//
	// For fields with same name, usually, the later one override the former and builtin ones.
	// But a vendor may change this behavior by merge them.
	// Read the references of the vendor you chose about that.
	//
	// If you want format you msg field, do it before you call this method.
	// If formatting is expensive, you'd better call Enabled method before do that.
	Errorw(message string, field ...Field)

	// WithField returns a new Logger which wrap extra Fields, including fields of current Logger.
	// This method should never return nil.
	//
	// For fields with same name, usually, the later one override the former and builtin ones.
	// But a vendor may change this behavior by merge them.
	// Read the references of the vendor you chose about that.
	WithField(field ...Field) Logger
}

Logger is logger interface.

func WithContextField

func WithContextField(ctx context.Context, logger Logger) Logger

WithContextField tries extract fields from context and returns a Logger with these fields. If no fields found, the origin logger is returned.

Directories

Path Synopsis
Package hook provide a logger wrapper that allow capture the logging method and parameters.
Package hook provide a logger wrapper that allow capture the logging method and parameters.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL