From 9c8673cd679e41557bfbf3a4f4975bec833cadd0 Mon Sep 17 00:00:00 2001 From: Young Xu Date: Mon, 3 Apr 2023 00:01:19 +0800 Subject: [PATCH] first commit --- .gitignore | 1 + LICENSE | 21 +++++++++ README.md | 81 +++++++++++++++++++++++++++++++++ formatter.go | 48 ++++++++++++++++++++ formatter_test.go | 113 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 10 ++++ go.sum | 13 ++++++ 7 files changed, 287 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 formatter.go create mode 100644 formatter_test.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62c8935 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fa7821c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Tomasz Tomalak + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..71488a3 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +[![status](https://github.com/t-tomalak/logrus-easy-formatter/workflows/Go/badge.svg)](https://github.com/t-tomalak/logrus-easy-formatter/actions) +[![Go Report Card](https://goreportcard.com/badge/github.com/t-tomalak/logrus-easy-formatter)](https://goreportcard.com/report/github.com/t-tomalak/logrus-easy-formatter) +## Logrus Easy Formatter +Provided formatter allow to easily format [Logrus](https://github.com/sirupsen/logrus) log output +Some inspiration taken from [logrus-prefixed-formatter](https://github.com/x-cray/logrus-prefixed-formatter) + +## Default output +When format options are not provided `Formatter` will output +```bash +[INFO]: 2006-01-02T15:04:05Z07:00 - Log message +``` + +## Sample Usage +Sample usage using available option to format output +```go +package main + +import ( + "os" + + "github.com/sirupsen/logrus" + "github.com/t-tomalak/logrus-easy-formatter" +) + +func main() { + logger := &logrus.Logger{ + Out: os.Stderr, + Level: logrus.DebugLevel, + Formatter: &easy.Formatter{ + TimestampFormat: "2006-01-02 15:04:05", + LogFormat: "[%lvl%]: %time% - %msg%", + }, + } + + logger.Printf("Log message") +} +``` +Above sample will produce: +```bash +[INFO]: 27-02-2018 19:16:55 - Log message +``` + +##### Usage with custom fields +Package also allows to include custom fields and format them(for now only limited to strings) + +```go +package main + +import ( + "os" + + "github.com/sirupsen/logrus" + "github.com/t-tomalak/logrus-easy-formatter" +) + +func main() { + logger := &logrus.Logger{ + Out: os.Stderr, + Level: logrus.DebugLevel, + Formatter: &easy.Formatter{ + LogFormat: "[%lvl%]: %time% - %msg% {%customField%}", + }, + } + + logger.WithField("customField", "Sample value").Printf("Log message") +} +``` +And after running will output +```bash +[INFO]: 27-02-2018 19:16:55 - Log message - {Sample value} +``` + +## ToDo +- [x] Customizable timestamp formats +- [x] Customizable output formats +- [x] Add tests +- [ ] Support for custom fields other then `string` +- [ ] Tests against all characters + +## License +This project is under the MIT License. See the LICENSE file for the full license text. \ No newline at end of file diff --git a/formatter.go b/formatter.go new file mode 100644 index 0000000..61d8751 --- /dev/null +++ b/formatter.go @@ -0,0 +1,48 @@ +// Package easy allows to easily format output of Logrus logger +package easy + +import ( + "bytes" + "fmt" + + "github.com/sirupsen/logrus" +) + +// Formatter implements logrus.Formatter interface. +type Formatter struct{} + +func (f *Formatter) shortLevel(level logrus.Level) string { + switch level { + case logrus.DebugLevel: + return "DEBU" + case logrus.InfoLevel: + return "INFO" + case logrus.WarnLevel: + return "WARN" + case logrus.ErrorLevel: + return "ERRO" + case logrus.FatalLevel: + return "FATAL" + case logrus.PanicLevel: + return "PANIC" + } + return "INFO" +} + +// Format building log message. +func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) { + var builder bytes.Buffer + + builder.WriteString("[") + builder.WriteString(f.shortLevel(entry.Level)) + builder.WriteString("]") + builder.WriteString(" [") + builder.WriteString(entry.Time.Format("2006-01-02 15:04:05.000")) + builder.WriteString("]") + + for k, val := range entry.Data { + builder.WriteString(fmt.Sprintf(" %s=%v", k, val)) + } + + return builder.Bytes(), nil +} diff --git a/formatter_test.go b/formatter_test.go new file mode 100644 index 0000000..0196d21 --- /dev/null +++ b/formatter_test.go @@ -0,0 +1,113 @@ +package easy + +import ( + "bytes" + "strings" + "testing" + "time" + + "github.com/sirupsen/logrus" +) + +func TestFormatterDefaultFormat(t *testing.T) { + f := Formatter{} + + e := logrus.WithField("", "") + e.Message = "Test Message" + e.Level = logrus.WarnLevel + e.Time = time.Now() + + b, _ := f.Format(e) + + expected := strings.Join([]string{"[WARNING]:", e.Time.Format(time.RFC3339), "- Test Message"}, " ") + if string(b) != expected { + t.Errorf("formatting expected result was %q instead of %q", string(b), expected) + } + +} + +func TestFormatterFormatWithCustomData(t *testing.T) { + f := Formatter{} + + testValues := []struct { + name string + format string + fields logrus.Fields + result string + }{ + { + "Single custom param", + "[%lvl%]: %time% - %first%", + map[string]interface{}{"first": "First Custom Param"}, + "[PANIC]: 0001-01-01T00:00:00Z - First Custom Param", + }, + { + "Multiple custom params of type string", + "[%lvl%]: %time% - %first% %second%", + map[string]interface{}{"first": "First Custom Param", "second": "Second Custom Param"}, + "[PANIC]: 0001-01-01T00:00:00Z - First Custom Param Second Custom Param", + }, + { + "Multiple custom params of different type", + "[%lvl%]: %time% - %string%, %bool%, %int%", + map[string]interface{}{"string": "String param", "bool": true, "int": 42}, + "[PANIC]: 0001-01-01T00:00:00Z - String param, true, 42", + }, + { + "Omits fields not included in format", + "[%lvl%]: %time% - %first% %random%", + map[string]interface{}{"first": "String param", "not_included": "random string"}, + "[PANIC]: 0001-01-01T00:00:00Z - String param %random%", + }, + } + for _, tv := range testValues { + t.Run(tv.name, func(t *testing.T) { + f.LogFormat = tv.format + b, _ := f.Format(logrus.WithFields(tv.fields)) + if string(b) != tv.result { + t.Errorf("formatting expected result was %q instead of %q", string(b), tv.result) + } + }) + } +} + +func TestFormatterFormatWithCustomDateFormat(t *testing.T) { + f := Formatter{} + + testValues := []struct { + name string + timestampFormat string + }{ + { + "Timestamp with RFC822 format", + time.RFC822, + }, + { + + "Timestamp with RFC850 format", + time.RFC850, + }, + { + "Timestamp with `yyyy-mm-dd hh:mm:ss` format", + "2006-01-02 15:04:05", + }, + { + "Timestamp with `yyyy-mm-dd` format", + "2006-01-02", + }, + } + + for _, tv := range testValues { + t.Run(tv.name, func(t *testing.T) { + f.TimestampFormat = tv.timestampFormat + e := logrus.WithField("", "") + e.Time = time.Now() + + b, _ := f.Format(e) + if !bytes.Contains(b, []byte(e.Time.Format(tv.timestampFormat))) { + t.Errorf("formatting expected format date was %q instead of %q", string(b), tv.timestampFormat) + } + }) + + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5944882 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module gitter.top/sync/logrus-formatter + +go 1.18 + +require github.com/sirupsen/logrus v1.4.2 + +require ( + github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + golang.org/x/sys v0.0.0-20190422165155-953cdadca894 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..706fe8d --- /dev/null +++ b/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=