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 }