115 lines
2.7 KiB
Go
115 lines
2.7 KiB
Go
package internalhttp
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"galaxy/mail/internal/logging"
|
|
"galaxy/mail/internal/telemetry"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
)
|
|
|
|
type edgeOutcome string
|
|
|
|
const (
|
|
edgeOutcomeSuccess edgeOutcome = "success"
|
|
edgeOutcomeRejected edgeOutcome = "rejected"
|
|
edgeOutcomeFailed edgeOutcome = "failed"
|
|
)
|
|
|
|
func instrumentRoute(route string, logger *slog.Logger, telemetryRuntime *telemetry.Runtime, next http.Handler) http.Handler {
|
|
if logger == nil {
|
|
logger = slog.Default()
|
|
}
|
|
|
|
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
startedAt := time.Now()
|
|
recorder := &observedResponseWriter{
|
|
ResponseWriter: writer,
|
|
statusCode: http.StatusOK,
|
|
}
|
|
|
|
next.ServeHTTP(recorder, request)
|
|
|
|
duration := time.Since(startedAt)
|
|
outcome := outcomeFromStatusCode(recorder.statusCode)
|
|
attrs := []attribute.KeyValue{
|
|
attribute.String("route", route),
|
|
attribute.String("method", request.Method),
|
|
attribute.String("edge_outcome", string(outcome)),
|
|
}
|
|
if recorder.errorCode != "" {
|
|
attrs = append(attrs, attribute.String("error_code", recorder.errorCode))
|
|
}
|
|
if telemetryRuntime != nil {
|
|
telemetryRuntime.RecordInternalHTTPRequest(request.Context(), attrs, duration)
|
|
}
|
|
|
|
logArgs := []any{
|
|
"component", "internal_http",
|
|
"transport", "http",
|
|
"route", route,
|
|
"method", request.Method,
|
|
"status_code", recorder.statusCode,
|
|
"duration_ms", float64(duration.Microseconds()) / 1000,
|
|
"edge_outcome", string(outcome),
|
|
}
|
|
if recorder.errorCode != "" {
|
|
logArgs = append(logArgs, "error_code", recorder.errorCode)
|
|
}
|
|
logArgs = append(logArgs, logging.TraceAttrsFromContext(request.Context())...)
|
|
|
|
switch outcome {
|
|
case edgeOutcomeSuccess:
|
|
logger.Info("internal request completed", logArgs...)
|
|
case edgeOutcomeFailed:
|
|
logger.Error("internal request failed", logArgs...)
|
|
default:
|
|
logger.Warn("internal request rejected", logArgs...)
|
|
}
|
|
})
|
|
}
|
|
|
|
type observedResponseWriter struct {
|
|
http.ResponseWriter
|
|
|
|
statusCode int
|
|
errorCode string
|
|
wroteHeader bool
|
|
}
|
|
|
|
func (writer *observedResponseWriter) WriteHeader(statusCode int) {
|
|
if writer.wroteHeader {
|
|
return
|
|
}
|
|
|
|
writer.statusCode = statusCode
|
|
writer.wroteHeader = true
|
|
writer.ResponseWriter.WriteHeader(statusCode)
|
|
}
|
|
|
|
func (writer *observedResponseWriter) Write(payload []byte) (int, error) {
|
|
if !writer.wroteHeader {
|
|
writer.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
return writer.ResponseWriter.Write(payload)
|
|
}
|
|
|
|
func (writer *observedResponseWriter) SetErrorCode(code string) {
|
|
writer.errorCode = code
|
|
}
|
|
|
|
func outcomeFromStatusCode(statusCode int) edgeOutcome {
|
|
switch {
|
|
case statusCode >= 500:
|
|
return edgeOutcomeFailed
|
|
case statusCode >= 400:
|
|
return edgeOutcomeRejected
|
|
default:
|
|
return edgeOutcomeSuccess
|
|
}
|
|
}
|