github.com/waldiirawan/apm-agent-go/v2@v2.2.2/config.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 // import "github.com/waldiirawan/apm-agent-go/v2"
    19  
    20  import (
    21  	"fmt"
    22  	"net/url"
    23  	"os"
    24  	"path/filepath"
    25  	"regexp"
    26  	"runtime"
    27  	"strconv"
    28  	"strings"
    29  	"sync/atomic"
    30  	"time"
    31  	"unsafe"
    32  
    33  	"github.com/pkg/errors"
    34  
    35  	"github.com/waldiirawan/apm-agent-go/v2/internal/apmlog"
    36  	"github.com/waldiirawan/apm-agent-go/v2/internal/configutil"
    37  	"github.com/waldiirawan/apm-agent-go/v2/internal/wildcard"
    38  	"github.com/waldiirawan/apm-agent-go/v2/transport"
    39  )
    40  
    41  const (
    42  	envMetricsInterval                 = "ELASTIC_APM_METRICS_INTERVAL"
    43  	envMaxSpans                        = "ELASTIC_APM_TRANSACTION_MAX_SPANS"
    44  	envTransactionSampleRate           = "ELASTIC_APM_TRANSACTION_SAMPLE_RATE"
    45  	envSanitizeFieldNames              = "ELASTIC_APM_SANITIZE_FIELD_NAMES"
    46  	envCaptureHeaders                  = "ELASTIC_APM_CAPTURE_HEADERS"
    47  	envCaptureBody                     = "ELASTIC_APM_CAPTURE_BODY"
    48  	envServiceName                     = "ELASTIC_APM_SERVICE_NAME"
    49  	envServiceVersion                  = "ELASTIC_APM_SERVICE_VERSION"
    50  	envEnvironment                     = "ELASTIC_APM_ENVIRONMENT"
    51  	envSpanStackTraceMinDuration       = "ELASTIC_APM_SPAN_STACK_TRACE_MIN_DURATION"
    52  	deprecatedEnvSpanFramesMinDuration = "ELASTIC_APM_SPAN_FRAMES_MIN_DURATION"
    53  	envActive                          = "ELASTIC_APM_ACTIVE"
    54  	envRecording                       = "ELASTIC_APM_RECORDING"
    55  	envAPIRequestSize                  = "ELASTIC_APM_API_REQUEST_SIZE"
    56  	envAPIRequestTime                  = "ELASTIC_APM_API_REQUEST_TIME"
    57  	envAPIBufferSize                   = "ELASTIC_APM_API_BUFFER_SIZE"
    58  	envMetricsBufferSize               = "ELASTIC_APM_METRICS_BUFFER_SIZE"
    59  	envDisableMetrics                  = "ELASTIC_APM_DISABLE_METRICS"
    60  	envIgnoreURLs                      = "ELASTIC_APM_TRANSACTION_IGNORE_URLS"
    61  	deprecatedEnvIgnoreURLs            = "ELASTIC_APM_IGNORE_URLS"
    62  	envGlobalLabels                    = "ELASTIC_APM_GLOBAL_LABELS"
    63  	envStackTraceLimit                 = "ELASTIC_APM_STACK_TRACE_LIMIT"
    64  	envCentralConfig                   = "ELASTIC_APM_CENTRAL_CONFIG"
    65  	envBreakdownMetrics                = "ELASTIC_APM_BREAKDOWN_METRICS"
    66  	envUseElasticTraceparentHeader     = "ELASTIC_APM_USE_ELASTIC_TRACEPARENT_HEADER"
    67  	envCloudProvider                   = "ELASTIC_APM_CLOUD_PROVIDER"
    68  	envContinuationStrategy            = "ELASTIC_APM_TRACE_CONTINUATION_STRATEGY"
    69  
    70  	// span_compression (default `true`)
    71  	envSpanCompressionEnabled = "ELASTIC_APM_SPAN_COMPRESSION_ENABLED"
    72  	// span_compression_exact_match_max_duration (default `50ms`)
    73  	envSpanCompressionExactMatchMaxDuration = "ELASTIC_APM_SPAN_COMPRESSION_EXACT_MATCH_MAX_DURATION"
    74  	// span_compression_same_kind_max_duration (default `0ms`)
    75  	envSpanCompressionSameKindMaxDuration = "ELASTIC_APM_SPAN_COMPRESSION_SAME_KIND_MAX_DURATION"
    76  
    77  	// exit_span_min_duration (default `1ms`)
    78  	envExitSpanMinDuration = "ELASTIC_APM_EXIT_SPAN_MIN_DURATION"
    79  
    80  	// NOTE(axw) profiling environment variables are experimental.
    81  	// They may be removed in a future minor version without being
    82  	// considered a breaking change.
    83  	envCPUProfileInterval  = "ELASTIC_APM_CPU_PROFILE_INTERVAL"
    84  	envCPUProfileDuration  = "ELASTIC_APM_CPU_PROFILE_DURATION"
    85  	envHeapProfileInterval = "ELASTIC_APM_HEAP_PROFILE_INTERVAL"
    86  
    87  	defaultAPIRequestSize            = 750 * configutil.KByte
    88  	defaultAPIRequestTime            = 10 * time.Second
    89  	defaultAPIBufferSize             = 1 * configutil.MByte
    90  	defaultMetricsBufferSize         = 750 * configutil.KByte
    91  	defaultMetricsInterval           = 30 * time.Second
    92  	defaultMaxSpans                  = 500
    93  	defaultCaptureHeaders            = true
    94  	defaultCaptureBody               = CaptureBodyOff
    95  	defaultSpanStackTraceMinDuration = 5 * time.Millisecond
    96  	defaultStackTraceLimit           = 50
    97  	defaultContinuationStrategy      = "continue"
    98  
    99  	defaultExitSpanMinDuration = time.Millisecond
   100  
   101  	minAPIBufferSize     = 10 * configutil.KByte
   102  	maxAPIBufferSize     = 100 * configutil.MByte
   103  	minAPIRequestSize    = 1 * configutil.KByte
   104  	maxAPIRequestSize    = 5 * configutil.MByte
   105  	minMetricsBufferSize = 10 * configutil.KByte
   106  	maxMetricsBufferSize = 100 * configutil.MByte
   107  
   108  	// Span Compressions default setting values
   109  	defaultSpanCompressionEnabled               = true
   110  	defaultSpanCompressionExactMatchMaxDuration = 50 * time.Millisecond
   111  	defaultSpanCompressionSameKindMaxDuration   = 0
   112  )
   113  
   114  var (
   115  	defaultSanitizedFieldNames = configutil.ParseWildcardPatterns(strings.Join([]string{
   116  		"password",
   117  		"passwd",
   118  		"pwd",
   119  		"secret",
   120  		"*key",
   121  		"*token*",
   122  		"*session*",
   123  		"*credit*",
   124  		"*card*",
   125  		"*auth*",
   126  		"set-cookie",
   127  		"*principal*",
   128  	}, ","))
   129  )
   130  
   131  // Regular expression matching comment characters to escape in the User-Agent header value.
   132  //
   133  // See https://httpwg.org/specs/rfc7230.html#field.components
   134  var httpComment = regexp.MustCompile("[^\\t \\x21-\\x27\\x2a-\\x5b\\x5d-\\x7e\\x80-\\xff]")
   135  
   136  func initialTransport(serviceName, serviceVersion string) (transport.Transport, error) {
   137  	// User-Agent should be "apm-agent-go/<agent-version> (service-name service-version)".
   138  	service := serviceName
   139  	if serviceVersion != "" {
   140  		service += " " + httpComment.ReplaceAllString(serviceVersion, "_")
   141  	}
   142  	userAgent := fmt.Sprintf("%s (%s)", transport.DefaultUserAgent(), service)
   143  	httpTransport, err := transport.NewHTTPTransport(transport.HTTPTransportOptions{
   144  		UserAgent: userAgent,
   145  	})
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	return httpTransport, nil
   150  }
   151  
   152  func initialRequestDuration() (time.Duration, error) {
   153  	return configutil.ParseDurationEnv(envAPIRequestTime, defaultAPIRequestTime)
   154  }
   155  
   156  func initialMetricsInterval() (time.Duration, error) {
   157  	return configutil.ParseDurationEnv(envMetricsInterval, defaultMetricsInterval)
   158  }
   159  
   160  func initialMetricsBufferSize() (int, error) {
   161  	size, err := configutil.ParseSizeEnv(envMetricsBufferSize, defaultMetricsBufferSize)
   162  	if err != nil {
   163  		return 0, err
   164  	}
   165  	if size < minMetricsBufferSize || size > maxMetricsBufferSize {
   166  		return 0, errors.Errorf(
   167  			"%s must be at least %s and less than %s, got %s",
   168  			envMetricsBufferSize, minMetricsBufferSize, maxMetricsBufferSize, size,
   169  		)
   170  	}
   171  	return int(size), nil
   172  }
   173  
   174  func initialAPIBufferSize() (int, error) {
   175  	size, err := configutil.ParseSizeEnv(envAPIBufferSize, defaultAPIBufferSize)
   176  	if err != nil {
   177  		return 0, err
   178  	}
   179  	if size < minAPIBufferSize || size > maxAPIBufferSize {
   180  		return 0, errors.Errorf(
   181  			"%s must be at least %s and less than %s, got %s",
   182  			envAPIBufferSize, minAPIBufferSize, maxAPIBufferSize, size,
   183  		)
   184  	}
   185  	return int(size), nil
   186  }
   187  
   188  func initialAPIRequestSize() (int, error) {
   189  	size, err := configutil.ParseSizeEnv(envAPIRequestSize, defaultAPIRequestSize)
   190  	if err != nil {
   191  		return 0, err
   192  	}
   193  	if size < minAPIRequestSize || size > maxAPIRequestSize {
   194  		return 0, errors.Errorf(
   195  			"%s must be at least %s and less than %s, got %s",
   196  			envAPIRequestSize, minAPIRequestSize, maxAPIRequestSize, size,
   197  		)
   198  	}
   199  	return int(size), nil
   200  }
   201  
   202  func initialMaxSpans() (int, error) {
   203  	value := os.Getenv(envMaxSpans)
   204  	if value == "" {
   205  		return defaultMaxSpans, nil
   206  	}
   207  	max, err := strconv.Atoi(value)
   208  	if err != nil {
   209  		return 0, errors.Wrapf(err, "failed to parse %s", envMaxSpans)
   210  	}
   211  	return max, nil
   212  }
   213  
   214  // initialSampler returns a nil Sampler if all transactions should be sampled.
   215  func initialSampler() (Sampler, error) {
   216  	value := os.Getenv(envTransactionSampleRate)
   217  	return parseSampleRate(envTransactionSampleRate, value)
   218  }
   219  
   220  // parseSampleRate parses a numeric sampling rate in the range [0,1.0], returning a Sampler.
   221  func parseSampleRate(name, value string) (Sampler, error) {
   222  	if value == "" {
   223  		value = "1"
   224  	}
   225  	ratio, err := strconv.ParseFloat(value, 64)
   226  	if err != nil {
   227  		return nil, errors.Wrapf(err, "failed to parse %s", name)
   228  	}
   229  	if ratio < 0.0 || ratio > 1.0 {
   230  		return nil, errors.Errorf(
   231  			"invalid value for %s: %s (out of range [0,1.0])",
   232  			name, value,
   233  		)
   234  	}
   235  	return NewRatioSampler(ratio), nil
   236  }
   237  
   238  func initialSanitizedFieldNames() wildcard.Matchers {
   239  	return configutil.ParseWildcardPatternsEnv(envSanitizeFieldNames, defaultSanitizedFieldNames)
   240  }
   241  
   242  func initContinuationStrategy() (string, error) {
   243  	value := os.Getenv(envContinuationStrategy)
   244  	if value == "" {
   245  		return defaultContinuationStrategy, nil
   246  	}
   247  	return value, validateContinuationStrategy(value)
   248  }
   249  
   250  func validateContinuationStrategy(value string) error {
   251  	switch value {
   252  	case "continue", "restart", "restart_external":
   253  		return nil
   254  	default:
   255  		return fmt.Errorf("unknown continuation strategy: %s", value)
   256  	}
   257  }
   258  
   259  func initialCaptureHeaders() (bool, error) {
   260  	return configutil.ParseBoolEnv(envCaptureHeaders, defaultCaptureHeaders)
   261  }
   262  
   263  func initialCaptureBody() (CaptureBodyMode, error) {
   264  	value := os.Getenv(envCaptureBody)
   265  	if value == "" {
   266  		return defaultCaptureBody, nil
   267  	}
   268  	return parseCaptureBody(envCaptureBody, value)
   269  }
   270  
   271  func parseCaptureBody(name, value string) (CaptureBodyMode, error) {
   272  	switch strings.TrimSpace(strings.ToLower(value)) {
   273  	case "all":
   274  		return CaptureBodyAll, nil
   275  	case "errors":
   276  		return CaptureBodyErrors, nil
   277  	case "transactions":
   278  		return CaptureBodyTransactions, nil
   279  	case "off":
   280  		return CaptureBodyOff, nil
   281  	}
   282  	return -1, errors.Errorf("invalid %s value %q", name, value)
   283  }
   284  
   285  func initialService() (name, version, environment string) {
   286  	name = os.Getenv(envServiceName)
   287  	version = os.Getenv(envServiceVersion)
   288  	environment = os.Getenv(envEnvironment)
   289  	if name == "" {
   290  		name = filepath.Base(os.Args[0])
   291  		if runtime.GOOS == "windows" {
   292  			name = strings.TrimSuffix(name, filepath.Ext(name))
   293  		}
   294  	}
   295  	name = sanitizeServiceName(name)
   296  	return name, version, environment
   297  }
   298  
   299  func initialSpanStackTraceMinDuration() (time.Duration, error) {
   300  	if v, err := configutil.ParseDurationEnv(envSpanStackTraceMinDuration, defaultSpanStackTraceMinDuration); err != nil || v != defaultSpanStackTraceMinDuration {
   301  		// if envSpanStackTraceMinDuration was provided ignore the deprecated option
   302  		return v, err
   303  	}
   304  
   305  	v, err := configutil.ParseDurationEnv(deprecatedEnvSpanFramesMinDuration, defaultSpanStackTraceMinDuration)
   306  	if err != nil {
   307  		return v, err
   308  	}
   309  
   310  	// The meaning of the value was changed.
   311  	// convert the old value in span_stack_trace_min_duration
   312  	if v == 0 {
   313  		return -1, nil
   314  	}
   315  	if v == -1 {
   316  		return 0, nil
   317  	}
   318  
   319  	return v, nil
   320  }
   321  
   322  func initialActive() (bool, error) {
   323  	return configutil.ParseBoolEnv(envActive, true)
   324  }
   325  
   326  func initialRecording() (bool, error) {
   327  	return configutil.ParseBoolEnv(envRecording, true)
   328  }
   329  
   330  func initialDisabledMetrics() wildcard.Matchers {
   331  	return configutil.ParseWildcardPatternsEnv(envDisableMetrics, nil)
   332  }
   333  
   334  func initialIgnoreTransactionURLs() wildcard.Matchers {
   335  	matchers := configutil.ParseWildcardPatternsEnv(envIgnoreURLs, nil)
   336  	if len(matchers) == 0 {
   337  		matchers = configutil.ParseWildcardPatternsEnv(deprecatedEnvIgnoreURLs, nil)
   338  	}
   339  	return matchers
   340  }
   341  
   342  func initialStackTraceLimit() (int, error) {
   343  	value := os.Getenv(envStackTraceLimit)
   344  	if value == "" {
   345  		return defaultStackTraceLimit, nil
   346  	}
   347  	limit, err := strconv.Atoi(value)
   348  	if err != nil {
   349  		return 0, errors.Wrapf(err, "failed to parse %s", envStackTraceLimit)
   350  	}
   351  	return limit, nil
   352  }
   353  
   354  func initialCentralConfigEnabled() (bool, error) {
   355  	return configutil.ParseBoolEnv(envCentralConfig, true)
   356  }
   357  
   358  func initialBreakdownMetricsEnabled() (bool, error) {
   359  	return configutil.ParseBoolEnv(envBreakdownMetrics, true)
   360  }
   361  
   362  func initialUseElasticTraceparentHeader() (bool, error) {
   363  	return configutil.ParseBoolEnv(envUseElasticTraceparentHeader, true)
   364  }
   365  
   366  func initialSpanCompressionEnabled() (bool, error) {
   367  	return configutil.ParseBoolEnv(envSpanCompressionEnabled,
   368  		defaultSpanCompressionEnabled,
   369  	)
   370  }
   371  
   372  func initialSpanCompressionExactMatchMaxDuration() (time.Duration, error) {
   373  	return configutil.ParseDurationEnv(
   374  		envSpanCompressionExactMatchMaxDuration,
   375  		defaultSpanCompressionExactMatchMaxDuration,
   376  	)
   377  }
   378  
   379  func initialSpanCompressionSameKindMaxDuration() (time.Duration, error) {
   380  	return configutil.ParseDurationEnv(
   381  		envSpanCompressionSameKindMaxDuration,
   382  		defaultSpanCompressionSameKindMaxDuration,
   383  	)
   384  }
   385  
   386  func initialCPUProfileIntervalDuration() (time.Duration, time.Duration, error) {
   387  	interval, err := configutil.ParseDurationEnv(envCPUProfileInterval, 0)
   388  	if err != nil || interval <= 0 {
   389  		return 0, 0, err
   390  	}
   391  	duration, err := configutil.ParseDurationEnv(envCPUProfileDuration, 0)
   392  	if err != nil || duration <= 0 {
   393  		return 0, 0, err
   394  	}
   395  	return interval, duration, nil
   396  }
   397  
   398  func initialHeapProfileInterval() (time.Duration, error) {
   399  	return configutil.ParseDurationEnv(envHeapProfileInterval, 0)
   400  }
   401  
   402  func initialExitSpanMinDuration() (time.Duration, error) {
   403  	return configutil.ParseDurationEnvOptions(
   404  		envExitSpanMinDuration, defaultExitSpanMinDuration,
   405  		configutil.DurationOptions{MinimumDurationUnit: time.Microsecond},
   406  	)
   407  }
   408  
   409  // updateRemoteConfig updates t and cfg with changes held in "attrs", and reverts to local
   410  // config for config attributes that have been removed (exist in old but not in attrs).
   411  //
   412  // On return from updateRemoteConfig, unapplied config will have been removed from attrs.
   413  func (t *Tracer) updateRemoteConfig(logger Logger, old, attrs map[string]string) {
   414  	warningf := func(string, ...interface{}) {}
   415  	debugf := func(string, ...interface{}) {}
   416  	errorf := func(string, ...interface{}) {}
   417  	if logger != nil {
   418  		warningf = logger.Warningf
   419  		debugf = logger.Debugf
   420  		errorf = logger.Errorf
   421  	}
   422  	envName := func(k string) string {
   423  		return "ELASTIC_APM_" + strings.ToUpper(k)
   424  	}
   425  
   426  	var updates []func(cfg *instrumentationConfig)
   427  	for k, v := range attrs {
   428  		if oldv, ok := old[k]; ok && oldv == v {
   429  			continue
   430  		}
   431  		switch envName(k) {
   432  		case envCaptureBody:
   433  			value, err := parseCaptureBody(k, v)
   434  			if err != nil {
   435  				errorf("central config failure: %s", err)
   436  				delete(attrs, k)
   437  				continue
   438  			} else {
   439  				updates = append(updates, func(cfg *instrumentationConfig) {
   440  					cfg.captureBody = value
   441  				})
   442  			}
   443  		case envMaxSpans:
   444  			value, err := strconv.Atoi(v)
   445  			if err != nil {
   446  				errorf("central config failure: failed to parse %s: %s", k, err)
   447  				delete(attrs, k)
   448  				continue
   449  			} else {
   450  				updates = append(updates, func(cfg *instrumentationConfig) {
   451  					cfg.maxSpans = value
   452  				})
   453  			}
   454  		case envExitSpanMinDuration:
   455  			duration, err := configutil.ParseDurationOptions(v, configutil.DurationOptions{
   456  				MinimumDurationUnit: time.Microsecond,
   457  			})
   458  			if err != nil {
   459  				errorf("central config failure: failed to parse %s: %s", k, err)
   460  				delete(attrs, k)
   461  				continue
   462  			}
   463  			updates = append(updates, func(cfg *instrumentationConfig) {
   464  				cfg.exitSpanMinDuration = duration
   465  			})
   466  		case envIgnoreURLs:
   467  			matchers := configutil.ParseWildcardPatterns(v)
   468  			updates = append(updates, func(cfg *instrumentationConfig) {
   469  				cfg.ignoreTransactionURLs = matchers
   470  			})
   471  		case envRecording:
   472  			recording, err := strconv.ParseBool(v)
   473  			if err != nil {
   474  				errorf("central config failure: failed to parse %s: %s", k, err)
   475  				delete(attrs, k)
   476  				continue
   477  			} else {
   478  				updates = append(updates, func(cfg *instrumentationConfig) {
   479  					cfg.recording = recording
   480  				})
   481  			}
   482  		case envSanitizeFieldNames:
   483  			matchers := configutil.ParseWildcardPatterns(v)
   484  			updates = append(updates, func(cfg *instrumentationConfig) {
   485  				cfg.sanitizedFieldNames = matchers
   486  			})
   487  		case envContinuationStrategy:
   488  			if err := validateContinuationStrategy(v); err != nil {
   489  				errorf("central config failure: failed to parse %s: %s", k, err)
   490  				delete(attrs, k)
   491  				continue
   492  			}
   493  			updates = append(updates, func(cfg *instrumentationConfig) {
   494  				cfg.continuationStrategy = v
   495  			})
   496  		case envSpanStackTraceMinDuration:
   497  			duration, err := configutil.ParseDuration(v)
   498  			if err != nil {
   499  				errorf("central config failure: failed to parse %s: %s", k, err)
   500  				delete(attrs, k)
   501  				continue
   502  			} else {
   503  				updates = append(updates, func(cfg *instrumentationConfig) {
   504  					cfg.spanStackTraceMinDuration = duration
   505  				})
   506  			}
   507  		case envStackTraceLimit:
   508  			limit, err := strconv.Atoi(v)
   509  			if err != nil {
   510  				errorf("central config failure: failed to parse %s: %s", k, err)
   511  				delete(attrs, k)
   512  				continue
   513  			} else {
   514  				updates = append(updates, func(cfg *instrumentationConfig) {
   515  					cfg.stackTraceLimit = limit
   516  				})
   517  			}
   518  		case envTransactionSampleRate:
   519  			sampler, err := parseSampleRate(k, v)
   520  			if err != nil {
   521  				errorf("central config failure: %s", err)
   522  				delete(attrs, k)
   523  				continue
   524  			} else {
   525  				updates = append(updates, func(cfg *instrumentationConfig) {
   526  					cfg.sampler = sampler
   527  				})
   528  			}
   529  		case apmlog.EnvLogLevel:
   530  			level, err := apmlog.ParseLogLevel(v)
   531  			if err != nil {
   532  				errorf("central config failure: %s", err)
   533  				delete(attrs, k)
   534  				continue
   535  			}
   536  			if dl := apmlog.DefaultLogger(); dl != nil && dl == logger {
   537  				updates = append(updates, func(*instrumentationConfig) {
   538  					dl.SetLevel(level)
   539  				})
   540  			} else {
   541  				warningf("central config ignored: %s set to %s, but custom logger in use", k, v)
   542  				delete(attrs, k)
   543  				continue
   544  			}
   545  		case envSpanCompressionEnabled:
   546  			val, err := strconv.ParseBool(v)
   547  			if err != nil {
   548  				errorf("central config failure: failed to parse %s: %s", k, err)
   549  				delete(attrs, k)
   550  				continue
   551  			}
   552  			updates = append(updates, func(cfg *instrumentationConfig) {
   553  				cfg.compressionOptions.enabled = val
   554  			})
   555  		case envSpanCompressionExactMatchMaxDuration:
   556  			duration, err := configutil.ParseDuration(v)
   557  			if err != nil {
   558  				errorf("central config failure: failed to parse %s: %s", k, err)
   559  				delete(attrs, k)
   560  				continue
   561  			}
   562  			updates = append(updates, func(cfg *instrumentationConfig) {
   563  				cfg.compressionOptions.exactMatchMaxDuration = duration
   564  			})
   565  		case envSpanCompressionSameKindMaxDuration:
   566  			duration, err := configutil.ParseDuration(v)
   567  			if err != nil {
   568  				errorf("central config failure: failed to parse %s: %s", k, err)
   569  				delete(attrs, k)
   570  				continue
   571  			}
   572  			updates = append(updates, func(cfg *instrumentationConfig) {
   573  				cfg.compressionOptions.sameKindMaxDuration = duration
   574  			})
   575  		default:
   576  			warningf("central config failure: unsupported config: %s", k)
   577  			delete(attrs, k)
   578  			continue
   579  		}
   580  		debugf("central config update: updated %s to %s", k, v)
   581  	}
   582  	for k := range old {
   583  		if _, ok := attrs[k]; ok {
   584  			continue
   585  		}
   586  		updates = append(updates, func(cfg *instrumentationConfig) {
   587  			if f, ok := cfg.local[envName(k)]; ok {
   588  				f(&cfg.instrumentationConfigValues)
   589  			}
   590  		})
   591  		debugf("central config update: reverted %s to local config", k)
   592  	}
   593  	if updates != nil {
   594  		remote := make(map[string]struct{})
   595  		for k := range attrs {
   596  			remote[envName(k)] = struct{}{}
   597  		}
   598  		t.updateInstrumentationConfig(func(cfg *instrumentationConfig) {
   599  			cfg.remote = remote
   600  			for _, update := range updates {
   601  				update(cfg)
   602  			}
   603  		})
   604  	}
   605  }
   606  
   607  // instrumentationConfig returns the current instrumentationConfig.
   608  //
   609  // The returned value is immutable.
   610  func (t *Tracer) instrumentationConfig() *instrumentationConfig {
   611  	config := atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&t.instrumentationConfigInternal)))
   612  	return (*instrumentationConfig)(config)
   613  }
   614  
   615  // setLocalInstrumentationConfig sets local transaction configuration with
   616  // the specified environment variable key.
   617  func (t *Tracer) setLocalInstrumentationConfig(envKey string, f func(cfg *instrumentationConfigValues)) {
   618  	t.updateInstrumentationConfig(func(cfg *instrumentationConfig) {
   619  		cfg.local[envKey] = f
   620  		if _, ok := cfg.remote[envKey]; !ok {
   621  			f(&cfg.instrumentationConfigValues)
   622  		}
   623  	})
   624  }
   625  
   626  func (t *Tracer) updateInstrumentationConfig(f func(cfg *instrumentationConfig)) {
   627  	for {
   628  		oldConfig := t.instrumentationConfig()
   629  		newConfig := *oldConfig
   630  		f(&newConfig)
   631  		if atomic.CompareAndSwapPointer(
   632  			(*unsafe.Pointer)(unsafe.Pointer(&t.instrumentationConfigInternal)),
   633  			unsafe.Pointer(oldConfig),
   634  			unsafe.Pointer(&newConfig),
   635  		) {
   636  			return
   637  		}
   638  	}
   639  }
   640  
   641  // IgnoredTransactionURL returns whether the given transaction URL should be ignored
   642  func (t *Tracer) IgnoredTransactionURL(url *url.URL) bool {
   643  	return t.instrumentationConfig().ignoreTransactionURLs.MatchAny(url.String())
   644  }
   645  
   646  // instrumentationConfig holds current configuration values, as well as information
   647  // required to revert from remote to local configuration.
   648  type instrumentationConfig struct {
   649  	instrumentationConfigValues
   650  
   651  	// local holds functions for setting instrumentationConfigValues to the most
   652  	// recently, locally specified configuration.
   653  	local map[string]func(*instrumentationConfigValues)
   654  
   655  	// remote holds the environment variable keys for applied remote config.
   656  	remote map[string]struct{}
   657  }
   658  
   659  // instrumentationConfigValues holds configuration that is accessible outside of the
   660  // tracer loop, for instrumentation: StartTransaction, StartSpan, CaptureError, etc.
   661  //
   662  // NOTE(axw) when adding configuration here, you must also update `newTracer` to
   663  // set the initial entry in instrumentationConfig.local, in order to properly reset
   664  // to the local value, even if the default is the zero value.
   665  type instrumentationConfigValues struct {
   666  	recording                 bool
   667  	captureBody               CaptureBodyMode
   668  	captureHeaders            bool
   669  	maxSpans                  int
   670  	sampler                   Sampler
   671  	spanStackTraceMinDuration time.Duration
   672  	exitSpanMinDuration       time.Duration
   673  	continuationStrategy      string
   674  	stackTraceLimit           int
   675  	propagateLegacyHeader     bool
   676  	sanitizedFieldNames       wildcard.Matchers
   677  	ignoreTransactionURLs     wildcard.Matchers
   678  	compressionOptions        compressionOptions
   679  }