Envoy 访问日志
访问记录
- HTTP 连接管理器、 tcp 代理和 thrift 代理 支持具有以下特性的可扩展访问日志记录:
- 每个连接流的任意数量的访问日志。
- 可定制的访问日志过滤器,允许将不同类型的请求和响应写入不同的访问日志。
- 可以使用侦听器访问日志启用下游连接访问日志记录。侦听器访问日志是对 HTTP 请求访问日志的补充,可以独立于过滤器访问日志单独启用。
访问日志接收器
文件
-
异步IO架构,访问日志记录不会阻塞主线程;
-
可自定义的访问日志格式,使用预定义字段以及HTTP请求和响应报文的任意标头;
gRRC
-
将访问日志发送到gRPC访问日志记录服务中;
Stdout
-
将日志发送到进程的标准输出上
Stderr
-
将日志发送到进程的错误输出上
访问日志过滤器
日志过滤器格式
--
listeners:
...
filter_chains:
filter_chain_match: {...}
use_proxy_proto: {...}
transport_socket: {...}
transport_socket_connect_timeout: {...}
name: ...
filters: # 组成过滤器链的单个网络过滤器列表,用于与侦听器建立连接。顺序很重要,因为过滤器在连接事件发生时按顺序处理。注意:如果过滤器列表为空,则默认关闭连接。
name: envoy.filters.network.http_connection_manager # 过滤器配置的名称。取决于typed_config配置的过滤器指定的名称。
typed_config: # 过滤器特定配置,这取决于被实例化的过滤器。
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
...
access_log: # 访问日志配置
name: ... # 要实例化的访问日志实现的名称,该名称必须与静态注册的访问日志相匹配,当前的内置的日志记录器有envoy.access_loggers.file、envoy.access_loggers.http_grpc、envoy.access_loggers.open_telemetry、envoy.access_loggers.stream、envoy.access_loggers.tcp_grpc和envoy.access_loggers.wasm几种
filter: # 用于确定输出哪些日志信息的过滤器,但仅能选择使用其中一种;
status_code_filter: {...} # 状态码过滤器。
duration_filter: {...} # 持续时间过滤器。
not_health_check_filter: {...} # 不健康检查过滤器。
traceable_filter: {...} # 可追踪过滤器。
runtime_filter: {...} # 运行时过滤器。
and_filter: {...}
or_filter: {...}
header_filter: {...}
response_flag_filter: {...}
grpc_status_filter: {...} # gRPC 状态过滤器。
extension_filter: {...} # 扩展过滤器。envoy.access_loggers.extension_filters.cel。
metadata_filter: {...} # 元数据过滤器
typed_config: {...} # 与选定的日志记录器类型相关的专用配置
envoy.access_loggers.file配置格式
--
listeners:
...
filter_chains:
filter_chain_match: {...}
use_proxy_proto: {...}
transport_socket: {...}
transport_socket_connect_timeout: {...}
name: ...
filters: # 组成过滤器链的单个网络过滤器列表,用于与侦听器建立连接。顺序很重要,因为过滤器在连接事件发生时按顺序处理。注意:如果过滤器列表为空,则默认关闭连接。
name: envoy.filters.network.http_connection_manager # 过滤器配置的名称。取决于typed_config配置的过滤器指定的名称。
typed_config: # 过滤器特定配置,这取决于被实例化的过滤器。
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
...
access_log: # 访问日志配置
name: envoy.access_loggers.file
typed_config: # 与选定的日志记录器类型相关的专用配置
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: ... # 本地文件系统上的日志文件路径;
format: ... # 访问日志格式字符串,Envoy有默认的日志格式,也支持用户自定义;该字段已废弃,将被log_format所取代;
json_format: {...} # json格式的访问日志字符串;该字段已废弃,将被log_format所取代;
typed_json_format: {...} # json格式的访问日志字符串;该字段已废弃,将被log_format所取代;
log_format: # 访问日志数据及格式定义,未定义时将使用默认值;format、json_format、typed_json_format和log_format仅可定义一个;
text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS%
%BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(XREQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" \"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\"\n" # 支持命令操作符的文本字串;text_format、json_format和text_format_source仅可定义一个
json_format: {"start": "[%START_TIME%] ", "method": "%REQ(:METHOD)%", "url": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%", "protocol": "%PROTOCOL%", "status":
"%RESPONSE_CODE%", "respflags": "%RESPONSE_FLAGS%", "bytes-received": "%BYTES_RECEIVED%", "bytes-sent": "%BYTES_SENT%", "duration": "%DURATION%",
"upstream-service-time": "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%", "x-forwarded-for": "%REQ(X-FORWARDED-FOR)%", "user-agent": "%REQ(USER-AGENT)%",
"request-id": "%REQ(X-REQUEST-ID)%", "authority": "%REQ(:AUTHORITY)%", "upstream-host": "%UPSTREAM_HOST%", "remote-ip":
"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"} # 支持命令操作符的json字串
text_format_source: {...} # 支持命令操作符的文本字串,字串来自filename、inline_bytes或inline_string数据源
omit_empty_values: ... # 是否忽略空值
content_type: ... # 内容类型,文本的默认类型为text/plain,json的默认类型为application/json
formatters: [] # 调用的日志格式化插件
filter: # 用于确定输出哪些日志信息的过滤器,但仅能选择使用其中一种;
status_code_filter: {...} # 状态码过滤器。
duration_filter: {...} # 持续时间过滤器。
not_health_check_filter: {...} # 不健康检查过滤器。
traceable_filter: {...} # 可追踪过滤器。
runtime_filter: {...} # 运行时过滤器。
and_filter: {...}
or_filter: {...}
header_filter: {...}
response_flag_filter: {...}
grpc_status_filter: {...} # gRPC 状态过滤器。
extension_filter: {...} # 扩展过滤器。
metadata_filter: {...} # 元数据过滤器
访问日志格式
格式规则
- 访问日志格式包含提取并相关数据并插入到格式指定处的命令运算符,目前支持两种格式
- format strings:格式字符串
- format dictionaries:格式字典
命令操作符
- 用于提取数据并插入到日志中
- 有些操作符对于TCP和HTTP来说其含义有所不同
- 几个操作符示例
- %REQ(X?Y):Z%:记录HTTP请求报文的指定标头(X)的值,Y是备用标头,Z是可选参数,表示字符串截断并保留最多Z个字符;X和Y标头均不存在时记录为“-”;TCP不支持;
- %RESP(X?Y):Z%:记录HTTP响应报文的指定标头的值;TCP不支持;
- %DURATION%:
- HTTP:从开始时间到最后一个字节输出的请求的总持续时长(以毫秒为单位);
- TCP:下游连接的总持续时长(以毫秒为单位);
format strings
格式字符串
- 所谓格式化字符串即文本字串,由format参数指定,含有命令操作符或直接文本字串,且必须显式指定换行符;
格式字符串格式
"[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" \"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\"\n"
格式字符串参数说明
- %START_TIME%:请求开始的时间戳
- %REQ(X?Y):Z%:请求报文中指定标头X的值,X不存在时则取标头Y的值;相应值的会截取最多不超过Z个字符;
- %RESP(X?Y):Z%:功能类似于“%REQ(X?Y):Z%”,但用于响应报文
- %PROTOCOL%:HTTP协议的版本,支持http/1.1、http/2和http/3;TCP代理因不支持而显示为“-”;
- %RESPONSE_CODE%:HTTP响应码; TCP代理因不支持而显示为“-”;
- %RESPONSE_FLAGS% :响应标志,用于进一步说明响应或连接的详细信息;
- %BYTES_RECEIVED%:接收的报文中body部分的大小;TCP代理中意味着下游接收到的字节数;
- %BYTES_SENT% :发送的报文中body部分的大小;TCP代理中意味着下游发送的字节数;
- %DURATION%:从接收到请求至发送完响应报文的最后一个字节所经历的时长;对TCP代理来说,则是指下游连接的维持时长;
- %UPSTREAM_HOST%:上游的URL,TCP代理中格式为“tcp://ip:port”;
格式字符串配置示例
--
listeners:
...
filter_chains:
filter_chain_match: {...}
use_proxy_proto: {...}
transport_socket: {...}
transport_socket_connect_timeout: {...}
name: ...
filters: # 组成过滤器链的单个网络过滤器列表,用于与侦听器建立连接。顺序很重要,因为过滤器在连接事件发生时按顺序处理。注意:如果过滤器列表为空,则默认关闭连接。
name: envoy.filters.network.http_connection_manager # 过滤器配置的名称。取决于typed_config配置的过滤器指定的名称。
typed_config: # 过滤器特定配置,这取决于被实例化的过滤器。
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
...
access_log: # 访问日志配置
name: envoy.access_loggers.file
typed_config: # 与选定的日志记录器类型相关的专用配置
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: "/dev/stdout" # 本地文件系统上的日志文件路径;
log_format: # 访问日志数据及格式定义,未定义时将使用默认值;format、json_format、typed_json_format和log_format仅可定义一个;
text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS%
%BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(XREQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" \"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\"\n" # 支持命令操作符的文本字串;text_format、json_format和text_format_source仅可定义一个
格式字符串日志示例
生成HTTP/1.1日志示例
[2022-09-19T11:15:50.779Z] "GET / HTTP/1.1" 200 - 0 75 1007 1006 "-" "curl/7.68.0" "05f0b283-e75c-4230-8271-2704056a3b05" "172.31.73.10" "172.31.73.2:80"
生成HTTP/2日志示例
[2022-09-19T11:15:50.779Z] "POST /api/v1/locations HTTP/2" 204 - 154 0 226 100 "10.0.35.28“ "nsq2http" "cc21d9b0-cf5c-432b-8c7e-98aeb7988cd2" "locations" "tcp://172.31.73.2:80"
format dictionaries
JSON格式
- 格式字典是指使用json_format格式定义的JSON结构化日志输出格式,它同样使用命令操作符提取数据并将其插入格式字典中以构造日志输出
-
允许用户使用自定义键
-
仅支持映射到字符串数据,且不支持嵌套
-
JSON格式示例
json_format: {"start": "[%START_TIME%] ", "method": "%REQ(:METHOD)%", "url": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%", "protocol": "%PROTOCOL%", "status": "%RESPONSE_CODE%", "respflags": "%RESPONSE_FLAGS%", "bytes-received": "%BYTES_RECEIVED%", "bytes-sent": "%BYTES_SENT%", "duration": "%DURATION%", "upstream-service-time": "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%", "x-forwarded-for": "%REQ(X-FORWARDED-FOR)%", "user-agent": "%REQ(USER-AGENT)%",
"request-id": "%REQ(X-REQUEST-ID)%", "authority": "%REQ(:AUTHORITY)%", "upstream-host": "%UPSTREAM_HOST%", "remote-ip": "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"}
JSON格式日志示例
{"method":"GET","authority":"172.31.73.10","request-id":"ef202fd6-7a5d-453e-aa57-cd6ee4b3ad89","status":200,"duration":7,"protocol":"HTTP/1.1","upstreamhost":"172.31.73.2:80","user-agent":"curl/7.68.0","upstream-service-time":"5","x-forwarded-for":null,"url":"/service/colors","respflags":"-","start":"[2021-11-
02T12:24:55.081Z] ","remote-ip":"172.31.73.1","bytes-sent":98,"bytes-received":0}
日志收集
-
通过容器日志接口收集日志是一种更为简便的日志收集方式
-
Docker Host上,容器日志存储于/var/lib/docker/containers/*/*.log 一类的日志文件中
- 此时,Envoy也直接也可将访问日志直接使用“/dev/stdout”发往控制台
-
日志收集配置示例
front-envoy.yaml
node:
id: front-envoy
cluster: mycluster
admin:
profile_path: /tmp/envoy.prof
access_log_path: /tmp/admin_access.log
address:
socket_address:
address: 0.0.0.0
port_value: 9901
layered_runtime:
layers:
- name: admin
admin_layer: {}
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 80
name: listener_http
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: "/dev/stdout"
log_format:
json_format: {"start": "[%START_TIME%] ", "method": "%REQ(:METHOD)%", "url": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%", "protocol": "%PROTOCOL%", "status": "%RESPONSE_CODE%", "respflags": "%RESPONSE_FLAGS%", "bytes-received": "%BYTES_RECEIVED%", "bytes-sent": "%BYTES_SENT%", "duration": "%DURATION%", "upstream-service-time": "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%", "x-forwarded-for": "%REQ(X-FORWARDED-FOR)%", "user-agent": "%REQ(USER-AGENT)%", "request-id": "%REQ(X-REQUEST-ID)%", "authority": "%REQ(:AUTHORITY)%", "upstream-host": "%UPSTREAM_HOST%", "remote-ip": "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"}
#text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" \"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\"\n"
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: vh_001
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: mycluster
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: mycluster
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: mycluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: webapp
port_value: 80
json_format
{"upstream-host":"172.31.73.2:80","start":"[2022-09-19T04:29:11.545Z] ","bytes-received":0,"user-agent":"curl/7.68.0","method":"GET","x-forwarded-for":null,"duration":1006,"upstream-service-time":"1006","remote-ip":"172.31.73.1","request-id":"9ecdacf3-305b-4f7d-a1fb-797687e285aa","status":200,"protocol":"HTTP/1.1","authority":"172.31.73.10","url":"/","bytes-sent":75,"respflags":"-"}
text_format
[2022-09-19T04:31:03.821Z] "GET / HTTP/1.1" 200 - 0 75 1006 1006 "-" "curl/7.68.0" "afc5cc13-5f91-49d2-af18-82e58bfc91dd" "172.31.73.10" "172.31.73.3:80" "172.31.73.1"
filebeat.yaml
filebeat.inputs:
- type: container
paths:
- '/var/lib/docker/containers/*/*.log'
# - '/logs/envoy/*access.log'
processors:
- add_docker_metadata:
host: "unix:///var/run/docker.sock"
- decode_json_fields:
fields: ["message"]
target: "json"
overwrite_keys: true
output.elasticsearch:
hosts: ["elasticsearch:9200"]
indices:
- index: "filebeat-%{+yyyy.MM.dd}"
logging.json: true
logging.metrics.enabled: false
docker-compose.yaml
version: '3.3'
services:
front-envoy:
image: envoyproxy/envoy:v1.23-latest
environment:
- ENVOY_UID=0
- ENVOY_GID=0
container_name: front-envoy
volumes:
- ./front-envoy.yaml:/etc/envoy/envoy.yaml
- ./logs/envoy:/logs/envoy
networks:
envoymesh:
ipv4_address: 172.31.76.10
aliases:
- front-envoy
expose:
# Expose ports 80 (for general traffic) and 9901 (for the admin server)
- "80"
- "9901"
service_blue:
image: ikubernetes/servicemesh-app:latest
networks:
envoymesh:
aliases:
- colored
- blue
environment:
- SERVICE_NAME=blue
expose:
- "80"
service_green:
image: ikubernetes/servicemesh-app:latest
networks:
envoymesh:
aliases:
- colored
- green
environment:
- SERVICE_NAME=green
expose:
- "80"
service_red:
image: ikubernetes/servicemesh-app:latest
networks:
envoymesh:
aliases:
- colored
- red
environment:
- SERVICE_NAME=red
expose:
- "80"
elasticsearch:
image: "docker.elastic.co/elasticsearch/elasticsearch:7.17.5"
environment:
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
- "discovery.type=single-node"
- "cluster.name=myes"
- "node.name=myes01"
ulimits:
memlock:
soft: -1
hard: -1
networks:
envoymesh:
ipv4_address: 172.31.76.15
aliases:
- es
- myes01
ports:
- "9200:9200"
#volumes:
#- elasticsearch_data:/usr/share/elasticsearch/data
kibana:
image: "docker.elastic.co/kibana/kibana:7.17.5"
environment:
ELASTICSEARCH_URL: http://myes01:9200
ELASTICSEARCH_HOSTS: '["http://myes01:9200"]'
networks:
envoymesh:
ipv4_address: 172.31.76.16
aliases:
- kibana
- kib
ports:
- "5601:5601"
filebeat:
image: "docker.elastic.co/beats/filebeat:7.17.5"
networks:
envoymesh:
ipv4_address: 172.31.76.17
aliases:
- filebeat
- fb
user: root
command: ["--strict.perms=false"]
volumes:
- ./filebeat/filebeat.yaml:/usr/share/filebeat/filebeat.yml
#- /var/lib/docker:/var/lib/docker:ro
#- /var/run/docker.sock:/var/run/docker.sock
- ./logs/envoy:/logs/envoy:ro
volumes:
elasticsearch_data:
networks:
envoymesh:
driver: bridge
ipam:
config:
- subnet: 172.31.76.0/24
参考文档
https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/observability/access_logging