|
5 | 5 | "context" |
6 | 6 | "encoding/json" |
7 | 7 | "fmt" |
| 8 | + "io" |
8 | 9 | "net/http" |
9 | 10 | "os" |
10 | 11 | "os/exec" |
@@ -38,8 +39,6 @@ func FlushMetrics(ctx context.Context) error { |
38 | 39 |
|
39 | 40 | iostream := iostreams.FromContext(ctx) |
40 | 41 |
|
41 | | - // On CI, always block on metrics send. This sucks, but the alternative is not getting metrics from CI at all. There are timeouts in place to prevent this from taking more than 15 seconds |
42 | | - |
43 | 42 | if iostream.IsInteractive() { |
44 | 43 | flyctl, err := os.Executable() |
45 | 44 | if err != nil { |
@@ -76,33 +75,85 @@ func FlushMetrics(ctx context.Context) error { |
76 | 75 | return nil |
77 | 76 | } |
78 | 77 |
|
79 | | -// / Spens up to 15 seconds sending all metrics collected so far to flyctl-metrics post endpoint |
80 | | -func SendMetrics(ctx context.Context, json string) error { |
81 | | - authToken, err := GetMetricsToken(ctx) |
| 78 | +func SendMetrics(ctx context.Context, jsonData string) error { |
| 79 | + cfg := config.FromContext(ctx) |
| 80 | + metricsToken, err := GetMetricsToken(ctx) |
82 | 81 | if err != nil { |
83 | | - return err |
| 82 | + fmt.Fprintf(os.Stderr, "Warning: Metrics token unavailable: %v\n", err) |
| 83 | + return nil |
84 | 84 | } |
85 | 85 |
|
86 | | - cfg := config.FromContext(ctx) |
87 | | - request, err := http.NewRequest("POST", cfg.MetricsBaseURL+"/metrics_post", bytes.NewBuffer([]byte(json))) |
| 86 | + baseURL := cfg.MetricsBaseURL |
| 87 | + endpoint := baseURL + "/metrics_post" |
| 88 | + userAgent := fmt.Sprintf("flyctl/%s", buildinfo.Info().Version) |
| 89 | + |
| 90 | + errChan := make(chan error, 1) |
| 91 | + |
| 92 | + go sendMetricsRequest(endpoint, metricsToken, userAgent, []byte(jsonData), errChan) |
| 93 | + |
| 94 | + err = waitForCompletion(errChan) |
88 | 95 | if err != nil { |
89 | | - return err |
| 96 | + fmt.Fprintf(os.Stderr, "Warning: Metrics send issue: %v\n", err) |
| 97 | + } |
| 98 | + |
| 99 | + return nil |
| 100 | +} |
| 101 | + |
| 102 | +func sendMetricsRequest(endpoint, token, userAgent string, data []byte, errChan chan<- error) { |
| 103 | + request, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(data)) |
| 104 | + if err != nil { |
| 105 | + errChan <- fmt.Errorf("failed to create request: %w", err) |
| 106 | + return |
90 | 107 | } |
91 | 108 |
|
92 | | - request.Header.Set("Authorization", authToken) |
93 | | - request.Header.Set("User-Agent", fmt.Sprintf("flyctl/%s", buildinfo.Info().Version)) |
| 109 | + request.Header.Set("Authorization", "Bearer "+token) |
| 110 | + request.Header.Set("User-Agent", userAgent) |
94 | 111 |
|
95 | | - retryTransport := rehttp.NewTransport(http.DefaultTransport, rehttp.RetryAll(rehttp.RetryMaxRetries(3), rehttp.RetryTimeoutErr()), rehttp.ConstDelay(0)) |
| 112 | + client := createHTTPClient() |
96 | 113 |
|
97 | | - client := http.Client{ |
| 114 | + resp, err := client.Do(request) |
| 115 | + if err != nil { |
| 116 | + errChan <- fmt.Errorf("failed to send metrics: %w", err) |
| 117 | + return |
| 118 | + } |
| 119 | + defer resp.Body.Close() |
| 120 | + |
| 121 | + if resp.StatusCode != http.StatusOK { |
| 122 | + body, _ := io.ReadAll(resp.Body) |
| 123 | + errChan <- fmt.Errorf("metrics send failed with status %d: %s", resp.StatusCode, string(body)) |
| 124 | + return |
| 125 | + } |
| 126 | + |
| 127 | + _, err = io.Copy(io.Discard, resp.Body) |
| 128 | + if err != nil { |
| 129 | + errChan <- fmt.Errorf("failed to read response body: %w", err) |
| 130 | + return |
| 131 | + } |
| 132 | + |
| 133 | + errChan <- nil |
| 134 | +} |
| 135 | + |
| 136 | +func createHTTPClient() *http.Client { |
| 137 | + retryTransport := rehttp.NewTransport( |
| 138 | + http.DefaultTransport, |
| 139 | + rehttp.RetryAll( |
| 140 | + rehttp.RetryMaxRetries(3), |
| 141 | + rehttp.RetryTimeoutErr(), |
| 142 | + ), |
| 143 | + rehttp.ConstDelay(0), |
| 144 | + ) |
| 145 | + |
| 146 | + return &http.Client{ |
98 | 147 | Transport: retryTransport, |
99 | 148 | Timeout: time.Second * 5, |
100 | 149 | } |
| 150 | +} |
101 | 151 |
|
102 | | - resp, err := client.Do(request) |
103 | | - if err != nil { |
| 152 | +func waitForCompletion(errChan <-chan error) error { |
| 153 | + select { |
| 154 | + case err := <-errChan: |
104 | 155 | return err |
| 156 | + case <-time.After(15 * time.Second): |
| 157 | + return fmt.Errorf("metrics send timed out after 15 seconds") |
105 | 158 | } |
106 | | - |
107 | | - return resp.Body.Close() |
108 | 159 | } |
0 commit comments