Skip to content

Commit f8dde8b

Browse files
committed
refactor(request-trace): unify include/exclude filters and simplify config dialog
replace flat request trace filters with reusable include/exclude rule sets update backend matching flow so response-stage filters control final trace persistence add unit tests for include/exclude, status code, method, wildcard, and duration matching update frontend config types and serialization to use the new filter shape simplify the request trace dialog layout and reduce duplicated localization keys
1 parent f798b7c commit f8dde8b

8 files changed

Lines changed: 474 additions & 251 deletions

File tree

src/BE/tests/Chats.BE.UnitTest/Services/RequestTracing/RequestTraceHelperTests.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Chats.BE.Services.Configs;
12
using Chats.BE.Services.RequestTracing;
23

34
namespace Chats.BE.UnitTest.Services.RequestTracing;
@@ -30,4 +31,89 @@ public void IsSmallKnownLength_ShouldRespectKnownLengthAndFloorCap()
3031
Assert.True(RequestTraceHelper.IsSmallKnownLength(2 * 1024 * 1024, 3 * 1024 * 1024));
3132
Assert.False(RequestTraceHelper.IsSmallKnownLength(4 * 1024 * 1024, 3 * 1024 * 1024));
3233
}
34+
35+
[Fact]
36+
public void MatchRequestStageFilters_ShouldIgnoreStatusCodeOnlyRulesUntilResponseStage()
37+
{
38+
RequestTraceFilters filters = new()
39+
{
40+
Include = new RequestTraceFilterRuleSet
41+
{
42+
StatusCodes = ["2xx"]
43+
},
44+
Exclude = new RequestTraceFilterRuleSet
45+
{
46+
StatusCodes = ["5xx"]
47+
}
48+
};
49+
50+
Assert.True(RequestTraceHelper.MatchRequestStageFilters(filters, "source-a", "GET", "/v1/chat/completions"));
51+
Assert.True(RequestTraceHelper.MatchResponseStageFilters(filters, "source-a", "GET", "/v1/chat/completions", 200, 10));
52+
Assert.False(RequestTraceHelper.MatchResponseStageFilters(filters, "source-a", "GET", "/v1/chat/completions", 500, 10));
53+
}
54+
55+
[Fact]
56+
public void MatchRequestStageFilters_ShouldApplyIncludeAndExcludeRules()
57+
{
58+
RequestTraceFilters filters = new()
59+
{
60+
Include = new RequestTraceFilterRuleSet
61+
{
62+
SourcePatterns = ["chatservice.*"],
63+
UrlPatterns = ["/v1/*"],
64+
Methods = ["POST"]
65+
},
66+
Exclude = new RequestTraceFilterRuleSet
67+
{
68+
UrlPatterns = ["/v1/internal/*"]
69+
}
70+
};
71+
72+
Assert.True(RequestTraceHelper.MatchRequestStageFilters(filters, "ChatService.Gemini", "post", "/V1/chat/completions"));
73+
Assert.False(RequestTraceHelper.MatchRequestStageFilters(filters, "ChatService.Gemini", "GET", "/V1/chat/completions"));
74+
Assert.False(RequestTraceHelper.MatchRequestStageFilters(filters, "ChatService.Gemini", "POST", "/v1/internal/health"));
75+
Assert.False(RequestTraceHelper.MatchRequestStageFilters(filters, "OtherService", "POST", "/v1/chat/completions"));
76+
}
77+
78+
[Fact]
79+
public void MatchResponseStageFilters_ShouldRespectIncludedStatusAndDuration()
80+
{
81+
RequestTraceFilters filters = new()
82+
{
83+
Include = new RequestTraceFilterRuleSet
84+
{
85+
UrlPatterns = ["/v1/*"],
86+
StatusCodes = ["2xx", "304"]
87+
},
88+
MinDurationMs = 100
89+
};
90+
91+
Assert.False(RequestTraceHelper.MatchResponseStageFilters(filters, "source-a", "GET", "/v1/models", null, 120));
92+
Assert.False(RequestTraceHelper.MatchResponseStageFilters(filters, "source-a", "GET", "/v1/models", 500, 120));
93+
Assert.False(RequestTraceHelper.MatchResponseStageFilters(filters, "source-a", "GET", "/v1/models", 200, 99));
94+
Assert.True(RequestTraceHelper.MatchResponseStageFilters(filters, "source-a", "GET", "/v1/models", 200, 120));
95+
Assert.True(RequestTraceHelper.MatchResponseStageFilters(filters, "source-a", "GET", "/v1/models", 304, 120));
96+
}
97+
98+
[Fact]
99+
public void MatchResponseStageFilters_ShouldLetExcludeOverrideInclude()
100+
{
101+
RequestTraceFilters filters = new()
102+
{
103+
Include = new RequestTraceFilterRuleSet
104+
{
105+
UrlPatterns = ["/v1/*"],
106+
Methods = ["POST"],
107+
StatusCodes = ["2xx"]
108+
},
109+
Exclude = new RequestTraceFilterRuleSet
110+
{
111+
UrlPatterns = ["/v1/internal/*"],
112+
StatusCodes = ["2xx"]
113+
}
114+
};
115+
116+
Assert.True(RequestTraceHelper.MatchResponseStageFilters(filters, "source-a", "POST", "/v1/chat/completions", 200, 20));
117+
Assert.False(RequestTraceHelper.MatchResponseStageFilters(filters, "source-a", "POST", "/v1/internal/cache", 200, 20));
118+
}
33119
}

src/BE/web/Services/Configs/RequestTraceConfig.cs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,30 @@ public record RequestTraceConfig
4848
/// 请求追踪过滤条件。
4949
/// </summary>
5050
public record RequestTraceFilters
51+
{
52+
/// <summary>
53+
/// 需要命中的过滤规则;为空表示不做包含限制。
54+
/// </summary>
55+
[JsonPropertyName("include")]
56+
public RequestTraceFilterRuleSet Include { get; init; } = new();
57+
58+
/// <summary>
59+
/// 需要排除的过滤规则;为空表示不做排除限制。
60+
/// </summary>
61+
[JsonPropertyName("exclude")]
62+
public RequestTraceFilterRuleSet Exclude { get; init; } = new();
63+
64+
/// <summary>
65+
/// 最小耗时阈值(毫秒),仅记录耗时不低于该值的请求。
66+
/// </summary>
67+
[JsonPropertyName("minDurationMs")]
68+
public int? MinDurationMs { get; init; }
69+
}
70+
71+
/// <summary>
72+
/// 请求追踪过滤规则集合。
73+
/// </summary>
74+
public record RequestTraceFilterRuleSet
5175
{
5276
/// <summary>
5377
/// 限定命中的来源名称模式列表;入站表示IP地址,出站表示 HttpClient 注入名称(如 ChatService.Gemini)。
@@ -57,35 +81,23 @@ public record RequestTraceFilters
5781
public string[]? SourcePatterns { get; init; } = null;
5882

5983
/// <summary>
60-
/// 仅包含这些 URL 模式(可使用通配符);为 null 表示不过滤(允许所有)。
61-
/// </summary>
62-
[JsonPropertyName("includeUrlPatterns")]
63-
public string[]? IncludeUrlPatterns { get; init; } = null;
64-
65-
/// <summary>
66-
/// 排除这些 URL 模式(可使用通配符);为 null 表示不过滤(允许所有)。
84+
/// 限定命中的 URL 模式(可使用通配符);为 null 表示不按 URL 限制(允许所有)。
6785
/// </summary>
68-
[JsonPropertyName("excludeUrlPatterns")]
69-
public string[]? ExcludeUrlPatterns { get; init; } = null;
86+
[JsonPropertyName("urlPatterns")]
87+
public string[]? UrlPatterns { get; init; } = null;
7088

7189
/// <summary>
72-
/// 仅包含这些 HTTP 方法(如 GET、POST);为 null 表示不过滤(允许所有)。
90+
/// 限定命中的 HTTP 方法(如 GET、POST);为 null 表示不按方法限制(允许所有)。
7391
/// </summary>
7492
[JsonPropertyName("methods")]
7593
public string[]? Methods { get; init; } = null;
7694

7795
/// <summary>
78-
/// 仅包含这些状态码规则;为 null 表示不过滤(允许所有)。
96+
/// 限定命中的状态码规则;为 null 表示不按状态码限制(允许所有)。
7997
/// 支持精确状态码(如 200、429)和分组写法(如 2xx、4xx、5xx)。
8098
/// </summary>
8199
[JsonPropertyName("statusCodes")]
82100
public string[]? StatusCodes { get; init; } = null;
83-
84-
/// <summary>
85-
/// 最小耗时阈值(毫秒),仅记录耗时不低于该值的请求。
86-
/// </summary>
87-
[JsonPropertyName("minDurationMs")]
88-
public int? MinDurationMs { get; init; }
89101
}
90102

91103
/// <summary>

0 commit comments

Comments
 (0)