From f9ae79614ab55278e35246a551bdf6ed8fe4bf6d Mon Sep 17 00:00:00 2001 From: andrey1s Date: Sun, 19 Sep 2021 16:50:42 +0300 Subject: [PATCH] restore v 0.2.0 --- .drone.yml | 50 +++++++++ .gitignore | 17 ++++ .golangci.yml | 33 ++++++ LICENSE | 19 ++++ README.md | 5 + doc.go | 4 + field.go | 40 ++++++++ global.go | 183 +++++++++++++++++++++++++++++++++ go.mod | 3 + level.go | 22 ++++ level_string.go | 30 ++++++ logger.go | 223 +++++++++++++++++++++++++++++++++++++++++ logger_example_test.go | 126 +++++++++++++++++++++++ logger_test.go | 31 ++++++ middleware.go | 111 ++++++++++++++++++++ std.go | 121 ++++++++++++++++++++++ 16 files changed, 1018 insertions(+) create mode 100644 .drone.yml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 doc.go create mode 100644 field.go create mode 100644 global.go create mode 100644 go.mod create mode 100644 level.go create mode 100644 level_string.go create mode 100644 logger.go create mode 100644 logger_example_test.go create mode 100644 logger_test.go create mode 100644 middleware.go create mode 100644 std.go 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) +}