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  }