diff --git a/docs/changelog/index.md b/docs/changelog/index.md index 33750dd5..f5d0fbf5 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -28,6 +28,7 @@ sidebar: false - 边缘访问日志现支持上报并存储 User-Agent;概览新增设备类型饼图,以及浏览器、操作系统、User-Agent 排行。 - 访问日志明细支持查看单条请求详情,可查看 User-Agent、IP 访问趋势与分析,并快捷将 IP 加入或移出 IP 组。 - 访问日志页签状态改为 URL 参数记忆,筛选后不会再跳回概览。 +- 访问日志支持边缘缓存状态(`cache_status`):明细列表与详情可查看命中 / 回源 / 未缓存。 - 优化访问日志概览的设备类型与状态码饼图布局,使设备类型与状态码饼图在 1024px(lg)以上屏幕保持双列并排展示,提升页面紧凑度。 - 修复访问日志概览在 ClickHouse 查询发生错误时静默吞掉报错的缺陷,引入错误日志输出,方便定位问题。 diff --git a/docs/docs.go b/docs/docs.go index b8e93d1e..c2a86b40 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -14390,6 +14390,10 @@ const docTemplate = `{ "description": "body bytes = 已提供数据", "type": "integer" }, + "cache_status": { + "description": "$upstream_cache_status", + "type": "string" + }, "host": { "type": "string" }, @@ -16511,6 +16515,9 @@ const docTemplate = `{ "observability.AccessLogView": { "type": "object", "properties": { + "cache_status": { + "type": "string" + }, "host": { "type": "string" }, diff --git a/docs/plan/20260718-access-log-cache-status.md b/docs/plan/20260718-access-log-cache-status.md new file mode 100644 index 00000000..0afd124a --- /dev/null +++ b/docs/plan/20260718-access-log-cache-status.md @@ -0,0 +1,62 @@ +# 访问日志 cache_status 明细可见 — 实现计划 + +说明:对应设计 [observability-data-model.md §3.5.1](../design/observability-data-model.md)。第一期只做明细可见,不上报 upstream 地址。 + +--- + +## 1. 目标与背景 + +* **需求背景**:访问日志无法判断请求是否命中边缘缓存、是否回源。 +* **开发范围 (Scope)**: + * **必做**:OpenResty 日志输出 `$upstream_cache_status`;Agent 上报;CH 入库;列表/详情展示三态标签。 + * **Out of Scope**:命中率看板、hourly 维度、`upstream_addr`。 + +--- + +## 2. 设计与决策 + +* **唯一字段**:`cache_status` string(原始值)。 +* **UI 三态(不落库)**: + * 命中:`HIT` / `STALE` / `REVALIDATED` / `UPDATING` + * 回源:`MISS` / `EXPIRED` + * 未缓存:`BYPASS` / `-` / 空 +* **数据流**:log_format → Agent parse → protocol → Server model → CH → API → 前端明细。 + +--- + +## 3. 修改清单 + +### 边缘 / 协议 + +* `pkg/render/openresty/types.go`、`internal/model/openflare_option.go`:`log_format` 增加 `cache_status` +* `internal/apps/agent/observability/traffic.go`:解析与映射 +* `pkg/protocol/agent.go`:`NodeAccessLog.CacheStatus` + +### Server / CH + +* goose:`202607180005_access_log_cache_status.sql` +* `internal/model/analytics/node_access_log.go`、writer、list/scan、store 映射 +* `internal/model/openflare_observability.go`、agent build records +* API `AccessLogView` + list 响应带 `cache_status` + +### 前端 + +* types / 明细列表标签 / 详情字段 +* 三态 helper:`resolveCacheOutcome(cache_status)` + +--- + +## 4. 验证 + +* `go test ./internal/apps/agent/observability/ ./internal/repository/analytics/ ./internal/apps/openflare/agent/` +* `make swagger`(若 Handler 响应结构变更) +* `make code-check` / `make prettier` + +--- + +## 5. 落地进度 + +* [x] log_format + protocol + agent parse +* [x] CH migration + 写入/读取 +* [x] API + 前端明细展示 +* [ ] 测试与提交 diff --git a/docs/plan/index.md b/docs/plan/index.md index 5c31cf48..7ecb8bda 100644 --- a/docs/plan/index.md +++ b/docs/plan/index.md @@ -16,6 +16,7 @@ * [Zone 与域名资源重构](./20260712-zone-domain-refactor.md):以 Zone 和正规化 Zone 域名替代托管域名及反代路由中的域名/证书冗余字段。 * [WAF 可编排规则](./20260713-waf-orchestration.md):使用 React Flow 编辑 DAG 规则,发布时编译并由 OpenResty 纯内存执行。 * [边缘可观测与业务流量统计重构](./20260717-observability-redesign.md):访问日志为业务唯一真相;Agent 只上报明细与主机读数;收敛「出站/已提供」双字段。 +* [访问日志 cache_status 明细可见](./20260718-access-log-cache-status.md):上报 `$upstream_cache_status`,明细展示命中/回源/未缓存三态。 ## 使用建议 diff --git a/docs/swagger.json b/docs/swagger.json index 470cbd95..4e5ef29d 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -14383,6 +14383,10 @@ "description": "body bytes = 已提供数据", "type": "integer" }, + "cache_status": { + "description": "$upstream_cache_status", + "type": "string" + }, "host": { "type": "string" }, @@ -16504,6 +16508,9 @@ "observability.AccessLogView": { "type": "object", "properties": { + "cache_status": { + "type": "string" + }, "host": { "type": "string" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 456f9846..07fb3166 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -657,6 +657,9 @@ definitions: bytes_sent: description: body bytes = 已提供数据 type: integer + cache_status: + description: $upstream_cache_status + type: string host: type: string logged_at_unix: @@ -2065,6 +2068,8 @@ definitions: type: object observability.AccessLogView: properties: + cache_status: + type: string host: type: string id: diff --git a/frontend/app/(main)/access-logs/components/access-log-detail-dialog.tsx b/frontend/app/(main)/access-logs/components/access-log-detail-dialog.tsx index 5c1864b4..b50b59ef 100644 --- a/frontend/app/(main)/access-logs/components/access-log-detail-dialog.tsx +++ b/frontend/app/(main)/access-logs/components/access-log-detail-dialog.tsx @@ -46,9 +46,11 @@ import { formatBytes, formatCompactNumber } from '@/lib/utils/metrics'; import { buildIPGroupPayloadFromGroup } from '../../waf/components/helpers'; import { + cacheOutcomeLabel, formatOverviewRangeHint, formatOverviewTrendLabel, OVERVIEW_RANGE_OPTIONS, + resolveCacheOutcome, type OverviewRangeHours, } from './access-log-utils'; @@ -463,6 +465,21 @@ export function AccessLogDetailDialog({ } /> + + + {cacheOutcomeLabel( + resolveCacheOutcome(activeItem.cache_status), + )} + + + {activeItem.cache_status || '—'} + + + } + /> IP 域名 路径 + 缓存 状态码 - {(data?.items ?? []).map((item) => ( - - - {formatDateTime(item.logged_at)} - - - {item.node_name || item.node_id} - - - {item.remote_addr} - - {item.host} - - {item.path} - - - - {item.status_code} - - - - - - - - - 查看详情 - - - - - ))} + {(data?.items ?? []).map((item) => { + const outcome = resolveCacheOutcome(item.cache_status); + return ( + + + {formatDateTime(item.logged_at)} + + + {item.node_name || item.node_id} + + + {item.remote_addr} + + {item.host} + + {item.path} + + + + {cacheOutcomeLabel(outcome)} + + + + + {item.status_code} + + + + + + + + + 查看详情 + + + + + ); + })} )} diff --git a/frontend/lib/services/openflare/types.ts b/frontend/lib/services/openflare/types.ts index fd20e6a7..f023eabd 100644 --- a/frontend/lib/services/openflare/types.ts +++ b/frontend/lib/services/openflare/types.ts @@ -544,6 +544,7 @@ export interface AccessLogItem { host: string; path: string; user_agent: string; + cache_status: string; status_code: number; } diff --git a/internal/apps/agent/observability/traffic.go b/internal/apps/agent/observability/traffic.go index 1ebd31a4..1cec9f60 100644 --- a/internal/apps/agent/observability/traffic.go +++ b/internal/apps/agent/observability/traffic.go @@ -23,6 +23,7 @@ type accessLogRecord struct { RemoteAddr string `json:"remote_addr"` Path string `json:"path"` UserAgent string `json:"user_agent"` + CacheStatus string `json:"cache_status"` Status int `json:"status"` BytesSent int64 `json:"bytes_sent"` RequestLength int64 `json:"request_length"` @@ -147,6 +148,7 @@ func (aggregate *trafficAggregate) consume(line []byte) { Host: strings.TrimSpace(record.Host), Path: normalizeAccessLogPath(record.Path), UserAgent: strings.TrimSpace(record.UserAgent), + CacheStatus: normalizeCacheStatus(record.CacheStatus), StatusCode: record.Status, BytesSent: record.BytesSent, RequestLength: record.RequestLength, @@ -160,6 +162,7 @@ type parsedAccessLogRecord struct { RemoteAddr string Path string UserAgent string + CacheStatus string Status int BytesSent int64 RequestLength int64 @@ -193,6 +196,7 @@ func parseJSONAccessLogRecord(raw string) (parsedAccessLogRecord, bool) { RemoteAddr: strings.TrimSpace(record.RemoteAddr), Path: normalizeAccessLogPath(record.Path), UserAgent: strings.TrimSpace(record.UserAgent), + CacheStatus: normalizeCacheStatus(record.CacheStatus), Status: record.Status, BytesSent: record.BytesSent, RequestLength: record.RequestLength, @@ -200,6 +204,14 @@ func parseJSONAccessLogRecord(raw string) (parsedAccessLogRecord, bool) { }, true } +func normalizeCacheStatus(value string) string { + trimmed := strings.TrimSpace(value) + if trimmed == "-" { + return "" + } + return trimmed +} + func parseCombinedAccessLogRecord(raw string) (parsedAccessLogRecord, bool) { matches := combinedAccessLogPattern.FindStringSubmatch(raw) if len(matches) != combinedAccessLogMatchGroupCount { diff --git a/internal/apps/agent/observability/traffic_test.go b/internal/apps/agent/observability/traffic_test.go index b7cc9696..3097addd 100644 --- a/internal/apps/agent/observability/traffic_test.go +++ b/internal/apps/agent/observability/traffic_test.go @@ -14,8 +14,8 @@ func TestCollectAccessLogsReturnsFactsOnly(t *testing.T) { tempDir := t.TempDir() logPath := filepath.Join(tempDir, "openflare_access.log") content := []byte( - "{\"ts\":\"2026-03-14T08:00:00Z\",\"host\":\"app.example.com\",\"path\":\"/login\",\"remote_addr\":\"10.0.0.1\",\"status\":200,\"request_length\":128,\"bytes_sent\":512,\"request_time\":0.015,\"user_agent\":\"Mozilla/5.0\"}\n" + - "{\"ts\":\"2026-03-14T08:00:05Z\",\"host\":\"api.example.com\",\"path\":\"/v1/ping\",\"remote_addr\":\"10.0.0.2\",\"status\":502,\"request_length\":64,\"bytes_sent\":256,\"request_time\":0.008,\"user_agent\":\"curl/8.0\"}\n", + "{\"ts\":\"2026-03-14T08:00:00Z\",\"host\":\"app.example.com\",\"path\":\"/login\",\"remote_addr\":\"10.0.0.1\",\"status\":200,\"request_length\":128,\"bytes_sent\":512,\"request_time\":0.015,\"user_agent\":\"Mozilla/5.0\",\"cache_status\":\"HIT\"}\n" + + "{\"ts\":\"2026-03-14T08:00:05Z\",\"host\":\"api.example.com\",\"path\":\"/v1/ping\",\"remote_addr\":\"10.0.0.2\",\"status\":502,\"request_length\":64,\"bytes_sent\":256,\"request_time\":0.008,\"user_agent\":\"curl/8.0\",\"cache_status\":\"MISS\"}\n", ) if err := os.WriteFile(logPath, content, 0o644); err != nil { t.Fatalf("WriteFile failed: %v", err) @@ -38,6 +38,9 @@ func TestCollectAccessLogsReturnsFactsOnly(t *testing.T) { if accessLogs[0].UserAgent != "Mozilla/5.0" || accessLogs[1].UserAgent != "curl/8.0" { t.Fatalf("unexpected user agents: %+v", accessLogs) } + if accessLogs[0].CacheStatus != "HIT" || accessLogs[1].CacheStatus != "MISS" { + t.Fatalf("unexpected cache status: %+v", accessLogs) + } snapshot, err := stateStore.Load() if err != nil { diff --git a/internal/apps/openflare/agent/observability.go b/internal/apps/openflare/agent/observability.go index c2ca984a..066da8c9 100644 --- a/internal/apps/openflare/agent/observability.go +++ b/internal/apps/openflare/agent/observability.go @@ -24,7 +24,8 @@ const ( healthSeverityWarning = "warning" healthSeverityCritical = "critical" accessLogPathMaxLength = 100 - accessLogUserAgentMaxLength = 512 + accessLogUserAgentMaxLength = 512 + accessLogCacheStatusMaxLength = 32 healthEventMessageMaxLength = 4096 ) @@ -210,6 +211,7 @@ func buildNodeAccessLogRecords(nodeID string, direct []NodeAccessLog, buffered [ Host: strings.TrimSpace(item.Host), Path: truncateForDatabase(strings.TrimSpace(item.Path), accessLogPathMaxLength), UserAgent: truncateForDatabase(strings.TrimSpace(item.UserAgent), accessLogUserAgentMaxLength), + CacheStatus: truncateForDatabase(strings.TrimSpace(item.CacheStatus), accessLogCacheStatusMaxLength), StatusCode: item.StatusCode, BytesSent: bytesSent, RequestLength: requestLength, diff --git a/internal/apps/openflare/observability/access_log_logics.go b/internal/apps/openflare/observability/access_log_logics.go index 823c2289..8a89ace8 100644 --- a/internal/apps/openflare/observability/access_log_logics.go +++ b/internal/apps/openflare/observability/access_log_logics.go @@ -46,16 +46,17 @@ type AccessLogQuery struct { // AccessLogView is a single access log row. type AccessLogView struct { - ID string `json:"id"` - NodeID string `json:"node_id"` - NodeName string `json:"node_name"` - LoggedAt time.Time `json:"logged_at"` - RemoteAddr string `json:"remote_addr"` - Region string `json:"region"` - Host string `json:"host"` - Path string `json:"path"` - UserAgent string `json:"user_agent"` - StatusCode int `json:"status_code"` + ID string `json:"id"` + NodeID string `json:"node_id"` + NodeName string `json:"node_name"` + LoggedAt time.Time `json:"logged_at"` + RemoteAddr string `json:"remote_addr"` + Region string `json:"region"` + Host string `json:"host"` + Path string `json:"path"` + UserAgent string `json:"user_agent"` + CacheStatus string `json:"cache_status"` + StatusCode int `json:"status_code"` } // AccessLogList is a paginated access log response. @@ -476,16 +477,17 @@ func ListAccessLogs(ctx context.Context, input AccessLogQuery) (*AccessLogList, continue } views = append(views, AccessLogView{ - ID: formatAccessLogID(item.ID), - NodeID: item.NodeID, - NodeName: nodeNames[item.NodeID], - LoggedAt: item.LoggedAt, - RemoteAddr: item.RemoteAddr, - Region: item.Region, - Host: item.Host, - Path: item.Path, - UserAgent: item.UserAgent, - StatusCode: item.StatusCode, + ID: formatAccessLogID(item.ID), + NodeID: item.NodeID, + NodeName: nodeNames[item.NodeID], + LoggedAt: item.LoggedAt, + RemoteAddr: item.RemoteAddr, + Region: item.Region, + Host: item.Host, + Path: item.Path, + UserAgent: item.UserAgent, + CacheStatus: item.CacheStatus, + StatusCode: item.StatusCode, }) } return &AccessLogList{ diff --git a/internal/db/migrator/goose/clickhouse/202607180005_access_log_cache_status.sql b/internal/db/migrator/goose/clickhouse/202607180005_access_log_cache_status.sql new file mode 100644 index 00000000..492ae702 --- /dev/null +++ b/internal/db/migrator/goose/clickhouse/202607180005_access_log_cache_status.sql @@ -0,0 +1,6 @@ +-- +goose Up +ALTER TABLE of_node_access_logs + ADD COLUMN IF NOT EXISTS cache_status String DEFAULT ''; + +-- +goose Down +ALTER TABLE of_node_access_logs DROP COLUMN IF EXISTS cache_status; diff --git a/internal/model/analytics/node_access_log.go b/internal/model/analytics/node_access_log.go index 2ab62e01..af99aa26 100644 --- a/internal/model/analytics/node_access_log.go +++ b/internal/model/analytics/node_access_log.go @@ -10,7 +10,7 @@ import ( const ( nodeAccessLogTableName = "of_node_access_logs" - nodeAccessLogInsertColumns = "id, node_id, logged_at, remote_addr, region, host, path, user_agent, status_code, bytes_sent, request_length, request_time_ms, created_at" + nodeAccessLogInsertColumns = "id, node_id, logged_at, remote_addr, region, host, path, user_agent, cache_status, status_code, bytes_sent, request_length, request_time_ms, created_at" ) // NodeAccessLog stores OpenFlare edge node access records in ClickHouse. @@ -23,6 +23,7 @@ type NodeAccessLog struct { Host string `gorm:"column:host"` Path string `gorm:"column:path"` UserAgent string `gorm:"column:user_agent"` + CacheStatus string `gorm:"column:cache_status"` StatusCode int32 `gorm:"column:status_code"` BytesSent uint64 `gorm:"column:bytes_sent"` RequestLength uint64 `gorm:"column:request_length"` diff --git a/internal/model/openflare_access_log_store.go b/internal/model/openflare_access_log_store.go index dced6bca..7591771e 100644 --- a/internal/model/openflare_access_log_store.go +++ b/internal/model/openflare_access_log_store.go @@ -285,6 +285,7 @@ func toAnalyticsNodeAccessLog(record *OpenFlareAccessLog) analyticsmodel.NodeAcc Host: record.Host, Path: record.Path, UserAgent: record.UserAgent, + CacheStatus: record.CacheStatus, StatusCode: openFlareAccessLogStatusCodeToInt32(record.StatusCode), BytesSent: bytesSent, RequestLength: requestLength, @@ -317,6 +318,7 @@ func fromAnalyticsNodeAccessLogs(rows []analyticsmodel.NodeAccessLog) []*OpenFla Host: row.Host, Path: row.Path, UserAgent: row.UserAgent, + CacheStatus: row.CacheStatus, StatusCode: int(row.StatusCode), BytesSent: bytesSent, RequestLength: requestLength, diff --git a/internal/model/openflare_observability.go b/internal/model/openflare_observability.go index dd07ba73..eb94b69b 100644 --- a/internal/model/openflare_observability.go +++ b/internal/model/openflare_observability.go @@ -48,6 +48,7 @@ type OpenFlareAccessLog struct { Host string `json:"host" gorm:"index;size:255"` Path string `json:"path" gorm:"size:2048"` UserAgent string `json:"user_agent" gorm:"column:user_agent;size:512"` + CacheStatus string `json:"cache_status" gorm:"column:cache_status;size:32"` StatusCode int `json:"status_code" gorm:"index"` BytesSent int64 `json:"bytes_sent" gorm:"column:bytes_sent;not null;default:0"` RequestLength int64 `json:"request_length" gorm:"column:request_length;not null;default:0"` diff --git a/internal/model/openflare_option.go b/internal/model/openflare_option.go index 48d69157..b889bd58 100644 --- a/internal/model/openflare_option.go +++ b/internal/model/openflare_option.go @@ -38,7 +38,7 @@ http { fastcgi_temp_path __OPENFLARE_NGINX_CACHE_DIR__/fastcgi_temp; uwsgi_temp_path __OPENFLARE_NGINX_CACHE_DIR__/uwsgi_temp; scgi_temp_path __OPENFLARE_NGINX_CACHE_DIR__/scgi_temp; -{{OpenRestyConnectionUpgradeMap}}{{OpenRestyDefaultServerBlock}} log_format openflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length,"user_agent":"$http_user_agent"}'; +{{OpenRestyConnectionUpgradeMap}}{{OpenRestyDefaultServerBlock}} log_format openflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length,"user_agent":"$http_user_agent","cache_status":"$upstream_cache_status"}'; access_log {{OpenRestyAccessLogPath}} openflare_json; sendfile on; tcp_nopush on; diff --git a/internal/repository/analytics/node_access_log.go b/internal/repository/analytics/node_access_log.go index a53ca80a..36092413 100644 --- a/internal/repository/analytics/node_access_log.go +++ b/internal/repository/analytics/node_access_log.go @@ -36,7 +36,7 @@ func ListNodeAccessLogs(ctx context.Context, filter NodeAccessLogFilter) ([]anal clause, args := buildNodeAccessLogFilterClause(filter) tableName := nodeAccessLogTableName() sql := fmt.Sprintf(` -SELECT id, node_id, logged_at, remote_addr, region, host, path, user_agent, status_code, bytes_sent, request_length, request_time_ms, created_at +SELECT id, node_id, logged_at, remote_addr, region, host, path, user_agent, cache_status, status_code, bytes_sent, request_length, request_time_ms, created_at FROM %s WHERE %s ORDER BY %s`, tableName, clause, nodeAccessLogOrderClause(filter.SortBy, filter.SortOrder)) @@ -69,6 +69,7 @@ func scanNodeAccessLogRows(rows driver.Rows) ([]analyticsmodel.NodeAccessLog, er &item.Host, &item.Path, &item.UserAgent, + &item.CacheStatus, &item.StatusCode, &item.BytesSent, &item.RequestLength, diff --git a/internal/repository/analytics/node_access_log_test.go b/internal/repository/analytics/node_access_log_test.go index ae3377e4..2e552d38 100644 --- a/internal/repository/analytics/node_access_log_test.go +++ b/internal/repository/analytics/node_access_log_test.go @@ -49,9 +49,10 @@ func TestBatchInsertNodeAccessLogs_UsesModelBatchSQL(t *testing.T) { assert.True(t, mockBatch.sendCalled) require.Len(t, mockBatch.rows, 1) assert.Equal(t, "node-a", mockBatch.rows[0][1]) - require.Len(t, mockBatch.rows[0], 13) - assert.Equal(t, "", mockBatch.rows[0][7]) // user_agent - assert.Equal(t, uint64(2048), mockBatch.rows[0][9]) // bytes_sent - assert.Equal(t, uint64(0), mockBatch.rows[0][10]) // request_length - assert.Equal(t, uint32(0), mockBatch.rows[0][11]) // request_time_ms + require.Len(t, mockBatch.rows[0], 14) + assert.Equal(t, "", mockBatch.rows[0][7]) // user_agent + assert.Equal(t, "", mockBatch.rows[0][8]) // cache_status + assert.Equal(t, uint64(2048), mockBatch.rows[0][10]) // bytes_sent + assert.Equal(t, uint64(0), mockBatch.rows[0][11]) // request_length + assert.Equal(t, uint32(0), mockBatch.rows[0][12]) // request_time_ms } diff --git a/internal/repository/analytics/node_access_log_writer.go b/internal/repository/analytics/node_access_log_writer.go index d69a167b..0840f63c 100644 --- a/internal/repository/analytics/node_access_log_writer.go +++ b/internal/repository/analytics/node_access_log_writer.go @@ -47,6 +47,7 @@ func BatchInsertNodeAccessLogs(ctx context.Context, logs []analyticsmodel.NodeAc logItem.Host, logItem.Path, strings.TrimSpace(logItem.UserAgent), + strings.TrimSpace(logItem.CacheStatus), logItem.StatusCode, logItem.BytesSent, logItem.RequestLength, diff --git a/pkg/protocol/agent.go b/pkg/protocol/agent.go index 80be3e6d..26572286 100644 --- a/pkg/protocol/agent.go +++ b/pkg/protocol/agent.go @@ -139,6 +139,7 @@ type NodeAccessLog struct { Host string `json:"host"` Path string `json:"path"` UserAgent string `json:"user_agent,omitempty"` + CacheStatus string `json:"cache_status,omitempty"` // $upstream_cache_status StatusCode int `json:"status_code"` BytesSent int64 `json:"bytes_sent"` // body bytes = 已提供数据 RequestLength int64 `json:"request_length"` // 接收数据 diff --git a/pkg/render/openresty/types.go b/pkg/render/openresty/types.go index 1fe3c6f0..b9d35a6e 100644 --- a/pkg/render/openresty/types.go +++ b/pkg/render/openresty/types.go @@ -60,7 +60,7 @@ http { fastcgi_temp_path __OPENFLARE_NGINX_CACHE_DIR__/fastcgi_temp; uwsgi_temp_path __OPENFLARE_NGINX_CACHE_DIR__/uwsgi_temp; scgi_temp_path __OPENFLARE_NGINX_CACHE_DIR__/scgi_temp; -{{OpenRestyConnectionUpgradeMap}}{{OpenRestyDefaultServerBlock}} log_format openflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length,"user_agent":"$http_user_agent"}'; +{{OpenRestyConnectionUpgradeMap}}{{OpenRestyDefaultServerBlock}} log_format openflare_json escape=json '{"ts":"$time_iso8601","host":"$host","path":"$request_uri","remote_addr":"$remote_addr","status":$status,"request_time":$request_time,"bytes_sent":$body_bytes_sent,"request_length":$request_length,"user_agent":"$http_user_agent","cache_status":"$upstream_cache_status"}'; access_log {{OpenRestyAccessLogPath}} openflare_json; sendfile on; tcp_nopush on;