fix: 验收修正
This commit is contained in:
@@ -128,7 +128,8 @@ func ReceiveLogs(ctx *gin.Context) {
|
||||
}
|
||||
rows, rejected, message := decodeLogs(request)
|
||||
if len(rows) > 0 {
|
||||
if err := impl.DBService.CreateInBatches(rows, 500).Error; err != nil {
|
||||
if err := impl.DBService.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "ingest_key"}}, DoNothing: true}).
|
||||
CreateInBatches(rows, 500).Error; err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "Log 入库失败"})
|
||||
return
|
||||
}
|
||||
@@ -282,8 +283,12 @@ func logModel(input logPayload, resourceAttrs map[string]interface{}) (models.Lo
|
||||
}
|
||||
serviceName := limitCharacters(textAttribute(resourceAttrs, "service.name"), 512)
|
||||
resourceUID := limitCharacters(firstTextAttribute(resourceAttrs, attrs, "resource.uid", "resource_uid"), 255)
|
||||
var ingestKey *string
|
||||
if value := limitCharacters(firstTextAttribute(attrs, nil, "log.record.uid"), 160); value != "" {
|
||||
ingestKey = &value
|
||||
}
|
||||
return models.LogEvent{
|
||||
CreatedAt: timeFromLog(input), SourceKind: "otlp", RawPayload: body, NormalizedSummary: body,
|
||||
IngestKey: ingestKey, CreatedAt: timeFromLog(input), SourceKind: "otlp", RawPayload: body, NormalizedSummary: body,
|
||||
NormalizedDetail: string(detailJSON), DeviceName: serviceName, ResourceType: "service", ResourceUID: resourceUID,
|
||||
ResourceID: resourceUID, ResourceName: serviceName, BusinessSystemID: uintAttribute(resourceAttrs, attrs, "business.system.id", "business_system_id"),
|
||||
TraceID: traceID, SpanID: spanID, MatchMethod: "otlp", DispatchStatus: "not_applicable",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package otlp
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -41,6 +44,19 @@ type dependencyAggregate struct {
|
||||
DurationMs float64
|
||||
}
|
||||
|
||||
type middlewareCandidate struct {
|
||||
CandidateKey string `json:"candidate_key"`
|
||||
Product string `json:"product"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Address string `json:"address,omitempty"`
|
||||
SourceService string `json:"source_service"`
|
||||
BusinessSystemID uint `json:"business_system_id"`
|
||||
FirstSeenAt time.Time `json:"first_seen_at"`
|
||||
LastSeenAt time.Time `json:"last_seen_at"`
|
||||
SpanCount int64 `json:"span_count"`
|
||||
ErrorCount int64 `json:"error_count"`
|
||||
}
|
||||
|
||||
func ListTraces(ctx *gin.Context) {
|
||||
page, size := pageParams(ctx)
|
||||
query := applyTraceFilters(impl.DBService.Model(&models.TraceSpan{}), ctx)
|
||||
@@ -128,6 +144,102 @@ func ListDependencies(ctx *gin.Context) {
|
||||
infra.Response.Success(ctx, gin.H{"items": edges, "count": len(edges)})
|
||||
}
|
||||
|
||||
// ListMiddlewareCandidates 从调用链标准属性中识别尚待管理员确认的中间件候选,不自动创建设备。
|
||||
func ListMiddlewareCandidates(ctx *gin.Context) {
|
||||
businessSystemID, err := strconv.ParseUint(strings.TrimSpace(ctx.Query("business_system_id")), 10, 32)
|
||||
if err != nil || businessSystemID == 0 {
|
||||
infra.Response.Error(ctx, errors.New("business_system_id 无效"))
|
||||
return
|
||||
}
|
||||
start := time.Now().UTC().Add(-30 * 24 * time.Hour)
|
||||
if value, parseErr := time.Parse(time.RFC3339, strings.TrimSpace(ctx.Query("start_time"))); parseErr == nil {
|
||||
start = value.UTC()
|
||||
}
|
||||
var rows []models.TraceSpan
|
||||
if err := impl.DBService.Where("business_system_id = ? AND start_time >= ?", uint(businessSystemID), start).
|
||||
Order("start_time DESC").Limit(100000).Find(&rows).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
aggregates := make(map[string]*middlewareCandidate)
|
||||
for _, row := range rows {
|
||||
product, displayName, address, ok := middlewareIdentity(row)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
key := middlewareCandidateKey(uint(businessSystemID), product, displayName, address)
|
||||
item := aggregates[key]
|
||||
if item == nil {
|
||||
item = &middlewareCandidate{
|
||||
CandidateKey: key, Product: product, DisplayName: displayName, Address: address,
|
||||
SourceService: row.ServiceName, BusinessSystemID: uint(businessSystemID),
|
||||
FirstSeenAt: row.StartTime, LastSeenAt: row.StartTime,
|
||||
}
|
||||
aggregates[key] = item
|
||||
}
|
||||
item.SpanCount++
|
||||
if row.StatusCode == "error" {
|
||||
item.ErrorCount++
|
||||
}
|
||||
if row.StartTime.Before(item.FirstSeenAt) {
|
||||
item.FirstSeenAt = row.StartTime
|
||||
}
|
||||
if row.StartTime.After(item.LastSeenAt) {
|
||||
item.LastSeenAt = row.StartTime
|
||||
item.SourceService = row.ServiceName
|
||||
}
|
||||
}
|
||||
items := make([]middlewareCandidate, 0, len(aggregates))
|
||||
for _, item := range aggregates {
|
||||
items = append(items, *item)
|
||||
}
|
||||
sort.Slice(items, func(left, right int) bool {
|
||||
if !items[left].LastSeenAt.Equal(items[right].LastSeenAt) {
|
||||
return items[left].LastSeenAt.After(items[right].LastSeenAt)
|
||||
}
|
||||
return items[left].CandidateKey < items[right].CandidateKey
|
||||
})
|
||||
infra.Response.Success(ctx, gin.H{"items": items, "count": len(items), "start_time": start})
|
||||
}
|
||||
|
||||
func middlewareIdentity(row models.TraceSpan) (string, string, string, bool) {
|
||||
attrs := make(map[string]interface{})
|
||||
resourceAttrs := make(map[string]interface{})
|
||||
_ = json.Unmarshal([]byte(row.Attributes), &attrs)
|
||||
_ = json.Unmarshal([]byte(row.ResourceAttrs), &resourceAttrs)
|
||||
rawProduct := firstTextAttribute(attrs, resourceAttrs, "messaging.system", "db.system.name", "db.system", "peer.service")
|
||||
product := knownMiddlewareProduct(rawProduct)
|
||||
if product == "" {
|
||||
return "", "", "", false
|
||||
}
|
||||
displayName := firstTextAttribute(attrs, resourceAttrs, "peer.service", "server.address", "network.peer.address", "net.peer.name")
|
||||
if displayName == "" {
|
||||
displayName = product
|
||||
}
|
||||
address := firstTextAttribute(attrs, resourceAttrs, "server.address", "network.peer.address", "net.peer.name")
|
||||
port := firstTextAttribute(attrs, resourceAttrs, "server.port", "network.peer.port", "net.peer.port")
|
||||
if address != "" && port != "" && !strings.Contains(address, ":") {
|
||||
address += ":" + port
|
||||
}
|
||||
return product, limitCharacters(displayName, 255), limitCharacters(address, 255), true
|
||||
}
|
||||
|
||||
func knownMiddlewareProduct(value string) string {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
for _, product := range []string{"nginx", "apache", "tomcat", "redis", "kafka", "rabbitmq", "elasticsearch", "activemq", "rocketmq", "websphere"} {
|
||||
if strings.Contains(value, product) {
|
||||
return product
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func middlewareCandidateKey(businessSystemID uint, product, name, address string) string {
|
||||
raw := fmt.Sprintf("%d\x00%s\x00%s\x00%s", businessSystemID, product, strings.ToLower(name), strings.ToLower(address))
|
||||
digest := sha256.Sum256([]byte(raw))
|
||||
return hex.EncodeToString(digest[:])
|
||||
}
|
||||
|
||||
func addDependency(aggregates map[string]*dependencyAggregate, source, target string, span models.TraceSpan) {
|
||||
key := source + "\x00" + target
|
||||
item := aggregates[key]
|
||||
|
||||
Reference in New Issue
Block a user