performHTTPRequest handles the actual HTTP request to the telemetry API
(ctx context.Context, event *EventPayload, version string)
| 112 | |
| 113 | // performHTTPRequest handles the actual HTTP request to the telemetry API |
| 114 | func (tc *Client) performHTTPRequest(ctx context.Context, event *EventPayload, version string) error { |
| 115 | // Wrap event in records array to match MarlinRequest format |
| 116 | requestBody := map[string]any{ |
| 117 | "records": []any{event}, |
| 118 | } |
| 119 | |
| 120 | // Serialize request to JSON |
| 121 | jsonData, err := json.Marshal(requestBody) |
| 122 | if err != nil { |
| 123 | return fmt.Errorf("failed to marshal request to JSON: %w", err) |
| 124 | } |
| 125 | |
| 126 | // Send request with timeout context. Telemetry sends should outlive a |
| 127 | // cancelled caller, but keep its trace context for correlation. |
| 128 | ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 10*time.Second) |
| 129 | defer cancel() |
| 130 | |
| 131 | // Create HTTP request |
| 132 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, tc.endpoint, bytes.NewBuffer(jsonData)) |
| 133 | if err != nil { |
| 134 | return fmt.Errorf("failed to create HTTP request: %w", err) |
| 135 | } |
| 136 | |
| 137 | // Set headers |
| 138 | req.Header.Set("Content-Type", "application/json") |
| 139 | req.Header.Set("User-Agent", "cagent/"+version) |
| 140 | if tc.apiKey != "" && tc.header != "" { |
| 141 | req.Header.Set(tc.header, tc.apiKey) |
| 142 | } |
| 143 | |
| 144 | // Debug: log request details |
| 145 | tc.logger.Debug("HTTP request details", |
| 146 | "method", req.Method, |
| 147 | "url", req.URL.String(), |
| 148 | "content_type", req.Header.Get("Content-Type"), |
| 149 | "user_agent", req.Header.Get("User-Agent"), |
| 150 | "has_header", req.Header.Get(tc.header) != "", |
| 151 | "header_length", len(req.Header.Get(tc.header)), |
| 152 | "payload_size", len(jsonData), |
| 153 | "payload", string(jsonData), |
| 154 | ) |
| 155 | |
| 156 | resp, err := tc.httpClient.Do(req) |
| 157 | if err != nil { |
| 158 | return fmt.Errorf("HTTP request failed: %w", err) |
| 159 | } |
| 160 | defer resp.Body.Close() |
| 161 | |
| 162 | // Check response status |
| 163 | if resp.StatusCode < 200 || resp.StatusCode >= 300 { |
| 164 | body := make([]byte, 1024) // Read up to 1KB of error response |
| 165 | n, _ := resp.Body.Read(body) |
| 166 | |
| 167 | // Enhanced error logging with response details |
| 168 | tc.logger.Debug("HTTP error response details", |
| 169 | "status_code", resp.StatusCode, |
| 170 | "status_text", resp.Status, |
| 171 | "content_type", resp.Header.Get("Content-Type"), |