github.com/newrelic/go-agent@v3.26.0+incompatible/internal_config.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package newrelic
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"net/http"
    10  	"os"
    11  	"strings"
    12  
    13  	"github.com/newrelic/go-agent/internal"
    14  	"github.com/newrelic/go-agent/internal/logger"
    15  	"github.com/newrelic/go-agent/internal/utilization"
    16  )
    17  
    18  func copyDestConfig(c AttributeDestinationConfig) AttributeDestinationConfig {
    19  	cp := c
    20  	if nil != c.Include {
    21  		cp.Include = make([]string, len(c.Include))
    22  		copy(cp.Include, c.Include)
    23  	}
    24  	if nil != c.Exclude {
    25  		cp.Exclude = make([]string, len(c.Exclude))
    26  		copy(cp.Exclude, c.Exclude)
    27  	}
    28  	return cp
    29  }
    30  
    31  func copyConfigReferenceFields(cfg Config) Config {
    32  	cp := cfg
    33  	if nil != cfg.Labels {
    34  		cp.Labels = make(map[string]string, len(cfg.Labels))
    35  		for key, val := range cfg.Labels {
    36  			cp.Labels[key] = val
    37  		}
    38  	}
    39  	if nil != cfg.ErrorCollector.IgnoreStatusCodes {
    40  		ignored := make([]int, len(cfg.ErrorCollector.IgnoreStatusCodes))
    41  		copy(ignored, cfg.ErrorCollector.IgnoreStatusCodes)
    42  		cp.ErrorCollector.IgnoreStatusCodes = ignored
    43  	}
    44  
    45  	cp.Attributes = copyDestConfig(cfg.Attributes)
    46  	cp.ErrorCollector.Attributes = copyDestConfig(cfg.ErrorCollector.Attributes)
    47  	cp.TransactionEvents.Attributes = copyDestConfig(cfg.TransactionEvents.Attributes)
    48  	cp.TransactionTracer.Attributes = copyDestConfig(cfg.TransactionTracer.Attributes)
    49  	cp.BrowserMonitoring.Attributes = copyDestConfig(cfg.BrowserMonitoring.Attributes)
    50  	cp.SpanEvents.Attributes = copyDestConfig(cfg.SpanEvents.Attributes)
    51  	cp.TransactionTracer.Segments.Attributes = copyDestConfig(cfg.TransactionTracer.Segments.Attributes)
    52  
    53  	return cp
    54  }
    55  
    56  func transportSetting(t http.RoundTripper) interface{} {
    57  	if nil == t {
    58  		return nil
    59  	}
    60  	return fmt.Sprintf("%T", t)
    61  }
    62  
    63  func loggerSetting(lg Logger) interface{} {
    64  	if nil == lg {
    65  		return nil
    66  	}
    67  	if _, ok := lg.(logger.ShimLogger); ok {
    68  		return nil
    69  	}
    70  	return fmt.Sprintf("%T", lg)
    71  }
    72  
    73  const (
    74  	// https://source.datanerd.us/agents/agent-specs/blob/master/Custom-Host-Names.md
    75  	hostByteLimit = 255
    76  )
    77  
    78  type settings Config
    79  
    80  func (s settings) MarshalJSON() ([]byte, error) {
    81  	c := Config(s)
    82  	transport := c.Transport
    83  	c.Transport = nil
    84  	l := c.Logger
    85  	c.Logger = nil
    86  
    87  	js, err := json.Marshal(c)
    88  	if nil != err {
    89  		return nil, err
    90  	}
    91  	fields := make(map[string]interface{})
    92  	err = json.Unmarshal(js, &fields)
    93  	if nil != err {
    94  		return nil, err
    95  	}
    96  	// The License field is not simply ignored by adding the `json:"-"` tag
    97  	// to it since we want to allow consumers to populate Config from JSON.
    98  	delete(fields, `License`)
    99  	fields[`Transport`] = transportSetting(transport)
   100  	fields[`Logger`] = loggerSetting(l)
   101  
   102  	// Browser monitoring support.
   103  	if c.BrowserMonitoring.Enabled {
   104  		fields[`browser_monitoring.loader`] = "rum"
   105  	}
   106  
   107  	return json.Marshal(fields)
   108  }
   109  
   110  func configConnectJSONInternal(c Config, pid int, util *utilization.Data, e internal.Environment, version string, securityPolicies *internal.SecurityPolicies, metadata map[string]string) ([]byte, error) {
   111  	return json.Marshal([]interface{}{struct {
   112  		Pid              int                         `json:"pid"`
   113  		Language         string                      `json:"language"`
   114  		Version          string                      `json:"agent_version"`
   115  		Host             string                      `json:"host"`
   116  		HostDisplayName  string                      `json:"display_host,omitempty"`
   117  		Settings         interface{}                 `json:"settings"`
   118  		AppName          []string                    `json:"app_name"`
   119  		HighSecurity     bool                        `json:"high_security"`
   120  		Labels           internal.Labels             `json:"labels,omitempty"`
   121  		Environment      internal.Environment        `json:"environment"`
   122  		Identifier       string                      `json:"identifier"`
   123  		Util             *utilization.Data           `json:"utilization"`
   124  		SecurityPolicies *internal.SecurityPolicies  `json:"security_policies,omitempty"`
   125  		Metadata         map[string]string           `json:"metadata"`
   126  		EventData        internal.EventHarvestConfig `json:"event_harvest_config"`
   127  	}{
   128  		Pid:             pid,
   129  		Language:        internal.AgentLanguage,
   130  		Version:         version,
   131  		Host:            internal.StringLengthByteLimit(util.Hostname, hostByteLimit),
   132  		HostDisplayName: internal.StringLengthByteLimit(c.HostDisplayName, hostByteLimit),
   133  		Settings:        (settings)(c),
   134  		AppName:         strings.Split(c.AppName, ";"),
   135  		HighSecurity:    c.HighSecurity,
   136  		Labels:          c.Labels,
   137  		Environment:     e,
   138  		// This identifier field is provided to avoid:
   139  		// https://newrelic.atlassian.net/browse/DSCORE-778
   140  		//
   141  		// This identifier is used by the collector to look up the real
   142  		// agent. If an identifier isn't provided, the collector will
   143  		// create its own based on the first appname, which prevents a
   144  		// single daemon from connecting "a;b" and "a;c" at the same
   145  		// time.
   146  		//
   147  		// Providing the identifier below works around this issue and
   148  		// allows users more flexibility in using application rollups.
   149  		Identifier:       c.AppName,
   150  		Util:             util,
   151  		SecurityPolicies: securityPolicies,
   152  		Metadata:         metadata,
   153  		EventData:        internal.DefaultEventHarvestConfig(c),
   154  	}})
   155  }
   156  
   157  const (
   158  	// https://source.datanerd.us/agents/agent-specs/blob/master/Connect-LEGACY.md#metadata-hash
   159  	metadataPrefix = "NEW_RELIC_METADATA_"
   160  )
   161  
   162  func gatherMetadata(environ func() []string) map[string]string {
   163  	metadata := make(map[string]string)
   164  	env := environ()
   165  	for _, pair := range env {
   166  		if strings.HasPrefix(pair, metadataPrefix) {
   167  			idx := strings.Index(pair, "=")
   168  			if idx >= 0 {
   169  				metadata[pair[0:idx]] = pair[idx+1:]
   170  			}
   171  		}
   172  	}
   173  	return metadata
   174  }
   175  
   176  // config allows CreateConnectJSON to be a method on a non-public type.
   177  type config struct{ Config }
   178  
   179  func (c config) CreateConnectJSON(securityPolicies *internal.SecurityPolicies) ([]byte, error) {
   180  	env := internal.NewEnvironment()
   181  	util := utilization.Gather(utilization.Config{
   182  		DetectAWS:         c.Utilization.DetectAWS,
   183  		DetectAzure:       c.Utilization.DetectAzure,
   184  		DetectPCF:         c.Utilization.DetectPCF,
   185  		DetectGCP:         c.Utilization.DetectGCP,
   186  		DetectDocker:      c.Utilization.DetectDocker,
   187  		DetectKubernetes:  c.Utilization.DetectKubernetes,
   188  		LogicalProcessors: c.Utilization.LogicalProcessors,
   189  		TotalRAMMIB:       c.Utilization.TotalRAMMIB,
   190  		BillingHostname:   c.Utilization.BillingHostname,
   191  	}, c.Logger)
   192  	return configConnectJSONInternal(c.Config, os.Getpid(), util, env, Version, securityPolicies, gatherMetadata(os.Environ))
   193  }