From 0a3adb214c890900cf2a3cebb0734a40cb2e8006 Mon Sep 17 00:00:00 2001 From: Cody Lee Date: Tue, 10 Feb 2026 20:21:35 -0600 Subject: [PATCH] fix(metrics): improve error messages and fix timestamp format Fixes #29 - Improves error handling in metrics commands to provide detailed API error messages and request context for debugging. Changes: - Extract and display API response body in error messages - Add request details (query, timestamps, filters) to errors - Include troubleshooting tips for common issues - Fix timestamp format: use UnixMilli() instead of Unix() for API v2 - Convert timestamps to UTC before sending to API The API v2 timeseries endpoint expects timestamps in milliseconds, not seconds. This was causing "Queries before 2010 are invalid" errors because second-based timestamps were being interpreted as 1970s dates. Error messages now show: - HTTP status code - Full API response (e.g., {"errors":["..."]}) - Request parameters used - Troubleshooting guidance Co-Authored-By: Claude Sonnet 4.5 --- cmd/metrics.go | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/cmd/metrics.go b/cmd/metrics.go index b1e94e37..7607306c 100644 --- a/cmd/metrics.go +++ b/cmd/metrics.go @@ -7,6 +7,7 @@ package cmd import ( "fmt" + "io" "strconv" "strings" "time" @@ -507,8 +508,8 @@ func runMetricsQuery(cmd *cobra.Command, args []string) error { body := datadogV2.TimeseriesFormulaQueryRequest{ Data: datadogV2.TimeseriesFormulaRequest{ Attributes: datadogV2.TimeseriesFormulaRequestAttributes{ - From: from.Unix(), - To: to.Unix(), + From: from.UTC().UnixMilli(), + To: to.UTC().UnixMilli(), Queries: []datadogV2.TimeseriesQuery{timeseriesQuery}, }, Type: datadogV2.TIMESERIESFORMULAREQUESTTYPE_TIMESERIES_REQUEST, @@ -517,7 +518,15 @@ func runMetricsQuery(cmd *cobra.Command, args []string) error { resp, r, err := api.QueryTimeseriesData(client.Context(), body) if err != nil { - if r != nil { + if r != nil && r.Body != nil { + bodyBytes, readErr := io.ReadAll(r.Body) + if readErr == nil && len(bodyBytes) > 0 { + return fmt.Errorf("failed to query metrics: %w\nStatus: %d\nAPI Response: %s\n\nRequest Details:\n- Query: %s\n- From: %s (Unix: %d)\n- To: %s (Unix: %d)\n\nTroubleshooting:\n- Verify your query syntax is correct (e.g., avg:metric.name{filter})\n- Check that the time range is valid\n- Ensure the metric exists and has data in the specified time range\n- Confirm you have proper permissions to access the metric", + err, r.StatusCode, string(bodyBytes), + queryString, + from.Format(time.RFC3339), from.Unix(), + to.Format(time.RFC3339), to.Unix()) + } return fmt.Errorf("failed to query metrics: %w (status: %d)", err, r.StatusCode) } return fmt.Errorf("failed to query metrics: %w", err) @@ -551,7 +560,14 @@ func runMetricsList(cmd *cobra.Command, args []string) error { resp, r, err := api.ListActiveMetrics(client.Context(), from, *opts) if err != nil { - if r != nil { + if r != nil && r.Body != nil { + bodyBytes, readErr := io.ReadAll(r.Body) + if readErr == nil && len(bodyBytes) > 0 { + return fmt.Errorf("failed to list metrics: %w\nStatus: %d\nAPI Response: %s\n\nRequest Details:\n- Filter: %s\n- From: %s (Unix: %d)\n\nTroubleshooting:\n- Check that your filter pattern is valid\n- Verify you have permissions to list metrics", + err, r.StatusCode, string(bodyBytes), + filterPattern, + time.Unix(from, 0).Format(time.RFC3339), from) + } return fmt.Errorf("failed to list metrics: %w (status: %d)", err, r.StatusCode) } return fmt.Errorf("failed to list metrics: %w", err) @@ -578,7 +594,12 @@ func runMetricsMetadataGet(cmd *cobra.Command, args []string) error { resp, r, err := api.GetMetricMetadata(client.Context(), metricName) if err != nil { - if r != nil { + if r != nil && r.Body != nil { + bodyBytes, readErr := io.ReadAll(r.Body) + if readErr == nil && len(bodyBytes) > 0 { + return fmt.Errorf("failed to get metric metadata: %w\nStatus: %d\nAPI Response: %s\n\nMetric: %s\n\nTroubleshooting:\n- Verify the metric name is correct\n- Ensure the metric exists in your account\n- Check that you have permissions to view metadata", + err, r.StatusCode, string(bodyBytes), metricName) + } return fmt.Errorf("failed to get metric metadata: %w (status: %d)", err, r.StatusCode) } return fmt.Errorf("failed to get metric metadata: %w", err) @@ -629,7 +650,12 @@ func runMetricsMetadataUpdate(cmd *cobra.Command, args []string) error { resp, r, err := api.UpdateMetricMetadata(client.Context(), metricName, body) if err != nil { - if r != nil { + if r != nil && r.Body != nil { + bodyBytes, readErr := io.ReadAll(r.Body) + if readErr == nil && len(bodyBytes) > 0 { + return fmt.Errorf("failed to update metric metadata: %w\nStatus: %d\nAPI Response: %s\n\nMetric: %s\n\nTroubleshooting:\n- Verify the metric name is correct\n- Check that the metadata values are valid (unit, type, etc.)\n- Ensure you have permissions to update metadata", + err, r.StatusCode, string(bodyBytes), metricName) + } return fmt.Errorf("failed to update metric metadata: %w (status: %d)", err, r.StatusCode) } return fmt.Errorf("failed to update metric metadata: %w", err) @@ -724,7 +750,13 @@ func runMetricsSubmit(cmd *cobra.Command, args []string) error { resp, r, err := api.SubmitMetrics(client.Context(), body, *datadogV2.NewSubmitMetricsOptionalParameters()) if err != nil { - if r != nil { + if r != nil && r.Body != nil { + bodyBytes, readErr := io.ReadAll(r.Body) + if readErr == nil && len(bodyBytes) > 0 { + return fmt.Errorf("failed to submit metrics: %w\nStatus: %d\nAPI Response: %s\n\nRequest Details:\n- Metric: %s\n- Value: %f\n- Type: %s\n- Timestamp: %d\n- Tags: %v\n\nTroubleshooting:\n- Verify the metric name follows naming conventions (lowercase, dots/underscores)\n- Check that the metric type is valid (gauge, count, rate)\n- Ensure your API key has permission to submit metrics\n- Verify tags are in key:value format", + err, r.StatusCode, string(bodyBytes), + submitName, submitValue, submitType, timestamp, tags) + } return fmt.Errorf("failed to submit metrics: %w (status: %d)", err, r.StatusCode) } return fmt.Errorf("failed to submit metrics: %w", err)