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 18package apm_test 19 20import ( 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 "go.elastic.co/apm" 35 "go.elastic.co/apm/apmtest" 36 "go.elastic.co/apm/model" 37 "go.elastic.co/apm/transport" 38 "go.elastic.co/apm/transport/transporttest" 39) 40 41func 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_URLS", server.URL) 56 defer os.Unsetenv("ELASTIC_APM_SERVER_URLS") 57 58 httpTransport, err := transport.NewHTTPTransport() 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 75func 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 90func 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 105func 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 120func 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 135func 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 147func 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 155func 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 174func TestTracerSanitizeFieldNamesEnv(t *testing.T) { 175 testTracerSanitizeFieldNamesEnv(t, "secRet", "[REDACTED]") 176 testTracerSanitizeFieldNamesEnv(t, "nada", "top") 177} 178 179func 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 195func TestTracerServiceNameEnvSanitizationSpecified(t *testing.T) { 196 _, _, service, _ := getSubprocessMetadata(t, "ELASTIC_APM_SERVICE_NAME=foo!bar") 197 assert.Equal(t, "foo_bar", service.Name) 198} 199 200func TestTracerServiceNameEnvSanitizationExecutableName(t *testing.T) { 201 _, _, service, _ := getSubprocessMetadata(t) 202 assert.Equal(t, "apm_test", service.Name) // .test -> _test 203} 204 205func TestTracerGlobalLabelsUnspecified(t *testing.T) { 206 _, _, _, labels := getSubprocessMetadata(t) 207 assert.Equal(t, model.StringMap{}, labels) 208} 209 210func 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 215func 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 220func 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 225func 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 230func 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 237func 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 261func 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 288func TestTracerSpanFramesMinDurationEnvInvalid(t *testing.T) { 289 os.Setenv("ELASTIC_APM_SPAN_FRAMES_MIN_DURATION", "aeon") 290 defer os.Unsetenv("ELASTIC_APM_SPAN_FRAMES_MIN_DURATION") 291 292 _, err := apm.NewTracer("tracer_testing", "") 293 assert.EqualError(t, err, "failed to parse ELASTIC_APM_SPAN_FRAMES_MIN_DURATION: invalid duration aeon") 294} 295 296func TestTracerStackTraceLimitEnv(t *testing.T) { 297 os.Setenv("ELASTIC_APM_STACK_TRACE_LIMIT", "0") 298 defer os.Unsetenv("ELASTIC_APM_STACK_TRACE_LIMIT") 299 300 tracer, transport := transporttest.NewRecorderTracer() 301 defer tracer.Close() 302 303 sendSpan := func() { 304 tx := tracer.StartTransaction("name", "type") 305 s := tx.StartSpan("name", "type", nil) 306 s.Duration = time.Second 307 s.End() 308 tx.End() 309 } 310 311 sendSpan() 312 tracer.SetStackTraceLimit(2) 313 sendSpan() 314 315 tracer.Flush(nil) 316 spans := transport.Payloads().Spans 317 require.Len(t, spans, 2) 318 assert.Nil(t, spans[0].Stacktrace) 319 assert.Len(t, spans[1].Stacktrace, 2) 320} 321 322func TestTracerStackTraceLimitEnvInvalid(t *testing.T) { 323 os.Setenv("ELASTIC_APM_STACK_TRACE_LIMIT", "sky") 324 defer os.Unsetenv("ELASTIC_APM_STACK_TRACE_LIMIT") 325 326 _, err := apm.NewTracer("tracer_testing", "") 327 assert.EqualError(t, err, "failed to parse ELASTIC_APM_STACK_TRACE_LIMIT: strconv.Atoi: parsing \"sky\": invalid syntax") 328} 329 330func TestTracerActiveEnv(t *testing.T) { 331 os.Setenv("ELASTIC_APM_ACTIVE", "false") 332 defer os.Unsetenv("ELASTIC_APM_ACTIVE") 333 334 tracer, transport := transporttest.NewRecorderTracer() 335 defer tracer.Close() 336 assert.False(t, tracer.Active()) 337 338 tx := tracer.StartTransaction("name", "type") 339 tx.End() 340 341 tracer.Flush(nil) 342 assert.Zero(t, transport.Payloads()) 343} 344 345func TestTracerActiveEnvInvalid(t *testing.T) { 346 os.Setenv("ELASTIC_APM_ACTIVE", "yep") 347 defer os.Unsetenv("ELASTIC_APM_ACTIVE") 348 349 _, err := apm.NewTracer("tracer_testing", "") 350 assert.EqualError(t, err, "failed to parse ELASTIC_APM_ACTIVE: strconv.ParseBool: parsing \"yep\": invalid syntax") 351} 352 353func TestTracerEnvironmentEnv(t *testing.T) { 354 os.Setenv("ELASTIC_APM_ENVIRONMENT", "friendly") 355 defer os.Unsetenv("ELASTIC_APM_ENVIRONMENT") 356 357 tracer, transport := transporttest.NewRecorderTracer() 358 defer tracer.Close() 359 360 tracer.StartTransaction("name", "type").End() 361 tracer.Flush(nil) 362 363 _, _, service, _ := transport.Metadata() 364 assert.Equal(t, "friendly", service.Environment) 365} 366 367func TestTracerCaptureHeadersEnv(t *testing.T) { 368 os.Setenv("ELASTIC_APM_CAPTURE_HEADERS", "false") 369 defer os.Unsetenv("ELASTIC_APM_CAPTURE_HEADERS") 370 371 tx, _, _ := apmtest.WithTransaction(func(ctx context.Context) { 372 req, err := http.NewRequest("GET", "http://testing.invalid", nil) 373 require.NoError(t, err) 374 req.Header.Set("foo", "bar") 375 respHeaders := make(http.Header) 376 respHeaders.Set("baz", "qux") 377 378 tx := apm.TransactionFromContext(ctx) 379 tx.Context.SetHTTPRequest(req) 380 tx.Context.SetHTTPResponseHeaders(respHeaders) 381 tx.Context.SetHTTPStatusCode(202) 382 }) 383 384 require.NotNil(t, tx.Context.Request) 385 require.NotNil(t, tx.Context.Response) 386 assert.Nil(t, tx.Context.Request.Headers) 387 assert.Nil(t, tx.Context.Response.Headers) 388} 389