diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ff49c47 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + test: + strategy: + matrix: + go-version: [1.21.x, 1.22.x, 1.23.x] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.63 + - run: go vet ./... + - run: go test ./... diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 70e012b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: go - -go: - - 1.0 - - 1.1 - - tip diff --git a/README.md b/README.md index aba9398..98e4289 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ## Golang logging library -[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/op/go-logging) [![build](https://img.shields.io/travis/op/go-logging.svg?style=flat)](https://travis-ci.org/op/go-logging) +[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/op/go-logging) +[![Build Status](https://github.com/keybase/go-logging/actions/workflows/ci.yml/badge.svg)](https://github.com/keybase/go-logging/actions) Package logging implements a logging infrastructure for Go. Its output format is customizable and supports different logging backends like syslog, file and @@ -72,11 +73,11 @@ func main() { ## Installing -### Using *go get* +### Using _go get_ $ go get github.com/op/go-logging -After this command *go-logging* is ready to use. Its source will be in: +After this command _go-logging_ is ready to use. Its source will be in: $GOPATH/src/pkg/github.com/op/go-logging @@ -90,4 +91,4 @@ For docs, see http://godoc.org/github.com/op/go-logging or run: ## Additional resources -* [wslog](https://godoc.org/github.com/cryptix/go/logging/wslog) -- exposes log messages through a WebSocket. +- [wslog](https://godoc.org/github.com/cryptix/go/logging/wslog) -- exposes log messages through a WebSocket. diff --git a/backend.go b/backend.go index 74d9201..b10dbd8 100644 --- a/backend.go +++ b/backend.go @@ -4,8 +4,13 @@ package logging +import ( + "sync" +) + // defaultBackend is the backend used for all logging calls. var defaultBackend LeveledBackend +var defaultBackendMutex sync.RWMutex // Backend is the interface which a log backend need to implement to be able to // be used as a logging backend. @@ -23,6 +28,8 @@ func SetBackend(backends ...Backend) LeveledBackend { backend = MultiLogger(backends...) } + defaultBackendMutex.Lock() + defer defaultBackendMutex.Unlock() defaultBackend = AddModuleLevel(backend) return defaultBackend } diff --git a/examples/example.go b/examples/example.go index 9f4ddee..030e332 100644 --- a/examples/example.go +++ b/examples/example.go @@ -3,7 +3,7 @@ package main import ( "os" - "github.com/op/go-logging" + "github.com/keybase/go-logging" ) var log = logging.MustGetLogger("example") @@ -27,7 +27,8 @@ func main() { // For demo purposes, create two backend for os.Stderr. backend1 := logging.NewLogBackend(os.Stderr, "", 0) backend2 := logging.NewLogBackend(os.Stderr, "", 0) - + backend1.Color = true + backend2.Color = true // For messages written to backend2 we want to add some additional // information to the output, including the used log level and the name of // the function. diff --git a/format.go b/format.go index 7160674..93169dd 100644 --- a/format.go +++ b/format.go @@ -154,17 +154,18 @@ type stringFormatter struct { // The verbs: // // General: -// %{id} Sequence number for log message (uint64). -// %{pid} Process id (int) -// %{time} Time when log occurred (time.Time) -// %{level} Log level (Level) -// %{module} Module (string) -// %{program} Basename of os.Args[0] (string) -// %{message} Message (string) -// %{longfile} Full file name and line number: /a/b/c/d.go:23 -// %{shortfile} Final file name element and line number: d.go:23 -// %{callpath} Callpath like main.a.b.c...c "..." meaning recursive call ~. meaning truncated path -// %{color} ANSI color based on log level +// +// %{id} Sequence number for log message (uint64). +// %{pid} Process id (int) +// %{time} Time when log occurred (time.Time) +// %{level} Log level (Level) +// %{module} Module (string) +// %{program} Basename of os.Args[0] (string) +// %{message} Message (string) +// %{longfile} Full file name and line number: /a/b/c/d.go:23 +// %{shortfile} Final file name element and line number: d.go:23 +// %{callpath} Callpath like main.a.b.c...c "..." meaning recursive call ~. meaning truncated path +// %{color} ANSI color based on log level // // For normal types, the output can be customized by using the 'verbs' defined // in the fmt package, eg. '%{id:04d}' to make the id output be '%04d' as the @@ -191,11 +192,12 @@ type stringFormatter struct { // future. // // Experimental: -// %{longpkg} Full package path, eg. github.com/go-logging -// %{shortpkg} Base package path, eg. go-logging -// %{longfunc} Full function name, eg. littleEndian.PutUint32 -// %{shortfunc} Base function name, eg. PutUint32 -// %{callpath} Call function path, eg. main.a.b.c +// +// %{longpkg} Full package path, eg. github.com/go-logging +// %{shortpkg} Base package path, eg. go-logging +// %{longfunc} Full function name, eg. littleEndian.PutUint32 +// %{shortfunc} Base function name, eg. PutUint32 +// %{callpath} Call function path, eg. main.a.b.c func NewStringFormatter(format string) (Formatter, error) { var fmter = &stringFormatter{} @@ -274,9 +276,9 @@ func (f *stringFormatter) add(verb fmtVerb, layout string) { func (f *stringFormatter) Format(calldepth int, r *Record, output io.Writer) error { for _, part := range f.parts { if part.verb == fmtVerbStatic { - output.Write([]byte(part.layout)) + _, _ = output.Write([]byte(part.layout)) } else if part.verb == fmtVerbTime { - output.Write([]byte(r.Time.Format(part.layout))) + _, _ = output.Write([]byte(r.Time.Format(part.layout))) } else if part.verb == fmtVerbLevelColor { doFmtVerbLevelColor(part.layout, r.Level, output) } else if part.verb == fmtVerbCallpath { @@ -284,28 +286,22 @@ func (f *stringFormatter) Format(calldepth int, r *Record, output io.Writer) err if err != nil { depth = 0 } - output.Write([]byte(formatCallpath(calldepth+1, depth))) + _, _ = output.Write([]byte(formatCallpath(calldepth+1, depth))) } else { var v interface{} switch part.verb { case fmtVerbLevel: v = r.Level - break case fmtVerbID: v = r.ID - break case fmtVerbPid: v = pid - break case fmtVerbProgram: v = program - break case fmtVerbModule: v = r.Module - break case fmtVerbMessage: v = r.Message() - break case fmtVerbLongfile, fmtVerbShortfile: _, file, line, ok := runtime.Caller(calldepth + 1) if !ok { diff --git a/format_test.go b/format_test.go index c008e9e..575067c 100644 --- a/format_test.go +++ b/format_test.go @@ -109,13 +109,13 @@ func TestFormatFuncName(t *testing.T) { "main", "main", "main"}, - {"github.com/op/go-logging.func·001", - "github.com/op/go-logging", + {"github.com/keybase/go-logging.func·001", + "github.com/keybase/go-logging", "go-logging", "func·001", "func·001"}, - {"github.com/op/go-logging.stringFormatter.Format", - "github.com/op/go-logging", + {"github.com/keybase/go-logging.stringFormatter.Format", + "github.com/keybase/go-logging", "go-logging", "stringFormatter.Format", "Format"}, diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..491caf1 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/keybase/go-logging + +go 1.21 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/level.go b/level.go index 98dd191..14d6f29 100644 --- a/level.go +++ b/level.go @@ -66,6 +66,7 @@ type LeveledBackend interface { } type moduleLeveled struct { + sync.RWMutex levels map[string]Level backend Backend formatter Formatter @@ -88,11 +89,13 @@ func AddModuleLevel(backend Backend) LeveledBackend { // GetLevel returns the log level for the given module. func (l *moduleLeveled) GetLevel(module string) Level { + l.RLock() + defer l.RUnlock() level, exists := l.levels[module] - if exists == false { + if !exists { level, exists = l.levels[""] // no configuration exists, default to debug - if exists == false { + if !exists { level = DEBUG } } @@ -101,6 +104,8 @@ func (l *moduleLeveled) GetLevel(module string) Level { // SetLevel sets the log level for the given module. func (l *moduleLeveled) SetLevel(level Level, module string) { + l.Lock() + defer l.Unlock() l.levels[module] = level } diff --git a/log.go b/log.go new file mode 100644 index 0000000..1562ebc --- /dev/null +++ b/log.go @@ -0,0 +1,57 @@ +// Copyright 2013, Örjan Persson. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package logging + +import ( + "fmt" + "io" +) + +type color int + +const ( + _ = iota + 30 + colorRed + colorGreen + colorYellow + _ + colorMagenta + colorCyan +) + +var ( + colors = []string{ + CRITICAL: colorSeq(colorMagenta), + ERROR: colorSeq(colorRed), + WARNING: colorSeq(colorYellow), + NOTICE: colorSeq(colorGreen), + DEBUG: colorSeq(colorCyan), + } + boldcolors = []string{ + CRITICAL: colorSeqBold(colorMagenta), + ERROR: colorSeqBold(colorRed), + WARNING: colorSeqBold(colorYellow), + NOTICE: colorSeqBold(colorGreen), + DEBUG: colorSeqBold(colorCyan), + } +) + +func colorSeq(color color) string { + return fmt.Sprintf("\033[%dm", int(color)) +} + +func colorSeqBold(color color) string { + return fmt.Sprintf("\033[%d;1m", int(color)) +} + +func doFmtVerbLevelColor(layout string, level Level, output io.Writer) { + if layout == "bold" { + _, _ = output.Write([]byte(boldcolors[level])) + } else if layout == "reset" { + _, _ = output.Write([]byte("\033[0m")) + } else { + _, _ = output.Write([]byte(colors[level])) + } +} diff --git a/log_nix.go b/log_nix.go index 4ff2ab1..e84b7c3 100644 --- a/log_nix.go +++ b/log_nix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows // Copyright 2013, Örjan Persson. All rights reserved. @@ -8,41 +9,10 @@ package logging import ( "bytes" - "fmt" "io" "log" ) -type color int - -const ( - ColorBlack = iota + 30 - ColorRed - ColorGreen - ColorYellow - ColorBlue - ColorMagenta - ColorCyan - ColorWhite -) - -var ( - colors = []string{ - CRITICAL: ColorSeq(ColorMagenta), - ERROR: ColorSeq(ColorRed), - WARNING: ColorSeq(ColorYellow), - NOTICE: ColorSeq(ColorGreen), - DEBUG: ColorSeq(ColorCyan), - } - boldcolors = []string{ - CRITICAL: ColorSeqBold(ColorMagenta), - ERROR: ColorSeqBold(ColorRed), - WARNING: ColorSeqBold(ColorYellow), - NOTICE: ColorSeqBold(ColorGreen), - DEBUG: ColorSeqBold(ColorCyan), - } -) - // LogBackend utilizes the standard log module. type LogBackend struct { Logger *log.Logger @@ -74,36 +44,3 @@ func (b *LogBackend) Log(level Level, calldepth int, rec *Record) error { return b.Logger.Output(calldepth+2, rec.Formatted(calldepth+1)) } - -// ConvertColors takes a list of ints representing colors for log levels and -// converts them into strings for ANSI color formatting -func ConvertColors(colors []int, bold bool) []string { - converted := []string{} - for _, i := range colors { - if bold { - converted = append(converted, ColorSeqBold(color(i))) - } else { - converted = append(converted, ColorSeq(color(i))) - } - } - - return converted -} - -func ColorSeq(color color) string { - return fmt.Sprintf("\033[%dm", int(color)) -} - -func ColorSeqBold(color color) string { - return fmt.Sprintf("\033[%d;1m", int(color)) -} - -func doFmtVerbLevelColor(layout string, level Level, output io.Writer) { - if layout == "bold" { - output.Write([]byte(boldcolors[level])) - } else if layout == "reset" { - output.Write([]byte("\033[0m")) - } else { - output.Write([]byte(colors[level])) - } -} diff --git a/log_test.go b/log_test.go index c7a645f..f98f96b 100644 --- a/log_test.go +++ b/log_test.go @@ -6,7 +6,7 @@ package logging import ( "bytes" - "io/ioutil" + "io" "log" "strings" "testing" @@ -60,7 +60,7 @@ func testCallpath(t *testing.T, format string, expect string) { } // Verify that the correct callpath is registered by go-logging if !strings.HasPrefix(parts[1], expect) { - t.Errorf("incorrect callpath: %s", parts[1]) + t.Errorf("incorrect callpath: %s missing prefix %s", parts[1], expect) } // Verify that the correct message is registered by go-logging if !strings.HasPrefix(parts[2], "test callpath") { @@ -69,12 +69,12 @@ func testCallpath(t *testing.T, format string, expect string) { } func TestLogCallpath(t *testing.T) { - testCallpath(t, "%{callpath} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") - testCallpath(t, "%{callpath:-1} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") - testCallpath(t, "%{callpath:0} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") + testCallpath(t, "%{callpath} %{message}", "TestLogCallpath.String.rec...a.b.c") + testCallpath(t, "%{callpath:-1} %{message}", "TestLogCallpath.String.rec...a.b.c") + testCallpath(t, "%{callpath:0} %{message}", "TestLogCallpath.String.rec...a.b.c") testCallpath(t, "%{callpath:1} %{message}", "~.c") - testCallpath(t, "%{callpath:2} %{message}", "~.b.c") - testCallpath(t, "%{callpath:3} %{message}", "~.a.b.c") + testCallpath(t, "%{callpath:2} %{message}", "~.c.c") + testCallpath(t, "%{callpath:3} %{message}", "~.b.c.c") } func BenchmarkLogMemoryBackendIgnored(b *testing.B) { @@ -98,20 +98,20 @@ func BenchmarkLogChannelMemoryBackend(b *testing.B) { } func BenchmarkLogLeveled(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) + backend := SetBackend(NewLogBackend(io.Discard, "", 0)) backend.SetLevel(INFO, "") RunLogBenchmark(b) } func BenchmarkLogLogBackend(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) + backend := SetBackend(NewLogBackend(io.Discard, "", 0)) backend.SetLevel(DEBUG, "") RunLogBenchmark(b) } func BenchmarkLogLogBackendColor(b *testing.B) { - colorizer := NewLogBackend(ioutil.Discard, "", 0) + colorizer := NewLogBackend(io.Discard, "", 0) colorizer.Color = true backend := SetBackend(colorizer) backend.SetLevel(DEBUG, "") @@ -119,13 +119,13 @@ func BenchmarkLogLogBackendColor(b *testing.B) { } func BenchmarkLogLogBackendStdFlags(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", log.LstdFlags)) + backend := SetBackend(NewLogBackend(io.Discard, "", log.LstdFlags)) backend.SetLevel(DEBUG, "") RunLogBenchmark(b) } func BenchmarkLogLogBackendLongFileFlag(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", log.Llongfile)) + backend := SetBackend(NewLogBackend(io.Discard, "", log.Llongfile)) backend.SetLevel(DEBUG, "") RunLogBenchmark(b) } @@ -141,14 +141,14 @@ func RunLogBenchmark(b *testing.B) { } func BenchmarkLogFixed(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) + backend := SetBackend(NewLogBackend(io.Discard, "", 0)) backend.SetLevel(DEBUG, "") RunLogBenchmarkFixedString(b) } func BenchmarkLogFixedIgnored(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) + backend := SetBackend(NewLogBackend(io.Discard, "", 0)) backend.SetLevel(INFO, "") RunLogBenchmarkFixedString(b) } diff --git a/log_windows.go b/log_windows.go index b8dc92c..c651cf7 100644 --- a/log_windows.go +++ b/log_windows.go @@ -1,4 +1,6 @@ +//go:build windows // +build windows + // Copyright 2013, Örjan Persson. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -17,10 +19,14 @@ var ( setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute") ) +type WORD uint16 + // Character attributes // Note: // -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan). -// Clearing all foreground or background colors results in black; setting all creates white. +// +// Clearing all foreground or background colors results in black; setting all creates white. +// // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes. const ( fgBlack = 0x0000 @@ -36,7 +42,7 @@ const ( ) var ( - colors = []uint16{ + win_colors = []uint16{ INFO: fgWhite, CRITICAL: fgMagenta, ERROR: fgRed, @@ -44,7 +50,7 @@ var ( NOTICE: fgGreen, DEBUG: fgCyan, } - boldcolors = []uint16{ + win_boldcolors = []uint16{ INFO: fgWhite | fgIntensity, CRITICAL: fgMagenta | fgIntensity, ERROR: fgRed | fgIntensity, @@ -84,7 +90,7 @@ func NewLogBackend(out io.Writer, prefix string, flag int) *LogBackend { func (b *LogBackend) Log(level Level, calldepth int, rec *Record) error { if b.Color && b.f != nil { buf := &bytes.Buffer{} - setConsoleTextAttribute(b.f, colors[level]) + setConsoleTextAttribute(b.f, win_colors[level]) buf.Write([]byte(rec.Formatted(calldepth + 1))) err := b.Logger.Output(calldepth+2, buf.String()) setConsoleTextAttribute(b.f, fgWhite) @@ -100,8 +106,3 @@ func setConsoleTextAttribute(f file, attribute uint16) bool { ok, _, _ := setConsoleTextAttributeProc.Call(f.Fd(), uintptr(attribute), 0) return ok != 0 } - -func doFmtVerbLevelColor(layout string, level Level, output io.Writer) { - // TODO not supported on Windows since the io.Writer here is actually a - // bytes.Buffer. -} diff --git a/logger.go b/logger.go index 535ed9b..aca1b35 100644 --- a/logger.go +++ b/logger.go @@ -59,7 +59,7 @@ type Record struct { func (r *Record) Formatted(calldepth int) string { if r.formatted == "" { var buf bytes.Buffer - r.formatter.Format(calldepth+1, r, &buf) + _ = r.formatter.Format(calldepth+1, r, &buf) r.formatted = buf.String() } return r.formatted @@ -70,7 +70,7 @@ func (r *Record) Message() string { if r.message == nil { // Redact the arguments that implements the Redactor interface for i, arg := range r.Args { - if redactor, ok := arg.(Redactor); ok == true { + if redactor, ok := arg.(Redactor); ok { r.Args[i] = redactor.Redacted() } } @@ -163,11 +163,13 @@ func (l *Logger) log(lvl Level, format *string, args ...interface{}) { // ExtraCallDepth allows this to be extended further up the stack in case we // are wrapping these methods, eg. to expose them package level if l.haveBackend { - l.backend.Log(lvl, 2+l.ExtraCalldepth, record) + _ = l.backend.Log(lvl, 2+l.ExtraCalldepth, record) return } - defaultBackend.Log(lvl, 2+l.ExtraCalldepth, record) + defaultBackendMutex.RLock() + defer defaultBackendMutex.RUnlock() + _ = defaultBackend.Log(lvl, 2+l.ExtraCalldepth, record) } // Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1). diff --git a/memory.go b/memory.go index 8d5152c..4af44fd 100644 --- a/memory.go +++ b/memory.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !appengine // +build !appengine package logging @@ -74,7 +75,7 @@ func (b *MemoryBackend) Log(level Level, calldepth int, rec *Record) error { tailp, np, ) - if swapped == true { + if swapped { if tailp == nil { b.head = np } else { @@ -100,7 +101,7 @@ func (b *MemoryBackend) Log(level Level, calldepth int, rec *Record) error { headp, unsafe.Pointer(head.next), ) - if swapped == true { + if swapped { atomic.AddInt32(&b.size, -1) break } @@ -128,11 +129,11 @@ const ( // ChannelMemoryBackend is very similar to the MemoryBackend, except that it // internally utilizes a channel. type ChannelMemoryBackend struct { + sync.Mutex maxSize int size int incoming chan *Record events chan event - mu sync.Mutex running bool flushWg sync.WaitGroup stopWg sync.WaitGroup @@ -156,11 +157,11 @@ func NewChannelMemoryBackend(size int) *ChannelMemoryBackend { // Start launches the internal goroutine which starts processing data from the // input channel. func (b *ChannelMemoryBackend) Start() { - b.mu.Lock() - defer b.mu.Unlock() + b.Lock() + defer b.Unlock() // Launch the goroutine unless it's already running. - if b.running != true { + if !b.running { b.running = true b.stopWg.Add(1) go b.process() @@ -212,12 +213,12 @@ func (b *ChannelMemoryBackend) Flush() { // Stop signals the internal goroutine to exit and waits until it have. func (b *ChannelMemoryBackend) Stop() { - b.mu.Lock() - if b.running == true { + b.Lock() + defer b.Unlock() + if b.running { b.running = false b.events <- eventStop } - b.mu.Unlock() b.stopWg.Wait() } diff --git a/syslog.go b/syslog.go index 4faa531..40fae75 100644 --- a/syslog.go +++ b/syslog.go @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//+build !windows,!plan9 +//go:build !windows && !plan9 +// +build !windows,!plan9 package logging diff --git a/syslog_fallback.go b/syslog_fallback.go index 91bc18d..05bb1e2 100644 --- a/syslog_fallback.go +++ b/syslog_fallback.go @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//+build windows plan9 +//go:build windows || plan9 +// +build windows plan9 package logging