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  }