{"record":{"id":"7000610f0acd9fc0","repo":"XTLS/Xray-core","slug":"err-error","errorCode":null,"errorMessage":"err.Error()","messagePattern":"err\\.Error\\(\\)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"warning","filePath":"app/metrics/metrics.go","lineNumber":161,"sourceCode":"\treturn mux\n}\n\nfunc (p *MetricsHandler) handleDebugVars(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\tvars := map[string]json.RawMessage{}\n\texpvar.Do(func(kv expvar.KeyValue) {\n\t\tvalue := json.RawMessage(kv.Value.String())\n\t\tif !json.Valid(value) {\n\t\t\tvalue = json.RawMessage(\"null\")\n\t\t}\n\t\tvars[kv.Key] = value\n\t})\n\tvars[\"stats\"] = marshalJSON(p.stats())\n\tvars[\"observatory\"] = marshalJSON(p.observatoryStatus())\n\n\tpayload, err := json.Marshal(vars)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Write(payload)\n}\n\nfunc marshalJSON(value interface{}) json.RawMessage {\n\tdata, err := json.Marshal(value)\n\tif err != nil {\n\t\treturn json.RawMessage(\"null\")\n\t}\n\treturn data\n}\n\nfunc (p *MetricsHandler) stats() map[string]map[string]map[string]int64 {\n\tresp := map[string]map[string]map[string]int64{\n\t\t\"inbound\":  {},\n\t\t\"outbound\": {},\n\t\t\"user\":     {},","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/metrics/metrics.go#L143-L179","documentation":"The metrics HTTP handler builds a JSON object from all registered expvar variables (plus stats and observatory blocks) and returns 500 with err.Error() if the final json.Marshal fails. Each expvar value is pre-validated with json.Valid and replaced by null when malformed, so a marshal failure here is nearly impossible in practice — it would require an invalid RawMessage to slip past validation or a map key collision producing an unencodable value. The error message surfaced to the client is the raw encoding/json error.","triggerScenarios":"GET on the metrics endpoint while some concurrently-updating expvar produces a .String() that races the json.Valid check and the marshal (mutation between validation and encoding), or a custom expvar publishing invalid JSON characters after validation.","commonSituations":"Custom expvars registered by plugins whose String() is not stable JSON; high-churn counters observed concurrently; realistically most users never see this path.","solutions":["Ensure every custom expvar's String() returns stable, valid JSON at all times (lock internal state while serializing).","If it fires intermittently under load, suspect a racy String(): guard the var's internals with a mutex so validation and marshal see the same bytes.","Optionally marshal-then-fallback to null for the whole payload instead of a 500, so monitoring scrapes don't fail."],"exampleFix":"// before\npayload, err := json.Marshal(vars)\nif err != nil {\n    http.Error(w, err.Error(), http.StatusInternalServerError)\n    return\n}\nw.Write(payload)\n\n// after: never fail the scrape, emit a minimal valid body instead\npayload, err := json.Marshal(vars)\nif err != nil {\n    log.Warnf(\"metrics marshal failed: %v\", err)\n    w.WriteHeader(http.StatusInternalServerError)\n    w.Write([]byte(`{\"error\":\"marshal failed\"}`))\n    return\n}\nw.Write(payload)","handlingStrategy":"fallback","validationCode":"expvar.Do(func(kv expvar.KeyValue) {\n    s := kv.Value.String()\n    if !json.Valid([]byte(s)) {\n        s = \"null\" // sanitize before it reaches the handler\n    }\n    vars[kv.Key] = json.RawMessage(s)\n})","typeGuard":null,"tryCatchPattern":"payload, err := json.Marshal(vars)\nif err != nil {\n    log.Warnf(\"metrics marshal failed: %v\", err)\n    w.Header().Set(\"Content-Type\", \"application/json\")\n    w.WriteHeader(http.StatusInternalServerError)\n    _, _ = w.Write([]byte(`{\"error\":\"metrics marshal failed\"}`))\n    return\n}","preventionTips":["Make every custom expvar's String() return stable, valid JSON under concurrent mutation (lock internals).","Test the metrics endpoint in CI with all your expvars registered.","Never leak raw marshal errors to scrapers; log internally and return a valid JSON error body."],"tags":["metrics","json","expvar","http-handler","observability"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}