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  }