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 }