alot more changes
This commit is contained in:
1 parent
23243ddbdc
commit
0ed2935f04
40 files changed
+2889
-115
No files matched your search
@@ -4,7 +4,9 @@ package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.tornberg.me/go-otel/internal/sourcemaps"
|
||||
@@ -20,6 +22,7 @@ import (
|
||||
"git.tornberg.me/go-otel/internal/issues"
|
||||
"git.tornberg.me/go-otel/internal/storage"
|
||||
"git.tornberg.me/go-otel/internal/websocket"
|
||||
"go.opentelemetry.io/collector/pdata/pcommon"
|
||||
"go.opentelemetry.io/collector/pdata/plog"
|
||||
"go.opentelemetry.io/collector/pdata/pmetric"
|
||||
"go.opentelemetry.io/collector/pdata/ptrace"
|
||||
@@ -477,32 +480,116 @@ func encodeIssueCursor(lastSeen int64, id string) string {
|
||||
// HandleSourceMap accepts a JavaScript source map for a deployed release.
|
||||
// Multipart fields: release, source, file.
|
||||
func (c *Collector) HandleSourceMap(w http.ResponseWriter, r *http.Request) {
|
||||
traceID, spanID := traceContextFromRequest(r)
|
||||
logUpload := func(severity plog.SeverityNumber, body string, attrs map[string]any) {
|
||||
if err := c.EmitInternalLog(r.Context(), severity, body, attrs, traceID, spanID); err != nil {
|
||||
log.Printf("failed to emit sourcemap diagnostic: %v", err)
|
||||
}
|
||||
}
|
||||
logUpload(plog.SeverityNumberInfo, "sourcemap upload received", map[string]any{
|
||||
"event.name": "sourcemap.upload",
|
||||
"http.route": "/api/sourcemaps",
|
||||
"http.request.method": r.Method,
|
||||
})
|
||||
if r.Method != http.MethodPost {
|
||||
logUpload(plog.SeverityNumberWarn, "sourcemap upload rejected: method not allowed", map[string]any{
|
||||
"event.name": "sourcemap.upload.rejected",
|
||||
"sourcemap.failure_stage": "method",
|
||||
})
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if !c.checkSourceMapKey(r) {
|
||||
logUpload(plog.SeverityNumberWarn, "sourcemap upload rejected: invalid or missing key", map[string]any{
|
||||
"event.name": "sourcemap.upload.rejected",
|
||||
"sourcemap.failure_stage": "auth",
|
||||
"client.address": clientIP(r),
|
||||
})
|
||||
http.Error(w, "invalid or missing sourcemap key", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if c.sourceMaps == nil {
|
||||
logUpload(plog.SeverityNumberError, "sourcemap upload failed: resolver is not configured", map[string]any{
|
||||
"event.name": "sourcemap.upload.failed",
|
||||
"sourcemap.failure_stage": "configuration",
|
||||
})
|
||||
http.Error(w, "source maps are not configured", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
if err := r.ParseMultipartForm(50 << 20); err != nil {
|
||||
logUpload(plog.SeverityNumberError, "sourcemap upload failed: invalid multipart body", map[string]any{
|
||||
"event.name": "sourcemap.upload.failed",
|
||||
"sourcemap.failure_stage": "multipart",
|
||||
"error.type": "multipart_parse",
|
||||
})
|
||||
http.Error(w, "invalid multipart body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
release, source := r.FormValue("release"), r.FormValue("source")
|
||||
file, _, err := r.FormFile("file")
|
||||
file, fileHeader, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
logUpload(plog.SeverityNumberError, "sourcemap upload failed: file is missing", map[string]any{
|
||||
"event.name": "sourcemap.upload.failed",
|
||||
"sourcemap.failure_stage": "file",
|
||||
"sourcemap.release": release,
|
||||
"sourcemap.source": source,
|
||||
"error.type": "missing_file",
|
||||
})
|
||||
http.Error(w, "file is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
if err := c.sourceMaps.Upload(release, source, file); err != nil {
|
||||
logUpload(plog.SeverityNumberError, "sourcemap upload failed", map[string]any{
|
||||
"event.name": "sourcemap.upload.failed",
|
||||
"sourcemap.failure_stage": "store",
|
||||
"sourcemap.release": release,
|
||||
"sourcemap.source": source,
|
||||
"sourcemap.size_bytes": fileHeader.Size,
|
||||
"error.type": "source_map_invalid_or_unwritable",
|
||||
"error.message": err.Error(),
|
||||
})
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
logUpload(plog.SeverityNumberInfo, "sourcemap upload stored", map[string]any{
|
||||
"event.name": "sourcemap.upload.stored",
|
||||
"sourcemap.release": release,
|
||||
"sourcemap.source": source,
|
||||
"sourcemap.size_bytes": fileHeader.Size,
|
||||
})
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
// checkSourceMapKey authenticates a source map upload against the shared
|
||||
// OTEL_SOURCEMAP_KEY (X-Sourcemap-Key header). Uploads are refused while no key
|
||||
// is configured so the endpoint is never open by accident.
|
||||
func (c *Collector) checkSourceMapKey(r *http.Request) bool {
|
||||
if c.sourceMapKey == "" {
|
||||
return false
|
||||
}
|
||||
got := r.Header.Get("X-Sourcemap-Key")
|
||||
return subtle.ConstantTimeCompare([]byte(got), []byte(c.sourceMapKey)) == 1
|
||||
}
|
||||
|
||||
// traceContextFromRequest preserves the caller's trace linkage for internal
|
||||
// logs when the upload came through an instrumented client or proxy.
|
||||
func traceContextFromRequest(r *http.Request) (pcommon.TraceID, pcommon.SpanID) {
|
||||
var traceID pcommon.TraceID
|
||||
var spanID pcommon.SpanID
|
||||
parts := strings.Split(r.Header.Get("Traceparent"), "-")
|
||||
if len(parts) != 4 || len(parts[1]) != 32 || len(parts[2]) != 16 {
|
||||
return traceID, spanID
|
||||
}
|
||||
if _, err := hex.Decode(traceID[:], []byte(parts[1])); err != nil {
|
||||
return pcommon.NewTraceIDEmpty(), pcommon.NewSpanIDEmpty()
|
||||
}
|
||||
if _, err := hex.Decode(spanID[:], []byte(parts[2])); err != nil {
|
||||
return pcommon.NewTraceIDEmpty(), pcommon.NewSpanIDEmpty()
|
||||
}
|
||||
return traceID, spanID
|
||||
}
|
||||
|
||||
// HandleListSourceMaps lists the release/source pairs currently loaded, so a
|
||||
// deploy can verify its uploads (and that they survived a restart).
|
||||
func (c *Collector) HandleListSourceMaps(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -518,6 +605,18 @@ func (c *Collector) HandleListSourceMaps(w http.ResponseWriter, r *http.Request)
|
||||
// uploaded source maps. Body: {"items":[{"release":"...","stacktrace":"..."}]};
|
||||
// the response has one resolved stacktrace per item, in order.
|
||||
func (c *Collector) HandleResolveStacks(w http.ResponseWriter, r *http.Request) {
|
||||
traceID, spanID := traceContextFromRequest(r)
|
||||
logResolve := func(severity plog.SeverityNumber, body string, attrs map[string]any) {
|
||||
if err := c.EmitInternalLog(r.Context(), severity, body, attrs, traceID, spanID); err != nil {
|
||||
log.Printf("failed to emit sourcemap diagnostic: %v", err)
|
||||
}
|
||||
}
|
||||
logResolve(plog.SeverityNumberInfo, "sourcemap resolution received", map[string]any{
|
||||
"event.name": "sourcemap.resolve",
|
||||
"http.route": "/api/sourcemaps/resolve",
|
||||
"http.request.method": r.Method,
|
||||
"sourcemap.resolver_ready": c.sourceMaps != nil,
|
||||
})
|
||||
var req struct {
|
||||
Items []struct {
|
||||
Release string `json:"release"`
|
||||
@@ -525,11 +624,17 @@ func (c *Collector) HandleResolveStacks(w http.ResponseWriter, r *http.Request)
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<20)).Decode(&req); err != nil {
|
||||
logResolve(plog.SeverityNumberError, "sourcemap resolution failed: invalid body", map[string]any{
|
||||
"event.name": "sourcemap.resolve.failed",
|
||||
"sourcemap.failure_stage": "decode",
|
||||
"error.type": "invalid_json",
|
||||
})
|
||||
http.Error(w, "invalid body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
out := make([]string, len(req.Items))
|
||||
frames := make([][]sourcemaps.Frame, len(req.Items))
|
||||
statusCounts := map[string]int{}
|
||||
for i, it := range req.Items {
|
||||
out[i] = it.Stacktrace
|
||||
frames[i] = []sourcemaps.Frame{}
|
||||
@@ -537,7 +642,20 @@ func (c *Collector) HandleResolveStacks(w http.ResponseWriter, r *http.Request)
|
||||
out[i] = c.sourceMaps.ResolveStack(it.Release, it.Stacktrace)
|
||||
frames[i] = c.sourceMaps.ResolveFrames(it.Release, it.Stacktrace)
|
||||
}
|
||||
for _, frame := range frames[i] {
|
||||
statusCounts[frame.Status]++
|
||||
}
|
||||
}
|
||||
logResolve(plog.SeverityNumberInfo, "sourcemap resolution completed", map[string]any{
|
||||
"event.name": "sourcemap.resolve.completed",
|
||||
"sourcemap.item_count": len(req.Items),
|
||||
"sourcemap.frame_count": statusCounts["mapped"] + statusCounts["no-release"] + statusCounts["no-map"] + statusCounts["unmapped"],
|
||||
"sourcemap.mapped": statusCounts["mapped"],
|
||||
"sourcemap.no_release": statusCounts["no-release"],
|
||||
"sourcemap.no_map": statusCounts["no-map"],
|
||||
"sourcemap.unmapped": statusCounts["unmapped"],
|
||||
"sourcemap.resolver_ready": c.sourceMaps != nil,
|
||||
})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"resolved": out, "frames": frames})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user