commit f9ae79614ab55278e35246a551bdf6ed8fe4bf6d Author: andrey1s Date: Sun Sep 19 16:50:42 2021 +0300 restore v 0.2.0 diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..c020dfe --- /dev/null +++ b/.drone.yml @@ -0,0 +1,50 @@ +kind: pipeline +name: default + +steps: +- name: test + image: golang:1.14.2 + volumes: + - name: deps + path: /go/src/mod + commands: + - go test + +- name: golangci-lint + image: golangci/golangci-lint:v1.29 + commands: + - golangci-lint run + +- name: logrus golangci-lint + image: golangci/golangci-lint:v1.29 + commands: + - cd logrus + - golangci-lint run + +- name: logrus test + image: golang:1.14.2 + volumes: + - name: deps + path: /go/src/mod + commands: + - cd logrus + - go test + +- name: zap golangci-lint + image: golangci/golangci-lint:v1.29 + commands: + - cd zap + - golangci-lint run + +- name: zap test + image: golang:1.14.2 + volumes: + - name: deps + path: /go/src/mod + commands: + - cd zap + - go test + +volumes: +- name: deps + temp: {} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4d432a --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# ---> Go +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..496809d --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,33 @@ +linters-settings: + dupl: + threshold: 100 + funlen: + lines: 100 + statements: 50 + goconst: + min-len: 2 + min-occurrences: 2 + gocyclo: + min-complexity: 15 + golint: + min-confidence: 0 + govet: + check-shadowing: true + lll: + line-length: 140 + maligned: + suggest-new: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + +linters: + enable-all: true + +issues: + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + - path: _test\.go + linters: + - gomnd diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0dcfb43 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +MIT License Copyright (c) 2020 go-4devs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c27dec8 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# log + +[![Build Status](https://drone.gitoa.ru/api/badges/go-4devs/log/status.svg)](https://drone.gitoa.ru/go-4devs/log) +[![Go Report Card](https://goreportcard.com/badge/gitoa.ru/go-4devs/log)](https://goreportcard.com/report/gitoa.ru/go-4devs/log) +[![GoDoc](https://godoc.org/gitoa.ru/go-4devs/log?status.svg)](http://godoc.org/gitoa.ru/go-4devs/log) diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..18b5f5b --- /dev/null +++ b/doc.go @@ -0,0 +1,4 @@ +/* +Package log logged data by handler and use processor, exposes eight methods to write logs to the eight RFC 5424 levels. +*/ +package log diff --git a/field.go b/field.go new file mode 100644 index 0000000..977463b --- /dev/null +++ b/field.go @@ -0,0 +1,40 @@ +package log + +import ( + "fmt" + "strings" +) + +// Fields slice field. +type Fields []Field + +// String implement stringer. +func (f Fields) String() string { + str := make([]string, len(f)) + for i, field := range f { + str[i] = field.String() + } + + return strings.Join(str, " ") +} + +// NewField create field. +func NewField(key string, value interface{}) Field { + return Field{Key: key, Value: value} +} + +// Field struct. +type Field struct { + Key string + Value interface{} +} + +// String implent stringer. +func (f Field) String() string { + return fmt.Sprintf("%s=%+v", f.Key, f.Value) +} + +// FieldError new errors field with key error. +func FieldError(err error) Field { + return NewField("error", err) +} diff --git a/global.go b/global.go new file mode 100644 index 0000000..03f9a94 --- /dev/null +++ b/global.go @@ -0,0 +1,183 @@ +package log + +import ( + "context" +) + +//nolint:gochecknoglobals +var global = With(New(), WithLevel(LevelDebug)) + +// SetLogger sets global used logger. This function is not thread-safe. +func SetLogger(l Logger) { + global = l +} + +// GetLogger return global logger. +func GetLogger() Logger { + return global +} + +// Emerg log by emergency level. +func Emerg(ctx context.Context, args ...interface{}) { + global.Emerg(ctx, args...) +} + +// Alert log by alert level. +func Alert(ctx context.Context, args ...interface{}) { + global.Alert(ctx, args...) +} + +// Crit log by critical level. +func Crit(ctx context.Context, args ...interface{}) { + global.Crit(ctx, args...) +} + +// Err log by error level. +func Err(ctx context.Context, args ...interface{}) { + global.Err(ctx, args...) +} + +// Warn logs by warning level. +func Warn(ctx context.Context, args ...interface{}) { + global.Warn(ctx, args...) +} + +// Notice log by notice level. +func Notice(ctx context.Context, args ...interface{}) { + global.Notice(ctx, args...) +} + +// Info log by info level. +func Info(ctx context.Context, args ...interface{}) { + global.Info(ctx, args...) +} + +// Debug log by debug level. +func Debug(ctx context.Context, args ...interface{}) { + global.Debug(ctx, args...) +} + +// Print log by info level and arguments. +func Print(args ...interface{}) { + global.Print(args...) +} + +// Fatal log by alert level and arguments. +func Fatal(args ...interface{}) { + global.Fatal(args...) +} + +// Panic log by emergency level and arguments. +func Panic(args ...interface{}) { + global.Panic(args...) +} + +// Println log by info level and arguments. +func Println(args ...interface{}) { + global.Println(args...) +} + +// Fatalln log by alert level and arguments. +func Fatalln(args ...interface{}) { + global.Fatalln(args...) +} + +// Panicln log by emergency level and arguments. +func Panicln(args ...interface{}) { + global.Panicln(args...) +} + +// EmergKV log by emergency level and key-values. +func EmergKV(ctx context.Context, msg string, args ...interface{}) { + global.EmergKV(ctx, msg, args...) +} + +// AlertKV log by alert level and key-values. +func AlertKV(ctx context.Context, msg string, args ...interface{}) { + global.AlertKV(ctx, msg, args...) +} + +// CritKV log by critcal level and key-values. +func CritKV(ctx context.Context, msg string, args ...interface{}) { + global.CritKV(ctx, msg, args...) +} + +// ErrKV log by error level and key-values. +func ErrKV(ctx context.Context, msg string, args ...interface{}) { + global.ErrKV(ctx, msg, args...) +} + +// WarnKV log by warning level and key-values. +func WarnKV(ctx context.Context, msg string, args ...interface{}) { + global.WarnKV(ctx, msg, args...) +} + +// NoticeKV log by notice level and key-values. +func NoticeKV(ctx context.Context, msg string, args ...interface{}) { + global.NoticeKV(ctx, msg, args...) +} + +// InfoKV log by info level and key-values. +func InfoKV(ctx context.Context, msg string, args ...interface{}) { + global.InfoKV(ctx, msg, args...) +} + +// DebugKV log by debug level and key-values. +func DebugKV(ctx context.Context, msg string, args ...interface{}) { + global.DebugKV(ctx, msg, args...) +} + +// Emergf log by emergency level by format and arguments. +func Emergf(ctx context.Context, format string, args ...interface{}) { + global.Emergf(ctx, format, args...) +} + +// Alertf log by alert level by format and arguments. +func Alertf(ctx context.Context, format string, args ...interface{}) { + global.Alertf(ctx, format, args...) +} + +// Critf log by critical level by format and arguments. +func Critf(ctx context.Context, format string, args ...interface{}) { + global.Critf(ctx, format, args...) +} + +// Errf log by error level by format and arguments. +func Errf(ctx context.Context, format string, args ...interface{}) { + global.Errf(ctx, format, args...) +} + +// Warnf log by warning level by format and arguments. +func Warnf(ctx context.Context, format string, args ...interface{}) { + global.Warnf(ctx, format, args...) +} + +// Noticef log by notice level by format and arguments. +func Noticef(ctx context.Context, format string, args ...interface{}) { + global.Noticef(ctx, format, args...) +} + +// Infof log by info level by format and arguments. +func Infof(ctx context.Context, format string, args ...interface{}) { + global.Noticef(ctx, format, args...) +} + +// Debugf log by debug level by format and arguments. +func Debugf(ctx context.Context, format string, args ...interface{}) { + global.Debugf(ctx, format, args...) +} + +// Printf log by info level by format and arguments without context. +func Printf(format string, args ...interface{}) { + global.Printf(format, args...) +} + +// Fatalf log by alert level by format and arguments without context. +func Fatalf(format string, args ...interface{}) { + global.Fatalf(format, args...) +} + +// Panicf log by emergency level and arguments without context. +func Panicf(format string, args ...interface{}) { + global.Panicf(format, args...) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1ddc29f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitoa.ru/go-4devs/log + +go 1.14 diff --git a/level.go b/level.go new file mode 100644 index 0000000..de8c03e --- /dev/null +++ b/level.go @@ -0,0 +1,22 @@ +package log + +//go:generate stringer -type=Level -linecomment + +// Level log. +type Level uint8 + +// awailable log levels. +const ( + LevelEmergency Level = iota // emergency + LevelAlert // alert + LevelCritical // critical + LevelError // error + LevelWarning // warning + LevelNotice // notice + LevelInfo // info + LevelDebug // debug +) + +func (l Level) MarshalJSON() ([]byte, error) { + return []byte("\"" + l.String() + "\""), nil +} diff --git a/level_string.go b/level_string.go new file mode 100644 index 0000000..def99d1 --- /dev/null +++ b/level_string.go @@ -0,0 +1,30 @@ +// Code generated by "stringer -type=Level -linecomment"; DO NOT EDIT. + +package log + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[LevelEmergency-0] + _ = x[LevelAlert-1] + _ = x[LevelCritical-2] + _ = x[LevelError-3] + _ = x[LevelWarning-4] + _ = x[LevelNotice-5] + _ = x[LevelInfo-6] + _ = x[LevelDebug-7] +} + +const _Level_name = "emergencyalertcriticalerrorwarningnoticeinfodebug" + +var _Level_index = [...]uint8{0, 9, 14, 22, 27, 34, 40, 44, 49} + +func (i Level) String() string { + if i >= Level(len(_Level_index)-1) { + return "Level(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Level_name[_Level_index[i]:_Level_index[i+1]] +} diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..536f740 --- /dev/null +++ b/logger.go @@ -0,0 +1,223 @@ +package log + +import ( + "context" + "fmt" +) + +// Logger logged message. +type Logger func(ctx context.Context, level Level, msg string, fields Fields) + +func (l Logger) log(ctx context.Context, level Level, args ...interface{}) { + l(ctx, level, fmt.Sprint(args...), nil) +} + +func (l Logger) logKV(ctx context.Context, level Level, msg string, args ...interface{}) { + l(ctx, level, msg, l.kv(ctx, args...)) +} + +func (l Logger) logf(ctx context.Context, level Level, format string, args ...interface{}) { + l(ctx, level, fmt.Sprintf(format, args...), nil) +} + +func (l Logger) logln(ctx context.Context, level Level, args ...interface{}) { + l(ctx, level, fmt.Sprintln(args...), nil) +} + +func (l Logger) kv(ctx context.Context, args ...interface{}) []Field { + fields := make([]Field, 0, len(args)) + + for i := 0; i < len(args); i++ { + if f, ok := args[i].(Field); ok { + fields = append(fields, f) + continue + } + + if i == len(args)-1 { + l(ctx, LevelCritical, fmt.Sprint("Ignored key without a value.", args[i]), fields) + break + } + + i++ + + key, val := args[i-1], args[i] + if keyStr, ok := key.(string); ok { + fields = append(fields, Field{Key: keyStr, Value: val}) + continue + } + + l(ctx, LevelCritical, fmt.Sprint("Ignored key-value pairs with non-string keys.", key, val), fields) + } + + return fields +} + +// With adds middlewares to logger. +func (l Logger) With(mw ...Middleware) Logger { + return With(l, mw...) +} + +// Emerg log by emergency level. +func (l Logger) Emerg(ctx context.Context, args ...interface{}) { + l.log(ctx, LevelEmergency, args...) +} + +// Alert log by alert level. +func (l Logger) Alert(ctx context.Context, args ...interface{}) { + l.log(ctx, LevelAlert, args...) +} + +// Crit log by critical level. +func (l Logger) Crit(ctx context.Context, args ...interface{}) { + l.log(ctx, LevelCritical, args...) +} + +// Err log by error level. +func (l Logger) Err(ctx context.Context, args ...interface{}) { + l.log(ctx, LevelError, args...) +} + +// Warn log by warning level. +func (l Logger) Warn(ctx context.Context, args ...interface{}) { + l.log(ctx, LevelWarning, args...) +} + +// Notice log by notice level. +func (l Logger) Notice(ctx context.Context, args ...interface{}) { + l.log(ctx, LevelNotice, args...) +} + +// Info log by info level. +func (l Logger) Info(ctx context.Context, args ...interface{}) { + l.log(ctx, LevelInfo, args...) +} + +// Debug log by debug level. +func (l Logger) Debug(ctx context.Context, args ...interface{}) { + l.log(ctx, LevelDebug, args...) +} + +// Print log by info level and arguments. +func (l Logger) Print(args ...interface{}) { + l.log(context.Background(), LevelInfo, args...) +} + +// Fatal log by alert level and arguments. +func (l Logger) Fatal(args ...interface{}) { + l.log(context.Background(), LevelAlert, args...) +} + +// Panic log by emergency level and arguments. +func (l Logger) Panic(args ...interface{}) { + l.log(context.Background(), LevelEmergency, args...) +} + +// Println log by info level and arguments. +func (l Logger) Println(args ...interface{}) { + l.logln(context.Background(), LevelInfo, args...) +} + +// Fatalln log by alert level and arguments. +func (l Logger) Fatalln(args ...interface{}) { + l.logln(context.Background(), LevelAlert, args...) +} + +// Panicln log by emergency level and arguments. +func (l Logger) Panicln(args ...interface{}) { + l.logln(context.Background(), LevelEmergency, args...) +} + +// EmergKV log by emergency level and key-values. +func (l Logger) EmergKV(ctx context.Context, msg string, args ...interface{}) { + l.logKV(ctx, LevelEmergency, msg, args...) +} + +// AlertKV log by alert level and key-values. +func (l Logger) AlertKV(ctx context.Context, msg string, args ...interface{}) { + l.logKV(ctx, LevelAlert, msg, args...) +} + +// CritKV log by critcal level and key-values. +func (l Logger) CritKV(ctx context.Context, msg string, args ...interface{}) { + l.logKV(ctx, LevelCritical, msg, args...) +} + +// ErrKV log by error level and key-values. +func (l Logger) ErrKV(ctx context.Context, msg string, args ...interface{}) { + l.logKV(ctx, LevelError, msg, args...) +} + +// WarnKV log by warning level and key-values. +func (l Logger) WarnKV(ctx context.Context, msg string, args ...interface{}) { + l.logKV(ctx, LevelWarning, msg, args...) +} + +// NoticeKV log by notice level and key-values. +func (l Logger) NoticeKV(ctx context.Context, msg string, args ...interface{}) { + l.logKV(ctx, LevelNotice, msg, args...) +} + +// InfoKV log by info level and key-values. +func (l Logger) InfoKV(ctx context.Context, msg string, args ...interface{}) { + l.logKV(ctx, LevelInfo, msg, args...) +} + +// DebugKV log by debug level and key-values. +func (l Logger) DebugKV(ctx context.Context, msg string, args ...interface{}) { + l.logKV(ctx, LevelDebug, msg, args...) +} + +// Emergf log by emergency level by format and arguments. +func (l Logger) Emergf(ctx context.Context, format string, args ...interface{}) { + l.logf(ctx, LevelEmergency, format, args...) +} + +// Alertf log by alert level by format and arguments. +func (l Logger) Alertf(ctx context.Context, format string, args ...interface{}) { + l.logf(ctx, LevelAlert, format, args...) +} + +// Critf log by critical level by format and arguments. +func (l Logger) Critf(ctx context.Context, format string, args ...interface{}) { + l.logf(ctx, LevelCritical, format, args...) +} + +// Errf log by error level by format and arguments. +func (l Logger) Errf(ctx context.Context, format string, args ...interface{}) { + l.logf(ctx, LevelError, format, args...) +} + +// Warnf log by warning level by format and arguments. +func (l Logger) Warnf(ctx context.Context, format string, args ...interface{}) { + l.logf(ctx, LevelWarning, format, args...) +} + +// Noticef log by notice level by format and arguments. +func (l Logger) Noticef(ctx context.Context, format string, args ...interface{}) { + l.logf(ctx, LevelNotice, format, args...) +} + +// Infof log by info level by format and arguments. +func (l Logger) Infof(ctx context.Context, format string, args ...interface{}) { + l.logf(ctx, LevelInfo, format, args...) +} + +// Debugf log by debug level by format and arguments. +func (l Logger) Debugf(ctx context.Context, format string, args ...interface{}) { + l.logf(ctx, LevelDebug, format, args...) +} + +// Printf log by info level by format and arguments without context. +func (l Logger) Printf(format string, args ...interface{}) { + l.logf(context.Background(), LevelInfo, format, args...) +} + +// Fatalf log by alert level by format and arguments without context. +func (l Logger) Fatalf(format string, args ...interface{}) { + l.logf(context.Background(), LevelAlert, format, args...) +} + +// Panicf log by emergency level and arguments without context. +func (l Logger) Panicf(format string, args ...interface{}) { + l.logf(context.Background(), LevelEmergency, format, args...) +} diff --git a/logger_example_test.go b/logger_example_test.go new file mode 100644 index 0000000..8d810df --- /dev/null +++ b/logger_example_test.go @@ -0,0 +1,126 @@ +package log_test + +import ( + "context" + "fmt" + std "log" + "os" + + "gitoa.ru/go-4devs/log" +) + +//nolint:gochecknoglobals +var ctx = context.Background() + +func ExampleNew() { + logger := log.New(log.WithStdout()) + logger.Info(ctx, "same message") + // Output: msg="same message" +} + +func ExampleInfo() { + std.SetOutput(os.Stdout) + std.SetFlags(0) + log.Info(ctx, "same message") + // Output: msg="same message" level=info +} + +func ExampleErrKV() { + std.SetOutput(os.Stdout) + std.SetFlags(0) + log.ErrKV(ctx, "same message", "key", "addition value") + // Output: msg="same message" key=addition value level=error +} + +func ExampleNew_errf() { + logger := log.New(log.WithStdout()) + logger.Errf(ctx, "same message %d", 1) + // Output: msg="same message 1" +} + +func ExampleNew_debugKV() { + logger := log.New(log.WithStdout()).With(log.WithLevel(log.LevelDebug)) + logger.DebugKV(ctx, "same message", "error", os.ErrNotExist) + // Output: msg="same message" error=file does not exist level=debug +} + +func ExampleNew_level() { + logger := log.New(log.WithStdout()).With(log.WithLevel(log.LevelError)) + logger.Info(ctx, "same message") + // Output: + + logger.Err(ctx, "same error message") + // Output: msg="same error message" level=error +} + +func ExampleNew_jsonFormat() { + logger := log.New(log.WithStdout(), log.WithJSONFormat()). + With( + log.WithCaller(4, true), + log.WithLevel(log.LevelDebug), + log.GoVersion("go-version"), + ) + logger.Err(ctx, "same error message") + // Output: {"caller":"logger_example_test.go:63","go-version":"go1.14.2","level":"error","msg":"same error message"} +} + +func ExampleNew_withLogger() { + stdlogger := std.New(os.Stdout, "same prefix ", std.Lshortfile) + logger := log.With( + log.New( + log.WithLogger( + stdlogger, + func(msg string, fields log.Fields) string { + return fmt.Sprint("msg=\"", msg, "\" ", fields) + }, + ), + log.WithCalldepth(9), + ), + log.WithLevel(log.LevelDebug), + log.GoVersion("go-version"), + ) + logger.Err(ctx, "same error message") + logger.InfoKV(ctx, "same info message", "api-version", 0.1) + + // Output: + // same prefix logger_example_test.go:82: msg="same error message" level=error go-version=go1.14.2 + // same prefix logger_example_test.go:83: msg="same info message" api-version=0.1 level=info go-version=go1.14.2 +} + +type ctxKey string + +func (c ctxKey) String() string { + return string(c) +} + +func levelInfo(ctx context.Context, level log.Level, msg string, fields log.Fields, handler log.Logger) { + handler(ctx, level, msg, append(fields, log.Field{Key: "level", Value: level})) +} + +func ExampleWith() { + var requestID ctxKey = "requestID" + vctx := context.WithValue(ctx, requestID, "6a5fa048-7181-11ea-bc55-0242ac130003") + + logger := log.With( + log.New(log.WithStdout()), + levelInfo, log.WithContextValue(requestID), log.KeyValue("api", "0.1.0"), log.GoVersion("go"), + ) + logger.Info(vctx, "same message") + // Output: msg="same message" level=info requestID=6a5fa048-7181-11ea-bc55-0242ac130003 api=0.1.0 go=go1.14.2 +} + +func ExampleLogger_Print() { + logger := log.With( + log.New(log.WithStdout()), + levelInfo, log.KeyValue("client", "http"), log.KeyValue("api", "0.1.0"), log.GoVersion("go"), + ) + logger.Print("same message") + // Output: msg="same message" level=info client=http api=0.1.0 go=go1.14.2 +} + +func ExamplePrint() { + std.SetOutput(os.Stdout) + std.SetFlags(0) + log.Print("same message") + // Output: msg="same message" level=info +} diff --git a/logger_test.go b/logger_test.go new file mode 100644 index 0000000..af48b2e --- /dev/null +++ b/logger_test.go @@ -0,0 +1,31 @@ +package log_test + +import ( + "bytes" + "context" + "os" + "testing" + + "gitoa.ru/go-4devs/log" +) + +func TestFields(t *testing.T) { + type rObj struct { + id string + } + + ctx := context.Background() + buf := &bytes.Buffer{} + log := log.New(log.WithWriter(buf)) + success := "msg=\"message\" err=file already exists version=0.1.0 obj={id:uid}\n" + + log.InfoKV(ctx, "message", + "err", os.ErrExist, + "version", "0.1.0", + "obj", rObj{id: "uid"}, + ) + + if success != buf.String() { + t.Errorf("invalid value\n got:%s\n exp:%s", buf, success) + } +} diff --git a/middleware.go b/middleware.go new file mode 100644 index 0000000..5103502 --- /dev/null +++ b/middleware.go @@ -0,0 +1,111 @@ +package log + +import ( + "context" + "fmt" + "path/filepath" + "runtime" + "time" +) + +// Middleware handle. +type Middleware func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) + +// With add middleware to logger. +func With(logger Logger, mw ...Middleware) Logger { + switch len(mw) { + case 0: + return logger + case 1: + return func(ctx context.Context, level Level, msg string, fields Fields) { + mw[0](ctx, level, msg, fields, logger) + } + } + + lastI := len(mw) - 1 + + return func(ctx context.Context, level Level, msg string, fields Fields) { + var ( + chainHandler func(ctx context.Context, level Level, msg string, fields Fields) + curI int + ) + + chainHandler = func(currentCtx context.Context, currentLevel Level, currentMsg string, currentFields Fields) { + if curI == lastI { + logger(currentCtx, currentLevel, currentMsg, currentFields) + return + } + curI++ + mw[curI](currentCtx, currentLevel, currentMsg, currentFields, chainHandler) + curI-- + } + + mw[0](ctx, level, msg, fields, chainHandler) + } +} + +// WithLevel sets log level. +func WithLevel(lvl Level) Middleware { + return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { + if level <= lvl { + handler(ctx, level, msg, append(fields, Field{Key: "level", Value: level})) + } + } +} + +// KeyValue add field by const key value. +func KeyValue(key string, value interface{}) Middleware { + return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { + handler(ctx, level, msg, append(fields, Field{Key: key, Value: value})) + } +} + +// GoVersion add field by go version. +func GoVersion(key string) Middleware { + return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { + handler(ctx, level, msg, append(fields, Field{Key: key, Value: runtime.Version()})) + } +} + +// WithContext add field by context key. +func WithContextValue(keys ...fmt.Stringer) Middleware { + return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { + ctxFields := make(Fields, len(keys)) + for i, key := range keys { + ctxFields[i] = Field{Key: key.String(), Value: ctx.Value(key)} + } + + handler(ctx, level, msg, append(fields, ctxFields...)) + } +} + +// WithCaller adds called file. +func WithCaller(calldepth int, short bool) Middleware { + return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { + _, file, line, ok := runtime.Caller(calldepth) + if !ok { + file, line = "???", 0 + } + + if short && ok { + file = filepath.Base(file) + } + + handler(ctx, level, msg, append(fields, NewField("caller", fmt.Sprint(file, ":", line)))) + } +} + +// WithTime adds time. +func WithTime(format string) Middleware { + return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { + handler(ctx, level, msg, append(fields, NewField("time", time.Now().Format(format)))) + } +} + +// WithMetrics adds handle metrics. +func WithMetrics(metrics func(level Level)) Middleware { + return func(ctx context.Context, level Level, msg string, fields Fields, handler Logger) { + go metrics(level) + handler(ctx, level, msg, fields) + } +} diff --git a/std.go b/std.go new file mode 100644 index 0000000..dc102c9 --- /dev/null +++ b/std.go @@ -0,0 +1,121 @@ +package log + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "os" +) + +const ( + calldepth = 3 +) + +// New creates standart logger. +func New(opts ...Option) Logger { + logger := logger{ + format: stringFormat, + output: log.Output, + calldepth: calldepth, + } + + for _, opt := range opts { + opt(&logger) + } + + return func(ctx context.Context, level Level, msg string, fields Fields) { + _ = logger.output(logger.calldepth, logger.format(msg, fields)) + + switch level { + case LevelEmergency: + panic(msg) + case LevelAlert: + os.Exit(1) + default: + } + } +} + +// Option configure log. +type Option func(*logger) + +// Format sets formats output message. +type Format func(msg string, fields Fields) string + +type logger struct { + output func(calldepth int, s string) error + format Format + calldepth int +} + +// WithWriter sets writer logger. +func WithWriter(writer io.Writer) Option { + return func(l *logger) { + l.output = log.New(writer, "", 0).Output + } +} + +// WithStdout sets logged to os.Stdout. +func WithStdout() Option { + return func(l *logger) { + l.output = log.New(os.Stdout, "", 0).Output + } +} + +// WithFormat sets format log. +func WithFormat(format Format) Option { + return func(l *logger) { + l.format = format + } +} + +// WithStringFormat sets format as simple string. +func WithStringFormat() Option { + return func(l *logger) { + l.format = stringFormat + } +} + +// WithJSONFormat sets json output format. +func WithJSONFormat() Option { + return func(l *logger) { + l.format = jsonFormat + } +} + +// WithCalldepth sets depth filename. +func WithCalldepth(calldepth int) Option { + return func(l *logger) { + l.calldepth = calldepth + } +} + +// WithLogger sets logger anf format. +func WithLogger(std *log.Logger, format Format) Option { + return func(l *logger) { + l.output = std.Output + l.format = format + } +} + +func stringFormat(msg string, fields Fields) string { + return fmt.Sprint("msg=\"", msg, "\" ", fields) +} + +func jsonFormat(msg string, fields Fields) string { + data := make(map[string]interface{}, len(fields)+1) + data["msg"] = msg + + for _, field := range fields { + data[field.Key] = field.Value + } + + res, err := json.Marshal(data) + if err != nil { + return stringFormat(msg, append(fields, FieldError(err))) + } + + return string(res) +}