github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/sensor.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2016
     3  
     4  package instana
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"net/http"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/instana/go-sensor/acceptor"
    17  	"github.com/instana/go-sensor/autoprofile"
    18  	"github.com/instana/go-sensor/aws"
    19  	"github.com/instana/go-sensor/logger"
    20  )
    21  
    22  const (
    23  	// DefaultMaxBufferedSpans is the default span buffer size
    24  	DefaultMaxBufferedSpans = 1000
    25  	// DefaultForceSpanSendAt is the default max number of spans to buffer before force sending them to the agent
    26  	DefaultForceSpanSendAt = 500
    27  
    28  	defaultServerlessTimeout = 500 * time.Millisecond
    29  )
    30  
    31  type AgentClient interface {
    32  	Ready() bool
    33  	SendMetrics(data acceptor.Metrics) error
    34  	SendEvent(event *EventData) error
    35  	SendSpans(spans []Span) error
    36  	SendProfiles(profiles []autoprofile.Profile) error
    37  	Flush(context.Context) error
    38  }
    39  
    40  // zero value for sensorS.Agent()
    41  type noopAgent struct{}
    42  
    43  func (noopAgent) Ready() bool                                       { return false }
    44  func (noopAgent) SendMetrics(data acceptor.Metrics) error           { return nil }
    45  func (noopAgent) SendEvent(event *EventData) error                  { return nil }
    46  func (noopAgent) SendSpans(spans []Span) error                      { return nil }
    47  func (noopAgent) SendProfiles(profiles []autoprofile.Profile) error { return nil }
    48  func (noopAgent) Flush(context.Context) error                       { return nil }
    49  
    50  type sensorS struct {
    51  	meter       *meterS
    52  	logger      LeveledLogger
    53  	options     *Options
    54  	serviceName string
    55  	binaryName  string
    56  
    57  	mu    sync.RWMutex
    58  	agent AgentClient
    59  }
    60  
    61  var (
    62  	sensor           *sensorS
    63  	muSensor         sync.Mutex
    64  	binaryName       = filepath.Base(os.Args[0])
    65  	processStartedAt = time.Now()
    66  	C                TracerLogger
    67  )
    68  
    69  func init() {
    70  	C = newNoopCollector()
    71  }
    72  
    73  func newSensor(options *Options) *sensorS {
    74  	options.setDefaults()
    75  
    76  	s := &sensorS{
    77  		options:     options,
    78  		serviceName: options.Service,
    79  		binaryName:  binaryName,
    80  	}
    81  
    82  	s.setLogger(defaultLogger)
    83  
    84  	// override service name with an env value if set
    85  	if name, ok := os.LookupEnv("INSTANA_SERVICE_NAME"); ok && strings.TrimSpace(name) != "" {
    86  		s.serviceName = name
    87  	}
    88  
    89  	// handle the legacy (instana.Options).LogLevel value if we use logger.Logger to log
    90  	if l, ok := s.logger.(*logger.Logger); ok {
    91  
    92  		_, isInstanaLogLevelSet := os.LookupEnv("INSTANA_LOG_LEVEL")
    93  
    94  		if !isInstanaLogLevelSet {
    95  			setLogLevel(l, options.LogLevel)
    96  		}
    97  	}
    98  
    99  	var agent AgentClient
   100  
   101  	if options.AgentClient != nil {
   102  		agent = options.AgentClient
   103  	}
   104  
   105  	if agentEndpoint := os.Getenv("INSTANA_ENDPOINT_URL"); agentEndpoint != "" && agent == nil {
   106  		s.logger.Debug("INSTANA_ENDPOINT_URL= is set, switching to the serverless mode")
   107  
   108  		timeout, err := parseInstanaTimeout(os.Getenv("INSTANA_TIMEOUT"))
   109  		if err != nil {
   110  			s.logger.Warn("malformed INSTANA_TIMEOUT value, falling back to the default one: ", err)
   111  			timeout = defaultServerlessTimeout
   112  		}
   113  
   114  		client, err := acceptor.NewHTTPClient(timeout)
   115  		if err != nil {
   116  			if err == acceptor.ErrMalformedProxyURL {
   117  				s.logger.Warn(err)
   118  			} else {
   119  				s.logger.Error("failed to initialize acceptor HTTP client, falling back to the default one: ", err)
   120  				client = http.DefaultClient
   121  			}
   122  		}
   123  
   124  		agent = newServerlessAgent(s.serviceOrBinaryName(), agentEndpoint, os.Getenv("INSTANA_AGENT_KEY"), client, s.logger)
   125  	}
   126  
   127  	if agent == nil {
   128  		agent = newAgent(s.serviceOrBinaryName(), s.options.AgentHost, s.options.AgentPort, s.logger)
   129  	}
   130  
   131  	s.setAgent(agent)
   132  	s.meter = newMeter(s.logger)
   133  
   134  	return s
   135  }
   136  
   137  func (r *sensorS) setLogger(l LeveledLogger) {
   138  	r.logger = l
   139  
   140  	if agent, ok := r.Agent().(*agentS); ok && agent != nil {
   141  		agent.setLogger(r.logger)
   142  	}
   143  }
   144  
   145  func (r *sensorS) setAgent(agent AgentClient) {
   146  	r.mu.Lock()
   147  	defer r.mu.Unlock()
   148  
   149  	r.agent = agent
   150  }
   151  
   152  // Agent returns the agent client used by the global sensor. It will return a noopAgent that is never ready
   153  // until both the global sensor and its agent are initialized
   154  func (r *sensorS) Agent() AgentClient {
   155  	if r == nil {
   156  		return noopAgent{}
   157  	}
   158  
   159  	r.mu.RLock()
   160  	defer r.mu.RUnlock()
   161  
   162  	if r.agent == nil {
   163  		return noopAgent{}
   164  	}
   165  
   166  	return r.agent
   167  }
   168  
   169  func (r *sensorS) serviceOrBinaryName() string {
   170  	if r == nil {
   171  		return ""
   172  	}
   173  	if r.serviceName != "" {
   174  		return r.serviceName
   175  	}
   176  	return r.binaryName
   177  }
   178  
   179  // InitSensor initializes the sensor (without tracing) to begin collecting
   180  // and reporting metrics.
   181  //
   182  // Deprecated: Use [StartMetrics] instead.
   183  func InitSensor(options *Options) {
   184  	if sensor != nil {
   185  		return
   186  	}
   187  
   188  	if options == nil {
   189  		options = DefaultOptions()
   190  	}
   191  
   192  	muSensor.Lock()
   193  	sensor = newSensor(options)
   194  	muSensor.Unlock()
   195  
   196  	// configure auto-profiling
   197  	autoprofile.SetLogger(sensor.logger)
   198  	autoprofile.SetOptions(autoprofile.Options{
   199  		IncludeProfilerFrames: options.IncludeProfilerFrames,
   200  		MaxBufferedProfiles:   options.MaxBufferedProfiles,
   201  	})
   202  
   203  	autoprofile.SetSendProfilesFunc(func(profiles []autoprofile.Profile) error {
   204  		if !sensor.Agent().Ready() {
   205  			return errors.New("sender not ready")
   206  		}
   207  
   208  		sensor.logger.Debug("sending profiles to agent")
   209  
   210  		return sensor.Agent().SendProfiles(profiles)
   211  	})
   212  
   213  	if _, ok := os.LookupEnv("INSTANA_AUTO_PROFILE"); ok || options.EnableAutoProfile {
   214  		if !options.EnableAutoProfile {
   215  			sensor.logger.Info("INSTANA_AUTO_PROFILE is set, activating AutoProfileā„¢")
   216  		}
   217  
   218  		autoprofile.Enable()
   219  	}
   220  
   221  	// start collecting metrics
   222  	go sensor.meter.Run(1 * time.Second)
   223  
   224  	sensor.logger.Debug("initialized Instana sensor v", Version)
   225  }
   226  
   227  // StartMetrics initializes the communication with the agent. Then it starts collecting and reporting metrics to the agent.
   228  // Calling StartMetrics multiple times has no effect and the function will simply return, and provided options will not
   229  // be reapplied.
   230  func StartMetrics(options *Options) {
   231  	InitSensor(options)
   232  }
   233  
   234  // Ready returns whether the Instana collector is ready to collect and send data to the agent
   235  func Ready() bool {
   236  	if sensor == nil {
   237  		return false
   238  	}
   239  
   240  	return sensor.Agent().Ready()
   241  }
   242  
   243  // Flush forces Instana collector to send all buffered data to the agent. This method is intended to implement
   244  // graceful service shutdown and not recommended for intermittent use. Once Flush() is called, it's not guaranteed
   245  // that collector remains in operational state.
   246  func Flush(ctx context.Context) error {
   247  	if sensor == nil {
   248  		return nil
   249  	}
   250  
   251  	return sensor.Agent().Flush(ctx)
   252  }
   253  
   254  // ShutdownSensor cleans up the internal global sensor reference. The next time that instana.InitSensor is called,
   255  // directly or indirectly, the internal sensor will be reinitialized.
   256  func ShutdownSensor() {
   257  	muSensor.Lock()
   258  	if sensor != nil {
   259  		sensor = nil
   260  	}
   261  	muSensor.Unlock()
   262  }
   263  
   264  func newServerlessAgent(serviceName, agentEndpoint, agentKey string, client *http.Client, logger LeveledLogger) AgentClient {
   265  	switch {
   266  	case os.Getenv("AWS_EXECUTION_ENV") == "AWS_ECS_FARGATE" && os.Getenv("ECS_CONTAINER_METADATA_URI") != "":
   267  		// AWS Fargate
   268  		return newFargateAgent(
   269  			serviceName,
   270  			agentEndpoint,
   271  			agentKey,
   272  			client,
   273  			aws.NewECSMetadataProvider(os.Getenv("ECS_CONTAINER_METADATA_URI"), client),
   274  			logger,
   275  		)
   276  	case strings.HasPrefix(os.Getenv("AWS_EXECUTION_ENV"), "AWS_Lambda_"):
   277  		// AWS Lambda
   278  		return newLambdaAgent(serviceName, agentEndpoint, agentKey, client, logger)
   279  	case os.Getenv("K_SERVICE") != "" && os.Getenv("K_CONFIGURATION") != "" && os.Getenv("K_REVISION") != "":
   280  		// Knative, e.g. Google Cloud Run
   281  		return newGCRAgent(serviceName, agentEndpoint, agentKey, client, logger)
   282  	case os.Getenv("FUNCTIONS_WORKER_RUNTIME") == azureCustomRuntime:
   283  		return newAzureAgent(agentEndpoint, agentKey, client, logger)
   284  	default:
   285  		return nil
   286  	}
   287  }