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 }