推送至 HTTP 服务器
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, BaseHTTPRequestHandlerimport 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/jsonself.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_key
和 access_key
,签名算法详情如下:1. 请求 URL 构成
如下所示,请求 URL 将在
?
后携带 auth_key
和 access_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 的值进行拆分,获取timestamp
,rand
和md5hash
。可先检查 timestamp
是否过期,过期时间建议为300s
,并基于上述规则拼装加密字符串,利用 SecretKey 拼装出需加密的字符串,加密后与 auth_key
中的 md5hash
值进行比较,相同则说明鉴权通过。3. 服务端解析鉴权请求代码示例
import hashlibfrom flask import Flask, requestapp = 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_keysecret_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 mainimport ("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() {<-doneif 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 即您提供的SecretIdauthKeys := 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 {//异常处理}}}// 获取SecretKeyfunc getSecretKey(accessKey string) string {if accessKey != "" {// 通过Access_key(SecretI)获取Secret_Keyreturn "secret_key"}return ""}