├── go.mod ├── trace ├── util.go ├── trace.go └── trace_test.go ├── tracer.go ├── .gitignore ├── tracer_test.go ├── buffer_pool_bench_test.go ├── uuid ├── v2 │ └── v2.go ├── uuid_test.go ├── v1 │ ├── timestamp.go │ ├── internal.go │ ├── v1.go │ └── v1x.go ├── variant.go ├── rand │ ├── int.go │ ├── mac.go │ ├── rand.go │ └── read.go ├── helper_test.go ├── helper.go └── uuid.go ├── concurrent_writer_test.go ├── time_format_bench_test.go ├── concurrent_writer.go ├── helper_test.go ├── location.go ├── json_formatter.go ├── LICENSE ├── helper.go ├── location_test.go ├── buffer_pool.go ├── time_format.go ├── logger_test.go ├── README.md ├── fields.go ├── noop_logger.go ├── level.go ├── json_formatter_test.go ├── text_formatter_test.go ├── time_format_test.go ├── context.go ├── std.go ├── options.go ├── text_formatter.go ├── shortcut.go ├── buffer_pool_test.go ├── fields_test.go ├── level_test.go ├── logger.go ├── options_test.go ├── context_test.go └── shortcut_without_logger_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/KeKe-Li/log 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /trace/util.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import "github.com/KeKe-Li/log/uuid" 4 | 5 | func NewTraceId() string { 6 | return string(uuid.NewV1().HexEncode()) 7 | } 8 | -------------------------------------------------------------------------------- /tracer.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "github.com/KeKe-Li/log/trace" 4 | 5 | var _ trace.Tracer = (*logger)(nil) 6 | 7 | func (l *logger) TraceId() string { 8 | return l.getOptions().traceId 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | .idea 14 | -------------------------------------------------------------------------------- /tracer_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/KeKe-Li/log/trace" 7 | ) 8 | 9 | func TestIsTracer(t *testing.T) { 10 | lg := New() 11 | _, ok := lg.(trace.Tracer) 12 | if !ok { 13 | t.Error("want true") 14 | return 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /buffer_pool_bench_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "testing" 4 | 5 | func BenchmarkGetBytesBufferPool(b *testing.B) { 6 | b.ReportAllocs() 7 | b.SetParallelism(64) 8 | b.RunParallel(func(pb *testing.PB) { 9 | for pb.Next() { 10 | getBytesBufferPool() 11 | } 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /uuid/v2/v2.go: -------------------------------------------------------------------------------- 1 | package v2 2 | 3 | import ( 4 | "crypto/sha1" 5 | ) 6 | 7 | func New(ns [16]byte, name []byte) (uuid [16]byte) { 8 | h := sha1.New() 9 | h.Write(ns[:]) 10 | h.Write(name) 11 | copy(uuid[:], h.Sum(nil)) 12 | uuid[8] = (uuid[8] & 0x3f) | 0x80 // set variant rfc4122 13 | uuid[6] = (uuid[6] & 0x0f) | 5<<4 // set version 5 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /uuid/uuid_test.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestVariantVersion(t *testing.T) { 8 | u1 := NewV1() 9 | u5 := NewV5(u1, []byte("test")) 10 | if u1.Variant() != VariantRFC4122 || u5.Variant() != VariantRFC4122 { 11 | t.Error("Variant is incorrect") 12 | return 13 | } 14 | if u1.Version() != 1 || u5.Version() != 5 { 15 | t.Error("Version is incorrect") 16 | return 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /concurrent_writer_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestConcurrentWriter(t *testing.T) { 9 | // nil 10 | { 11 | w := ConcurrentWriter(nil) 12 | if w != nil { 13 | t.Error("want nil") 14 | return 15 | } 16 | } 17 | // non-nil 18 | { 19 | w := bytes.NewBuffer(make([]byte, 0, 64)) 20 | 21 | cw := ConcurrentWriter(w) 22 | cw.Write([]byte("123456789")) 23 | 24 | have := w.String() 25 | want := "123456789" 26 | if have != want { 27 | t.Errorf("have:%s, want:%s", have, want) 28 | return 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /time_format_bench_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func BenchmarkFormatTime(b *testing.B) { 9 | now := time.Now() 10 | 11 | b.ReportAllocs() 12 | b.ResetTimer() 13 | for i := 0; i < b.N; i++ { 14 | FormatTime(now) 15 | } 16 | } 17 | 18 | func BenchmarkFormatTimeString(b *testing.B) { 19 | now := time.Now() 20 | 21 | b.ReportAllocs() 22 | b.ResetTimer() 23 | for i := 0; i < b.N; i++ { 24 | FormatTimeString(now) 25 | } 26 | } 27 | 28 | func BenchmarkStdTimeFormat(b *testing.B) { 29 | now := time.Now() 30 | 31 | b.ReportAllocs() 32 | b.ResetTimer() 33 | for i := 0; i < b.N; i++ { 34 | now.Format(TimeFormatLayout) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /concurrent_writer.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | ConcurrentStdout io.Writer = ConcurrentWriter(os.Stdout) 11 | ConcurrentStderr io.Writer = ConcurrentWriter(os.Stderr) 12 | ) 13 | 14 | // ConcurrentWriter wraps an io.Writer and returns a concurrent io.Writer. 15 | func ConcurrentWriter(w io.Writer) io.Writer { 16 | if w == nil { 17 | return nil 18 | } 19 | return &concurrentWriter{ 20 | w: w, 21 | } 22 | } 23 | 24 | type concurrentWriter struct { 25 | mu sync.Mutex 26 | w io.Writer 27 | } 28 | 29 | func (w *concurrentWriter) Write(p []byte) (n int, err error) { 30 | w.mu.Lock() 31 | defer w.mu.Unlock() 32 | return w.w.Write(p) 33 | } 34 | -------------------------------------------------------------------------------- /uuid/v1/timestamp.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // The number of 100-nanoseconds from "1582-10-15 00:00:00 +0000 UTC" to "1970-01-01 00:00:00 +0000 UTC". 8 | const unixToUUID = 122192928000000000 9 | 10 | // uuidTimestamp returns the number of 100-nanoseconds elapsed since "1582-10-15 00:00:00 +0000 UTC"(UUID-epoch). 11 | func uuidTimestamp() int64 { 12 | timeNow := time.Now() 13 | return timeNow.Unix()*1e7 + int64(timeNow.Nanosecond())/100 + unixToUUID 14 | } 15 | 16 | // tillNext100nano spin wait till next 100-nanosecond. 17 | func tillNext100nano(lastTimestamp int64) int64 { 18 | timestamp := uuidTimestamp() 19 | for timestamp <= lastTimestamp { 20 | timestamp = uuidTimestamp() 21 | } 22 | return timestamp 23 | } 24 | -------------------------------------------------------------------------------- /uuid/variant.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | type Variant byte 4 | 5 | const ( 6 | VariantNCS Variant = iota 7 | VariantRFC4122 8 | VariantMicrosoft 9 | VariantFuture 10 | ) 11 | 12 | func (v Variant) String() string { 13 | switch v { 14 | case VariantNCS: 15 | return "NCS-Variant" 16 | case VariantRFC4122: 17 | return "RFC4122-Variant" 18 | case VariantMicrosoft: 19 | return "Microsoft-Variant" 20 | case VariantFuture: 21 | return "Future-Variant" 22 | default: 23 | return "Unknown-Variant" 24 | } 25 | } 26 | 27 | func (uuid UUID) Variant() Variant { 28 | switch { 29 | case (uuid[8] & 0x80) == 0x00: 30 | return VariantNCS 31 | case (uuid[8] & 0xc0) == 0x80: 32 | return VariantRFC4122 33 | case (uuid[8] & 0xe0) == 0xc0: 34 | return VariantMicrosoft 35 | default: 36 | return VariantFuture 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /helper_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "testing" 4 | 5 | func TestJSON(t *testing.T) { 6 | x := &struct { 7 | A string `json:"a"` 8 | B int `json:"b"` 9 | C string `json:"-"` 10 | }{ 11 | A: "a_val", 12 | B: 100, 13 | C: "c", 14 | } 15 | have := JSON(x) 16 | want := `{"a":"a_val","b":100}` 17 | if have != want { 18 | t.Errorf("have:%s, want:%s", have, want) 19 | return 20 | } 21 | } 22 | 23 | func TestXML(t *testing.T) { 24 | x := &struct { 25 | XMLName struct{} `xml:"msg"` 26 | A string `xml:"a"` 27 | B int `xml:"b"` 28 | C string `xml:"-"` 29 | }{ 30 | A: "a_val", 31 | B: 100, 32 | C: "c", 33 | } 34 | have := XML(x) 35 | want := `a_val100` 36 | if have != want { 37 | t.Errorf("have:%s, want:%s", have, want) 38 | return 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /uuid/rand/int.go: -------------------------------------------------------------------------------- 1 | package rand 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | const ( 8 | uint31Mask = ^(uint32(1) << 31) 9 | uint63Mask = ^(uint64(1) << 63) 10 | ) 11 | 12 | // Int31 returns a non-negative random 31-bit integer as an int32. 13 | func Int31() int32 { 14 | u32 := Uint32() 15 | return int32(u32 & uint31Mask) 16 | } 17 | 18 | // Int63 returns a non-negative random 63-bit integer as an int64. 19 | func Int63() int64 { 20 | u64 := Uint64() 21 | return int64(u64 & uint63Mask) 22 | } 23 | 24 | // Uint32 returns a random 32-bit integer as a uint32. 25 | func Uint32() uint32 { 26 | var x [4]byte 27 | Read(x[:]) 28 | return binary.BigEndian.Uint32(x[:]) 29 | } 30 | 31 | // Uint64 returns a random 64-bit integer as a uint64. 32 | func Uint64() uint64 { 33 | var x [8]byte 34 | Read(x[:]) 35 | return binary.BigEndian.Uint64(x[:]) 36 | } 37 | -------------------------------------------------------------------------------- /location.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "path" 5 | "runtime" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | func callerLocation(skip int) string { 11 | pc, file, line, ok := runtime.Caller(skip + 1) 12 | if !ok { 13 | return "???" 14 | } 15 | fn := runtime.FuncForPC(pc) 16 | if fn == nil { 17 | return trimFileName(file) + ":" + strconv.Itoa(line) 18 | } 19 | return trimFuncName(fn.Name()) + "(" + trimFileName(file) + ":" + strconv.Itoa(line) + ")" 20 | } 21 | 22 | func trimFuncName(name string) string { 23 | return path.Base(name) 24 | } 25 | 26 | func trimFileName(name string) string { 27 | i := strings.Index(name, "/src/") + len("/src/") 28 | if i >= len("/src/") && i < len(name) /* BCE */ { 29 | name = name[i:] 30 | } 31 | i = strings.LastIndex(name, "/vendor/") + len("/vendor/") 32 | if i >= len("/vendor/") && i < len(name) /* BCE */ { 33 | return name[i:] 34 | } 35 | return name 36 | } 37 | -------------------------------------------------------------------------------- /json_formatter.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | var JsonFormatter Formatter = jsonFormatter{} 9 | 10 | type jsonFormatter struct{} 11 | 12 | func (jsonFormatter) Format(entry *Entry) ([]byte, error) { 13 | var buffer *bytes.Buffer 14 | if entry.Buffer != nil { 15 | buffer = entry.Buffer 16 | } else { 17 | buffer = bytes.NewBuffer(make([]byte, 0, 16<<10)) 18 | } 19 | var fields map[string]interface{} 20 | if fields = entry.Fields; len(fields) > 0 { 21 | prefixFieldClashes(fields) 22 | for k, v := range fields { 23 | if vv, ok := v.(error); ok { 24 | fields[k] = vv.Error() 25 | } 26 | } 27 | } else { 28 | fields = make(map[string]interface{}, 8) 29 | } 30 | fields[fieldKeyTime] = FormatTimeString(entry.Time.In(_beijingLocation)) 31 | fields[fieldKeyLevel] = entry.Level.String() 32 | fields[fieldKeyTraceId] = entry.TraceId 33 | fields[fieldKeyLocation] = entry.Location 34 | fields[fieldKeyMessage] = entry.Message 35 | if err := json.NewEncoder(buffer).Encode(fields); err != nil { 36 | return nil, err 37 | } 38 | return buffer.Bytes(), nil 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 keke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /helper.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | ) 7 | 8 | // JSON is a helper function, following is its function code. 9 | // 10 | // data, _ := json.Marshal(v) 11 | // return string(data) 12 | func JSON(v interface{}) string { 13 | pool := getBytesBufferPool() 14 | buffer := pool.Get() 15 | defer pool.Put(buffer) 16 | buffer.Reset() 17 | 18 | if err := json.NewEncoder(buffer).Encode(v); err != nil { 19 | return "" 20 | } 21 | data := buffer.Bytes() 22 | 23 | // remove the trailing newline 24 | i := len(data) - 1 25 | if i < 0 || i >= len(data) /* BCE */ { 26 | return "" 27 | } 28 | if data[i] == '\n' { 29 | data = data[:i] 30 | } 31 | return string(data) 32 | } 33 | 34 | // XML is a helper function, following is its function code. 35 | // 36 | // data, _ := xml.Marshal(v) 37 | // return string(data) 38 | func XML(v interface{}) string { 39 | pool := getBytesBufferPool() 40 | buffer := pool.Get() 41 | defer pool.Put(buffer) 42 | buffer.Reset() 43 | 44 | if err := xml.NewEncoder(buffer).Encode(v); err != nil { 45 | return "" 46 | } 47 | return string(buffer.Bytes()) 48 | } 49 | -------------------------------------------------------------------------------- /location_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func testCallerLocation() string { 9 | return callerLocation(0) 10 | } 11 | 12 | func TestCallerLocation(t *testing.T) { 13 | location := testCallerLocation() 14 | switch { 15 | case location == "log.testCallerLocation(github.com/chanxuehong/log/location_test.go:9)": 16 | case strings.HasPrefix(location, "log.testCallerLocation(") && strings.HasSuffix(location, "/log/location_test.go:9)"): 17 | default: 18 | t.Errorf("not expected location: %s", location) 19 | return 20 | } 21 | } 22 | 23 | func TestTrimFileName(t *testing.T) { 24 | tests := []struct { 25 | str string 26 | want string 27 | }{ 28 | { 29 | "/a/b/c.go", 30 | "/a/b/c.go", 31 | }, 32 | { 33 | "/a/src/b/c.go", 34 | "b/c.go", 35 | }, 36 | { 37 | "/a/src/d/vendor/b/c.go", 38 | "b/c.go", 39 | }, 40 | { 41 | "/a/vendor/b/c.go", 42 | "b/c.go", 43 | }, 44 | } 45 | 46 | for _, v := range tests { 47 | have := trimFileName(v.str) 48 | if have != v.want { 49 | t.Errorf("have:%s, want:%s", have, v.want) 50 | return 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /buffer_pool.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | "sync/atomic" 7 | "unsafe" 8 | ) 9 | 10 | type BytesBufferPool interface { 11 | Get() *bytes.Buffer 12 | Put(*bytes.Buffer) 13 | } 14 | 15 | var _BytesBufferPoolPtr unsafe.Pointer = unsafe.Pointer(&_defaultBytesBufferPool) // *BytesBufferPool 16 | 17 | func getBytesBufferPool() BytesBufferPool { 18 | ptr := (*BytesBufferPool)(atomic.LoadPointer(&_BytesBufferPoolPtr)) 19 | return *ptr 20 | } 21 | 22 | func SetBytesBufferPool(pool BytesBufferPool) { 23 | if pool == nil { 24 | return 25 | } 26 | atomic.StorePointer(&_BytesBufferPoolPtr, unsafe.Pointer(&pool)) 27 | } 28 | 29 | var _defaultBytesBufferPool BytesBufferPool = &bytesBufferPool{ 30 | pool: sync.Pool{ 31 | New: syncPoolNew, 32 | }, 33 | } 34 | 35 | func syncPoolNew() interface{} { 36 | return bytes.NewBuffer(make([]byte, 0, 16<<10)) 37 | } 38 | 39 | type bytesBufferPool struct { 40 | pool sync.Pool 41 | } 42 | 43 | func (p *bytesBufferPool) Get() *bytes.Buffer { 44 | return p.pool.Get().(*bytes.Buffer) 45 | } 46 | 47 | func (p *bytesBufferPool) Put(x *bytes.Buffer) { 48 | if x == nil { 49 | return 50 | } 51 | p.pool.Put(x) 52 | } 53 | -------------------------------------------------------------------------------- /time_format.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "time" 4 | 5 | const TimeFormatLayout = "2006-01-02 15:04:05.000" 6 | 7 | // 2006-01-02 15:04:05.000 8 | func FormatTimeString(t time.Time) string { 9 | result := FormatTime(t) 10 | return string(result[:]) 11 | } 12 | 13 | // 2006-01-02 15:04:05.000 14 | func FormatTime(t time.Time) (result [23]byte) { 15 | year, month, day := t.Date() 16 | hour, min, sec := t.Clock() 17 | nsec := t.Nanosecond() 18 | 19 | itoa(result[:4], year) 20 | result[4] = '-' 21 | itoa(result[5:7], int(month)) 22 | result[7] = '-' 23 | itoa(result[8:10], day) 24 | result[10] = ' ' 25 | itoa(result[11:13], hour) 26 | result[13] = ':' 27 | itoa(result[14:16], min) 28 | result[16] = ':' 29 | itoa(result[17:19], sec) 30 | result[19] = '.' 31 | itoa(result[20:], nsec/1e6) 32 | return 33 | } 34 | 35 | // itoa format integer to fixed-width decimal ASCII. 36 | // 37 | // requirement: 38 | // n must be greater than or equal to 0 39 | // buf must be large enough to accommodate n 40 | func itoa(buf []byte, n int) { 41 | i := len(buf) - 1 42 | for n >= 10 { 43 | q := n / 10 44 | buf[i] = byte('0' + n - q*10) 45 | i-- 46 | n = q 47 | } 48 | // n < 10 49 | buf[i] = byte('0' + n) 50 | i-- 51 | for i >= 0 { 52 | buf[i] = '0' 53 | i-- 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /logger_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "testing" 4 | 5 | func TestLogger_New(t *testing.T) { 6 | lg1 := _New([]Option{ 7 | WithFormatter(JsonFormatter), 8 | WithOutput(ConcurrentStderr), 9 | WithLevel(InfoLevel), 10 | }) 11 | 12 | lg2 := _New(nil) 13 | lg2.SetFormatter(JsonFormatter) 14 | lg2.SetOutput(ConcurrentStderr) 15 | lg2.SetLevel(InfoLevel) 16 | 17 | opts1 := lg1.getOptions() 18 | opts2 := lg2.getOptions() 19 | 20 | if *opts1 != *opts2 { 21 | t.Errorf("have:%v, want:%v", opts1, opts2) 22 | return 23 | } 24 | } 25 | 26 | func TestLogger_SetOptions_GetOptions(t *testing.T) { 27 | lg := _New(nil) 28 | 29 | // default 30 | { 31 | opts := lg.getOptions() 32 | want := options{ 33 | traceId: "", 34 | formatter: TextFormatter, 35 | output: ConcurrentStdout, 36 | level: DebugLevel, 37 | } 38 | if have := *opts; have != want { 39 | t.Errorf("have:%v, want:%v", have, want) 40 | return 41 | } 42 | } 43 | // set and get 44 | { 45 | opts := &options{ 46 | traceId: "123456789", 47 | formatter: JsonFormatter, 48 | output: ConcurrentStderr, 49 | level: InfoLevel, 50 | } 51 | lg.setOptions(opts) 52 | have := lg.getOptions() 53 | if *have != *opts { 54 | t.Errorf("have:%v, want:%v", have, opts) 55 | return 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /uuid/helper_test.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEncodeDecode(t *testing.T) { 8 | u1 := NewV1() 9 | encoded := u1.Encode() 10 | u2, err := Decode(encoded) 11 | if err != nil { 12 | t.Error("decode failed") 13 | return 14 | } 15 | if u1 != u2 { 16 | t.Error("encode and decode mismatch") 17 | return 18 | } 19 | } 20 | 21 | //var ( 22 | // NamespaceDNS, _ = Decode([]byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) 23 | // NamespaceURL, _ = Decode([]byte("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) 24 | // NamespaceOID, _ = Decode([]byte("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) 25 | // NamespaceX500, _ = Decode([]byte("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) 26 | //) 27 | 28 | func TestNamespace(t *testing.T) { 29 | if NamespaceDNS.String() != "6ba7b810-9dad-11d1-80b4-00c04fd430c8" { 30 | t.Error("decode and encode mismatch") 31 | return 32 | } 33 | if NamespaceURL.String() != "6ba7b811-9dad-11d1-80b4-00c04fd430c8" { 34 | t.Error("decode and encode mismatch") 35 | return 36 | } 37 | if NamespaceOID.String() != "6ba7b812-9dad-11d1-80b4-00c04fd430c8" { 38 | t.Error("decode and encode mismatch") 39 | return 40 | } 41 | if NamespaceX500.String() != "6ba7b814-9dad-11d1-80b4-00c04fd430c8" { 42 | t.Error("decode and encode mismatch") 43 | return 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### go log system 2 | 3 |

4 | 5 | 6 | 7 |

8 | 9 | log is a powerful logging framework that provides log custom log level. 10 | 11 | log provides Fatal, Error, Warn, Info, Debug level log. and with the requestID can quickly go to the current line of code. 12 | 13 | If you like,please give a star。 14 | 15 | #### How to Use 16 | 17 | it's very simple and easy to use. 18 | 19 | ```go 20 | func main(){ 21 | l := log.New(log.WithTraceId(trace.NewTraceId())) 22 | ctx := log.NewContext(context.Background(), l) 23 | 24 | v, err := rand.Int(rand.Reader, big.NewInt(int64(16))) 25 | if err != nil { 26 | log.ErrorContext(ctx, " rand.In failed", "error", err.Error()) 27 | return 28 | } 29 | log.InfoContext(ctx, "the rand Int result", "v", v) 30 | } 31 | ``` 32 | 33 | This use is made of the key and the Value output. 34 | 35 | ### License 36 | 37 | This is free software distributed under the terms of the MIT license 38 | -------------------------------------------------------------------------------- /trace/trace.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | type Tracer interface { 9 | TraceId() string 10 | } 11 | 12 | type traceIdContextKey struct{} 13 | 14 | var _traceIdContextKey traceIdContextKey 15 | 16 | func NewContext(ctx context.Context, traceId string) context.Context { 17 | if traceId == "" { 18 | return ctx 19 | } 20 | if ctx == nil { 21 | return context.WithValue(context.Background(), _traceIdContextKey, traceId) 22 | } 23 | if value, ok := ctx.Value(_traceIdContextKey).(string); ok && value == traceId { 24 | return ctx 25 | } 26 | return context.WithValue(ctx, _traceIdContextKey, traceId) 27 | } 28 | 29 | func FromContext(ctx context.Context) (traceId string, ok bool) { 30 | if ctx == nil { 31 | return "", false 32 | } 33 | traceId, ok = ctx.Value(_traceIdContextKey).(string) 34 | return 35 | } 36 | 37 | func FromRequest(req *http.Request) (traceId string, ok bool) { 38 | traceId, ok = FromContext(req.Context()) 39 | if ok { 40 | return traceId, true 41 | } 42 | return FromHeader(req.Header) 43 | } 44 | 45 | const TraceIdHeaderKey = "X-Request-Id" 46 | 47 | func FromHeader(header http.Header) (traceId string, ok bool) { 48 | traceId = header.Get(TraceIdHeaderKey) 49 | if traceId != "" { 50 | return traceId, true 51 | } 52 | return "", false 53 | } 54 | -------------------------------------------------------------------------------- /fields.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "errors" 4 | 5 | var ( 6 | _ErrNumberOfFieldsMustNotBeOdd error = errors.New("the number of fields must not be odd") 7 | _ErrTypeOfFieldKeyMustBeString error = errors.New("the type of field key must be string") 8 | _ErrFieldKeyMustNotBeEmpty error = errors.New("the field key must not be empty") 9 | ) 10 | 11 | func combineFields(m map[string]interface{}, fields []interface{}) (map[string]interface{}, error) { 12 | if len(fields) == 0 { 13 | return cloneFields(m), nil 14 | } 15 | if len(fields)&1 != 0 { 16 | return cloneFields(m), _ErrNumberOfFieldsMustNotBeOdd 17 | } 18 | 19 | m2 := make(map[string]interface{}, 8+len(m)+len(fields)>>1) // 8 is reserved for the standard field 20 | for k, v := range m { 21 | m2[k] = v 22 | } 23 | var ( 24 | k string 25 | ok bool 26 | ) 27 | for i, v := range fields { 28 | if i&1 == 0 { // key 29 | k, ok = v.(string) 30 | if !ok { 31 | return m2, _ErrTypeOfFieldKeyMustBeString 32 | } 33 | if k == "" { 34 | return m2, _ErrFieldKeyMustNotBeEmpty 35 | } 36 | } else { // value 37 | m2[k] = v 38 | } 39 | } 40 | return m2, nil 41 | } 42 | 43 | func cloneFields(fields map[string]interface{}) map[string]interface{} { 44 | if len(fields) == 0 { 45 | return nil 46 | } 47 | m := make(map[string]interface{}, len(fields)) 48 | for k, v := range fields { 49 | m[k] = v 50 | } 51 | return m 52 | } 53 | -------------------------------------------------------------------------------- /noop_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "io" 4 | 5 | // NoopLogger no operation Logger 6 | type NoopLogger struct{} 7 | 8 | // Fatal impl Logger Fatal 9 | func (NoopLogger) Fatal(msg string, fields ...interface{}) { 10 | } 11 | 12 | // Error impl Logger Error 13 | func (NoopLogger) Error(msg string, fields ...interface{}) { 14 | } 15 | 16 | // Warn impl Logger Warn 17 | func (NoopLogger) Warn(msg string, fields ...interface{}) { 18 | } 19 | 20 | // Info impl Logger Info 21 | func (NoopLogger) Info(msg string, fields ...interface{}) { 22 | } 23 | 24 | // Debug impl Logger Debug 25 | func (NoopLogger) Debug(msg string, fields ...interface{}) { 26 | } 27 | 28 | // Output impl Logger Output 29 | func (NoopLogger) Output(calldepth int, level Level, msg string, fields ...interface{}) { 30 | } 31 | 32 | // WithField impl Logger WithField 33 | func (NoopLogger) WithField(key string, value interface{}) Logger { 34 | return NoopLogger{} 35 | } 36 | 37 | // WithFields impl Logger WithFields 38 | func (NoopLogger) WithFields(fields ...interface{}) Logger { 39 | return NoopLogger{} 40 | } 41 | 42 | // SetFormatter impl Logger SetFormatter 43 | func (NoopLogger) SetFormatter(Formatter) { 44 | } 45 | 46 | // SetOutput impl Logger SetOutput 47 | func (NoopLogger) SetOutput(io.Writer) { 48 | } 49 | 50 | // SetLevel impl Logger SetLevel 51 | func (NoopLogger) SetLevel(Level) error { 52 | return nil 53 | } 54 | 55 | // SetLevelString impl Logger SetLevelString 56 | func (NoopLogger) SetLevelString(string) error { 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /level.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func init() { 9 | // since invalidLevel must be 0, forced check. 10 | if invalidLevel != 0 { 11 | panic("invalidLevel must equal 0") 12 | } 13 | } 14 | 15 | const ( 16 | invalidLevel Level = iota 17 | FatalLevel 18 | ErrorLevel 19 | WarnLevel 20 | InfoLevel 21 | DebugLevel 22 | ) 23 | 24 | func isValidLevel(level Level) bool { 25 | return level >= FatalLevel && level <= DebugLevel 26 | } 27 | func isLevelEnabled(level, loggerLevel Level) bool { 28 | return loggerLevel >= level 29 | } 30 | 31 | const ( 32 | FatalLevelString = "fatal" 33 | ErrorLevelString = "error" 34 | WarnLevelString = "warning" 35 | InfoLevelString = "info" 36 | DebugLevelString = "debug" 37 | ) 38 | 39 | func parseLevelString(str string) (level Level, ok bool) { 40 | switch strings.ToLower(str) { 41 | case DebugLevelString: 42 | return DebugLevel, true 43 | case InfoLevelString: 44 | return InfoLevel, true 45 | case WarnLevelString: 46 | return WarnLevel, true 47 | case ErrorLevelString: 48 | return ErrorLevel, true 49 | case FatalLevelString: 50 | return FatalLevel, true 51 | default: 52 | return invalidLevel, false 53 | } 54 | } 55 | 56 | type Level uint 57 | 58 | func (level Level) String() string { 59 | switch level { 60 | case FatalLevel: 61 | return FatalLevelString 62 | case ErrorLevel: 63 | return ErrorLevelString 64 | case WarnLevel: 65 | return WarnLevelString 66 | case InfoLevel: 67 | return InfoLevelString 68 | case DebugLevel: 69 | return DebugLevelString 70 | default: 71 | return fmt.Sprintf("unknown_%d", level) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /uuid/rand/mac.go: -------------------------------------------------------------------------------- 1 | package rand 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | ) 7 | 8 | var MAC [6]byte = getMAC() // One MAC of this machine; Particular case, it is a random bytes. 9 | 10 | var zeroMAC [8]byte 11 | 12 | func getMAC() (mac [6]byte) { 13 | interfaces, err := net.Interfaces() 14 | if err != nil { 15 | return genMAC() 16 | } 17 | 18 | // Gets a MAC from interfaces of this machine, 19 | // the MAC of up state interface is preferred. 20 | found := false // Says it has found a MAC 21 | for _, itf := range interfaces { 22 | if itf.Flags&net.FlagLoopback == net.FlagLoopback || 23 | itf.Flags&net.FlagPointToPoint == net.FlagPointToPoint { 24 | continue 25 | } 26 | 27 | switch hardwareAddr := itf.HardwareAddr; len(hardwareAddr) { 28 | case 6: // MAC-48, EUI-48 29 | if bytes.Equal(hardwareAddr, zeroMAC[:6]) { 30 | continue 31 | } 32 | if itf.Flags&net.FlagUp == 0 { 33 | if !found { 34 | copy(mac[:], hardwareAddr) 35 | found = true 36 | } 37 | continue 38 | } 39 | copy(mac[:], hardwareAddr) 40 | return 41 | case 8: // EUI-64 42 | if bytes.Equal(hardwareAddr, zeroMAC[:]) { 43 | continue 44 | } 45 | if itf.Flags&net.FlagUp == 0 { 46 | if !found { 47 | copy(mac[:3], hardwareAddr) 48 | copy(mac[3:], hardwareAddr[5:]) 49 | found = true 50 | } 51 | continue 52 | } 53 | copy(mac[:3], hardwareAddr) 54 | copy(mac[3:], hardwareAddr[5:]) 55 | return 56 | } 57 | } 58 | if found { 59 | return 60 | } 61 | 62 | return genMAC() 63 | } 64 | 65 | // generates a random MAC. 66 | func genMAC() (mac [6]byte) { 67 | Read(mac[:]) 68 | mac[0] |= 0x01 // multicast 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /uuid/v1/internal.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | 7 | "github.com/KeKe-Li/log/uuid/rand" 8 | ) 9 | 10 | var MAC [6]byte = getMAC() // One MAC of this machine; Particular case, it is a random bytes. 11 | 12 | var zeroMAC [8]byte 13 | 14 | func getMAC() (mac [6]byte) { 15 | interfaces, err := net.Interfaces() 16 | if err != nil { 17 | return genMAC() 18 | } 19 | 20 | // Gets a MAC from interfaces of this machine, 21 | // the MAC of up state interface is preferred. 22 | found := false // Says it has found a MAC 23 | for _, itf := range interfaces { 24 | if itf.Flags&net.FlagLoopback == net.FlagLoopback || 25 | itf.Flags&net.FlagPointToPoint == net.FlagPointToPoint { 26 | continue 27 | } 28 | 29 | switch hardwareAddr := itf.HardwareAddr; len(hardwareAddr) { 30 | case 6: // MAC-48, EUI-48 31 | if bytes.Equal(hardwareAddr, zeroMAC[:6]) { 32 | continue 33 | } 34 | if itf.Flags&net.FlagUp == 0 { 35 | if !found { 36 | copy(mac[:], hardwareAddr) 37 | found = true 38 | } 39 | continue 40 | } 41 | copy(mac[:], hardwareAddr) 42 | return 43 | case 8: // EUI-64 44 | if bytes.Equal(hardwareAddr, zeroMAC[:]) { 45 | continue 46 | } 47 | if itf.Flags&net.FlagUp == 0 { 48 | if !found { 49 | copy(mac[:3], hardwareAddr) 50 | copy(mac[3:], hardwareAddr[5:]) 51 | found = true 52 | } 53 | continue 54 | } 55 | copy(mac[:3], hardwareAddr) 56 | copy(mac[3:], hardwareAddr[5:]) 57 | return 58 | } 59 | } 60 | if found { 61 | return 62 | } 63 | 64 | return genMAC() 65 | } 66 | 67 | // generates a random MAC. 68 | func genMAC() (mac [6]byte) { 69 | rand.Read(mac[:]) 70 | mac[0] |= 0x01 // multicast 71 | return 72 | } 73 | -------------------------------------------------------------------------------- /uuid/rand/rand.go: -------------------------------------------------------------------------------- 1 | package rand 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | ) 10 | 11 | const ( 12 | saltLen = 45 // see New(), 6+4+45==55<56, Best performance for md5 13 | saltUpdateInterval = 3600 // seconds 14 | ) 15 | 16 | var ( 17 | gSalt = make([]byte, saltLen) 18 | gSequence = Uint32() 19 | 20 | gMutex sync.Mutex 21 | gSaltLastUpdateTimestamp int64 = -saltUpdateInterval 22 | ) 23 | 24 | // New returns 16-byte raw random bytes. 25 | // It is not printable, you can use encoding/hex or encoding/base64 to print it. 26 | func New() (rd [16]byte) { 27 | timeNow := time.Now() 28 | timeNowUnix := timeNow.Unix() 29 | 30 | if timeNowUnix >= atomic.LoadInt64(&gSaltLastUpdateTimestamp)+saltUpdateInterval { 31 | gMutex.Lock() // Lock 32 | if timeNowUnix >= gSaltLastUpdateTimestamp+saltUpdateInterval { 33 | gSaltLastUpdateTimestamp = timeNowUnix 34 | gMutex.Unlock() // Unlock 35 | 36 | Read(gSalt) 37 | copy(rd[:], gSalt) 38 | return 39 | } 40 | gMutex.Unlock() // Unlock 41 | } 42 | sequence := atomic.AddUint32(&gSequence, 1) 43 | 44 | timeNowUnixNano := timeNow.UnixNano() 45 | var src [6 + 4 + saltLen]byte // 6+4+45==55 46 | src[0] = byte(timeNowUnixNano >> 40) 47 | src[1] = byte(timeNowUnixNano >> 32) 48 | src[2] = byte(timeNowUnixNano >> 24) 49 | src[3] = byte(timeNowUnixNano >> 16) 50 | src[4] = byte(timeNowUnixNano >> 8) 51 | src[5] = byte(timeNowUnixNano) 52 | src[6] = byte(sequence >> 24) 53 | src[7] = byte(sequence >> 16) 54 | src[8] = byte(sequence >> 8) 55 | src[9] = byte(sequence) 56 | copy(src[10:], gSalt) 57 | 58 | return md5.Sum(src[:]) 59 | } 60 | 61 | // NewHex returns 32-byte hex-encoded bytes. 62 | func NewHex() (rd []byte) { 63 | rdx := New() 64 | rd = make([]byte, hex.EncodedLen(len(rdx))) 65 | hex.Encode(rd, rdx[:]) 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /uuid/helper.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | ) 7 | 8 | func HexEncode(uuid UUID) []byte { 9 | buf := make([]byte, 32) 10 | hex.Encode(buf, uuid[:]) 11 | return buf 12 | } 13 | 14 | // Encode encodes UUID to "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format. 15 | func Encode(uuid UUID) []byte { 16 | const encodedUUIDLen = 36 17 | var buf [encodedUUIDLen]byte 18 | hex.Encode(buf[:8], uuid[:4]) 19 | buf[8] = '-' 20 | hex.Encode(buf[9:13], uuid[4:6]) 21 | buf[13] = '-' 22 | hex.Encode(buf[14:18], uuid[6:8]) 23 | buf[18] = '-' 24 | hex.Encode(buf[19:23], uuid[8:10]) 25 | buf[23] = '-' 26 | hex.Encode(buf[24:], uuid[10:]) 27 | return buf[:] 28 | } 29 | 30 | var ( 31 | NamespaceDNS, _ = Decode([]byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) 32 | NamespaceURL, _ = Decode([]byte("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) 33 | NamespaceOID, _ = Decode([]byte("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) 34 | NamespaceX500, _ = Decode([]byte("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) 35 | ) 36 | 37 | // Decode decodes data with "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format into UUID. 38 | func Decode(data []byte) (uuid UUID, err error) { 39 | const encodedUUIDLen = 36 40 | if len(data) != encodedUUIDLen { 41 | err = errors.New("invalid UUID data") 42 | return 43 | } 44 | _ = data[encodedUUIDLen-1] 45 | if data[8] != '-' || data[13] != '-' || data[18] != '-' || data[23] != '-' { 46 | err = errors.New("invalid UUID data") 47 | return 48 | } 49 | if _, err = hex.Decode(uuid[:4], data[:8]); err != nil { 50 | return 51 | } 52 | if _, err = hex.Decode(uuid[4:6], data[9:13]); err != nil { 53 | return 54 | } 55 | if _, err = hex.Decode(uuid[6:8], data[14:18]); err != nil { 56 | return 57 | } 58 | if _, err = hex.Decode(uuid[8:10], data[19:23]); err != nil { 59 | return 60 | } 61 | _, err = hex.Decode(uuid[10:], data[24:]) 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /json_formatter_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestJsonFormatter_Format(t *testing.T) { 11 | entry := &Entry{ 12 | Location: "function(file:line)", 13 | Time: time.Date(2018, time.May, 20, 8, 20, 30, 666000000, time.UTC), 14 | Level: InfoLevel, 15 | TraceId: "trace_id_123456789", 16 | Message: "message_123456789", 17 | Fields: map[string]interface{}{ 18 | "key1": "fields_value1", 19 | "key2": "fields_value2", 20 | "key3": &testError{X: "123456789"}, // error 21 | fieldKeyTime: "time", 22 | fieldKeyLevel: "level", 23 | fieldKeyTraceId: "request_id", 24 | fieldKeyLocation: "location", 25 | fieldKeyMessage: "msg", 26 | }, 27 | Buffer: nil, 28 | } 29 | data, err := JsonFormatter.Format(entry) 30 | if err != nil { 31 | t.Error(err.Error()) 32 | return 33 | } 34 | if len(data) == 0 || data[len(data)-1] != '\n' { 35 | t.Error("want end with '\n'") 36 | return 37 | } 38 | var have map[string]string 39 | if err = json.Unmarshal(data, &have); err != nil { 40 | t.Error(err.Error()) 41 | return 42 | } 43 | want := map[string]string{ 44 | "time": "2018-05-20 16:20:30.666", 45 | "level": "info", 46 | "request_id": "trace_id_123456789", 47 | "location": "function(file:line)", 48 | "msg": "message_123456789", 49 | "fields.level": "level", 50 | "fields.location": "location", 51 | "fields.msg": "msg", 52 | "fields.request_id": "request_id", 53 | "fields.time": "time", 54 | "key1": "fields_value1", 55 | "key2": "fields_value2", 56 | "key3": "test_error_123456789", // error 57 | } 58 | if !reflect.DeepEqual(have, want) { 59 | t.Errorf("\nhave:%v\nwant:%v", have, want) 60 | return 61 | } 62 | } 63 | 64 | type testError struct { 65 | X string `json:"x"` 66 | } 67 | 68 | func (e *testError) Error() string { 69 | return "test_error_123456789" 70 | } 71 | -------------------------------------------------------------------------------- /text_formatter_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestTextFormatter_Format(t *testing.T) { 11 | entry := &Entry{ 12 | Location: "function(file:line)", 13 | Time: time.Date(2018, time.May, 20, 8, 20, 30, 666777888, time.UTC), 14 | Level: InfoLevel, 15 | TraceId: "trace_id_123456789", 16 | Message: "message_123456789", 17 | Fields: map[string]interface{}{ 18 | "key1": "fields_value1", 19 | "key2": "fields_value2", 20 | "key3": &testError{X: "123456789"}, // error 21 | "key4": json.RawMessage([]byte(`{"code":0,"msg":""}`)), 22 | fieldKeyTime: "time", 23 | fieldKeyLevel: "level", 24 | fieldKeyTraceId: "request_id", 25 | fieldKeyLocation: "location", 26 | fieldKeyMessage: "msg", 27 | }, 28 | Buffer: nil, 29 | } 30 | have, err := TextFormatter.Format(entry) 31 | if err != nil { 32 | t.Error(err.Error()) 33 | return 34 | } 35 | want := `time=2018-05-20 16:20:30.666, level=info, request_id=trace_id_123456789, location=function(file:line), msg=message_123456789, ` + 36 | `fields.level=level, fields.location=location, fields.msg=msg, fields.request_id=request_id, fields.time=time, ` + 37 | `key1=fields_value1, key2=fields_value2, key3=test_error_123456789, key4={"code":0,"msg":""}` + "\n" 38 | if string(have) != want { 39 | t.Errorf("\nhave:%s\nwant:%s", have, want) 40 | return 41 | } 42 | } 43 | 44 | func TestPrefixFieldClashes(t *testing.T) { 45 | m := map[string]interface{}{ 46 | "time": "time", 47 | "fields.time": "fields.time", 48 | "level": "level", 49 | "fields.level": "fields.level", 50 | "fields.level.2": "fields.level.2", 51 | } 52 | prefixFieldClashes(m) 53 | want := map[string]interface{}{ 54 | "fields.time.2": "time", 55 | "fields.time": "fields.time", 56 | "fields.level.3": "level", 57 | "fields.level": "fields.level", 58 | "fields.level.2": "fields.level.2", 59 | } 60 | if !reflect.DeepEqual(m, want) { 61 | t.Errorf("\nhave:%v\nwant:%v", m, want) 62 | return 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /uuid/rand/read.go: -------------------------------------------------------------------------------- 1 | package rand 2 | 3 | import ( 4 | cryptorand "crypto/rand" 5 | mathrand "math/rand" 6 | "time" 7 | ) 8 | 9 | func init() { 10 | mathrand.Seed(time.Now().UnixNano()) 11 | } 12 | 13 | // Read reads len(p)-byte raw random bytes to p. 14 | func Read(p []byte) { 15 | if len(p) <= 0 { 16 | return 17 | } 18 | 19 | // get from crypto/rand 20 | if _, err := cryptorand.Read(p); err == nil { 21 | return 22 | } 23 | 24 | // get from math/rand 25 | timeNowNano := int64(time.Now().Nanosecond()) 26 | timeNowNano = timeNowNano<<32 | timeNowNano 27 | for len(p) > 0 { 28 | n := mathrand.Int63() ^ timeNowNano 29 | switch len(p) { 30 | case 8: 31 | p[0] = byte(n >> 56) 32 | p[1] = byte(n >> 48) 33 | p[2] = byte(n >> 40) 34 | p[3] = byte(n >> 32) 35 | p[4] = byte(n >> 24) 36 | p[5] = byte(n >> 16) 37 | p[6] = byte(n >> 8) 38 | p[7] = byte(n) 39 | return 40 | case 4: 41 | p[0] = byte(n >> 24) 42 | p[1] = byte(n >> 16) 43 | p[2] = byte(n >> 8) 44 | p[3] = byte(n) 45 | return 46 | case 1: 47 | p[0] = byte(n) 48 | return 49 | case 2: 50 | p[0] = byte(n >> 8) 51 | p[1] = byte(n) 52 | return 53 | case 3: 54 | p[0] = byte(n >> 16) 55 | p[1] = byte(n >> 8) 56 | p[2] = byte(n) 57 | return 58 | case 5: 59 | p[0] = byte(n >> 32) 60 | p[1] = byte(n >> 24) 61 | p[2] = byte(n >> 16) 62 | p[3] = byte(n >> 8) 63 | p[4] = byte(n) 64 | return 65 | case 6: 66 | p[0] = byte(n >> 40) 67 | p[1] = byte(n >> 32) 68 | p[2] = byte(n >> 24) 69 | p[3] = byte(n >> 16) 70 | p[4] = byte(n >> 8) 71 | p[5] = byte(n) 72 | return 73 | case 7: 74 | p[0] = byte(n >> 48) 75 | p[1] = byte(n >> 40) 76 | p[2] = byte(n >> 32) 77 | p[3] = byte(n >> 24) 78 | p[4] = byte(n >> 16) 79 | p[5] = byte(n >> 8) 80 | p[6] = byte(n) 81 | return 82 | default: // len(p) > 8 83 | _ = p[8] 84 | p[0] = byte(n >> 56) 85 | p[1] = byte(n >> 48) 86 | p[2] = byte(n >> 40) 87 | p[3] = byte(n >> 32) 88 | p[4] = byte(n >> 24) 89 | p[5] = byte(n >> 16) 90 | p[6] = byte(n >> 8) 91 | p[7] = byte(n) 92 | p = p[8:] 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /time_format_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestFormatTime(t *testing.T) { 10 | now := time.Now() 11 | have := FormatTime(now) 12 | want := now.Format(TimeFormatLayout) 13 | if have := string(have[:]); have != want { 14 | t.Errorf("have:%s, want:%s", have, want) 15 | return 16 | } 17 | } 18 | 19 | func TestFormatTimeString(t *testing.T) { 20 | now := time.Now() 21 | have := FormatTimeString(now) 22 | want := now.Format(TimeFormatLayout) 23 | if have != want { 24 | t.Errorf("have:%s, want:%s", have, want) 25 | return 26 | } 27 | } 28 | 29 | func TestItoa(t *testing.T) { 30 | tests := []struct { 31 | buf []byte 32 | n int 33 | want []byte 34 | }{ 35 | { 36 | make([]byte, 2), 37 | 0, 38 | []byte{'0', '0'}, 39 | }, 40 | { 41 | make([]byte, 2), 42 | 1, 43 | []byte{'0', '1'}, 44 | }, 45 | { 46 | make([]byte, 2), 47 | 2, 48 | []byte{'0', '2'}, 49 | }, 50 | { 51 | make([]byte, 2), 52 | 11, 53 | []byte{'1', '1'}, 54 | }, 55 | { 56 | make([]byte, 2), 57 | 98, 58 | []byte{'9', '8'}, 59 | }, 60 | 61 | { 62 | make([]byte, 3), 63 | 0, 64 | []byte{'0', '0', '0'}, 65 | }, 66 | { 67 | make([]byte, 3), 68 | 6, 69 | []byte{'0', '0', '6'}, 70 | }, 71 | { 72 | make([]byte, 3), 73 | 67, 74 | []byte{'0', '6', '7'}, 75 | }, 76 | { 77 | make([]byte, 3), 78 | 567, 79 | []byte{'5', '6', '7'}, 80 | }, 81 | 82 | { 83 | make([]byte, 4), 84 | 0, 85 | []byte{'0', '0', '0', '0'}, 86 | }, 87 | { 88 | make([]byte, 4), 89 | 6, 90 | []byte{'0', '0', '0', '6'}, 91 | }, 92 | { 93 | make([]byte, 4), 94 | 67, 95 | []byte{'0', '0', '6', '7'}, 96 | }, 97 | { 98 | make([]byte, 4), 99 | 567, 100 | []byte{'0', '5', '6', '7'}, 101 | }, 102 | { 103 | make([]byte, 4), 104 | 4567, 105 | []byte{'4', '5', '6', '7'}, 106 | }, 107 | } 108 | 109 | for _, v := range tests { 110 | itoa(v.buf, v.n) 111 | if !bytes.Equal(v.buf, v.want) { 112 | t.Errorf("n=%d, have=%v, want=%v", v.n, v.buf, v.want) 113 | return 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | type loggerContextKey struct{} 9 | 10 | var _loggerContextKey loggerContextKey 11 | 12 | func NewContext(ctx context.Context, logger Logger) context.Context { 13 | if logger == nil { 14 | return ctx 15 | } 16 | if ctx == nil { 17 | return context.WithValue(context.Background(), _loggerContextKey, logger) 18 | } 19 | if value, ok := ctx.Value(_loggerContextKey).(Logger); ok && value == logger { 20 | return ctx 21 | } 22 | return context.WithValue(ctx, _loggerContextKey, logger) 23 | } 24 | 25 | func FromContext(ctx context.Context) (lg Logger, ok bool) { 26 | if ctx == nil { 27 | return nil, false 28 | } 29 | lg, ok = ctx.Value(_loggerContextKey).(Logger) 30 | return 31 | } 32 | 33 | func MustFromContext(ctx context.Context) Logger { 34 | lg, ok := FromContext(ctx) 35 | if !ok { 36 | panic("log: failed to get from context.Context") 37 | } 38 | return lg 39 | } 40 | 41 | func FromContextOrNew(ctx context.Context, new func() Logger) (lg Logger, ctx2 context.Context, isNew bool) { 42 | lg, ok := FromContext(ctx) 43 | if ok { 44 | return lg, ctx, false 45 | } 46 | if new != nil { 47 | lg = new() 48 | ctx2 = NewContext(ctx, lg) 49 | isNew = true 50 | return 51 | } 52 | lg = New() 53 | ctx2 = NewContext(ctx, lg) 54 | isNew = true 55 | return 56 | } 57 | 58 | func NewRequest(req *http.Request, logger Logger) *http.Request { 59 | if logger == nil { 60 | return req 61 | } 62 | ctx := req.Context() 63 | ctx2 := NewContext(ctx, logger) 64 | if ctx2 == ctx { 65 | return req 66 | } 67 | return req.WithContext(ctx2) 68 | } 69 | 70 | func FromRequest(req *http.Request) (lg Logger, ok bool) { 71 | return FromContext(req.Context()) 72 | } 73 | 74 | func MustFromRequest(req *http.Request) Logger { 75 | lg, ok := FromRequest(req) 76 | if !ok { 77 | panic("log: failed to get from http.Request") 78 | } 79 | return lg 80 | } 81 | 82 | func FromRequestOrNew(req *http.Request, new func() Logger) (lg Logger, req2 *http.Request, isNew bool) { 83 | lg, ok := FromRequest(req) 84 | if ok { 85 | return lg, req, false 86 | } 87 | if new != nil { 88 | lg = new() 89 | req2 = NewRequest(req, lg) 90 | isNew = true 91 | return 92 | } 93 | lg = New() 94 | req2 = NewRequest(req, lg) 95 | isNew = true 96 | return 97 | } 98 | -------------------------------------------------------------------------------- /uuid/uuid.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "crypto/sha1" 5 | 6 | v1 "github.com/KeKe-Li/log/uuid/v1" 7 | v2 "github.com/KeKe-Li/log/uuid/v2" 8 | ) 9 | 10 | // +------ 0 ------+------ 1 ------+------ 2 ------+------ 3 ------+ 11 | // +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+ 12 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | // | time_low | 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | // | time_mid | time_hi_and_version | 16 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | // |clk_seq_hi_res | clk_seq_low | node (0-1) | 18 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | // | node (2-5) | 20 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | 22 | type UUID [16]byte 23 | 24 | // NewV1 returns a STANDARD version 1 UUID. 25 | func NewV1() UUID { 26 | return v1.New() 27 | } 28 | 29 | // NewV5 returns a STANDARD version 5 UUID. 30 | func NewV5(ns UUID, name []byte) UUID { 31 | return v2.New(ns, name) 32 | } 33 | 34 | // NewV1x returns a NONSTANDARD UUID(lower probability of conflict). 35 | func NewV1x() UUID { 36 | return v1.Newx() 37 | } 38 | 39 | func (uuid UUID) Version() byte { 40 | return uuid[6] >> 4 41 | } 42 | 43 | func (uuid UUID) HexEncode() []byte { 44 | return HexEncode(uuid) 45 | } 46 | 47 | // Encode encodes UUID to "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format. 48 | func (uuid UUID) Encode() []byte { 49 | return Encode(uuid) 50 | } 51 | 52 | // String returns "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format. 53 | func (uuid UUID) String() string { 54 | return string(Encode(uuid)) 55 | } 56 | 57 | // Decode decodes data with "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" format into UUID. 58 | func (uuid *UUID) Decode(data []byte) (err error) { 59 | u, err := Decode(data) 60 | if err != nil { 61 | return 62 | } 63 | *uuid = u 64 | return 65 | } 66 | 67 | func New(ns [16]byte, name []byte) (uuid [16]byte) { 68 | h := sha1.New() 69 | h.Write(ns[:]) 70 | h.Write(name) 71 | copy(uuid[:], h.Sum(nil)) 72 | uuid[8] = (uuid[8] & 0x3f) | 0x80 // set variant rfc4122 73 | uuid[6] = (uuid[6] & 0x0f) | 5<<4 // set version 5 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /std.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "io" 4 | 5 | var _std = _New(nil) 6 | 7 | // Fatal logs a message at FatalLevel on the standard logger. 8 | // For more information see the Logger interface. 9 | func Fatal(msg string, fields ...interface{}) { 10 | _std.output(1, FatalLevel, msg, fields) 11 | } 12 | 13 | // Error logs a message at ErrorLevel on the standard logger. 14 | // For more information see the Logger interface. 15 | func Error(msg string, fields ...interface{}) { 16 | _std.output(1, ErrorLevel, msg, fields) 17 | } 18 | 19 | // Warn logs a message at WarnLevel on the standard logger. 20 | // For more information see the Logger interface. 21 | func Warn(msg string, fields ...interface{}) { 22 | _std.output(1, WarnLevel, msg, fields) 23 | } 24 | 25 | // Info logs a message at InfoLevel on the standard logger. 26 | // For more information see the Logger interface. 27 | func Info(msg string, fields ...interface{}) { 28 | _std.output(1, InfoLevel, msg, fields) 29 | } 30 | 31 | // Debug logs a message at DebugLevel on the standard logger. 32 | // For more information see the Logger interface. 33 | func Debug(msg string, fields ...interface{}) { 34 | _std.output(1, DebugLevel, msg, fields) 35 | } 36 | 37 | // Output logs a message at specified level on the standard logger. 38 | // For more information see the Logger interface. 39 | func Output(calldepth int, level Level, msg string, fields ...interface{}) { 40 | _std.Output(calldepth+1, level, msg, fields...) 41 | } 42 | 43 | // WithField creates a new Logger from the standard Logger and adds a field to it. 44 | // For more information see the Logger interface. 45 | func WithField(key string, value interface{}) Logger { 46 | return _std.WithField(key, value) 47 | } 48 | 49 | // WithFields creates a new Logger from the standard Logger and adds multiple fields to it. 50 | // For more information see the Logger interface. 51 | func WithFields(fields ...interface{}) Logger { 52 | return _std.WithFields(fields...) 53 | } 54 | 55 | // SetFormatter sets the standard logger formatter. 56 | func SetFormatter(formatter Formatter) { 57 | _std.SetFormatter(formatter) 58 | } 59 | 60 | // SetOutput sets the standard logger output. 61 | // NOTE: output must be thread-safe, see ConcurrentWriter. 62 | func SetOutput(output io.Writer) { 63 | _std.SetOutput(output) 64 | } 65 | 66 | // SetLevel sets the standard logger level. 67 | func SetLevel(level Level) error { 68 | return _std.SetLevel(level) 69 | } 70 | 71 | // SetLevelString sets the standard logger level. 72 | func SetLevelString(str string) error { 73 | return _std.SetLevelString(str) 74 | } 75 | -------------------------------------------------------------------------------- /uuid/v1/v1.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/KeKe-Li/log/uuid/rand" 7 | ) 8 | 9 | // +------ 0 ------+------ 1 ------+------ 2 ------+------ 3 ------+ 10 | // +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+ 11 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | // | time_low | 13 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | // | time_mid | time_hi_and_version | 15 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | // |clk_seq_hi_res | clk_seq_low | node (0-1) | 17 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | // | node (2-5) | 19 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | 21 | var node = rand.MAC[:] // read only. 22 | 23 | const sequenceMask uint32 = 0x3FFF // 14bits 24 | 25 | var ( 26 | gMutex sync.Mutex 27 | gSequenceStart uint32 = rand.Uint32() & sequenceMask 28 | gLastTimestamp int64 = -1 29 | gLastSequence uint32 = gSequenceStart 30 | ) 31 | 32 | // New returns a STANDARD version 1 UUID. 33 | func New() (uuid [16]byte) { 34 | var ( 35 | timestamp = uuidTimestamp() 36 | sequence uint32 37 | ) 38 | 39 | gMutex.Lock() // Lock 40 | switch { 41 | case timestamp > gLastTimestamp: 42 | sequence = gSequenceStart 43 | gLastTimestamp = timestamp 44 | gLastSequence = sequence 45 | gMutex.Unlock() // Unlock 46 | case timestamp == gLastTimestamp: 47 | sequence = (gLastSequence + 1) & sequenceMask 48 | if sequence == gSequenceStart { 49 | timestamp = tillNext100nano(timestamp) 50 | gLastTimestamp = timestamp 51 | } 52 | gLastSequence = sequence 53 | gMutex.Unlock() // Unlock 54 | default: // timestamp < lastTimestamp 55 | gSequenceStart = rand.Uint32() & sequenceMask // NOTE 56 | sequence = gSequenceStart 57 | gLastTimestamp = timestamp 58 | gLastSequence = sequence 59 | gMutex.Unlock() // Unlock 60 | } 61 | 62 | // time_low 63 | uuid[0] = byte(timestamp >> 24) 64 | uuid[1] = byte(timestamp >> 16) 65 | uuid[2] = byte(timestamp >> 8) 66 | uuid[3] = byte(timestamp) 67 | 68 | // time_mid 69 | uuid[4] = byte(timestamp >> 40) 70 | uuid[5] = byte(timestamp >> 32) 71 | 72 | // time_hi_and_version 73 | uuid[6] = byte(timestamp>>56) & 0x0F 74 | uuid[6] |= 0x10 // version 1, 4bits 75 | uuid[7] = byte(timestamp >> 48) 76 | 77 | // clk_seq_hi_res 78 | uuid[8] = byte(sequence>>8) & 0x3F 79 | uuid[8] |= 0x80 // variant, 2bits 80 | 81 | // clk_seq_low 82 | uuid[9] = byte(sequence) 83 | 84 | // node 85 | copy(uuid[10:], node) 86 | return 87 | } 88 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "sync/atomic" 6 | "unsafe" 7 | ) 8 | 9 | type Option func(*options) 10 | 11 | func WithTraceId(traceId string) Option { 12 | return func(o *options) { 13 | o.traceId = traceId 14 | } 15 | } 16 | 17 | func WithTraceIdFunc(fn func() string) Option { 18 | return func(o *options) { 19 | if fn == nil { 20 | return 21 | } 22 | o.traceId = fn() 23 | } 24 | } 25 | 26 | // WithOutput sets the logger output. 27 | // NOTE: output must be thread-safe, see ConcurrentWriter. 28 | func WithOutput(output io.Writer) Option { 29 | return func(o *options) { 30 | if output == nil { 31 | return 32 | } 33 | o.output = output 34 | } 35 | } 36 | 37 | func WithFormatter(formatter Formatter) Option { 38 | return func(o *options) { 39 | if formatter == nil { 40 | return 41 | } 42 | o.formatter = formatter 43 | } 44 | } 45 | 46 | func WithLevel(level Level) Option { 47 | return func(o *options) { 48 | if !isValidLevel(level) { 49 | return 50 | } 51 | o.level = level 52 | } 53 | } 54 | 55 | func WithLevelString(str string) Option { 56 | return func(o *options) { 57 | level, ok := parseLevelString(str) 58 | if !ok { 59 | return 60 | } 61 | o.level = level 62 | } 63 | } 64 | 65 | type options struct { 66 | traceId string 67 | formatter Formatter 68 | output io.Writer 69 | level Level 70 | } 71 | 72 | func (opts *options) SetFormatter(formatter Formatter) { 73 | if formatter == nil { 74 | return 75 | } 76 | opts.formatter = formatter 77 | } 78 | func (opts *options) SetOutput(output io.Writer) { 79 | if output == nil { 80 | return 81 | } 82 | opts.output = output 83 | } 84 | func (opts *options) SetLevel(level Level) { 85 | if !isValidLevel(level) { 86 | return 87 | } 88 | opts.level = level 89 | } 90 | 91 | func newOptions(opts []Option) *options { 92 | var o options 93 | for _, opt := range getDefaultOptions() { 94 | if opt == nil { 95 | continue 96 | } 97 | opt(&o) 98 | } 99 | for _, opt := range opts { 100 | if opt == nil { 101 | continue 102 | } 103 | opt(&o) 104 | } 105 | if o.formatter == nil { 106 | o.formatter = TextFormatter 107 | } 108 | if o.output == nil { 109 | o.output = ConcurrentStdout 110 | } 111 | if o.level == invalidLevel { 112 | o.level = DebugLevel 113 | } 114 | return &o 115 | } 116 | 117 | var _defaultOptionsPtr unsafe.Pointer // *[]Option 118 | 119 | func SetDefaultOptions(opts []Option) { 120 | if opts == nil { 121 | atomic.StorePointer(&_defaultOptionsPtr, nil) 122 | return 123 | } 124 | atomic.StorePointer(&_defaultOptionsPtr, unsafe.Pointer(&opts)) 125 | } 126 | 127 | func getDefaultOptions() []Option { 128 | ptr := (*[]Option)(atomic.LoadPointer(&_defaultOptionsPtr)) 129 | if ptr == nil { 130 | return nil 131 | } 132 | return *ptr 133 | } 134 | -------------------------------------------------------------------------------- /uuid/v1/v1x.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "os" 5 | "sync" 6 | 7 | "github.com/KeKe-Li/log/uuid/rand" 8 | ) 9 | 10 | // +------ 0 ------+------ 1 ------+------ 2 ------+------ 3 ------+ 11 | // +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+ 12 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | // | time_low | 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | // | time_mid | time_hi_and_pid_low | 16 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | // |clk_seq_hi_pid | clk_seq_low | node (0-1) | 18 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | // | node (2-5) | 20 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | 22 | var pid = byte(hash(uint64(os.Getpid()))) // 6-bit hash of os.Getpid(), read only. 23 | 24 | // hash uint64 to a 6-bit integer value. 25 | func hash(x uint64) uint64 { 26 | return (x ^ x>>6 ^ x>>12 ^ x>>18 ^ x>>24 ^ x>>30 ^ x>>36 ^ x>>42 ^ x>>48 ^ x>>54 ^ x>>60) & 0x3f 27 | } 28 | 29 | var xNode = MAC[:] // read only. 30 | 31 | const xSequenceMask uint32 = 0x3FFF // 14bits 32 | 33 | var ( 34 | gxMutex sync.Mutex 35 | gxSequenceStart uint32 = rand.Uint32() & xSequenceMask 36 | gxLastTimestamp int64 = -1 37 | gxLastSequence uint32 = gxSequenceStart 38 | ) 39 | 40 | // Newx returns a NONSTANDARD UUID(lower probability of conflict). 41 | func Newx() (uuid [16]byte) { 42 | var ( 43 | timestamp = uuidTimestamp() 44 | sequence uint32 45 | ) 46 | 47 | gxMutex.Lock() // Lock 48 | switch { 49 | case timestamp > gxLastTimestamp: 50 | sequence = gxSequenceStart 51 | gxLastTimestamp = timestamp 52 | gxLastSequence = sequence 53 | gxMutex.Unlock() // Unlock 54 | case timestamp == gxLastTimestamp: 55 | sequence = (gxLastSequence + 1) & xSequenceMask 56 | if sequence == gxSequenceStart { 57 | timestamp = tillNext100nano(timestamp) 58 | gxLastTimestamp = timestamp 59 | } 60 | gxLastSequence = sequence 61 | gxMutex.Unlock() // Unlock 62 | default: // timestamp < xLastTimestamp 63 | gxSequenceStart = rand.Uint32() & xSequenceMask // NOTE 64 | sequence = gxSequenceStart 65 | gxLastTimestamp = timestamp 66 | gxLastSequence = sequence 67 | gxMutex.Unlock() // Unlock 68 | } 69 | 70 | // time_low 71 | uuid[0] = byte(timestamp >> 24) 72 | uuid[1] = byte(timestamp >> 16) 73 | uuid[2] = byte(timestamp >> 8) 74 | uuid[3] = byte(timestamp) 75 | 76 | // time_mid 77 | uuid[4] = byte(timestamp >> 40) 78 | uuid[5] = byte(timestamp >> 32) 79 | 80 | // time_hi_and_pid_low 81 | uuid[6] = byte(timestamp >> 52) 82 | uuid[7] = byte(timestamp>>48) << 4 83 | uuid[7] |= pid & 0x0F // pid, 4bits 84 | 85 | // clk_seq_hi_pid 86 | uuid[8] = byte(sequence>>8) & 0x3F 87 | uuid[8] |= (pid << 2) & 0xC0 // // pid, 2bits 88 | 89 | // clk_seq_low 90 | uuid[9] = byte(sequence) 91 | 92 | // node 93 | copy(uuid[10:], xNode) 94 | return 95 | } 96 | -------------------------------------------------------------------------------- /text_formatter.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "sort" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | var TextFormatter Formatter = textFormatter{} 13 | 14 | type textFormatter struct{} 15 | 16 | func (f textFormatter) Format(entry *Entry) ([]byte, error) { 17 | var buffer *bytes.Buffer 18 | if entry.Buffer != nil { 19 | buffer = entry.Buffer 20 | } else { 21 | buffer = bytes.NewBuffer(make([]byte, 0, 16<<10)) 22 | } 23 | f.appendKeyValue(buffer, fieldKeyTime, FormatTimeString(entry.Time.In(_beijingLocation))) 24 | f.appendKeyValue(buffer, fieldKeyLevel, entry.Level.String()) 25 | f.appendKeyValue(buffer, fieldKeyTraceId, entry.TraceId) 26 | f.appendKeyValue(buffer, fieldKeyLocation, entry.Location) 27 | f.appendKeyValue(buffer, fieldKeyMessage, entry.Message) 28 | if fields := entry.Fields; len(fields) > 0 { 29 | prefixFieldClashes(fields) 30 | keys := make([]string, 0, len(fields)) 31 | for k := range fields { 32 | keys = append(keys, k) 33 | } 34 | sort.Strings(keys) 35 | for _, k := range keys { 36 | v := fields[k] 37 | f.appendKeyValue(buffer, k, v) 38 | } 39 | } 40 | buffer.WriteByte('\n') 41 | return buffer.Bytes(), nil 42 | } 43 | 44 | func (f textFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { 45 | if b.Len() > 0 { 46 | b.WriteString(", ") 47 | } 48 | b.WriteString(key) 49 | b.WriteByte('=') 50 | f.appendValue(b, value) 51 | } 52 | 53 | func (f textFormatter) appendValue(b *bytes.Buffer, value interface{}) { 54 | var stringVal string 55 | switch v := value.(type) { 56 | case string: 57 | stringVal = v 58 | case json.RawMessage: 59 | stringVal = string(v) 60 | default: 61 | stringVal = fmt.Sprint(value) 62 | } 63 | b.WriteString(stringVal) 64 | } 65 | 66 | var _beijingLocation = time.FixedZone("Asia/Shanghai", 8*60*60) 67 | 68 | const ( 69 | fieldKeyTime = "time" 70 | fieldKeyLevel = "level" 71 | fieldKeyTraceId = "request_id" 72 | fieldKeyLocation = "location" 73 | fieldKeyMessage = "msg" 74 | ) 75 | 76 | func prefixFieldClashes(data map[string]interface{}) { 77 | if v, ok := data[fieldKeyTime]; ok { 78 | delete(data, fieldKeyTime) 79 | newKey := "fields." + fieldKeyTime 80 | for key, i := newKey, 2; ; i++ { 81 | _, ok = data[key] 82 | if !ok { 83 | data[key] = v 84 | break 85 | } 86 | key = newKey + "." + strconv.Itoa(i) 87 | } 88 | } 89 | if v, ok := data[fieldKeyLevel]; ok { 90 | delete(data, fieldKeyLevel) 91 | newKey := "fields." + fieldKeyLevel 92 | for key, i := newKey, 2; ; i++ { 93 | _, ok = data[key] 94 | if !ok { 95 | data[key] = v 96 | break 97 | } 98 | key = newKey + "." + strconv.Itoa(i) 99 | } 100 | } 101 | if v, ok := data[fieldKeyTraceId]; ok { 102 | delete(data, fieldKeyTraceId) 103 | newKey := "fields." + fieldKeyTraceId 104 | for key, i := newKey, 2; ; i++ { 105 | _, ok = data[key] 106 | if !ok { 107 | data[key] = v 108 | break 109 | } 110 | key = newKey + "." + strconv.Itoa(i) 111 | } 112 | } 113 | if v, ok := data[fieldKeyLocation]; ok { 114 | delete(data, fieldKeyLocation) 115 | newKey := "fields." + fieldKeyLocation 116 | for key, i := newKey, 2; ; i++ { 117 | _, ok = data[key] 118 | if !ok { 119 | data[key] = v 120 | break 121 | } 122 | key = newKey + "." + strconv.Itoa(i) 123 | } 124 | } 125 | if v, ok := data[fieldKeyMessage]; ok { 126 | delete(data, fieldKeyMessage) 127 | newKey := "fields." + fieldKeyMessage 128 | for key, i := newKey, 2; ; i++ { 129 | _, ok = data[key] 130 | if !ok { 131 | data[key] = v 132 | break 133 | } 134 | key = newKey + "." + strconv.Itoa(i) 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /shortcut.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // FatalContext is a shortcut to the following code: 8 | // lg, ok := FromContext(ctx) 9 | // if ok { 10 | // lg.Output(1, FatalLevel, msg, fields...) 11 | // return 12 | // } 13 | // Output(1, FatalLevel, msg, fields...) 14 | func FatalContext(ctx context.Context, msg string, fields ...interface{}) { 15 | lg, ok := FromContext(ctx) 16 | if ok { 17 | lg.Output(1, FatalLevel, msg, fields...) 18 | return 19 | } 20 | Output(1, FatalLevel, msg, fields...) 21 | } 22 | 23 | // ErrorContext is a shortcut to the following code: 24 | // lg, ok := FromContext(ctx) 25 | // if ok { 26 | // lg.Output(1, ErrorLevel, msg, fields...) 27 | // return 28 | // } 29 | // Output(1, ErrorLevel, msg, fields...) 30 | func ErrorContext(ctx context.Context, msg string, fields ...interface{}) { 31 | lg, ok := FromContext(ctx) 32 | if ok { 33 | lg.Output(1, ErrorLevel, msg, fields...) 34 | return 35 | } 36 | Output(1, ErrorLevel, msg, fields...) 37 | } 38 | 39 | // WarnContext is a shortcut to the following code: 40 | // lg, ok := FromContext(ctx) 41 | // if ok { 42 | // lg.Output(1, WarnLevel, msg, fields...) 43 | // return 44 | // } 45 | // Output(1, WarnLevel, msg, fields...) 46 | func WarnContext(ctx context.Context, msg string, fields ...interface{}) { 47 | lg, ok := FromContext(ctx) 48 | if ok { 49 | lg.Output(1, WarnLevel, msg, fields...) 50 | return 51 | } 52 | Output(1, WarnLevel, msg, fields...) 53 | } 54 | 55 | // InfoContext is a shortcut to the following code: 56 | // lg, ok := FromContext(ctx) 57 | // if ok { 58 | // lg.Output(1, InfoLevel, msg, fields...) 59 | // return 60 | // } 61 | // Output(1, InfoLevel, msg, fields...) 62 | func InfoContext(ctx context.Context, msg string, fields ...interface{}) { 63 | lg, ok := FromContext(ctx) 64 | if ok { 65 | lg.Output(1, InfoLevel, msg, fields...) 66 | return 67 | } 68 | Output(1, InfoLevel, msg, fields...) 69 | } 70 | 71 | // DebugContext is a shortcut to the following code: 72 | // lg, ok := FromContext(ctx) 73 | // if ok { 74 | // lg.Output(1, DebugLevel, msg, fields...) 75 | // return 76 | // } 77 | // Output(1, DebugLevel, msg, fields...) 78 | func DebugContext(ctx context.Context, msg string, fields ...interface{}) { 79 | lg, ok := FromContext(ctx) 80 | if ok { 81 | lg.Output(1, DebugLevel, msg, fields...) 82 | return 83 | } 84 | Output(1, DebugLevel, msg, fields...) 85 | } 86 | 87 | // OutputContext is a shortcut to the following code: 88 | // lg, ok := FromContext(ctx) 89 | // if ok { 90 | // lg.Output(calldepth+1, level, msg, fields...) 91 | // return 92 | // } 93 | // Output(calldepth+1, level, msg, fields...) 94 | func OutputContext(ctx context.Context, calldepth int, level Level, msg string, fields ...interface{}) { 95 | lg, ok := FromContext(ctx) 96 | if ok { 97 | lg.Output(calldepth+1, level, msg, fields...) 98 | return 99 | } 100 | Output(calldepth+1, level, msg, fields...) 101 | } 102 | 103 | // WithFieldContext is a shortcut to the following code: 104 | // lg, ok := FromContext(ctx) 105 | // if ok { 106 | // return lg.WithField(key, value) 107 | // } 108 | // return WithField(key, value) 109 | func WithFieldContext(ctx context.Context, key string, value interface{}) Logger { 110 | lg, ok := FromContext(ctx) 111 | if ok { 112 | return lg.WithField(key, value) 113 | } 114 | return WithField(key, value) 115 | } 116 | 117 | // WithFieldsContext is a shortcut to the following code: 118 | // lg, ok := FromContext(ctx) 119 | // if ok { 120 | // return lg.WithFields(fields...) 121 | // } 122 | // return WithFields(fields...) 123 | func WithFieldsContext(ctx context.Context, fields ...interface{}) Logger { 124 | lg, ok := FromContext(ctx) 125 | if ok { 126 | return lg.WithFields(fields...) 127 | } 128 | return WithFields(fields...) 129 | } 130 | -------------------------------------------------------------------------------- /buffer_pool_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "runtime/debug" 6 | "testing" 7 | ) 8 | 9 | func TestDefaultBytesBufferPool(t *testing.T) { 10 | debug.SetGCPercent(-1) 11 | defer debug.SetGCPercent(100) 12 | 13 | // new 14 | func() { 15 | pool := getBytesBufferPool() 16 | 17 | buffer := pool.Get() 18 | defer pool.Put(buffer) 19 | 20 | have := buffer.String() 21 | want := "" 22 | if have != want { 23 | t.Errorf("have:%s, want:%s", have, want) 24 | return 25 | } 26 | }() 27 | 28 | // clean up 29 | { 30 | pool := getBytesBufferPool() 31 | for i := 0; i < 10; i++ { 32 | pool.Get() 33 | } 34 | } 35 | 36 | // new twice 37 | func() { 38 | pool := getBytesBufferPool() 39 | 40 | buffer := pool.Get() 41 | defer pool.Put(buffer) 42 | 43 | have := buffer.String() 44 | want := "" 45 | if have != want { 46 | t.Errorf("have:%s, want:%s", have, want) 47 | return 48 | } 49 | buffer.WriteString("buffer") 50 | 51 | buffer2 := pool.Get() 52 | defer pool.Put(buffer2) 53 | 54 | have = buffer2.String() 55 | want = "" 56 | if have != want { 57 | t.Errorf("have:%s, want:%s", have, want) 58 | return 59 | } 60 | }() 61 | 62 | // clean up 63 | { 64 | pool := getBytesBufferPool() 65 | for i := 0; i < 10; i++ { 66 | pool.Get() 67 | } 68 | } 69 | 70 | // reuse 71 | func() { 72 | func() { 73 | pool := getBytesBufferPool() 74 | 75 | buffer := pool.Get() 76 | defer pool.Put(buffer) 77 | 78 | have := buffer.String() 79 | want := "" 80 | if have != want { 81 | t.Errorf("have:%s, want:%s", have, want) 82 | return 83 | } 84 | buffer.WriteString("buffer") 85 | }() 86 | 87 | func() { 88 | pool := getBytesBufferPool() 89 | 90 | buffer := pool.Get() 91 | defer pool.Put(buffer) 92 | 93 | have := buffer.String() 94 | want := "buffer" 95 | if have != want { 96 | t.Errorf("have:%s, want:%s", have, want) 97 | return 98 | } 99 | }() 100 | }() 101 | 102 | // clean up 103 | { 104 | pool := getBytesBufferPool() 105 | for i := 0; i < 10; i++ { 106 | pool.Get() 107 | } 108 | } 109 | 110 | // reuse and put nil 111 | func() { 112 | func() { 113 | pool := getBytesBufferPool() 114 | 115 | buffer := pool.Get() 116 | defer pool.Put(buffer) 117 | 118 | have := buffer.String() 119 | want := "" 120 | if have != want { 121 | t.Errorf("have:%s, want:%s", have, want) 122 | return 123 | } 124 | buffer.WriteString("buffer") 125 | }() 126 | 127 | pool := getBytesBufferPool() 128 | for i := 0; i < 10; i++ { 129 | pool.Put(nil) 130 | } 131 | 132 | func() { 133 | pool := getBytesBufferPool() 134 | 135 | buffer := pool.Get() 136 | defer pool.Put(buffer) 137 | 138 | have := buffer.String() 139 | want := "buffer" 140 | if have != want { 141 | t.Errorf("have:%s, want:%s", have, want) 142 | return 143 | } 144 | }() 145 | }() 146 | } 147 | 148 | func Test_SetBytesBufferPool_GetBytesBufferPool(t *testing.T) { 149 | defer SetBytesBufferPool(_defaultBytesBufferPool) 150 | 151 | // get default BytesBufferPool 152 | pool := getBytesBufferPool() 153 | if _, ok := pool.(*bytesBufferPool); !ok { 154 | t.Error("want type *bytesBufferPool") 155 | return 156 | } 157 | 158 | // SetBytesBufferPool with nil 159 | pool = nil 160 | SetBytesBufferPool(pool) 161 | pool = getBytesBufferPool() 162 | if _, ok := pool.(*bytesBufferPool); !ok { 163 | t.Error("want type *bytesBufferPool") 164 | return 165 | } 166 | 167 | // SetBytesBufferPool with non-nil 168 | SetBytesBufferPool(&testBytesBufferPool{}) 169 | pool = getBytesBufferPool() 170 | if _, ok := pool.(*testBytesBufferPool); !ok { 171 | t.Error("want type *testBytesBufferPool") 172 | return 173 | } 174 | 175 | // SetBytesBufferPool with nil 176 | pool = nil 177 | SetBytesBufferPool(pool) 178 | pool = getBytesBufferPool() 179 | if _, ok := pool.(*testBytesBufferPool); !ok { 180 | t.Error("want type *testBytesBufferPool") 181 | return 182 | } 183 | } 184 | 185 | type testBytesBufferPool struct{} 186 | 187 | func (*testBytesBufferPool) Get() *bytes.Buffer { return nil } 188 | func (*testBytesBufferPool) Put(x *bytes.Buffer) {} 189 | -------------------------------------------------------------------------------- /fields_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestCombineFields(t *testing.T) { 9 | // empty fields 10 | { 11 | old := map[string]interface{}{ 12 | "ka": "va", 13 | "kb": "vb", 14 | } 15 | 16 | have, err := combineFields(old, nil) 17 | if err != nil { 18 | t.Error("want nil") 19 | return 20 | } 21 | if !reflect.DeepEqual(have, old) { 22 | t.Errorf("have:%v, old:%v", have, old) 23 | return 24 | } 25 | 26 | // check if it is a new one 27 | for k := range old { 28 | delete(old, k) 29 | } 30 | if len(have) == len(old) { 31 | t.Errorf("want not equal, have:%v, old:%v", have, old) 32 | return 33 | } 34 | } 35 | // ddd number of field 36 | { 37 | old := map[string]interface{}{ 38 | "ka": "va", 39 | "kb": "vb", 40 | } 41 | 42 | have, err := combineFields(old, []interface{}{"kc", "vc", "kd"}) 43 | if err != _ErrNumberOfFieldsMustNotBeOdd { 44 | t.Error("want equal") 45 | return 46 | } 47 | if !reflect.DeepEqual(have, old) { 48 | t.Errorf("have:%v, old:%v", have, old) 49 | return 50 | } 51 | 52 | // check if it is a new one 53 | for k := range old { 54 | delete(old, k) 55 | } 56 | if len(have) == len(old) { 57 | t.Errorf("want not equal, have:%v, old:%v", have, old) 58 | return 59 | } 60 | } 61 | // nil old 62 | { 63 | have, err := combineFields(nil, []interface{}{"kc", "vc", "kb", "vb2"}) 64 | if err != nil { 65 | t.Error("want nil") 66 | return 67 | } 68 | want := map[string]interface{}{ 69 | "kb": "vb2", 70 | "kc": "vc", 71 | } 72 | if !reflect.DeepEqual(have, want) { 73 | t.Errorf("have:%v, want:%v", have, want) 74 | return 75 | } 76 | } 77 | // non-nil old 78 | { 79 | old := map[string]interface{}{ 80 | "ka": "va", 81 | "kb": "vb", 82 | } 83 | 84 | have, err := combineFields(old, []interface{}{"kc", "vc", "kb", "vb2"}) 85 | if err != nil { 86 | t.Error("want nil") 87 | return 88 | } 89 | want := map[string]interface{}{ 90 | "ka": "va", 91 | "kb": "vb2", 92 | "kc": "vc", 93 | } 94 | if !reflect.DeepEqual(have, want) { 95 | t.Errorf("have:%v, want:%v", have, want) 96 | return 97 | } 98 | 99 | // check if it is a new one 100 | if reflect.DeepEqual(have, old) { 101 | t.Errorf("want not equal, have:%v, old:%v", have, old) 102 | return 103 | } 104 | } 105 | // non-nil old with non-string type of key 106 | { 107 | old := map[string]interface{}{ 108 | "ka": "va", 109 | "kb": "vb", 110 | } 111 | 112 | have, err := combineFields(old, []interface{}{"kc", "vc", "kb", "vb2", 1, 2, "kd", "vd"}) 113 | if err != _ErrTypeOfFieldKeyMustBeString { 114 | t.Error("want equal") 115 | return 116 | } 117 | want := map[string]interface{}{ 118 | "ka": "va", 119 | "kb": "vb2", 120 | "kc": "vc", 121 | } 122 | if !reflect.DeepEqual(have, want) { 123 | t.Errorf("have:%v, want:%v", have, want) 124 | return 125 | } 126 | 127 | // check if it is a new one 128 | if reflect.DeepEqual(have, old) { 129 | t.Errorf("want not equal, have:%v, old:%v", have, old) 130 | return 131 | } 132 | } 133 | // non-nil old with empty key 134 | { 135 | old := map[string]interface{}{ 136 | "ka": "va", 137 | "kb": "vb", 138 | } 139 | 140 | have, err := combineFields(old, []interface{}{"kc", "vc", "kb", "vb2", "", "vd", "ke", "ve"}) 141 | if err != _ErrFieldKeyMustNotBeEmpty { 142 | t.Error("want equal") 143 | return 144 | } 145 | want := map[string]interface{}{ 146 | "ka": "va", 147 | "kb": "vb2", 148 | "kc": "vc", 149 | } 150 | if !reflect.DeepEqual(have, want) { 151 | t.Errorf("have:%v, want:%v", have, want) 152 | return 153 | } 154 | 155 | // check if it is a new one 156 | if reflect.DeepEqual(have, old) { 157 | t.Errorf("want not equal, have:%v, old:%v", have, old) 158 | return 159 | } 160 | } 161 | } 162 | 163 | func TestCloneFields(t *testing.T) { 164 | // nil source 165 | { 166 | var m map[string]interface{} 167 | m2 := cloneFields(m) 168 | if m2 != nil { 169 | t.Error("want nil") 170 | return 171 | } 172 | } 173 | // non-nil source 174 | { 175 | m := map[string]interface{}{ 176 | "a": "va", 177 | "b": "vb", 178 | } 179 | m2 := cloneFields(m) 180 | if !reflect.DeepEqual(m, m2) { 181 | t.Errorf("m2:%v, m:%v", m2, m) 182 | return 183 | } 184 | // check if it is a new one 185 | for k := range m { 186 | delete(m, k) 187 | } 188 | if len(m2) == len(m) { 189 | t.Errorf("want not equal, m2:%v, m:%v", m2, m) 190 | return 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /level_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsValidLevel(t *testing.T) { 8 | tests := []struct { 9 | level Level 10 | want bool 11 | }{ 12 | { 13 | FatalLevel, 14 | true, 15 | }, 16 | { 17 | ErrorLevel, 18 | true, 19 | }, 20 | { 21 | WarnLevel, 22 | true, 23 | }, 24 | { 25 | InfoLevel, 26 | true, 27 | }, 28 | { 29 | DebugLevel, 30 | true, 31 | }, 32 | { 33 | invalidLevel, 34 | false, 35 | }, 36 | { 37 | 0, 38 | false, 39 | }, 40 | { 41 | 100, 42 | false, 43 | }, 44 | { 45 | 6, 46 | false, 47 | }, 48 | } 49 | for _, v := range tests { 50 | have := isValidLevel(v.level) 51 | if have != v.want { 52 | t.Errorf("level:%v, have:%t, want:%t", v.level, have, v.want) 53 | return 54 | } 55 | } 56 | } 57 | 58 | func TestIsLevelEnabled(t *testing.T) { 59 | tests := []struct { 60 | level Level 61 | loggerLevel Level 62 | want bool 63 | }{ 64 | // level is fatal 65 | { 66 | FatalLevel, 67 | FatalLevel, 68 | true, 69 | }, 70 | { 71 | FatalLevel, 72 | ErrorLevel, 73 | true, 74 | }, 75 | { 76 | FatalLevel, 77 | WarnLevel, 78 | true, 79 | }, 80 | { 81 | FatalLevel, 82 | InfoLevel, 83 | true, 84 | }, 85 | { 86 | FatalLevel, 87 | DebugLevel, 88 | true, 89 | }, 90 | 91 | // level is error 92 | { 93 | ErrorLevel, 94 | FatalLevel, 95 | false, 96 | }, 97 | { 98 | ErrorLevel, 99 | ErrorLevel, 100 | true, 101 | }, 102 | { 103 | ErrorLevel, 104 | WarnLevel, 105 | true, 106 | }, 107 | { 108 | ErrorLevel, 109 | InfoLevel, 110 | true, 111 | }, 112 | { 113 | ErrorLevel, 114 | DebugLevel, 115 | true, 116 | }, 117 | 118 | // level is warning 119 | { 120 | WarnLevel, 121 | FatalLevel, 122 | false, 123 | }, 124 | { 125 | WarnLevel, 126 | ErrorLevel, 127 | false, 128 | }, 129 | { 130 | WarnLevel, 131 | WarnLevel, 132 | true, 133 | }, 134 | { 135 | WarnLevel, 136 | InfoLevel, 137 | true, 138 | }, 139 | { 140 | WarnLevel, 141 | DebugLevel, 142 | true, 143 | }, 144 | 145 | // level is info 146 | { 147 | InfoLevel, 148 | FatalLevel, 149 | false, 150 | }, 151 | { 152 | InfoLevel, 153 | ErrorLevel, 154 | false, 155 | }, 156 | { 157 | InfoLevel, 158 | WarnLevel, 159 | false, 160 | }, 161 | { 162 | InfoLevel, 163 | InfoLevel, 164 | true, 165 | }, 166 | { 167 | InfoLevel, 168 | DebugLevel, 169 | true, 170 | }, 171 | 172 | // level is debug 173 | { 174 | DebugLevel, 175 | FatalLevel, 176 | false, 177 | }, 178 | { 179 | DebugLevel, 180 | ErrorLevel, 181 | false, 182 | }, 183 | { 184 | DebugLevel, 185 | WarnLevel, 186 | false, 187 | }, 188 | { 189 | DebugLevel, 190 | InfoLevel, 191 | false, 192 | }, 193 | { 194 | DebugLevel, 195 | DebugLevel, 196 | true, 197 | }, 198 | } 199 | for _, v := range tests { 200 | have := isLevelEnabled(v.level, v.loggerLevel) 201 | if have != v.want { 202 | t.Errorf("level:%v, loggerLevel:%v, have:%t, want:%t", v.level, v.loggerLevel, have, v.want) 203 | return 204 | } 205 | } 206 | } 207 | 208 | func TestParseLevelString(t *testing.T) { 209 | tests := []struct { 210 | str string 211 | level Level 212 | ok bool 213 | }{ 214 | { 215 | "fatal", 216 | FatalLevel, 217 | true, 218 | }, 219 | { 220 | "error", 221 | ErrorLevel, 222 | true, 223 | }, 224 | { 225 | "warning", 226 | WarnLevel, 227 | true, 228 | }, 229 | { 230 | "info", 231 | InfoLevel, 232 | true, 233 | }, 234 | { 235 | "debug", 236 | DebugLevel, 237 | true, 238 | }, 239 | { 240 | "trace", 241 | invalidLevel, 242 | false, 243 | }, 244 | { 245 | "panic", 246 | invalidLevel, 247 | false, 248 | }, 249 | { 250 | "", 251 | invalidLevel, 252 | false, 253 | }, 254 | } 255 | for _, v := range tests { 256 | level, ok := parseLevelString(v.str) 257 | if level != v.level || ok != v.ok { 258 | t.Errorf("str:%s, have:(%d, %t), want:(%d, %t)", v.str, level, ok, v.level, v.ok) 259 | return 260 | } 261 | } 262 | } 263 | 264 | func TestLevel_String(t *testing.T) { 265 | tests := []struct { 266 | level Level 267 | str string 268 | }{ 269 | { 270 | FatalLevel, 271 | "fatal", 272 | }, 273 | { 274 | ErrorLevel, 275 | "error", 276 | }, 277 | { 278 | WarnLevel, 279 | "warning", 280 | }, 281 | { 282 | InfoLevel, 283 | "info", 284 | }, 285 | { 286 | DebugLevel, 287 | "debug", 288 | }, 289 | } 290 | for _, v := range tests { 291 | str := v.level.String() 292 | if str != v.str { 293 | t.Errorf("level:%v, have:%s, want:%s", v.level, str, v.str) 294 | return 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "sync" 8 | "sync/atomic" 9 | "time" 10 | "unsafe" 11 | ) 12 | 13 | type Logger interface { 14 | // Fatal logs a message at FatalLevel. 15 | // 16 | // Unlike other golang log libraries (for example, the golang standard log library), 17 | // Fatal just logs a message and does not call os.Exit, so you need to explicitly call os.Exit if necessary. 18 | // 19 | // For fields, the following conditions must be satisfied 20 | // 1. the len(fields) must be an even number, that is to say len(fields)%2==0 21 | // 2. the even index element of fields must be non-empty string 22 | Fatal(msg string, fields ...interface{}) 23 | 24 | // Error logs a message at ErrorLevel. 25 | // The requirements for fields can see the comments of Fatal. 26 | Error(msg string, fields ...interface{}) 27 | 28 | // Warn logs a message at WarnLevel. 29 | // The requirements for fields can see the comments of Fatal. 30 | Warn(msg string, fields ...interface{}) 31 | 32 | // Info logs a message at InfoLevel. 33 | // The requirements for fields can see the comments of Fatal. 34 | Info(msg string, fields ...interface{}) 35 | 36 | // Debug logs a message at DebugLevel. 37 | // The requirements for fields can see the comments of Fatal. 38 | Debug(msg string, fields ...interface{}) 39 | 40 | // Output logs a message at specified level. 41 | // 42 | // For level==FatalLevel, unlike other golang log libraries (for example, the golang standard log library), 43 | // Output just logs a message and does not call os.Exit, so you need to explicitly call os.Exit if necessary. 44 | // 45 | // The requirements for fields can see the comments of Fatal. 46 | Output(calldepth int, level Level, msg string, fields ...interface{}) 47 | 48 | // WithField creates a new Logger from the current Logger and adds a field to it. 49 | WithField(key string, value interface{}) Logger 50 | 51 | // WithFields creates a new Logger from the current Logger and adds multiple fields to it. 52 | // The requirements for fields can see the comments of Fatal. 53 | WithFields(fields ...interface{}) Logger 54 | 55 | // SetFormatter sets the logger formatter. 56 | SetFormatter(Formatter) 57 | 58 | // SetOutput sets the logger output. 59 | SetOutput(io.Writer) 60 | 61 | // SetLevel sets the logger level. 62 | SetLevel(Level) error 63 | 64 | // SetLevelString sets the logger level. 65 | SetLevelString(string) error 66 | } 67 | 68 | type Formatter interface { 69 | Format(*Entry) ([]byte, error) 70 | } 71 | 72 | type Entry struct { 73 | Location string // function(file:line) 74 | Time time.Time 75 | Level Level 76 | TraceId string 77 | Message string 78 | Fields map[string]interface{} 79 | Buffer *bytes.Buffer 80 | } 81 | 82 | func New(opts ...Option) Logger { return _New(opts) } 83 | 84 | func _New(opts []Option) *logger { 85 | l := &logger{} 86 | l.setOptions(newOptions(opts)) 87 | return l 88 | } 89 | 90 | type logger struct { 91 | mu sync.Mutex // protects the following options field 92 | options unsafe.Pointer // *options 93 | 94 | fields map[string]interface{} 95 | } 96 | 97 | func (l *logger) getOptions() (opts *options) { 98 | return (*options)(atomic.LoadPointer(&l.options)) 99 | } 100 | func (l *logger) setOptions(opts *options) { 101 | atomic.StorePointer(&l.options, unsafe.Pointer(opts)) 102 | } 103 | 104 | func (l *logger) SetFormatter(formatter Formatter) { 105 | if formatter == nil { 106 | return 107 | } 108 | l.mu.Lock() 109 | defer l.mu.Unlock() 110 | 111 | opts := *l.getOptions() 112 | opts.SetFormatter(formatter) 113 | l.setOptions(&opts) 114 | } 115 | func (l *logger) SetOutput(output io.Writer) { 116 | if output == nil { 117 | return 118 | } 119 | l.mu.Lock() 120 | defer l.mu.Unlock() 121 | 122 | opts := *l.getOptions() 123 | opts.SetOutput(output) 124 | l.setOptions(&opts) 125 | } 126 | func (l *logger) SetLevel(level Level) error { 127 | if !isValidLevel(level) { 128 | return fmt.Errorf("invalid level: %d", level) 129 | } 130 | l.setLevel(level) 131 | return nil 132 | } 133 | func (l *logger) SetLevelString(str string) error { 134 | level, ok := parseLevelString(str) 135 | if !ok { 136 | return fmt.Errorf("invalid level string: %q", str) 137 | } 138 | l.setLevel(level) 139 | return nil 140 | } 141 | func (l *logger) setLevel(level Level) { 142 | l.mu.Lock() 143 | defer l.mu.Unlock() 144 | 145 | opts := *l.getOptions() 146 | opts.SetLevel(level) 147 | l.setOptions(&opts) 148 | } 149 | 150 | func (l *logger) Fatal(msg string, fields ...interface{}) { 151 | l.output(1, FatalLevel, msg, fields) 152 | } 153 | func (l *logger) Error(msg string, fields ...interface{}) { 154 | l.output(1, ErrorLevel, msg, fields) 155 | } 156 | func (l *logger) Warn(msg string, fields ...interface{}) { 157 | l.output(1, WarnLevel, msg, fields) 158 | } 159 | func (l *logger) Info(msg string, fields ...interface{}) { 160 | l.output(1, InfoLevel, msg, fields) 161 | } 162 | func (l *logger) Debug(msg string, fields ...interface{}) { 163 | l.output(1, DebugLevel, msg, fields) 164 | } 165 | 166 | func (l *logger) Output(calldepth int, level Level, msg string, fields ...interface{}) { 167 | if !isValidLevel(level) { 168 | return 169 | } 170 | if calldepth < 0 { 171 | calldepth = 0 172 | } 173 | l.output(calldepth+1, level, msg, fields) 174 | } 175 | 176 | func (l *logger) output(calldepth int, level Level, msg string, fields []interface{}) { 177 | opts := l.getOptions() 178 | if !isLevelEnabled(level, opts.level) { 179 | return 180 | } 181 | location := callerLocation(calldepth + 1) 182 | 183 | combinedFields, err := combineFields(l.fields, fields) 184 | if err != nil { 185 | fmt.Fprintf(ConcurrentStderr, "log: failed to combine fields, error=%v, location=%s\n", err, location) 186 | } 187 | 188 | pool := getBytesBufferPool() 189 | buffer := pool.Get() 190 | defer pool.Put(buffer) 191 | buffer.Reset() 192 | 193 | data, err := opts.formatter.Format(&Entry{ 194 | Location: location, 195 | Time: time.Now(), 196 | Level: level, 197 | TraceId: opts.traceId, 198 | Message: msg, 199 | Fields: combinedFields, 200 | Buffer: buffer, 201 | }) 202 | if err != nil { 203 | fmt.Fprintf(ConcurrentStderr, "log: failed to format Entry, error=%v, location=%s\n", err, location) 204 | return 205 | } 206 | if _, err = opts.output.Write(data); err != nil { 207 | fmt.Fprintf(ConcurrentStderr, "log: failed to write to log, error=%v, location=%s\n", err, location) 208 | return 209 | } 210 | } 211 | 212 | func (l *logger) WithField(key string, value interface{}) Logger { 213 | if key == "" { 214 | return l 215 | } 216 | if len(l.fields) == 0 { 217 | nl := &logger{ 218 | fields: map[string]interface{}{key: value}, 219 | } 220 | nl.setOptions(l.getOptions()) 221 | return nl 222 | } 223 | m := make(map[string]interface{}, len(l.fields)+1) 224 | for k, v := range l.fields { 225 | m[k] = v 226 | } 227 | m[key] = value 228 | nl := &logger{ 229 | fields: m, 230 | } 231 | nl.setOptions(l.getOptions()) 232 | return nl 233 | } 234 | 235 | func (l *logger) WithFields(fields ...interface{}) Logger { 236 | if len(fields) == 0 { 237 | return l 238 | } 239 | m, err := combineFields(l.fields, fields) 240 | if err != nil { 241 | fmt.Fprintf(ConcurrentStderr, "log: failed to combine fields, error=%v, location=%s\n", err, callerLocation(1)) 242 | } 243 | nl := &logger{ 244 | fields: m, 245 | } 246 | nl.setOptions(l.getOptions()) 247 | return nl 248 | } 249 | -------------------------------------------------------------------------------- /trace/trace_test.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func Test_NewContext_FromContext(t *testing.T) { 10 | // no called NewContext yet 11 | { 12 | ctx := context.Background() 13 | 14 | id, ok := FromContext(ctx) 15 | wantId, wantOk := "", false 16 | if id != wantId || ok != wantOk { 17 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 18 | return 19 | } 20 | } 21 | 22 | // called NewContext with empty traceId 23 | { 24 | ctx := context.Background() 25 | 26 | traceId := "" 27 | ctx = NewContext(ctx, traceId) 28 | id, ok := FromContext(ctx) 29 | wantId, wantOk := "", false 30 | if id != wantId || ok != wantOk { 31 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 32 | return 33 | } 34 | } 35 | 36 | // called NewContext with non-empty traceId 37 | { 38 | ctx := context.Background() 39 | 40 | traceId := "123456789" 41 | ctx = NewContext(ctx, traceId) 42 | id, ok := FromContext(ctx) 43 | wantId, wantOk := traceId, true 44 | if id != wantId || ok != wantOk { 45 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 46 | return 47 | } 48 | } 49 | } 50 | 51 | func TestNewContext(t *testing.T) { 52 | // empty traceId 53 | { 54 | ctx := context.Background() 55 | 56 | ctx2 := NewContext(ctx, "") 57 | if ctx != ctx2 { 58 | t.Error("want equal") 59 | return 60 | } 61 | } 62 | 63 | // nil context.Context 64 | { 65 | var ctx context.Context 66 | 67 | ctx2 := NewContext(ctx, "123456789") 68 | if ctx == ctx2 { 69 | t.Error("want not equal") 70 | return 71 | } 72 | 73 | id, ok := FromContext(ctx2) 74 | wantId, wantOk := "123456789", true 75 | if id != wantId || ok != wantOk { 76 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 77 | return 78 | } 79 | } 80 | 81 | // serially NewContext with same traceId 82 | { 83 | ctx := context.Background() 84 | 85 | traceId := "123456789" 86 | ctx2 := NewContext(ctx, traceId) 87 | ctx3 := NewContext(ctx2, traceId) 88 | if ctx2 != ctx3 { 89 | t.Error("want equal") 90 | return 91 | } 92 | } 93 | 94 | // parallel NewContext with same traceId 95 | { 96 | ctx := context.Background() 97 | 98 | traceId := "123456789" 99 | ctx2 := NewContext(ctx, traceId) 100 | ctx3 := NewContext(ctx, traceId) 101 | if ctx2 == ctx3 { 102 | t.Error("want not equal") 103 | return 104 | } 105 | } 106 | } 107 | 108 | func TestFromContext(t *testing.T) { 109 | // nil context 110 | { 111 | var ctx context.Context 112 | id, ok := FromContext(ctx) 113 | wantId, wantOk := "", false 114 | if id != wantId || ok != wantOk { 115 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 116 | return 117 | } 118 | } 119 | 120 | // non-nil context that does not contain traceId 121 | { 122 | ctx := context.Background() 123 | id, ok := FromContext(ctx) 124 | wantId, wantOk := "", false 125 | if id != wantId || ok != wantOk { 126 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 127 | return 128 | } 129 | } 130 | 131 | // non-nil context that contains empty traceId 132 | // this will never happen, see NewContext 133 | 134 | // non-nil context that contains non-empty traceId 135 | { 136 | ctx := context.WithValue(context.Background(), _traceIdContextKey, "123456789") 137 | id, ok := FromContext(ctx) 138 | wantId, wantOk := "123456789", true 139 | if id != wantId || ok != wantOk { 140 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 141 | return 142 | } 143 | } 144 | } 145 | 146 | func TestFromRequest(t *testing.T) { 147 | //// nil *http.Request 148 | //{ 149 | // var req *http.Request 150 | // id, ok := FromRequest(req) 151 | // wantId, wantOk := "", false 152 | // if id != wantId || ok != wantOk { 153 | // t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 154 | // return 155 | // } 156 | //} 157 | 158 | // non-nil *http.Request 159 | 160 | // nil Request.Context() 161 | { 162 | // nil Request.Header 163 | { 164 | req := &http.Request{} 165 | 166 | id, ok := FromRequest(req) 167 | wantId, wantOk := "", false 168 | if id != wantId || ok != wantOk { 169 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 170 | return 171 | } 172 | } 173 | 174 | // non-nil Request.Header without valid traceId 175 | { 176 | req := &http.Request{} 177 | 178 | header := make(http.Header) 179 | req.Header = header 180 | 181 | id, ok := FromRequest(req) 182 | wantId, wantOk := "", false 183 | if id != wantId || ok != wantOk { 184 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 185 | return 186 | } 187 | } 188 | 189 | // non-nil Request.Header with valid traceId 190 | { 191 | req := &http.Request{} 192 | 193 | header := make(http.Header) 194 | header.Set(TraceIdHeaderKey, "123456789") 195 | req.Header = header 196 | 197 | id, ok := FromRequest(req) 198 | wantId, wantOk := "123456789", true 199 | if id != wantId || ok != wantOk { 200 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 201 | return 202 | } 203 | } 204 | } 205 | 206 | // non-nil Request.Context() without valid traceId 207 | { 208 | // nil Request.Header 209 | { 210 | req := &http.Request{} 211 | req = req.WithContext(context.Background()) 212 | 213 | id, ok := FromRequest(req) 214 | wantId, wantOk := "", false 215 | if id != wantId || ok != wantOk { 216 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 217 | return 218 | } 219 | } 220 | 221 | // non-nil Request.Header without valid traceId 222 | { 223 | req := &http.Request{} 224 | req = req.WithContext(context.Background()) 225 | 226 | header := make(http.Header) 227 | req.Header = header 228 | 229 | id, ok := FromRequest(req) 230 | wantId, wantOk := "", false 231 | if id != wantId || ok != wantOk { 232 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 233 | return 234 | } 235 | } 236 | 237 | // non-nil Request.Header with valid traceId 238 | { 239 | req := &http.Request{} 240 | req = req.WithContext(context.Background()) 241 | 242 | header := make(http.Header) 243 | header.Set(TraceIdHeaderKey, "123456789") 244 | req.Header = header 245 | 246 | id, ok := FromRequest(req) 247 | wantId, wantOk := "123456789", true 248 | if id != wantId || ok != wantOk { 249 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 250 | return 251 | } 252 | } 253 | } 254 | 255 | // non-nil Request.Context() with valid traceId 256 | { 257 | // nil Request.Header 258 | { 259 | req := &http.Request{} 260 | 261 | traceId := "987654321" 262 | ctx := NewContext(context.Background(), traceId) 263 | req = req.WithContext(ctx) 264 | 265 | id, ok := FromRequest(req) 266 | wantId, wantOk := traceId, true 267 | if id != wantId || ok != wantOk { 268 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 269 | return 270 | } 271 | } 272 | 273 | // non-nil Request.Header without valid traceId 274 | { 275 | req := &http.Request{} 276 | 277 | traceId := "987654321" 278 | ctx := NewContext(context.Background(), traceId) 279 | req = req.WithContext(ctx) 280 | 281 | header := make(http.Header) 282 | req.Header = header 283 | 284 | id, ok := FromRequest(req) 285 | wantId, wantOk := traceId, true 286 | if id != wantId || ok != wantOk { 287 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 288 | return 289 | } 290 | } 291 | 292 | // non-nil Request.Header with valid traceId 293 | { 294 | req := &http.Request{} 295 | 296 | traceId := "987654321" 297 | ctx := NewContext(context.Background(), traceId) 298 | req = req.WithContext(ctx) 299 | 300 | header := make(http.Header) 301 | header.Set(TraceIdHeaderKey, "123456789") 302 | req.Header = header 303 | 304 | id, ok := FromRequest(req) 305 | wantId, wantOk := traceId, true 306 | if id != wantId || ok != wantOk { 307 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 308 | return 309 | } 310 | } 311 | } 312 | } 313 | 314 | func TestFromHeader(t *testing.T) { 315 | // nil header 316 | { 317 | var header http.Header 318 | id, ok := FromHeader(header) 319 | wantId, wantOk := "", false 320 | if id != wantId || ok != wantOk { 321 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 322 | return 323 | } 324 | } 325 | 326 | // non-nil header without TraceIdHeaderKey 327 | { 328 | header := make(http.Header) 329 | id, ok := FromHeader(header) 330 | wantId, wantOk := "", false 331 | if id != wantId || ok != wantOk { 332 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 333 | return 334 | } 335 | } 336 | 337 | // non-nil header with empty value for TraceIdHeaderKey 338 | { 339 | header := make(http.Header) 340 | header.Set(TraceIdHeaderKey, "") 341 | id, ok := FromHeader(header) 342 | wantId, wantOk := "", false 343 | if id != wantId || ok != wantOk { 344 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 345 | return 346 | } 347 | } 348 | 349 | // non-nil header with valid value for TraceIdHeaderKey 350 | { 351 | header := make(http.Header) 352 | header.Set(TraceIdHeaderKey, "123456789") 353 | id, ok := FromHeader(header) 354 | wantId, wantOk := "123456789", true 355 | if id != wantId || ok != wantOk { 356 | t.Errorf("have:(%s, %t), want:(%s, %t)", id, ok, wantId, wantOk) 357 | return 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /options_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestWithTraceId(t *testing.T) { 11 | // empty traceId 12 | { 13 | opt := WithTraceId("") 14 | 15 | var o = options{ 16 | traceId: "987654321", 17 | } 18 | opt(&o) 19 | want := options{ 20 | traceId: "", 21 | } 22 | if o != want { 23 | t.Errorf("have:%+v, want:%+v", o, want) 24 | return 25 | } 26 | } 27 | // non-empty traceId 28 | { 29 | opt := WithTraceId("123456789") 30 | 31 | var o = options{ 32 | traceId: "987654321", 33 | } 34 | opt(&o) 35 | want := options{ 36 | traceId: "123456789", 37 | } 38 | if o != want { 39 | t.Errorf("have:%+v, want:%+v", o, want) 40 | return 41 | } 42 | } 43 | } 44 | 45 | func TestWithTraceIdFunc(t *testing.T) { 46 | // nil function 47 | { 48 | var fn func() string 49 | opt := WithTraceIdFunc(fn) 50 | 51 | var o = options{ 52 | traceId: "987654321", 53 | } 54 | opt(&o) 55 | want := options{ 56 | traceId: "987654321", 57 | } 58 | if o != want { 59 | t.Errorf("have:%+v, want:%+v", o, want) 60 | return 61 | } 62 | } 63 | // non-nil function with empty returns 64 | { 65 | fn := func() string { return "" } 66 | opt := WithTraceIdFunc(fn) 67 | 68 | var o = options{ 69 | traceId: "987654321", 70 | } 71 | opt(&o) 72 | want := options{ 73 | traceId: "", 74 | } 75 | if o != want { 76 | t.Errorf("have:%+v, want:%+v", o, want) 77 | return 78 | } 79 | } 80 | // non-nil function with non-empty returns 81 | { 82 | fn := func() string { return "123456789" } 83 | opt := WithTraceIdFunc(fn) 84 | 85 | var o = options{ 86 | traceId: "987654321", 87 | } 88 | opt(&o) 89 | want := options{ 90 | traceId: "123456789", 91 | } 92 | if o != want { 93 | t.Errorf("have:%+v, want:%+v", o, want) 94 | return 95 | } 96 | } 97 | } 98 | 99 | func TestWithFormatter(t *testing.T) { 100 | // nil Formatter 101 | { 102 | var formatter Formatter 103 | opt := WithFormatter(formatter) 104 | 105 | var o = options{ 106 | formatter: JsonFormatter, 107 | } 108 | opt(&o) 109 | want := options{ 110 | formatter: JsonFormatter, 111 | } 112 | if o != want { 113 | t.Errorf("have:%+v, want:%+v", o, want) 114 | return 115 | } 116 | } 117 | // non-nil Formatter 118 | { 119 | opt := WithFormatter(TextFormatter) 120 | 121 | var o = options{ 122 | formatter: JsonFormatter, 123 | } 124 | opt(&o) 125 | want := options{ 126 | formatter: TextFormatter, 127 | } 128 | if o != want { 129 | t.Errorf("have:%+v, want:%+v", o, want) 130 | return 131 | } 132 | } 133 | } 134 | 135 | func TestWithOutput(t *testing.T) { 136 | // nil output 137 | { 138 | var output io.Writer 139 | opt := WithOutput(output) 140 | 141 | var o = options{ 142 | output: ConcurrentStderr, 143 | } 144 | opt(&o) 145 | want := options{ 146 | output: ConcurrentStderr, 147 | } 148 | if o != want { 149 | t.Errorf("have:%+v, want:%+v", o, want) 150 | return 151 | } 152 | } 153 | // non-nil output 154 | { 155 | opt := WithOutput(ConcurrentStdout) 156 | 157 | var o = options{ 158 | output: ConcurrentStderr, 159 | } 160 | opt(&o) 161 | want := options{ 162 | output: ConcurrentStdout, 163 | } 164 | if o != want { 165 | t.Errorf("have:%+v, want:%+v", o, want) 166 | return 167 | } 168 | } 169 | } 170 | 171 | func TestWithLevel(t *testing.T) { 172 | // fatal-1 173 | { 174 | opt := WithLevel(FatalLevel - 1) 175 | 176 | var o = options{ 177 | level: DebugLevel, 178 | } 179 | opt(&o) 180 | want := options{ 181 | level: DebugLevel, 182 | } 183 | if o != want { 184 | t.Errorf("have:%+v, want:%+v", o, want) 185 | return 186 | } 187 | } 188 | // fatal 189 | { 190 | opt := WithLevel(FatalLevel) 191 | 192 | var o = options{ 193 | level: DebugLevel, 194 | } 195 | opt(&o) 196 | want := options{ 197 | level: FatalLevel, 198 | } 199 | if o != want { 200 | t.Errorf("have:%+v, want:%+v", o, want) 201 | return 202 | } 203 | } 204 | // error 205 | { 206 | opt := WithLevel(ErrorLevel) 207 | 208 | var o = options{ 209 | level: DebugLevel, 210 | } 211 | opt(&o) 212 | want := options{ 213 | level: ErrorLevel, 214 | } 215 | if o != want { 216 | t.Errorf("have:%+v, want:%+v", o, want) 217 | return 218 | } 219 | } 220 | // warning 221 | { 222 | opt := WithLevel(WarnLevel) 223 | 224 | var o = options{ 225 | level: DebugLevel, 226 | } 227 | opt(&o) 228 | want := options{ 229 | level: WarnLevel, 230 | } 231 | if o != want { 232 | t.Errorf("have:%+v, want:%+v", o, want) 233 | return 234 | } 235 | } 236 | // info 237 | { 238 | opt := WithLevel(InfoLevel) 239 | 240 | var o = options{ 241 | level: DebugLevel, 242 | } 243 | opt(&o) 244 | want := options{ 245 | level: InfoLevel, 246 | } 247 | if o != want { 248 | t.Errorf("have:%+v, want:%+v", o, want) 249 | return 250 | } 251 | } 252 | // debug 253 | { 254 | opt := WithLevel(DebugLevel) 255 | 256 | var o = options{ 257 | level: FatalLevel, 258 | } 259 | opt(&o) 260 | want := options{ 261 | level: DebugLevel, 262 | } 263 | if o != want { 264 | t.Errorf("have:%+v, want:%+v", o, want) 265 | return 266 | } 267 | } 268 | // debug+1 269 | { 270 | opt := WithLevel(DebugLevel + 1) 271 | 272 | var o = options{ 273 | level: FatalLevel, 274 | } 275 | opt(&o) 276 | want := options{ 277 | level: FatalLevel, 278 | } 279 | if o != want { 280 | t.Errorf("have:%+v, want:%+v", o, want) 281 | return 282 | } 283 | } 284 | } 285 | 286 | func TestWithLevelString(t *testing.T) { 287 | // panic 288 | { 289 | opt := WithLevelString("panic") 290 | 291 | var o = options{ 292 | level: DebugLevel, 293 | } 294 | opt(&o) 295 | want := options{ 296 | level: DebugLevel, 297 | } 298 | if o != want { 299 | t.Errorf("have:%+v, want:%+v", o, want) 300 | return 301 | } 302 | } 303 | // fatal 304 | { 305 | opt := WithLevelString(FatalLevelString) 306 | 307 | var o = options{ 308 | level: DebugLevel, 309 | } 310 | opt(&o) 311 | want := options{ 312 | level: FatalLevel, 313 | } 314 | if o != want { 315 | t.Errorf("have:%+v, want:%+v", o, want) 316 | return 317 | } 318 | } 319 | // error 320 | { 321 | opt := WithLevelString(ErrorLevelString) 322 | 323 | var o = options{ 324 | level: DebugLevel, 325 | } 326 | opt(&o) 327 | want := options{ 328 | level: ErrorLevel, 329 | } 330 | if o != want { 331 | t.Errorf("have:%+v, want:%+v", o, want) 332 | return 333 | } 334 | } 335 | // warning 336 | { 337 | opt := WithLevelString(WarnLevelString) 338 | 339 | var o = options{ 340 | level: DebugLevel, 341 | } 342 | opt(&o) 343 | want := options{ 344 | level: WarnLevel, 345 | } 346 | if o != want { 347 | t.Errorf("have:%+v, want:%+v", o, want) 348 | return 349 | } 350 | } 351 | // info 352 | { 353 | opt := WithLevelString(InfoLevelString) 354 | 355 | var o = options{ 356 | level: DebugLevel, 357 | } 358 | opt(&o) 359 | want := options{ 360 | level: InfoLevel, 361 | } 362 | if o != want { 363 | t.Errorf("have:%+v, want:%+v", o, want) 364 | return 365 | } 366 | } 367 | // debug 368 | { 369 | opt := WithLevelString(DebugLevelString) 370 | 371 | var o = options{ 372 | level: FatalLevel, 373 | } 374 | opt(&o) 375 | want := options{ 376 | level: DebugLevel, 377 | } 378 | if o != want { 379 | t.Errorf("have:%+v, want:%+v", o, want) 380 | return 381 | } 382 | } 383 | // trace 384 | { 385 | opt := WithLevelString("trace") 386 | 387 | var o = options{ 388 | level: FatalLevel, 389 | } 390 | opt(&o) 391 | want := options{ 392 | level: FatalLevel, 393 | } 394 | if o != want { 395 | t.Errorf("have:%+v, want:%+v", o, want) 396 | return 397 | } 398 | } 399 | } 400 | 401 | func TestOptions_SetFormatter(t *testing.T) { 402 | // nil Formatter 403 | { 404 | var o = options{ 405 | formatter: JsonFormatter, 406 | } 407 | var formatter Formatter 408 | o.SetFormatter(formatter) 409 | 410 | want := options{ 411 | formatter: JsonFormatter, 412 | } 413 | if o != want { 414 | t.Errorf("have:%+v, want:%+v", o, want) 415 | return 416 | } 417 | } 418 | // non-nil Formatter 419 | { 420 | var o = options{ 421 | formatter: JsonFormatter, 422 | } 423 | o.SetFormatter(TextFormatter) 424 | 425 | want := options{ 426 | formatter: TextFormatter, 427 | } 428 | if o != want { 429 | t.Errorf("have:%+v, want:%+v", o, want) 430 | return 431 | } 432 | } 433 | } 434 | 435 | func TestOptions_SetOutput(t *testing.T) { 436 | // nil output 437 | { 438 | var o = options{ 439 | output: ConcurrentStderr, 440 | } 441 | var output io.Writer 442 | o.SetOutput(output) 443 | 444 | want := options{ 445 | output: ConcurrentStderr, 446 | } 447 | if o != want { 448 | t.Errorf("have:%+v, want:%+v", o, want) 449 | return 450 | } 451 | } 452 | // non-nil output 453 | { 454 | var o = options{ 455 | output: ConcurrentStderr, 456 | } 457 | o.SetOutput(ConcurrentStdout) 458 | 459 | want := options{ 460 | output: ConcurrentStdout, 461 | } 462 | if o != want { 463 | t.Errorf("have:%+v, want:%+v", o, want) 464 | return 465 | } 466 | } 467 | } 468 | 469 | func TestOptions_SetLevel(t *testing.T) { 470 | // fatal-1 471 | { 472 | var o = options{ 473 | level: DebugLevel, 474 | } 475 | o.SetLevel(FatalLevel - 1) 476 | 477 | want := options{ 478 | level: DebugLevel, 479 | } 480 | if o != want { 481 | t.Errorf("have:%+v, want:%+v", o, want) 482 | return 483 | } 484 | } 485 | // fatal 486 | { 487 | var o = options{ 488 | level: DebugLevel, 489 | } 490 | o.SetLevel(FatalLevel) 491 | 492 | want := options{ 493 | level: FatalLevel, 494 | } 495 | if o != want { 496 | t.Errorf("have:%+v, want:%+v", o, want) 497 | return 498 | } 499 | } 500 | // error 501 | { 502 | var o = options{ 503 | level: DebugLevel, 504 | } 505 | o.SetLevel(ErrorLevel) 506 | 507 | want := options{ 508 | level: ErrorLevel, 509 | } 510 | if o != want { 511 | t.Errorf("have:%+v, want:%+v", o, want) 512 | return 513 | } 514 | } 515 | // warning 516 | { 517 | var o = options{ 518 | level: DebugLevel, 519 | } 520 | o.SetLevel(WarnLevel) 521 | 522 | want := options{ 523 | level: WarnLevel, 524 | } 525 | if o != want { 526 | t.Errorf("have:%+v, want:%+v", o, want) 527 | return 528 | } 529 | } 530 | // info 531 | { 532 | var o = options{ 533 | level: DebugLevel, 534 | } 535 | o.SetLevel(InfoLevel) 536 | 537 | want := options{ 538 | level: InfoLevel, 539 | } 540 | if o != want { 541 | t.Errorf("have:%+v, want:%+v", o, want) 542 | return 543 | } 544 | } 545 | // debug 546 | { 547 | var o = options{ 548 | level: FatalLevel, 549 | } 550 | o.SetLevel(DebugLevel) 551 | 552 | want := options{ 553 | level: DebugLevel, 554 | } 555 | if o != want { 556 | t.Errorf("have:%+v, want:%+v", o, want) 557 | return 558 | } 559 | } 560 | // debug+1 561 | { 562 | var o = options{ 563 | level: FatalLevel, 564 | } 565 | o.SetLevel(DebugLevel + 1) 566 | 567 | want := options{ 568 | level: FatalLevel, 569 | } 570 | if o != want { 571 | t.Errorf("have:%+v, want:%+v", o, want) 572 | return 573 | } 574 | } 575 | } 576 | 577 | func TestNewOptions(t *testing.T) { 578 | // nil == getDefaultOptions() 579 | { 580 | SetDefaultOptions(nil) 581 | 582 | defaultWant := options{ 583 | traceId: "", 584 | formatter: TextFormatter, 585 | output: ConcurrentStdout, 586 | level: DebugLevel, 587 | } 588 | 589 | // with none 590 | { 591 | opts := newOptions(nil) 592 | want := defaultWant 593 | if have := *opts; have != want { 594 | t.Errorf("have:%+v, want:%+v", have, want) 595 | return 596 | } 597 | } 598 | // WithTraceId 599 | { 600 | opts := newOptions([]Option{WithTraceId("123456789")}) 601 | want := defaultWant 602 | want.traceId = "123456789" 603 | if have := *opts; have != want { 604 | t.Errorf("have:%+v, want:%+v", have, want) 605 | return 606 | } 607 | } 608 | // WithTraceIdFunc 609 | { 610 | fn := func() string { return "123456789" } 611 | opts := newOptions([]Option{WithTraceIdFunc(fn)}) 612 | want := defaultWant 613 | want.traceId = "123456789" 614 | if have := *opts; have != want { 615 | t.Errorf("have:%+v, want:%+v", have, want) 616 | return 617 | } 618 | } 619 | // WithFormatter 620 | { 621 | opts := newOptions([]Option{WithFormatter(JsonFormatter)}) 622 | want := defaultWant 623 | want.formatter = JsonFormatter 624 | if have := *opts; have != want { 625 | t.Errorf("have:%+v, want:%+v", have, want) 626 | return 627 | } 628 | } 629 | // WithOutput 630 | { 631 | opts := newOptions([]Option{WithOutput(ConcurrentStderr)}) 632 | want := defaultWant 633 | want.output = ConcurrentStderr 634 | if have := *opts; have != want { 635 | t.Errorf("have:%+v, want:%+v", have, want) 636 | return 637 | } 638 | } 639 | // WithLevel 640 | { 641 | opts := newOptions([]Option{WithLevel(InfoLevel)}) 642 | want := defaultWant 643 | want.level = InfoLevel 644 | if have := *opts; have != want { 645 | t.Errorf("have:%+v, want:%+v", have, want) 646 | return 647 | } 648 | } 649 | // WithLevelString 650 | { 651 | opts := newOptions([]Option{WithLevelString(WarnLevelString)}) 652 | want := defaultWant 653 | want.level = WarnLevel 654 | if have := *opts; have != want { 655 | t.Errorf("have:%+v, want:%+v", have, want) 656 | return 657 | } 658 | } 659 | } 660 | 661 | // nil != getDefaultOptions() 662 | { 663 | formatter := testJsonFormatter{} 664 | output := ConcurrentWriter(os.Stdout) 665 | SetDefaultOptions([]Option{WithTraceId("987654321"), WithFormatter(formatter), WithOutput(output), WithLevel(FatalLevel)}) 666 | defer SetDefaultOptions(nil) 667 | 668 | defaultWant := options{ 669 | traceId: "987654321", 670 | formatter: formatter, 671 | output: output, 672 | level: FatalLevel, 673 | } 674 | 675 | // with none 676 | { 677 | opts := newOptions(nil) 678 | want := defaultWant 679 | if have := *opts; have != want { 680 | t.Errorf("have:%+v, want:%+v", have, want) 681 | return 682 | } 683 | } 684 | // WithTraceId 685 | { 686 | opts := newOptions([]Option{WithTraceId("123456789")}) 687 | want := defaultWant 688 | want.traceId = "123456789" 689 | if have := *opts; have != want { 690 | t.Errorf("have:%+v, want:%+v", have, want) 691 | return 692 | } 693 | } 694 | // WithTraceIdFunc 695 | { 696 | fn := func() string { return "123456789" } 697 | opts := newOptions([]Option{WithTraceIdFunc(fn)}) 698 | want := defaultWant 699 | want.traceId = "123456789" 700 | if have := *opts; have != want { 701 | t.Errorf("have:%+v, want:%+v", have, want) 702 | return 703 | } 704 | } 705 | // WithFormatter 706 | { 707 | opts := newOptions([]Option{WithFormatter(TextFormatter)}) 708 | want := defaultWant 709 | want.formatter = TextFormatter 710 | if have := *opts; have != want { 711 | t.Errorf("have:%+v, want:%+v", have, want) 712 | return 713 | } 714 | } 715 | // WithOutput 716 | { 717 | opts := newOptions([]Option{WithOutput(ConcurrentStdout)}) 718 | want := defaultWant 719 | want.output = ConcurrentStdout 720 | if have := *opts; have != want { 721 | t.Errorf("have:%+v, want:%+v", have, want) 722 | return 723 | } 724 | } 725 | // WithLevel 726 | { 727 | opts := newOptions([]Option{WithLevel(InfoLevel)}) 728 | want := defaultWant 729 | want.level = InfoLevel 730 | if have := *opts; have != want { 731 | t.Errorf("have:%+v, want:%+v", have, want) 732 | return 733 | } 734 | } 735 | // WithLevelString 736 | { 737 | opts := newOptions([]Option{WithLevelString(WarnLevelString)}) 738 | want := defaultWant 739 | want.level = WarnLevel 740 | if have := *opts; have != want { 741 | t.Errorf("have:%+v, want:%+v", have, want) 742 | return 743 | } 744 | } 745 | } 746 | } 747 | 748 | func Test_SetDefaultOptions_GetDefaultOptions(t *testing.T) { 749 | defer SetDefaultOptions(nil) 750 | 751 | // no called SetDefaultOptions yet 752 | opts := getDefaultOptions() 753 | if opts != nil { 754 | t.Errorf("have:%v, want:nil", opts) 755 | return 756 | } 757 | 758 | // call SetDefaultOptions with non-nil []Option 759 | opt := WithFormatter(JsonFormatter) 760 | opts = []Option{ 761 | opt, 762 | } 763 | SetDefaultOptions(opts) 764 | have := getDefaultOptions() 765 | if !reflect.DeepEqual(have, opts) { 766 | t.Errorf("have:%v, want:%v", have, opts) 767 | return 768 | } 769 | 770 | // call SetDefaultOptions with nil []Option 771 | SetDefaultOptions(nil) 772 | opts = getDefaultOptions() 773 | if opts != nil { 774 | t.Errorf("have:%v, want:nil", opts) 775 | return 776 | } 777 | 778 | // call SetDefaultOptions with non-nil []Option 779 | opt = WithFormatter(TextFormatter) 780 | opts = []Option{ 781 | opt, 782 | } 783 | SetDefaultOptions(opts) 784 | have = getDefaultOptions() 785 | if !reflect.DeepEqual(have, opts) { 786 | t.Errorf("have:%v, want:%v", have, opts) 787 | return 788 | } 789 | } 790 | -------------------------------------------------------------------------------- /context_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func Test_NewContext_FromContext(t *testing.T) { 10 | // no called NewContext yet 11 | { 12 | ctx := context.Background() 13 | 14 | lg, ok := FromContext(ctx) 15 | want, wantOk := Logger(nil), false 16 | if lg != want || ok != wantOk { 17 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 18 | return 19 | } 20 | } 21 | 22 | // called NewContext with nil Logger 23 | { 24 | ctx := context.Background() 25 | 26 | _lg := Logger(nil) 27 | ctx = NewContext(ctx, _lg) 28 | 29 | lg, ok := FromContext(ctx) 30 | want, wantOk := Logger(nil), false 31 | if lg != want || ok != wantOk { 32 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 33 | return 34 | } 35 | } 36 | 37 | // called NewContext with non-nil Logger, nil context.Context 38 | { 39 | var ctx context.Context 40 | 41 | _lg := New() 42 | ctx = NewContext(ctx, _lg) 43 | 44 | lg, ok := FromContext(ctx) 45 | want, wantOk := _lg, true 46 | if lg != want || ok != wantOk { 47 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 48 | return 49 | } 50 | } 51 | 52 | // called NewContext with non-nil Logger, non-nil context.Context 53 | { 54 | ctx := context.Background() 55 | 56 | _lg := New() 57 | ctx = NewContext(ctx, _lg) 58 | 59 | lg, ok := FromContext(ctx) 60 | want, wantOk := _lg, true 61 | if lg != want || ok != wantOk { 62 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 63 | return 64 | } 65 | } 66 | } 67 | 68 | func TestNewContext(t *testing.T) { 69 | // nil Logger 70 | { 71 | ctx := context.Background() 72 | 73 | ctx2 := NewContext(ctx, Logger(nil)) 74 | if ctx != ctx2 { 75 | t.Error("want equal") 76 | return 77 | } 78 | } 79 | 80 | // nil context.Context 81 | { 82 | var ctx context.Context 83 | 84 | _lg := New() 85 | ctx2 := NewContext(ctx, _lg) 86 | if ctx == ctx2 { 87 | t.Error("want not equal") 88 | return 89 | } 90 | 91 | lg, ok := FromContext(ctx2) 92 | want, wantOk := _lg, true 93 | if lg != want || ok != wantOk { 94 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 95 | return 96 | } 97 | } 98 | 99 | // serially NewContext with same Logger 100 | { 101 | ctx := context.Background() 102 | 103 | _lg := New() 104 | ctx2 := NewContext(ctx, _lg) 105 | ctx3 := NewContext(ctx2, _lg) 106 | if ctx2 != ctx3 { 107 | t.Error("want equal") 108 | return 109 | } 110 | } 111 | 112 | // parallel NewContext with same Logger 113 | { 114 | ctx := context.Background() 115 | 116 | _lg := New() 117 | ctx2 := NewContext(ctx, _lg) 118 | ctx3 := NewContext(ctx, _lg) 119 | if ctx2 == ctx3 { 120 | t.Error("want not equal") 121 | return 122 | } 123 | } 124 | } 125 | 126 | func TestFromContext(t *testing.T) { 127 | // nil context 128 | { 129 | var ctx context.Context 130 | 131 | lg, ok := FromContext(ctx) 132 | want, wantOk := Logger(nil), false 133 | if lg != want || ok != wantOk { 134 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 135 | return 136 | } 137 | } 138 | 139 | // non-nil context that does not contain Logger 140 | { 141 | ctx := context.Background() 142 | 143 | lg, ok := FromContext(ctx) 144 | want, wantOk := Logger(nil), false 145 | if lg != want || ok != wantOk { 146 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 147 | return 148 | } 149 | } 150 | 151 | // non-nil context that contains nil Logger 152 | // this will never happen, see NewContext 153 | 154 | // non-nil context that contains non-nil Logger 155 | { 156 | _lg := New() 157 | ctx := context.WithValue(context.Background(), _loggerContextKey, _lg) 158 | 159 | lg, ok := FromContext(ctx) 160 | want, wantOk := _lg, true 161 | if lg != want || ok != wantOk { 162 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 163 | return 164 | } 165 | } 166 | } 167 | 168 | func TestMustFromContext(t *testing.T) { 169 | // nil context 170 | func() { 171 | var lg Logger 172 | 173 | defer func() { 174 | if err := recover(); err != nil { 175 | if lg != nil { 176 | t.Error("lg must nil") 177 | } 178 | } else { 179 | t.Error("must panic") 180 | } 181 | }() 182 | 183 | var ctx context.Context 184 | lg = MustFromContext(ctx) 185 | }() 186 | 187 | // non-nil context that does not contain Logger 188 | func() { 189 | var lg Logger 190 | 191 | defer func() { 192 | if err := recover(); err != nil { 193 | if lg != nil { 194 | t.Error("lg must nil") 195 | } 196 | } else { 197 | t.Error("must panic") 198 | } 199 | }() 200 | 201 | ctx := context.Background() 202 | lg = MustFromContext(ctx) 203 | }() 204 | 205 | // non-nil context that contains nil Logger 206 | // this will never happen, see NewContext 207 | 208 | // non-nil context that contains non-nil Logger 209 | func() { 210 | var lg Logger 211 | 212 | defer func() { 213 | if err := recover(); err != nil { 214 | t.Error("must not panic") 215 | } else { 216 | if lg == nil { 217 | t.Error("lg must not nil") 218 | } 219 | } 220 | }() 221 | 222 | _lg := New() 223 | ctx := context.WithValue(context.Background(), _loggerContextKey, _lg) 224 | 225 | lg = MustFromContext(ctx) 226 | if lg != _lg { 227 | t.Error("want equal") 228 | return 229 | } 230 | }() 231 | } 232 | 233 | func TestFromContextOrNew(t *testing.T) { 234 | // nil new function 235 | { 236 | // nil context 237 | { 238 | var ctx context.Context 239 | 240 | lg, ctx2, ok := FromContextOrNew(ctx, nil) 241 | if lg == nil { 242 | t.Error("want non-nil") 243 | return 244 | } 245 | if !ok { 246 | t.Error("want true") 247 | return 248 | } 249 | if ctx2 == ctx { 250 | t.Error("want not equal") 251 | return 252 | } 253 | lg2, ok := FromContext(ctx2) 254 | if !ok { 255 | t.Error("want true") 256 | return 257 | } 258 | if lg2 != lg { 259 | t.Error("want equal") 260 | return 261 | } 262 | } 263 | 264 | // non-nil context that does not contain Logger 265 | { 266 | ctx := context.Background() 267 | 268 | lg, ctx2, ok := FromContextOrNew(ctx, nil) 269 | if lg == nil { 270 | t.Error("want non-nil") 271 | return 272 | } 273 | if !ok { 274 | t.Error("want true") 275 | return 276 | } 277 | if ctx2 == ctx { 278 | t.Error("want not equal") 279 | return 280 | } 281 | lg2, ok := FromContext(ctx2) 282 | if !ok { 283 | t.Error("want true") 284 | return 285 | } 286 | if lg2 != lg { 287 | t.Error("want equal") 288 | return 289 | } 290 | } 291 | 292 | // non-nil context that contains nil Logger 293 | // this will never happen, see NewContext 294 | 295 | // non-nil context that contains non-nil Logger 296 | { 297 | _lg := New() 298 | ctx := context.WithValue(context.Background(), _loggerContextKey, _lg) 299 | 300 | lg, ctx2, ok := FromContextOrNew(ctx, nil) 301 | if lg != _lg { 302 | t.Error("want equal") 303 | return 304 | } 305 | if ok { 306 | t.Error("want false") 307 | return 308 | } 309 | if ctx2 != ctx { 310 | t.Error("want equal") 311 | return 312 | } 313 | lg2, ok := FromContext(ctx2) 314 | if !ok { 315 | t.Error("want true") 316 | return 317 | } 318 | if lg2 != lg { 319 | t.Error("want equal") 320 | return 321 | } 322 | } 323 | } 324 | 325 | // non-nil new func 326 | { 327 | var _newLogger = New() 328 | var _new = func() Logger { return _newLogger } 329 | 330 | // nil context 331 | { 332 | var ctx context.Context 333 | 334 | lg, ctx2, ok := FromContextOrNew(ctx, _new) 335 | if lg != _newLogger { 336 | t.Error("want equal") 337 | return 338 | } 339 | if !ok { 340 | t.Error("want true") 341 | return 342 | } 343 | if ctx2 == ctx { 344 | t.Error("want not equal") 345 | return 346 | } 347 | lg2, ok := FromContext(ctx2) 348 | if !ok { 349 | t.Error("want true") 350 | return 351 | } 352 | if lg2 != lg { 353 | t.Error("want equal") 354 | return 355 | } 356 | } 357 | 358 | // non-nil context that does not contain Logger 359 | { 360 | ctx := context.Background() 361 | 362 | lg, ctx2, ok := FromContextOrNew(ctx, _new) 363 | if lg != _newLogger { 364 | t.Error("want equal") 365 | return 366 | } 367 | if !ok { 368 | t.Error("want true") 369 | return 370 | } 371 | if ctx2 == ctx { 372 | t.Error("want not equal") 373 | return 374 | } 375 | lg2, ok := FromContext(ctx2) 376 | if !ok { 377 | t.Error("want true") 378 | return 379 | } 380 | if lg2 != lg { 381 | t.Error("want equal") 382 | return 383 | } 384 | } 385 | 386 | // non-nil context that contains nil Logger 387 | // this will never happen, see NewContext 388 | 389 | // non-nil context that contains non-nil Logger 390 | { 391 | _lg := New() 392 | ctx := context.WithValue(context.Background(), _loggerContextKey, _lg) 393 | 394 | lg, ctx2, ok := FromContextOrNew(ctx, _new) 395 | if lg != _lg { 396 | t.Error("want equal") 397 | return 398 | } 399 | if ok { 400 | t.Error("want false") 401 | return 402 | } 403 | if ctx2 != ctx { 404 | t.Error("want equal") 405 | return 406 | } 407 | lg2, ok := FromContext(ctx2) 408 | if !ok { 409 | t.Error("want true") 410 | return 411 | } 412 | if lg2 != lg { 413 | t.Error("want equal") 414 | return 415 | } 416 | } 417 | } 418 | } 419 | 420 | func Test_NewRequest_FromRequest(t *testing.T) { 421 | // no called NewContext yet 422 | { 423 | req := &http.Request{} 424 | req = req.WithContext(context.Background()) 425 | 426 | lg, ok := FromRequest(req) 427 | want, wantOk := Logger(nil), false 428 | if lg != want || ok != wantOk { 429 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 430 | return 431 | } 432 | } 433 | 434 | // called NewContext with nil Logger 435 | { 436 | req := &http.Request{} 437 | req = req.WithContext(context.Background()) 438 | 439 | _lg := Logger(nil) 440 | req = NewRequest(req, _lg) 441 | 442 | lg, ok := FromRequest(req) 443 | want, wantOk := Logger(nil), false 444 | if lg != want || ok != wantOk { 445 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 446 | return 447 | } 448 | } 449 | 450 | // called NewContext with non-nil Logger, nil context.Context 451 | { 452 | req := &http.Request{} 453 | 454 | _lg := New() 455 | req = NewRequest(req, _lg) 456 | 457 | lg, ok := FromRequest(req) 458 | want, wantOk := _lg, true 459 | if lg != want || ok != wantOk { 460 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 461 | return 462 | } 463 | } 464 | 465 | // called NewContext with non-nil Logger, non-nil context.Context 466 | { 467 | req := &http.Request{} 468 | req = req.WithContext(context.Background()) 469 | 470 | _lg := New() 471 | req = NewRequest(req, _lg) 472 | 473 | lg, ok := FromRequest(req) 474 | want, wantOk := _lg, true 475 | if lg != want || ok != wantOk { 476 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 477 | return 478 | } 479 | } 480 | } 481 | 482 | func TestNewRequest(t *testing.T) { 483 | // nil Logger 484 | { 485 | req := &http.Request{} 486 | 487 | req2 := NewRequest(req, Logger(nil)) 488 | if req != req2 { 489 | t.Error("want equal") 490 | return 491 | } 492 | } 493 | 494 | // nil context.Context 495 | { 496 | req := &http.Request{} 497 | 498 | _lg := New() 499 | req2 := NewRequest(req, _lg) 500 | if req == req2 { 501 | t.Error("want not equal") 502 | return 503 | } 504 | 505 | lg, ok := FromRequest(req2) 506 | want, wantOk := _lg, true 507 | if lg != want || ok != wantOk { 508 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 509 | return 510 | } 511 | } 512 | 513 | // serially NewContext with same Logger 514 | { 515 | req := &http.Request{} 516 | req = req.WithContext(context.Background()) 517 | 518 | _lg := New() 519 | req2 := NewRequest(req, _lg) 520 | req3 := NewRequest(req2, _lg) 521 | if req2 != req3 { 522 | t.Error("want equal") 523 | return 524 | } 525 | } 526 | 527 | // parallel NewContext with same Logger 528 | { 529 | req := &http.Request{} 530 | req = req.WithContext(context.Background()) 531 | 532 | _lg := New() 533 | req2 := NewRequest(req, _lg) 534 | req3 := NewRequest(req, _lg) 535 | if req2 == req3 { 536 | t.Error("want not equal") 537 | return 538 | } 539 | } 540 | } 541 | 542 | func TestFromRequest(t *testing.T) { 543 | //// nil Request 544 | //{ 545 | // var req *http.Request 546 | // 547 | // lg, ok := FromRequest(req) 548 | // want, wantOk := Logger(nil), false 549 | // if lg != want || ok != wantOk { 550 | // t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 551 | // return 552 | // } 553 | //} 554 | 555 | // nil context 556 | { 557 | req := &http.Request{} 558 | 559 | lg, ok := FromRequest(req) 560 | want, wantOk := Logger(nil), false 561 | if lg != want || ok != wantOk { 562 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 563 | return 564 | } 565 | } 566 | 567 | // non-nil context that does not contain Logger 568 | { 569 | req := &http.Request{} 570 | 571 | ctx := context.Background() 572 | req = req.WithContext(ctx) 573 | 574 | lg, ok := FromRequest(req) 575 | want, wantOk := Logger(nil), false 576 | if lg != want || ok != wantOk { 577 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 578 | return 579 | } 580 | } 581 | 582 | // non-nil context that contains nil Logger 583 | // this will never happen, see NewContext 584 | 585 | // non-nil context that contains non-nil Logger 586 | { 587 | req := &http.Request{} 588 | 589 | _lg := New() 590 | ctx := context.WithValue(context.Background(), _loggerContextKey, _lg) 591 | req = req.WithContext(ctx) 592 | 593 | lg, ok := FromRequest(req) 594 | want, wantOk := _lg, true 595 | if lg != want || ok != wantOk { 596 | t.Errorf("have:(%#v, %t), want:(%#v, %t)", lg, ok, want, wantOk) 597 | return 598 | } 599 | } 600 | } 601 | 602 | func TestMustFromRequest(t *testing.T) { 603 | //// nil Request 604 | //func() { 605 | // var lg Logger 606 | // 607 | // defer func() { 608 | // if err := recover(); err != nil { 609 | // if lg != nil { 610 | // t.Error("lg must nil") 611 | // } 612 | // } else { 613 | // t.Error("must panic") 614 | // } 615 | // }() 616 | // 617 | // var req *http.Request 618 | // 619 | // lg = MustFromRequest(req) 620 | //}() 621 | 622 | // nil context 623 | func() { 624 | var lg Logger 625 | 626 | defer func() { 627 | if err := recover(); err != nil { 628 | if lg != nil { 629 | t.Error("lg must nil") 630 | } 631 | } else { 632 | t.Error("must panic") 633 | } 634 | }() 635 | 636 | req := &http.Request{} 637 | 638 | lg = MustFromRequest(req) 639 | }() 640 | 641 | // non-nil context that does not contain Logger 642 | func() { 643 | var lg Logger 644 | 645 | defer func() { 646 | if err := recover(); err != nil { 647 | if lg != nil { 648 | t.Error("lg must nil") 649 | } 650 | } else { 651 | t.Error("must panic") 652 | } 653 | }() 654 | 655 | req := &http.Request{} 656 | 657 | ctx := context.Background() 658 | req = req.WithContext(ctx) 659 | 660 | lg = MustFromRequest(req) 661 | }() 662 | 663 | // non-nil context that contains nil Logger 664 | // this will never happen, see NewContext 665 | 666 | // non-nil context that contains non-nil Logger 667 | func() { 668 | var lg Logger 669 | 670 | defer func() { 671 | if err := recover(); err != nil { 672 | t.Error("must not panic") 673 | } else { 674 | if lg == nil { 675 | t.Error("lg must not nil") 676 | } 677 | } 678 | }() 679 | 680 | req := &http.Request{} 681 | 682 | _lg := New() 683 | ctx := context.WithValue(context.Background(), _loggerContextKey, _lg) 684 | req = req.WithContext(ctx) 685 | 686 | lg = MustFromRequest(req) 687 | if lg != _lg { 688 | t.Error("want equal") 689 | return 690 | } 691 | }() 692 | } 693 | 694 | func TestFromRequestOrNew(t *testing.T) { 695 | // nil new function 696 | { 697 | //// nil Request 698 | //{ 699 | // var req *http.Request 700 | // 701 | // lg, req2, ok := FromRequestOrNew(req, nil) 702 | // if lg == nil { 703 | // t.Error("want non-nil") 704 | // return 705 | // } 706 | // if !ok { 707 | // t.Error("want true") 708 | // return 709 | // } 710 | // if req2 == req { 711 | // t.Error("want not equal") 712 | // return 713 | // } 714 | // lg2, ok := FromRequest(req2) 715 | // if !ok { 716 | // t.Error("want true") 717 | // return 718 | // } 719 | // if lg2 != lg { 720 | // t.Error("want equal") 721 | // return 722 | // } 723 | //} 724 | 725 | // nil context 726 | { 727 | req := &http.Request{} 728 | 729 | lg, req2, ok := FromRequestOrNew(req, nil) 730 | if lg == nil { 731 | t.Error("want non-nil") 732 | return 733 | } 734 | if !ok { 735 | t.Error("want true") 736 | return 737 | } 738 | if req2 == req { 739 | t.Error("want not equal") 740 | return 741 | } 742 | lg2, ok := FromRequest(req2) 743 | if !ok { 744 | t.Error("want true") 745 | return 746 | } 747 | if lg2 != lg { 748 | t.Error("want equal") 749 | return 750 | } 751 | } 752 | 753 | // non-nil context that does not contain Logger 754 | { 755 | req := &http.Request{} 756 | 757 | ctx := context.Background() 758 | req = req.WithContext(ctx) 759 | 760 | lg, req2, ok := FromRequestOrNew(req, nil) 761 | if lg == nil { 762 | t.Error("want non-nil") 763 | return 764 | } 765 | if !ok { 766 | t.Error("want true") 767 | return 768 | } 769 | if req2 == req { 770 | t.Error("want not equal") 771 | return 772 | } 773 | lg2, ok := FromRequest(req2) 774 | if !ok { 775 | t.Error("want true") 776 | return 777 | } 778 | if lg2 != lg { 779 | t.Error("want equal") 780 | return 781 | } 782 | } 783 | 784 | // non-nil context that contains nil Logger 785 | // this will never happen, see NewContext 786 | 787 | // non-nil context that contains non-nil Logger 788 | { 789 | req := &http.Request{} 790 | 791 | _lg := New() 792 | ctx := context.WithValue(context.Background(), _loggerContextKey, _lg) 793 | req = req.WithContext(ctx) 794 | 795 | lg, req2, ok := FromRequestOrNew(req, nil) 796 | if lg != _lg { 797 | t.Error("want equal") 798 | return 799 | } 800 | if ok { 801 | t.Error("want false") 802 | return 803 | } 804 | if req2 != req { 805 | t.Error("want equal") 806 | return 807 | } 808 | lg2, ok := FromRequest(req2) 809 | if !ok { 810 | t.Error("want true") 811 | return 812 | } 813 | if lg2 != lg { 814 | t.Error("want equal") 815 | return 816 | } 817 | } 818 | } 819 | 820 | // non-nil new function 821 | { 822 | var _newLogger = New() 823 | var _new = func() Logger { return _newLogger } 824 | 825 | //// nil Request 826 | //{ 827 | // var req *http.Request 828 | // 829 | // lg, req2, ok := FromRequestOrNew(req, _new) 830 | // if lg != _newLogger { 831 | // t.Error("want equal") 832 | // return 833 | // } 834 | // if !ok { 835 | // t.Error("want true") 836 | // return 837 | // } 838 | // if req2 == req { 839 | // t.Error("want not equal") 840 | // return 841 | // } 842 | // lg2, ok := FromRequest(req2) 843 | // if !ok { 844 | // t.Error("want true") 845 | // return 846 | // } 847 | // if lg2 != lg { 848 | // t.Error("want equal") 849 | // return 850 | // } 851 | //} 852 | 853 | // nil context 854 | { 855 | req := &http.Request{} 856 | 857 | lg, req2, ok := FromRequestOrNew(req, _new) 858 | if lg != _newLogger { 859 | t.Error("want equal") 860 | return 861 | } 862 | if !ok { 863 | t.Error("want true") 864 | return 865 | } 866 | if req2 == req { 867 | t.Error("want not equal") 868 | return 869 | } 870 | lg2, ok := FromRequest(req2) 871 | if !ok { 872 | t.Error("want true") 873 | return 874 | } 875 | if lg2 != lg { 876 | t.Error("want equal") 877 | return 878 | } 879 | } 880 | 881 | // non-nil context that does not contain Logger 882 | { 883 | req := &http.Request{} 884 | 885 | ctx := context.Background() 886 | req = req.WithContext(ctx) 887 | 888 | lg, req2, ok := FromRequestOrNew(req, _new) 889 | if lg != _newLogger { 890 | t.Error("want equal") 891 | return 892 | } 893 | if !ok { 894 | t.Error("want true") 895 | return 896 | } 897 | if req2 == req { 898 | t.Error("want not equal") 899 | return 900 | } 901 | lg2, ok := FromRequest(req2) 902 | if !ok { 903 | t.Error("want true") 904 | return 905 | } 906 | if lg2 != lg { 907 | t.Error("want equal") 908 | return 909 | } 910 | } 911 | 912 | // non-nil context that contains nil Logger 913 | // this will never happen, see NewContext 914 | 915 | // non-nil context that contains non-nil Logger 916 | { 917 | req := &http.Request{} 918 | 919 | _lg := New() 920 | ctx := context.WithValue(context.Background(), _loggerContextKey, _lg) 921 | req = req.WithContext(ctx) 922 | 923 | lg, req2, ok := FromRequestOrNew(req, _new) 924 | if lg != _lg { 925 | t.Error("want equal") 926 | return 927 | } 928 | if ok { 929 | t.Error("want false") 930 | return 931 | } 932 | if req2 != req { 933 | t.Error("want equal") 934 | return 935 | } 936 | lg2, ok := FromRequest(req2) 937 | if !ok { 938 | t.Error("want true") 939 | return 940 | } 941 | if lg2 != lg { 942 | t.Error("want equal") 943 | return 944 | } 945 | } 946 | } 947 | } 948 | -------------------------------------------------------------------------------- /shortcut_without_logger_test.go: -------------------------------------------------------------------------------- 1 | // <-- to adjust the line number 2 | package log 3 | 4 | import ( 5 | "bytes" 6 | "context" 7 | "encoding/json" 8 | "reflect" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func testFatalContextLocationWithoutLogger() { 14 | FatalContext(testWithoutLoggerContext, "msg") 15 | } 16 | func testErrorContextLocationWithoutLogger() { 17 | ErrorContext(testWithoutLoggerContext, "msg") 18 | } 19 | func testWarnContextLocationWithoutLogger() { 20 | WarnContext(testWithoutLoggerContext, "msg") 21 | } 22 | func testInfoContextLocationWithoutLogger() { 23 | InfoContext(testWithoutLoggerContext, "msg") 24 | } 25 | func testDebugContextLocationWithoutLogger() { 26 | DebugContext(testWithoutLoggerContext, "msg") 27 | } 28 | func testOutputContextLocationWithoutLogger() { 29 | OutputContext(testWithoutLoggerContext, 0, ErrorLevel, "msg") 30 | } 31 | 32 | var testWithoutLoggerContext = context.Background() 33 | 34 | func setWithoutLoggerContextOptionsToDefault() { 35 | SetFormatter(TextFormatter) 36 | SetOutput(ConcurrentStdout) 37 | SetLevel(DebugLevel) 38 | } 39 | 40 | //type locationFormat struct{} 41 | // 42 | //func (locationFormat) Format(entry *Entry) ([]byte, error) { 43 | // return []byte(entry.Location), nil 44 | //} 45 | 46 | func TestLocationContextWithoutLoggerWithoutLogger(t *testing.T) { 47 | defer setWithoutLoggerContextOptionsToDefault() 48 | 49 | // fatal 50 | { 51 | var buf bytes.Buffer 52 | SetOutput(ConcurrentWriter(&buf)) 53 | SetFormatter(locationFormat{}) 54 | 55 | testFatalContextLocationWithoutLogger() 56 | 57 | location := buf.String() 58 | switch { 59 | case location == "log.testFatalContextLocationWithoutLogger(github.com/chanxuehong/log/shortcut_without_logger_test.go:14)": 60 | case strings.HasPrefix(location, "log.testFatalContextLocationWithoutLogger(") && strings.HasSuffix(location, "/log/shortcut_without_logger_test.go:14)"): 61 | default: 62 | t.Errorf("not expected location: %s", location) 63 | return 64 | } 65 | } 66 | 67 | // error 68 | { 69 | var buf bytes.Buffer 70 | SetOutput(ConcurrentWriter(&buf)) 71 | SetFormatter(locationFormat{}) 72 | 73 | testErrorContextLocationWithoutLogger() 74 | 75 | location := buf.String() 76 | switch { 77 | case location == "log.testErrorContextLocationWithoutLogger(github.com/chanxuehong/log/shortcut_without_logger_test.go:17)": 78 | case strings.HasPrefix(location, "log.testErrorContextLocationWithoutLogger(") && strings.HasSuffix(location, "/log/shortcut_without_logger_test.go:17)"): 79 | default: 80 | t.Errorf("not expected location: %s", location) 81 | return 82 | } 83 | } 84 | 85 | // warning 86 | { 87 | var buf bytes.Buffer 88 | SetOutput(ConcurrentWriter(&buf)) 89 | SetFormatter(locationFormat{}) 90 | 91 | testWarnContextLocationWithoutLogger() 92 | 93 | location := buf.String() 94 | switch { 95 | case location == "log.testWarnContextLocationWithoutLogger(github.com/chanxuehong/log/shortcut_without_logger_test.go:20)": 96 | case strings.HasPrefix(location, "log.testWarnContextLocationWithoutLogger(") && strings.HasSuffix(location, "/log/shortcut_without_logger_test.go:20)"): 97 | default: 98 | t.Errorf("not expected location: %s", location) 99 | return 100 | } 101 | } 102 | 103 | // info 104 | { 105 | var buf bytes.Buffer 106 | SetOutput(ConcurrentWriter(&buf)) 107 | SetFormatter(locationFormat{}) 108 | 109 | testInfoContextLocationWithoutLogger() 110 | 111 | location := buf.String() 112 | switch { 113 | case location == "log.testInfoContextLocationWithoutLogger(github.com/chanxuehong/log/shortcut_without_logger_test.go:23)": 114 | case strings.HasPrefix(location, "log.testInfoContextLocationWithoutLogger(") && strings.HasSuffix(location, "/log/shortcut_without_logger_test.go:23)"): 115 | default: 116 | t.Errorf("not expected location: %s", location) 117 | return 118 | } 119 | } 120 | 121 | // debug 122 | { 123 | var buf bytes.Buffer 124 | SetOutput(ConcurrentWriter(&buf)) 125 | SetFormatter(locationFormat{}) 126 | 127 | testDebugContextLocationWithoutLogger() 128 | 129 | location := buf.String() 130 | switch { 131 | case location == "log.testDebugContextLocationWithoutLogger(github.com/chanxuehong/log/shortcut_without_logger_test.go:26)": 132 | case strings.HasPrefix(location, "log.testDebugContextLocationWithoutLogger(") && strings.HasSuffix(location, "/log/shortcut_without_logger_test.go:26)"): 133 | default: 134 | t.Errorf("not expected location: %s", location) 135 | return 136 | } 137 | } 138 | 139 | // output 140 | { 141 | var buf bytes.Buffer 142 | SetOutput(ConcurrentWriter(&buf)) 143 | SetFormatter(locationFormat{}) 144 | 145 | testOutputContextLocationWithoutLogger() 146 | 147 | location := buf.String() 148 | switch { 149 | case location == "log.testOutputContextLocationWithoutLogger(github.com/chanxuehong/log/shortcut_without_logger_test.go:29)": 150 | case strings.HasPrefix(location, "log.testOutputContextLocationWithoutLogger(") && strings.HasSuffix(location, "/log/shortcut_without_logger_test.go:29)"): 151 | default: 152 | t.Errorf("not expected location: %s", location) 153 | return 154 | } 155 | } 156 | } 157 | 158 | //type testJsonFormatter struct{} 159 | // 160 | //func (testJsonFormatter) Format(entry *Entry) ([]byte, error) { 161 | // // ignored Entry.Location 162 | // 163 | // // check entry.Time 164 | // t := time.Now() 165 | // d := t.Sub(entry.Time) 166 | // if d > time.Millisecond || d < -time.Millisecond { 167 | // return nil, fmt.Errorf("time mismatch, have:%v, want:%v", entry.Time, t) 168 | // } 169 | // 170 | // // ignored entry.Time 171 | // m := make(map[string]interface{}) 172 | // prefixFieldClashes(entry.Fields) 173 | // m[fieldKeyTraceId] = entry.TraceId 174 | // m[fieldKeyLevel] = entry.Level.String() 175 | // m[fieldKeyMessage] = entry.Message 176 | // for k, v := range entry.Fields { 177 | // m[k] = v 178 | // } 179 | // return json.Marshal(m) 180 | //} 181 | 182 | func TestFatalContextWithoutLogger(t *testing.T) { 183 | defer setWithoutLoggerContextOptionsToDefault() 184 | 185 | var buf bytes.Buffer 186 | SetOutput(ConcurrentWriter(&buf)) 187 | SetFormatter(testJsonFormatter{}) 188 | 189 | FatalContext(testWithoutLoggerContext, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 190 | data := buf.Bytes() 191 | 192 | var have map[string]interface{} 193 | if err := json.Unmarshal(data, &have); err != nil { 194 | t.Error(err.Error()) 195 | return 196 | } 197 | want := map[string]interface{}{ 198 | fieldKeyTraceId: "", 199 | fieldKeyLevel: FatalLevelString, 200 | fieldKeyMessage: "fatal-msg", 201 | "field1-key": "field1-value", 202 | "field2-key": "field2-value", 203 | } 204 | if !reflect.DeepEqual(have, want) { 205 | t.Errorf("\nhave:%v\nwant:%v", have, want) 206 | return 207 | } 208 | } 209 | 210 | func TestErrorContextWithoutLogger(t *testing.T) { 211 | defer setWithoutLoggerContextOptionsToDefault() 212 | 213 | var buf bytes.Buffer 214 | SetOutput(ConcurrentWriter(&buf)) 215 | SetFormatter(testJsonFormatter{}) 216 | 217 | ErrorContext(testWithoutLoggerContext, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 218 | data := buf.Bytes() 219 | 220 | var have map[string]interface{} 221 | if err := json.Unmarshal(data, &have); err != nil { 222 | t.Error(err.Error()) 223 | return 224 | } 225 | want := map[string]interface{}{ 226 | fieldKeyTraceId: "", 227 | fieldKeyLevel: ErrorLevelString, 228 | fieldKeyMessage: "error-msg", 229 | "field1-key": "field1-value", 230 | "field2-key": "field2-value", 231 | } 232 | if !reflect.DeepEqual(have, want) { 233 | t.Errorf("\nhave:%v\nwant:%v", have, want) 234 | return 235 | } 236 | } 237 | 238 | func TestWarnContextWithoutLogger(t *testing.T) { 239 | defer setWithoutLoggerContextOptionsToDefault() 240 | 241 | var buf bytes.Buffer 242 | SetOutput(ConcurrentWriter(&buf)) 243 | SetFormatter(testJsonFormatter{}) 244 | 245 | WarnContext(testWithoutLoggerContext, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 246 | data := buf.Bytes() 247 | 248 | var have map[string]interface{} 249 | if err := json.Unmarshal(data, &have); err != nil { 250 | t.Error(err.Error()) 251 | return 252 | } 253 | want := map[string]interface{}{ 254 | fieldKeyTraceId: "", 255 | fieldKeyLevel: WarnLevelString, 256 | fieldKeyMessage: "warning-msg", 257 | "field1-key": "field1-value", 258 | "field2-key": "field2-value", 259 | } 260 | if !reflect.DeepEqual(have, want) { 261 | t.Errorf("\nhave:%v\nwant:%v", have, want) 262 | return 263 | } 264 | } 265 | 266 | func TestInfoContextWithoutLogger(t *testing.T) { 267 | defer setWithoutLoggerContextOptionsToDefault() 268 | 269 | var buf bytes.Buffer 270 | SetOutput(ConcurrentWriter(&buf)) 271 | SetFormatter(testJsonFormatter{}) 272 | 273 | InfoContext(testWithoutLoggerContext, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 274 | data := buf.Bytes() 275 | 276 | var have map[string]interface{} 277 | if err := json.Unmarshal(data, &have); err != nil { 278 | t.Error(err.Error()) 279 | return 280 | } 281 | want := map[string]interface{}{ 282 | fieldKeyTraceId: "", 283 | fieldKeyLevel: InfoLevelString, 284 | fieldKeyMessage: "info-msg", 285 | "field1-key": "field1-value", 286 | "field2-key": "field2-value", 287 | } 288 | if !reflect.DeepEqual(have, want) { 289 | t.Errorf("\nhave:%v\nwant:%v", have, want) 290 | return 291 | } 292 | } 293 | 294 | func TestDebugContextWithoutLogger(t *testing.T) { 295 | defer setWithoutLoggerContextOptionsToDefault() 296 | 297 | var buf bytes.Buffer 298 | SetOutput(ConcurrentWriter(&buf)) 299 | SetFormatter(testJsonFormatter{}) 300 | 301 | DebugContext(testWithoutLoggerContext, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 302 | data := buf.Bytes() 303 | 304 | var have map[string]interface{} 305 | if err := json.Unmarshal(data, &have); err != nil { 306 | t.Error(err.Error()) 307 | return 308 | } 309 | want := map[string]interface{}{ 310 | fieldKeyTraceId: "", 311 | fieldKeyLevel: DebugLevelString, 312 | fieldKeyMessage: "debug-msg", 313 | "field1-key": "field1-value", 314 | "field2-key": "field2-value", 315 | } 316 | if !reflect.DeepEqual(have, want) { 317 | t.Errorf("\nhave:%v\nwant:%v", have, want) 318 | return 319 | } 320 | } 321 | 322 | func TestOutputContextWithoutLogger(t *testing.T) { 323 | defer setWithoutLoggerContextOptionsToDefault() 324 | 325 | // fatal 326 | { 327 | var buf bytes.Buffer 328 | SetOutput(ConcurrentWriter(&buf)) 329 | SetFormatter(testJsonFormatter{}) 330 | 331 | OutputContext(testWithoutLoggerContext, 0, FatalLevel, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 332 | data := buf.Bytes() 333 | 334 | var have map[string]interface{} 335 | if err := json.Unmarshal(data, &have); err != nil { 336 | t.Error(err.Error()) 337 | return 338 | } 339 | want := map[string]interface{}{ 340 | fieldKeyTraceId: "", 341 | fieldKeyLevel: FatalLevelString, 342 | fieldKeyMessage: "fatal-msg", 343 | "field1-key": "field1-value", 344 | "field2-key": "field2-value", 345 | } 346 | if !reflect.DeepEqual(have, want) { 347 | t.Errorf("\nhave:%v\nwant:%v", have, want) 348 | return 349 | } 350 | } 351 | // error 352 | { 353 | var buf bytes.Buffer 354 | SetOutput(ConcurrentWriter(&buf)) 355 | SetFormatter(testJsonFormatter{}) 356 | 357 | OutputContext(testWithoutLoggerContext, 0, ErrorLevel, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 358 | data := buf.Bytes() 359 | 360 | var have map[string]interface{} 361 | if err := json.Unmarshal(data, &have); err != nil { 362 | t.Error(err.Error()) 363 | return 364 | } 365 | want := map[string]interface{}{ 366 | fieldKeyTraceId: "", 367 | fieldKeyLevel: ErrorLevelString, 368 | fieldKeyMessage: "error-msg", 369 | "field1-key": "field1-value", 370 | "field2-key": "field2-value", 371 | } 372 | if !reflect.DeepEqual(have, want) { 373 | t.Errorf("\nhave:%v\nwant:%v", have, want) 374 | return 375 | } 376 | } 377 | // warning 378 | { 379 | var buf bytes.Buffer 380 | SetOutput(ConcurrentWriter(&buf)) 381 | SetFormatter(testJsonFormatter{}) 382 | 383 | OutputContext(testWithoutLoggerContext, 0, WarnLevel, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 384 | data := buf.Bytes() 385 | 386 | var have map[string]interface{} 387 | if err := json.Unmarshal(data, &have); err != nil { 388 | t.Error(err.Error()) 389 | return 390 | } 391 | want := map[string]interface{}{ 392 | fieldKeyTraceId: "", 393 | fieldKeyLevel: WarnLevelString, 394 | fieldKeyMessage: "warning-msg", 395 | "field1-key": "field1-value", 396 | "field2-key": "field2-value", 397 | } 398 | if !reflect.DeepEqual(have, want) { 399 | t.Errorf("\nhave:%v\nwant:%v", have, want) 400 | return 401 | } 402 | } 403 | // info 404 | { 405 | var buf bytes.Buffer 406 | SetOutput(ConcurrentWriter(&buf)) 407 | SetFormatter(testJsonFormatter{}) 408 | 409 | OutputContext(testWithoutLoggerContext, 0, InfoLevel, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 410 | data := buf.Bytes() 411 | 412 | var have map[string]interface{} 413 | if err := json.Unmarshal(data, &have); err != nil { 414 | t.Error(err.Error()) 415 | return 416 | } 417 | want := map[string]interface{}{ 418 | fieldKeyTraceId: "", 419 | fieldKeyLevel: InfoLevelString, 420 | fieldKeyMessage: "info-msg", 421 | "field1-key": "field1-value", 422 | "field2-key": "field2-value", 423 | } 424 | if !reflect.DeepEqual(have, want) { 425 | t.Errorf("\nhave:%v\nwant:%v", have, want) 426 | return 427 | } 428 | } 429 | // debug 430 | { 431 | var buf bytes.Buffer 432 | SetOutput(ConcurrentWriter(&buf)) 433 | SetFormatter(testJsonFormatter{}) 434 | 435 | OutputContext(testWithoutLoggerContext, 0, DebugLevel, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 436 | data := buf.Bytes() 437 | 438 | var have map[string]interface{} 439 | if err := json.Unmarshal(data, &have); err != nil { 440 | t.Error(err.Error()) 441 | return 442 | } 443 | want := map[string]interface{}{ 444 | fieldKeyTraceId: "", 445 | fieldKeyLevel: DebugLevelString, 446 | fieldKeyMessage: "debug-msg", 447 | "field1-key": "field1-value", 448 | "field2-key": "field2-value", 449 | } 450 | if !reflect.DeepEqual(have, want) { 451 | t.Errorf("\nhave:%v\nwant:%v", have, want) 452 | return 453 | } 454 | } 455 | // fatal - 1 456 | { 457 | var buf bytes.Buffer 458 | SetOutput(ConcurrentWriter(&buf)) 459 | SetFormatter(testJsonFormatter{}) 460 | 461 | OutputContext(testWithoutLoggerContext, 0, FatalLevel-1, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 462 | data := buf.Bytes() 463 | if len(data) != 0 { 464 | t.Errorf("want empty, but now is: %s", data) 465 | return 466 | } 467 | } 468 | // debug + 1 469 | { 470 | var buf bytes.Buffer 471 | SetOutput(ConcurrentWriter(&buf)) 472 | SetFormatter(testJsonFormatter{}) 473 | 474 | OutputContext(testWithoutLoggerContext, 0, DebugLevel+1, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 475 | data := buf.Bytes() 476 | if len(data) != 0 { 477 | t.Errorf("want empty, but now is: %s", data) 478 | return 479 | } 480 | } 481 | } 482 | 483 | func TestWithFieldContextWithoutLogger(t *testing.T) { 484 | defer setWithoutLoggerContextOptionsToDefault() 485 | 486 | var buf bytes.Buffer 487 | SetOutput(ConcurrentWriter(&buf)) 488 | SetFormatter(testJsonFormatter{}) 489 | 490 | { 491 | l := WithFieldContext(testWithoutLoggerContext, "field100-key", "field100-value") 492 | l.Error("error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 493 | data := buf.Bytes() 494 | 495 | var have map[string]interface{} 496 | if err := json.Unmarshal(data, &have); err != nil { 497 | t.Error(err.Error()) 498 | return 499 | } 500 | want := map[string]interface{}{ 501 | fieldKeyTraceId: "", 502 | fieldKeyLevel: ErrorLevelString, 503 | fieldKeyMessage: "error-msg", 504 | "field1-key": "field1-value", 505 | "field2-key": "field2-value", 506 | "field100-key": "field100-value", 507 | } 508 | if !reflect.DeepEqual(have, want) { 509 | t.Errorf("\nhave:%v\nwant:%v", have, want) 510 | return 511 | } 512 | } 513 | 514 | buf.Reset() 515 | 516 | // WithField cannot affect original logger 517 | { 518 | ErrorContext(testWithoutLoggerContext, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 519 | data := buf.Bytes() 520 | 521 | var have map[string]interface{} 522 | if err := json.Unmarshal(data, &have); err != nil { 523 | t.Error(err.Error()) 524 | return 525 | } 526 | want := map[string]interface{}{ 527 | fieldKeyTraceId: "", 528 | fieldKeyLevel: ErrorLevelString, 529 | fieldKeyMessage: "error-msg", 530 | "field1-key": "field1-value", 531 | "field2-key": "field2-value", 532 | } 533 | if !reflect.DeepEqual(have, want) { 534 | t.Errorf("\nhave:%v\nwant:%v", have, want) 535 | return 536 | } 537 | } 538 | } 539 | 540 | func TestWithFieldsContextWithoutLogger(t *testing.T) { 541 | defer setWithoutLoggerContextOptionsToDefault() 542 | 543 | var buf bytes.Buffer 544 | SetOutput(ConcurrentWriter(&buf)) 545 | SetFormatter(testJsonFormatter{}) 546 | 547 | { 548 | l := WithFieldsContext(testWithoutLoggerContext, "field100-key", "field100-value", "field200-key", "field200-value") 549 | l.Error("error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 550 | data := buf.Bytes() 551 | 552 | var have map[string]interface{} 553 | if err := json.Unmarshal(data, &have); err != nil { 554 | t.Error(err.Error()) 555 | return 556 | } 557 | want := map[string]interface{}{ 558 | fieldKeyTraceId: "", 559 | fieldKeyLevel: ErrorLevelString, 560 | fieldKeyMessage: "error-msg", 561 | "field1-key": "field1-value", 562 | "field2-key": "field2-value", 563 | "field100-key": "field100-value", 564 | "field200-key": "field200-value", 565 | } 566 | if !reflect.DeepEqual(have, want) { 567 | t.Errorf("\nhave:%v\nwant:%v", have, want) 568 | return 569 | } 570 | } 571 | 572 | buf.Reset() 573 | 574 | // WithFields cannot affect original logger 575 | { 576 | ErrorContext(testWithoutLoggerContext, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 577 | data := buf.Bytes() 578 | 579 | var have map[string]interface{} 580 | if err := json.Unmarshal(data, &have); err != nil { 581 | t.Error(err.Error()) 582 | return 583 | } 584 | want := map[string]interface{}{ 585 | fieldKeyTraceId: "", 586 | fieldKeyLevel: ErrorLevelString, 587 | fieldKeyMessage: "error-msg", 588 | "field1-key": "field1-value", 589 | "field2-key": "field2-value", 590 | } 591 | if !reflect.DeepEqual(have, want) { 592 | t.Errorf("\nhave:%v\nwant:%v", have, want) 593 | return 594 | } 595 | } 596 | } 597 | 598 | func TestLeveledOutputContextWithoutLogger(t *testing.T) { 599 | defer setWithoutLoggerContextOptionsToDefault() 600 | 601 | // fatal 602 | { 603 | SetLevel(FatalLevel) 604 | 605 | // fatal 606 | { 607 | var buf bytes.Buffer 608 | SetOutput(ConcurrentWriter(&buf)) 609 | SetFormatter(testJsonFormatter{}) 610 | 611 | OutputContext(testWithoutLoggerContext, 0, FatalLevel, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 612 | data := buf.Bytes() 613 | 614 | var have map[string]interface{} 615 | if err := json.Unmarshal(data, &have); err != nil { 616 | t.Error(err.Error()) 617 | return 618 | } 619 | want := map[string]interface{}{ 620 | fieldKeyTraceId: "", 621 | fieldKeyLevel: FatalLevelString, 622 | fieldKeyMessage: "fatal-msg", 623 | "field1-key": "field1-value", 624 | "field2-key": "field2-value", 625 | } 626 | if !reflect.DeepEqual(have, want) { 627 | t.Errorf("\nhave:%v\nwant:%v", have, want) 628 | return 629 | } 630 | } 631 | // error 632 | { 633 | var buf bytes.Buffer 634 | SetOutput(ConcurrentWriter(&buf)) 635 | SetFormatter(testJsonFormatter{}) 636 | 637 | OutputContext(testWithoutLoggerContext, 0, ErrorLevel, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 638 | data := buf.Bytes() 639 | if len(data) != 0 { 640 | t.Errorf("want empty, but now is: %s", data) 641 | return 642 | } 643 | } 644 | // warning 645 | { 646 | var buf bytes.Buffer 647 | SetOutput(ConcurrentWriter(&buf)) 648 | SetFormatter(testJsonFormatter{}) 649 | 650 | OutputContext(testWithoutLoggerContext, 0, WarnLevel, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 651 | data := buf.Bytes() 652 | if len(data) != 0 { 653 | t.Errorf("want empty, but now is: %s", data) 654 | return 655 | } 656 | } 657 | // info 658 | { 659 | var buf bytes.Buffer 660 | SetOutput(ConcurrentWriter(&buf)) 661 | SetFormatter(testJsonFormatter{}) 662 | 663 | OutputContext(testWithoutLoggerContext, 0, InfoLevel, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 664 | data := buf.Bytes() 665 | if len(data) != 0 { 666 | t.Errorf("want empty, but now is: %s", data) 667 | return 668 | } 669 | } 670 | // debug 671 | { 672 | var buf bytes.Buffer 673 | SetOutput(ConcurrentWriter(&buf)) 674 | SetFormatter(testJsonFormatter{}) 675 | 676 | OutputContext(testWithoutLoggerContext, 0, DebugLevel, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 677 | data := buf.Bytes() 678 | if len(data) != 0 { 679 | t.Errorf("want empty, but now is: %s", data) 680 | return 681 | } 682 | } 683 | } 684 | 685 | // error 686 | { 687 | SetLevel(ErrorLevel) 688 | 689 | // fatal 690 | { 691 | var buf bytes.Buffer 692 | SetOutput(ConcurrentWriter(&buf)) 693 | SetFormatter(testJsonFormatter{}) 694 | 695 | OutputContext(testWithoutLoggerContext, 0, FatalLevel, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 696 | data := buf.Bytes() 697 | 698 | var have map[string]interface{} 699 | if err := json.Unmarshal(data, &have); err != nil { 700 | t.Error(err.Error()) 701 | return 702 | } 703 | want := map[string]interface{}{ 704 | fieldKeyTraceId: "", 705 | fieldKeyLevel: FatalLevelString, 706 | fieldKeyMessage: "fatal-msg", 707 | "field1-key": "field1-value", 708 | "field2-key": "field2-value", 709 | } 710 | if !reflect.DeepEqual(have, want) { 711 | t.Errorf("\nhave:%v\nwant:%v", have, want) 712 | return 713 | } 714 | } 715 | // error 716 | { 717 | var buf bytes.Buffer 718 | SetOutput(ConcurrentWriter(&buf)) 719 | SetFormatter(testJsonFormatter{}) 720 | 721 | OutputContext(testWithoutLoggerContext, 0, ErrorLevel, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 722 | data := buf.Bytes() 723 | 724 | var have map[string]interface{} 725 | if err := json.Unmarshal(data, &have); err != nil { 726 | t.Error(err.Error()) 727 | return 728 | } 729 | want := map[string]interface{}{ 730 | fieldKeyTraceId: "", 731 | fieldKeyLevel: ErrorLevelString, 732 | fieldKeyMessage: "error-msg", 733 | "field1-key": "field1-value", 734 | "field2-key": "field2-value", 735 | } 736 | if !reflect.DeepEqual(have, want) { 737 | t.Errorf("\nhave:%v\nwant:%v", have, want) 738 | return 739 | } 740 | } 741 | // warning 742 | { 743 | var buf bytes.Buffer 744 | SetOutput(ConcurrentWriter(&buf)) 745 | SetFormatter(testJsonFormatter{}) 746 | 747 | OutputContext(testWithoutLoggerContext, 0, WarnLevel, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 748 | data := buf.Bytes() 749 | if len(data) != 0 { 750 | t.Errorf("want empty, but now is: %s", data) 751 | return 752 | } 753 | } 754 | // info 755 | { 756 | var buf bytes.Buffer 757 | SetOutput(ConcurrentWriter(&buf)) 758 | SetFormatter(testJsonFormatter{}) 759 | 760 | OutputContext(testWithoutLoggerContext, 0, InfoLevel, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 761 | data := buf.Bytes() 762 | if len(data) != 0 { 763 | t.Errorf("want empty, but now is: %s", data) 764 | return 765 | } 766 | } 767 | // debug 768 | { 769 | var buf bytes.Buffer 770 | SetOutput(ConcurrentWriter(&buf)) 771 | SetFormatter(testJsonFormatter{}) 772 | 773 | OutputContext(testWithoutLoggerContext, 0, DebugLevel, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 774 | data := buf.Bytes() 775 | if len(data) != 0 { 776 | t.Errorf("want empty, but now is: %s", data) 777 | return 778 | } 779 | } 780 | } 781 | 782 | // warning 783 | { 784 | SetLevel(WarnLevel) 785 | 786 | // fatal 787 | { 788 | var buf bytes.Buffer 789 | SetOutput(ConcurrentWriter(&buf)) 790 | SetFormatter(testJsonFormatter{}) 791 | 792 | OutputContext(testWithoutLoggerContext, 0, FatalLevel, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 793 | data := buf.Bytes() 794 | 795 | var have map[string]interface{} 796 | if err := json.Unmarshal(data, &have); err != nil { 797 | t.Error(err.Error()) 798 | return 799 | } 800 | want := map[string]interface{}{ 801 | fieldKeyTraceId: "", 802 | fieldKeyLevel: FatalLevelString, 803 | fieldKeyMessage: "fatal-msg", 804 | "field1-key": "field1-value", 805 | "field2-key": "field2-value", 806 | } 807 | if !reflect.DeepEqual(have, want) { 808 | t.Errorf("\nhave:%v\nwant:%v", have, want) 809 | return 810 | } 811 | } 812 | // error 813 | { 814 | var buf bytes.Buffer 815 | SetOutput(ConcurrentWriter(&buf)) 816 | SetFormatter(testJsonFormatter{}) 817 | 818 | OutputContext(testWithoutLoggerContext, 0, ErrorLevel, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 819 | data := buf.Bytes() 820 | 821 | var have map[string]interface{} 822 | if err := json.Unmarshal(data, &have); err != nil { 823 | t.Error(err.Error()) 824 | return 825 | } 826 | want := map[string]interface{}{ 827 | fieldKeyTraceId: "", 828 | fieldKeyLevel: ErrorLevelString, 829 | fieldKeyMessage: "error-msg", 830 | "field1-key": "field1-value", 831 | "field2-key": "field2-value", 832 | } 833 | if !reflect.DeepEqual(have, want) { 834 | t.Errorf("\nhave:%v\nwant:%v", have, want) 835 | return 836 | } 837 | } 838 | // warning 839 | { 840 | var buf bytes.Buffer 841 | SetOutput(ConcurrentWriter(&buf)) 842 | SetFormatter(testJsonFormatter{}) 843 | 844 | OutputContext(testWithoutLoggerContext, 0, WarnLevel, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 845 | data := buf.Bytes() 846 | 847 | var have map[string]interface{} 848 | if err := json.Unmarshal(data, &have); err != nil { 849 | t.Error(err.Error()) 850 | return 851 | } 852 | want := map[string]interface{}{ 853 | fieldKeyTraceId: "", 854 | fieldKeyLevel: WarnLevelString, 855 | fieldKeyMessage: "warning-msg", 856 | "field1-key": "field1-value", 857 | "field2-key": "field2-value", 858 | } 859 | if !reflect.DeepEqual(have, want) { 860 | t.Errorf("\nhave:%v\nwant:%v", have, want) 861 | return 862 | } 863 | } 864 | // info 865 | { 866 | var buf bytes.Buffer 867 | SetOutput(ConcurrentWriter(&buf)) 868 | SetFormatter(testJsonFormatter{}) 869 | 870 | OutputContext(testWithoutLoggerContext, 0, InfoLevel, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 871 | data := buf.Bytes() 872 | if len(data) != 0 { 873 | t.Errorf("want empty, but now is: %s", data) 874 | return 875 | } 876 | } 877 | // debug 878 | { 879 | var buf bytes.Buffer 880 | SetOutput(ConcurrentWriter(&buf)) 881 | SetFormatter(testJsonFormatter{}) 882 | 883 | OutputContext(testWithoutLoggerContext, 0, DebugLevel, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 884 | data := buf.Bytes() 885 | if len(data) != 0 { 886 | t.Errorf("want empty, but now is: %s", data) 887 | return 888 | } 889 | } 890 | } 891 | 892 | // info 893 | { 894 | SetLevel(InfoLevel) 895 | 896 | // fatal 897 | { 898 | var buf bytes.Buffer 899 | SetOutput(ConcurrentWriter(&buf)) 900 | SetFormatter(testJsonFormatter{}) 901 | 902 | OutputContext(testWithoutLoggerContext, 0, FatalLevel, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 903 | data := buf.Bytes() 904 | 905 | var have map[string]interface{} 906 | if err := json.Unmarshal(data, &have); err != nil { 907 | t.Error(err.Error()) 908 | return 909 | } 910 | want := map[string]interface{}{ 911 | fieldKeyTraceId: "", 912 | fieldKeyLevel: FatalLevelString, 913 | fieldKeyMessage: "fatal-msg", 914 | "field1-key": "field1-value", 915 | "field2-key": "field2-value", 916 | } 917 | if !reflect.DeepEqual(have, want) { 918 | t.Errorf("\nhave:%v\nwant:%v", have, want) 919 | return 920 | } 921 | } 922 | // error 923 | { 924 | var buf bytes.Buffer 925 | SetOutput(ConcurrentWriter(&buf)) 926 | SetFormatter(testJsonFormatter{}) 927 | 928 | OutputContext(testWithoutLoggerContext, 0, ErrorLevel, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 929 | data := buf.Bytes() 930 | 931 | var have map[string]interface{} 932 | if err := json.Unmarshal(data, &have); err != nil { 933 | t.Error(err.Error()) 934 | return 935 | } 936 | want := map[string]interface{}{ 937 | fieldKeyTraceId: "", 938 | fieldKeyLevel: ErrorLevelString, 939 | fieldKeyMessage: "error-msg", 940 | "field1-key": "field1-value", 941 | "field2-key": "field2-value", 942 | } 943 | if !reflect.DeepEqual(have, want) { 944 | t.Errorf("\nhave:%v\nwant:%v", have, want) 945 | return 946 | } 947 | } 948 | // warning 949 | { 950 | var buf bytes.Buffer 951 | SetOutput(ConcurrentWriter(&buf)) 952 | SetFormatter(testJsonFormatter{}) 953 | 954 | OutputContext(testWithoutLoggerContext, 0, WarnLevel, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 955 | data := buf.Bytes() 956 | 957 | var have map[string]interface{} 958 | if err := json.Unmarshal(data, &have); err != nil { 959 | t.Error(err.Error()) 960 | return 961 | } 962 | want := map[string]interface{}{ 963 | fieldKeyTraceId: "", 964 | fieldKeyLevel: WarnLevelString, 965 | fieldKeyMessage: "warning-msg", 966 | "field1-key": "field1-value", 967 | "field2-key": "field2-value", 968 | } 969 | if !reflect.DeepEqual(have, want) { 970 | t.Errorf("\nhave:%v\nwant:%v", have, want) 971 | return 972 | } 973 | } 974 | // info 975 | { 976 | var buf bytes.Buffer 977 | SetOutput(ConcurrentWriter(&buf)) 978 | SetFormatter(testJsonFormatter{}) 979 | 980 | OutputContext(testWithoutLoggerContext, 0, InfoLevel, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 981 | data := buf.Bytes() 982 | 983 | var have map[string]interface{} 984 | if err := json.Unmarshal(data, &have); err != nil { 985 | t.Error(err.Error()) 986 | return 987 | } 988 | want := map[string]interface{}{ 989 | fieldKeyTraceId: "", 990 | fieldKeyLevel: InfoLevelString, 991 | fieldKeyMessage: "info-msg", 992 | "field1-key": "field1-value", 993 | "field2-key": "field2-value", 994 | } 995 | if !reflect.DeepEqual(have, want) { 996 | t.Errorf("\nhave:%v\nwant:%v", have, want) 997 | return 998 | } 999 | } 1000 | // debug 1001 | { 1002 | var buf bytes.Buffer 1003 | SetOutput(ConcurrentWriter(&buf)) 1004 | SetFormatter(testJsonFormatter{}) 1005 | 1006 | OutputContext(testWithoutLoggerContext, 0, DebugLevel, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1007 | data := buf.Bytes() 1008 | if len(data) != 0 { 1009 | t.Errorf("want empty, but now is: %s", data) 1010 | return 1011 | } 1012 | } 1013 | } 1014 | 1015 | // debug 1016 | { 1017 | SetLevel(DebugLevel) 1018 | 1019 | // fatal 1020 | { 1021 | var buf bytes.Buffer 1022 | SetOutput(ConcurrentWriter(&buf)) 1023 | SetFormatter(testJsonFormatter{}) 1024 | 1025 | OutputContext(testWithoutLoggerContext, 0, FatalLevel, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1026 | data := buf.Bytes() 1027 | 1028 | var have map[string]interface{} 1029 | if err := json.Unmarshal(data, &have); err != nil { 1030 | t.Error(err.Error()) 1031 | return 1032 | } 1033 | want := map[string]interface{}{ 1034 | fieldKeyTraceId: "", 1035 | fieldKeyLevel: FatalLevelString, 1036 | fieldKeyMessage: "fatal-msg", 1037 | "field1-key": "field1-value", 1038 | "field2-key": "field2-value", 1039 | } 1040 | if !reflect.DeepEqual(have, want) { 1041 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1042 | return 1043 | } 1044 | } 1045 | // error 1046 | { 1047 | var buf bytes.Buffer 1048 | SetOutput(ConcurrentWriter(&buf)) 1049 | SetFormatter(testJsonFormatter{}) 1050 | 1051 | OutputContext(testWithoutLoggerContext, 0, ErrorLevel, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1052 | data := buf.Bytes() 1053 | 1054 | var have map[string]interface{} 1055 | if err := json.Unmarshal(data, &have); err != nil { 1056 | t.Error(err.Error()) 1057 | return 1058 | } 1059 | want := map[string]interface{}{ 1060 | fieldKeyTraceId: "", 1061 | fieldKeyLevel: ErrorLevelString, 1062 | fieldKeyMessage: "error-msg", 1063 | "field1-key": "field1-value", 1064 | "field2-key": "field2-value", 1065 | } 1066 | if !reflect.DeepEqual(have, want) { 1067 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1068 | return 1069 | } 1070 | } 1071 | // warning 1072 | { 1073 | var buf bytes.Buffer 1074 | SetOutput(ConcurrentWriter(&buf)) 1075 | SetFormatter(testJsonFormatter{}) 1076 | 1077 | OutputContext(testWithoutLoggerContext, 0, WarnLevel, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1078 | data := buf.Bytes() 1079 | 1080 | var have map[string]interface{} 1081 | if err := json.Unmarshal(data, &have); err != nil { 1082 | t.Error(err.Error()) 1083 | return 1084 | } 1085 | want := map[string]interface{}{ 1086 | fieldKeyTraceId: "", 1087 | fieldKeyLevel: WarnLevelString, 1088 | fieldKeyMessage: "warning-msg", 1089 | "field1-key": "field1-value", 1090 | "field2-key": "field2-value", 1091 | } 1092 | if !reflect.DeepEqual(have, want) { 1093 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1094 | return 1095 | } 1096 | } 1097 | // info 1098 | { 1099 | var buf bytes.Buffer 1100 | SetOutput(ConcurrentWriter(&buf)) 1101 | SetFormatter(testJsonFormatter{}) 1102 | 1103 | OutputContext(testWithoutLoggerContext, 0, InfoLevel, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1104 | data := buf.Bytes() 1105 | 1106 | var have map[string]interface{} 1107 | if err := json.Unmarshal(data, &have); err != nil { 1108 | t.Error(err.Error()) 1109 | return 1110 | } 1111 | want := map[string]interface{}{ 1112 | fieldKeyTraceId: "", 1113 | fieldKeyLevel: InfoLevelString, 1114 | fieldKeyMessage: "info-msg", 1115 | "field1-key": "field1-value", 1116 | "field2-key": "field2-value", 1117 | } 1118 | if !reflect.DeepEqual(have, want) { 1119 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1120 | return 1121 | } 1122 | } 1123 | // debug 1124 | { 1125 | var buf bytes.Buffer 1126 | SetOutput(ConcurrentWriter(&buf)) 1127 | SetFormatter(testJsonFormatter{}) 1128 | 1129 | OutputContext(testWithoutLoggerContext, 0, DebugLevel, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1130 | data := buf.Bytes() 1131 | 1132 | var have map[string]interface{} 1133 | if err := json.Unmarshal(data, &have); err != nil { 1134 | t.Error(err.Error()) 1135 | return 1136 | } 1137 | want := map[string]interface{}{ 1138 | fieldKeyTraceId: "", 1139 | fieldKeyLevel: DebugLevelString, 1140 | fieldKeyMessage: "debug-msg", 1141 | "field1-key": "field1-value", 1142 | "field2-key": "field2-value", 1143 | } 1144 | if !reflect.DeepEqual(have, want) { 1145 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1146 | return 1147 | } 1148 | } 1149 | } 1150 | } 1151 | 1152 | func TestLeveledPrintContextWithoutLogger(t *testing.T) { 1153 | defer setWithoutLoggerContextOptionsToDefault() 1154 | 1155 | // fatal 1156 | { 1157 | SetLevel(FatalLevel) 1158 | 1159 | // fatal 1160 | { 1161 | var buf bytes.Buffer 1162 | SetOutput(ConcurrentWriter(&buf)) 1163 | SetFormatter(testJsonFormatter{}) 1164 | 1165 | FatalContext(testWithoutLoggerContext, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1166 | data := buf.Bytes() 1167 | 1168 | var have map[string]interface{} 1169 | if err := json.Unmarshal(data, &have); err != nil { 1170 | t.Error(err.Error()) 1171 | return 1172 | } 1173 | want := map[string]interface{}{ 1174 | fieldKeyTraceId: "", 1175 | fieldKeyLevel: FatalLevelString, 1176 | fieldKeyMessage: "fatal-msg", 1177 | "field1-key": "field1-value", 1178 | "field2-key": "field2-value", 1179 | } 1180 | if !reflect.DeepEqual(have, want) { 1181 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1182 | return 1183 | } 1184 | } 1185 | // error 1186 | { 1187 | var buf bytes.Buffer 1188 | SetOutput(ConcurrentWriter(&buf)) 1189 | SetFormatter(testJsonFormatter{}) 1190 | 1191 | ErrorContext(testWithoutLoggerContext, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1192 | data := buf.Bytes() 1193 | if len(data) != 0 { 1194 | t.Errorf("want empty, but now is: %s", data) 1195 | return 1196 | } 1197 | } 1198 | // warning 1199 | { 1200 | var buf bytes.Buffer 1201 | SetOutput(ConcurrentWriter(&buf)) 1202 | SetFormatter(testJsonFormatter{}) 1203 | 1204 | WarnContext(testWithoutLoggerContext, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1205 | data := buf.Bytes() 1206 | if len(data) != 0 { 1207 | t.Errorf("want empty, but now is: %s", data) 1208 | return 1209 | } 1210 | } 1211 | // info 1212 | { 1213 | var buf bytes.Buffer 1214 | SetOutput(ConcurrentWriter(&buf)) 1215 | SetFormatter(testJsonFormatter{}) 1216 | 1217 | InfoContext(testWithoutLoggerContext, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1218 | data := buf.Bytes() 1219 | if len(data) != 0 { 1220 | t.Errorf("want empty, but now is: %s", data) 1221 | return 1222 | } 1223 | } 1224 | // debug 1225 | { 1226 | var buf bytes.Buffer 1227 | SetOutput(ConcurrentWriter(&buf)) 1228 | SetFormatter(testJsonFormatter{}) 1229 | 1230 | DebugContext(testWithoutLoggerContext, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1231 | data := buf.Bytes() 1232 | if len(data) != 0 { 1233 | t.Errorf("want empty, but now is: %s", data) 1234 | return 1235 | } 1236 | } 1237 | } 1238 | 1239 | // error 1240 | { 1241 | SetLevel(ErrorLevel) 1242 | 1243 | // fatal 1244 | { 1245 | var buf bytes.Buffer 1246 | SetOutput(ConcurrentWriter(&buf)) 1247 | SetFormatter(testJsonFormatter{}) 1248 | 1249 | FatalContext(testWithoutLoggerContext, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1250 | data := buf.Bytes() 1251 | 1252 | var have map[string]interface{} 1253 | if err := json.Unmarshal(data, &have); err != nil { 1254 | t.Error(err.Error()) 1255 | return 1256 | } 1257 | want := map[string]interface{}{ 1258 | fieldKeyTraceId: "", 1259 | fieldKeyLevel: FatalLevelString, 1260 | fieldKeyMessage: "fatal-msg", 1261 | "field1-key": "field1-value", 1262 | "field2-key": "field2-value", 1263 | } 1264 | if !reflect.DeepEqual(have, want) { 1265 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1266 | return 1267 | } 1268 | } 1269 | // error 1270 | { 1271 | var buf bytes.Buffer 1272 | SetOutput(ConcurrentWriter(&buf)) 1273 | SetFormatter(testJsonFormatter{}) 1274 | 1275 | ErrorContext(testWithoutLoggerContext, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1276 | data := buf.Bytes() 1277 | 1278 | var have map[string]interface{} 1279 | if err := json.Unmarshal(data, &have); err != nil { 1280 | t.Error(err.Error()) 1281 | return 1282 | } 1283 | want := map[string]interface{}{ 1284 | fieldKeyTraceId: "", 1285 | fieldKeyLevel: ErrorLevelString, 1286 | fieldKeyMessage: "error-msg", 1287 | "field1-key": "field1-value", 1288 | "field2-key": "field2-value", 1289 | } 1290 | if !reflect.DeepEqual(have, want) { 1291 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1292 | return 1293 | } 1294 | } 1295 | // warning 1296 | { 1297 | var buf bytes.Buffer 1298 | SetOutput(ConcurrentWriter(&buf)) 1299 | SetFormatter(testJsonFormatter{}) 1300 | 1301 | WarnContext(testWithoutLoggerContext, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1302 | data := buf.Bytes() 1303 | if len(data) != 0 { 1304 | t.Errorf("want empty, but now is: %s", data) 1305 | return 1306 | } 1307 | } 1308 | // info 1309 | { 1310 | var buf bytes.Buffer 1311 | SetOutput(ConcurrentWriter(&buf)) 1312 | SetFormatter(testJsonFormatter{}) 1313 | 1314 | InfoContext(testWithoutLoggerContext, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1315 | data := buf.Bytes() 1316 | if len(data) != 0 { 1317 | t.Errorf("want empty, but now is: %s", data) 1318 | return 1319 | } 1320 | } 1321 | // debug 1322 | { 1323 | var buf bytes.Buffer 1324 | SetOutput(ConcurrentWriter(&buf)) 1325 | SetFormatter(testJsonFormatter{}) 1326 | 1327 | DebugContext(testWithoutLoggerContext, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1328 | data := buf.Bytes() 1329 | if len(data) != 0 { 1330 | t.Errorf("want empty, but now is: %s", data) 1331 | return 1332 | } 1333 | } 1334 | } 1335 | 1336 | // warning 1337 | { 1338 | SetLevel(WarnLevel) 1339 | 1340 | // fatal 1341 | { 1342 | var buf bytes.Buffer 1343 | SetOutput(ConcurrentWriter(&buf)) 1344 | SetFormatter(testJsonFormatter{}) 1345 | 1346 | FatalContext(testWithoutLoggerContext, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1347 | data := buf.Bytes() 1348 | 1349 | var have map[string]interface{} 1350 | if err := json.Unmarshal(data, &have); err != nil { 1351 | t.Error(err.Error()) 1352 | return 1353 | } 1354 | want := map[string]interface{}{ 1355 | fieldKeyTraceId: "", 1356 | fieldKeyLevel: FatalLevelString, 1357 | fieldKeyMessage: "fatal-msg", 1358 | "field1-key": "field1-value", 1359 | "field2-key": "field2-value", 1360 | } 1361 | if !reflect.DeepEqual(have, want) { 1362 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1363 | return 1364 | } 1365 | } 1366 | // error 1367 | { 1368 | var buf bytes.Buffer 1369 | SetOutput(ConcurrentWriter(&buf)) 1370 | SetFormatter(testJsonFormatter{}) 1371 | 1372 | ErrorContext(testWithoutLoggerContext, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1373 | data := buf.Bytes() 1374 | 1375 | var have map[string]interface{} 1376 | if err := json.Unmarshal(data, &have); err != nil { 1377 | t.Error(err.Error()) 1378 | return 1379 | } 1380 | want := map[string]interface{}{ 1381 | fieldKeyTraceId: "", 1382 | fieldKeyLevel: ErrorLevelString, 1383 | fieldKeyMessage: "error-msg", 1384 | "field1-key": "field1-value", 1385 | "field2-key": "field2-value", 1386 | } 1387 | if !reflect.DeepEqual(have, want) { 1388 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1389 | return 1390 | } 1391 | } 1392 | // warning 1393 | { 1394 | var buf bytes.Buffer 1395 | SetOutput(ConcurrentWriter(&buf)) 1396 | SetFormatter(testJsonFormatter{}) 1397 | 1398 | WarnContext(testWithoutLoggerContext, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1399 | data := buf.Bytes() 1400 | 1401 | var have map[string]interface{} 1402 | if err := json.Unmarshal(data, &have); err != nil { 1403 | t.Error(err.Error()) 1404 | return 1405 | } 1406 | want := map[string]interface{}{ 1407 | fieldKeyTraceId: "", 1408 | fieldKeyLevel: WarnLevelString, 1409 | fieldKeyMessage: "warning-msg", 1410 | "field1-key": "field1-value", 1411 | "field2-key": "field2-value", 1412 | } 1413 | if !reflect.DeepEqual(have, want) { 1414 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1415 | return 1416 | } 1417 | } 1418 | // info 1419 | { 1420 | var buf bytes.Buffer 1421 | SetOutput(ConcurrentWriter(&buf)) 1422 | SetFormatter(testJsonFormatter{}) 1423 | 1424 | InfoContext(testWithoutLoggerContext, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1425 | data := buf.Bytes() 1426 | if len(data) != 0 { 1427 | t.Errorf("want empty, but now is: %s", data) 1428 | return 1429 | } 1430 | } 1431 | // debug 1432 | { 1433 | var buf bytes.Buffer 1434 | SetOutput(ConcurrentWriter(&buf)) 1435 | SetFormatter(testJsonFormatter{}) 1436 | 1437 | DebugContext(testWithoutLoggerContext, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1438 | data := buf.Bytes() 1439 | if len(data) != 0 { 1440 | t.Errorf("want empty, but now is: %s", data) 1441 | return 1442 | } 1443 | } 1444 | } 1445 | 1446 | // info 1447 | { 1448 | SetLevel(InfoLevel) 1449 | 1450 | // fatal 1451 | { 1452 | var buf bytes.Buffer 1453 | SetOutput(ConcurrentWriter(&buf)) 1454 | SetFormatter(testJsonFormatter{}) 1455 | 1456 | FatalContext(testWithoutLoggerContext, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1457 | data := buf.Bytes() 1458 | 1459 | var have map[string]interface{} 1460 | if err := json.Unmarshal(data, &have); err != nil { 1461 | t.Error(err.Error()) 1462 | return 1463 | } 1464 | want := map[string]interface{}{ 1465 | fieldKeyTraceId: "", 1466 | fieldKeyLevel: FatalLevelString, 1467 | fieldKeyMessage: "fatal-msg", 1468 | "field1-key": "field1-value", 1469 | "field2-key": "field2-value", 1470 | } 1471 | if !reflect.DeepEqual(have, want) { 1472 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1473 | return 1474 | } 1475 | } 1476 | // error 1477 | { 1478 | var buf bytes.Buffer 1479 | SetOutput(ConcurrentWriter(&buf)) 1480 | SetFormatter(testJsonFormatter{}) 1481 | 1482 | ErrorContext(testWithoutLoggerContext, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1483 | data := buf.Bytes() 1484 | 1485 | var have map[string]interface{} 1486 | if err := json.Unmarshal(data, &have); err != nil { 1487 | t.Error(err.Error()) 1488 | return 1489 | } 1490 | want := map[string]interface{}{ 1491 | fieldKeyTraceId: "", 1492 | fieldKeyLevel: ErrorLevelString, 1493 | fieldKeyMessage: "error-msg", 1494 | "field1-key": "field1-value", 1495 | "field2-key": "field2-value", 1496 | } 1497 | if !reflect.DeepEqual(have, want) { 1498 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1499 | return 1500 | } 1501 | } 1502 | // warning 1503 | { 1504 | var buf bytes.Buffer 1505 | SetOutput(ConcurrentWriter(&buf)) 1506 | SetFormatter(testJsonFormatter{}) 1507 | 1508 | WarnContext(testWithoutLoggerContext, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1509 | data := buf.Bytes() 1510 | 1511 | var have map[string]interface{} 1512 | if err := json.Unmarshal(data, &have); err != nil { 1513 | t.Error(err.Error()) 1514 | return 1515 | } 1516 | want := map[string]interface{}{ 1517 | fieldKeyTraceId: "", 1518 | fieldKeyLevel: WarnLevelString, 1519 | fieldKeyMessage: "warning-msg", 1520 | "field1-key": "field1-value", 1521 | "field2-key": "field2-value", 1522 | } 1523 | if !reflect.DeepEqual(have, want) { 1524 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1525 | return 1526 | } 1527 | } 1528 | // info 1529 | { 1530 | var buf bytes.Buffer 1531 | SetOutput(ConcurrentWriter(&buf)) 1532 | SetFormatter(testJsonFormatter{}) 1533 | 1534 | InfoContext(testWithoutLoggerContext, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1535 | data := buf.Bytes() 1536 | 1537 | var have map[string]interface{} 1538 | if err := json.Unmarshal(data, &have); err != nil { 1539 | t.Error(err.Error()) 1540 | return 1541 | } 1542 | want := map[string]interface{}{ 1543 | fieldKeyTraceId: "", 1544 | fieldKeyLevel: InfoLevelString, 1545 | fieldKeyMessage: "info-msg", 1546 | "field1-key": "field1-value", 1547 | "field2-key": "field2-value", 1548 | } 1549 | if !reflect.DeepEqual(have, want) { 1550 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1551 | return 1552 | } 1553 | } 1554 | // debug 1555 | { 1556 | var buf bytes.Buffer 1557 | SetOutput(ConcurrentWriter(&buf)) 1558 | SetFormatter(testJsonFormatter{}) 1559 | 1560 | DebugContext(testWithoutLoggerContext, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1561 | data := buf.Bytes() 1562 | if len(data) != 0 { 1563 | t.Errorf("want empty, but now is: %s", data) 1564 | return 1565 | } 1566 | } 1567 | } 1568 | 1569 | // debug 1570 | { 1571 | SetLevel(DebugLevel) 1572 | 1573 | // fatal 1574 | { 1575 | var buf bytes.Buffer 1576 | SetOutput(ConcurrentWriter(&buf)) 1577 | SetFormatter(testJsonFormatter{}) 1578 | 1579 | FatalContext(testWithoutLoggerContext, "fatal-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1580 | data := buf.Bytes() 1581 | 1582 | var have map[string]interface{} 1583 | if err := json.Unmarshal(data, &have); err != nil { 1584 | t.Error(err.Error()) 1585 | return 1586 | } 1587 | want := map[string]interface{}{ 1588 | fieldKeyTraceId: "", 1589 | fieldKeyLevel: FatalLevelString, 1590 | fieldKeyMessage: "fatal-msg", 1591 | "field1-key": "field1-value", 1592 | "field2-key": "field2-value", 1593 | } 1594 | if !reflect.DeepEqual(have, want) { 1595 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1596 | return 1597 | } 1598 | } 1599 | // error 1600 | { 1601 | var buf bytes.Buffer 1602 | SetOutput(ConcurrentWriter(&buf)) 1603 | SetFormatter(testJsonFormatter{}) 1604 | 1605 | ErrorContext(testWithoutLoggerContext, "error-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1606 | data := buf.Bytes() 1607 | 1608 | var have map[string]interface{} 1609 | if err := json.Unmarshal(data, &have); err != nil { 1610 | t.Error(err.Error()) 1611 | return 1612 | } 1613 | want := map[string]interface{}{ 1614 | fieldKeyTraceId: "", 1615 | fieldKeyLevel: ErrorLevelString, 1616 | fieldKeyMessage: "error-msg", 1617 | "field1-key": "field1-value", 1618 | "field2-key": "field2-value", 1619 | } 1620 | if !reflect.DeepEqual(have, want) { 1621 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1622 | return 1623 | } 1624 | } 1625 | // warning 1626 | { 1627 | var buf bytes.Buffer 1628 | SetOutput(ConcurrentWriter(&buf)) 1629 | SetFormatter(testJsonFormatter{}) 1630 | 1631 | WarnContext(testWithoutLoggerContext, "warning-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1632 | data := buf.Bytes() 1633 | 1634 | var have map[string]interface{} 1635 | if err := json.Unmarshal(data, &have); err != nil { 1636 | t.Error(err.Error()) 1637 | return 1638 | } 1639 | want := map[string]interface{}{ 1640 | fieldKeyTraceId: "", 1641 | fieldKeyLevel: WarnLevelString, 1642 | fieldKeyMessage: "warning-msg", 1643 | "field1-key": "field1-value", 1644 | "field2-key": "field2-value", 1645 | } 1646 | if !reflect.DeepEqual(have, want) { 1647 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1648 | return 1649 | } 1650 | } 1651 | // info 1652 | { 1653 | var buf bytes.Buffer 1654 | SetOutput(ConcurrentWriter(&buf)) 1655 | SetFormatter(testJsonFormatter{}) 1656 | 1657 | InfoContext(testWithoutLoggerContext, "info-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1658 | data := buf.Bytes() 1659 | 1660 | var have map[string]interface{} 1661 | if err := json.Unmarshal(data, &have); err != nil { 1662 | t.Error(err.Error()) 1663 | return 1664 | } 1665 | want := map[string]interface{}{ 1666 | fieldKeyTraceId: "", 1667 | fieldKeyLevel: InfoLevelString, 1668 | fieldKeyMessage: "info-msg", 1669 | "field1-key": "field1-value", 1670 | "field2-key": "field2-value", 1671 | } 1672 | if !reflect.DeepEqual(have, want) { 1673 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1674 | return 1675 | } 1676 | } 1677 | // debug 1678 | { 1679 | var buf bytes.Buffer 1680 | SetOutput(ConcurrentWriter(&buf)) 1681 | SetFormatter(testJsonFormatter{}) 1682 | 1683 | DebugContext(testWithoutLoggerContext, "debug-msg", "field1-key", "field1-value", "field2-key", "field2-value") 1684 | data := buf.Bytes() 1685 | 1686 | var have map[string]interface{} 1687 | if err := json.Unmarshal(data, &have); err != nil { 1688 | t.Error(err.Error()) 1689 | return 1690 | } 1691 | want := map[string]interface{}{ 1692 | fieldKeyTraceId: "", 1693 | fieldKeyLevel: DebugLevelString, 1694 | fieldKeyMessage: "debug-msg", 1695 | "field1-key": "field1-value", 1696 | "field2-key": "field2-value", 1697 | } 1698 | if !reflect.DeepEqual(have, want) { 1699 | t.Errorf("\nhave:%v\nwant:%v", have, want) 1700 | return 1701 | } 1702 | } 1703 | } 1704 | } 1705 | --------------------------------------------------------------------------------