@@ -11,6 +11,7 @@ import (
1111
1212 "agent-metadata-action/internal/logging"
1313 "agent-metadata-action/internal/models"
14+ "agent-metadata-action/internal/retry"
1415)
1516
1617// InstrumentationClient handles instrumentation metadata operations
@@ -69,91 +70,112 @@ func (c *InstrumentationClient) SendMetadata(ctx context.Context, agentType stri
6970 "agent.version" : agentVersion ,
7071 })
7172 logging .Errorf (ctx , "Failed to marshal metadata: %v" , err )
72- return fmt .Errorf ("failed to marshal metadata: %w" , err )
73+ return retry . NewNonRetryableError ( fmt .Errorf ("failed to marshal metadata: %w" , err ) )
7374 }
7475 logging .Debugf (ctx , "JSON payload size: %d bytes" , len (jsonBody ))
7576 logging .Debugf (ctx , "Configuration definitions count: %d" , len (metadata .ConfigurationDefinitions ))
7677 logging .Debugf (ctx , "Agent control entries: %d" , len (metadata .AgentControlDefinitions ))
7778
78- // Create HTTP request
79- logging .Debug (ctx , "Creating HTTP POST request..." )
80- req , err := http .NewRequestWithContext (ctx , "POST" , url , bytes .NewBuffer (jsonBody ))
81- if err != nil {
82- logging .NoticeErrorWithCategory (ctx , err , "metadata.send" , map [string ]interface {}{
83- "error.operation" : "create_http_request" ,
84- "http.url" : url ,
85- "agent.type" : agentType ,
86- "agent.version" : agentVersion ,
87- })
88- logging .Errorf (ctx , "Failed to create request: %v" , err )
89- return fmt .Errorf ("failed to create request: %w" , err )
79+ // Execute request with retry logic
80+ retryConfig := retry.Config {
81+ MaxAttempts : 3 ,
82+ BaseDelay : 2 * time .Second ,
83+ Operation : "Metadata submission" ,
9084 }
9185
92- // Set headers
93- logging .Debug (ctx , "Setting request headers..." )
94- req .Header .Set ("Content-Type" , "application/json" )
95- req .Header .Set ("Authorization" , fmt .Sprintf ("Bearer %s" , c .token ))
96- // SECURITY: Token is in header but not logged
86+ err = retry .Do (ctx , retryConfig , func () error {
87+ // Create HTTP request (must be recreated for each retry)
88+ logging .Debug (ctx , "Creating HTTP POST request..." )
89+ req , err := http .NewRequestWithContext (ctx , "POST" , url , bytes .NewBuffer (jsonBody ))
90+ if err != nil {
91+ logging .NoticeErrorWithCategory (ctx , err , "metadata.send" , map [string ]interface {}{
92+ "error.operation" : "create_http_request" ,
93+ "http.url" : url ,
94+ "agent.type" : agentType ,
95+ "agent.version" : agentVersion ,
96+ })
97+ logging .Errorf (ctx , "Failed to create request: %v" , err )
98+ return retry .NewNonRetryableError (fmt .Errorf ("failed to create request: %w" , err ))
99+ }
97100
98- // Execute request
99- logging .Debug (ctx , "Sending HTTP request..." )
100- startTime := time .Now ()
101- resp , err := c .httpClient .Do (req )
102- duration := time .Since (startTime )
101+ // Set headers
102+ logging .Debug (ctx , "Setting request headers..." )
103+ req .Header .Set ("Content-Type" , "application/json" )
104+ req .Header .Set ("Authorization" , fmt .Sprintf ("Bearer %s" , c .token ))
105+
106+ // Execute request
107+ logging .Debug (ctx , "Sending HTTP request..." )
108+ startTime := time .Now ()
109+ resp , err := c .httpClient .Do (req )
110+ duration := time .Since (startTime )
111+
112+ if err != nil {
113+ logging .NoticeErrorWithCategory (ctx , err , "metadata.send" , map [string ]interface {}{
114+ "error.operation" : "execute_http_request" ,
115+ "http.url" : url ,
116+ "http.duration" : duration .String (),
117+ "agent.type" : agentType ,
118+ "agent.version" : agentVersion ,
119+ })
120+ logging .Errorf (ctx , "HTTP request failed after %s: %v" , duration , err )
121+ return fmt .Errorf ("failed to send metadata: %w" , err )
122+ }
123+ defer resp .Body .Close ()
103124
104- if err != nil {
105- logging .NoticeErrorWithCategory (ctx , err , "metadata.send" , map [string ]interface {}{
106- "error.operation" : "execute_http_request" ,
107- "http.url" : url ,
108- "http.duration" : duration .String (),
109- "agent.type" : agentType ,
110- "agent.version" : agentVersion ,
111- })
112- logging .Errorf (ctx , "HTTP request failed after %s: %v" , duration , err )
113- return fmt .Errorf ("failed to send metadata: %w" , err )
114- }
115- defer resp .Body .Close ()
125+ logging .Debugf (ctx , "Response received in %s" , duration )
126+ logging .Debugf (ctx , "HTTP status code: %d %s" , resp .StatusCode , resp .Status )
116127
117- logging .Debugf (ctx , "Response received in %s" , duration )
118- logging .Debugf (ctx , "HTTP status code: %d %s" , resp .StatusCode , resp .Status )
128+ // Read response body for error details (with size limit)
129+ logging .Debug (ctx , "Reading response body..." )
130+ body , err := io .ReadAll (resp .Body )
131+ if err != nil {
132+ logging .Errorf (ctx , "Failed to read response body: %v" , err )
133+ return fmt .Errorf ("failed to read response: %w" , err )
134+ }
135+ logging .Debugf (ctx , "Response body size: %d bytes" , len (body ))
136+
137+ // Check for non-2xx status codes
138+ if resp .StatusCode < 200 || resp .StatusCode >= 300 {
139+ // Log response body for debugging, but truncate if too large
140+ responsePreview := string (body )
141+ if len (responsePreview ) > 500 {
142+ responsePreview = responsePreview [:500 ] + "... (truncated)"
143+ }
144+
145+ err := fmt .Errorf ("metadata submission failed with status %d: %s" , resp .StatusCode , string (body ))
146+ logging .NoticeErrorWithCategory (ctx , err , "metadata.send" , map [string ]interface {}{
147+ "error.operation" : "http_non_2xx_response" ,
148+ "http.status_code" : resp .StatusCode ,
149+ "http.url" : url ,
150+ "http.response_body" : responsePreview ,
151+ "agent.type" : agentType ,
152+ "agent.version" : agentVersion ,
153+ })
154+ logging .Errorf (ctx , "Metadata submission failed with status %d" , resp .StatusCode )
155+ logging .Debugf (ctx , "Error response body: %s" , responsePreview )
156+
157+ // Determine if error is retryable
158+ // Retry on: 5xx (server errors), 408 (timeout), 429 (rate limit)
159+ // Don't retry: 4xx (client errors, except 408 and 429)
160+ isRetryable := resp .StatusCode >= 500 || resp .StatusCode == 408 || resp .StatusCode == 429
161+ if ! isRetryable {
162+ return retry .NewNonRetryableError (err )
163+ }
164+ return err
165+ }
119166
120- // Read response body for error details (with size limit)
121- logging .Debug (ctx , "Reading response body..." )
122- body , err := io .ReadAll (resp .Body )
123- if err != nil {
124- logging .Errorf (ctx , "Failed to read response body: %v" , err )
125- return fmt .Errorf ("failed to read response: %w" , err )
126- }
127- logging .Debugf (ctx , "Response body size: %d bytes" , len (body ))
128-
129- // Check for non-2xx status codes
130- if resp .StatusCode < 200 || resp .StatusCode >= 300 {
131- // Log response body for debugging, but truncate if too large
132- responsePreview := string (body )
133- if len (responsePreview ) > 500 {
134- responsePreview = responsePreview [:500 ] + "... (truncated)"
167+ // Success logging
168+ if len (body ) > 0 {
169+ logging .Debugf (ctx , "Success response: %s" , string (body ))
135170 }
136171
137- err := fmt .Errorf ("metadata submission failed with status %d: %s" , resp .StatusCode , string (body ))
138- logging .NoticeErrorWithCategory (ctx , err , "metadata.send" , map [string ]interface {}{
139- "error.operation" : "http_non_2xx_response" ,
140- "http.status_code" : resp .StatusCode ,
141- "http.url" : url ,
142- "http.response_body" : responsePreview ,
143- "agent.type" : agentType ,
144- "agent.version" : agentVersion ,
145- })
146- logging .Errorf (ctx , "Metadata submission failed with status %d" , resp .StatusCode )
147- logging .Debugf (ctx , "Error response body: %s" , responsePreview )
172+ return nil
173+ })
148174
175+ if err != nil {
149176 return err
150177 }
151178
152- // Success logging
153179 logging .Notice (ctx , "Metadata successfully submitted to instrumentation service" )
154- if len (body ) > 0 {
155- logging .Debugf (ctx , "Success response: %s" , string (body ))
156- }
157-
158180 return nil
159181}
0 commit comments