github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/lambda_agent.go (about) 1 // (c) Copyright IBM Corp. 2021 2 // (c) Copyright Instana Inc. 2020 3 4 package instana 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net/http" 14 "os" 15 "strconv" 16 "sync" 17 "time" 18 19 "github.com/instana/go-sensor/acceptor" 20 "github.com/instana/go-sensor/autoprofile" 21 ) 22 23 const awsLambdaAgentFlushPeriod = 2 * time.Second 24 25 type lambdaAgent struct { 26 Endpoint string 27 Key string 28 PID int 29 30 snapshot serverlessSnapshot 31 32 mu sync.Mutex 33 spanQueue []Span 34 35 client *http.Client 36 logger LeveledLogger 37 } 38 39 func newLambdaAgent( 40 serviceName, acceptorEndpoint, agentKey string, 41 client *http.Client, 42 logger LeveledLogger, 43 ) *lambdaAgent { 44 if logger == nil { 45 logger = defaultLogger 46 } 47 48 if client == nil { 49 client = http.DefaultClient 50 } 51 52 logger.Debug("initializing aws lambda agent") 53 54 agent := &lambdaAgent{ 55 Endpoint: acceptorEndpoint, 56 Key: agentKey, 57 PID: os.Getpid(), 58 client: client, 59 logger: logger, 60 } 61 62 go func() { 63 t := time.NewTicker(awsLambdaAgentFlushPeriod) 64 defer t.Stop() 65 66 for range t.C { 67 if err := agent.Flush(context.Background()); err != nil { 68 agent.logger.Error("failed to post collected data: ", err) 69 } 70 } 71 }() 72 73 return agent 74 } 75 76 func (a *lambdaAgent) Ready() bool { return true } 77 78 func (a *lambdaAgent) SendMetrics(data acceptor.Metrics) error { return nil } 79 80 func (a *lambdaAgent) SendEvent(event *EventData) error { return nil } 81 82 func (a *lambdaAgent) SendSpans(spans []Span) error { 83 a.enqueueSpans(spans) 84 return nil 85 } 86 87 func (a *lambdaAgent) SendProfiles(profiles []autoprofile.Profile) error { return nil } 88 89 func (a *lambdaAgent) Flush(ctx context.Context) error { 90 snapshot := a.collectSnapshot(a.spanQueue) 91 92 if snapshot.EntityID == "" { 93 return ErrAgentNotReady 94 } 95 96 from := newServerlessAgentFromS(snapshot.EntityID, "aws") 97 98 payload := struct { 99 Metrics metricsPayload `json:"metrics,omitempty"` 100 Spans []Span `json:"spans,omitempty"` 101 }{ 102 Metrics: metricsPayload{ 103 Plugins: []acceptor.PluginPayload{ 104 acceptor.NewAWSLambdaPluginPayload(snapshot.EntityID), 105 }, 106 }, 107 } 108 109 a.mu.Lock() 110 payload.Spans = make([]Span, len(a.spanQueue)) 111 copy(payload.Spans, a.spanQueue) 112 a.spanQueue = a.spanQueue[:0] 113 a.mu.Unlock() 114 115 for i := range payload.Spans { 116 payload.Spans[i].From = from 117 } 118 119 buf := bytes.NewBuffer(nil) 120 if err := json.NewEncoder(buf).Encode(payload); err != nil { 121 return fmt.Errorf("failed to marshal traces payload: %s", err) 122 } 123 124 req, err := http.NewRequest(http.MethodPost, a.Endpoint+"/bundle", buf) 125 if err != nil { 126 a.enqueueSpans(payload.Spans) 127 return fmt.Errorf("failed to prepare send traces request: %s", err) 128 } 129 130 req.Header.Set("Content-Type", "application/json") 131 132 if err := a.sendRequest(req.WithContext(ctx)); err != nil { 133 a.enqueueSpans(payload.Spans) 134 return fmt.Errorf("failed to send traces, will retry later: %s", err) 135 } 136 137 return nil 138 } 139 140 func (a *lambdaAgent) enqueueSpans(spans []Span) { 141 a.mu.Lock() 142 defer a.mu.Unlock() 143 144 a.spanQueue = append(a.spanQueue, spans...) 145 } 146 147 func (a *lambdaAgent) sendRequest(req *http.Request) error { 148 req.Header.Set("X-Instana-Host", a.snapshot.Host) 149 req.Header.Set("X-Instana-Key", a.Key) 150 req.Header.Set("X-Instana-Time", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)) 151 152 resp, err := a.client.Do(req) 153 if err != nil { 154 return fmt.Errorf("failed to send request to the serverless agent: %s", err) 155 } 156 157 defer resp.Body.Close() 158 159 if resp.StatusCode >= http.StatusBadRequest { 160 respBody, err := ioutil.ReadAll(resp.Body) 161 if err != nil { 162 a.logger.Debug("failed to read serverless agent response: ", err) 163 return nil 164 } 165 166 a.logger.Info("serverless agent has responded with ", resp.Status, ": ", string(respBody)) 167 return nil 168 } 169 170 io.CopyN(ioutil.Discard, resp.Body, 1<<20) 171 172 return nil 173 } 174 175 func (a *lambdaAgent) collectSnapshot(spans []Span) serverlessSnapshot { 176 if a.snapshot.EntityID != "" { 177 return a.snapshot 178 } 179 180 // searching for the lambda entry span in reverse order, since it's 181 // more likely to be finished last 182 for i := len(spans) - 1; i >= 0; i-- { 183 sp, ok := spans[i].Data.(AWSLambdaSpanData) 184 if !ok { 185 continue 186 } 187 188 a.snapshot = serverlessSnapshot{ 189 EntityID: sp.Snapshot.ARN, 190 Host: sp.Snapshot.ARN, 191 } 192 a.logger.Debug("collected snapshot") 193 194 break 195 } 196 197 return a.snapshot 198 }