diff --git a/README.md b/README.md index f884417..78c906d 100644 --- a/README.md +++ b/README.md @@ -246,9 +246,6 @@ handler := monitor.New(mux, monitor.Config{ Description: "Live production service metrics.", Footer: "Copyright 2026 Example Inc.", FaviconURL: "/assets/favicon.svg", - ServiceName: "payments-api", - Version: "v1.2.0", - Environment: "production", DefaultLanguage: "en", DefaultTheme: "dark", Background: "solid", @@ -274,9 +271,6 @@ Defaults: | `Description` | `Live process, runtime, system, and HTTP metrics for this Go service.` | Short visible description below the header. | | `Footer` | `Powered by github.com/gofurry/monitor - MIT License.` | Footer text for copyright, ownership, or license notes. | | `FaviconURL` | built-in favicon | Overrides the dashboard favicon with a root-relative path or absolute HTTP(S) URL. Empty or invalid values use the built-in favicon. | -| `ServiceName` | executable basename | Service name shown in the UI and snapshot. | -| `Version` | Go module build version | Running service version; a configured value takes precedence. | -| `Environment` | empty | Deployment environment such as `production`. | | `DefaultLanguage` | `en` | Initial UI language when no browser preference is saved. Supported values: `en`, `zh-CN`. | | `DefaultTheme` | `dark` | Initial UI theme when no browser preference is saved. Supported values: `light`, `dark`. | | `Background` | `solid` | HTML page background. Supported values: `solid`, `grid`. | @@ -378,15 +372,6 @@ Use a dedicated observability stack such as Prometheus, Grafana, tracing, and ce "duration_ns": 1850000, "partial": false }, - "service": { - "name": "payments-api", - "version": "v1.2.0", - "environment": "production", - "go_version": "go1.24.6", - "module": "example.com/payments", - "revision": "abc123", - "vcs_modified": false - }, "pid": { "cpu_percent": 2.4, "rss_bytes": 48140288, diff --git a/SECURITY.md b/SECURITY.md index a04a04b..6ca82fc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -33,7 +33,7 @@ This is a small open-source project without a formal security response SLA. Main ## Dashboard Exposure -The monitor endpoint exposes service identity, build metadata, process, runtime, host, network, container, and HTTP traffic statistics. Treat it as an operational endpoint rather than a public application page. +The monitor endpoint exposes process, runtime, host, network, container, and HTTP traffic statistics. Treat it as an operational endpoint rather than a public application page. - Use `Config.Authorize` or an upstream authenticated proxy in production. - Restrict the endpoint at the network layer where practical and serve it only over TLS. @@ -42,4 +42,4 @@ The monitor endpoint exposes service identity, build metadata, process, runtime, `Authorize` runs only for the configured monitor path and before HTTP method handling. A denied request receives `401 Unauthorized` for HTML, JSON, `HEAD`, and unsupported methods. The package does not implement TLS, token storage, rate limiting, or role management. -JSON build metadata is limited to the Go version, module, module version, VCS revision, and modified state. It intentionally excludes the executable path, working directory, environment variables, and remote repository URL. Collection failures expose stable identifiers instead of underlying operating-system error text. +The JSON snapshot intentionally excludes executable paths, working directories, environment variables, build metadata, and remote repository URLs. Collection failures expose stable identifiers instead of underlying operating-system error text. diff --git a/buildinfo.go b/buildinfo.go deleted file mode 100644 index a8a7a6d..0000000 --- a/buildinfo.go +++ /dev/null @@ -1,52 +0,0 @@ -package monitor - -import ( - "os" - "path/filepath" - "runtime" - "runtime/debug" - "strings" -) - -func collectServiceStats(cfg Config) ServiceStats { - stats := ServiceStats{ - Name: strings.TrimSpace(cfg.ServiceName), - Version: strings.TrimSpace(cfg.Version), - Environment: strings.TrimSpace(cfg.Environment), - GoVersion: runtime.Version(), - } - - if info, ok := debug.ReadBuildInfo(); ok && info != nil { - stats.Module = info.Main.Path - if stats.Version == "" { - stats.Version = info.Main.Version - } - for _, setting := range info.Settings { - switch setting.Key { - case "vcs.revision": - stats.Revision = setting.Value - case "vcs.modified": - stats.VCSModified = setting.Value == "true" - } - } - } - if stats.Version == "" { - stats.Version = "(devel)" - } - if stats.Name == "" { - stats.Name = executableName() - } - return stats -} - -func executableName() string { - if executable, err := os.Executable(); err == nil { - if name := filepath.Base(executable); name != "." && name != string(filepath.Separator) { - return name - } - } - if len(os.Args) > 0 { - return filepath.Base(os.Args[0]) - } - return "" -} diff --git a/collect.go b/collect.go index f86900b..ff5289c 100644 --- a/collect.go +++ b/collect.go @@ -29,7 +29,6 @@ func (m *Monitor) collectOnce() { stats := Stats{ SchemaVersion: StatsSchemaVersion, - Service: m.service, PID: pidStats, Runtime: m.collectRuntime(), OS: osStats, diff --git a/config.go b/config.go index 532f457..da0a402 100644 --- a/config.go +++ b/config.go @@ -45,16 +45,6 @@ type Config struct { // the built-in favicon. FaviconURL string - // ServiceName identifies the service in the dashboard and JSON snapshot. - // Empty uses the executable name. - ServiceName string - - // Version identifies the running service version. Empty uses Go build info. - Version string - - // Environment describes the deployment environment, such as "production". - Environment string - // DefaultLanguage controls the initial HTML UI language when the browser has // no saved monitor language preference. Supported values are "en" and // "zh-CN". Empty or unsupported values use "en". diff --git a/docs/monitor-v1.2-implementation-plan.md b/docs/monitor-v1.2-implementation-plan.md index ff58825..568a004 100644 --- a/docs/monitor-v1.2-implementation-plan.md +++ b/docs/monitor-v1.2-implementation-plan.md @@ -30,13 +30,12 @@ 1. JSON Snapshot 缺少版本与采集状态信息。 2. HTTP 指标缺少 RPS、错误率、P50/P95/P99 等生产环境常用指标。 3. 生命周期最大延迟 `max_ns` 长时间运行后参考价值不足。 -4. Monitor 页面缺少服务版本、构建信息、运行环境等上下文。 -5. 容器环境下 Host 指标可能不能真实反映进程资源限制。 -6. Monitor 页面暴露生产运行信息,但目前缺少内建访问控制挂钩。 -7. `Refresh` 可被错误配置为极小值,造成过度采集。 -8. 手工 Request 生命周期 API 存在 in-flight 下溢风险。 -9. CI 仍可补充 race、Windows 与多 Go 版本验证。 -10. JSON / UI 对“指标值为 0”和“采集失败”缺少区分。 +4. 容器环境下 Host 指标可能不能真实反映进程资源限制。 +5. Monitor 页面暴露生产运行信息,但目前缺少内建访问控制挂钩。 +6. `Refresh` 可被错误配置为极小值,造成过度采集。 +7. 手工 Request 生命周期 API 存在 in-flight 下溢风险。 +8. CI 仍可补充 race、Windows 与多 Go 版本验证。 +9. JSON / UI 对“指标值为 0”和“采集失败”缺少区分。 --- @@ -156,11 +155,11 @@ type Stats struct { CollectedAt time.Time `json:"collected_at"` Collection CollectionStats `json:"collection"` - Service ServiceStats `json:"service"` - PID PIDStats `json:"pid"` - Runtime RuntimeStats `json:"runtime"` - OS OSStats `json:"os"` - HTTP HTTPStats `json:"http"` + PID PIDStats `json:"pid"` + Runtime RuntimeStats `json:"runtime"` + OS OSStats `json:"os"` + Container ContainerStats `json:"container"` + HTTP HTTPStats `json:"http"` } ``` @@ -587,100 +586,11 @@ MaxNS = 生命周期最大值 --- -# 9. Service / Build Info +# 9. Header / Identity Scope -## 9.1 目标 +v1.2 不引入服务身份、部署环境或构建信息配置。Monitor 无法为不同项目统一定义这些字段的业务语义,而且这些信息会扩大公共 API 与数据暴露面。 -让用户打开 `/monitor` 后第一眼知道: - -```text -这是哪个服务 -跑的是哪个版本 -是什么环境 -使用什么 Go 版本 -编译版本是什么 -``` - ---- - -## 9.2 配置 - -新增: - -```go -type Config struct { - // existing... - - ServiceName string - Version string - Environment string -} -``` - -推荐默认: - -```text -ServiceName = executable name -Version = build info module version / "(devel)" -Environment = "" -``` - ---- - -## 9.3 自动采集 - -可以通过: - -```go -runtime.Version() -debug.ReadBuildInfo() -os.Executable() -``` - -获得: - -```text -Go Version -Module -Module Version -VCS Revision -VCS Time -VCS Modified -Executable -``` - ---- - -## 9.4 数据结构 - -```go -type ServiceStats struct { - Name string `json:"name,omitempty"` - Version string `json:"version,omitempty"` - Environment string `json:"environment,omitempty"` - - GoVersion string `json:"go_version"` - Module string `json:"module,omitempty"` - Revision string `json:"revision,omitempty"` - VCSModified bool `json:"vcs_modified,omitempty"` -} -``` - ---- - -## 9.5 隐私原则 - -默认不要暴露: - -- 完整 executable filesystem path -- GOPATH -- 工作目录 -- Build Host -- 用户名 -- 环境变量 -- Git Remote URL - -UI/JSON 仅保留必要构建标识。 +界面左上方只展示用户配置的 `Title`。JSON 不包含 service、environment、Go build info、可执行文件路径、工作目录或环境变量。 --- @@ -1313,9 +1223,6 @@ IgnoreRequest 新增: ```text -ServiceName -Version -Environment Authorize ``` @@ -1674,9 +1581,6 @@ runtime usage resource usage disk mount information request traffic -service version -revision -environment name ``` 推荐用户: @@ -1746,18 +1650,13 @@ Snapshot 模型稳定 --- -## Phase 3:Service Info +## Phase 3:Header Scope 任务: -- [ ] Config.ServiceName -- [ ] Config.Version -- [ ] Config.Environment -- [ ] `runtime.Version` -- [ ] `debug.ReadBuildInfo` -- [ ] VCS revision -- [ ] UI header/service section -- [ ] README example +- [ ] 左上方只展示 `Title` +- [ ] 不增加 service identity 配置 +- [ ] 不采集或暴露 Go build info --- @@ -1888,7 +1787,6 @@ v1.2 improves production visibility while keeping monitor lightweight and depend - Recent HTTP 4xx / 5xx / error rates. - Approximate P50 / P95 / P99 request latency. - Recent maximum request latency. -- Service and Go build information. - Optional request authorization hook for the monitor endpoint. - Linux cgroup v2 container memory and CPU limit metrics. - Network receive / transmit metrics. @@ -1897,7 +1795,7 @@ v1.2 improves production visibility while keeping monitor lightweight and depend ### Improved - Monitor UI now distinguishes unavailable metrics from real zero values. -- Added production-oriented service and HTTP summary information. +- Added production-oriented HTTP summary information. - Refresh intervals are clamped to a safe minimum. - Improved CI coverage with race and cross-platform testing. @@ -2029,8 +1927,6 @@ optional collectors v1.2 最重要的成果不是“增加多少个指标”,而是让用户打开 `/monitor` 后,可以在几秒钟内回答: ```text -这个服务是谁? -运行的是哪个版本? 服务活着多久了? 当前请求压力多大? 错误率有没有异常? diff --git a/docs/releases/v1.2.0.md b/docs/releases/v1.2.0.md index 8efbd2f..85a5d3d 100644 --- a/docs/releases/v1.2.0.md +++ b/docs/releases/v1.2.0.md @@ -1,6 +1,6 @@ # v1.2.0 Release Notes -- Add snapshot metadata, service identity, partial collection status, and stable collector error identifiers. +- Add snapshot metadata, partial collection status, and stable collector error identifiers. - Add RPS, recent HTTP error rates, fixed-bucket latency percentiles, and `BeginRequest`. - Add aggregate network I/O and Linux cgroup v2 container limits. - Add optional monitor-path authorization and an expanded live dashboard. diff --git a/docs/zh/README.md b/docs/zh/README.md index b791603..a27c296 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -243,9 +243,6 @@ handler := monitor.New(mux, monitor.Config{ Description: "Live production service metrics.", Footer: "Copyright 2026 Example Inc.", FaviconURL: "/assets/favicon.svg", - ServiceName: "payments-api", - Version: "v1.2.0", - Environment: "production", DefaultLanguage: "en", DefaultTheme: "dark", Background: "solid", @@ -271,9 +268,6 @@ handler := monitor.New(mux, monitor.Config{ | `Description` | `Live process, runtime, system, and HTTP metrics for this Go service.` | 页头下方的简短说明。 | | `Footer` | `Powered by github.com/gofurry/monitor - MIT License.` | 页脚文本,可用于版权、归属或许可证说明。 | | `FaviconURL` | 内嵌 favicon | 用站内相对 URL 或 HTTP(S) 绝对 URL 覆盖仪表盘图标。为空或无效时使用内嵌图标。 | -| `ServiceName` | 可执行文件名 | UI 和快照中显示的服务名。 | -| `Version` | Go module 构建版本 | 当前服务版本;显式配置优先。 | -| `Environment` | 空 | 部署环境,例如 `production`。 | | `DefaultLanguage` | `en` | 浏览器没有保存偏好时的初始 UI 语言。支持值:`en`、`zh-CN`。 | | `DefaultTheme` | `dark` | 浏览器没有保存偏好时的初始 UI 主题。支持值:`light`、`dark`。 | | `Background` | `solid` | HTML 页面背景。支持值:`solid`、`grid`。 | @@ -367,15 +361,6 @@ TLS 终止和网络层访问限制应在本包之外完成,监控 token 不要 "duration_ns": 1850000, "partial": false }, - "service": { - "name": "payments-api", - "version": "v1.2.0", - "environment": "production", - "go_version": "go1.24.6", - "module": "example.com/payments", - "revision": "abc123", - "vcs_modified": false - }, "pid": { "cpu_percent": 2.4, "rss_bytes": 48140288, diff --git a/docs/zh/SECURITY.md b/docs/zh/SECURITY.md index 2ab3661..2039dbb 100644 --- a/docs/zh/SECURITY.md +++ b/docs/zh/SECURITY.md @@ -33,7 +33,7 @@ ## 仪表盘暴露风险 -监控端点会暴露服务标识、构建元数据、进程、runtime、主机、网络、容器和 HTTP 流量统计。请将它视为运维端点,而不是公开应用页面。 +监控端点会暴露进程、runtime、主机、网络、容器和 HTTP 流量统计。请将它视为运维端点,而不是公开应用页面。 - 生产环境使用 `Config.Authorize` 或上游认证代理。 - 在可行时通过网络层限制访问,并且只通过 TLS 提供端点。 @@ -42,4 +42,4 @@ `Authorize` 只作用于配置的监控路径,并在 HTTP 方法处理之前执行。被拒绝的 HTML、JSON、`HEAD` 和不支持的方法请求都会返回 `401 Unauthorized`。本包不提供 TLS、token 存储、限流或角色管理。 -JSON 构建元数据仅包含 Go 版本、module、module version、VCS revision 和 modified 状态,刻意排除可执行文件完整路径、工作目录、环境变量和远程仓库 URL。采集失败只暴露稳定标识,不返回底层操作系统错误文本。 +JSON 快照刻意排除可执行文件完整路径、工作目录、环境变量、构建元数据和远程仓库 URL。采集失败只暴露稳定标识,不返回底层操作系统错误文本。 diff --git a/examples/basic/main.go b/examples/basic/main.go index 7fc938e..d2ad69f 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -44,9 +44,6 @@ func main() { Title: "Example Monitor", Description: "Live process, runtime, system, container, network, and HTTP metrics.", Footer: "Powered by github.com/gofurry/monitor - MIT License.", - ServiceName: "monitor-example", - Version: "v1.2.0-demo", - Environment: "development", DefaultLanguage: "en", DefaultTheme: "dark", DefaultSampleWindow: 60, diff --git a/internal/ui/app.js b/internal/ui/app.js index cb7b7ac..225c1b9 100644 --- a/internal/ui/app.js +++ b/internal/ui/app.js @@ -203,7 +203,6 @@ let currentHTTPStatusCodes = {}; let currentCollectionErrors = new Set(); let currentContainer = {}; - let currentService = {}; let lastCollectedAt = 0; function storageGet(key) { @@ -238,7 +237,6 @@ updateDiskUI(); updateRuntimeDetailUI(); updateHTTPStatusUI(); - updateServiceUI(); updateContainerUI(); } function resolveTheme(mode) { @@ -340,22 +338,11 @@ function unavailable(key) { return currentCollectionErrors.has(key); } - function updateServiceUI() { - const service = currentService || {}; - const name = $("service-name"); - const meta = $("service-meta"); - const parts = []; - if (service.environment) parts.push(service.environment); - if (service.version) parts.push(service.version); - if (service.go_version) parts.push(service.go_version); - name.textContent = service.name || ""; - name.hidden = !service.name; - meta.textContent = parts.join(" · "); - meta.hidden = !parts.length; - } function updateContainerUI() { const container = currentContainer || {}; const card = $("container-card"); + const cards = card.closest(".cards"); + if (cards) cards.classList.toggle("cards--with-container", Boolean(container.detected)); card.hidden = !container.detected; if (!container.detected) return; if (unavailable("container.memory")) { @@ -593,10 +580,8 @@ const latency = http.latency || {}; const rates = http.rates || {}; currentCollectionErrors = new Set(Array.isArray(collection.errors) ? collection.errors : []); - currentService = data.service || {}; currentContainer = data.container || {}; currentRuntime = data.runtime || {}; - updateServiceUI(); updateContainerUI(); $("pid-cpu").textContent = unavailable("pid.cpu") ? "N/A" : pct(pid.cpu_percent); $("pid-rss").textContent = unavailable("pid.memory") ? "N/A" : bytes(pid.rss_bytes); diff --git a/internal/ui/page.html b/internal/ui/page.html index dbb0c5e..7eb6233 100644 --- a/internal/ui/page.html +++ b/internal/ui/page.html @@ -13,8 +13,6 @@

{{ .Title }}

- -
diff --git a/internal/ui/style.css b/internal/ui/style.css index d9c8a64..8ba51c0 100644 --- a/internal/ui/style.css +++ b/internal/ui/style.css @@ -141,17 +141,6 @@ .header-main { min-width: 0; } - .service-name { - margin-top: 8px; - font-size: 1.05rem; - font-weight: 720; - color: var(--text); - } - .service-meta { - margin-top: 3px; - color: var(--muted); - font-size: 0.9rem; - } h1 { margin: 0; overflow-wrap: anywhere; @@ -334,6 +323,11 @@ grid-template-columns: repeat(4, minmax(0, 1fr)); margin-top: 20px; } + @media (min-width: 981px) { + .cards.cards--with-container { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + } .description-card { margin-top: 4px; padding: 12px 14px 12px 16px; diff --git a/monitor.go b/monitor.go index d309577..3b44259 100644 --- a/monitor.go +++ b/monitor.go @@ -26,7 +26,6 @@ type Monitor struct { startedAt time.Time proc *process.Process - service ServiceStats requests atomic.Uint64 inFlight atomic.Uint64 @@ -81,7 +80,6 @@ func NewMonitor(next http.Handler, config ...Config) *Monitor { cfg: cfg, startedAt: time.Now(), proc: currentProcess(), - service: collectServiceStats(cfg), stopCh: make(chan struct{}), doneCh: make(chan struct{}), } diff --git a/monitor_test.go b/monitor_test.go index 17499df..75c4e5a 100644 --- a/monitor_test.go +++ b/monitor_test.go @@ -470,9 +470,9 @@ func TestMonitorHTMLIncludesEnhancedUI(t *testing.T) { `id="http-latency-recent"`, `id="http-latency-p95"`, `id="http-error-rate"`, - `id="service-name"`, - `id="service-meta"`, `id="container-card"`, + `cards--with-container`, + `grid-template-columns: repeat(5, minmax(0, 1fr))`, `id="os-network-rx-rate"`, `id="http-status-button"`, `id="http-status-modal"`, @@ -502,7 +502,6 @@ func TestMonitorHTMLIncludesEnhancedUI(t *testing.T) { `function updateRuntimeDetailUI`, `function updateHTTPStatusUI`, `function updateContainerUI`, - `function updateServiceUI`, `latencyRecentNS`, `latencyP95NS`, `nextGCMiB`, diff --git a/stats.go b/stats.go index ce8eaef..37c1034 100644 --- a/stats.go +++ b/stats.go @@ -6,13 +6,12 @@ import "time" // version of monitor. const StatsSchemaVersion = 1 -// Stats is a point-in-time JSON snapshot of service, process, runtime, system, +// Stats is a point-in-time JSON snapshot of process, runtime, system, // container, and HTTP metrics. type Stats struct { SchemaVersion int `json:"schema_version"` CollectedAt time.Time `json:"collected_at"` Collection CollectionStats `json:"collection"` - Service ServiceStats `json:"service"` PID PIDStats `json:"pid"` Runtime RuntimeStats `json:"runtime"` OS OSStats `json:"os"` @@ -27,17 +26,6 @@ type CollectionStats struct { Errors []string `json:"errors,omitempty"` } -// ServiceStats describes the running service and its Go build metadata. -type ServiceStats struct { - Name string `json:"name,omitempty"` - Version string `json:"version,omitempty"` - Environment string `json:"environment,omitempty"` - GoVersion string `json:"go_version"` - Module string `json:"module,omitempty"` - Revision string `json:"revision,omitempty"` - VCSModified bool `json:"vcs_modified,omitempty"` -} - // PIDStats describes the current process. type PIDStats struct { CPUPercent float64 `json:"cpu_percent"` diff --git a/v12_test.go b/v12_test.go index 8adb5c0..0c80106 100644 --- a/v12_test.go +++ b/v12_test.go @@ -227,25 +227,27 @@ func TestRefreshIsClamped(t *testing.T) { } } -func TestServiceStatsOverridesAndPrivacy(t *testing.T) { - stats := collectServiceStats(Config{ - ServiceName: "example-api", - Version: "v1.2.0", - Environment: "production", - }) - if stats.Name != "example-api" || stats.Version != "v1.2.0" || stats.Environment != "production" { - t.Fatalf("service stats = %+v", stats) - } - if stats.GoVersion == "" { - t.Fatal("Go version is empty") - } - data, err := json.Marshal(stats) +func TestSnapshotAndHeaderOmitServiceIdentity(t *testing.T) { + m := NewMonitor(http.NotFoundHandler(), Config{Refresh: time.Hour}) + defer m.Stop() + + data, err := json.Marshal(m.Current()) if err != nil { t.Fatal(err) } - for _, forbidden := range []string{"executable", "working_directory", "gopath", "remote_url"} { - if strings.Contains(strings.ToLower(string(data)), forbidden) { - t.Fatalf("service JSON contains private field %q: %s", forbidden, data) + var snapshot map[string]json.RawMessage + if err := json.Unmarshal(data, &snapshot); err != nil { + t.Fatal(err) + } + if _, exists := snapshot["service"]; exists { + t.Fatalf("snapshot contains service identity: %s", data) + } + + recorder := httptest.NewRecorder() + m.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/monitor", nil)) + for _, unwanted := range []string{`id="service-name"`, `id="service-meta"`} { + if strings.Contains(recorder.Body.String(), unwanted) { + t.Fatalf("monitor header contains %q", unwanted) } } }