pax_global_header00006660000000000000000000000064131375720760014525gustar00rootroot0000000000000052 comment=bb2702d423886830dee131692131d35648c382e2 logrus-prefixed-formatter-0.5.2/000077500000000000000000000000001313757207600166515ustar00rootroot00000000000000logrus-prefixed-formatter-0.5.2/.gitignore000066400000000000000000000005251313757207600206430ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof # IDEA files .idea *.iml # OS X .DS_Store # Unit testing *.coverprofile logrus-prefixed-formatter-0.5.2/.travis.yml000066400000000000000000000001461313757207600207630ustar00rootroot00000000000000language: go go: - 1.5 - 1.6 - 1.7 install: - make deps script: - make test sudo: false logrus-prefixed-formatter-0.5.2/LICENSE000066400000000000000000000020721313757207600176570ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017 Denis Parchenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. logrus-prefixed-formatter-0.5.2/Makefile000066400000000000000000000006701313757207600203140ustar00rootroot00000000000000NAME=logrus-prefixed-formatter PACKAGES=$(shell go list ./...) deps: @echo "--> Installing dependencies" @go get -d -v -t ./... test-deps: @which ginkgo 2>/dev/null ; if [ $$? -eq 1 ]; then \ go get -u -v github.com/onsi/ginkgo/ginkgo; \ fi test: test-deps @echo "--> Running tests" @ginkgo -r --randomizeAllSpecs --randomizeSuites --failOnPending --cover --trace --race format: @echo "--> Running go fmt" @go fmt $(PACKAGES) logrus-prefixed-formatter-0.5.2/README.md000066400000000000000000000106661313757207600201410ustar00rootroot00000000000000# Logrus Prefixed Log Formatter [![Build Status](https://travis-ci.org/x-cray/logrus-prefixed-formatter.svg?branch=master)](https://travis-ci.org/x-cray/logrus-prefixed-formatter) [Logrus](https://github.com/sirupsen/logrus) formatter mainly based on original `logrus.TextFormatter` but with slightly modified colored output and support for log entry prefixes, e.g. message source followed by a colon. In addition, custom color themes are supported. ![Formatter screenshot](http://cl.ly/image/1w0B3F233F3z/formatter-screenshot@2x.png) Just like with the original `logrus.TextFormatter` when a TTY is not attached, the output is compatible with the [logfmt](http://godoc.org/github.com/kr/logfmt) format: ```text time="Oct 27 00:44:26" level=debug msg="Started observing beach" animal=walrus number=8 time="Oct 27 00:44:26" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10 time="Oct 27 00:44:26" level=warning msg="The group's number increased tremendously!" number=122 omg=true time="Oct 27 00:44:26" level=debug msg="Temperature changes" temperature=-4 time="Oct 27 00:44:26" level=panic msg="It's over 9000!" animal=orca size=9009 time="Oct 27 00:44:26" level=fatal msg="The ice breaks!" number=100 omg=true exit status 1 ``` ## Installation To install formatter, use `go get`: ```sh $ go get github.com/x-cray/logrus-prefixed-formatter ``` ## Usage Here is how it should be used: ```go package main import ( "github.com/sirupsen/logrus" prefixed "github.com/x-cray/logrus-prefixed-formatter" ) var log = logrus.New() func init() { log.Formatter = new(prefixed.TextFormatter) log.Level = logrus.DebugLevel } func main() { log.WithFields(logrus.Fields{ "prefix": "main", "animal": "walrus", "number": 8, }).Debug("Started observing beach") log.WithFields(logrus.Fields{ "prefix": "sensor", "temperature": -4, }).Info("Temperature changes") } ``` ## API `prefixed.TextFormatter` exposes the following fields and methods. ### Fields * `ForceColors bool` — set to true to bypass checking for a TTY before outputting colors. * `DisableColors bool` — force disabling colors. For a TTY colors are enabled by default. * `DisableUppercase bool` — set to true to turn off the conversion of the log level names to uppercase. * `ForceFormatting bool` — force formatted layout, even for non-TTY output. * `DisableTimestamp bool` — disable timestamp logging. Useful when output is redirected to logging system that already adds timestamps. * `FullTimestamp bool` — enable logging the full timestamp when a TTY is attached instead of just the time passed since beginning of execution. * `TimestampFormat string` — timestamp format to use for display when a full timestamp is printed. * `DisableSorting bool` — the fields are sorted by default for a consistent output. For applications that log extremely frequently and don't use the JSON formatter this may not be desired. * `QuoteEmptyFields bool` — wrap empty fields in quotes if true. * `QuoteCharacter string` — can be set to the override the default quoting character `"` with something else. For example: `'`, or `` ` ``. * `SpacePadding int` — pad msg field with spaces on the right for display. The value for this parameter will be the size of padding. Its default value is zero, which means no padding will be applied. ### Methods #### `SetColorScheme(colorScheme *prefixed.ColorScheme)` Sets an alternative color scheme for colored output. `prefixed.ColorScheme` struct supports the following fields: * `InfoLevelStyle string` — info level style. * `WarnLevelStyle string` — warn level style. * `ErrorLevelStyle string` — error style. * `FatalLevelStyle string` — fatal level style. * `PanicLevelStyle string` — panic level style. * `DebugLevelStyle string` — debug level style. * `PrefixStyle string` — prefix style. * `TimestampStyle string` — timestamp style. Color styles should be specified using [mgutz/ansi](https://github.com/mgutz/ansi#style-format) style syntax. For example, here is the default theme: ```go InfoLevelStyle: "green", WarnLevelStyle: "yellow", ErrorLevelStyle: "red", FatalLevelStyle: "red", PanicLevelStyle: "red", DebugLevelStyle: "blue", PrefixStyle: "cyan", TimestampStyle: "black+h" ``` It's not necessary to specify all colors when changing color scheme if you want to change just specific ones: ```go formatter.SetColorScheme(&prefixed.ColorScheme{ PrefixStyle: "blue+b", TimestampStyle: "white+h", }) ``` # License MIT logrus-prefixed-formatter-0.5.2/examples/000077500000000000000000000000001313757207600204675ustar00rootroot00000000000000logrus-prefixed-formatter-0.5.2/examples/basic.go000066400000000000000000000024051313757207600221000ustar00rootroot00000000000000package main import ( "github.com/sirupsen/logrus" prefixed "github.com/x-cray/logrus-prefixed-formatter" ) var log = logrus.New() func init() { formatter := new(prefixed.TextFormatter) log.Formatter = formatter log.Level = logrus.DebugLevel } func main() { defer func() { err := recover() if err != nil { // Fatal message log.WithFields(logrus.Fields{ "omg": true, "number": 100, }).Fatal("[main] The ice breaks!") } }() // You could either provide a map key called `prefix` to add prefix log.WithFields(logrus.Fields{ "prefix": "main", "animal": "walrus", "number": 8, }).Debug("Started observing beach") // Or you can simply add prefix in square brackets within message itself log.WithFields(logrus.Fields{ "animal": "walrus", "size": 10, }).Debug("[main] A group of walrus emerges from the ocean") // Warning message log.WithFields(logrus.Fields{ "omg": true, "number": 122, }).Warn("[main] The group's number increased tremendously!") // Information message log.WithFields(logrus.Fields{ "prefix": "sensor", "temperature": -4, }).Info("Temperature changes") // Panic message log.WithFields(logrus.Fields{ "prefix": "sensor", "animal": "orca", "size": 9009, }).Panic("It's over 9000!") } logrus-prefixed-formatter-0.5.2/examples/themes.go000066400000000000000000000020651313757207600223060ustar00rootroot00000000000000package main import ( "github.com/sirupsen/logrus" prefixed "github.com/x-cray/logrus-prefixed-formatter" ) var log = logrus.New() func init() { formatter := new(prefixed.TextFormatter) formatter.FullTimestamp = true // Set specific colors for prefix and timestamp formatter.SetColorScheme(&prefixed.ColorScheme{ PrefixStyle: "blue+b", TimestampStyle: "white+h", }) log.Formatter = formatter log.Level = logrus.DebugLevel } func main() { log.WithFields(logrus.Fields{ "prefix": "main", "animal": "walrus", "number": 8, }).Debug("Started observing beach") // Or you can simply add prefix in square brackets within message itself log.WithFields(logrus.Fields{ "animal": "walrus", "size": 10, }).Debug("[main] A group of walrus emerges from the ocean") // Warning message log.WithFields(logrus.Fields{ "omg": true, "number": 122, }).Warn("[main] The group's number increased tremendously!") // Information message log.WithFields(logrus.Fields{ "prefix": "sensor", "temperature": -4, }).Info("Temperature changes") } logrus-prefixed-formatter-0.5.2/formatter.go000066400000000000000000000230121313757207600212010ustar00rootroot00000000000000package prefixed import ( "bytes" "fmt" "io" "os" "regexp" "sort" "strings" "sync" "time" "github.com/sirupsen/logrus" "github.com/mgutz/ansi" "golang.org/x/crypto/ssh/terminal" ) const defaultTimestampFormat = time.RFC3339 var ( baseTimestamp time.Time = time.Now() defaultColorScheme *ColorScheme = &ColorScheme{ InfoLevelStyle: "green", WarnLevelStyle: "yellow", ErrorLevelStyle: "red", FatalLevelStyle: "red", PanicLevelStyle: "red", DebugLevelStyle: "blue", PrefixStyle: "cyan", TimestampStyle: "black+h", } noColorsColorScheme *compiledColorScheme = &compiledColorScheme{ InfoLevelColor: ansi.ColorFunc(""), WarnLevelColor: ansi.ColorFunc(""), ErrorLevelColor: ansi.ColorFunc(""), FatalLevelColor: ansi.ColorFunc(""), PanicLevelColor: ansi.ColorFunc(""), DebugLevelColor: ansi.ColorFunc(""), PrefixColor: ansi.ColorFunc(""), TimestampColor: ansi.ColorFunc(""), } defaultCompiledColorScheme *compiledColorScheme = compileColorScheme(defaultColorScheme) ) func miniTS() int { return int(time.Since(baseTimestamp) / time.Second) } type ColorScheme struct { InfoLevelStyle string WarnLevelStyle string ErrorLevelStyle string FatalLevelStyle string PanicLevelStyle string DebugLevelStyle string PrefixStyle string TimestampStyle string } type compiledColorScheme struct { InfoLevelColor func(string) string WarnLevelColor func(string) string ErrorLevelColor func(string) string FatalLevelColor func(string) string PanicLevelColor func(string) string DebugLevelColor func(string) string PrefixColor func(string) string TimestampColor func(string) string } type TextFormatter struct { // Set to true to bypass checking for a TTY before outputting colors. ForceColors bool // Force disabling colors. For a TTY colors are enabled by default. DisableColors bool // Force formatted layout, even for non-TTY output. ForceFormatting bool // Disable timestamp logging. useful when output is redirected to logging // system that already adds timestamps. DisableTimestamp bool // Disable the conversion of the log levels to uppercase DisableUppercase bool // Enable logging the full timestamp when a TTY is attached instead of just // the time passed since beginning of execution. FullTimestamp bool // Timestamp format to use for display when a full timestamp is printed. TimestampFormat string // The fields are sorted by default for a consistent output. For applications // that log extremely frequently and don't use the JSON formatter this may not // be desired. DisableSorting bool // Wrap empty fields in quotes if true. QuoteEmptyFields bool // Can be set to the override the default quoting character " // with something else. For example: ', or `. QuoteCharacter string // Pad msg field with spaces on the right for display. // The value for this parameter will be the size of padding. // Its default value is zero, which means no padding will be applied for msg. SpacePadding int // Color scheme to use. colorScheme *compiledColorScheme // Whether the logger's out is to a terminal. isTerminal bool sync.Once } func getCompiledColor(main string, fallback string) func(string) string { var style string if main != "" { style = main } else { style = fallback } return ansi.ColorFunc(style) } func compileColorScheme(s *ColorScheme) *compiledColorScheme { return &compiledColorScheme{ InfoLevelColor: getCompiledColor(s.InfoLevelStyle, defaultColorScheme.InfoLevelStyle), WarnLevelColor: getCompiledColor(s.WarnLevelStyle, defaultColorScheme.WarnLevelStyle), ErrorLevelColor: getCompiledColor(s.ErrorLevelStyle, defaultColorScheme.ErrorLevelStyle), FatalLevelColor: getCompiledColor(s.FatalLevelStyle, defaultColorScheme.FatalLevelStyle), PanicLevelColor: getCompiledColor(s.PanicLevelStyle, defaultColorScheme.PanicLevelStyle), DebugLevelColor: getCompiledColor(s.DebugLevelStyle, defaultColorScheme.DebugLevelStyle), PrefixColor: getCompiledColor(s.PrefixStyle, defaultColorScheme.PrefixStyle), TimestampColor: getCompiledColor(s.TimestampStyle, defaultColorScheme.TimestampStyle), } } func (f *TextFormatter) init(entry *logrus.Entry) { if len(f.QuoteCharacter) == 0 { f.QuoteCharacter = "\"" } if entry.Logger != nil { f.isTerminal = f.checkIfTerminal(entry.Logger.Out) } } func (f *TextFormatter) checkIfTerminal(w io.Writer) bool { switch v := w.(type) { case *os.File: return terminal.IsTerminal(int(v.Fd())) default: return false } } func (f *TextFormatter) SetColorScheme(colorScheme *ColorScheme) { f.colorScheme = compileColorScheme(colorScheme) } func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) { var b *bytes.Buffer var keys []string = make([]string, 0, len(entry.Data)) for k := range entry.Data { keys = append(keys, k) } lastKeyIdx := len(keys) - 1 if !f.DisableSorting { sort.Strings(keys) } if entry.Buffer != nil { b = entry.Buffer } else { b = &bytes.Buffer{} } prefixFieldClashes(entry.Data) f.Do(func() { f.init(entry) }) isFormatted := f.ForceFormatting || f.isTerminal timestampFormat := f.TimestampFormat if timestampFormat == "" { timestampFormat = defaultTimestampFormat } if isFormatted { isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors var colorScheme *compiledColorScheme if isColored { if f.colorScheme == nil { colorScheme = defaultCompiledColorScheme } else { colorScheme = f.colorScheme } } else { colorScheme = noColorsColorScheme } f.printColored(b, entry, keys, timestampFormat, colorScheme) } else { if !f.DisableTimestamp { f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat), true) } f.appendKeyValue(b, "level", entry.Level.String(), true) if entry.Message != "" { f.appendKeyValue(b, "msg", entry.Message, lastKeyIdx >= 0) } for i, key := range keys { f.appendKeyValue(b, key, entry.Data[key], lastKeyIdx != i) } } b.WriteByte('\n') return b.Bytes(), nil } func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string, colorScheme *compiledColorScheme) { var levelColor func(string) string var levelText string switch entry.Level { case logrus.InfoLevel: levelColor = colorScheme.InfoLevelColor case logrus.WarnLevel: levelColor = colorScheme.WarnLevelColor case logrus.ErrorLevel: levelColor = colorScheme.ErrorLevelColor case logrus.FatalLevel: levelColor = colorScheme.FatalLevelColor case logrus.PanicLevel: levelColor = colorScheme.PanicLevelColor default: levelColor = colorScheme.DebugLevelColor } if entry.Level != logrus.WarnLevel { levelText = entry.Level.String() } else { levelText = "warn" } if !f.DisableUppercase { levelText = strings.ToUpper(levelText) } level := levelColor(fmt.Sprintf("%5s", levelText)) prefix := "" message := entry.Message if prefixValue, ok := entry.Data["prefix"]; ok { prefix = colorScheme.PrefixColor(" " + prefixValue.(string) + ":") } else { prefixValue, trimmedMsg := extractPrefix(entry.Message) if len(prefixValue) > 0 { prefix = colorScheme.PrefixColor(" " + prefixValue + ":") message = trimmedMsg } } messageFormat := "%s" if f.SpacePadding != 0 { messageFormat = fmt.Sprintf("%%-%ds", f.SpacePadding) } if f.DisableTimestamp { fmt.Fprintf(b, "%s%s "+messageFormat, level, prefix, message) } else { var timestamp string if !f.FullTimestamp { timestamp = fmt.Sprintf("[%04d]", miniTS()) } else { timestamp = fmt.Sprintf("[%s]", entry.Time.Format(timestampFormat)) } fmt.Fprintf(b, "%s %s%s "+messageFormat, colorScheme.TimestampColor(timestamp), level, prefix, message) } for _, k := range keys { if k != "prefix" { v := entry.Data[k] fmt.Fprintf(b, " %s=%+v", levelColor(k), v) } } } func (f *TextFormatter) needsQuoting(text string) bool { if f.QuoteEmptyFields && len(text) == 0 { return true } for _, ch := range text { if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '.') { return true } } return false } func extractPrefix(msg string) (string, string) { prefix := "" regex := regexp.MustCompile("^\\[(.*?)\\]") if regex.MatchString(msg) { match := regex.FindString(msg) prefix, msg = match[1:len(match)-1], strings.TrimSpace(msg[len(match):]) } return prefix, msg } func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}, appendSpace bool) { b.WriteString(key) b.WriteByte('=') f.appendValue(b, value) if appendSpace { b.WriteByte(' ') } } func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { switch value := value.(type) { case string: if !f.needsQuoting(value) { b.WriteString(value) } else { fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter) } case error: errmsg := value.Error() if !f.needsQuoting(errmsg) { b.WriteString(errmsg) } else { fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter) } default: fmt.Fprint(b, value) } } // This is to not silently overwrite `time`, `msg` and `level` fields when // dumping it. If this code wasn't there doing: // // logrus.WithField("level", 1).Info("hello") // // would just silently drop the user provided level. Instead with this code // it'll be logged as: // // {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} func prefixFieldClashes(data logrus.Fields) { if t, ok := data["time"]; ok { data["fields.time"] = t } if m, ok := data["msg"]; ok { data["fields.msg"] = m } if l, ok := data["level"]; ok { data["fields.level"] = l } } logrus-prefixed-formatter-0.5.2/formatter_test.go000066400000000000000000000023211313757207600222400ustar00rootroot00000000000000package prefixed_test import ( . "github.com/x-cray/logrus-prefixed-formatter" "github.com/sirupsen/logrus" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Formatter", func() { var formatter *TextFormatter var log *logrus.Logger var output *LogOutput BeforeEach(func() { output = new(LogOutput) formatter = new(TextFormatter) log = logrus.New() log.Out = output log.Formatter = formatter log.Level = logrus.DebugLevel }) Describe("logfmt output", func() { It("should output simple message", func() { formatter.DisableTimestamp = true log.Debug("test") Ω(output.GetValue()).Should(Equal("level=debug msg=test\n")) }) It("should output message with additional field", func() { formatter.DisableTimestamp = true log.WithFields(logrus.Fields{ "animal": "walrus" }).Debug("test") Ω(output.GetValue()).Should(Equal("level=debug msg=test animal=walrus\n")) }) }) Describe("Formatted output", func() { It("should output formatted message", func() { formatter.DisableTimestamp = true formatter.ForceFormatting = true log.Debug("test") Ω(output.GetValue()).Should(Equal("DEBUG test\n")) }) }) Describe("Theming support", func() { }) }) logrus-prefixed-formatter-0.5.2/logrus_prefixed_formatter_suite_test.go000066400000000000000000000006551313757207600267420ustar00rootroot00000000000000package prefixed_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "testing" ) type LogOutput struct { buffer string } func (o *LogOutput) Write(p []byte) (int, error) { o.buffer += string(p[:]) return len(p), nil } func (o *LogOutput) GetValue() string { return o.buffer } func TestLogrusPrefixedFormatter(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "LogrusPrefixedFormatter Suite") }