diff --git a/CHANGELOG.md b/CHANGELOG.md index da962fd..cf96615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.0] - 2019-08-12 +### Added +- Trailing newline character `\n` trimming enabled by default + ## [1.0.0] - 2019-08-09 ### Changes - Initial version \ No newline at end of file diff --git a/configs.go b/configs.go index 78f3ead..d5dae47 100644 --- a/configs.go +++ b/configs.go @@ -5,8 +5,17 @@ import "github.com/sirupsen/logrus" // Config holds the configuration to be used with WithConfig() configurer // This struct is useful to embed into configuration structs parsed with libraries like envconfig type Config struct { - Level string `default:"info"` - Fields logrus.Fields `default:"logger:stdlib"` + Level string `default:"info"` + Fields logrus.Fields `default:"logger:stdlib"` + TrailingNewLineTrimming bool `default:"true"` +} + +// WithTrailingNewLineTrimming configures trailing newline trimming. This is true by default, because +// log.Print adds a newline in the end of its messages, which does not make sense inside of a logrus log +func WithTrailingNewLineTrimming(trim bool) Configurer { + return func(w *writer) { + w.trailingNewLineTrimming = trim + } } // WithLogger configures the logger with the one provided diff --git a/configs_test.go b/configs_test.go index def9e61..230fbfa 100644 --- a/configs_test.go +++ b/configs_test.go @@ -1,6 +1,7 @@ package logrusiowriter import ( + "bytes" "testing" "github.com/sirupsen/logrus" @@ -51,4 +52,73 @@ func TestWithConfig(t *testing.T) { t.Errorf("Error provided to OnLevelParseError should not be nil") } }) + + t.Run("trimming", func(t *testing.T) { + const newLineAppendedByLogrus = "\n" + for _, tc := range []struct { + desc string + trimming bool + input string + expected string + }{ + { + desc: "zerolength with trimming", + trimming: true, + input: "", + expected: "level=info" + newLineAppendedByLogrus, + }, + { + desc: "zerolength without trimming", + trimming: false, + input: "", + expected: "level=info" + newLineAppendedByLogrus, + }, + { + desc: "only newline with trimming", + trimming: true, + input: "\n", + expected: "level=info" + newLineAppendedByLogrus, + }, + { + desc: "only newline without trimming", + trimming: false, + input: "\n", + expected: `level=info msg="\n"` + newLineAppendedByLogrus, + }, + { + desc: "with trailing newline and trimming", + trimming: true, + input: "message\n", + expected: "level=info msg=message" + newLineAppendedByLogrus, + }, + { + desc: "with trailing newline and no trimming", + trimming: false, + input: "message\n", + expected: `level=info msg="message\n"` + newLineAppendedByLogrus, + }, + { + desc: "no newline with trimming", + trimming: true, + input: "message", + expected: "level=info msg=message" + newLineAppendedByLogrus, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + buf := &bytes.Buffer{} + + bufLogger := logrus.New() + bufLogger.SetOutput(buf) + bufLogger.Formatter.(*logrus.TextFormatter).DisableTimestamp = true + + writer := New(WithLogger(bufLogger), WithTrailingNewLineTrimming(tc.trimming)) + + _, _ = writer.Write([]byte(tc.input)) + + if buf.String() != tc.expected { + t.Errorf("Unexpected output\nExpected: '%s'\nGot: '%s'", tc.expected, buf.String()) + } + }) + } + }) } diff --git a/example_configs_test.go b/example_configs_test.go index a8627e3..d805319 100644 --- a/example_configs_test.go +++ b/example_configs_test.go @@ -79,6 +79,18 @@ func ExampleWithConfigInterface() { // level=trace msg="Hello World!" config=interface } +func ExampleWithNewLineTrimming() { + removeTimestampAndSetOutputToStdout(logrus.StandardLogger()) + + writer := logrusiowriter.New( + logrusiowriter.WithTrailingNewLineTrimming(true), + ) + + _, _ = fmt.Fprint(writer, "Hello World!\n") + // Output: + // level=info msg="Hello World!" +} + type configProvider struct{} func (configProvider) Level() logrus.Level { return logrus.TraceLevel } diff --git a/example_writer_test.go b/example_writer_test.go index 8a86e78..8aed16a 100644 --- a/example_writer_test.go +++ b/example_writer_test.go @@ -16,5 +16,5 @@ func ExampleNew() { log.Printf("Standard log") // Output: - // level=info msg="Standard log\n" + // level=info msg="Standard log" } diff --git a/writer.go b/writer.go index 506a091..de08ddf 100644 --- a/writer.go +++ b/writer.go @@ -11,9 +11,10 @@ import ( // If no Configurers provided, the writer will log with Info level and no fields using the logrus.StandardLogger func New(cfg ...Configurer) io.Writer { w := &writer{ - logger: logrus.StandardLogger(), - level: logrus.InfoLevel, - fields: make(map[string]interface{}), + logger: logrus.StandardLogger(), + level: logrus.InfoLevel, + fields: make(map[string]interface{}), + trailingNewLineTrimming: true, } for _, c := range cfg { c(w) @@ -27,13 +28,18 @@ type Configurer func(*writer) // writer implements io.Writer type writer struct { - logger logrus.FieldLogger - level logrus.Level - fields map[string]interface{} + logger logrus.FieldLogger + level logrus.Level + fields map[string]interface{} + trailingNewLineTrimming bool } // Write will write with the logger, level and fields set in the writer func (w *writer) Write(bytes []byte) (int, error) { + l := len(bytes) + if w.trailingNewLineTrimming && l > 0 && bytes[l-1] == '\n' { + bytes = bytes[:l-1] + } w.logger.WithFields(w.fields).Log(w.level, string(bytes)) - return len(bytes), nil + return l, nil }