├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── example └── main.go ├── formatter.go └── tests └── formatter_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # IDE 15 | *.code-workspace 16 | .idea/ 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.10" 5 | - "1.11" 6 | - "1.12" 7 | - "1.13" 8 | - "1.14" 9 | - master 10 | 11 | before_script: 12 | - go get golang.org/x/tools/cmd/cover 13 | - go get github.com/mattn/goveralls 14 | - go get -t github.com/sirupsen/logrus 15 | 16 | after_success: 17 | - make cover 18 | - $GOPATH/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken "$COVERALLS_TOKEN" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anton Fisher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # nested-logrus-formatter 2 | 3 | .PHONY: all 4 | all: test demo 5 | 6 | .PHONY: test 7 | test: 8 | go test ./tests/* -v -count 1 9 | 10 | cover: 11 | go test ./tests/* -v -covermode=count -coverprofile=coverage.out 12 | 13 | .PHONY: demo 14 | demo: 15 | go run example/main.go 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nested-logrus-formatter 2 | 3 | [![Build Status](https://travis-ci.org/antonfisher/nested-logrus-formatter.svg?branch=master)](https://travis-ci.org/antonfisher/nested-logrus-formatter) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/antonfisher/nested-logrus-formatter)](https://goreportcard.com/report/github.com/antonfisher/nested-logrus-formatter) 5 | [![GoDoc](https://godoc.org/github.com/antonfisher/nested-logrus-formatter?status.svg)](https://godoc.org/github.com/antonfisher/nested-logrus-formatter) 6 | 7 | Human-readable log formatter, converts _logrus_ fields to a nested structure: 8 | 9 | ![Screenshot](https://raw.githubusercontent.com/antonfisher/nested-logrus-formatter/docs/images/demo.png) 10 | 11 | ## Configuration: 12 | 13 | ```go 14 | type Formatter struct { 15 | // FieldsOrder - default: fields sorted alphabetically 16 | FieldsOrder []string 17 | 18 | // TimestampFormat - default: time.StampMilli = "Jan _2 15:04:05.000" 19 | TimestampFormat string 20 | 21 | // HideKeys - show [fieldValue] instead of [fieldKey:fieldValue] 22 | HideKeys bool 23 | 24 | // NoColors - disable colors 25 | NoColors bool 26 | 27 | // NoFieldsColors - apply colors only to the level, default is level + fields 28 | NoFieldsColors bool 29 | 30 | // NoFieldsSpace - no space between fields 31 | NoFieldsSpace bool 32 | 33 | // ShowFullLevel - show a full level [WARNING] instead of [WARN] 34 | ShowFullLevel bool 35 | 36 | // NoUppercaseLevel - no upper case for level value 37 | NoUppercaseLevel bool 38 | 39 | // TrimMessages - trim whitespaces on messages 40 | TrimMessages bool 41 | 42 | // CallerFirst - print caller info first 43 | CallerFirst bool 44 | 45 | // CustomCallerFormatter - set custom formatter for caller info 46 | CustomCallerFormatter func(*runtime.Frame) string 47 | } 48 | ``` 49 | 50 | ## Usage 51 | 52 | ```go 53 | import ( 54 | nested "github.com/antonfisher/nested-logrus-formatter" 55 | "github.com/sirupsen/logrus" 56 | ) 57 | 58 | log := logrus.New() 59 | log.SetFormatter(&nested.Formatter{ 60 | HideKeys: true, 61 | FieldsOrder: []string{"component", "category"}, 62 | }) 63 | 64 | log.Info("just info message") 65 | // Output: Jan _2 15:04:05.000 [INFO] just info message 66 | 67 | log.WithField("component", "rest").Warn("warn message") 68 | // Output: Jan _2 15:04:05.000 [WARN] [rest] warn message 69 | ``` 70 | 71 | See more examples in the [tests](./tests/formatter_test.go) file. 72 | 73 | ## Development 74 | 75 | ```bash 76 | # run tests: 77 | make test 78 | 79 | # run demo: 80 | make demo 81 | ``` 82 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/antonfisher/nested-logrus-formatter" 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func main() { 10 | fmt.Print("\n--- nested-logrus-formatter ---\n\n") 11 | printDemo(&formatter.Formatter{ 12 | HideKeys: true, 13 | FieldsOrder: []string{"component", "category", "req"}, 14 | }, "nested-logrus-formatter") 15 | 16 | fmt.Print("\n--- default logrus formatter ---\n\n") 17 | printDemo(nil, "default logrus formatter") 18 | } 19 | 20 | func printDemo(f logrus.Formatter, title string) { 21 | l := logrus.New() 22 | 23 | l.SetLevel(logrus.DebugLevel) 24 | 25 | if f != nil { 26 | l.SetFormatter(f) 27 | } 28 | 29 | // enable/disable file/function name 30 | l.SetReportCaller(false) 31 | 32 | l.Infof("this is %v demo", title) 33 | 34 | lWebServer := l.WithField("component", "web-server") 35 | lWebServer.Info("starting...") 36 | 37 | lWebServerReq := lWebServer.WithFields(logrus.Fields{ 38 | "req": "GET /api/stats", 39 | "reqId": "#1", 40 | }) 41 | 42 | lWebServerReq.Info("params: startYear=2048") 43 | lWebServerReq.Error("response: 400 Bad Request") 44 | 45 | lDbConnector := l.WithField("category", "db-connector") 46 | lDbConnector.Info("connecting to db on 10.10.10.13...") 47 | lDbConnector.Warn("connection took 10s") 48 | 49 | l.Info("demo end.") 50 | } 51 | -------------------------------------------------------------------------------- /formatter.go: -------------------------------------------------------------------------------- 1 | package formatter 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "runtime" 7 | "sort" 8 | "strings" 9 | "time" 10 | 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // Formatter - logrus formatter, implements logrus.Formatter 15 | type Formatter struct { 16 | // FieldsOrder - default: fields sorted alphabetically 17 | FieldsOrder []string 18 | 19 | // TimestampFormat - default: time.StampMilli = "Jan _2 15:04:05.000" 20 | TimestampFormat string 21 | 22 | // HideKeys - show [fieldValue] instead of [fieldKey:fieldValue] 23 | HideKeys bool 24 | 25 | // NoColors - disable colors 26 | NoColors bool 27 | 28 | // NoFieldsColors - apply colors only to the level, default is level + fields 29 | NoFieldsColors bool 30 | 31 | // NoFieldsSpace - no space between fields 32 | NoFieldsSpace bool 33 | 34 | // ShowFullLevel - show a full level [WARNING] instead of [WARN] 35 | ShowFullLevel bool 36 | 37 | // NoUppercaseLevel - no upper case for level value 38 | NoUppercaseLevel bool 39 | 40 | // TrimMessages - trim whitespaces on messages 41 | TrimMessages bool 42 | 43 | // CallerFirst - print caller info first 44 | CallerFirst bool 45 | 46 | // CustomCallerFormatter - set custom formatter for caller info 47 | CustomCallerFormatter func(*runtime.Frame) string 48 | } 49 | 50 | // Format an log entry 51 | func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) { 52 | levelColor := getColorByLevel(entry.Level) 53 | 54 | timestampFormat := f.TimestampFormat 55 | if timestampFormat == "" { 56 | timestampFormat = time.StampMilli 57 | } 58 | 59 | // output buffer 60 | b := &bytes.Buffer{} 61 | 62 | // write time 63 | b.WriteString(entry.Time.Format(timestampFormat)) 64 | 65 | // write level 66 | var level string 67 | if f.NoUppercaseLevel { 68 | level = entry.Level.String() 69 | } else { 70 | level = strings.ToUpper(entry.Level.String()) 71 | } 72 | 73 | if f.CallerFirst { 74 | f.writeCaller(b, entry) 75 | } 76 | 77 | if !f.NoColors { 78 | fmt.Fprintf(b, "\x1b[%dm", levelColor) 79 | } 80 | 81 | b.WriteString(" [") 82 | if f.ShowFullLevel { 83 | b.WriteString(level) 84 | } else { 85 | b.WriteString(level[:4]) 86 | } 87 | b.WriteString("]") 88 | 89 | if !f.NoFieldsSpace { 90 | b.WriteString(" ") 91 | } 92 | 93 | if !f.NoColors && f.NoFieldsColors { 94 | b.WriteString("\x1b[0m") 95 | } 96 | 97 | // write fields 98 | if f.FieldsOrder == nil { 99 | f.writeFields(b, entry) 100 | } else { 101 | f.writeOrderedFields(b, entry) 102 | } 103 | 104 | if f.NoFieldsSpace { 105 | b.WriteString(" ") 106 | } 107 | 108 | if !f.NoColors && !f.NoFieldsColors { 109 | b.WriteString("\x1b[0m") 110 | } 111 | 112 | // write message 113 | if f.TrimMessages { 114 | b.WriteString(strings.TrimSpace(entry.Message)) 115 | } else { 116 | b.WriteString(entry.Message) 117 | } 118 | 119 | if !f.CallerFirst { 120 | f.writeCaller(b, entry) 121 | } 122 | 123 | b.WriteByte('\n') 124 | 125 | return b.Bytes(), nil 126 | } 127 | 128 | func (f *Formatter) writeCaller(b *bytes.Buffer, entry *logrus.Entry) { 129 | if entry.HasCaller() { 130 | if f.CustomCallerFormatter != nil { 131 | fmt.Fprintf(b, f.CustomCallerFormatter(entry.Caller)) 132 | } else { 133 | fmt.Fprintf( 134 | b, 135 | " (%s:%d %s)", 136 | entry.Caller.File, 137 | entry.Caller.Line, 138 | entry.Caller.Function, 139 | ) 140 | } 141 | } 142 | } 143 | 144 | func (f *Formatter) writeFields(b *bytes.Buffer, entry *logrus.Entry) { 145 | if len(entry.Data) != 0 { 146 | fields := make([]string, 0, len(entry.Data)) 147 | for field := range entry.Data { 148 | fields = append(fields, field) 149 | } 150 | 151 | sort.Strings(fields) 152 | 153 | for _, field := range fields { 154 | f.writeField(b, entry, field) 155 | } 156 | } 157 | } 158 | 159 | func (f *Formatter) writeOrderedFields(b *bytes.Buffer, entry *logrus.Entry) { 160 | length := len(entry.Data) 161 | foundFieldsMap := map[string]bool{} 162 | for _, field := range f.FieldsOrder { 163 | if _, ok := entry.Data[field]; ok { 164 | foundFieldsMap[field] = true 165 | length-- 166 | f.writeField(b, entry, field) 167 | } 168 | } 169 | 170 | if length > 0 { 171 | notFoundFields := make([]string, 0, length) 172 | for field := range entry.Data { 173 | if foundFieldsMap[field] == false { 174 | notFoundFields = append(notFoundFields, field) 175 | } 176 | } 177 | 178 | sort.Strings(notFoundFields) 179 | 180 | for _, field := range notFoundFields { 181 | f.writeField(b, entry, field) 182 | } 183 | } 184 | } 185 | 186 | func (f *Formatter) writeField(b *bytes.Buffer, entry *logrus.Entry, field string) { 187 | if f.HideKeys { 188 | fmt.Fprintf(b, "[%v]", entry.Data[field]) 189 | } else { 190 | fmt.Fprintf(b, "[%s:%v]", field, entry.Data[field]) 191 | } 192 | 193 | if !f.NoFieldsSpace { 194 | b.WriteString(" ") 195 | } 196 | } 197 | 198 | const ( 199 | colorRed = 31 200 | colorYellow = 33 201 | colorBlue = 36 202 | colorGray = 37 203 | ) 204 | 205 | func getColorByLevel(level logrus.Level) int { 206 | switch level { 207 | case logrus.DebugLevel, logrus.TraceLevel: 208 | return colorGray 209 | case logrus.WarnLevel: 210 | return colorYellow 211 | case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel: 212 | return colorRed 213 | default: 214 | return colorBlue 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /tests/formatter_test.go: -------------------------------------------------------------------------------- 1 | package formatter_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "path" 8 | "regexp" 9 | "runtime" 10 | "strings" 11 | "testing" 12 | 13 | formatter "github.com/antonfisher/nested-logrus-formatter" 14 | "github.com/sirupsen/logrus" 15 | ) 16 | 17 | func ExampleFormatter_Format_default() { 18 | l := logrus.New() 19 | l.SetOutput(os.Stdout) 20 | l.SetLevel(logrus.DebugLevel) 21 | l.SetFormatter(&formatter.Formatter{ 22 | NoColors: true, 23 | TimestampFormat: "-", 24 | }) 25 | 26 | l.Debug("test1") 27 | l.Info("test2") 28 | l.Warn("test3") 29 | l.Error("test4") 30 | 31 | // Output: 32 | // - [DEBU] test1 33 | // - [INFO] test2 34 | // - [WARN] test3 35 | // - [ERRO] test4 36 | } 37 | 38 | func ExampleFormatter_Format_full_level() { 39 | l := logrus.New() 40 | l.SetOutput(os.Stdout) 41 | l.SetLevel(logrus.DebugLevel) 42 | l.SetFormatter(&formatter.Formatter{ 43 | NoColors: true, 44 | TimestampFormat: "-", 45 | ShowFullLevel: true, 46 | }) 47 | 48 | l.Debug("test1") 49 | l.Info("test2") 50 | l.Warn("test3") 51 | l.Error(" test4") 52 | 53 | // Output: 54 | // - [DEBUG] test1 55 | // - [INFO] test2 56 | // - [WARNING] test3 57 | // - [ERROR] test4 58 | } 59 | func ExampleFormatter_Format_show_keys() { 60 | l := logrus.New() 61 | l.SetOutput(os.Stdout) 62 | l.SetLevel(logrus.DebugLevel) 63 | l.SetFormatter(&formatter.Formatter{ 64 | NoColors: true, 65 | TimestampFormat: "-", 66 | HideKeys: false, 67 | }) 68 | 69 | ll := l.WithField("category", "rest") 70 | 71 | l.Info("test1") 72 | ll.Info("test2") 73 | 74 | // Output: 75 | // - [INFO] test1 76 | // - [INFO] [category:rest] test2 77 | } 78 | 79 | func ExampleFormatter_Format_hide_keys() { 80 | l := logrus.New() 81 | l.SetOutput(os.Stdout) 82 | l.SetLevel(logrus.DebugLevel) 83 | l.SetFormatter(&formatter.Formatter{ 84 | NoColors: true, 85 | TimestampFormat: "-", 86 | HideKeys: true, 87 | }) 88 | 89 | ll := l.WithField("category", "rest") 90 | 91 | l.Info("test1") 92 | ll.Info("test2") 93 | 94 | // Output: 95 | // - [INFO] test1 96 | // - [INFO] [rest] test2 97 | } 98 | 99 | func ExampleFormatter_Format_sort_order() { 100 | l := logrus.New() 101 | l.SetOutput(os.Stdout) 102 | l.SetLevel(logrus.DebugLevel) 103 | l.SetFormatter(&formatter.Formatter{ 104 | NoColors: true, 105 | TimestampFormat: "-", 106 | HideKeys: false, 107 | }) 108 | 109 | ll := l.WithField("component", "main") 110 | lll := ll.WithField("category", "rest") 111 | 112 | l.Info("test1") 113 | ll.Info("test2") 114 | lll.Info("test3") 115 | 116 | // Output: 117 | // - [INFO] test1 118 | // - [INFO] [component:main] test2 119 | // - [INFO] [category:rest] [component:main] test3 120 | } 121 | 122 | func ExampleFormatter_Format_field_order() { 123 | l := logrus.New() 124 | l.SetOutput(os.Stdout) 125 | l.SetLevel(logrus.DebugLevel) 126 | l.SetFormatter(&formatter.Formatter{ 127 | NoColors: true, 128 | TimestampFormat: "-", 129 | FieldsOrder: []string{"component", "category"}, 130 | HideKeys: false, 131 | }) 132 | 133 | ll := l.WithField("component", "main") 134 | lll := ll.WithField("category", "rest") 135 | 136 | l.Info("test1") 137 | ll.Info("test2") 138 | lll.Info("test3") 139 | 140 | // Output: 141 | // - [INFO] test1 142 | // - [INFO] [component:main] test2 143 | // - [INFO] [component:main] [category:rest] test3 144 | } 145 | 146 | func ExampleFormatter_Format_no_fields_space() { 147 | l := logrus.New() 148 | l.SetOutput(os.Stdout) 149 | l.SetLevel(logrus.DebugLevel) 150 | l.SetFormatter(&formatter.Formatter{ 151 | NoColors: true, 152 | TimestampFormat: "-", 153 | FieldsOrder: []string{"component", "category"}, 154 | HideKeys: false, 155 | NoFieldsSpace: true, 156 | }) 157 | 158 | ll := l.WithField("component", "main") 159 | lll := ll.WithField("category", "rest") 160 | 161 | l.Info("test1") 162 | ll.Info("test2") 163 | lll.Info("test3") 164 | 165 | // Output: 166 | // - [INFO] test1 167 | // - [INFO][component:main] test2 168 | // - [INFO][component:main][category:rest] test3 169 | } 170 | 171 | func ExampleFormatter_Format_no_uppercase_level() { 172 | l := logrus.New() 173 | l.SetOutput(os.Stdout) 174 | l.SetLevel(logrus.DebugLevel) 175 | l.SetFormatter(&formatter.Formatter{ 176 | NoColors: true, 177 | TimestampFormat: "-", 178 | FieldsOrder: []string{"component", "category"}, 179 | NoUppercaseLevel: true, 180 | }) 181 | 182 | ll := l.WithField("component", "main") 183 | lll := ll.WithField("category", "rest") 184 | llll := ll.WithField("category", "other") 185 | 186 | l.Debug("test1") 187 | ll.Info("test2") 188 | lll.Warn("test3") 189 | llll.Error("test4") 190 | 191 | // Output: 192 | // - [debu] test1 193 | // - [info] [component:main] test2 194 | // - [warn] [component:main] [category:rest] test3 195 | // - [erro] [component:main] [category:other] test4 196 | } 197 | 198 | func ExampleFormatter_Format_trim_message() { 199 | l := logrus.New() 200 | l.SetOutput(os.Stdout) 201 | l.SetLevel(logrus.DebugLevel) 202 | l.SetFormatter(&formatter.Formatter{ 203 | TrimMessages: true, 204 | NoColors: true, 205 | TimestampFormat: "-", 206 | }) 207 | 208 | l.Debug(" test1 ") 209 | l.Info("test2 ") 210 | l.Warn(" test3") 211 | l.Error(" test4 ") 212 | 213 | // Output: 214 | // - [DEBU] test1 215 | // - [INFO] test2 216 | // - [WARN] test3 217 | // - [ERRO] test4 218 | } 219 | 220 | func TestFormatter_Format_with_report_caller(t *testing.T) { 221 | output := bytes.NewBuffer([]byte{}) 222 | 223 | l := logrus.New() 224 | l.SetOutput(output) 225 | l.SetLevel(logrus.DebugLevel) 226 | l.SetFormatter(&formatter.Formatter{ 227 | NoColors: true, 228 | TimestampFormat: "-", 229 | }) 230 | l.SetReportCaller(true) 231 | 232 | l.Debug("test1") 233 | 234 | line, err := output.ReadString('\n') 235 | if err != nil { 236 | t.Errorf("Cannot read log output: %v", err) 237 | } 238 | 239 | expectedRegExp := "- \\[DEBU\\] test1 \\(.+\\.go:[0-9]+ .+\\)\n$" 240 | match, err := regexp.MatchString( 241 | expectedRegExp, 242 | line, 243 | ) 244 | if err != nil { 245 | t.Errorf("Cannot check regexp: %v", err) 246 | } else if !match { 247 | t.Errorf( 248 | "logger.SetReportCaller(true) output doesn't match, expected: %s to find in: '%s'", 249 | expectedRegExp, 250 | line, 251 | ) 252 | } 253 | } 254 | 255 | func TestFormatter_Format_with_report_caller_and_CallerFirst_true(t *testing.T) { 256 | output := bytes.NewBuffer([]byte{}) 257 | 258 | l := logrus.New() 259 | l.SetOutput(output) 260 | l.SetLevel(logrus.DebugLevel) 261 | l.SetFormatter(&formatter.Formatter{ 262 | NoColors: true, 263 | TimestampFormat: "-", 264 | CallerFirst: true, 265 | }) 266 | l.SetReportCaller(true) 267 | 268 | l.Debug("test1") 269 | 270 | line, err := output.ReadString('\n') 271 | if err != nil { 272 | t.Errorf("Cannot read log output: %v", err) 273 | } 274 | 275 | expectedRegExp := "- \\(.+\\.go:[0-9]+ .+\\) \\[DEBU\\] test1\n$" 276 | match, err := regexp.MatchString( 277 | expectedRegExp, 278 | line, 279 | ) 280 | 281 | if err != nil { 282 | t.Errorf("Cannot check regexp: %v", err) 283 | } else if !match { 284 | t.Errorf( 285 | "logger.SetReportCaller(true) output doesn't match, expected: %s to find in: '%s'", 286 | expectedRegExp, 287 | line, 288 | ) 289 | } 290 | } 291 | 292 | func TestFormatter_Format_with_report_caller_and_CustomCallerFormatter(t *testing.T) { 293 | output := bytes.NewBuffer([]byte{}) 294 | 295 | l := logrus.New() 296 | l.SetOutput(output) 297 | l.SetLevel(logrus.DebugLevel) 298 | l.SetFormatter(&formatter.Formatter{ 299 | NoColors: true, 300 | TimestampFormat: "-", 301 | CallerFirst: true, 302 | CustomCallerFormatter: func(f *runtime.Frame) string { 303 | s := strings.Split(f.Function, ".") 304 | funcName := s[len(s)-1] 305 | return fmt.Sprintf(" [%s:%d][%s()]", path.Base(f.File), f.Line, funcName) 306 | }, 307 | }) 308 | l.SetReportCaller(true) 309 | 310 | l.Debug("test1") 311 | 312 | line, err := output.ReadString('\n') 313 | if err != nil { 314 | t.Errorf("Cannot read log output: %v", err) 315 | } 316 | 317 | expectedRegExp := "- \\[.+\\.go:[0-9]+\\]\\[.+\\(\\)\\] \\[DEBU\\] test1\n$" 318 | match, err := regexp.MatchString( 319 | expectedRegExp, 320 | line, 321 | ) 322 | if err != nil { 323 | t.Errorf("Cannot check regexp: %v", err) 324 | } else if !match { 325 | t.Errorf( 326 | "logger.SetReportCaller(true) output doesn't match, expected: %s to find in: '%s'", 327 | expectedRegExp, 328 | line, 329 | ) 330 | } 331 | } 332 | --------------------------------------------------------------------------------