有奖捉虫:行业应用 & 管理与支持文档专题 HOT
EdgeOne 实时日志推送支持将日志推送至自定义接口地址,您可通过控制台或 API 进行配置。EdgeOne 可通过 HTTP POST 请求调用您提供的后端接口地址,将日志在 HTTP Body 中传输到您指定的服务器上。

操作步骤

1. 登录 边缘安全加速平台 EO 控制台,在左侧菜单栏中,单击站点列表,在站点列表内单击需配置的站点,进入站点详情页面。
2. 在站点详情页面,单击日志服务 > 实时日志。
3. 在实时日志页面,单击新建推送任务。
4. 在选择日志源页面,填写任务名称、选择日志类型、服务区域、需推送日志的域名/四层代理实例,单击下一步
5. 在定义推送内容页面:
(必选)在预设字段列表中勾选需要推送的日志字段;
(可选)添加自定义日志字段,支持从请求头、响应头、Cookie 头中提取指定字段名称的值;
(可选)配置推送日志筛选条件,默认推送全量日志;
(可选)在高级配置中,配置采样比例,默认不开启采样,推送 100% 日志至目的地。
(可选)在高级配置中,配置日志输出格式,默认格式为 JSON Lines。
6. 在选择目的地页面,选择 HTTP 服务(POST),单击下一步
7. 在目的地信息页面,填写相关目的地及参数信息。
参数名称
说明
接口地址
填入您的日志接收接口地址,例如:https://www.example.com/edgeone-logs
内容压缩
为减少日志内容的大小,节约流量开销,您可以通过勾选使用 gzip 压缩日志文件开启内容压缩,EdgeOne 将会使用 gzip 格式压缩日志后再传输日志,并且会增加 HTTP 头部Content-Encoding: gzip来标明压缩格式。
源站鉴权
选择为加密鉴权时,推送日志时将携带鉴权信息供源站进行验证,保证数据来源身份的安全性。鉴权算法见:鉴权算法参考
自定义 HTTP 请求头
添加需要 EdgeOne 发起请求时携带的 HTTP 头部。例如:
通过添加头部 log-source: EdgeOne 来识别日志来源为 EdgeOne。
通过添加头部BatchSize: ${batchSize}来获取每次 POST 请求内推送的日志条数。
说明
若您填写的头部名称为 Content-Type 等 EdgeOne 日志推送默认携带的头部,那么您填写的头部值将覆盖默认值。
8. 单击推送,在弹窗中确认相关费用提示,单击确定创建
9. 实时日志推送任务在配置阶段为了校验接口连通性,将向接口地址发送测试数据进行验证,数据格式如下所示:
{ "ClientState": "CH-AH", "EdgeResponseTime": 366, "RequestID": "13515444256055847385", "ClientRegion": "CN", "RemotePort": 443, "RequestHost": "www.tencent.com", "RequestMethod": "GET", "RequestUrlQueryString": "-", "RequestUrl": "/en-us/about.html", "RequestProtocol": "HTTP/2.0", "EdgeServerID": "336d5ebc5436534e61d16e63ddfca327-d41d8cd98f00b204e9800998ecf8427e", "RequestTime": "2022-07-01T02:37:13Z", "EdgeCacheStatus": "-", "EdgeResponseBytes": 39430, "EdgeResponseStatusCode": 200, "ClientIP": "0.0.0.0", "RequestReferer": "https://www.tencent.com/", "RequestUA": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Mobile/15E148 Safari/604.1", "EdgeServerIP": "0.0.0.0", "RequestRange": "0-100/200", "EdgeInternalTime": 334, "RequestBytes": 237 }

相关参考

服务端解析日志代码示例

当您未开启源站鉴权时,可参考以下 Python 代码在服务端解析请求正文中的日志内容。
# 导入Python标准库中的模块
import time # 用于获取当前时间
import gzip # 用于处理gzip压缩的数据
# 从http.server模块导入HTTPServer和BaseHTTPRequestHandler类
from http.server import HTTPServer, BaseHTTPRequestHandler
import json # 用于处理JSON数据格式
# 定义一个继承自BaseHTTPRequestHandler的类,用于处理HTTP请求
class Resquest(BaseHTTPRequestHandler):
# 重写do_POST方法,该方法会在服务器接收到POST请求时被调用
def do_POST(self):
# 打印请求头信息
print(str(self.headers))
# 打印HTTP请求的命令(如POST)
print(self.command)
# 读取请求体内容,根据请求头中的Content-Length字段确定读取的长度
req_datas = self.rfile.read(int(self.headers['content-length']))
try:
# 尝试解码请求体内容并打印
print(req_datas.decode())
except Exception as e:
# 如果解码过程中发生异常,打印异常信息
print(e)
# 检查请求头中是否有Content-Encoding: gzip,如果有,则解压请求体
if self.headers['Content-Encoding'] == 'gzip':
data = gzip.decompress(req_datas)
# 打印解压后的gzip内容
print('---------------decompress gzip content-------------------------')
print(data.decode())
# 检查请求的路径是否为 '/edgeone-logs',如果不是,则返回404错误
if self.path != '/edgeone-logs':
self.send_error(404, "Page not Found!")
return
# 如果请求路径正确,准备响应数据
data = {
'result_code': '1',
'result_desc': 'Success',
'timestamp': int(time.time()) # 响应当前时间戳
}
# 发送HTTP响应状态码200,表示请求成功
self.send_response(200)
# 设置响应头Content-type为application/json
self.send_header('Content-type', 'application/json')
# 结束响应头的发送
self.end_headers()
# 将响应数据以JSON格式写入到响应体中
self.wfile.write(json.dumps(data).encode('utf-8'))
# 检查当前脚本是否作为主程序运行
if __name__ == '__main__':
# 定义服务器监听的地址和端口,您可将9002替换为自定义端口
host = ('', 9002)
# 创建HTTPServer对象,传入监听地址和端口以及处理请求的请求处理器类
server = HTTPServer(host, Resquest)
# 打印服务器启动信息
print("Starting server, listen at: %s:%s" % host)
# 启动服务器,使其持续运行直到外部中断
server.serve_forever()

请求鉴权算法

如果您在推送目的地信息中,源站鉴权内选择了加密签名,可输入您自定义配置 SecretId 和 SecretKey,EdgeOne 将在请求 URL 中增加签名auth_keyaccess_key,签名算法详情如下:
1. 请求 URL 构成
如下所示,请求 URL 将在后携带 auth_keyaccess_key
http://DomainName[:port]/[uri]?auth_key=timestamp-rand-md5hash&access_key=SecretId
参数说明:
timestamp:请求当前时间,使用 Unix 秒级10位时间戳。
rand:随机数。
access_key:用于标识接口请求方的身份,即您所自定义配置的 SecretId。
SecretKey:固定长度 32 位,即您所自定义配置的 SecretKey。
uri:资源标识符,例如:/access_log/post
md5hash:md5hash = md5sum(string_to_sign),其中 string_to_sign ="uri-timestamp-rand-SecretKey"。通过md5算法计算出的验证串,数字0-9和小写英文字母 a-z 混合,固定长度为32个字符。
2. 计算示例
假定填入参数为: 接口地址:https://www.example.com/access_log/post SecretId = YourID SecretKey = YourKey uri = /access_log/post timestamp = 1571587200 rand = 0
string_to_sign = "/access_log/post-1571587200-0-YourKey"
基于该字符串计算出:
md5hash=md5sum("/access_log/post-1571587200-0-YourKey")=1f7ffa7bff8f06bbfbe2ace0f14b7e16
最终推送时的请求 url 为:
https://www.example.com/cdnlog/post?auth_key=1571587200-0-1f7ffa7bff8f06bbfbe2ace0f14b7e16&access_key=YourID
服务端在接收到推送请求后,提取auth_key的值. 对 auth_key 的值进行拆分,获取timestamprandmd5hash。可先检查 timestamp 是否过期,过期时间建议为300s,并基于上述规则拼装加密字符串,利用 SecretKey 拼装出需加密的字符串,加密后与 auth_key 中的 md5hash 值进行比较,相同则说明鉴权通过。
3. 服务端解析鉴权请求代码示例
Python
Golang
import hashlib
?
from flask import Flask, request
?
app = Flask(__name__)
?
?
def get_rsp(msg, result={}, code=0):
return {
"respCode": code,
"respMsg": msg,
"result": result
}
?
?
def get_secret_key(access_key):
return "secret_key"
?
?
@app.route("/access_log/post", methods=['POST'])
def access_log():
if request.method == 'POST':
if request.content_type.startswith('application/json'):
current_time_ts, rand_num, md5hash = request.args.get("auth_key").split("-")
# 判断请求时间是否是在有效期内
if time.time() - int(current_time_ts) > 300:
return get_rsp(msg="The request is out of time", code=-1)
?
access_key = request.args.get("access_key")
# 通过access_key(SecretId)获取secret_key
secret_key = get_secret_key(access_key)
raw_str = "%s-%s-%s-%s" % (request.path, current_time_ts, rand_num, secret_key)
auth_md5hash = hashlib.md5(raw_str.encode("utf-8")).hexdigest()
if auth_md5hash == md5hash:
# 认证通过
if request.headers['content-encoding'] == 'gzip':
# 解压数据
pass
# 数据处理
return get_rsp("ok")
return get_rsp(msg="Please use content_type by application/json", code=-1)
return get_rsp(msg="The request method not find, method == %s" % request.method, code=-1)
?
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888, debug=True)
?
package main
?
import (
"context"
"crypto/md5"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
)
?
func main() {
mux := http.NewServeMux()
mux.Handle("/access_log/post", &logHandler{})
?
server := &http.Server{
Addr: ":5000",
Handler: mux,
}
?
// 创建系统信号接收器
done := make(chan os.Signal)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-done
?
if err := server.Shutdown(context.Background()); err != nil {
log.Fatal("Shutdown server:", err)
}
}()
?
err := server.ListenAndServe()
if err != nil {
if err == http.ErrServerClosed {
log.Print("Server closed under request")
} else {
log.Fatal("Server closed unexpected")
}
}
}
?
type logHandler struct{}
?
func (*logHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
query := r.URL.Query()
authKey := query.Get("auth_key")
accessKey := query.Get("access_key")//access_key 即您提供的SecretId
authKeys := strings.Split(authKey, "-")
if len(authKeys) == 3 {
currentTimeTs := authKeys[0]
//进行时间戳有效期判断
RandNum := authKeys[1]
md5Hash := authKeys[2]
secretKey := getSecretKey(accessKey)
authStr := fmt.Sprintf("%s-%s-%s-%s", "/access_log/post", currentTimeTs, RandNum, secretKey)
data := []byte(authStr)
has := md5.Sum(data)
authMd5 := fmt.Sprintf("%x", has) //转换成字符串进行比较
if authMd5 == md5Hash {
// todo 认证成功
if r.Header.Get("Content-Encoding") == "gzip" {
//解压数据
}
//数据处理
}
} else {
//异常处理
}
}
}
?
// 获取SecretKey
func getSecretKey(accessKey string) string {
if accessKey != "" {
// 通过Access_key(SecretI)获取Secret_Key
return "secret_key"
}
return ""
}
?
?


http://www.vxiaotou.com