Refactor log

This commit is contained in:
世界 2022-07-12 15:17:29 +08:00
parent b47f3adbb3
commit 4fc763cfa2
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
46 changed files with 760 additions and 457 deletions

119
log/default.go Normal file
View file

@ -0,0 +1,119 @@
package log
import (
"context"
"io"
"os"
"time"
F "github.com/sagernet/sing/common/format"
)
var _ Factory = (*simpleFactory)(nil)
type simpleFactory struct {
formatter Formatter
writer io.Writer
level Level
}
func NewFactory(formatter Formatter, writer io.Writer) Factory {
return &simpleFactory{
formatter: formatter,
writer: writer,
level: LevelTrace,
}
}
func (f *simpleFactory) Level() Level {
return f.level
}
func (f *simpleFactory) SetLevel(level Level) {
f.level = level
}
func (f *simpleFactory) Logger() ContextLogger {
return f.NewLogger("")
}
func (f *simpleFactory) NewLogger(tag string) ContextLogger {
return &simpleLogger{f, tag}
}
var _ ContextLogger = (*simpleLogger)(nil)
type simpleLogger struct {
*simpleFactory
tag string
}
func (l *simpleLogger) Log(ctx context.Context, level Level, args []any) {
if level > l.level {
return
}
message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), time.Now()) + "\n"
if level == LevelPanic {
panic(message)
}
l.writer.Write([]byte(message))
if level == LevelFatal {
os.Exit(1)
}
}
func (l *simpleLogger) Trace(args ...any) {
l.Log(nil, LevelTrace, args)
}
func (l *simpleLogger) Debug(args ...any) {
l.Log(nil, LevelDebug, args)
}
func (l *simpleLogger) Info(args ...any) {
l.Log(nil, LevelInfo, args)
}
func (l *simpleLogger) Warn(args ...any) {
l.Log(nil, LevelWarn, args)
}
func (l *simpleLogger) Error(args ...any) {
l.Log(nil, LevelError, args)
}
func (l *simpleLogger) Fatal(args ...any) {
l.Log(nil, LevelFatal, args)
}
func (l *simpleLogger) Panic(args ...any) {
l.Log(nil, LevelPanic, args)
}
func (l *simpleLogger) TraceContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelTrace, args)
}
func (l *simpleLogger) DebugContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelDebug, args)
}
func (l *simpleLogger) InfoContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelInfo, args)
}
func (l *simpleLogger) WarnContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelWarn, args)
}
func (l *simpleLogger) ErrorContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelError, args)
}
func (l *simpleLogger) FatalContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelFatal, args)
}
func (l *simpleLogger) PanicContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelPanic, args)
}

69
log/export.go Normal file
View file

@ -0,0 +1,69 @@
package log
import (
"context"
"os"
"time"
)
var std ContextLogger
func init() {
std = NewFactory(Formatter{BaseTime: time.Now()}, os.Stderr).Logger()
}
func Trace(args ...any) {
std.Trace(args...)
}
func Debug(args ...any) {
std.Debug(args...)
}
func Info(args ...any) {
std.Info(args...)
}
func Warn(args ...any) {
std.Warn(args...)
}
func Error(args ...any) {
std.Error(args...)
}
func Fatal(args ...any) {
std.Fatal(args...)
}
func Panic(args ...any) {
std.Panic(args...)
}
func TraceContext(ctx context.Context, args ...any) {
std.TraceContext(ctx, args...)
}
func DebugContext(ctx context.Context, args ...any) {
std.DebugContext(ctx, args...)
}
func InfoContext(ctx context.Context, args ...any) {
std.InfoContext(ctx, args...)
}
func WarnContext(ctx context.Context, args ...any) {
std.WarnContext(ctx, args...)
}
func ErrorContext(ctx context.Context, args ...any) {
std.ErrorContext(ctx, args...)
}
func FatalContext(ctx context.Context, args ...any) {
std.FatalContext(ctx, args...)
}
func PanicContext(ctx context.Context, args ...any) {
std.PanicContext(ctx, args...)
}

45
log/factory.go Normal file
View file

@ -0,0 +1,45 @@
package log
import (
"context"
"github.com/sagernet/sing/common/observable"
)
type Factory interface {
Level() Level
SetLevel(level Level)
Logger() ContextLogger
NewLogger(tag string) ContextLogger
}
type ObservableFactory interface {
Factory
observable.Observable[Entry]
}
type Entry struct {
Level Level
Message string
}
type Logger interface {
Trace(args ...any)
Debug(args ...any)
Info(args ...any)
Warn(args ...any)
Error(args ...any)
Fatal(args ...any)
Panic(args ...any)
}
type ContextLogger interface {
Logger
TraceContext(ctx context.Context, args ...any)
DebugContext(ctx context.Context, args ...any)
InfoContext(ctx context.Context, args ...any)
WarnContext(ctx context.Context, args ...any)
ErrorContext(ctx context.Context, args ...any)
FatalContext(ctx context.Context, args ...any)
PanicContext(ctx context.Context, args ...any)
}

83
log/format.go Normal file
View file

@ -0,0 +1,83 @@
package log
import (
"context"
"strconv"
"strings"
"time"
F "github.com/sagernet/sing/common/format"
"github.com/logrusorgru/aurora"
)
type Formatter struct {
BaseTime time.Time
DisableColors bool
DisableTimestamp bool
FullTimestamp bool
TimestampFormat string
}
func (f Formatter) Format(ctx context.Context, level Level, tag string, message string, timestamp time.Time) string {
levelString := strings.ToUpper(FormatLevel(level))
if !f.DisableColors {
switch level {
case LevelDebug, LevelTrace:
levelString = aurora.White(levelString).String()
case LevelInfo:
levelString = aurora.Cyan(levelString).String()
case LevelWarn:
levelString = aurora.Yellow(levelString).String()
case LevelError, LevelFatal, LevelPanic:
levelString = aurora.Red(levelString).String()
}
}
if tag != "" {
message = tag + ": " + message
}
var id uint32
var hasId bool
if ctx != nil {
id, hasId = IDFromContext(ctx)
}
if hasId {
var color aurora.Color
color = aurora.Color(uint8(id))
color %= 215
row := uint(color / 36)
column := uint(color % 36)
var r, g, b float32
r = float32(row * 51)
g = float32(column / 6 * 51)
b = float32((column % 6) * 51)
luma := 0.2126*r + 0.7152*g + 0.0722*b
if luma < 60 {
row = 5 - row
column = 35 - column
color = aurora.Color(row*36 + column)
}
color += 16
color = color << 16
color |= 1 << 14
message = F.ToString("[", aurora.Colorize(id, color).String(), "] ", message)
}
switch {
case f.DisableTimestamp:
message = levelString + " " + message
case f.FullTimestamp:
message = F.ToString(int(timestamp.Sub(f.BaseTime)/time.Second)) + " " + levelString + " " + message
default:
message = levelString + "[" + xd(int(timestamp.Sub(f.BaseTime)/time.Second), 4) + "] " + message
}
return message
}
func xd(value int, x int) string {
message := strconv.Itoa(value)
for len(message) < x {
message = "0" + message
}
return message
}

View file

@ -11,23 +11,13 @@ func init() {
random.InitializeSeed()
}
var idType = (*idContext)(nil)
type idKey struct{}
type idContext struct {
context.Context
id uint32
func ContextWithNewID(ctx context.Context) context.Context {
return context.WithValue(ctx, (*idKey)(nil), rand.Uint32())
}
func (c *idContext) Value(key any) any {
if key == idType {
return c
}
return c.Context.Value(key)
}
func ContextWithID(ctx context.Context) context.Context {
if ctx.Value(idType) != nil {
return ctx
}
return &idContext{ctx, rand.Uint32()}
func IDFromContext(ctx context.Context) (uint32, bool) {
id, loaded := ctx.Value((*idKey)(nil)).(uint32)
return id, loaded
}

59
log/level.go Normal file
View file

@ -0,0 +1,59 @@
package log
import (
E "github.com/sagernet/sing/common/exceptions"
)
type Level = uint8
const (
LevelPanic Level = iota
LevelFatal
LevelError
LevelWarn
LevelInfo
LevelDebug
LevelTrace
)
func FormatLevel(level Level) string {
switch level {
case LevelTrace:
return "trace"
case LevelDebug:
return "debug"
case LevelInfo:
return "info"
case LevelWarn:
return "warn"
case LevelError:
return "error"
case LevelFatal:
return "fatal"
case LevelPanic:
return "panic"
default:
return "unknown"
}
}
func ParseLevel(level string) (Level, error) {
switch level {
case "trace":
return LevelTrace, nil
case "debug":
return LevelDebug, nil
case "info":
return LevelInfo, nil
case "warn", "warning":
return LevelWarn, nil
case "error":
return LevelError, nil
case "fatal":
return LevelFatal, nil
case "panic":
return LevelPanic, nil
default:
return LevelTrace, E.New("unknown log level: ", level)
}
}

View file

@ -1,30 +0,0 @@
package log
import (
"context"
"github.com/sagernet/sing-box/option"
)
type Logger interface {
Start() error
Close() error
Trace(args ...interface{})
Debug(args ...interface{})
Info(args ...interface{})
Print(args ...interface{})
Warn(args ...interface{})
Warning(args ...interface{})
Error(args ...interface{})
Fatal(args ...interface{})
Panic(args ...interface{})
WithContext(ctx context.Context) Logger
WithPrefix(prefix string) Logger
}
func NewLogger(options option.LogOption) (Logger, error) {
if options.Disabled {
return NewNopLogger(), nil
}
return NewLogrusLogger(options)
}

View file

@ -1,74 +0,0 @@
package log
import (
"context"
"os"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sirupsen/logrus"
)
var _ Logger = (*logrusLogger)(nil)
type logrusLogger struct {
abstractLogrusLogger
outputPath string
output *os.File
}
type abstractLogrusLogger interface {
logrus.Ext1FieldLogger
WithContext(ctx context.Context) *logrus.Entry
}
func NewLogrusLogger(options option.LogOption) (*logrusLogger, error) {
logger := logrus.New()
logger.SetLevel(logrus.TraceLevel)
logger.SetFormatter(&LogrusTextFormatter{
DisableColors: options.DisableColor || options.Output != "",
DisableTimestamp: !options.Timestamp && options.Output != "",
FullTimestamp: options.Timestamp,
})
logger.AddHook(new(logrusHook))
var err error
if options.Level != "" {
logger.Level, err = logrus.ParseLevel(options.Level)
if err != nil {
return nil, err
}
}
return &logrusLogger{logger, options.Output, nil}, nil
}
func (l *logrusLogger) Start() error {
if l.outputPath != "" {
output, err := os.OpenFile(l.outputPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return E.Cause(err, "open log output")
}
l.abstractLogrusLogger.(*logrus.Logger).SetOutput(output)
}
return nil
}
func (l *logrusLogger) Close() error {
return common.Close(common.PtrOrNil(l.output))
}
func (l *logrusLogger) WithContext(ctx context.Context) Logger {
return &logrusLogger{abstractLogrusLogger: l.abstractLogrusLogger.WithContext(ctx)}
}
func (l *logrusLogger) WithPrefix(prefix string) Logger {
if entry, isEntry := l.abstractLogrusLogger.(*logrus.Entry); isEntry {
loadedPrefix := entry.Data["prefix"]
if loadedPrefix != "" {
prefix = F.ToString(loadedPrefix, prefix)
}
}
return &logrusLogger{abstractLogrusLogger: l.WithField("prefix", prefix)}
}

View file

@ -1,49 +0,0 @@
package log
import (
F "github.com/sagernet/sing/common/format"
"github.com/logrusorgru/aurora"
"github.com/sirupsen/logrus"
)
type logrusHook struct{}
func (h *logrusHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (h *logrusHook) Fire(entry *logrus.Entry) error {
if prefix, loaded := entry.Data["prefix"]; loaded {
prefixStr := prefix.(string)
delete(entry.Data, "prefix")
entry.Message = prefixStr + entry.Message
}
var idCtx *idContext
if entry.Context != nil {
idCtx, _ = entry.Context.Value(idType).(*idContext)
}
if idCtx != nil {
var color aurora.Color
color = aurora.Color(uint8(idCtx.id))
color %= 215
row := uint(color / 36)
column := uint(color % 36)
var r, g, b float32
r = float32(row * 51)
g = float32(column / 6 * 51)
b = float32((column % 6) * 51)
luma := 0.2126*r + 0.7152*g + 0.0722*b
if luma < 60 {
row = 5 - row
column = 35 - column
color = aurora.Color(row*36 + column)
}
color += 16
color = color << 16
color |= 1 << 14
entry.Message = F.ToString("[", aurora.Colorize(idCtx.id, color).String(), "] ", entry.Message)
}
return nil
}

View file

@ -1,83 +0,0 @@
package log
import (
"bytes"
"fmt"
"strings"
"time"
"github.com/sirupsen/logrus"
)
const (
red = 31
yellow = 33
blue = 36
gray = 37
)
var baseTimestamp time.Time
func init() {
baseTimestamp = time.Now()
}
type LogrusTextFormatter struct {
DisableColors bool
DisableTimestamp bool
FullTimestamp bool
TimestampFormat string
}
func (f *LogrusTextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = &bytes.Buffer{}
}
timestampFormat := f.TimestampFormat
if timestampFormat == "" {
timestampFormat = "-0700 2006-01-02 15:04:05"
}
f.print(b, entry, timestampFormat)
b.WriteByte('\n')
return b.Bytes(), nil
}
func (f *LogrusTextFormatter) print(b *bytes.Buffer, entry *logrus.Entry, timestampFormat string) {
var levelColor int
switch entry.Level {
case logrus.DebugLevel, logrus.TraceLevel:
levelColor = gray
case logrus.WarnLevel:
levelColor = yellow
case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
levelColor = red
case logrus.InfoLevel:
levelColor = blue
default:
levelColor = blue
}
levelText := strings.ToUpper(entry.Level.String())
if !f.DisableColors {
switch {
case f.DisableTimestamp:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s", levelColor, levelText, entry.Message)
case !f.FullTimestamp:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
default:
fmt.Fprintf(b, "%s \x1b[%dm%s\x1b[0m %-44s", entry.Time.Format(timestampFormat), levelColor, levelText, entry.Message)
}
} else {
switch {
case f.DisableTimestamp:
fmt.Fprintf(b, "%s %-44s", levelText, entry.Message)
case !f.FullTimestamp:
fmt.Fprintf(b, "%s[%04d] %-44s", levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
default:
fmt.Fprintf(b, "[%s] %s %-44s", entry.Time.Format(timestampFormat), levelText, entry.Message)
}
}
}

View file

@ -2,53 +2,67 @@ package log
import "context"
var _ Logger = (*nopLogger)(nil)
var _ Factory = (*nopFactory)(nil)
type nopLogger struct{}
type nopFactory struct{}
func NewNopLogger() Logger {
return (*nopLogger)(nil)
func NewNOPFactory() Factory {
return (*nopFactory)(nil)
}
func (l *nopLogger) Start() error {
return nil
func (f *nopFactory) Level() Level {
return LevelTrace
}
func (l *nopLogger) Close() error {
return nil
func (f *nopFactory) SetLevel(level Level) {
}
func (l *nopLogger) Trace(args ...interface{}) {
func (f *nopFactory) Logger() ContextLogger {
return f
}
func (l *nopLogger) Debug(args ...interface{}) {
func (f *nopFactory) NewLogger(tag string) ContextLogger {
return f
}
func (l *nopLogger) Info(args ...interface{}) {
func (f *nopFactory) Trace(args ...any) {
}
func (l *nopLogger) Print(args ...interface{}) {
func (f *nopFactory) Debug(args ...any) {
}
func (l *nopLogger) Warn(args ...interface{}) {
func (f *nopFactory) Info(args ...any) {
}
func (l *nopLogger) Warning(args ...interface{}) {
func (f *nopFactory) Warn(args ...any) {
}
func (l *nopLogger) Error(args ...interface{}) {
func (f *nopFactory) Error(args ...any) {
}
func (l *nopLogger) Fatal(args ...interface{}) {
func (f *nopFactory) Fatal(args ...any) {
}
func (l *nopLogger) Panic(args ...interface{}) {
func (f *nopFactory) Panic(args ...any) {
}
func (l *nopLogger) WithContext(ctx context.Context) Logger {
return l
func (f *nopFactory) TraceContext(ctx context.Context, args ...any) {
}
func (l *nopLogger) WithPrefix(prefix string) Logger {
return l
func (f *nopFactory) DebugContext(ctx context.Context, args ...any) {
}
func (f *nopFactory) InfoContext(ctx context.Context, args ...any) {
}
func (f *nopFactory) WarnContext(ctx context.Context, args ...any) {
}
func (f *nopFactory) ErrorContext(ctx context.Context, args ...any) {
}
func (f *nopFactory) FatalContext(ctx context.Context, args ...any) {
}
func (f *nopFactory) PanicContext(ctx context.Context, args ...any) {
}

136
log/observable.go Normal file
View file

@ -0,0 +1,136 @@
package log
import (
"context"
"io"
"os"
"time"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/observable"
)
var _ Factory = (*observableFactory)(nil)
type observableFactory struct {
formatter Formatter
writer io.Writer
level Level
subscriber *observable.Subscriber[Entry]
observer *observable.Observer[Entry]
}
func NewObservableFactory(formatter Formatter, writer io.Writer) ObservableFactory {
factory := &observableFactory{
formatter: formatter,
writer: writer,
level: LevelTrace,
subscriber: observable.NewSubscriber[Entry](128),
}
factory.observer = observable.NewObserver[Entry](factory.subscriber, 64)
return factory
}
func (f *observableFactory) Level() Level {
return f.level
}
func (f *observableFactory) SetLevel(level Level) {
f.level = level
}
func (f *observableFactory) Logger() ContextLogger {
return f.NewLogger("")
}
func (f *observableFactory) NewLogger(tag string) ContextLogger {
return &observableLogger{f, tag}
}
func (f *observableFactory) Subscribe() (subscription observable.Subscription[Entry], done <-chan struct{}, err error) {
return f.observer.Subscribe()
}
func (f *observableFactory) UnSubscribe(sub observable.Subscription[Entry]) {
f.observer.UnSubscribe(sub)
}
var _ ContextLogger = (*observableLogger)(nil)
type observableLogger struct {
*observableFactory
tag string
}
func (l *observableLogger) Log(ctx context.Context, level Level, args []any) {
if level > l.level {
return
}
message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), time.Now()) + "\n"
if level == LevelPanic {
panic(message)
}
l.writer.Write([]byte(message))
if level == LevelFatal {
os.Exit(1)
}
if l.subscriber != nil {
l.subscriber.Emit(Entry{level, message})
}
}
func (l *observableLogger) Trace(args ...any) {
l.Log(nil, LevelTrace, args)
}
func (l *observableLogger) Debug(args ...any) {
l.Log(nil, LevelDebug, args)
}
func (l *observableLogger) Info(args ...any) {
l.Log(nil, LevelInfo, args)
}
func (l *observableLogger) Warn(args ...any) {
l.Log(nil, LevelWarn, args)
}
func (l *observableLogger) Error(args ...any) {
l.Log(nil, LevelError, args)
}
func (l *observableLogger) Fatal(args ...any) {
l.Log(nil, LevelFatal, args)
}
func (l *observableLogger) Panic(args ...any) {
l.Log(nil, LevelPanic, args)
}
func (l *observableLogger) TraceContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelTrace, args)
}
func (l *observableLogger) DebugContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelDebug, args)
}
func (l *observableLogger) InfoContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelInfo, args)
}
func (l *observableLogger) WarnContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelWarn, args)
}
func (l *observableLogger) ErrorContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelError, args)
}
func (l *observableLogger) FatalContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelFatal, args)
}
func (l *observableLogger) PanicContext(ctx context.Context, args ...any) {
l.Log(ctx, LevelPanic, args)
}