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