github.com/newrelic/go-agent@v3.26.0+incompatible/internal/attributes_test.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package internal
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/newrelic/go-agent/internal/crossagent"
    15  )
    16  
    17  type AttributeTestcase struct {
    18  	Testname string `json:"testname"`
    19  	Config   struct {
    20  		AttributesEnabled        bool     `json:"attributes.enabled"`
    21  		AttributesInclude        []string `json:"attributes.include"`
    22  		AttributesExclude        []string `json:"attributes.exclude"`
    23  		BrowserAttributesEnabled bool     `json:"browser_monitoring.attributes.enabled"`
    24  		BrowserAttributesInclude []string `json:"browser_monitoring.attributes.include"`
    25  		BrowserAttributesExclude []string `json:"browser_monitoring.attributes.exclude"`
    26  		ErrorAttributesEnabled   bool     `json:"error_collector.attributes.enabled"`
    27  		ErrorAttributesInclude   []string `json:"error_collector.attributes.include"`
    28  		ErrorAttributesExclude   []string `json:"error_collector.attributes.exclude"`
    29  		EventsAttributesEnabled  bool     `json:"transaction_events.attributes.enabled"`
    30  		EventsAttributesInclude  []string `json:"transaction_events.attributes.include"`
    31  		EventsAttributesExclude  []string `json:"transaction_events.attributes.exclude"`
    32  		TracerAttributesEnabled  bool     `json:"transaction_tracer.attributes.enabled"`
    33  		TracerAttributesInclude  []string `json:"transaction_tracer.attributes.include"`
    34  		TracerAttributesExclude  []string `json:"transaction_tracer.attributes.exclude"`
    35  	} `json:"config"`
    36  	Key                  string   `json:"input_key"`
    37  	InputDestinations    []string `json:"input_default_destinations"`
    38  	ExpectedDestinations []string `json:"expected_destinations"`
    39  }
    40  
    41  var (
    42  	destTranslate = map[string]destinationSet{
    43  		"attributes":         DestAll,
    44  		"transaction_events": destTxnEvent,
    45  		"transaction_tracer": destTxnTrace,
    46  		"error_collector":    destError,
    47  		"browser_monitoring": destBrowser,
    48  	}
    49  )
    50  
    51  func destinationsFromArray(dests []string) destinationSet {
    52  	d := destNone
    53  	for _, s := range dests {
    54  		if x, ok := destTranslate[s]; ok {
    55  			d |= x
    56  		}
    57  	}
    58  	return d
    59  }
    60  
    61  func destToString(d destinationSet) string {
    62  	if 0 == d {
    63  		return "none"
    64  	}
    65  	out := ""
    66  	for _, ds := range []struct {
    67  		Name string
    68  		Dest destinationSet
    69  	}{
    70  		{Name: "event", Dest: destTxnEvent},
    71  		{Name: "trace", Dest: destTxnTrace},
    72  		{Name: "error", Dest: destError},
    73  		{Name: "browser", Dest: destBrowser},
    74  		{Name: "span", Dest: destSpan},
    75  		{Name: "segment", Dest: destSegment},
    76  	} {
    77  		if 0 != d&ds.Dest {
    78  			if "" == out {
    79  				out = ds.Name
    80  			} else {
    81  				out = out + "," + ds.Name
    82  			}
    83  		}
    84  	}
    85  	return out
    86  }
    87  
    88  func runAttributeTestcase(t *testing.T, js json.RawMessage) {
    89  	var tc AttributeTestcase
    90  
    91  	tc.Config.AttributesEnabled = true
    92  	tc.Config.BrowserAttributesEnabled = false
    93  	tc.Config.ErrorAttributesEnabled = true
    94  	tc.Config.EventsAttributesEnabled = true
    95  	tc.Config.TracerAttributesEnabled = true
    96  
    97  	if err := json.Unmarshal(js, &tc); nil != err {
    98  		t.Error(err)
    99  		return
   100  	}
   101  
   102  	input := AttributeConfigInput{
   103  		Attributes: AttributeDestinationConfig{
   104  			Enabled: tc.Config.AttributesEnabled,
   105  			Include: tc.Config.AttributesInclude,
   106  			Exclude: tc.Config.AttributesExclude,
   107  		},
   108  		ErrorCollector: AttributeDestinationConfig{
   109  			Enabled: tc.Config.ErrorAttributesEnabled,
   110  			Include: tc.Config.ErrorAttributesInclude,
   111  			Exclude: tc.Config.ErrorAttributesExclude,
   112  		},
   113  		TransactionEvents: AttributeDestinationConfig{
   114  			Enabled: tc.Config.EventsAttributesEnabled,
   115  			Include: tc.Config.EventsAttributesInclude,
   116  			Exclude: tc.Config.EventsAttributesExclude,
   117  		},
   118  		BrowserMonitoring: AttributeDestinationConfig{
   119  			Enabled: tc.Config.BrowserAttributesEnabled,
   120  			Include: tc.Config.BrowserAttributesInclude,
   121  			Exclude: tc.Config.BrowserAttributesExclude,
   122  		},
   123  		TransactionTracer: AttributeDestinationConfig{
   124  			Enabled: tc.Config.TracerAttributesEnabled,
   125  			Include: tc.Config.TracerAttributesInclude,
   126  			Exclude: tc.Config.TracerAttributesExclude,
   127  		},
   128  	}
   129  
   130  	cfg := CreateAttributeConfig(input, true)
   131  
   132  	inputDests := destinationsFromArray(tc.InputDestinations)
   133  	expectedDests := destinationsFromArray(tc.ExpectedDestinations)
   134  
   135  	out := applyAttributeConfig(cfg, tc.Key, inputDests)
   136  
   137  	if out != expectedDests {
   138  		t.Errorf(`name="%s"  input="%s"  expected="%s"  got="%s"`,
   139  			tc.Testname,
   140  			destToString(inputDests),
   141  			destToString(expectedDests),
   142  			destToString(out))
   143  	}
   144  }
   145  
   146  func TestCrossAgentAttributes(t *testing.T) {
   147  	var tcs []json.RawMessage
   148  
   149  	err := crossagent.ReadJSON("attribute_configuration.json", &tcs)
   150  	if err != nil {
   151  		t.Fatal(err)
   152  	}
   153  
   154  	for _, tc := range tcs {
   155  		runAttributeTestcase(t, tc)
   156  	}
   157  }
   158  
   159  func TestWriteAttributeValueJSON(t *testing.T) {
   160  	buf := &bytes.Buffer{}
   161  	w := jsonFieldsWriter{buf: buf}
   162  
   163  	buf.WriteByte('{')
   164  	writeAttributeValueJSON(&w, "a", `escape\me!`)
   165  	writeAttributeValueJSON(&w, "a", true)
   166  	writeAttributeValueJSON(&w, "a", false)
   167  	writeAttributeValueJSON(&w, "a", uint8(1))
   168  	writeAttributeValueJSON(&w, "a", uint16(2))
   169  	writeAttributeValueJSON(&w, "a", uint32(3))
   170  	writeAttributeValueJSON(&w, "a", uint64(4))
   171  	writeAttributeValueJSON(&w, "a", uint(5))
   172  	writeAttributeValueJSON(&w, "a", uintptr(6))
   173  	writeAttributeValueJSON(&w, "a", int8(-1))
   174  	writeAttributeValueJSON(&w, "a", int16(-2))
   175  	writeAttributeValueJSON(&w, "a", int32(-3))
   176  	writeAttributeValueJSON(&w, "a", int64(-4))
   177  	writeAttributeValueJSON(&w, "a", int(-5))
   178  	writeAttributeValueJSON(&w, "a", float32(1.5))
   179  	writeAttributeValueJSON(&w, "a", float64(4.56))
   180  	buf.WriteByte('}')
   181  
   182  	expect := CompactJSONString(`{
   183  		"a":"escape\\me!",
   184  		"a":true,
   185  		"a":false,
   186  		"a":1,
   187  		"a":2,
   188  		"a":3,
   189  		"a":4,
   190  		"a":5,
   191  		"a":6,
   192  		"a":-1,
   193  		"a":-2,
   194  		"a":-3,
   195  		"a":-4,
   196  		"a":-5,
   197  		"a":1.5,
   198  		"a":4.56
   199  		}`)
   200  	js := buf.String()
   201  	if js != expect {
   202  		t.Error(js, expect)
   203  	}
   204  }
   205  
   206  func TestValidAttributeTypes(t *testing.T) {
   207  	testcases := []struct {
   208  		Input interface{}
   209  		Valid bool
   210  	}{
   211  		// Valid attribute types.
   212  		{Input: "string value", Valid: true},
   213  		{Input: true, Valid: true},
   214  		{Input: uint8(0), Valid: true},
   215  		{Input: uint16(0), Valid: true},
   216  		{Input: uint32(0), Valid: true},
   217  		{Input: uint64(0), Valid: true},
   218  		{Input: int8(0), Valid: true},
   219  		{Input: int16(0), Valid: true},
   220  		{Input: int32(0), Valid: true},
   221  		{Input: int64(0), Valid: true},
   222  		{Input: float32(0), Valid: true},
   223  		{Input: float64(0), Valid: true},
   224  		{Input: uint(0), Valid: true},
   225  		{Input: int(0), Valid: true},
   226  		{Input: uintptr(0), Valid: true},
   227  		// Invalid attribute types.
   228  		{Input: nil, Valid: false},
   229  		{Input: struct{}{}, Valid: false},
   230  		{Input: &struct{}{}, Valid: false},
   231  	}
   232  
   233  	for _, tc := range testcases {
   234  		val, err := ValidateUserAttribute("key", tc.Input)
   235  		_, invalid := err.(ErrInvalidAttributeType)
   236  		if tc.Valid == invalid {
   237  			t.Error(tc.Input, tc.Valid, val, err)
   238  		}
   239  	}
   240  }
   241  
   242  func TestUserAttributeValLength(t *testing.T) {
   243  	cfg := CreateAttributeConfig(sampleAttributeConfigInput, true)
   244  	attrs := NewAttributes(cfg)
   245  
   246  	atLimit := strings.Repeat("a", attributeValueLengthLimit)
   247  	tooLong := atLimit + "a"
   248  
   249  	err := AddUserAttribute(attrs, `escape\me`, tooLong, DestAll)
   250  	if err != nil {
   251  		t.Error(err)
   252  	}
   253  	js := userAttributesStringJSON(attrs, DestAll, nil)
   254  	if `{"escape\\me":"`+atLimit+`"}` != js {
   255  		t.Error(js)
   256  	}
   257  }
   258  
   259  func TestUserAttributeKeyLength(t *testing.T) {
   260  	cfg := CreateAttributeConfig(sampleAttributeConfigInput, true)
   261  	attrs := NewAttributes(cfg)
   262  
   263  	lengthyKey := strings.Repeat("a", attributeKeyLengthLimit+1)
   264  	err := AddUserAttribute(attrs, lengthyKey, 123, DestAll)
   265  	if _, ok := err.(invalidAttributeKeyErr); !ok {
   266  		t.Error(err)
   267  	}
   268  	js := userAttributesStringJSON(attrs, DestAll, nil)
   269  	if `{}` != js {
   270  		t.Error(js)
   271  	}
   272  }
   273  
   274  func TestNumUserAttributesLimit(t *testing.T) {
   275  	cfg := CreateAttributeConfig(sampleAttributeConfigInput, true)
   276  	attrs := NewAttributes(cfg)
   277  
   278  	for i := 0; i < attributeUserLimit; i++ {
   279  		s := strconv.Itoa(i)
   280  		err := AddUserAttribute(attrs, s, s, DestAll)
   281  		if err != nil {
   282  			t.Fatal(err)
   283  		}
   284  	}
   285  
   286  	err := AddUserAttribute(attrs, "cant_add_me", 123, DestAll)
   287  	if _, ok := err.(userAttributeLimitErr); !ok {
   288  		t.Fatal(err)
   289  	}
   290  
   291  	js := userAttributesStringJSON(attrs, DestAll, nil)
   292  	var out map[string]string
   293  	err = json.Unmarshal([]byte(js), &out)
   294  	if nil != err {
   295  		t.Fatal(err)
   296  	}
   297  	if len(out) != attributeUserLimit {
   298  		t.Error(len(out))
   299  	}
   300  	if strings.Contains(js, "cant_add_me") {
   301  		t.Fatal(js)
   302  	}
   303  
   304  	// Now test that replacement works when the limit is reached.
   305  	err = AddUserAttribute(attrs, "0", "BEEN_REPLACED", DestAll)
   306  	if nil != err {
   307  		t.Fatal(err)
   308  	}
   309  	js = userAttributesStringJSON(attrs, DestAll, nil)
   310  	if !strings.Contains(js, "BEEN_REPLACED") {
   311  		t.Fatal(js)
   312  	}
   313  }
   314  
   315  func TestExtraAttributesIncluded(t *testing.T) {
   316  	cfg := CreateAttributeConfig(sampleAttributeConfigInput, true)
   317  	attrs := NewAttributes(cfg)
   318  
   319  	err := AddUserAttribute(attrs, "a", 1, DestAll)
   320  	if nil != err {
   321  		t.Error(err)
   322  	}
   323  	js := userAttributesStringJSON(attrs, DestAll, map[string]interface{}{"b": 2})
   324  	if `{"b":2,"a":1}` != js {
   325  		t.Error(js)
   326  	}
   327  }
   328  
   329  func TestExtraAttributesPrecedence(t *testing.T) {
   330  	cfg := CreateAttributeConfig(sampleAttributeConfigInput, true)
   331  	attrs := NewAttributes(cfg)
   332  
   333  	err := AddUserAttribute(attrs, "a", 1, DestAll)
   334  	if nil != err {
   335  		t.Error(err)
   336  	}
   337  	js := userAttributesStringJSON(attrs, DestAll, map[string]interface{}{"a": 2})
   338  	if `{"a":2}` != js {
   339  		t.Error(js)
   340  	}
   341  }
   342  
   343  func TestIncludeDisabled(t *testing.T) {
   344  	input := sampleAttributeConfigInput
   345  	input.Attributes.Include = append(input.Attributes.Include, "include_me")
   346  	cfg := CreateAttributeConfig(input, false)
   347  	attrs := NewAttributes(cfg)
   348  
   349  	err := AddUserAttribute(attrs, "include_me", 1, destNone)
   350  	if nil != err {
   351  		t.Error(err)
   352  	}
   353  	js := userAttributesStringJSON(attrs, DestAll, nil)
   354  	if `{}` != js {
   355  		t.Error(js)
   356  	}
   357  }
   358  
   359  func agentAttributesMap(attrs *Attributes, d destinationSet) map[string]interface{} {
   360  	buf := &bytes.Buffer{}
   361  	agentAttributesJSON(attrs, buf, d)
   362  	var m map[string]interface{}
   363  	err := json.Unmarshal(buf.Bytes(), &m)
   364  	if err != nil {
   365  		panic(err)
   366  	}
   367  	return m
   368  }
   369  
   370  func TestRequestAgentAttributesEmptyInput(t *testing.T) {
   371  	cfg := CreateAttributeConfig(sampleAttributeConfigInput, true)
   372  	attrs := NewAttributes(cfg)
   373  	RequestAgentAttributes(attrs, "", nil, nil)
   374  	got := agentAttributesMap(attrs, DestAll)
   375  	expectAttributes(t, got, map[string]interface{}{})
   376  }
   377  
   378  func TestRequestAgentAttributesPresent(t *testing.T) {
   379  	req, err := http.NewRequest("GET", "http://www.newrelic.com?remove=me", nil)
   380  	if nil != err {
   381  		t.Fatal(err)
   382  	}
   383  	req.Header.Set("Accept", "the-accept")
   384  	req.Header.Set("Content-Type", "the-content-type")
   385  	req.Header.Set("Host", "the-host")
   386  	req.Header.Set("User-Agent", "the-agent")
   387  	req.Header.Set("Referer", "http://www.example.com")
   388  	req.Header.Set("Content-Length", "123")
   389  
   390  	cfg := CreateAttributeConfig(sampleAttributeConfigInput, true)
   391  
   392  	attrs := NewAttributes(cfg)
   393  	RequestAgentAttributes(attrs, req.Method, req.Header, req.URL)
   394  	got := agentAttributesMap(attrs, DestAll)
   395  	expectAttributes(t, got, map[string]interface{}{
   396  		"request.headers.contentType":   "the-content-type",
   397  		"request.headers.host":          "the-host",
   398  		"request.headers.User-Agent":    "the-agent",
   399  		"request.headers.referer":       "http://www.example.com",
   400  		"request.headers.contentLength": 123,
   401  		"request.method":                "GET",
   402  		"request.uri":                   "http://www.newrelic.com",
   403  		"request.headers.accept":        "the-accept",
   404  	})
   405  }
   406  
   407  func BenchmarkAgentAttributes(b *testing.B) {
   408  	cfg := CreateAttributeConfig(sampleAttributeConfigInput, true)
   409  
   410  	req, err := http.NewRequest("GET", "http://www.newrelic.com", nil)
   411  	if nil != err {
   412  		b.Fatal(err)
   413  	}
   414  
   415  	req.Header.Set("Accept", "zap")
   416  	req.Header.Set("Content-Type", "zap")
   417  	req.Header.Set("Host", "zap")
   418  	req.Header.Set("User-Agent", "zap")
   419  	req.Header.Set("Referer", "http://www.newrelic.com")
   420  	req.Header.Set("Content-Length", "123")
   421  
   422  	b.ResetTimer()
   423  	b.ReportAllocs()
   424  
   425  	for i := 0; i < b.N; i++ {
   426  		attrs := NewAttributes(cfg)
   427  		RequestAgentAttributes(attrs, req.Method, req.Header, req.URL)
   428  		buf := bytes.Buffer{}
   429  		agentAttributesJSON(attrs, &buf, destTxnTrace)
   430  	}
   431  }
   432  
   433  func TestGetAgentValue(t *testing.T) {
   434  	// Test nil safe
   435  	var attrs *Attributes
   436  	outstr, outother := attrs.GetAgentValue(attributeRequestURI, destTxnTrace)
   437  	if outstr != "" || outother != nil {
   438  		t.Error(outstr, outother)
   439  	}
   440  
   441  	c := sampleAttributeConfigInput
   442  	c.TransactionTracer.Exclude = []string{"request.uri"}
   443  	cfg := CreateAttributeConfig(c, true)
   444  	attrs = NewAttributes(cfg)
   445  	attrs.Agent.Add(attributeResponseHeadersContentLength, "", 123)
   446  	attrs.Agent.Add(attributeRequestMethod, "GET", nil)
   447  	attrs.Agent.Add(attributeRequestURI, "/url", nil) // disabled by configuration
   448  
   449  	outstr, outother = attrs.GetAgentValue(attributeResponseHeadersContentLength, destTxnTrace)
   450  	if outstr != "" || outother != 123 {
   451  		t.Error(outstr, outother)
   452  	}
   453  	outstr, outother = attrs.GetAgentValue(attributeRequestMethod, destTxnTrace)
   454  	if outstr != "GET" || outother != nil {
   455  		t.Error(outstr, outother)
   456  	}
   457  	outstr, outother = attrs.GetAgentValue(attributeRequestURI, destTxnTrace)
   458  	if outstr != "" || outother != nil {
   459  		t.Error(outstr, outother)
   460  	}
   461  }