mirror of
https://github.com/hanzoai/telemetry.git
synced 2026-10-30 20:01:49 +00:00
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description Run the `make modernize` tool. https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize Signed-off-by: Paulo Dias <[email protected]>
262 lines
7.5 KiB
Go
262 lines
7.5 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package otlpreceiver // import "go.opentelemetry.io/collector/receiver/otlpreceiver"
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
"go.opentelemetry.io/collector/internal/statusutil"
|
|
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
|
|
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/logs"
|
|
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/metrics"
|
|
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/profiles"
|
|
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/trace"
|
|
)
|
|
|
|
// Pre-computed status with code=Internal to be used in case of a marshaling error.
|
|
var fallbackMsg = []byte(`{"code": 13, "message": "failed to marshal error message"}`)
|
|
|
|
const fallbackContentType = "application/json"
|
|
|
|
func handleTraces(resp http.ResponseWriter, req *http.Request, tracesReceiver *trace.Receiver) {
|
|
enc, ok := readContentType(resp, req)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
body, ok := readAndCloseBody(resp, req, enc)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
otlpReq, err := enc.unmarshalTracesRequest(body)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
otlpResp, err := tracesReceiver.Export(req.Context(), otlpReq)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
msg, err := enc.marshalTracesResponse(otlpResp)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
writeResponse(resp, enc.contentType(), http.StatusOK, msg)
|
|
}
|
|
|
|
func handleMetrics(resp http.ResponseWriter, req *http.Request, metricsReceiver *metrics.Receiver) {
|
|
enc, ok := readContentType(resp, req)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
body, ok := readAndCloseBody(resp, req, enc)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
otlpReq, err := enc.unmarshalMetricsRequest(body)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
otlpResp, err := metricsReceiver.Export(req.Context(), otlpReq)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
msg, err := enc.marshalMetricsResponse(otlpResp)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
writeResponse(resp, enc.contentType(), http.StatusOK, msg)
|
|
}
|
|
|
|
func handleLogs(resp http.ResponseWriter, req *http.Request, logsReceiver *logs.Receiver) {
|
|
enc, ok := readContentType(resp, req)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
body, ok := readAndCloseBody(resp, req, enc)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
otlpReq, err := enc.unmarshalLogsRequest(body)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
otlpResp, err := logsReceiver.Export(req.Context(), otlpReq)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
msg, err := enc.marshalLogsResponse(otlpResp)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
writeResponse(resp, enc.contentType(), http.StatusOK, msg)
|
|
}
|
|
|
|
func handleProfiles(resp http.ResponseWriter, req *http.Request, profilesReceiver *profiles.Receiver) {
|
|
enc, ok := readContentType(resp, req)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
body, ok := readAndCloseBody(resp, req, enc)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
otlpReq, err := enc.unmarshalProfilesRequest(body)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
otlpResp, err := profilesReceiver.Export(req.Context(), otlpReq)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
msg, err := enc.marshalProfilesResponse(otlpResp)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
writeResponse(resp, enc.contentType(), http.StatusOK, msg)
|
|
}
|
|
|
|
func readContentType(resp http.ResponseWriter, req *http.Request) (encoder, bool) {
|
|
if req.Method != http.MethodPost {
|
|
handleUnmatchedMethod(resp)
|
|
return nil, false
|
|
}
|
|
|
|
switch getMimeTypeFromContentType(req.Header.Get("Content-Type")) {
|
|
case pbContentType:
|
|
return pbEncoder, true
|
|
case jsonContentType:
|
|
return jsEncoder, true
|
|
default:
|
|
handleUnmatchedContentType(resp)
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
func readAndCloseBody(resp http.ResponseWriter, req *http.Request, enc encoder) ([]byte, bool) {
|
|
body, err := io.ReadAll(req.Body)
|
|
if err != nil {
|
|
writeError(resp, enc, err, http.StatusBadRequest)
|
|
return nil, false
|
|
}
|
|
if err = req.Body.Close(); err != nil {
|
|
writeError(resp, enc, err, http.StatusBadRequest)
|
|
return nil, false
|
|
}
|
|
return body, true
|
|
}
|
|
|
|
// writeError encodes the HTTP error inside a rpc.Status message as required by the OTLP protocol.
|
|
func writeError(w http.ResponseWriter, encoder encoder, err error, statusCode int) {
|
|
s, ok := status.FromError(err)
|
|
if ok {
|
|
statusCode = errors.GetHTTPStatusCodeFromStatus(s)
|
|
} else {
|
|
s = statusutil.NewStatusFromMsgAndHTTPCode(err.Error(), statusCode)
|
|
}
|
|
writeStatusResponse(w, encoder, statusCode, s)
|
|
}
|
|
|
|
// errorHandler encodes the HTTP error message inside a rpc.Status message as required
|
|
// by the OTLP protocol.
|
|
func errorHandler(w http.ResponseWriter, r *http.Request, errMsg string, statusCode int) {
|
|
s := statusutil.NewStatusFromMsgAndHTTPCode(errMsg, statusCode)
|
|
contentType := r.Header.Get("Content-Type")
|
|
if contentType == "" {
|
|
contentType = fallbackContentType
|
|
}
|
|
switch getMimeTypeFromContentType(contentType) {
|
|
case pbContentType:
|
|
writeStatusResponse(w, pbEncoder, statusCode, s)
|
|
return
|
|
case jsonContentType:
|
|
writeStatusResponse(w, jsEncoder, statusCode, s)
|
|
return
|
|
}
|
|
writeResponse(w, fallbackContentType, http.StatusInternalServerError, fallbackMsg)
|
|
}
|
|
|
|
func writeStatusResponse(w http.ResponseWriter, enc encoder, statusCode int, st *status.Status) {
|
|
// https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#otlphttp-throttling
|
|
if statusCode == http.StatusTooManyRequests || statusCode == http.StatusServiceUnavailable {
|
|
retryInfo := statusutil.GetRetryInfo(st)
|
|
// Check if server returned throttling information.
|
|
if retryInfo != nil {
|
|
// We are throttled. Wait before retrying as requested by the server.
|
|
// The value of Retry-After field can be either an HTTP-date or a number of
|
|
// seconds to delay after the response is received. See https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3
|
|
//
|
|
// Retry-After = HTTP-date / delay-seconds
|
|
//
|
|
// Use delay-seconds since is easier to format as well as does not require clock synchronization.
|
|
w.Header().Set("Retry-After", strconv.FormatInt(int64(retryInfo.GetRetryDelay().AsDuration()/time.Second), 10))
|
|
}
|
|
}
|
|
msg, err := enc.marshalStatus(st.Proto())
|
|
if err != nil {
|
|
writeResponse(w, fallbackContentType, http.StatusInternalServerError, fallbackMsg)
|
|
return
|
|
}
|
|
|
|
writeResponse(w, enc.contentType(), statusCode, msg)
|
|
}
|
|
|
|
func writeResponse(w http.ResponseWriter, contentType string, statusCode int, msg []byte) {
|
|
w.Header().Set("Content-Type", contentType)
|
|
w.WriteHeader(statusCode)
|
|
// Nothing we can do with the error if we cannot write to the response.
|
|
_, _ = w.Write(msg)
|
|
}
|
|
|
|
func getMimeTypeFromContentType(contentType string) string {
|
|
mediatype, _, err := mime.ParseMediaType(contentType)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return mediatype
|
|
}
|
|
|
|
func handleUnmatchedMethod(resp http.ResponseWriter) {
|
|
hst := http.StatusMethodNotAllowed
|
|
writeResponse(resp, "text/plain", hst, fmt.Appendf(nil, "%v method not allowed, supported: [POST]", hst))
|
|
}
|
|
|
|
func handleUnmatchedContentType(resp http.ResponseWriter) {
|
|
hst := http.StatusUnsupportedMediaType
|
|
writeResponse(resp, "text/plain", hst, fmt.Appendf(nil, "%v unsupported media type, supported: [%s, %s]", hst, jsonContentType, pbContentType))
|
|
}
|