github.com/newrelic/go-agent@v3.26.0+incompatible/_integrations/logcontext/nrlogrusplugin/nrlogrusplugin_test.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package nrlogrusplugin 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 "errors" 11 "io" 12 "testing" 13 "time" 14 15 newrelic "github.com/newrelic/go-agent" 16 "github.com/newrelic/go-agent/internal" 17 "github.com/newrelic/go-agent/internal/integrationsupport" 18 "github.com/newrelic/go-agent/internal/sysinfo" 19 "github.com/sirupsen/logrus" 20 ) 21 22 var ( 23 testTime = time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) 24 matchAnything = struct{}{} 25 ) 26 27 func newTestLogger(out io.Writer) *logrus.Logger { 28 l := logrus.New() 29 l.Formatter = ContextFormatter{} 30 l.SetReportCaller(true) 31 l.SetOutput(out) 32 return l 33 } 34 35 func validateOutput(t *testing.T, out *bytes.Buffer, expected map[string]interface{}) { 36 var actual map[string]interface{} 37 if err := json.Unmarshal(out.Bytes(), &actual); nil != err { 38 t.Fatal("failed to unmarshal log output:", err) 39 } 40 for k, v := range expected { 41 found, ok := actual[k] 42 if !ok { 43 t.Errorf("key %s not found:\nactual=%s", k, actual) 44 } 45 if v != matchAnything && found != v { 46 t.Errorf("value for key %s is incorrect:\nactual=%s\nexpected=%s", k, found, v) 47 } 48 } 49 for k, v := range actual { 50 if _, ok := expected[k]; !ok { 51 t.Errorf("unexpected key found:\nkey=%s\nvalue=%s", k, v) 52 } 53 } 54 } 55 56 func BenchmarkWithOutTransaction(b *testing.B) { 57 log := newTestLogger(bytes.NewBuffer([]byte(""))) 58 ctx := context.Background() 59 60 b.ResetTimer() 61 b.ReportAllocs() 62 63 for i := 0; i < b.N; i++ { 64 log.WithContext(ctx).Info("Hello World!") 65 } 66 } 67 68 func BenchmarkJSONFormatter(b *testing.B) { 69 log := newTestLogger(bytes.NewBuffer([]byte(""))) 70 log.Formatter = new(logrus.JSONFormatter) 71 ctx := context.Background() 72 73 b.ResetTimer() 74 b.ReportAllocs() 75 76 for i := 0; i < b.N; i++ { 77 log.WithContext(ctx).Info("Hello World!") 78 } 79 } 80 81 func BenchmarkTextFormatter(b *testing.B) { 82 log := newTestLogger(bytes.NewBuffer([]byte(""))) 83 log.Formatter = new(logrus.TextFormatter) 84 ctx := context.Background() 85 86 b.ResetTimer() 87 b.ReportAllocs() 88 89 for i := 0; i < b.N; i++ { 90 log.WithContext(ctx).Info("Hello World!") 91 } 92 } 93 94 func BenchmarkWithTransaction(b *testing.B) { 95 app := integrationsupport.NewTestApp(nil, nil) 96 txn := app.StartTransaction("TestLogDistributedTracingDisabled", nil, nil) 97 log := newTestLogger(bytes.NewBuffer([]byte(""))) 98 ctx := newrelic.NewContext(context.Background(), txn) 99 100 b.ResetTimer() 101 b.ReportAllocs() 102 103 for i := 0; i < b.N; i++ { 104 log.WithContext(ctx).Info("Hello World!") 105 } 106 } 107 108 func TestLogNoContext(t *testing.T) { 109 out := bytes.NewBuffer([]byte{}) 110 log := newTestLogger(out) 111 log.WithTime(testTime).Info("Hello World!") 112 validateOutput(t, out, map[string]interface{}{ 113 "file.name": matchAnything, 114 "line.number": matchAnything, 115 "log.level": "info", 116 "message": "Hello World!", 117 "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogNoContext", 118 "timestamp": float64(1417136460000), 119 }) 120 } 121 122 func TestLogNoTxn(t *testing.T) { 123 out := bytes.NewBuffer([]byte{}) 124 log := newTestLogger(out) 125 log.WithTime(testTime).WithContext(context.Background()).Info("Hello World!") 126 validateOutput(t, out, map[string]interface{}{ 127 "file.name": matchAnything, 128 "line.number": matchAnything, 129 "log.level": "info", 130 "message": "Hello World!", 131 "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogNoTxn", 132 "timestamp": float64(1417136460000), 133 }) 134 } 135 136 func TestLogDistributedTracingDisabled(t *testing.T) { 137 app := integrationsupport.NewTestApp(nil, nil) 138 txn := app.StartTransaction("TestLogDistributedTracingDisabled", nil, nil) 139 out := bytes.NewBuffer([]byte{}) 140 log := newTestLogger(out) 141 ctx := newrelic.NewContext(context.Background(), txn) 142 host, _ := sysinfo.Hostname() 143 log.WithTime(testTime).WithContext(ctx).Info("Hello World!") 144 validateOutput(t, out, map[string]interface{}{ 145 "entity.name": integrationsupport.SampleAppName, 146 "entity.type": "SERVICE", 147 "file.name": matchAnything, 148 "hostname": host, 149 "line.number": matchAnything, 150 "log.level": "info", 151 "message": "Hello World!", 152 "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogDistributedTracingDisabled", 153 "timestamp": float64(1417136460000), 154 }) 155 } 156 157 func TestLogSampledFalse(t *testing.T) { 158 app := integrationsupport.NewTestApp( 159 func(reply *internal.ConnectReply) { 160 reply.AdaptiveSampler = internal.SampleNothing{} 161 reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) 162 }, 163 func(cfg *newrelic.Config) { 164 cfg.DistributedTracer.Enabled = true 165 cfg.CrossApplicationTracer.Enabled = false 166 }) 167 txn := app.StartTransaction("TestLogSampledFalse", nil, nil) 168 out := bytes.NewBuffer([]byte{}) 169 log := newTestLogger(out) 170 ctx := newrelic.NewContext(context.Background(), txn) 171 host, _ := sysinfo.Hostname() 172 log.WithTime(testTime).WithContext(ctx).Info("Hello World!") 173 validateOutput(t, out, map[string]interface{}{ 174 "entity.name": integrationsupport.SampleAppName, 175 "entity.type": "SERVICE", 176 "file.name": matchAnything, 177 "hostname": host, 178 "line.number": matchAnything, 179 "log.level": "info", 180 "message": "Hello World!", 181 "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogSampledFalse", 182 "timestamp": float64(1417136460000), 183 "trace.id": "d9466896a525ccbf", 184 }) 185 } 186 187 func TestLogSampledTrue(t *testing.T) { 188 app := integrationsupport.NewTestApp( 189 func(reply *internal.ConnectReply) { 190 reply.AdaptiveSampler = internal.SampleEverything{} 191 reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) 192 }, 193 func(cfg *newrelic.Config) { 194 cfg.DistributedTracer.Enabled = true 195 cfg.CrossApplicationTracer.Enabled = false 196 }) 197 txn := app.StartTransaction("TestLogSampledTrue", nil, nil) 198 out := bytes.NewBuffer([]byte{}) 199 log := newTestLogger(out) 200 ctx := newrelic.NewContext(context.Background(), txn) 201 host, _ := sysinfo.Hostname() 202 log.WithTime(testTime).WithContext(ctx).Info("Hello World!") 203 validateOutput(t, out, map[string]interface{}{ 204 "entity.name": integrationsupport.SampleAppName, 205 "entity.type": "SERVICE", 206 "file.name": matchAnything, 207 "hostname": host, 208 "line.number": matchAnything, 209 "log.level": "info", 210 "message": "Hello World!", 211 "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogSampledTrue", 212 "span.id": "bcfb32e050b264b8", 213 "timestamp": float64(1417136460000), 214 "trace.id": "d9466896a525ccbf", 215 }) 216 } 217 218 func TestEntryUsedTwice(t *testing.T) { 219 out := bytes.NewBuffer([]byte{}) 220 log := newTestLogger(out) 221 entry := log.WithTime(testTime) 222 223 // First log has dt enabled, ensure trace.id and span.id are included 224 app := integrationsupport.NewTestApp( 225 func(reply *internal.ConnectReply) { 226 reply.AdaptiveSampler = internal.SampleEverything{} 227 reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) 228 }, 229 func(cfg *newrelic.Config) { 230 cfg.DistributedTracer.Enabled = true 231 cfg.CrossApplicationTracer.Enabled = false 232 }) 233 txn := app.StartTransaction("TestEntryUsedTwice1", nil, nil) 234 ctx := newrelic.NewContext(context.Background(), txn) 235 host, _ := sysinfo.Hostname() 236 entry.WithContext(ctx).Info("Hello World!") 237 validateOutput(t, out, map[string]interface{}{ 238 "entity.name": integrationsupport.SampleAppName, 239 "entity.type": "SERVICE", 240 "file.name": matchAnything, 241 "hostname": host, 242 "line.number": matchAnything, 243 "log.level": "info", 244 "message": "Hello World!", 245 "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestEntryUsedTwice", 246 "span.id": "bcfb32e050b264b8", 247 "timestamp": float64(1417136460000), 248 "trace.id": "d9466896a525ccbf", 249 }) 250 251 // First log has dt enabled, ensure trace.id and span.id are included 252 out.Reset() 253 app = integrationsupport.NewTestApp(nil, 254 func(cfg *newrelic.Config) { 255 cfg.DistributedTracer.Enabled = false 256 }) 257 txn = app.StartTransaction("TestEntryUsedTwice2", nil, nil) 258 ctx = newrelic.NewContext(context.Background(), txn) 259 host, _ = sysinfo.Hostname() 260 entry.WithContext(ctx).Info("Hello World! Again!") 261 validateOutput(t, out, map[string]interface{}{ 262 "entity.name": integrationsupport.SampleAppName, 263 "entity.type": "SERVICE", 264 "file.name": matchAnything, 265 "hostname": host, 266 "line.number": matchAnything, 267 "log.level": "info", 268 "message": "Hello World! Again!", 269 "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestEntryUsedTwice", 270 "timestamp": float64(1417136460000), 271 }) 272 } 273 274 func TestEntryError(t *testing.T) { 275 app := integrationsupport.NewTestApp(nil, nil) 276 txn := app.StartTransaction("TestEntryError", nil, nil) 277 out := bytes.NewBuffer([]byte{}) 278 log := newTestLogger(out) 279 ctx := newrelic.NewContext(context.Background(), txn) 280 host, _ := sysinfo.Hostname() 281 log.WithTime(testTime).WithContext(ctx).WithField("func", func() {}).Info("Hello World!") 282 validateOutput(t, out, map[string]interface{}{ 283 "entity.name": integrationsupport.SampleAppName, 284 "entity.type": "SERVICE", 285 "file.name": matchAnything, 286 "hostname": host, 287 "line.number": matchAnything, 288 "log.level": "info", 289 // Since the err field on the Entry is private we cannot record it. 290 //"logrus_error": `can not add field "func"`, 291 "message": "Hello World!", 292 "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestEntryError", 293 "timestamp": float64(1417136460000), 294 }) 295 } 296 297 func TestWithCustomField(t *testing.T) { 298 app := integrationsupport.NewTestApp(nil, nil) 299 txn := app.StartTransaction("TestWithCustomField", nil, nil) 300 out := bytes.NewBuffer([]byte{}) 301 log := newTestLogger(out) 302 ctx := newrelic.NewContext(context.Background(), txn) 303 host, _ := sysinfo.Hostname() 304 log.WithTime(testTime).WithContext(ctx).WithField("zip", "zap").Info("Hello World!") 305 validateOutput(t, out, map[string]interface{}{ 306 "entity.name": integrationsupport.SampleAppName, 307 "entity.type": "SERVICE", 308 "file.name": matchAnything, 309 "hostname": host, 310 "line.number": matchAnything, 311 "log.level": "info", 312 "message": "Hello World!", 313 "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestWithCustomField", 314 "timestamp": float64(1417136460000), 315 "zip": "zap", 316 }) 317 } 318 319 func TestCustomFieldTypes(t *testing.T) { 320 out := bytes.NewBuffer([]byte{}) 321 322 testcases := []struct { 323 input interface{} 324 output string 325 }{ 326 {input: true, output: "true"}, 327 {input: false, output: "false"}, 328 {input: uint8(42), output: "42"}, 329 {input: uint16(42), output: "42"}, 330 {input: uint32(42), output: "42"}, 331 {input: uint(42), output: "42"}, 332 {input: uintptr(42), output: "42"}, 333 {input: int8(42), output: "42"}, 334 {input: int16(42), output: "42"}, 335 {input: int32(42), output: "42"}, 336 {input: int64(42), output: "42"}, 337 {input: float32(42), output: "42"}, 338 {input: float64(42), output: "42"}, 339 {input: errors.New("Ooops an error"), output: `"Ooops an error"`}, 340 {input: []int{1, 2, 3}, output: `"[]int{1, 2, 3}"`}, 341 } 342 343 for _, test := range testcases { 344 out.Reset() 345 writeValue(out, test.input) 346 if out.String() != test.output { 347 t.Errorf("Incorrect output written:\nactual=%s\nexpected=%s", 348 out.String(), test.output) 349 } 350 } 351 } 352 353 func TestUnsetCaller(t *testing.T) { 354 out := bytes.NewBuffer([]byte{}) 355 log := newTestLogger(out) 356 log.SetReportCaller(false) 357 log.WithTime(testTime).Info("Hello World!") 358 validateOutput(t, out, map[string]interface{}{ 359 "log.level": "info", 360 "message": "Hello World!", 361 "timestamp": float64(1417136460000), 362 }) 363 } 364 365 func TestCustomFieldNameCollision(t *testing.T) { 366 out := bytes.NewBuffer([]byte{}) 367 log := newTestLogger(out) 368 log.SetReportCaller(false) 369 log.WithTime(testTime).WithField("timestamp", "Yesterday").Info("Hello World!") 370 validateOutput(t, out, map[string]interface{}{ 371 "log.level": "info", 372 "message": "Hello World!", 373 // Reserved keys will be overwritten 374 "timestamp": float64(1417136460000), 375 }) 376 } 377 378 type gopher struct { 379 name string 380 } 381 382 func (g *gopher) MarshalJSON() ([]byte, error) { 383 return json.Marshal(g.name) 384 } 385 386 func TestCustomJSONMarshaller(t *testing.T) { 387 out := bytes.NewBuffer([]byte{}) 388 log := newTestLogger(out) 389 log.SetReportCaller(false) 390 log.WithTime(testTime).WithField("gopher", &gopher{name: "sam"}).Info("Hello World!") 391 validateOutput(t, out, map[string]interface{}{ 392 "gopher": "sam", 393 "log.level": "info", 394 "message": "Hello World!", 395 "timestamp": float64(1417136460000), 396 }) 397 }