github.com/mier85/go-sensor@v1.30.1-0.20220920111756-9bf41b3bc7e0/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/mier85/go-sensor/acceptor"
    17  	"github.com/mier85/go-sensor/autoprofile"
    18  	"github.com/mier85/go-sensor/aws"
    19  	"github.com/mier85/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  
    56  	mu    sync.RWMutex
    57  	agent agentClient
    58  }
    59  
    60  var (
    61  	sensor           *sensorS
    62  	binaryName       = filepath.Base(os.Args[0])
    63  	processStartedAt = time.Now()
    64  )
    65  
    66  func newSensor(options *Options) *sensorS {
    67  	options.setDefaults()
    68  
    69  	s := &sensorS{
    70  		options:     options,
    71  		serviceName: options.Service,
    72  	}
    73  	if s.serviceName == "" {
    74  		s.serviceName = binaryName
    75  	}
    76  
    77  	s.setLogger(defaultLogger)
    78  
    79  	// override service name with an env value if set
    80  	if name, ok := os.LookupEnv("INSTANA_SERVICE_NAME"); ok {
    81  		s.serviceName = name
    82  	}
    83  
    84  	// handle the legacy (instana.Options).LogLevel value if we use logger.Logger to log
    85  	if l, ok := s.logger.(*logger.Logger); ok {
    86  
    87  		_, isInstanaLogLevelSet := os.LookupEnv("INSTANA_LOG_LEVEL")
    88  
    89  		if !isInstanaLogLevelSet {
    90  			setLogLevel(l, options.LogLevel)
    91  		}
    92  	}
    93  
    94  	var agent agentClient
    95  	if agentEndpoint := os.Getenv("INSTANA_ENDPOINT_URL"); agentEndpoint != "" {
    96  		s.logger.Debug("INSTANA_ENDPOINT_URL= is set, switching to the serverless mode")
    97  
    98  		timeout, err := parseInstanaTimeout(os.Getenv("INSTANA_TIMEOUT"))
    99  		if err != nil {
   100  			s.logger.Warn("malformed INSTANA_TIMEOUT value, falling back to the default one: ", err)
   101  			timeout = defaultServerlessTimeout
   102  		}
   103  
   104  		client, err := acceptor.NewHTTPClient(timeout)
   105  		if err != nil {
   106  			if err == acceptor.ErrMalformedProxyURL {
   107  				s.logger.Warn(err)
   108  			} else {
   109  				s.logger.Error("failed to initialize acceptor HTTP client, falling back to the default one: ", err)
   110  				client = http.DefaultClient
   111  			}
   112  		}
   113  
   114  		agent = newServerlessAgent(s.serviceName, agentEndpoint, os.Getenv("INSTANA_AGENT_KEY"), client, s.logger)
   115  	}
   116  
   117  	if agent == nil {
   118  		agent = newAgent(s.serviceName, s.options.AgentHost, s.options.AgentPort, s.logger)
   119  	}
   120  
   121  	s.setAgent(agent)
   122  	s.meter = newMeter(s.logger)
   123  
   124  	return s
   125  }
   126  
   127  func (r *sensorS) setLogger(l LeveledLogger) {
   128  	r.logger = l
   129  
   130  	if agent, ok := r.Agent().(*agentS); ok && agent != nil {
   131  		agent.setLogger(r.logger)
   132  	}
   133  }
   134  
   135  func (r *sensorS) setAgent(agent agentClient) {
   136  	r.mu.Lock()
   137  	defer r.mu.Unlock()
   138  
   139  	r.agent = agent
   140  }
   141  
   142  // Agent returns the agent client used by the global sensor. It will return a noopAgent that is never ready
   143  // until both the global sensor and its agent are initialized
   144  func (r *sensorS) Agent() agentClient {
   145  	if r == nil {
   146  		return noopAgent{}
   147  	}
   148  
   149  	r.mu.RLock()
   150  	defer r.mu.RUnlock()
   151  
   152  	if r.agent == nil {
   153  		return noopAgent{}
   154  	}
   155  
   156  	return r.agent
   157  }
   158  
   159  // InitSensor intializes the sensor (without tracing) to begin collecting
   160  // and reporting metrics.
   161  func InitSensor(options *Options) {
   162  	if sensor != nil {
   163  		return
   164  	}
   165  
   166  	if options == nil {
   167  		options = DefaultOptions()
   168  	}
   169  
   170  	sensor = newSensor(options)
   171  
   172  	// configure auto-profiling
   173  	autoprofile.SetLogger(sensor.logger)
   174  	autoprofile.SetOptions(autoprofile.Options{
   175  		IncludeProfilerFrames: options.IncludeProfilerFrames,
   176  		MaxBufferedProfiles:   options.MaxBufferedProfiles,
   177  	})
   178  
   179  	autoprofile.SetSendProfilesFunc(func(profiles []autoprofile.Profile) error {
   180  		if !sensor.Agent().Ready() {
   181  			return errors.New("sender not ready")
   182  		}
   183  
   184  		sensor.logger.Debug("sending profiles to agent")
   185  
   186  		return sensor.Agent().SendProfiles(profiles)
   187  	})
   188  
   189  	if _, ok := os.LookupEnv("INSTANA_AUTO_PROFILE"); ok || options.EnableAutoProfile {
   190  		if !options.EnableAutoProfile {
   191  			sensor.logger.Info("INSTANA_AUTO_PROFILE is set, activating AutoProfileā„¢")
   192  		}
   193  
   194  		autoprofile.Enable()
   195  	}
   196  
   197  	// start collecting metrics
   198  	go sensor.meter.Run(1 * time.Second)
   199  
   200  	sensor.logger.Debug("initialized Instana sensor v", Version)
   201  }
   202  
   203  // Ready returns whether the Instana collector is ready to collect and send data to the agent
   204  func Ready() bool {
   205  	if sensor == nil {
   206  		return false
   207  	}
   208  
   209  	return sensor.Agent().Ready()
   210  }
   211  
   212  // Flush forces Instana collector to send all buffered data to the agent. This method is intended to implement
   213  // graceful service shutdown and not recommended for intermittent use. Once Flush() is called, it's not guaranteed
   214  // that collector remains in operational state.
   215  func Flush(ctx context.Context) error {
   216  	if sensor == nil {
   217  		return nil
   218  	}
   219  
   220  	return sensor.Agent().Flush(ctx)
   221  }
   222  
   223  func newServerlessAgent(serviceName, agentEndpoint, agentKey string, client *http.Client, logger LeveledLogger) agentClient {
   224  	switch {
   225  	case os.Getenv("AWS_EXECUTION_ENV") == "AWS_ECS_FARGATE" && os.Getenv("ECS_CONTAINER_METADATA_URI") != "":
   226  		// AWS Fargate
   227  		return newFargateAgent(
   228  			serviceName,
   229  			agentEndpoint,
   230  			agentKey,
   231  			client,
   232  			aws.NewECSMetadataProvider(os.Getenv("ECS_CONTAINER_METADATA_URI"), client),
   233  			logger,
   234  		)
   235  	case strings.HasPrefix(os.Getenv("AWS_EXECUTION_ENV"), "AWS_Lambda_"):
   236  		// AWS Lambda
   237  		return newLambdaAgent(serviceName, agentEndpoint, agentKey, client, logger)
   238  	case os.Getenv("K_SERVICE") != "" && os.Getenv("K_CONFIGURATION") != "" && os.Getenv("K_REVISION") != "":
   239  		// Knative, e.g. Google Cloud Run
   240  		return newGCRAgent(serviceName, agentEndpoint, agentKey, client, logger)
   241  	default:
   242  		return nil
   243  	}
   244  }