mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 07:06:36 +08:00
feat(obs): 访问日志 SSOT 与 edge_health,去掉协议兼容层
Agent 仅上报 host_metrics/edge_health/access_logs;业务流量与 UV 由 Server 侧访问日志聚合。新增 of_node_edge_health 与 of_access_log_hourly, 删除 request_reports/openresty 吞吐路径;API 不再暴露 traffic_reports 与 openresty_rx|tx。心跳/离线默认阈值与回填迁移一并入库。
This commit is contained in:
@@ -2,147 +2,52 @@ package nginx
|
||||
|
||||
import "github.com/Rain-kl/Wavelet/internal/apps/agent/protocol"
|
||||
|
||||
const (
|
||||
openRestyObservabilityWindowTTL = "7200"
|
||||
openRestyObservabilityWindowSize = "60"
|
||||
)
|
||||
// Local OpenResty observability endpoint (target model):
|
||||
// GET /openflare/observability returns instantaneous health/connections only.
|
||||
// Business traffic is collected exclusively from access.log.
|
||||
|
||||
const openRestyObservabilityInitLua = `local dict = ngx.shared.openflare_observability
|
||||
if not dict then
|
||||
return
|
||||
end
|
||||
|
||||
return
|
||||
const openRestyObservabilityInitLua = `return
|
||||
`
|
||||
|
||||
const openRestyObservabilityLogLua = `local dict = ngx.shared.openflare_observability
|
||||
if not dict then
|
||||
return
|
||||
end
|
||||
|
||||
local request_uri = tostring(ngx.var.uri or "")
|
||||
if request_uri == "/openflare/observability" or request_uri == "/openflare/stub_status" then
|
||||
return
|
||||
end
|
||||
|
||||
local ttl = ` + openRestyObservabilityWindowTTL + `
|
||||
local now = ngx.time()
|
||||
local window_size = ` + openRestyObservabilityWindowSize + `
|
||||
local window_start = now - (now % window_size)
|
||||
|
||||
local function ensure_counter(key)
|
||||
dict:add(key, 0, ttl)
|
||||
end
|
||||
|
||||
local function incr(key, delta)
|
||||
ensure_counter(key)
|
||||
local value, err = dict:incr(key, delta)
|
||||
if not value and err == "not found" then
|
||||
dict:set(key, delta, ttl)
|
||||
end
|
||||
end
|
||||
|
||||
local function remember_value(list_key, marker_key, value)
|
||||
if value == "" then
|
||||
return
|
||||
end
|
||||
if not dict:add(marker_key, 1, ttl) then
|
||||
return
|
||||
end
|
||||
local existing = dict:get(list_key)
|
||||
if not existing or existing == "" then
|
||||
dict:set(list_key, value, ttl)
|
||||
return
|
||||
end
|
||||
dict:set(list_key, existing .. "\n" .. value, ttl)
|
||||
end
|
||||
|
||||
local window_prefix = tostring(window_start)
|
||||
incr("request_count:" .. window_prefix, 1)
|
||||
|
||||
local status = tostring(ngx.status or 0)
|
||||
if status ~= "0" then
|
||||
incr("status:" .. window_prefix .. ":" .. status, 1)
|
||||
remember_value(
|
||||
"status_keys:" .. window_prefix,
|
||||
"status_marker:" .. window_prefix .. ":" .. status,
|
||||
status
|
||||
)
|
||||
if tonumber(status) and tonumber(status) >= 500 then
|
||||
incr("error_count:" .. window_prefix, 1)
|
||||
end
|
||||
end
|
||||
|
||||
local host = tostring(ngx.var.host or "")
|
||||
if host ~= "" then
|
||||
incr("domain:" .. window_prefix .. ":" .. host, 1)
|
||||
remember_value(
|
||||
"domain_keys:" .. window_prefix,
|
||||
"domain_marker:" .. window_prefix .. ":" .. host,
|
||||
host
|
||||
)
|
||||
end
|
||||
|
||||
local remote_addr = tostring(ngx.var.binary_remote_addr or ngx.var.remote_addr or "")
|
||||
if remote_addr ~= "" and dict:add("visitor:" .. window_prefix .. ":" .. remote_addr, 1, ttl) then
|
||||
incr("unique_visitor_count:" .. window_prefix, 1)
|
||||
end
|
||||
|
||||
local request_length = tonumber(ngx.var.request_length) or 0
|
||||
if request_length > 0 then
|
||||
incr("openresty_rx_bytes:" .. window_prefix, request_length)
|
||||
end
|
||||
|
||||
local bytes_sent = tonumber(ngx.var.bytes_sent) or tonumber(ngx.var.body_bytes_sent) or 0
|
||||
if bytes_sent > 0 then
|
||||
incr("openresty_tx_bytes:" .. window_prefix, bytes_sent)
|
||||
end
|
||||
// log.lua no longer accumulates business counters (access.log is the authority).
|
||||
const openRestyObservabilityLogLua = `return
|
||||
`
|
||||
|
||||
// read.lua exposes stub_status-style connection gauges as JSON.
|
||||
const openRestyObservabilityReadLua = `local cjson = require "cjson.safe"
|
||||
|
||||
local dict = ngx.shared.openflare_observability
|
||||
if not dict then
|
||||
ngx.status = ngx.HTTP_SERVICE_UNAVAILABLE
|
||||
ngx.say(cjson.encode({ message = "shared dict unavailable" }))
|
||||
return
|
||||
end
|
||||
|
||||
local now = ngx.time()
|
||||
local window_size = ` + openRestyObservabilityWindowSize + `
|
||||
local window_start = now - (now % window_size)
|
||||
local current_window = tostring(window_start)
|
||||
|
||||
local function read_counter(key)
|
||||
return tonumber(dict:get(key) or 0) or 0
|
||||
end
|
||||
|
||||
local function read_map(window_id, prefix, list_key)
|
||||
local result = {}
|
||||
local raw = dict:get(list_key .. ":" .. window_id)
|
||||
if not raw or raw == "" then
|
||||
return result
|
||||
local function read_stub_status()
|
||||
local res = ngx.location.capture("/openflare/stub_status")
|
||||
if not res or res.status ~= 200 or not res.body then
|
||||
return nil
|
||||
end
|
||||
for value in string.gmatch(raw, "[^\n]+") do
|
||||
result[value] = read_counter(prefix .. ":" .. window_id .. ":" .. value)
|
||||
end
|
||||
return result
|
||||
local body = res.body
|
||||
local active = tonumber(string.match(body, "Active connections:%s*(%d+)")) or 0
|
||||
local reading = tonumber(string.match(body, "Reading:%s*(%d+)")) or 0
|
||||
local writing = tonumber(string.match(body, "Writing:%s*(%d+)")) or 0
|
||||
local waiting = tonumber(string.match(body, "Waiting:%s*(%d+)")) or 0
|
||||
return {
|
||||
active = active,
|
||||
reading = reading,
|
||||
writing = writing,
|
||||
waiting = waiting
|
||||
}
|
||||
end
|
||||
|
||||
local connections = read_stub_status()
|
||||
local payload = {
|
||||
window_started_at_unix = window_start,
|
||||
window_ended_at_unix = now,
|
||||
request_count = read_counter("request_count:" .. current_window),
|
||||
error_count = read_counter("error_count:" .. current_window),
|
||||
unique_visitor_count = read_counter("unique_visitor_count:" .. current_window),
|
||||
status_codes = read_map(current_window, "status", "status_keys"),
|
||||
top_domains = read_map(current_window, "domain", "domain_keys"),
|
||||
source_countries = {},
|
||||
openresty_rx_bytes = read_counter("openresty_rx_bytes:" .. current_window),
|
||||
openresty_tx_bytes = read_counter("openresty_tx_bytes:" .. current_window)
|
||||
ok = connections ~= nil,
|
||||
captured_at_unix = ngx.time(),
|
||||
connections = connections or {
|
||||
active = 0,
|
||||
reading = 0,
|
||||
writing = 0,
|
||||
waiting = 0
|
||||
}
|
||||
}
|
||||
|
||||
ngx.header.content_type = "application/json"
|
||||
ngx.status = ngx.HTTP_OK
|
||||
ngx.say(cjson.encode(payload))
|
||||
`
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestManagedObservabilityLuaIsHealthOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := ManagedObservabilityLuaFiles()
|
||||
var logLua, readLua string
|
||||
for _, file := range files {
|
||||
switch file.Path {
|
||||
case "log.lua":
|
||||
logLua = file.Content
|
||||
case "read.lua":
|
||||
readLua = file.Content
|
||||
}
|
||||
}
|
||||
if logLua == "" || readLua == "" {
|
||||
t.Fatal("expected log.lua and read.lua")
|
||||
}
|
||||
// Business counters must not be written in log phase.
|
||||
if strings.Contains(logLua, "openresty_rx_bytes") ||
|
||||
strings.Contains(logLua, "request_count") {
|
||||
t.Fatal("log.lua must not accumulate business counters")
|
||||
}
|
||||
if !strings.Contains(readLua, "connections") || !strings.Contains(readLua, "ok") {
|
||||
t.Fatal("read.lua must expose ok + connections health snapshot")
|
||||
}
|
||||
if strings.Contains(readLua, "top_domains") || strings.Contains(readLua, "request_count") {
|
||||
t.Fatal("read.lua must not expose business traffic aggregates")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user