diff --git a/backend/kitex_gen/coze/loop/observability/openapi/coze.loop.observability.openapi.go b/backend/kitex_gen/coze/loop/observability/openapi/coze.loop.observability.openapi.go index 75b6538d4..c64556a1c 100644 --- a/backend/kitex_gen/coze/loop/observability/openapi/coze.loop.observability.openapi.go +++ b/backend/kitex_gen/coze/loop/observability/openapi/coze.loop.observability.openapi.go @@ -5040,6 +5040,7 @@ func (p *SearchTraceOApiData) Field4DeepEqual(src *bool) bool { type SearchTraceTreeOApiRequest struct { WorkspaceID *int64 `thrift:"workspace_id,1,optional" frugal:"1,optional,i64" json:"workspace_id" form:"workspace_id" ` + Logid *string `thrift:"logid,2,optional" frugal:"2,optional,string" form:"logid" json:"logid,omitempty"` TraceID *string `thrift:"trace_id,3,optional" frugal:"3,optional,string" json:"trace_id" form:"trace_id" ` // ms StartTime *int64 `thrift:"start_time,4,optional" frugal:"4,optional,i64" json:"start_time" form:"start_time" ` @@ -5074,6 +5075,18 @@ func (p *SearchTraceTreeOApiRequest) GetWorkspaceID() (v int64) { return *p.WorkspaceID } +var SearchTraceTreeOApiRequest_Logid_DEFAULT string + +func (p *SearchTraceTreeOApiRequest) GetLogid() (v string) { + if p == nil { + return + } + if !p.IsSetLogid() { + return SearchTraceTreeOApiRequest_Logid_DEFAULT + } + return *p.Logid +} + var SearchTraceTreeOApiRequest_TraceID_DEFAULT string func (p *SearchTraceTreeOApiRequest) GetTraceID() (v string) { @@ -5191,6 +5204,9 @@ func (p *SearchTraceTreeOApiRequest) GetBase() (v *base.Base) { func (p *SearchTraceTreeOApiRequest) SetWorkspaceID(val *int64) { p.WorkspaceID = val } +func (p *SearchTraceTreeOApiRequest) SetLogid(val *string) { + p.Logid = val +} func (p *SearchTraceTreeOApiRequest) SetTraceID(val *string) { p.TraceID = val } @@ -5224,6 +5240,7 @@ func (p *SearchTraceTreeOApiRequest) SetBase(val *base.Base) { var fieldIDToName_SearchTraceTreeOApiRequest = map[int16]string{ 1: "workspace_id", + 2: "logid", 3: "trace_id", 4: "start_time", 5: "end_time", @@ -5240,6 +5257,10 @@ func (p *SearchTraceTreeOApiRequest) IsSetWorkspaceID() bool { return p.WorkspaceID != nil } +func (p *SearchTraceTreeOApiRequest) IsSetLogid() bool { + return p.Logid != nil +} + func (p *SearchTraceTreeOApiRequest) IsSetTraceID() bool { return p.TraceID != nil } @@ -5303,6 +5324,14 @@ func (p *SearchTraceTreeOApiRequest) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 2: + if fieldTypeId == thrift.STRING { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } case 3: if fieldTypeId == thrift.STRING { if err = p.ReadField3(iprot); err != nil { @@ -5430,6 +5459,17 @@ func (p *SearchTraceTreeOApiRequest) ReadField1(iprot thrift.TProtocol) error { p.WorkspaceID = _field return nil } +func (p *SearchTraceTreeOApiRequest) ReadField2(iprot thrift.TProtocol) error { + + var _field *string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = &v + } + p.Logid = _field + return nil +} func (p *SearchTraceTreeOApiRequest) ReadField3(iprot thrift.TProtocol) error { var _field *string @@ -5542,6 +5582,10 @@ func (p *SearchTraceTreeOApiRequest) Write(oprot thrift.TProtocol) (err error) { fieldId = 1 goto WriteFieldError } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } if err = p.writeField3(oprot); err != nil { fieldId = 3 goto WriteFieldError @@ -5618,6 +5662,24 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) } +func (p *SearchTraceTreeOApiRequest) writeField2(oprot thrift.TProtocol) (err error) { + if p.IsSetLogid() { + if err = oprot.WriteFieldBegin("logid", thrift.STRING, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(*p.Logid); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} func (p *SearchTraceTreeOApiRequest) writeField3(oprot thrift.TProtocol) (err error) { if p.IsSetTraceID() { if err = oprot.WriteFieldBegin("trace_id", thrift.STRING, 3); err != nil { @@ -5814,6 +5876,9 @@ func (p *SearchTraceTreeOApiRequest) DeepEqual(ano *SearchTraceTreeOApiRequest) if !p.Field1DeepEqual(ano.WorkspaceID) { return false } + if !p.Field2DeepEqual(ano.Logid) { + return false + } if !p.Field3DeepEqual(ano.TraceID) { return false } @@ -5859,6 +5924,18 @@ func (p *SearchTraceTreeOApiRequest) Field1DeepEqual(src *int64) bool { } return true } +func (p *SearchTraceTreeOApiRequest) Field2DeepEqual(src *string) bool { + + if p.Logid == src { + return true + } else if p.Logid == nil || src == nil { + return false + } + if strings.Compare(*p.Logid, *src) != 0 { + return false + } + return true +} func (p *SearchTraceTreeOApiRequest) Field3DeepEqual(src *string) bool { if p.TraceID == src { diff --git a/backend/kitex_gen/coze/loop/observability/openapi/k-coze.loop.observability.openapi.go b/backend/kitex_gen/coze/loop/observability/openapi/k-coze.loop.observability.openapi.go index cd57ca4f5..075a8b0a9 100644 --- a/backend/kitex_gen/coze/loop/observability/openapi/k-coze.loop.observability.openapi.go +++ b/backend/kitex_gen/coze/loop/observability/openapi/k-coze.loop.observability.openapi.go @@ -3764,6 +3764,20 @@ func (p *SearchTraceTreeOApiRequest) FastRead(buf []byte) (int, error) { goto SkipFieldError } } + case 2: + if fieldTypeId == thrift.STRING { + l, err = p.FastReadField2(buf[offset:]) + offset += l + if err != nil { + goto ReadFieldError + } + } else { + l, err = thrift.Binary.Skip(buf[offset:], fieldTypeId) + offset += l + if err != nil { + goto SkipFieldError + } + } case 3: if fieldTypeId == thrift.STRING { l, err = p.FastReadField3(buf[offset:]) @@ -3943,6 +3957,20 @@ func (p *SearchTraceTreeOApiRequest) FastReadField1(buf []byte) (int, error) { return offset, nil } +func (p *SearchTraceTreeOApiRequest) FastReadField2(buf []byte) (int, error) { + offset := 0 + + var _field *string + if v, l, err := thrift.Binary.ReadString(buf[offset:]); err != nil { + return offset, err + } else { + offset += l + _field = &v + } + p.Logid = _field + return offset, nil +} + func (p *SearchTraceTreeOApiRequest) FastReadField3(buf []byte) (int, error) { offset := 0 @@ -4089,6 +4117,7 @@ func (p *SearchTraceTreeOApiRequest) FastWriteNocopy(buf []byte, w thrift.Nocopy offset += p.fastWriteField5(buf[offset:], w) offset += p.fastWriteField6(buf[offset:], w) offset += p.fastWriteField11(buf[offset:], w) + offset += p.fastWriteField2(buf[offset:], w) offset += p.fastWriteField3(buf[offset:], w) offset += p.fastWriteField8(buf[offset:], w) offset += p.fastWriteField10(buf[offset:], w) @@ -4104,6 +4133,7 @@ func (p *SearchTraceTreeOApiRequest) BLength() int { l := 0 if p != nil { l += p.field1Length() + l += p.field2Length() l += p.field3Length() l += p.field4Length() l += p.field5Length() @@ -4128,6 +4158,15 @@ func (p *SearchTraceTreeOApiRequest) fastWriteField1(buf []byte, w thrift.Nocopy return offset } +func (p *SearchTraceTreeOApiRequest) fastWriteField2(buf []byte, w thrift.NocopyWriter) int { + offset := 0 + if p.IsSetLogid() { + offset += thrift.Binary.WriteFieldBegin(buf[offset:], thrift.STRING, 2) + offset += thrift.Binary.WriteStringNocopy(buf[offset:], w, *p.Logid) + } + return offset +} + func (p *SearchTraceTreeOApiRequest) fastWriteField3(buf []byte, w thrift.NocopyWriter) int { offset := 0 if p.IsSetTraceID() { @@ -4225,6 +4264,15 @@ func (p *SearchTraceTreeOApiRequest) field1Length() int { return l } +func (p *SearchTraceTreeOApiRequest) field2Length() int { + l := 0 + if p.IsSetLogid() { + l += thrift.Binary.FieldBeginLength() + l += thrift.Binary.StringLengthNocopy(*p.Logid) + } + return l +} + func (p *SearchTraceTreeOApiRequest) field3Length() int { l := 0 if p.IsSetTraceID() { @@ -4324,6 +4372,14 @@ func (p *SearchTraceTreeOApiRequest) DeepCopy(s interface{}) error { p.WorkspaceID = &tmp } + if src.Logid != nil { + var tmp string + if *src.Logid != "" { + tmp = kutils.StringDeepCopy(*src.Logid) + } + p.Logid = &tmp + } + if src.TraceID != nil { var tmp string if *src.TraceID != "" { diff --git a/backend/modules/observability/application/openapi.go b/backend/modules/observability/application/openapi.go index de64e9333..86c92aa8b 100644 --- a/backend/modules/observability/application/openapi.go +++ b/backend/modules/observability/application/openapi.go @@ -709,7 +709,7 @@ func (o *OpenAPIApplication) SearchTraceTreeOApi(ctx context.Context, req *opena }() if req != nil && req.GetStartTime() == 0 && req.GetEndTime() == 0 && o.timeRange != nil { - st, et := o.timeRange.GetTimeRange(ctx, strconv.FormatInt(req.GetWorkspaceID(), 10), "", req.GetTraceID(), 1000*60*60*24) + st, et := o.timeRange.GetTimeRange(ctx, strconv.FormatInt(req.GetWorkspaceID(), 10), req.GetLogid(), req.GetTraceID(), 1000*60*60*24) if st != nil && et != nil { req.StartTime = st req.EndTime = et @@ -769,7 +769,7 @@ func (o *OpenAPIApplication) SearchTraceTreeOApi(ctx context.Context, req *opena func (o *OpenAPIApplication) validateSearchTraceTreeOApiReq(ctx context.Context, req *openapi.SearchTraceTreeOApiRequest) error { if req == nil { return errorx.NewByCode(obErrorx.CommercialCommonInvalidParamCodeCode, errorx.WithExtraMsg("no request provided")) - } else if req.GetTraceID() == "" { + } else if req.GetTraceID() == "" && req.GetLogid() == "" { return errorx.NewByCode(obErrorx.CommercialCommonInvalidParamCodeCode, errorx.WithExtraMsg("at least need trace_id or log_id")) } else if req.Limit > MaxTraceTreeLength || req.Limit < 0 { return errorx.NewByCode(obErrorx.CommercialCommonInvalidParamCodeCode, errorx.WithExtraMsg("invalid limit")) @@ -801,6 +801,7 @@ func (o *OpenAPIApplication) buildSearchTraceTreeOApiReq(ctx context.Context, re ThirdPartyWorkspaceID: o.workspace.GetThirdPartyQueryWorkSpaceID(ctx, req.GetWorkspaceID()), Tenants: o.tenant.GetOAPIQueryTenants(ctx, platformType), TraceID: req.GetTraceID(), + LogID: req.GetLogid(), StartTime: req.GetStartTime(), EndTime: req.GetEndTime(), Limit: req.GetLimit(), diff --git a/backend/modules/observability/application/openapi_test.go b/backend/modules/observability/application/openapi_test.go index ea80dabb8..cf4d7420a 100755 --- a/backend/modules/observability/application/openapi_test.go +++ b/backend/modules/observability/application/openapi_test.go @@ -2835,12 +2835,35 @@ func TestOpenAPIApplication_validateSearchTraceTreeOApiReq(t *testing.T) { err := app.validateSearchTraceTreeOApiReq(context.Background(), nil) assert.Error(t, err) - // 测试空trace_id + // 测试trace_id和logid均为空 err = app.validateSearchTraceTreeOApiReq(context.Background(), &openapi.SearchTraceTreeOApiRequest{ TraceID: ptr.Of(""), + Logid: ptr.Of(""), }) assert.Error(t, err) + startTime := time.Now().Add(-time.Hour).UnixMilli() + endTime := time.Now().UnixMilli() + + // 测试仅传logid + err = app.validateSearchTraceTreeOApiReq(context.Background(), &openapi.SearchTraceTreeOApiRequest{ + Logid: ptr.Of("test-log-id"), + StartTime: &startTime, + EndTime: &endTime, + Limit: 10, + }) + assert.NoError(t, err) + + // 测试trace_id和logid同时存在 + err = app.validateSearchTraceTreeOApiReq(context.Background(), &openapi.SearchTraceTreeOApiRequest{ + TraceID: ptr.Of("test-trace-id"), + Logid: ptr.Of("test-log-id"), + StartTime: &startTime, + EndTime: &endTime, + Limit: 10, + }) + assert.NoError(t, err) + // 测试超过最大限制 err = app.validateSearchTraceTreeOApiReq(context.Background(), &openapi.SearchTraceTreeOApiRequest{ TraceID: ptr.Of("test-trace-id"), @@ -2856,8 +2879,8 @@ func TestOpenAPIApplication_validateSearchTraceTreeOApiReq(t *testing.T) { assert.Error(t, err) // 测试正常情况 - startTime := time.Now().UnixMilli() - endTime := time.Now().Add(1 * time.Hour).UnixMilli() // 结束时间晚于开始时间 + startTime = time.Now().UnixMilli() + endTime = time.Now().Add(1 * time.Hour).UnixMilli() // 结束时间晚于开始时间 err = app.validateSearchTraceTreeOApiReq(context.Background(), &openapi.SearchTraceTreeOApiRequest{ TraceID: ptr.Of("test-trace-id"), Limit: 10, @@ -2897,6 +2920,7 @@ func TestOpenAPIApplication_buildSearchTraceTreeOApiReq(t *testing.T) { req := &openapi.SearchTraceTreeOApiRequest{ WorkspaceID: ptr.Of(int64(123)), TraceID: ptr.Of("test-trace-id"), + Logid: ptr.Of("test-log-id"), StartTime: ptr.Of(time.Now().Add(-1 * time.Hour).UnixMilli()), EndTime: ptr.Of(time.Now().UnixMilli()), Limit: 10, @@ -2919,6 +2943,7 @@ func TestOpenAPIApplication_buildSearchTraceTreeOApiReq(t *testing.T) { assert.Equal(t, int64(123), result.WorkspaceID) assert.Equal(t, "third-party-123", result.ThirdPartyWorkspaceID) assert.Equal(t, "test-trace-id", result.TraceID) + assert.Equal(t, "test-log-id", result.LogID) assert.Equal(t, int32(10), result.Limit) assert.False(t, result.WithDetail) assert.Len(t, result.Tenants, 2) @@ -3088,6 +3113,64 @@ func TestOpenAPIApplication_SearchTraceTreeOApi(t *testing.T) { assert.Nil(t, resp) }) + t.Run("logid only resolves time range and searches trace", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + traceServiceMock := servicemocks.NewMockITraceService(ctrl) + authMock := rpcmocks.NewMockIAuthProvider(ctrl) + authMock.EXPECT().GetClaim(gomock.Any()).Return(nil).AnyTimes() + tenantMock := tenantmocks.NewMockITenantProvider(ctrl) + workspaceMock := workspacemocks.NewMockIWorkSpaceProvider(ctrl) + rateLimiter := limitermocks.NewMockIRateLimiter(ctrl) + traceConfigMock := configmocks.NewMockITraceConfig(ctrl) + metricsMock := metricsmocks.NewMockITraceMetrics(ctrl) + collectorMock := collectormocks.NewMockICollectorProvider(ctrl) + timeRangeMock := time_rangemocks.NewMockITimeRangeProvider(ctrl) + + now := time.Now().UnixMilli() + start := now - 3600000 + timeRangeMock.EXPECT().GetTimeRange(gomock.Any(), "123", "log123", "", int64(1000*60*60*24)).Return(&start, &now) + authMock.EXPECT().CheckQueryPermission(gomock.Any(), "123", "platform").Return(nil) + rateLimiter.EXPECT().AllowN(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&limiter.Result{Allowed: true}, nil) + traceConfigMock.EXPECT().GetQueryMaxQPS(gomock.Any(), gomock.Any()).Return(10, nil) + workspaceMock.EXPECT().GetThirdPartyQueryWorkSpaceID(gomock.Any(), int64(123)).Return("third-party-123") + tenantMock.EXPECT().GetOAPIQueryTenants(gomock.Any(), gomock.Any()).Return([]string{"tenant1"}) + traceServiceMock.EXPECT().SearchTraceOApi(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, req *service.SearchTraceOApiReq) (*service.SearchTraceOApiResp, error) { + assert.Empty(t, req.TraceID) + assert.Equal(t, "log123", req.LogID) + assert.Equal(t, start, req.StartTime) + assert.Equal(t, now, req.EndTime) + return &service.SearchTraceOApiResp{Spans: []*loop_span.Span{{SpanID: "test"}}}, nil + }) + metricsMock.EXPECT().EmitTraceOapi(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + collectorMock.EXPECT().CollectTraceOpenAPIEvent(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + + app := &OpenAPIApplication{ + traceService: traceServiceMock, + auth: authMock, + tenant: tenantMock, + workspace: workspaceMock, + rateLimiter: rateLimiter, + traceConfig: traceConfigMock, + metrics: metricsMock, + collector: collectorMock, + timeRange: timeRangeMock, + } + req := &openapi.SearchTraceTreeOApiRequest{ + WorkspaceID: ptr.Of(int64(123)), + Logid: ptr.Of("log123"), + Limit: 10, + PlatformType: ptr.Of(common.PlatformType("platform")), + } + + resp, err := app.SearchTraceTreeOApi(context.Background(), req) + assert.NoError(t, err) + if assert.NotNil(t, resp) && assert.NotNil(t, resp.Data) { + assert.Len(t, resp.Data.Spans, 1) + } + }) + t.Run("permission denied", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/idl/thrift/coze/loop/observability/coze.loop.observability.openapi.thrift b/idl/thrift/coze/loop/observability/coze.loop.observability.openapi.thrift index 25b408716..640336b95 100644 --- a/idl/thrift/coze/loop/observability/coze.loop.observability.openapi.thrift +++ b/idl/thrift/coze/loop/observability/coze.loop.observability.openapi.thrift @@ -106,6 +106,7 @@ struct SearchTraceOApiData { struct SearchTraceTreeOApiRequest { 1: optional i64 workspace_id (api.js_conv='true', go.tag='json:"workspace_id"', api.body="workspace_id") + 2: optional string logid (api.body="logid") 3: optional string trace_id (go.tag='json:"trace_id"', api.body="trace_id") 4: optional i64 start_time (api.js_conv='true', go.tag='json:"start_time"', api.body="start_time") // ms 5: optional i64 end_time (api.js_conv='true', go.tag='json:"end_time"', api.body="end_time") // ms