@@ -16,17 +16,19 @@ import (
16
16
17
17
"github.com/dustin/go-humanize"
18
18
"github.com/go-kit/log"
19
+ "github.com/gogo/protobuf/proto"
19
20
"github.com/prometheus/client_golang/prometheus"
20
21
"github.com/prometheus/client_golang/prometheus/promauto"
21
22
"github.com/prometheus/prometheus/model/labels"
22
-
23
- loki_util "github.com/grafana/loki/v3/pkg/util "
23
+ "google.golang.org/grpc/codes"
24
+ grpcstatus "google.golang.org/grpc/status "
24
25
25
26
"github.com/grafana/loki/v3/pkg/analytics"
26
27
"github.com/grafana/loki/v3/pkg/loghttp"
27
28
"github.com/grafana/loki/v3/pkg/logproto"
28
29
"github.com/grafana/loki/v3/pkg/logql/syntax"
29
30
"github.com/grafana/loki/v3/pkg/util"
31
+ loki_util "github.com/grafana/loki/v3/pkg/util"
30
32
"github.com/grafana/loki/v3/pkg/util/constants"
31
33
"github.com/grafana/loki/v3/pkg/util/unmarshal"
32
34
unmarshal2 "github.com/grafana/loki/v3/pkg/util/unmarshal/legacy"
@@ -86,6 +88,7 @@ func (EmptyLimits) DiscoverServiceName(string) []string {
86
88
type (
87
89
RequestParser func (userID string , r * http.Request , tenantsRetention TenantsRetention , limits Limits , tracker UsageTracker , logPushRequestStreams bool , logger log.Logger ) (* logproto.PushRequest , * Stats , error )
88
90
RequestParserWrapper func (inner RequestParser ) RequestParser
91
+ ErrorWriter func (w http.ResponseWriter , error string , code int , logger log.Logger )
89
92
)
90
93
91
94
type Stats struct {
@@ -307,3 +310,62 @@ func RetentionPeriodToString(retentionPeriod time.Duration) string {
307
310
}
308
311
return retentionHours
309
312
}
313
+
314
+ // OTLPError writes an OTLP-compliant error response to the given http.ResponseWriter.
315
+ //
316
+ // According to the OTLP spec: https://opentelemetry.io/docs/specs/otlp/#failures-1
317
+ // Re. the error response format
318
+ // > If the processing of the request fails, the server MUST respond with appropriate HTTP 4xx or HTTP 5xx status code.
319
+ // > The response body for all HTTP 4xx and HTTP 5xx responses MUST be a Protobuf-encoded Status message that describes the problem.
320
+ // > This specification does not use Status.code field and the server MAY omit Status.code field.
321
+ // > The clients are not expected to alter their behavior based on Status.code field but MAY record it for troubleshooting purposes.
322
+ // > The Status.message field SHOULD contain a developer-facing error message as defined in Status message schema.
323
+ //
324
+ // Re. retryable errors
325
+ // > The requests that receive a response status code listed in following table SHOULD be retried.
326
+ // > All other 4xx or 5xx response status codes MUST NOT be retried
327
+ // > 429 Too Many Requests
328
+ // > 502 Bad Gateway
329
+ // > 503 Service Unavailable
330
+ // > 504 Gateway Timeout
331
+ // In loki, we expect clients to retry on 500 errors, so we map 500 errors to 503.
332
+ func OTLPError (w http.ResponseWriter , error string , code int , logger log.Logger ) {
333
+ // Map 500 errors to 503. 500 errors are never retried on the client side, but 503 are.
334
+ if code == http .StatusInternalServerError {
335
+ code = http .StatusServiceUnavailable
336
+ }
337
+
338
+ // As per the OTLP spec, we send the status code on the http header.
339
+ w .WriteHeader (code )
340
+
341
+ // Status 0 because we omit the Status.code field.
342
+ status := grpcstatus .New (0 , error ).Proto ()
343
+ respBytes , err := proto .Marshal (status )
344
+ if err != nil {
345
+ level .Error (logger ).Log ("msg" , "failed to marshal error response" , "error" , err )
346
+ writeResponseFailedBody , _ := proto .Marshal (grpcstatus .New (
347
+ codes .Internal ,
348
+ fmt .Sprintf ("failed to marshal error response: %s" , err .Error ()),
349
+ ).Proto ())
350
+ _ , _ = w .Write (writeResponseFailedBody )
351
+ return
352
+ }
353
+
354
+ w .Header ().Set (contentType , "application/octet-stream" )
355
+ if _ , err = w .Write (respBytes ); err != nil {
356
+ level .Error (logger ).Log ("msg" , "failed to write error response" , "error" , err )
357
+ writeResponseFailedBody , _ := proto .Marshal (grpcstatus .New (
358
+ codes .Internal ,
359
+ fmt .Sprintf ("failed write error: %s" , err .Error ()),
360
+ ).Proto ())
361
+ _ , _ = w .Write (writeResponseFailedBody )
362
+ }
363
+ }
364
+
365
+ var _ ErrorWriter = OTLPError
366
+
367
+ func HTTPError (w http.ResponseWriter , error string , code int , _ log.Logger ) {
368
+ http .Error (w , error , code )
369
+ }
370
+
371
+ var _ ErrorWriter = HTTPError
0 commit comments