diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc17e08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +*.iml +*.out +*.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/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 4b7d233..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,19 +0,0 @@ -# Changelog - -## 2.0.0-rc1 (2016-02-11) - -Time flies and it has been three years since this package was first released. -There have been a couple of API changes I have wanted to do for some time but -I've tried to maintain backwards compatibility. Some inconsistencies in the -API have started to show, proper vendor support in Go out of the box and -the fact that `go vet` will give warnings -- I have decided to bump the major -version. - -* Make eg. `Info` and `Infof` do different things. You want to change all calls - to `Info` with a string format go to `Infof` etc. In many cases, `go vet` will - guide you. -* `Id` in `Record` is now called `ID` - -## 1.0.0 (2013-02-21) - -Initial release diff --git a/CONTRIBUTORS b/CONTRIBUTORS deleted file mode 100644 index 958416e..0000000 --- a/CONTRIBUTORS +++ /dev/null @@ -1,5 +0,0 @@ -Alec Thomas -Guilhem Lettron -Ivan Daniluk -Nimi Wariboko Jr -Róbert Selvek diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f1f6cfc..0000000 --- a/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013 Örjan Persson. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md deleted file mode 100644 index 0a7326b..0000000 --- a/README.md +++ /dev/null @@ -1,93 +0,0 @@ -## 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) - -Package logging implements a logging infrastructure for Go. Its output format -is customizable and supports different logging backends like syslog, file and -memory. Multiple backends can be utilized with different log levels per backend -and logger. - -**_NOTE:_** backwards compatibility promise have been dropped for master. Please -vendor this package or use `gopkg.in/op/go-logging.v1` for previous version. See -[changelog](CHANGELOG.md) for details. - -## Example - -Let's have a look at an [example](examples/example.go) which demonstrates most -of the features found in this library. - -[![Example Output](examples/example.png)](examples/example.go) - -```go -package main - -import ( - "os" - - "github.com/op/go-logging" -) - -var log = logging.MustGetLogger("example") - -// Example format string. Everything except the message has a custom color -// which is dependent on the log level. Many fields have a custom output -// formatting too, eg. the time returns the hour down to the milli second. -var format = logging.MustStringFormatter( - `%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, -) - -// Password is just an example type implementing the Redactor interface. Any -// time this is logged, the Redacted() function will be called. -type Password string - -func (p Password) Redacted() interface{} { - return logging.Redact(string(p)) -} - -func main() { - // For demo purposes, create two backend for os.Stderr. - backend1 := logging.NewLogBackend(os.Stderr, "", 0) - backend2 := logging.NewLogBackend(os.Stderr, "", 0) - - // 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. - backend2Formatter := logging.NewBackendFormatter(backend2, format) - - // Only errors and more severe messages should be sent to backend1 - backend1Leveled := logging.AddModuleLevel(backend1) - backend1Leveled.SetLevel(logging.ERROR, "") - - // Set the backends to be used. - logging.SetBackend(backend1Leveled, backend2Formatter) - - log.Debugf("debug %s", Password("secret")) - log.Info("info") - log.Notice("notice") - log.Warning("warning") - log.Error("err") - log.Critical("crit") -} -``` - -## Installing - -### Using *go get* - - $ go get github.com/op/go-logging - -After this command *go-logging* is ready to use. Its source will be in: - - $GOPATH/src/pkg/github.com/op/go-logging - -You can use `go get -u` to update the package. - -## Documentation - -For docs, see http://godoc.org/github.com/op/go-logging or run: - - $ godoc github.com/op/go-logging - -## Additional resources - -* [wslog](https://godoc.org/github.com/cryptix/exp/wslog) -- exposes log messages through a WebSocket. diff --git a/backend.go b/backend.go deleted file mode 100644 index 74d9201..0000000 --- a/backend.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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 - -// defaultBackend is the backend used for all logging calls. -var defaultBackend LeveledBackend - -// Backend is the interface which a log backend need to implement to be able to -// be used as a logging backend. -type Backend interface { - Log(Level, int, *Record) error -} - -// SetBackend replaces the backend currently set with the given new logging -// backend. -func SetBackend(backends ...Backend) LeveledBackend { - var backend Backend - if len(backends) == 1 { - backend = backends[0] - } else { - backend = MultiLogger(backends...) - } - - defaultBackend = AddModuleLevel(backend) - return defaultBackend -} - -// SetLevel sets the logging level for the specified module. The module -// corresponds to the string specified in GetLogger. -func SetLevel(level Level, module string) { - defaultBackend.SetLevel(level, module) -} - -// GetLevel returns the logging level for the specified module. -func GetLevel(module string) Level { - return defaultBackend.GetLevel(module) -} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..035ea17 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: '2' +services: + go-logging: + container_name: go-logging + image: golang:1.9 + volumes: + - $PWD:/go/src/github.com/mageddo/go-logging + working_dir: /go/src/github.com/mageddo/go-logging + command: tail -f /dev/null diff --git a/example_test.go b/example_test.go deleted file mode 100644 index 52fd733..0000000 --- a/example_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package logging - -import "os" - -func Example() { - // This call is for testing purposes and will set the time to unix epoch. - InitForTesting(DEBUG) - - var log = MustGetLogger("example") - - // For demo purposes, create two backend for os.Stdout. - // - // os.Stderr should most likely be used in the real world but then the - // "Output:" check in this example would not work. - backend1 := NewLogBackend(os.Stdout, "", 0) - backend2 := NewLogBackend(os.Stdout, "", 0) - - // 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. - var format = MustStringFormatter( - `%{time:15:04:05.000} %{shortfunc} %{level:.1s} %{message}`, - ) - backend2Formatter := NewBackendFormatter(backend2, format) - - // Only errors and more severe messages should be sent to backend2 - backend2Leveled := AddModuleLevel(backend2Formatter) - backend2Leveled.SetLevel(ERROR, "") - - // Set the backends to be used and the default level. - SetBackend(backend1, backend2Leveled) - - log.Debugf("debug %s", "arg") - log.Error("error") - - // Output: - // debug arg - // error - // 00:00:00.000 Example E error -} diff --git a/examples/example.go b/examples/example.go deleted file mode 100644 index 9f4ddee..0000000 --- a/examples/example.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "os" - - "github.com/op/go-logging" -) - -var log = logging.MustGetLogger("example") - -// Example format string. Everything except the message has a custom color -// which is dependent on the log level. Many fields have a custom output -// formatting too, eg. the time returns the hour down to the milli second. -var format = logging.MustStringFormatter( - `%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, -) - -// Password is just an example type implementing the Redactor interface. Any -// time this is logged, the Redacted() function will be called. -type Password string - -func (p Password) Redacted() interface{} { - return logging.Redact(string(p)) -} - -func main() { - // For demo purposes, create two backend for os.Stderr. - backend1 := logging.NewLogBackend(os.Stderr, "", 0) - backend2 := logging.NewLogBackend(os.Stderr, "", 0) - - // 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. - backend2Formatter := logging.NewBackendFormatter(backend2, format) - - // Only errors and more severe messages should be sent to backend1 - backend1Leveled := logging.AddModuleLevel(backend1) - backend1Leveled.SetLevel(logging.ERROR, "") - - // Set the backends to be used. - logging.SetBackend(backend1Leveled, backend2Formatter) - - log.Debugf("debug %s", Password("secret")) - log.Info("info") - log.Notice("notice") - log.Warning("warning") - log.Error("err") - log.Critical("crit") -} diff --git a/examples/example.png b/examples/example.png deleted file mode 100644 index ff3392b..0000000 Binary files a/examples/example.png and /dev/null differ diff --git a/format.go b/format.go deleted file mode 100644 index 7160674..0000000 --- a/format.go +++ /dev/null @@ -1,414 +0,0 @@ -// 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 ( - "bytes" - "errors" - "fmt" - "io" - "os" - "path" - "path/filepath" - "regexp" - "runtime" - "strconv" - "strings" - "sync" - "time" -) - -// TODO see Formatter interface in fmt/print.go -// TODO try text/template, maybe it have enough performance -// TODO other template systems? -// TODO make it possible to specify formats per backend? -type fmtVerb int - -const ( - fmtVerbTime fmtVerb = iota - fmtVerbLevel - fmtVerbID - fmtVerbPid - fmtVerbProgram - fmtVerbModule - fmtVerbMessage - fmtVerbLongfile - fmtVerbShortfile - fmtVerbLongpkg - fmtVerbShortpkg - fmtVerbLongfunc - fmtVerbShortfunc - fmtVerbCallpath - fmtVerbLevelColor - - // Keep last, there are no match for these below. - fmtVerbUnknown - fmtVerbStatic -) - -var fmtVerbs = []string{ - "time", - "level", - "id", - "pid", - "program", - "module", - "message", - "longfile", - "shortfile", - "longpkg", - "shortpkg", - "longfunc", - "shortfunc", - "callpath", - "color", -} - -const rfc3339Milli = "2006-01-02T15:04:05.999Z07:00" - -var defaultVerbsLayout = []string{ - rfc3339Milli, - "s", - "d", - "d", - "s", - "s", - "s", - "s", - "s", - "s", - "s", - "s", - "s", - "0", - "", -} - -var ( - pid = os.Getpid() - program = filepath.Base(os.Args[0]) -) - -func getFmtVerbByName(name string) fmtVerb { - for i, verb := range fmtVerbs { - if name == verb { - return fmtVerb(i) - } - } - return fmtVerbUnknown -} - -// Formatter is the required interface for a custom log record formatter. -type Formatter interface { - Format(calldepth int, r *Record, w io.Writer) error -} - -// formatter is used by all backends unless otherwise overriden. -var formatter struct { - sync.RWMutex - def Formatter -} - -func getFormatter() Formatter { - formatter.RLock() - defer formatter.RUnlock() - return formatter.def -} - -var ( - // DefaultFormatter is the default formatter used and is only the message. - DefaultFormatter = MustStringFormatter("%{message}") - - // GlogFormatter mimics the glog format - GlogFormatter = MustStringFormatter("%{level:.1s}%{time:0102 15:04:05.999999} %{pid} %{shortfile}] %{message}") -) - -// SetFormatter sets the default formatter for all new backends. A backend will -// fetch this value once it is needed to format a record. Note that backends -// will cache the formatter after the first point. For now, make sure to set -// the formatter before logging. -func SetFormatter(f Formatter) { - formatter.Lock() - defer formatter.Unlock() - formatter.def = f -} - -var formatRe = regexp.MustCompile(`%{([a-z]+)(?::(.*?[^\\]))?}`) - -type part struct { - verb fmtVerb - layout string -} - -// stringFormatter contains a list of parts which explains how to build the -// formatted string passed on to the logging backend. -type stringFormatter struct { - parts []part -} - -// NewStringFormatter returns a new Formatter which outputs the log record as a -// string based on the 'verbs' specified in the format string. -// -// 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 -// -// 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 -// format string. -// -// For time.Time, use the same layout as time.Format to change the time format -// when output, eg "2006-01-02T15:04:05.999Z-07:00". -// -// For the 'color' verb, the output can be adjusted to either use bold colors, -// i.e., '%{color:bold}' or to reset the ANSI attributes, i.e., -// '%{color:reset}' Note that if you use the color verb explicitly, be sure to -// reset it or else the color state will persist past your log message. e.g., -// "%{color:bold}%{time:15:04:05} %{level:-8s}%{color:reset} %{message}" will -// just colorize the time and level, leaving the message uncolored. -// -// For the 'callpath' verb, the output can be adjusted to limit the printing -// the stack depth. i.e. '%{callpath:3}' will print '~.a.b.c' -// -// Colors on Windows is unfortunately not supported right now and is currently -// a no-op. -// -// There's also a couple of experimental 'verbs'. These are exposed to get -// feedback and needs a bit of tinkering. Hence, they might change in the -// 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 -func NewStringFormatter(format string) (Formatter, error) { - var fmter = &stringFormatter{} - - // Find the boundaries of all %{vars} - matches := formatRe.FindAllStringSubmatchIndex(format, -1) - if matches == nil { - return nil, errors.New("logger: invalid log format: " + format) - } - - // Collect all variables and static text for the format - prev := 0 - for _, m := range matches { - start, end := m[0], m[1] - if start > prev { - fmter.add(fmtVerbStatic, format[prev:start]) - } - - name := format[m[2]:m[3]] - verb := getFmtVerbByName(name) - if verb == fmtVerbUnknown { - return nil, errors.New("logger: unknown variable: " + name) - } - - // Handle layout customizations or use the default. If this is not for the - // time, color formatting or callpath, we need to prefix with %. - layout := defaultVerbsLayout[verb] - if m[4] != -1 { - layout = format[m[4]:m[5]] - } - if verb != fmtVerbTime && verb != fmtVerbLevelColor && verb != fmtVerbCallpath { - layout = "%" + layout - } - - fmter.add(verb, layout) - prev = end - } - end := format[prev:] - if end != "" { - fmter.add(fmtVerbStatic, end) - } - - // Make a test run to make sure we can format it correctly. - t, err := time.Parse(time.RFC3339, "2010-02-04T21:00:57-08:00") - if err != nil { - panic(err) - } - testFmt := "hello %s" - r := &Record{ - ID: 12345, - Time: t, - Module: "logger", - Args: []interface{}{"go"}, - fmt: &testFmt, - } - if err := fmter.Format(0, r, &bytes.Buffer{}); err != nil { - return nil, err - } - - return fmter, nil -} - -// MustStringFormatter is equivalent to NewStringFormatter with a call to panic -// on error. -func MustStringFormatter(format string) Formatter { - f, err := NewStringFormatter(format) - if err != nil { - panic("Failed to initialized string formatter: " + err.Error()) - } - return f -} - -func (f *stringFormatter) add(verb fmtVerb, layout string) { - f.parts = append(f.parts, part{verb, layout}) -} - -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)) - } else if part.verb == fmtVerbTime { - output.Write([]byte(r.Time.Format(part.layout))) - } else if part.verb == fmtVerbLevelColor { - doFmtVerbLevelColor(part.layout, r.Level, output) - } else if part.verb == fmtVerbCallpath { - depth, err := strconv.Atoi(part.layout) - if err != nil { - depth = 0 - } - 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 { - file = "???" - line = 0 - } else if part.verb == fmtVerbShortfile { - file = filepath.Base(file) - } - v = fmt.Sprintf("%s:%d", file, line) - case fmtVerbLongfunc, fmtVerbShortfunc, - fmtVerbLongpkg, fmtVerbShortpkg: - // TODO cache pc - v = "???" - if pc, _, _, ok := runtime.Caller(calldepth + 1); ok { - if f := runtime.FuncForPC(pc); f != nil { - v = formatFuncName(part.verb, f.Name()) - } - } - default: - panic("unhandled format part") - } - fmt.Fprintf(output, part.layout, v) - } - } - return nil -} - -// formatFuncName tries to extract certain part of the runtime formatted -// function name to some pre-defined variation. -// -// This function is known to not work properly if the package path or name -// contains a dot. -func formatFuncName(v fmtVerb, f string) string { - i := strings.LastIndex(f, "/") - j := strings.Index(f[i+1:], ".") - if j < 1 { - return "???" - } - pkg, fun := f[:i+j+1], f[i+j+2:] - switch v { - case fmtVerbLongpkg: - return pkg - case fmtVerbShortpkg: - return path.Base(pkg) - case fmtVerbLongfunc: - return fun - case fmtVerbShortfunc: - i = strings.LastIndex(fun, ".") - return fun[i+1:] - } - panic("unexpected func formatter") -} - -func formatCallpath(calldepth int, depth int) string { - v := "" - callers := make([]uintptr, 64) - n := runtime.Callers(calldepth+2, callers) - oldPc := callers[n-1] - - start := n - 3 - if depth > 0 && start >= depth { - start = depth - 1 - v += "~." - } - recursiveCall := false - for i := start; i >= 0; i-- { - pc := callers[i] - if oldPc == pc { - recursiveCall = true - continue - } - oldPc = pc - if recursiveCall { - recursiveCall = false - v += ".." - } - if i < start { - v += "." - } - if f := runtime.FuncForPC(pc); f != nil { - v += formatFuncName(fmtVerbShortfunc, f.Name()) - } - } - return v -} - -// backendFormatter combines a backend with a specific formatter making it -// possible to have different log formats for different backends. -type backendFormatter struct { - b Backend - f Formatter -} - -// NewBackendFormatter creates a new backend which makes all records that -// passes through it beeing formatted by the specific formatter. -func NewBackendFormatter(b Backend, f Formatter) Backend { - return &backendFormatter{b, f} -} - -// Log implements the Log function required by the Backend interface. -func (bf *backendFormatter) Log(level Level, calldepth int, r *Record) error { - // Make a shallow copy of the record and replace any formatter - r2 := *r - r2.formatter = bf.f - return bf.b.Log(level, calldepth+1, &r2) -} diff --git a/format_test.go b/format_test.go deleted file mode 100644 index c008e9e..0000000 --- a/format_test.go +++ /dev/null @@ -1,184 +0,0 @@ -// 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 ( - "bytes" - "testing" -) - -func TestFormat(t *testing.T) { - backend := InitForTesting(DEBUG) - - f, err := NewStringFormatter("%{shortfile} %{time:2006-01-02T15:04:05} %{level:.1s} %{id:04d} %{module} %{message}") - if err != nil { - t.Fatalf("failed to set format: %s", err) - } - SetFormatter(f) - - log := MustGetLogger("module") - log.Debug("hello") - - line := MemoryRecordN(backend, 0).Formatted(0) - if "format_test.go:24 1970-01-01T00:00:00 D 0001 module hello" != line { - t.Errorf("Unexpected format: %s", line) - } -} - -func logAndGetLine(backend *MemoryBackend) string { - MustGetLogger("foo").Debug("hello") - return MemoryRecordN(backend, 0).Formatted(1) -} - -func getLastLine(backend *MemoryBackend) string { - return MemoryRecordN(backend, 0).Formatted(1) -} - -func realFunc(backend *MemoryBackend) string { - return logAndGetLine(backend) -} - -type structFunc struct{} - -func (structFunc) Log(backend *MemoryBackend) string { - return logAndGetLine(backend) -} - -func TestRealFuncFormat(t *testing.T) { - backend := InitForTesting(DEBUG) - SetFormatter(MustStringFormatter("%{shortfunc}")) - - line := realFunc(backend) - if "realFunc" != line { - t.Errorf("Unexpected format: %s", line) - } -} - -func TestStructFuncFormat(t *testing.T) { - backend := InitForTesting(DEBUG) - SetFormatter(MustStringFormatter("%{longfunc}")) - - var x structFunc - line := x.Log(backend) - if "structFunc.Log" != line { - t.Errorf("Unexpected format: %s", line) - } -} - -func TestVarFuncFormat(t *testing.T) { - backend := InitForTesting(DEBUG) - SetFormatter(MustStringFormatter("%{shortfunc}")) - - var varFunc = func() string { - return logAndGetLine(backend) - } - - line := varFunc() - if "???" == line || "TestVarFuncFormat" == line || "varFunc" == line { - t.Errorf("Unexpected format: %s", line) - } -} - -func TestFormatFuncName(t *testing.T) { - var tests = []struct { - filename string - longpkg string - shortpkg string - longfunc string - shortfunc string - }{ - {"", - "???", - "???", - "???", - "???"}, - {"main", - "???", - "???", - "???", - "???"}, - {"main.", - "main", - "main", - "", - ""}, - {"main.main", - "main", - "main", - "main", - "main"}, - {"github.com/op/go-logging.func·001", - "github.com/op/go-logging", - "go-logging", - "func·001", - "func·001"}, - {"github.com/op/go-logging.stringFormatter.Format", - "github.com/op/go-logging", - "go-logging", - "stringFormatter.Format", - "Format"}, - } - - var v string - for _, test := range tests { - v = formatFuncName(fmtVerbLongpkg, test.filename) - if test.longpkg != v { - t.Errorf("%s != %s", test.longpkg, v) - } - v = formatFuncName(fmtVerbShortpkg, test.filename) - if test.shortpkg != v { - t.Errorf("%s != %s", test.shortpkg, v) - } - v = formatFuncName(fmtVerbLongfunc, test.filename) - if test.longfunc != v { - t.Errorf("%s != %s", test.longfunc, v) - } - v = formatFuncName(fmtVerbShortfunc, test.filename) - if test.shortfunc != v { - t.Errorf("%s != %s", test.shortfunc, v) - } - } -} - -func TestBackendFormatter(t *testing.T) { - InitForTesting(DEBUG) - - // Create two backends and wrap one of the with a backend formatter - b1 := NewMemoryBackend(1) - b2 := NewMemoryBackend(1) - - f := MustStringFormatter("%{level} %{message}") - bf := NewBackendFormatter(b2, f) - - SetBackend(b1, bf) - - log := MustGetLogger("module") - log.Info("foo") - if "foo" != getLastLine(b1) { - t.Errorf("Unexpected line: %s", getLastLine(b1)) - } - if "INFO foo" != getLastLine(b2) { - t.Errorf("Unexpected line: %s", getLastLine(b2)) - } -} - -func BenchmarkStringFormatter(b *testing.B) { - fmt := "%{time:2006-01-02T15:04:05} %{level:.1s} %{id:04d} %{module} %{message}" - f := MustStringFormatter(fmt) - - backend := InitForTesting(DEBUG) - buf := &bytes.Buffer{} - log := MustGetLogger("module") - log.Debug("") - record := MemoryRecordN(backend, 0) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := f.Format(1, record, buf); err != nil { - b.Fatal(err) - buf.Truncate(0) - } - } -} diff --git a/gologprinter.go b/gologprinter.go new file mode 100644 index 0000000..c005012 --- /dev/null +++ b/gologprinter.go @@ -0,0 +1,33 @@ +package logging + +import ( + "log" + "io" +) + +// +// log using golang native logger solution +// +type gologPrinter struct { + log *log.Logger +} + +func(p *gologPrinter) Printf(format string, args ...interface{}) { + p.log.Printf(format, args...) +} + +func(p *gologPrinter) Println(args ...interface{}) { + p.log.Println(args...) +} + +func(p *gologPrinter) SetFlags(flags int){ + p.log.SetFlags(flags) +} + +func(p *gologPrinter) SetOutput(out io.Writer){ + p.log.SetOutput(out) +} + +func NewGologPrinter(out io.Writer, prefix string, flag int) *gologPrinter { + return &gologPrinter{log: log.New(out, prefix, flag)} +} diff --git a/level.go b/level.go deleted file mode 100644 index 98dd191..0000000 --- a/level.go +++ /dev/null @@ -1,128 +0,0 @@ -// 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 ( - "errors" - "strings" - "sync" -) - -// ErrInvalidLogLevel is used when an invalid log level has been used. -var ErrInvalidLogLevel = errors.New("logger: invalid log level") - -// Level defines all available log levels for log messages. -type Level int - -// Log levels. -const ( - CRITICAL Level = iota - ERROR - WARNING - NOTICE - INFO - DEBUG -) - -var levelNames = []string{ - "CRITICAL", - "ERROR", - "WARNING", - "NOTICE", - "INFO", - "DEBUG", -} - -// String returns the string representation of a logging level. -func (p Level) String() string { - return levelNames[p] -} - -// LogLevel returns the log level from a string representation. -func LogLevel(level string) (Level, error) { - for i, name := range levelNames { - if strings.EqualFold(name, level) { - return Level(i), nil - } - } - return ERROR, ErrInvalidLogLevel -} - -// Leveled interface is the interface required to be able to add leveled -// logging. -type Leveled interface { - GetLevel(string) Level - SetLevel(Level, string) - IsEnabledFor(Level, string) bool -} - -// LeveledBackend is a log backend with additional knobs for setting levels on -// individual modules to different levels. -type LeveledBackend interface { - Backend - Leveled -} - -type moduleLeveled struct { - levels map[string]Level - backend Backend - formatter Formatter - once sync.Once -} - -// AddModuleLevel wraps a log backend with knobs to have different log levels -// for different modules. -func AddModuleLevel(backend Backend) LeveledBackend { - var leveled LeveledBackend - var ok bool - if leveled, ok = backend.(LeveledBackend); !ok { - leveled = &moduleLeveled{ - levels: make(map[string]Level), - backend: backend, - } - } - return leveled -} - -// GetLevel returns the log level for the given module. -func (l *moduleLeveled) GetLevel(module string) Level { - level, exists := l.levels[module] - if exists == false { - level, exists = l.levels[""] - // no configuration exists, default to debug - if exists == false { - level = DEBUG - } - } - return level -} - -// SetLevel sets the log level for the given module. -func (l *moduleLeveled) SetLevel(level Level, module string) { - l.levels[module] = level -} - -// IsEnabledFor will return true if logging is enabled for the given module. -func (l *moduleLeveled) IsEnabledFor(level Level, module string) bool { - return level <= l.GetLevel(module) -} - -func (l *moduleLeveled) Log(level Level, calldepth int, rec *Record) (err error) { - if l.IsEnabledFor(level, rec.Module) { - // TODO get rid of traces of formatter here. BackendFormatter should be used. - rec.formatter = l.getFormatterAndCacheCurrent() - err = l.backend.Log(level, calldepth+1, rec) - } - return -} - -func (l *moduleLeveled) getFormatterAndCacheCurrent() Formatter { - l.once.Do(func() { - if l.formatter == nil { - l.formatter = getFormatter() - } - }) - return l.formatter -} diff --git a/level_test.go b/level_test.go deleted file mode 100644 index c8f9a37..0000000 --- a/level_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// 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 "testing" - -func TestLevelString(t *testing.T) { - // Make sure all levels can be converted from string -> constant -> string - for _, name := range levelNames { - level, err := LogLevel(name) - if err != nil { - t.Errorf("failed to get level: %v", err) - continue - } - - if level.String() != name { - t.Errorf("invalid level conversion: %v != %v", level, name) - } - } -} - -func TestLevelLogLevel(t *testing.T) { - tests := []struct { - expected Level - level string - }{ - {-1, "bla"}, - {INFO, "iNfO"}, - {ERROR, "error"}, - {WARNING, "warninG"}, - } - - for _, test := range tests { - level, err := LogLevel(test.level) - if err != nil { - if test.expected == -1 { - continue - } else { - t.Errorf("failed to convert %s: %s", test.level, err) - } - } - if test.expected != level { - t.Errorf("failed to convert %s to level: %s != %s", test.level, test.expected, level) - } - } -} - -func TestLevelModuleLevel(t *testing.T) { - backend := NewMemoryBackend(128) - - leveled := AddModuleLevel(backend) - leveled.SetLevel(NOTICE, "") - leveled.SetLevel(ERROR, "foo") - leveled.SetLevel(INFO, "foo.bar") - leveled.SetLevel(WARNING, "bar") - - expected := []struct { - level Level - module string - }{ - {NOTICE, ""}, - {NOTICE, "something"}, - {ERROR, "foo"}, - {INFO, "foo.bar"}, - {WARNING, "bar"}, - } - - for _, e := range expected { - actual := leveled.GetLevel(e.module) - if e.level != actual { - t.Errorf("unexpected level in %s: %s != %s", e.module, e.level, actual) - } - } -} diff --git a/log_nix.go b/log_nix.go deleted file mode 100644 index 4ff2ab1..0000000 --- a/log_nix.go +++ /dev/null @@ -1,109 +0,0 @@ -// +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. - -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 - Color bool - ColorConfig []string -} - -// NewLogBackend creates a new LogBackend. -func NewLogBackend(out io.Writer, prefix string, flag int) *LogBackend { - return &LogBackend{Logger: log.New(out, prefix, flag)} -} - -// Log implements the Backend interface. -func (b *LogBackend) Log(level Level, calldepth int, rec *Record) error { - if b.Color { - col := colors[level] - if len(b.ColorConfig) > int(level) && b.ColorConfig[level] != "" { - col = b.ColorConfig[level] - } - - buf := &bytes.Buffer{} - buf.Write([]byte(col)) - buf.Write([]byte(rec.Formatted(calldepth + 1))) - buf.Write([]byte("\033[0m")) - // For some reason, the Go logger arbitrarily decided "2" was the correct - // call depth... - return b.Logger.Output(calldepth+2, buf.String()) - } - - 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 deleted file mode 100644 index c7a645f..0000000 --- a/log_test.go +++ /dev/null @@ -1,163 +0,0 @@ -// 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 ( - "bytes" - "io/ioutil" - "log" - "strings" - "testing" -) - -func TestLogCalldepth(t *testing.T) { - buf := &bytes.Buffer{} - SetBackend(NewLogBackend(buf, "", log.Lshortfile)) - SetFormatter(MustStringFormatter("%{shortfile} %{level} %{message}")) - - log := MustGetLogger("test") - log.Info("test filename") - - parts := strings.SplitN(buf.String(), " ", 2) - - // Verify that the correct filename is registered by the stdlib logger - if !strings.HasPrefix(parts[0], "log_test.go:") { - t.Errorf("incorrect filename: %s", parts[0]) - } - // Verify that the correct filename is registered by go-logging - if !strings.HasPrefix(parts[1], "log_test.go:") { - t.Errorf("incorrect filename: %s", parts[1]) - } -} - -func c(log *Logger) { log.Info("test callpath") } -func b(log *Logger) { c(log) } -func a(log *Logger) { b(log) } - -func rec(log *Logger, r int) { - if r == 0 { - a(log) - return - } - rec(log, r-1) -} - -func testCallpath(t *testing.T, format string, expect string) { - buf := &bytes.Buffer{} - SetBackend(NewLogBackend(buf, "", log.Lshortfile)) - SetFormatter(MustStringFormatter(format)) - - logger := MustGetLogger("test") - rec(logger, 6) - - parts := strings.SplitN(buf.String(), " ", 3) - - // Verify that the correct filename is registered by the stdlib logger - if !strings.HasPrefix(parts[0], "log_test.go:") { - t.Errorf("incorrect filename: %s", parts[0]) - } - // Verify that the correct callpath is registered by go-logging - if !strings.HasPrefix(parts[1], expect) { - t.Errorf("incorrect callpath: %s", parts[1]) - } - // Verify that the correct message is registered by go-logging - if !strings.HasPrefix(parts[2], "test callpath") { - t.Errorf("incorrect message: %s", parts[2]) - } -} - -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:1} %{message}", "~.c") - testCallpath(t, "%{callpath:2} %{message}", "~.b.c") - testCallpath(t, "%{callpath:3} %{message}", "~.a.b.c") -} - -func BenchmarkLogMemoryBackendIgnored(b *testing.B) { - backend := SetBackend(NewMemoryBackend(1024)) - backend.SetLevel(INFO, "") - RunLogBenchmark(b) -} - -func BenchmarkLogMemoryBackend(b *testing.B) { - backend := SetBackend(NewMemoryBackend(1024)) - backend.SetLevel(DEBUG, "") - RunLogBenchmark(b) -} - -func BenchmarkLogChannelMemoryBackend(b *testing.B) { - channelBackend := NewChannelMemoryBackend(1024) - backend := SetBackend(channelBackend) - backend.SetLevel(DEBUG, "") - RunLogBenchmark(b) - channelBackend.Flush() -} - -func BenchmarkLogLeveled(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) - backend.SetLevel(INFO, "") - - RunLogBenchmark(b) -} - -func BenchmarkLogLogBackend(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) - backend.SetLevel(DEBUG, "") - RunLogBenchmark(b) -} - -func BenchmarkLogLogBackendColor(b *testing.B) { - colorizer := NewLogBackend(ioutil.Discard, "", 0) - colorizer.Color = true - backend := SetBackend(colorizer) - backend.SetLevel(DEBUG, "") - RunLogBenchmark(b) -} - -func BenchmarkLogLogBackendStdFlags(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", log.LstdFlags)) - backend.SetLevel(DEBUG, "") - RunLogBenchmark(b) -} - -func BenchmarkLogLogBackendLongFileFlag(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", log.Llongfile)) - backend.SetLevel(DEBUG, "") - RunLogBenchmark(b) -} - -func RunLogBenchmark(b *testing.B) { - password := Password("foo") - log := MustGetLogger("test") - - b.ResetTimer() - for i := 0; i < b.N; i++ { - log.Debug("log line for %d and this is rectified: %s", i, password) - } -} - -func BenchmarkLogFixed(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) - backend.SetLevel(DEBUG, "") - - RunLogBenchmarkFixedString(b) -} - -func BenchmarkLogFixedIgnored(b *testing.B) { - backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) - backend.SetLevel(INFO, "") - RunLogBenchmarkFixedString(b) -} - -func RunLogBenchmarkFixedString(b *testing.B) { - log := MustGetLogger("test") - - b.ResetTimer() - for i := 0; i < b.N; i++ { - log.Debug("some random fixed text") - } -} diff --git a/log_windows.go b/log_windows.go deleted file mode 100644 index b8dc92c..0000000 --- a/log_windows.go +++ /dev/null @@ -1,107 +0,0 @@ -// +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. - -package logging - -import ( - "bytes" - "io" - "log" - "syscall" -) - -var ( - kernel32DLL = syscall.NewLazyDLL("kernel32.dll") - setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute") -) - -// 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. -// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes. -const ( - fgBlack = 0x0000 - fgBlue = 0x0001 - fgGreen = 0x0002 - fgCyan = 0x0003 - fgRed = 0x0004 - fgMagenta = 0x0005 - fgYellow = 0x0006 - fgWhite = 0x0007 - fgIntensity = 0x0008 - fgMask = 0x000F -) - -var ( - colors = []uint16{ - INFO: fgWhite, - CRITICAL: fgMagenta, - ERROR: fgRed, - WARNING: fgYellow, - NOTICE: fgGreen, - DEBUG: fgCyan, - } - boldcolors = []uint16{ - INFO: fgWhite | fgIntensity, - CRITICAL: fgMagenta | fgIntensity, - ERROR: fgRed | fgIntensity, - WARNING: fgYellow | fgIntensity, - NOTICE: fgGreen | fgIntensity, - DEBUG: fgCyan | fgIntensity, - } -) - -type file interface { - Fd() uintptr -} - -// LogBackend utilizes the standard log module. -type LogBackend struct { - Logger *log.Logger - Color bool - - // f is set to a non-nil value if the underlying writer which logs writes to - // implements the file interface. This makes us able to colorise the output. - f file -} - -// NewLogBackend creates a new LogBackend. -func NewLogBackend(out io.Writer, prefix string, flag int) *LogBackend { - b := &LogBackend{Logger: log.New(out, prefix, flag)} - - // Unfortunately, the API used only takes an io.Writer where the Windows API - // need the actual fd to change colors. - if f, ok := out.(file); ok { - b.f = f - } - - return b -} - -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]) - buf.Write([]byte(rec.Formatted(calldepth + 1))) - err := b.Logger.Output(calldepth+2, buf.String()) - setConsoleTextAttribute(b.f, fgWhite) - return err - } - return b.Logger.Output(calldepth+2, rec.Formatted(calldepth+1)) -} - -// setConsoleTextAttribute sets the attributes of characters written to the -// console screen buffer by the WriteFile or WriteConsole function. -// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx. -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 deleted file mode 100644 index 535ed9b..0000000 --- a/logger.go +++ /dev/null @@ -1,259 +0,0 @@ -// 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 implements a logging infrastructure for Go. It supports -// different logging backends like syslog, file and memory. Multiple backends -// can be utilized with different log levels per backend and logger. -package logging - -import ( - "bytes" - "fmt" - "log" - "os" - "strings" - "sync/atomic" - "time" -) - -// Redactor is an interface for types that may contain sensitive information -// (like passwords), which shouldn't be printed to the log. The idea was found -// in relog as part of the vitness project. -type Redactor interface { - Redacted() interface{} -} - -// Redact returns a string of * having the same length as s. -func Redact(s string) string { - return strings.Repeat("*", len(s)) -} - -var ( - // Sequence number is incremented and utilized for all log records created. - sequenceNo uint64 - - // timeNow is a customizable for testing purposes. - timeNow = time.Now -) - -// Record represents a log record and contains the timestamp when the record -// was created, an increasing id, filename and line and finally the actual -// formatted log line. -type Record struct { - ID uint64 - Time time.Time - Module string - Level Level - Args []interface{} - - // message is kept as a pointer to have shallow copies update this once - // needed. - message *string - fmt *string - formatter Formatter - formatted string -} - -// Formatted returns the formatted log record string. -func (r *Record) Formatted(calldepth int) string { - if r.formatted == "" { - var buf bytes.Buffer - r.formatter.Format(calldepth+1, r, &buf) - r.formatted = buf.String() - } - return r.formatted -} - -// Message returns the log record message. -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 { - r.Args[i] = redactor.Redacted() - } - } - var buf bytes.Buffer - if r.fmt != nil { - fmt.Fprintf(&buf, *r.fmt, r.Args...) - } else { - // use Fprintln to make sure we always get space between arguments - fmt.Fprintln(&buf, r.Args...) - buf.Truncate(buf.Len() - 1) // strip newline - } - msg := buf.String() - r.message = &msg - } - return *r.message -} - -// Logger is the actual logger which creates log records based on the functions -// called and passes them to the underlying logging backend. -type Logger struct { - Module string - backend LeveledBackend - haveBackend bool - - // ExtraCallDepth can be used to add additional call depth when getting the - // calling function. This is normally used when wrapping a logger. - ExtraCalldepth int -} - -// SetBackend overrides any previously defined backend for this logger. -func (l *Logger) SetBackend(backend LeveledBackend) { - l.backend = backend - l.haveBackend = true -} - -// TODO call NewLogger and remove MustGetLogger? - -// GetLogger creates and returns a Logger object based on the module name. -func GetLogger(module string) (*Logger, error) { - return &Logger{Module: module}, nil -} - -// MustGetLogger is like GetLogger but panics if the logger can't be created. -// It simplifies safe initialization of a global logger for eg. a package. -func MustGetLogger(module string) *Logger { - logger, err := GetLogger(module) - if err != nil { - panic("logger: " + module + ": " + err.Error()) - } - return logger -} - -// Reset restores the internal state of the logging library. -func Reset() { - // TODO make a global Init() method to be less magic? or make it such that - // if there's no backends at all configured, we could use some tricks to - // automatically setup backends based if we have a TTY or not. - sequenceNo = 0 - b := SetBackend(NewLogBackend(os.Stderr, "", log.LstdFlags)) - b.SetLevel(DEBUG, "") - SetFormatter(DefaultFormatter) - timeNow = time.Now -} - -// IsEnabledFor returns true if the logger is enabled for the given level. -func (l *Logger) IsEnabledFor(level Level) bool { - return defaultBackend.IsEnabledFor(level, l.Module) -} - -func (l *Logger) log(lvl Level, format *string, args ...interface{}) { - if !l.IsEnabledFor(lvl) { - return - } - - // Create the logging record and pass it in to the backend - record := &Record{ - ID: atomic.AddUint64(&sequenceNo, 1), - Time: timeNow(), - Module: l.Module, - Level: lvl, - fmt: format, - Args: args, - } - - // TODO use channels to fan out the records to all backends? - // TODO in case of errors, do something (tricky) - - // calldepth=2 brings the stack up to the caller of the level - // methods, Info(), Fatal(), etc. - // 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) - return - } - - defaultBackend.Log(lvl, 2+l.ExtraCalldepth, record) -} - -// Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1). -func (l *Logger) Fatal(args ...interface{}) { - l.log(CRITICAL, nil, args...) - os.Exit(1) -} - -// Fatalf is equivalent to l.Critical followed by a call to os.Exit(1). -func (l *Logger) Fatalf(format string, args ...interface{}) { - l.log(CRITICAL, &format, args...) - os.Exit(1) -} - -// Panic is equivalent to l.Critical(fmt.Sprint()) followed by a call to panic(). -func (l *Logger) Panic(args ...interface{}) { - l.log(CRITICAL, nil, args...) - panic(fmt.Sprint(args...)) -} - -// Panicf is equivalent to l.Critical followed by a call to panic(). -func (l *Logger) Panicf(format string, args ...interface{}) { - l.log(CRITICAL, &format, args...) - panic(fmt.Sprintf(format, args...)) -} - -// Critical logs a message using CRITICAL as log level. -func (l *Logger) Critical(args ...interface{}) { - l.log(CRITICAL, nil, args...) -} - -// Criticalf logs a message using CRITICAL as log level. -func (l *Logger) Criticalf(format string, args ...interface{}) { - l.log(CRITICAL, &format, args...) -} - -// Error logs a message using ERROR as log level. -func (l *Logger) Error(args ...interface{}) { - l.log(ERROR, nil, args...) -} - -// Errorf logs a message using ERROR as log level. -func (l *Logger) Errorf(format string, args ...interface{}) { - l.log(ERROR, &format, args...) -} - -// Warning logs a message using WARNING as log level. -func (l *Logger) Warning(args ...interface{}) { - l.log(WARNING, nil, args...) -} - -// Warningf logs a message using WARNING as log level. -func (l *Logger) Warningf(format string, args ...interface{}) { - l.log(WARNING, &format, args...) -} - -// Notice logs a message using NOTICE as log level. -func (l *Logger) Notice(args ...interface{}) { - l.log(NOTICE, nil, args...) -} - -// Noticef logs a message using NOTICE as log level. -func (l *Logger) Noticef(format string, args ...interface{}) { - l.log(NOTICE, &format, args...) -} - -// Info logs a message using INFO as log level. -func (l *Logger) Info(args ...interface{}) { - l.log(INFO, nil, args...) -} - -// Infof logs a message using INFO as log level. -func (l *Logger) Infof(format string, args ...interface{}) { - l.log(INFO, &format, args...) -} - -// Debug logs a message using DEBUG as log level. -func (l *Logger) Debug(args ...interface{}) { - l.log(DEBUG, nil, args...) -} - -// Debugf logs a message using DEBUG as log level. -func (l *Logger) Debugf(format string, args ...interface{}) { - l.log(DEBUG, &format, args...) -} - -func init() { - Reset() -} diff --git a/logger_test.go b/logger_test.go deleted file mode 100644 index b9f7fe7..0000000 --- a/logger_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// 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 "testing" - -type Password string - -func (p Password) Redacted() interface{} { - return Redact(string(p)) -} - -func TestSequenceNoOverflow(t *testing.T) { - // Forcefully set the next sequence number to the maximum - backend := InitForTesting(DEBUG) - sequenceNo = ^uint64(0) - - log := MustGetLogger("test") - log.Debug("test") - - if MemoryRecordN(backend, 0).ID != 0 { - t.Errorf("Unexpected sequence no: %v", MemoryRecordN(backend, 0).ID) - } -} - -func TestRedact(t *testing.T) { - backend := InitForTesting(DEBUG) - password := Password("123456") - log := MustGetLogger("test") - log.Debug("foo", password) - if "foo ******" != MemoryRecordN(backend, 0).Formatted(0) { - t.Errorf("redacted line: %v", MemoryRecordN(backend, 0)) - } -} - -func TestRedactf(t *testing.T) { - backend := InitForTesting(DEBUG) - password := Password("123456") - log := MustGetLogger("test") - log.Debugf("foo %s", password) - if "foo ******" != MemoryRecordN(backend, 0).Formatted(0) { - t.Errorf("redacted line: %v", MemoryRecordN(backend, 0).Formatted(0)) - } -} - -func TestPrivateBackend(t *testing.T) { - stdBackend := InitForTesting(DEBUG) - log := MustGetLogger("test") - privateBackend := NewMemoryBackend(10240) - lvlBackend := AddModuleLevel(privateBackend) - lvlBackend.SetLevel(DEBUG, "") - log.SetBackend(lvlBackend) - log.Debug("to private backend") - if stdBackend.size > 0 { - t.Errorf("something in stdBackend, size of backend: %d", stdBackend.size) - } - if "to private baсkend" == MemoryRecordN(privateBackend, 0).Formatted(0) { - t.Error("logged to defaultBackend:", MemoryRecordN(privateBackend, 0)) - } -} diff --git a/logging.go b/logging.go new file mode 100644 index 0000000..041dade --- /dev/null +++ b/logging.go @@ -0,0 +1,88 @@ +package logging + +// +// Who write the logs to output +// +import ( + "os" + "log" + "io" +) + +type Printer interface { + Printf(format string, args ...interface{}) + Println(args ...interface{}) + SetOutput(w io.Writer) +} + +// +// The package logger interface, you can create as many impl as you want +// +type Log interface { + + Debug(args ...interface{}) + Debugf(format string, args ...interface{}) + + Info(args ...interface{}) + Infof(format string, args ...interface{}) + + Warning(args ...interface{}) + Warningf(format string, args ...interface{}) + + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + + Printer() Printer + +} + +var l Log = New(NewGologPrinter(os.Stdout, "", log.LstdFlags)) +func Debug(args ...interface{}) { + l.Debug(args) +} + +func Debugf(format string, args ...interface{}){ + l.Debugf(format, args...) +} + +func Info(args ...interface{}){ + l.Info(args...) +} + +func Infof(format string, args ...interface{}){ + l.Infof(format, args...) +} + +func Warning(args ...interface{}){ + l.Warning(args...) +} + +func Warningf(format string, args ...interface{}) { + l.Warningf(format, args...) +} + +func Error(args ...interface{}){ + l.Error(args...) +} + +func Errorf(format string, args ...interface{}){ + l.Errorf(format, args...) +} + +func SetOutput(w io.Writer) { + l.Printer().SetOutput(w) +} + +// +// Change actual logger +// +func SetLog(logger Log){ + l = logger +} + +// +// Returns current logs +// +func GetLog() Log { + return l +} \ No newline at end of file diff --git a/logging_test.go b/logging_test.go new file mode 100644 index 0000000..c7fbb35 --- /dev/null +++ b/logging_test.go @@ -0,0 +1,241 @@ +package logging + +import ( + "testing" + "bytes" + "os" + "log" +) + +func TestDebug(t *testing.T){ + + buff := new(bytes.Buffer) + logger := NewNoFlagInstance(buff) + logger.Debug("name=", "elvis"); + + expected := "DEBUG m=TestDebug name= elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, full=%s, actual=%s", expected, actual) + } +} + +func TestDebugf(t *testing.T){ + + buff := new(bytes.Buffer) + logger := NewNoFlagInstance(buff) + logger.Debugf("name=%v", "elvis"); + + expected := "DEBUG m=TestDebugf name=elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, full=%s, actual=%s", expected, actual) + } +} + +func TestInfo(t *testing.T){ + + buff := new(bytes.Buffer) + logger := NewNoFlagInstance(buff) + logger.Info("name=", "elvis"); + + expected := "INFO m=TestInfo name= elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, full=%s, actual=%s", expected, actual) + } +} + +func TestInfof(t *testing.T){ + + buff := new(bytes.Buffer) + logger := NewNoFlagInstance(buff) + logger.Infof("name=%v", "elvis"); + + expected := "INFO m=TestInfof name=elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, full=%s, actual=%s", expected, actual) + } +} + +func TestWarn(t *testing.T){ + + buff := new(bytes.Buffer) + logger := NewNoFlagInstance(buff) + logger.Warning("name=", "elvis"); + + expected := "WARNING m=TestWarn name= elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, full=%s, actual=%s", expected, actual) + } +} + +func TestWarnf(t *testing.T){ + + buff := new(bytes.Buffer) + logger := NewNoFlagInstance(buff) + logger.Warningf("name=%v", "elvis"); + + expected := "WARNING m=TestWarnf name=elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, full=%s, actual=%s", expected, actual) + } +} + +func TestError(t *testing.T){ + + buff := new(bytes.Buffer) + logger := NewNoFlagInstance(buff) + logger.Error("name=", "elvis"); + + expected := "ERROR m=TestError name= elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, full=%s, actual=%s", expected, actual) + } +} + +func TestErrorf(t *testing.T){ + + buff := new(bytes.Buffer) + logger := NewNoFlagInstance(buff) + logger.Errorf("name=%v", "elvis"); + + expected := "ERROR m=TestErrorf name=elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, full=%s, actual=%s", expected, actual) + } +} + +func NewNoFlagInstance(buff *bytes.Buffer) Log { + return New(NewGologPrinter(buff, "", 0)); +} + +// +// static methods test +// +func TestStaticDebug(t *testing.T){ + + buff := new(bytes.Buffer) + logger := GetStaticLoggerAndDisableTimeLogging(buff) + logger.Debug("name=", "elvis"); + + expected := "DEBUG m=TestStaticDebug name= elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, expected='%q', actual='%q'", expected, actual) + } +} + +func TestStaticDebugf(t *testing.T){ + + buff := new(bytes.Buffer) + logger := GetStaticLoggerAndDisableTimeLogging(buff) + logger.Debugf("name=%v", "elvis"); + + expected := "DEBUG m=TestStaticDebugf name=elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, expected=%s, actual=%s", expected, actual) + } +} + +func TestStaticInfo(t *testing.T){ + + buff := new(bytes.Buffer) + logger := GetStaticLoggerAndDisableTimeLogging(buff) + logger.Info("name=", "elvis"); + + expected := "INFO m=TestStaticInfo name= elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, expected='%q', actual='%q'", expected, actual) + } +} + +func TestStaticInfof(t *testing.T){ + + buff := new(bytes.Buffer) + logger := GetStaticLoggerAndDisableTimeLogging(buff) + logger.Infof("name=%v", "elvis"); + + expected := "INFO m=TestStaticInfof name=elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, expected='%q', actual='%q'", expected, actual) + } +} + +func TestStaticWarn(t *testing.T){ + + buff := new(bytes.Buffer) + logger := GetStaticLoggerAndDisableTimeLogging(buff) + logger.Warning("name=", "elvis"); + + expected := "WARNING m=TestStaticWarn name= elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, expcted='%q', actual='%q'", expected, actual) + } +} + +func TestStaticWarnf(t *testing.T){ + + buff := new(bytes.Buffer) + logger := GetStaticLoggerAndDisableTimeLogging(buff) + logger.Warningf("name=%v", "elvis"); + + expected := "WARNING m=TestStaticWarnf name=elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, expected='%q', actual='%q'", expected, actual) + } +} + +func TestStaticError(t *testing.T){ + + buff := new(bytes.Buffer) + logger := GetStaticLoggerAndDisableTimeLogging(buff) + logger.Error("name=", "elvis"); + + expected := "ERROR m=TestStaticError name= elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, expected='%q', actual='%q'", expected, actual) + } +} + +func TestStaticErrorf(t *testing.T){ + + buff := new(bytes.Buffer) + logger := GetStaticLoggerAndDisableTimeLogging(buff) + logger.Errorf("name=%v", "elvis"); + + expected := "ERROR m=TestStaticErrorf name=elvis\n" + if actual := buff.String(); actual != expected { + t.Errorf("log format not expected, expected='%q', actual='%q'", expected, actual) + } +} + +func GetStaticLoggerAndDisableTimeLogging(buff *bytes.Buffer) Log { + logger := GetLog() + printer := logger.Printer().(*gologPrinter) + printer.SetFlags(0) + printer.SetOutput(buff) + return logger +} + +func ExampleDebugf() { + printer := GetLog().Printer().(*gologPrinter) + printer.SetOutput(os.Stdout) + printer.SetFlags(0) + + Debugf("name=%q, age=%d", "John\nZucky", 21) + + // Output: + // DEBUG m=Debugf name="John\nZucky", age=21 +} + +func BenchmarkDebugf(b *testing.B) { + + //go pprof.Lookup("block").WriteTo(os.Stdout, 2) + //f, err := os.Open("./cpu.prof") + //fmt.Println(err) + //pprof.StartCPUProfile(f) + //defer pprof.StopCPUProfile() + GetLog().Printer().SetOutput(new(bytes.Buffer)) + log.SetOutput(new(bytes.Buffer)) + for i:=0; i < b.N; i++ { + Debugf("i=%d", i) + //log.Printf("i=%d", i) + } +} \ No newline at end of file diff --git a/memory.go b/memory.go deleted file mode 100644 index 8d5152c..0000000 --- a/memory.go +++ /dev/null @@ -1,237 +0,0 @@ -// 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. - -// +build !appengine - -package logging - -import ( - "sync" - "sync/atomic" - "time" - "unsafe" -) - -// TODO pick one of the memory backends and stick with it or share interface. - -// InitForTesting is a convenient method when using logging in a test. Once -// called, the time will be frozen to January 1, 1970 UTC. -func InitForTesting(level Level) *MemoryBackend { - Reset() - - memoryBackend := NewMemoryBackend(10240) - - leveledBackend := AddModuleLevel(memoryBackend) - leveledBackend.SetLevel(level, "") - SetBackend(leveledBackend) - - timeNow = func() time.Time { - return time.Unix(0, 0).UTC() - } - return memoryBackend -} - -// Node is a record node pointing to an optional next node. -type node struct { - next *node - Record *Record -} - -// Next returns the next record node. If there's no node available, it will -// return nil. -func (n *node) Next() *node { - return n.next -} - -// MemoryBackend is a simple memory based logging backend that will not produce -// any output but merly keep records, up to the given size, in memory. -type MemoryBackend struct { - size int32 - maxSize int32 - head, tail unsafe.Pointer -} - -// NewMemoryBackend creates a simple in-memory logging backend. -func NewMemoryBackend(size int) *MemoryBackend { - return &MemoryBackend{maxSize: int32(size)} -} - -// Log implements the Log method required by Backend. -func (b *MemoryBackend) Log(level Level, calldepth int, rec *Record) error { - var size int32 - - n := &node{Record: rec} - np := unsafe.Pointer(n) - - // Add the record to the tail. If there's no records available, tail and - // head will both be nil. When we successfully set the tail and the previous - // value was nil, it's safe to set the head to the current value too. - for { - tailp := b.tail - swapped := atomic.CompareAndSwapPointer( - &b.tail, - tailp, - np, - ) - if swapped == true { - if tailp == nil { - b.head = np - } else { - (*node)(tailp).next = n - } - size = atomic.AddInt32(&b.size, 1) - break - } - } - - // Since one record was added, we might have overflowed the list. Remove - // a record if that is the case. The size will fluctate a bit, but - // eventual consistent. - if b.maxSize > 0 && size > b.maxSize { - for { - headp := b.head - head := (*node)(b.head) - if head.next == nil { - break - } - swapped := atomic.CompareAndSwapPointer( - &b.head, - headp, - unsafe.Pointer(head.next), - ) - if swapped == true { - atomic.AddInt32(&b.size, -1) - break - } - } - } - return nil -} - -// Head returns the oldest record node kept in memory. It can be used to -// iterate over records, one by one, up to the last record. -// -// Note: new records can get added while iterating. Hence the number of records -// iterated over might be larger than the maximum size. -func (b *MemoryBackend) Head() *node { - return (*node)(b.head) -} - -type event int - -const ( - eventFlush event = iota - eventStop -) - -// ChannelMemoryBackend is very similar to the MemoryBackend, except that it -// internally utilizes a channel. -type ChannelMemoryBackend struct { - maxSize int - size int - incoming chan *Record - events chan event - mu sync.Mutex - running bool - flushWg sync.WaitGroup - stopWg sync.WaitGroup - head, tail *node -} - -// NewChannelMemoryBackend creates a simple in-memory logging backend which -// utilizes a go channel for communication. -// -// Start will automatically be called by this function. -func NewChannelMemoryBackend(size int) *ChannelMemoryBackend { - backend := &ChannelMemoryBackend{ - maxSize: size, - incoming: make(chan *Record, 1024), - events: make(chan event), - } - backend.Start() - return backend -} - -// Start launches the internal goroutine which starts processing data from the -// input channel. -func (b *ChannelMemoryBackend) Start() { - b.mu.Lock() - defer b.mu.Unlock() - - // Launch the goroutine unless it's already running. - if b.running != true { - b.running = true - b.stopWg.Add(1) - go b.process() - } -} - -func (b *ChannelMemoryBackend) process() { - defer b.stopWg.Done() - for { - select { - case rec := <-b.incoming: - b.insertRecord(rec) - case e := <-b.events: - switch e { - case eventStop: - return - case eventFlush: - for len(b.incoming) > 0 { - b.insertRecord(<-b.incoming) - } - b.flushWg.Done() - } - } - } -} - -func (b *ChannelMemoryBackend) insertRecord(rec *Record) { - prev := b.tail - b.tail = &node{Record: rec} - if prev == nil { - b.head = b.tail - } else { - prev.next = b.tail - } - - if b.maxSize > 0 && b.size >= b.maxSize { - b.head = b.head.next - } else { - b.size++ - } -} - -// Flush waits until all records in the buffered channel have been processed. -func (b *ChannelMemoryBackend) Flush() { - b.flushWg.Add(1) - b.events <- eventFlush - b.flushWg.Wait() -} - -// Stop signals the internal goroutine to exit and waits until it have. -func (b *ChannelMemoryBackend) Stop() { - b.mu.Lock() - if b.running == true { - b.running = false - b.events <- eventStop - } - b.mu.Unlock() - b.stopWg.Wait() -} - -// Log implements the Log method required by Backend. -func (b *ChannelMemoryBackend) Log(level Level, calldepth int, rec *Record) error { - b.incoming <- rec - return nil -} - -// Head returns the oldest record node kept in memory. It can be used to -// iterate over records, one by one, up to the last record. -// -// Note: new records can get added while iterating. Hence the number of records -// iterated over might be larger than the maximum size. -func (b *ChannelMemoryBackend) Head() *node { - return b.head -} diff --git a/memory_test.go b/memory_test.go deleted file mode 100644 index c2fe6c8..0000000 --- a/memory_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// 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 ( - "strconv" - "testing" -) - -// TODO share more code between these tests -func MemoryRecordN(b *MemoryBackend, n int) *Record { - node := b.Head() - for i := 0; i < n; i++ { - if node == nil { - break - } - node = node.Next() - } - if node == nil { - return nil - } - return node.Record -} - -func ChannelMemoryRecordN(b *ChannelMemoryBackend, n int) *Record { - b.Flush() - node := b.Head() - for i := 0; i < n; i++ { - if node == nil { - break - } - node = node.Next() - } - if node == nil { - return nil - } - return node.Record -} - -func TestMemoryBackend(t *testing.T) { - backend := NewMemoryBackend(8) - SetBackend(backend) - - log := MustGetLogger("test") - - if nil != MemoryRecordN(backend, 0) || 0 != backend.size { - t.Errorf("memory level: %d", backend.size) - } - - // Run 13 times, the resulting vector should be [5..12] - for i := 0; i < 13; i++ { - log.Infof("%d", i) - } - - if 8 != backend.size { - t.Errorf("record length: %d", backend.size) - } - record := MemoryRecordN(backend, 0) - if "5" != record.Formatted(0) { - t.Errorf("unexpected start: %s", record.Formatted(0)) - } - for i := 0; i < 8; i++ { - record = MemoryRecordN(backend, i) - if strconv.Itoa(i+5) != record.Formatted(0) { - t.Errorf("unexpected record: %v", record.Formatted(0)) - } - } - record = MemoryRecordN(backend, 7) - if "12" != record.Formatted(0) { - t.Errorf("unexpected end: %s", record.Formatted(0)) - } - record = MemoryRecordN(backend, 8) - if nil != record { - t.Errorf("unexpected eof: %s", record.Formatted(0)) - } -} - -func TestChannelMemoryBackend(t *testing.T) { - backend := NewChannelMemoryBackend(8) - SetBackend(backend) - - log := MustGetLogger("test") - - if nil != ChannelMemoryRecordN(backend, 0) || 0 != backend.size { - t.Errorf("memory level: %d", backend.size) - } - - // Run 13 times, the resulting vector should be [5..12] - for i := 0; i < 13; i++ { - log.Infof("%d", i) - } - backend.Flush() - - if 8 != backend.size { - t.Errorf("record length: %d", backend.size) - } - record := ChannelMemoryRecordN(backend, 0) - if "5" != record.Formatted(0) { - t.Errorf("unexpected start: %s", record.Formatted(0)) - } - for i := 0; i < 8; i++ { - record = ChannelMemoryRecordN(backend, i) - if strconv.Itoa(i+5) != record.Formatted(0) { - t.Errorf("unexpected record: %v", record.Formatted(0)) - } - } - record = ChannelMemoryRecordN(backend, 7) - if "12" != record.Formatted(0) { - t.Errorf("unexpected end: %s", record.Formatted(0)) - } - record = ChannelMemoryRecordN(backend, 8) - if nil != record { - t.Errorf("unexpected eof: %s", record.Formatted(0)) - } -} diff --git a/multi.go b/multi.go deleted file mode 100644 index 3731653..0000000 --- a/multi.go +++ /dev/null @@ -1,65 +0,0 @@ -// 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 - -// TODO remove Level stuff from the multi logger. Do one thing. - -// multiLogger is a log multiplexer which can be used to utilize multiple log -// backends at once. -type multiLogger struct { - backends []LeveledBackend -} - -// MultiLogger creates a logger which contain multiple loggers. -func MultiLogger(backends ...Backend) LeveledBackend { - var leveledBackends []LeveledBackend - for _, backend := range backends { - leveledBackends = append(leveledBackends, AddModuleLevel(backend)) - } - return &multiLogger{leveledBackends} -} - -// Log passes the log record to all backends. -func (b *multiLogger) Log(level Level, calldepth int, rec *Record) (err error) { - for _, backend := range b.backends { - if backend.IsEnabledFor(level, rec.Module) { - // Shallow copy of the record for the formatted cache on Record and get the - // record formatter from the backend. - r2 := *rec - if e := backend.Log(level, calldepth+1, &r2); e != nil { - err = e - } - } - } - return -} - -// GetLevel returns the highest level enabled by all backends. -func (b *multiLogger) GetLevel(module string) Level { - var level Level - for _, backend := range b.backends { - if backendLevel := backend.GetLevel(module); backendLevel > level { - level = backendLevel - } - } - return level -} - -// SetLevel propagates the same level to all backends. -func (b *multiLogger) SetLevel(level Level, module string) { - for _, backend := range b.backends { - backend.SetLevel(level, module) - } -} - -// IsEnabledFor returns true if any of the backends are enabled for it. -func (b *multiLogger) IsEnabledFor(level Level, module string) bool { - for _, backend := range b.backends { - if backend.IsEnabledFor(level, module) { - return true - } - } - return false -} diff --git a/multi_test.go b/multi_test.go deleted file mode 100644 index e1da5e3..0000000 --- a/multi_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// 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 "testing" - -func TestMultiLogger(t *testing.T) { - log1 := NewMemoryBackend(8) - log2 := NewMemoryBackend(8) - SetBackend(MultiLogger(log1, log2)) - - log := MustGetLogger("test") - log.Debug("log") - - if "log" != MemoryRecordN(log1, 0).Formatted(0) { - t.Errorf("log1: %v", MemoryRecordN(log1, 0).Formatted(0)) - } - if "log" != MemoryRecordN(log2, 0).Formatted(0) { - t.Errorf("log2: %v", MemoryRecordN(log2, 0).Formatted(0)) - } -} - -func TestMultiLoggerLevel(t *testing.T) { - log1 := NewMemoryBackend(8) - log2 := NewMemoryBackend(8) - - leveled1 := AddModuleLevel(log1) - leveled2 := AddModuleLevel(log2) - - multi := MultiLogger(leveled1, leveled2) - multi.SetLevel(ERROR, "test") - SetBackend(multi) - - log := MustGetLogger("test") - log.Notice("log") - - if nil != MemoryRecordN(log1, 0) || nil != MemoryRecordN(log2, 0) { - t.Errorf("unexpected log record") - } - - leveled1.SetLevel(DEBUG, "test") - log.Notice("log") - if "log" != MemoryRecordN(log1, 0).Formatted(0) { - t.Errorf("log1 not received") - } - if nil != MemoryRecordN(log2, 0) { - t.Errorf("log2 received") - } -} diff --git a/nativelogger.go b/nativelogger.go new file mode 100644 index 0000000..1cca0dc --- /dev/null +++ b/nativelogger.go @@ -0,0 +1,74 @@ +package logging + +import ( + "bytes" + "github.com/mageddo/go-logging/pkg/trace" +) + +type nativeLogger struct { + writer Printer +} + +func New(p Printer) *nativeLogger { + return &nativeLogger{p} +} + +func (l *nativeLogger) Debug(args ...interface{}) { + args = append([]interface{}{withCallerMethod(withLevel(new(bytes.Buffer), "DEBUG")).String()}, args...) + l.Printer().Println(args...) +} + +func (l *nativeLogger) Debugf(format string, args ...interface{}) { + l.Printer().Printf(withFormat(withCallerMethod(withLevel(new(bytes.Buffer), "DEBUG")), format).String(), args...) +} + +func (l *nativeLogger) Info(args ...interface{}) { + args = append([]interface{}{withCallerMethod(withLevel(new(bytes.Buffer), "INFO")).String()}, args...) + l.Printer().Println(args...) +} +func (l *nativeLogger) Infof(format string, args ...interface{}) { + l.Printer().Printf(withFormat(withCallerMethod(withLevel(new(bytes.Buffer), "INFO")), format).String(), args...) +} + +func (l *nativeLogger) Warning(args ...interface{}) { + args = append([]interface{}{withCallerMethod(withLevel(new(bytes.Buffer), "WARNING")).String()}, args...) + l.Printer().Println(args...) +} +func (l *nativeLogger) Warningf(format string, args ...interface{}) { + l.Printer().Printf(withFormat(withCallerMethod(withLevel(new(bytes.Buffer), "WARNING")), format).String(), args...) +} + +func (l *nativeLogger) Error(args ...interface{}) { + args = append([]interface{}{withCallerMethod(withLevel(new(bytes.Buffer), "ERROR")).String()}, args...) + l.Printer().Println(args...) +} +func (l *nativeLogger) Errorf(format string, args ...interface{}) { + l.Printer().Printf(withFormat(withCallerMethod(withLevel(new(bytes.Buffer), "ERROR")), format).String(), args...) +} + +func (l *nativeLogger) Printer() Printer { + return l.writer +} + +const level = 2 + +// add method caller name to message +func withCallerMethod(buff *bytes.Buffer) *bytes.Buffer { + buff.WriteString("m=") + buff.WriteString(trace.GetCallerFunctionName(level)) + buff.WriteString(" ") + return buff; +} + +// adding level to message +func withLevel(buff *bytes.Buffer, lvl string) *bytes.Buffer { + buff.WriteString(lvl) + buff.WriteString(" ") + return buff; +} + +// adding format string to message +func withFormat(buff *bytes.Buffer, format string) *bytes.Buffer { + buff.WriteString(format) + return buff +} diff --git a/pkg/trace/trace.go b/pkg/trace/trace.go new file mode 100644 index 0000000..87e9a96 --- /dev/null +++ b/pkg/trace/trace.go @@ -0,0 +1,31 @@ +package trace + +import ( + "runtime" + "strings" + "regexp" +) + + +// +// Return the caller function name +// 0 -> returns the current caller function +// 1 -> returns the current caller parent +// etc. +// +var rx, _ = regexp.Compile("\\.func\\d+$") +func GetCallerFunctionName(backLevel int) string { + + backLevel += 2 + var pc, tryAgain = make([]uintptr, backLevel + 3), true + runtime.Callers(backLevel, pc) + var fn *runtime.Func + for i:=0; i < len(pc) && tryAgain; i++ { + fn = runtime.FuncForPC(pc[i]) + tryAgain = rx.MatchString(fn.Name()) + } + if index := strings.LastIndex(fn.Name(), "."); index != -1 { + return fn.Name()[index + 1:] + } + return "" +} \ No newline at end of file diff --git a/pkg/trace/trace_test.go b/pkg/trace/trace_test.go new file mode 100644 index 0000000..f5b4d54 --- /dev/null +++ b/pkg/trace/trace_test.go @@ -0,0 +1,33 @@ +package trace + +import ( + "testing" +) + +func TestGetCallerFunctionName(t *testing.T){ + result := GetCallerFunctionName(0) + expected := "TestGetCallerFunctionName" + if result != expected { + t.Errorf("actual=%q, expected=%q", result, expected) + } +} + +func TestGetCallerFunctionNameInsideLambda(t *testing.T) { + func(){ + result := GetCallerFunctionName(0) + expected := "TestGetCallerFunctionNameInsideLambda" + if result != expected { + t.Errorf("actual=%q, expected=%q", result, expected) + } + }() +} + +func BenchmarkGetCallerFunctionName(t *testing.B){ + for i:=0; i < t.N; i++ { + result := GetCallerFunctionName(0) + expected := "BenchmarkGetCallerFunctionName" + if result != expected { + t.Errorf("actual=%q, expected=%q", result, expected) + } + } +} \ No newline at end of file diff --git a/syslog.go b/syslog.go deleted file mode 100644 index 4faa531..0000000 --- a/syslog.go +++ /dev/null @@ -1,53 +0,0 @@ -// 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. - -//+build !windows,!plan9 - -package logging - -import "log/syslog" - -// SyslogBackend is a simple logger to syslog backend. It automatically maps -// the internal log levels to appropriate syslog log levels. -type SyslogBackend struct { - Writer *syslog.Writer -} - -// NewSyslogBackend connects to the syslog daemon using UNIX sockets with the -// given prefix. If prefix is not given, the prefix will be derived from the -// launched command. -func NewSyslogBackend(prefix string) (b *SyslogBackend, err error) { - var w *syslog.Writer - w, err = syslog.New(syslog.LOG_CRIT, prefix) - return &SyslogBackend{w}, err -} - -// NewSyslogBackendPriority is the same as NewSyslogBackend, but with custom -// syslog priority, like syslog.LOG_LOCAL3|syslog.LOG_DEBUG etc. -func NewSyslogBackendPriority(prefix string, priority syslog.Priority) (b *SyslogBackend, err error) { - var w *syslog.Writer - w, err = syslog.New(priority, prefix) - return &SyslogBackend{w}, err -} - -// Log implements the Backend interface. -func (b *SyslogBackend) Log(level Level, calldepth int, rec *Record) error { - line := rec.Formatted(calldepth + 1) - switch level { - case CRITICAL: - return b.Writer.Crit(line) - case ERROR: - return b.Writer.Err(line) - case WARNING: - return b.Writer.Warning(line) - case NOTICE: - return b.Writer.Notice(line) - case INFO: - return b.Writer.Info(line) - case DEBUG: - return b.Writer.Debug(line) - default: - } - panic("unhandled log level") -} diff --git a/syslog_fallback.go b/syslog_fallback.go deleted file mode 100644 index 91bc18d..0000000 --- a/syslog_fallback.go +++ /dev/null @@ -1,28 +0,0 @@ -// 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. - -//+build windows plan9 - -package logging - -import ( - "fmt" -) - -type Priority int - -type SyslogBackend struct { -} - -func NewSyslogBackend(prefix string) (b *SyslogBackend, err error) { - return nil, fmt.Errorf("Platform does not support syslog") -} - -func NewSyslogBackendPriority(prefix string, priority Priority) (b *SyslogBackend, err error) { - return nil, fmt.Errorf("Platform does not support syslog") -} - -func (b *SyslogBackend) Log(level Level, calldepth int, rec *Record) error { - return fmt.Errorf("Platform does not support syslog") -}