github.com/waldiirawan/apm-agent-go/v2@v2.2.2/env_test.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package apm_test 19 20 import ( 21 "context" 22 "io" 23 "io/ioutil" 24 "net/http" 25 "net/http/httptest" 26 "os" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 34 "github.com/waldiirawan/apm-agent-go/v2" 35 "github.com/waldiirawan/apm-agent-go/v2/apmtest" 36 "github.com/waldiirawan/apm-agent-go/v2/model" 37 "github.com/waldiirawan/apm-agent-go/v2/transport" 38 "github.com/waldiirawan/apm-agent-go/v2/transport/transporttest" 39 ) 40 41 func TestTracerRequestTimeEnv(t *testing.T) { 42 os.Setenv("ELASTIC_APM_API_REQUEST_TIME", "1s") 43 defer os.Unsetenv("ELASTIC_APM_API_REQUEST_TIME") 44 45 requestHandled := make(chan struct{}, 1) 46 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 47 if req.URL.Path != "/intake/v2/events" { 48 return 49 } 50 io.Copy(ioutil.Discard, req.Body) 51 requestHandled <- struct{}{} 52 })) 53 defer server.Close() 54 55 os.Setenv("ELASTIC_APM_SERVER_URL", server.URL) 56 defer os.Unsetenv("ELASTIC_APM_SERVER_URL") 57 58 httpTransport, err := transport.NewHTTPTransport(transport.HTTPTransportOptions{}) 59 require.NoError(t, err) 60 tracer, err := apm.NewTracerOptions(apm.TracerOptions{ 61 ServiceName: "tracer_testing", 62 Transport: httpTransport, 63 }) 64 require.NoError(t, err) 65 defer tracer.Close() 66 67 clientStart := time.Now() 68 tracer.StartTransaction("name", "type").End() 69 <-requestHandled 70 clientEnd := time.Now() 71 72 assert.WithinDuration(t, clientStart.Add(time.Second), clientEnd, 200*time.Millisecond) 73 } 74 75 func TestTracerRequestTimeEnvInvalid(t *testing.T) { 76 t.Run("invalid_duration", func(t *testing.T) { 77 os.Setenv("ELASTIC_APM_API_REQUEST_TIME", "aeon") 78 defer os.Unsetenv("ELASTIC_APM_API_REQUEST_TIME") 79 _, err := apm.NewTracer("tracer_testing", "") 80 assert.EqualError(t, err, "failed to parse ELASTIC_APM_API_REQUEST_TIME: invalid duration aeon") 81 }) 82 t.Run("missing_suffix", func(t *testing.T) { 83 os.Setenv("ELASTIC_APM_API_REQUEST_TIME", "1") 84 defer os.Unsetenv("ELASTIC_APM_API_REQUEST_TIME") 85 _, err := apm.NewTracer("tracer_testing", "") 86 assert.EqualError(t, err, "failed to parse ELASTIC_APM_API_REQUEST_TIME: missing unit in duration 1 (allowed units: ms, s, m)") 87 }) 88 } 89 90 func TestTracerRequestSizeEnvInvalid(t *testing.T) { 91 t.Run("too_small", func(t *testing.T) { 92 os.Setenv("ELASTIC_APM_API_REQUEST_SIZE", "1B") 93 defer os.Unsetenv("ELASTIC_APM_API_REQUEST_SIZE") 94 _, err := apm.NewTracer("tracer_testing", "") 95 assert.EqualError(t, err, "ELASTIC_APM_API_REQUEST_SIZE must be at least 1KB and less than 5MB, got 1B") 96 }) 97 t.Run("too_large", func(t *testing.T) { 98 os.Setenv("ELASTIC_APM_API_REQUEST_SIZE", "500GB") 99 defer os.Unsetenv("ELASTIC_APM_API_REQUEST_SIZE") 100 _, err := apm.NewTracer("tracer_testing", "") 101 assert.EqualError(t, err, "ELASTIC_APM_API_REQUEST_SIZE must be at least 1KB and less than 5MB, got 500GB") 102 }) 103 } 104 105 func TestTracerBufferSizeEnvInvalid(t *testing.T) { 106 t.Run("too_small", func(t *testing.T) { 107 os.Setenv("ELASTIC_APM_API_BUFFER_SIZE", "1B") 108 defer os.Unsetenv("ELASTIC_APM_API_BUFFER_SIZE") 109 _, err := apm.NewTracer("tracer_testing", "") 110 assert.EqualError(t, err, "ELASTIC_APM_API_BUFFER_SIZE must be at least 10KB and less than 100MB, got 1B") 111 }) 112 t.Run("too_large", func(t *testing.T) { 113 os.Setenv("ELASTIC_APM_API_BUFFER_SIZE", "500GB") 114 defer os.Unsetenv("ELASTIC_APM_API_BUFFER_SIZE") 115 _, err := apm.NewTracer("tracer_testing", "") 116 assert.EqualError(t, err, "ELASTIC_APM_API_BUFFER_SIZE must be at least 10KB and less than 100MB, got 500GB") 117 }) 118 } 119 120 func TestTracerMetricsBufferSizeEnvInvalid(t *testing.T) { 121 t.Run("too_small", func(t *testing.T) { 122 os.Setenv("ELASTIC_APM_METRICS_BUFFER_SIZE", "1B") 123 defer os.Unsetenv("ELASTIC_APM_METRICS_BUFFER_SIZE") 124 _, err := apm.NewTracer("tracer_testing", "") 125 assert.EqualError(t, err, "ELASTIC_APM_METRICS_BUFFER_SIZE must be at least 10KB and less than 100MB, got 1B") 126 }) 127 t.Run("too_large", func(t *testing.T) { 128 os.Setenv("ELASTIC_APM_METRICS_BUFFER_SIZE", "500GB") 129 defer os.Unsetenv("ELASTIC_APM_METRICS_BUFFER_SIZE") 130 _, err := apm.NewTracer("tracer_testing", "") 131 assert.EqualError(t, err, "ELASTIC_APM_METRICS_BUFFER_SIZE must be at least 10KB and less than 100MB, got 500GB") 132 }) 133 } 134 135 func TestTracerTransactionRateEnv(t *testing.T) { 136 t.Run("0.5", func(t *testing.T) { 137 testTracerTransactionRateEnv(t, "0.5", 0.5) 138 }) 139 t.Run("0.75", func(t *testing.T) { 140 testTracerTransactionRateEnv(t, "0.75", 0.75) 141 }) 142 t.Run("1.0", func(t *testing.T) { 143 testTracerTransactionRateEnv(t, "1.0", 1.0) 144 }) 145 } 146 147 func TestTracerTransactionRateEnvInvalid(t *testing.T) { 148 os.Setenv("ELASTIC_APM_TRANSACTION_SAMPLE_RATE", "2.0") 149 defer os.Unsetenv("ELASTIC_APM_TRANSACTION_SAMPLE_RATE") 150 151 _, err := apm.NewTracer("tracer_testing", "") 152 assert.EqualError(t, err, "invalid value for ELASTIC_APM_TRANSACTION_SAMPLE_RATE: 2.0 (out of range [0,1.0])") 153 } 154 155 func testTracerTransactionRateEnv(t *testing.T, envValue string, ratio float64) { 156 os.Setenv("ELASTIC_APM_TRANSACTION_SAMPLE_RATE", envValue) 157 defer os.Unsetenv("ELASTIC_APM_TRANSACTION_SAMPLE_RATE") 158 159 tracer := apmtest.NewDiscardTracer() 160 defer tracer.Close() 161 162 const N = 10000 163 var sampled int 164 for i := 0; i < N; i++ { 165 tx := tracer.StartTransaction("name", "type") 166 if tx.Sampled() { 167 sampled++ 168 } 169 tx.End() 170 } 171 assert.InDelta(t, N*ratio, sampled, N*0.02) // allow 2% error 172 } 173 174 func TestTracerSanitizeFieldNamesEnv(t *testing.T) { 175 testTracerSanitizeFieldNamesEnv(t, "secRet", "[REDACTED]") 176 testTracerSanitizeFieldNamesEnv(t, "nada", "top") 177 } 178 179 func testTracerSanitizeFieldNamesEnv(t *testing.T, envValue, expect string) { 180 os.Setenv("ELASTIC_APM_SANITIZE_FIELD_NAMES", envValue) 181 defer os.Unsetenv("ELASTIC_APM_SANITIZE_FIELD_NAMES") 182 183 req, _ := http.NewRequest("GET", "http://server.testing/", nil) 184 req.AddCookie(&http.Cookie{Name: "secret", Value: "top"}) 185 186 tx, _, _ := apmtest.WithTransaction(func(ctx context.Context) { 187 tx := apm.TransactionFromContext(ctx) 188 tx.Context.SetHTTPRequest(req) 189 }) 190 assert.Equal(t, tx.Context.Request.Cookies, model.Cookies{ 191 {Name: "secret", Value: expect}, 192 }) 193 } 194 195 func TestTracerServiceNameEnvSanitizationSpecified(t *testing.T) { 196 _, _, service, _ := getSubprocessMetadata(t, "ELASTIC_APM_SERVICE_NAME=foo!bar") 197 assert.Equal(t, "foo_bar", service.Name) 198 } 199 200 func TestTracerServiceNameEnvSanitizationExecutableName(t *testing.T) { 201 _, _, service, _ := getSubprocessMetadata(t) 202 assert.Equal(t, "apm_test", service.Name) // .test -> _test 203 } 204 205 func TestTracerGlobalLabelsUnspecified(t *testing.T) { 206 _, _, _, labels := getSubprocessMetadata(t) 207 assert.Equal(t, model.StringMap{}, labels) 208 } 209 210 func TestTracerGlobalLabelsSpecified(t *testing.T) { 211 _, _, _, labels := getSubprocessMetadata(t, "ELASTIC_APM_GLOBAL_LABELS=a=b,c = d") 212 assert.Equal(t, model.StringMap{{Key: "a", Value: "b"}, {Key: "c", Value: "d"}}, labels) 213 } 214 215 func TestTracerGlobalLabelsIgnoreInvalid(t *testing.T) { 216 _, _, _, labels := getSubprocessMetadata(t, "ELASTIC_APM_GLOBAL_LABELS=a,=,b==c,d=") 217 assert.Equal(t, model.StringMap{{Key: "b", Value: "=c"}, {Key: "d", Value: ""}}, labels) 218 } 219 220 func TestTracerCaptureBodyEnv(t *testing.T) { 221 t.Run("all", func(t *testing.T) { testTracerCaptureBodyEnv(t, "all", true) }) 222 t.Run("transactions", func(t *testing.T) { testTracerCaptureBodyEnv(t, "transactions", true) }) 223 } 224 225 func TestTracerCaptureBodyEnvOff(t *testing.T) { 226 t.Run("unset", func(t *testing.T) { testTracerCaptureBodyEnv(t, "", false) }) 227 t.Run("off", func(t *testing.T) { testTracerCaptureBodyEnv(t, "off", false) }) 228 } 229 230 func TestTracerCaptureBodyEnvInvalid(t *testing.T) { 231 os.Setenv("ELASTIC_APM_CAPTURE_BODY", "invalid") 232 defer os.Unsetenv("ELASTIC_APM_CAPTURE_BODY") 233 _, err := apm.NewTracer("", "") 234 assert.EqualError(t, err, `invalid ELASTIC_APM_CAPTURE_BODY value "invalid"`) 235 } 236 237 func testTracerCaptureBodyEnv(t *testing.T, envValue string, expectBody bool) { 238 os.Setenv("ELASTIC_APM_CAPTURE_BODY", envValue) 239 defer os.Unsetenv("ELASTIC_APM_CAPTURE_BODY") 240 241 tracer, transport := transporttest.NewRecorderTracer() 242 defer tracer.Close() 243 244 req, _ := http.NewRequest("GET", "/", strings.NewReader("foo_bar")) 245 body := tracer.CaptureHTTPRequestBody(req) 246 tx := tracer.StartTransaction("name", "type") 247 tx.Context.SetHTTPRequest(req) 248 tx.Context.SetHTTPRequestBody(body) 249 tx.End() 250 tracer.Flush(nil) 251 252 out := transport.Payloads().Transactions[0] 253 if expectBody { 254 require.NotNil(t, out.Context.Request.Body) 255 assert.Equal(t, "foo_bar", out.Context.Request.Body.Raw) 256 } else { 257 assert.Nil(t, out.Context.Request.Body) 258 } 259 } 260 261 func TestTracerSpanFramesMinDurationEnv(t *testing.T) { 262 os.Setenv("ELASTIC_APM_SPAN_FRAMES_MIN_DURATION", "10ms") 263 defer os.Unsetenv("ELASTIC_APM_SPAN_FRAMES_MIN_DURATION") 264 265 tracer, transport := transporttest.NewRecorderTracer() 266 defer tracer.Close() 267 268 tx := tracer.StartTransaction("name", "type") 269 s := tx.StartSpan("name", "type", nil) 270 s.Duration = 9 * time.Millisecond 271 s.End() 272 s = tx.StartSpan("name", "type", nil) 273 s.Duration = 10 * time.Millisecond 274 s.End() 275 tx.End() 276 tracer.Flush(nil) 277 278 spans := transport.Payloads().Spans 279 assert.Len(t, spans, 2) 280 281 // Span 0 took only 9ms, so we don't set its stacktrace. 282 assert.Nil(t, spans[0].Stacktrace) 283 284 // Span 1 took the required 10ms, so we set its stacktrace. 285 assert.NotNil(t, spans[1].Stacktrace) 286 } 287 288 func TestTracerStackTraceMinDurationEnv(t *testing.T) { 289 os.Setenv("ELASTIC_APM_SPAN_STACK_TRACE_MIN_DURATION", "10ms") 290 defer os.Unsetenv("ELASTIC_APM_SPAN_STACK_TRACE_MIN_DURATION") 291 292 tracer, transport := transporttest.NewRecorderTracer() 293 defer tracer.Close() 294 295 tx := tracer.StartTransaction("name", "type") 296 s := tx.StartSpan("name", "type", nil) 297 s.Duration = 9 * time.Millisecond 298 s.End() 299 s = tx.StartSpan("name", "type", nil) 300 s.Duration = 10 * time.Millisecond 301 s.End() 302 tx.End() 303 tracer.Flush(nil) 304 305 spans := transport.Payloads().Spans 306 assert.Len(t, spans, 2) 307 308 // Span 0 took only 9ms, so we don't set its stacktrace. 309 assert.Nil(t, spans[0].Stacktrace) 310 311 // Span 1 took the required 10ms, so we set its stacktrace. 312 assert.NotNil(t, spans[1].Stacktrace) 313 } 314 315 func TestTracerSpanFramesMinDurationEnvInvalid(t *testing.T) { 316 os.Setenv("ELASTIC_APM_SPAN_FRAMES_MIN_DURATION", "aeon") 317 defer os.Unsetenv("ELASTIC_APM_SPAN_FRAMES_MIN_DURATION") 318 319 _, err := apm.NewTracer("tracer_testing", "") 320 assert.EqualError(t, err, "failed to parse ELASTIC_APM_SPAN_FRAMES_MIN_DURATION: invalid duration aeon") 321 } 322 323 func TestTracerStackTraceMinDurationEnvInvalid(t *testing.T) { 324 os.Setenv("ELASTIC_APM_SPAN_STACK_TRACE_MIN_DURATION", "aeon") 325 defer os.Unsetenv("ELASTIC_APM_SPAN_STACK_TRACE_MIN_DURATION") 326 327 _, err := apm.NewTracer("tracer_testing", "") 328 assert.EqualError(t, err, "failed to parse ELASTIC_APM_SPAN_STACK_TRACE_MIN_DURATION: invalid duration aeon") 329 } 330 331 func TestTracerStackTraceLimitEnv(t *testing.T) { 332 os.Setenv("ELASTIC_APM_STACK_TRACE_LIMIT", "0") 333 defer os.Unsetenv("ELASTIC_APM_STACK_TRACE_LIMIT") 334 335 tracer, transport := transporttest.NewRecorderTracer() 336 defer tracer.Close() 337 338 sendSpan := func() { 339 tx := tracer.StartTransaction("name", "type") 340 s := tx.StartSpan("name", "type", nil) 341 s.Duration = time.Second 342 s.End() 343 tx.End() 344 } 345 346 sendSpan() 347 tracer.SetStackTraceLimit(2) 348 sendSpan() 349 350 tracer.Flush(nil) 351 spans := transport.Payloads().Spans 352 require.Len(t, spans, 2) 353 assert.Nil(t, spans[0].Stacktrace) 354 assert.Len(t, spans[1].Stacktrace, 2) 355 } 356 357 func TestTracerStackTraceLimitEnvInvalid(t *testing.T) { 358 os.Setenv("ELASTIC_APM_STACK_TRACE_LIMIT", "sky") 359 defer os.Unsetenv("ELASTIC_APM_STACK_TRACE_LIMIT") 360 361 _, err := apm.NewTracer("tracer_testing", "") 362 assert.EqualError(t, err, "failed to parse ELASTIC_APM_STACK_TRACE_LIMIT: strconv.Atoi: parsing \"sky\": invalid syntax") 363 } 364 365 func TestTracerActiveEnv(t *testing.T) { 366 os.Setenv("ELASTIC_APM_ACTIVE", "false") 367 defer os.Unsetenv("ELASTIC_APM_ACTIVE") 368 369 tracer, transport := transporttest.NewRecorderTracer() 370 defer tracer.Close() 371 assert.False(t, tracer.Active()) 372 assert.False(t, tracer.Recording()) // inactive => not recording 373 374 tx := tracer.StartTransaction("name", "type") 375 tx.End() 376 377 tracer.Flush(nil) 378 assert.Zero(t, transport.Payloads()) 379 } 380 381 func TestTracerActiveEnvInvalid(t *testing.T) { 382 os.Setenv("ELASTIC_APM_ACTIVE", "yep") 383 defer os.Unsetenv("ELASTIC_APM_ACTIVE") 384 385 _, err := apm.NewTracer("tracer_testing", "") 386 assert.EqualError(t, err, "failed to parse ELASTIC_APM_ACTIVE: strconv.ParseBool: parsing \"yep\": invalid syntax") 387 } 388 389 func TestTracerEnvironmentEnv(t *testing.T) { 390 os.Setenv("ELASTIC_APM_ENVIRONMENT", "friendly") 391 defer os.Unsetenv("ELASTIC_APM_ENVIRONMENT") 392 393 tracer, transport := transporttest.NewRecorderTracer() 394 defer tracer.Close() 395 396 tracer.StartTransaction("name", "type").End() 397 tracer.Flush(nil) 398 399 _, _, service, _ := transport.Metadata() 400 assert.Equal(t, "friendly", service.Environment) 401 } 402 403 func TestTracerCaptureHeadersEnv(t *testing.T) { 404 os.Setenv("ELASTIC_APM_CAPTURE_HEADERS", "false") 405 defer os.Unsetenv("ELASTIC_APM_CAPTURE_HEADERS") 406 407 tx, _, _ := apmtest.WithTransaction(func(ctx context.Context) { 408 req, err := http.NewRequest("GET", "http://testing.invalid", nil) 409 require.NoError(t, err) 410 req.Header.Set("foo", "bar") 411 respHeaders := make(http.Header) 412 respHeaders.Set("baz", "qux") 413 414 tx := apm.TransactionFromContext(ctx) 415 tx.Context.SetHTTPRequest(req) 416 tx.Context.SetHTTPResponseHeaders(respHeaders) 417 tx.Context.SetHTTPStatusCode(202) 418 }) 419 420 require.NotNil(t, tx.Context.Request) 421 require.NotNil(t, tx.Context.Response) 422 assert.Nil(t, tx.Context.Request.Headers) 423 assert.Nil(t, tx.Context.Response.Headers) 424 } 425 426 func TestServiceNodeNameEnvSpecified(t *testing.T) { 427 _, _, service, _ := getSubprocessMetadata(t, "ELASTIC_APM_SERVICE_NODE_NAME=foo_bar") 428 assert.Equal(t, "foo_bar", service.Node.ConfiguredName) 429 } 430 431 func TestUseElasticTraceparentHeader(t *testing.T) { 432 t.Run("default", func(t *testing.T) { testUseElasticTraceparentHeader(t, "", true) }) 433 t.Run("false", func(t *testing.T) { testUseElasticTraceparentHeader(t, "false", false) }) 434 t.Run("true", func(t *testing.T) { testUseElasticTraceparentHeader(t, "true", true) }) 435 } 436 437 func testUseElasticTraceparentHeader(t *testing.T, envValue string, expectPropagate bool) { 438 os.Setenv("ELASTIC_APM_USE_ELASTIC_TRACEPARENT_HEADER", envValue) 439 defer os.Unsetenv("ELASTIC_APM_USE_ELASTIC_TRACEPARENT_HEADER") 440 441 tracer := apmtest.NewDiscardTracer() 442 defer tracer.Close() 443 444 tx := tracer.StartTransaction("name", "type") 445 propagate := tx.ShouldPropagateLegacyHeader() 446 tx.Discard() 447 assert.Equal(t, expectPropagate, propagate) 448 }