1
1
package ghacache
2
2
3
3
import (
4
+ "errors"
4
5
"fmt"
5
6
"github.com/cirruslabs/cirrus-cli/internal/agent/client"
6
7
"github.com/cirruslabs/cirrus-cli/internal/agent/http_cache/ghacache/httprange"
7
8
"github.com/cirruslabs/cirrus-cli/internal/agent/http_cache/ghacache/uploadable"
8
9
"github.com/cirruslabs/cirrus-cli/pkg/api"
10
+ "github.com/getsentry/sentry-go"
9
11
"github.com/go-chi/render"
10
12
"github.com/puzpuzpuz/xsync/v3"
11
13
"github.com/samber/lo"
14
+ "golang.org/x/exp/slog"
12
15
"google.golang.org/grpc/codes"
13
16
"google.golang.org/grpc/status"
14
17
"log"
@@ -80,7 +83,7 @@ func (cache *GHACache) get(writer http.ResponseWriter, request *http.Request) {
80
83
}
81
84
82
85
fail (writer , request , http .StatusInternalServerError , "GHA cache failed to " +
83
- "retrieve information about cache key %q: %v" , keys [0 ], err )
86
+ "retrieve information about cache entry" , "key" , keys [0 ], "err" , err )
84
87
85
88
return
86
89
}
@@ -104,7 +107,7 @@ func (cache *GHACache) reserveUploadable(writer http.ResponseWriter, request *ht
104
107
105
108
if err := render .DecodeJSON (request .Body , & jsonReq ); err != nil {
106
109
fail (writer , request , http .StatusBadRequest , "GHA cache failed to read/decode the " +
107
- "JSON passed to the reserve uploadable endpoint: %v " , err )
110
+ "JSON passed to the reserve uploadable endpoint" , "err " , err )
108
111
109
112
return
110
113
}
@@ -121,7 +124,7 @@ func (cache *GHACache) reserveUploadable(writer http.ResponseWriter, request *ht
121
124
})
122
125
if err != nil {
123
126
fail (writer , request , http .StatusInternalServerError , "GHA cache failed to create " +
124
- "multipart upload for key %q and version %q: %v " , jsonReq .Key , jsonReq .Version , err )
127
+ "multipart upload" , " key" , jsonReq .Key , "version" , jsonReq .Version , "err" , err )
125
128
126
129
return
127
130
}
@@ -142,31 +145,31 @@ func (cache *GHACache) updateUploadable(writer http.ResponseWriter, request *htt
142
145
143
146
uploadable , ok := cache .uploadables .Load (id )
144
147
if ! ok {
145
- fail (writer , request , http .StatusNotFound , "GHA cache failed to find an uploadable " +
146
- "with ID %d " , id )
148
+ fail (writer , request , http .StatusNotFound , "GHA cache failed to find an uploadable" ,
149
+ "id " , id )
147
150
148
151
return
149
152
}
150
153
151
154
httpRanges , err := httprange .ParseRange (request .Header .Get ("Content-Range" ), math .MaxInt64 )
152
155
if err != nil {
153
- fail (writer , request , http .StatusBadRequest , "GHA cache failed to parse Content-Range header %q: %v " ,
154
- request .Header .Get ("Content-Range" ), err )
156
+ fail (writer , request , http .StatusBadRequest , "GHA cache failed to parse Content-Range header" ,
157
+ "header_value" , request .Header .Get ("Content-Range" ), "err" , err )
155
158
156
159
return
157
160
}
158
161
159
162
if len (httpRanges ) != 1 {
160
- fail (writer , request , http .StatusBadRequest , "GHA cache expected exactly one Content-Range value, got %d " ,
161
- len (httpRanges ))
163
+ fail (writer , request , http .StatusBadRequest , "GHA cache expected exactly one Content-Range value" ,
164
+ "expected" , 1 , "actual" , len (httpRanges ))
162
165
163
166
return
164
167
}
165
168
166
169
partNumber , err := uploadable .RangeToPart .Tell (request .Context (), httpRanges [0 ].Start , httpRanges [0 ].Length )
167
170
if err != nil {
168
171
fail (writer , request , http .StatusBadRequest , "GHA cache failed to tell the part number for " +
169
- "Content-Range header %q: %v" , request .Header .Get ("Content-Range" ), err )
172
+ "Content-Range header" , "header_value" , request .Header .Get ("Content-Range" ), "err" , err )
170
173
171
174
return
172
175
}
@@ -182,16 +185,17 @@ func (cache *GHACache) updateUploadable(writer http.ResponseWriter, request *htt
182
185
})
183
186
if err != nil {
184
187
fail (writer , request , http .StatusInternalServerError , "GHA cache failed create pre-signed " +
185
- "upload part URL for key %q, version %q and part %d: %v " , uploadable .Key (),
186
- uploadable . Version () , partNumber , err )
188
+ "upload part URL" , " key" , uploadable . Key (), " version" , uploadable .Version (),
189
+ "part_number" , partNumber , "err" , err )
187
190
188
191
return
189
192
}
190
193
191
194
uploadPartRequest , err := http .NewRequest (http .MethodPut , response .Url , request .Body )
192
195
if err != nil {
193
196
fail (writer , request , http .StatusInternalServerError , "GHA cache failed to create upload part " +
194
- "request for key %q, version %q and part %d: %v" , uploadable .Key (), uploadable .Version (), partNumber , err )
197
+ "request" , "key" , uploadable .Key (), "version" , uploadable .Version (), "part_number" , partNumber ,
198
+ "err" , err )
195
199
196
200
return
197
201
}
@@ -213,8 +217,9 @@ func (cache *GHACache) updateUploadable(writer http.ResponseWriter, request *htt
213
217
// Return HTTP 502 to cause the cache-related code in the Actions Toolkit to make a re-try[1].
214
218
//
215
219
// [1]: https://github.com/actions/toolkit/blob/6dd369c0e648ed58d0ead326cf2426906ea86401/packages/cache/src/internal/requestUtils.ts#L24-L34
216
- fail (writer , request , http .StatusBadGateway , "GHA cache failed to upload part " +
217
- "for key %q, version %q and part %d: %v" , uploadable .Key (), uploadable .Version (), partNumber , err )
220
+ fail (writer , request , http .StatusBadGateway , "GHA cache failed to upload part" ,
221
+ "key" , uploadable .Key (), "version" , uploadable .Version (), "part_number" , partNumber ,
222
+ "err" , err )
218
223
219
224
return
220
225
}
@@ -224,17 +229,18 @@ func (cache *GHACache) updateUploadable(writer http.ResponseWriter, request *htt
224
229
// code in the Actions Toolkit will hopefully make a re-try[1].
225
230
//
226
231
// [1]: https://github.com/actions/toolkit/blob/6dd369c0e648ed58d0ead326cf2426906ea86401/packages/cache/src/internal/requestUtils.ts#L24-L34
227
- fail (writer , request , uploadPartResponse .StatusCode , "GHA cache failed to upload part " +
228
- "for key %q, version %q and part %d: got HTTP %d " , uploadable .Key (), uploadable .Version (), partNumber ,
229
- uploadPartResponse .StatusCode )
232
+ fail (writer , request , uploadPartResponse .StatusCode , "GHA cache failed to upload part" ,
233
+ "key" , uploadable .Key (), "version" , uploadable .Version (), "part_number" , partNumber ,
234
+ "unexpected_status_code" , uploadPartResponse .StatusCode )
230
235
231
236
return
232
237
}
233
238
234
239
err = uploadable .AppendPart (uint32 (partNumber ), uploadPartResponse .Header .Get ("ETag" ), httpRanges [0 ].Length )
235
240
if err != nil {
236
- fail (writer , request , http .StatusInternalServerError , "GHA cache failed to append part " +
237
- "for key %q, version %q and part %d: %v" , uploadable .Key (), uploadable .Version (), partNumber , err )
241
+ fail (writer , request , http .StatusInternalServerError , "GHA cache failed to append part" ,
242
+ "key" , uploadable .Key (), "version" , uploadable .Version (), "part_number" , partNumber ,
243
+ "err" , err )
238
244
239
245
return
240
246
}
@@ -253,8 +259,8 @@ func (cache *GHACache) commitUploadable(writer http.ResponseWriter, request *htt
253
259
254
260
uploadable , ok := cache .uploadables .Load (id )
255
261
if ! ok {
256
- fail (writer , request , http .StatusNotFound , "GHA cache failed to find an uploadable " +
257
- "with ID %d " , id )
262
+ fail (writer , request , http .StatusNotFound , "GHA cache failed to find an uploadable" ,
263
+ "id " , id )
258
264
259
265
return
260
266
}
@@ -265,23 +271,23 @@ func (cache *GHACache) commitUploadable(writer http.ResponseWriter, request *htt
265
271
266
272
if err := render .DecodeJSON (request .Body , & jsonReq ); err != nil {
267
273
fail (writer , request , http .StatusBadRequest , "GHA cache failed to read/decode " +
268
- "the JSON passed to the commit uploadable endpoint: %v " , err )
274
+ "the JSON passed to the commit uploadable endpoint" , "err " , err )
269
275
270
276
return
271
277
}
272
278
273
279
parts , partsSize , err := uploadable .Finalize ()
274
280
if err != nil {
275
281
fail (writer , request , http .StatusInternalServerError , "GHA cache failed to " +
276
- "finalize uploadable %d: %v" , id , err )
282
+ "finalize uploadable" , "id" , id , "err" , err )
277
283
278
284
return
279
285
}
280
286
281
287
if jsonReq .Size != partsSize {
282
288
fail (writer , request , http .StatusBadRequest , "GHA cache detected a cache entry " +
283
- "size mismatch for uploadable %d: expected %d bytes, got %d bytes" ,
284
- id , partsSize , jsonReq .Size )
289
+ "size mismatch for uploadable" , "id" , id , "expected_bytes" , partsSize ,
290
+ "actual_bytes" , jsonReq .Size )
285
291
286
292
return
287
293
}
@@ -295,9 +301,9 @@ func (cache *GHACache) commitUploadable(writer http.ResponseWriter, request *htt
295
301
Parts : parts ,
296
302
})
297
303
if err != nil {
298
- fail (writer , request , http .StatusInternalServerError , "GHA cache failed to commit multipart upload " +
299
- "for key %q, version %q and uploadable %q: %v" , uploadable .Key (), uploadable .Version (),
300
- uploadable . UploadID () , err )
304
+ fail (writer , request , http .StatusInternalServerError , "GHA cache failed to commit multipart upload" ,
305
+ "id" , uploadable . UploadID (), "key" , uploadable .Key (), "version" , uploadable .Version (),
306
+ "err" , err )
301
307
302
308
return
303
309
}
@@ -327,11 +333,50 @@ func getID(request *http.Request) (int64, bool) {
327
333
return id , true
328
334
}
329
335
330
- func fail (writer http.ResponseWriter , request * http.Request , status int , format string , args ... interface {}) {
331
- message := fmt .Sprintf (format , args ... )
336
+ func fail (writer http.ResponseWriter , request * http.Request , status int , msg string , args ... any ) {
337
+ // Report failure to the Sentry
338
+ hub := sentry .GetHubFromContext (request .Context ())
332
339
340
+ hub .WithScope (func (scope * sentry.Scope ) {
341
+ scope .AddEventProcessor (func (event * sentry.Event , hint * sentry.EventHint ) * sentry.Event {
342
+ // Swap the exception type and value to work around
343
+ // https://github.com/getsentry/sentry/issues/17837
344
+ savedType := event .Exception [0 ].Type
345
+ event .Exception [0 ].Type = event .Exception [0 ].Value
346
+ event .Exception [0 ].Value = savedType
347
+
348
+ return event
349
+ })
350
+
351
+ argsAsSentryContext := sentry.Context {}
352
+
353
+ for _ , chunk := range lo .Chunk (args , 2 ) {
354
+ key := fmt .Sprintf ("%v" , chunk [0 ])
355
+
356
+ var value string
357
+
358
+ if len (chunk ) > 1 {
359
+ value = fmt .Sprintf ("%v" , chunk [1 ])
360
+ }
361
+
362
+ argsAsSentryContext [key ] = value
363
+ }
364
+
365
+ scope .SetContext ("Arguments" , argsAsSentryContext )
366
+
367
+ hub .CaptureException (errors .New (msg ))
368
+ })
369
+
370
+ // Format failure message for non-structured consumers
371
+ var stringBuilder strings.Builder
372
+ logger := slog .New (slog .NewTextHandler (& stringBuilder , nil ))
373
+ logger .Error (msg , args ... )
374
+ message := stringBuilder .String ()
375
+
376
+ // Report failure to the logger
333
377
log .Println (message )
334
378
379
+ // Report failure to the caller
335
380
writer .WriteHeader (status )
336
381
jsonResp := struct {
337
382
Message string `json:"message"`
0 commit comments