Переглянути джерело

Feature #TASK_QT-31498 【Q4】add User-Agent

Carl 6 місяців тому
батько
коміт
07e1ed39a0

+ 3 - 2
containers/container_apm.go

@@ -164,14 +164,15 @@ func (c *Container) onL7RequestApm(pid uint32, fd uint64, timestamp uint64, r *l
 			}
 			if err == nil {
 				if r.TraceType == 0 {
-					method, requestURI, sn, sport := l7.ParseHttpHost(r.Payload, r.IsTls)
+					method, requestURI, sn, sport, userAgent := l7.ParseHttpHostWithUserAgent(r.Payload, r.IsTls)
+					// userAgent 可以在这里使用,例如传递给 trace.TraceStartEvent
 					ip, _ := netaddr.ParseIP(sn)
 					//codeType := c.GetCodeTypeFromCache(pid)
 					container_id := ""
 					if c.cgroup != nil {
 						container_id = c.cgroup.ContainerId
 					}
-					trace.TraceStartEvent(method, requestURI, sn, sport, r.Status, netaddr.IPPortFrom(ip, sport), pid, c.GetAppInfo(), container_id)
+					trace.TraceStartEvent(method, requestURI, sn, userAgent, sport, r.Status, netaddr.IPPortFrom(ip, sport), pid, c.GetAppInfo(), container_id)
 					c.SendEvent(trace, r.TraceId)
 				} else if r.TraceType == 1 {
 					ipAddr, port, containerID := c.getGrpcServerNetworkInfo()

+ 55 - 18
ebpftracer/l7/http.go

@@ -21,13 +21,43 @@ func ParseHttp(payload []byte) (string, string) {
 	return string(method), string(uri)
 }
 
-func ParseHttpHost(payload []byte, isTls bool) (string, string, string, uint16) {
+// parseHttpHeader 解析 HTTP 头部字段的值
+func parseHttpHeader(payload []byte, headerName string) string {
+	headerBytes := []byte(headerName)
+	headerIdx := bytes.Index(payload, headerBytes)
+	if headerIdx == -1 {
+		return ""
+	}
+
+	headerStart := headerIdx + len(headerBytes)
+	if headerStart >= len(payload) {
+		return ""
+	}
+
+	// 查找头部值的结束位置(\r\n)
+	headerEnd := bytes.Index(payload[headerStart:], []byte("\r\n"))
+	if headerEnd == -1 {
+		// 如果没有找到 \r\n,使用剩余数据的长度
+		headerEnd = len(payload) - headerStart
+	}
+
+	if headerEnd <= 0 {
+		return ""
+	}
+
+	headerValue := payload[headerStart : headerStart+headerEnd]
+	return string(bytes.TrimSpace(headerValue))
+}
+
+// parseHttpHostCommon 解析 HTTP 请求的公共逻辑,返回 method, requestURI, host, port, rest
+// rest 是解析完 method 和 uri 后剩余的 payload 部分,用于后续解析其他头部
+func parseHttpHostCommon(payload []byte, isTls bool) (string, string, string, uint16, []byte) {
 	method, rest, ok := bytes.Cut(payload, space)
 	if !ok {
-		return "", "", "", 0
+		return "", "", "", 0, nil
 	}
 	if !isHttpMethod(string(method)) {
-		return "", "", "", 0
+		return "", "", "", 0, nil
 	}
 
 	uri, rest, ok := bytes.Cut(rest, space)
@@ -35,21 +65,12 @@ func ParseHttpHost(payload []byte, isTls bool) (string, string, string, uint16)
 		uri = append(uri, []byte("...")...)
 	}
 
-	//hostStart := bytes.Index(rest, []byte("Host:")) + len("Host:")
-	hostHeader := "Host:"
-	hostIdx := bytes.Index(rest, []byte(hostHeader))
-	if hostIdx == -1 {
-		return string(method), string(uri), "", 0
-	}
-	hostStart := hostIdx + len(hostHeader)
-	hostEnd := bytes.Index(rest[hostStart:], []byte("\r\n"))
-	var hostPortBts []byte
-	if hostEnd == -1 {
-		hostPortBts = rest[hostStart:]
-	} else {
-		hostPortBts = rest[hostStart : hostStart+hostEnd]
+	// 解析 Host 头部
+	hostPort := parseHttpHeader(rest, "Host:")
+	if hostPort == "" {
+		return string(method), string(uri), "", 0, rest
 	}
-	hostPort := string(bytes.TrimSpace(hostPortBts))
+
 	hostParts := strings.Split(hostPort, ":")
 	host := hostParts[0]
 
@@ -69,5 +90,21 @@ func ParseHttpHost(payload []byte, isTls bool) (string, string, string, uint16)
 		}
 	}
 
-	return string(method), string(uri), host, port
+	return string(method), string(uri), host, port, rest
+}
+
+// ParseHttpHost 解析 HTTP 请求,返回 method, requestURI, host, port
+// 不解析 User-Agent,性能更优
+func ParseHttpHost(payload []byte, isTls bool) (string, string, string, uint16) {
+	method, uri, host, port, _ := parseHttpHostCommon(payload, isTls)
+	return method, uri, host, port
+}
+
+// ParseHttpHostWithUserAgent 解析 HTTP 请求,返回 method, requestURI, host, port, userAgent
+// 包含 User-Agent 解析,适用于需要 User-Agent 信息的场景
+func ParseHttpHostWithUserAgent(payload []byte, isTls bool) (string, string, string, uint16, string) {
+	method, uri, host, port, rest := parseHttpHostCommon(payload, isTls)
+	// 解析 User-Agent 头部
+	userAgent := parseHttpHeader(rest, "User-Agent:")
+	return method, uri, host, port, userAgent
 }

+ 4 - 0
pkg/go.opentelemetry.io/otel/exporters/otlp/otlptrace/apm_exporter.go

@@ -87,6 +87,7 @@ type RootDataT struct {
 	VipIds          []interface{} `json:"vip_ids"`
 	SrcAddr         string        `json:"src_addr"`
 	DestinationAddr string        `json:"destination_addr"`
+	UserAgent       string        `json:"user_agent"`
 	// op 新增字段
 	Pid         uint32 `json:"pid"`
 	ContainerID string `json:"container_id"`
@@ -467,6 +468,7 @@ func initRootDataFromEvent() RootDataT {
 		DestinationAddr: "",
 		Sys:             sysTag,
 		SystemUUID:      systemUUID,
+		UserAgent:       "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
 	}
 	return data
 }
@@ -791,6 +793,8 @@ func buildAppMapFromEvent(traceRoot *RootDataT, sd apmTraceSpan) int {
 			traceRoot.Pid = uint32(attr.Value.AsInt64())
 		case "server.container_id":
 			traceRoot.ContainerID = attr.Value.AsString()
+		case "server.user_agent":
+			traceRoot.UserAgent = attr.Value.AsString()
 		}
 	}
 	traceRoot.Maps = append(traceRoot.Maps, mNode)

+ 2 - 1
tracing/apm_tracing.go

@@ -150,7 +150,7 @@ func (t *Trace) GrpcServerTraceStartEvent(sn string, sport uint16, r *l7.Request
 	t.startReady()
 }
 
-func (t *Trace) TraceStartEvent(method, path, sn string, sport uint16, status l7.Status, addr netaddr.IPPort, pid uint32, appInfo AppInfo, container_id string) {
+func (t *Trace) TraceStartEvent(method, path, sn, ua string, sport uint16, status l7.Status, addr netaddr.IPPort, pid uint32, appInfo AppInfo, container_id string) {
 	t.span.SetAttributes(semconv.HTTPURL(fmt.Sprintf("http://%s:%d%s", sn, sport, path)),
 		semconv.HTTPMethod(method),
 		attribute.String("http.uri", path))
@@ -171,6 +171,7 @@ func (t *Trace) TraceStartEvent(method, path, sn string, sport uint16, status l7
 		attribute.Int64("server.instance_id", appInfo.InstanceIdHash.IntVal),
 		attribute.Int("server.pid", int(pid)),
 		attribute.String("server.container_id", container_id),
+		attribute.String("server.user_agent", ua),
 	}
 	t.span.SetAttributes(t.commonAttrs...)
 	t.startReady()