@@ -16,17 +16,19 @@ import (
1616
1717 "github.com/dustin/go-humanize"
1818 "github.com/go-kit/log"
19+ "github.com/gogo/protobuf/proto"
1920 "github.com/prometheus/client_golang/prometheus"
2021 "github.com/prometheus/client_golang/prometheus/promauto"
2122 "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 "
2425
2526 "github.com/grafana/loki/v3/pkg/analytics"
2627 "github.com/grafana/loki/v3/pkg/loghttp"
2728 "github.com/grafana/loki/v3/pkg/logproto"
2829 "github.com/grafana/loki/v3/pkg/logql/syntax"
2930 "github.com/grafana/loki/v3/pkg/util"
31+ loki_util "github.com/grafana/loki/v3/pkg/util"
3032 "github.com/grafana/loki/v3/pkg/util/constants"
3133 "github.com/grafana/loki/v3/pkg/util/unmarshal"
3234 unmarshal2 "github.com/grafana/loki/v3/pkg/util/unmarshal/legacy"
@@ -86,6 +88,7 @@ func (EmptyLimits) DiscoverServiceName(string) []string {
8688type (
8789 RequestParser func (userID string , r * http.Request , tenantsRetention TenantsRetention , limits Limits , tracker UsageTracker , logPushRequestStreams bool , logger log.Logger ) (* logproto.PushRequest , * Stats , error )
8890 RequestParserWrapper func (inner RequestParser ) RequestParser
91+ ErrorWriter func (w http.ResponseWriter , error string , code int , logger log.Logger )
8992)
9093
9194type Stats struct {
@@ -307,3 +310,62 @@ func RetentionPeriodToString(retentionPeriod time.Duration) string {
307310 }
308311 return retentionHours
309312}
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