log/add processor logtext;

develop log/v0.15.0
Ge Song 10 months ago
parent bef1a8cdc0
commit 8d24ba105d

@ -21,11 +21,12 @@ func main() {
}
```
`log` 模块包含日志处理的代码, 由 3 个包组成:
`log` 模块包含日志处理的代码, 由 4 个包组成:
1. [logsdk](./logsdk): 日志实现;
2. [logjson](./logsdk/logjson): 控制台 JSON 日志处理器(`Processor`);
3. [log](.): 根目录, 提供全局 `Logger`, 把 `Logger` / `Entry` 相关的方法封装成函数.
3. [logtext](./logsdk/logtext): 控制台文本日志处理器(`Processor`);
4. [log](.): 根目录, 提供全局 `Logger`, 把 `Logger` / `Entry` 相关的方法封装成函数.
## 基本概念
@ -88,7 +89,7 @@ log.Logger()
关闭强制生成调用栈, `log.Logger().SetReportStackLevel(logsdk.LevelDisabled)`;
记录 panic, 在 `defer v = recover()` 后执行 `log.Z(context.Background(), v)`, 其中 `Z` 需要是附加调用栈的等级;
记录 panic, 在 `defer v = recover()` 后执行 `log.Z(context.Background(), v)`, 其中 `Z` 需要是附加调用栈的等级(例如: `Error`);
mock, 实现 mock 日志处理器对生成的日志进行处理;
@ -96,5 +97,6 @@ mock, 实现 mock 日志处理器对生成的日志进行处理;
## 日志处理器
- [logsdk/logjson](logsdk/logjson) 控制台 JSON 日志
- [go get git.blauwelle.com/go/crate/logotel](../logotel) OpenTelemetry 日志
- [logsdk/logjson](logsdk/logjson) 控制台 JSON 日志.
- [logtext](./logsdk/logtext): 控制台文本日志.
- [go get git.blauwelle.com/go/crate/logotel](../logotel) OpenTelemetry 日志.

@ -2,7 +2,6 @@ package logjson
import (
"io"
"os"
"time"
)
@ -60,10 +59,10 @@ func newConfig(opts ...Option) *config {
opt.apply(cfg)
}
if !cfg.hasPool {
cfg.bytesBufferPool = NewBytesBufferPool(bytesBufferInitialSize, bytesBufferMaximumSize)
cfg.bytesBufferPool = DefaultBytesBufferPool
}
if cfg.output == nil {
cfg.output = NewSyncWriter(os.Stderr)
cfg.output = DefaultStderrSyncWriter
}
if cfg.timestampFormat == "" {
cfg.timestampFormat = time.RFC3339Nano

@ -10,6 +10,8 @@ const (
bytesBufferMaximumSize = 4096
)
var DefaultBytesBufferPool = NewBytesBufferPool(bytesBufferInitialSize, bytesBufferMaximumSize)
type BytesBufferPool interface {
Get() *bytes.Buffer
Put(buffer *bytes.Buffer)

@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"io"
"os"
"git.blauwelle.com/go/crate/log/logsdk"
)
@ -69,11 +68,11 @@ func (processor *Processor) Process(_ context.Context, entry logsdk.ReadonlyEntr
// Encode 2次分配
if err := encoder.Encode(m); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "JSON processor cannot encode log %#v: %s\n", m, err.Error())
_, _ = fmt.Fprintf(processor.output, "JSON processor cannot encode log %#v: %s\n", m, err.Error())
}
if _, err := buf.WriteTo(processor.output); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "JSON processor cannot write log: %s\n", err.Error())
_, _ = fmt.Fprintf(processor.output, "JSON processor cannot write log: %s\n", err.Error())
}
}

@ -2,9 +2,14 @@ package logjson
import (
"io"
"os"
"sync"
)
var (
DefaultStderrSyncWriter = NewSyncWriter(os.Stderr)
)
// NewSyncWriter 返回写互斥的 io.Writer
func NewSyncWriter(writer io.Writer) io.Writer {
return &syncWriter{

@ -0,0 +1,69 @@
package logtext
import (
"io"
"time"
"git.blauwelle.com/go/crate/log/logsdk/logjson"
)
type Option interface {
apply(cfg *config)
}
func WithBufferPool(pool logjson.BytesBufferPool) Option {
return optionFunc(func(cfg *config) {
cfg.bytesBufferPool = pool
})
}
func WithOutput(w io.Writer) Option {
return optionFunc(func(cfg *config) {
cfg.output = w
})
}
func WithTimeFormat(format string) Option {
return optionFunc(func(cfg *config) {
cfg.timeFormat = format
cfg.timePadding = len(format)
})
}
func WithDisableTime(disable bool) Option {
return optionFunc(func(cfg *config) {
cfg.disableTime = disable
})
}
type config struct {
bytesBufferPool logjson.BytesBufferPool
output io.Writer
timeFormat string
timePadding int
disableTime bool
}
func newConfig(opts ...Option) *config {
cfg := new(config)
for _, opt := range opts {
opt.apply(cfg)
}
if cfg.bytesBufferPool == nil {
cfg.bytesBufferPool = logjson.DefaultBytesBufferPool
}
if cfg.output == nil {
cfg.output = logjson.DefaultStderrSyncWriter
}
if cfg.timeFormat == "" {
cfg.timeFormat = time.RFC3339Nano
cfg.timePadding = len(cfg.timeFormat)
}
return cfg
}
type optionFunc func(cfg *config)
func (fn optionFunc) apply(cfg *config) {
fn(cfg)
}

@ -0,0 +1,146 @@
package logtext
import (
"bytes"
"context"
"fmt"
"io"
"strconv"
"git.blauwelle.com/go/crate/log/logsdk"
"git.blauwelle.com/go/crate/log/logsdk/logjson"
)
var _ logsdk.EntryProcessor = &Processor{}
func New(opts ...Option) *Processor {
cfg := newConfig(opts...)
return &Processor{
bytesBufferPool: cfg.bytesBufferPool,
output: cfg.output,
timeFormat: cfg.timeFormat,
timePadding: cfg.timePadding,
disableTime: cfg.disableTime,
}
}
type Processor struct {
bytesBufferPool logjson.BytesBufferPool
output io.Writer
timeFormat string
timePadding int
disableTime bool
}
func (processor *Processor) Process(_ context.Context, entry logsdk.ReadonlyEntry) {
buf := processor.bytesBufferPool.Get()
buf.Reset()
defer processor.bytesBufferPool.Put(buf)
buf.Write(formatLevel(entry.Level))
if !processor.disableTime {
buf.WriteByte(' ')
timeValue := entry.Time.Format(processor.timeFormat)
buf.WriteString(timeValue)
writeSpace(buf, processor.timePadding-len(timeValue))
}
for _, field := range entry.Fields {
buf.WriteByte(' ')
buf.WriteString(field.Key)
buf.WriteByte('=')
writeValue(buf, field.Value)
}
if entry.Message != "" {
buf.WriteByte(' ')
buf.WriteString(entry.Message)
}
buf.WriteByte('\n')
if entry.Caller.IsValid() && len(entry.Stack) == 0 {
writeFrame(buf, entry.Caller)
}
if len(entry.Stack) > 0 {
for _, frame := range entry.Stack {
writeFrame(buf, frame)
}
}
if _, err := buf.WriteTo(processor.output); err != nil {
_, _ = fmt.Fprintf(processor.output, "TEXT processor cannot write log: %s\n", err.Error())
}
}
func writeSpace(buf *bytes.Buffer, n int) {
for i := 0; i < n; i++ {
buf.WriteByte(' ')
}
}
func writeFrame(buf *bytes.Buffer, frame logsdk.Frame) {
buf.WriteByte('|')
buf.WriteByte(' ')
buf.WriteString(frame.Function)
buf.WriteByte('\n')
buf.WriteByte('|')
buf.WriteByte(' ')
buf.WriteByte('\t')
buf.WriteString(frame.File)
buf.WriteByte(':')
buf.WriteString(strconv.Itoa(frame.Line))
buf.WriteByte('\n')
}
func writeValue(buf *bytes.Buffer, value any) {
switch value := value.(type) {
case nil:
buf.WriteString("<nil>")
case string:
buf.WriteString(value)
case error:
buf.WriteString(value.Error())
case fmt.Stringer:
buf.WriteString(value.String())
default:
_, _ = fmt.Fprintf(buf, "%v", value)
}
}
func formatLevel(level logsdk.Level) []byte {
switch level {
case logsdk.LevelPanic:
return []byte(LevelPanicValue)
case logsdk.LevelFatal:
return []byte(LevelFatalValue)
case logsdk.LevelError:
return []byte(LevelErrorValue)
case logsdk.LevelWarn:
return []byte(LevelWarnValue)
case logsdk.LevelInfo:
return []byte(LevelInfoValue)
case logsdk.LevelDebug:
return []byte(LevelDebugValue)
case logsdk.LevelTrace:
return []byte(LevelTraceValue)
case logsdk.LevelDisabled:
fallthrough
default:
return []byte(levelUnknownValue)
}
}
const (
LevelPanicValue = "PNC"
LevelFatalValue = "FTL"
LevelErrorValue = "ERR"
LevelWarnValue = "WRN"
LevelInfoValue = "INF"
LevelDebugValue = "DBG"
LevelTraceValue = "TRC"
levelUnknownValue = "UNK"
)
Loading…
Cancel
Save