This is an automated email from the ASF dual-hosted git repository.
zqr10159 pushed a commit to branch 2.0.0
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/2.0.0 by this push:
new 469251108e Add operation filters to trace log handoffs
469251108e is described below
commit 469251108e3b47c4c9dc265a92c7754f224bdc56
Author: Logic <[email protected]>
AuthorDate: Wed Jun 10 09:03:28 2026 +0800
Add operation filters to trace log handoffs
---
web-next/lib/trace-manage/view-model.test.ts | 35 ++++++++++++++++++++++++++++
web-next/lib/trace-manage/view-model.ts | 33 ++++++++++++++++++++++++++
2 files changed, 68 insertions(+)
diff --git a/web-next/lib/trace-manage/view-model.test.ts
b/web-next/lib/trace-manage/view-model.test.ts
index a7ac84bb06..fe58661ef5 100644
--- a/web-next/lib/trace-manage/view-model.test.ts
+++ b/web-next/lib/trace-manage/view-model.test.ts
@@ -916,6 +916,7 @@ describe('trace view model', () => {
expect(logParams.get('end')).toBe('1100');
expect(logParams.get('returnTo')).toBe('/trace/manage?traceId=trace-1&spanId=span-root-1');
expect(logParams.get('returnLabel')).toBeNull();
+ expect(logParams.get('attributeFilter')).toBeNull();
const metricsParams = new URL(result.metricsHref,
'https://example.com').searchParams;
expect(metricsParams.get('traceId')).toBe('trace-1');
@@ -929,6 +930,40 @@ describe('trace view model', () => {
expect(metricsParams.get('returnLabel')).toBeNull();
});
+ it('adds an executable log attribute filter for operation-level trace
handoffs', () => {
+ const result = buildTraceHandoffLinks(
+ null,
+ {
+ spanName: 'POST /checkout',
+ serviceName: 'checkout',
+ spanAttributes: {
+ 'http.route': '/checkout/:id'
+ }
+ } as any,
+ {
+ serviceName: 'checkout',
+ serviceNamespace: 'payments',
+ environment: 'prod',
+ operationName: 'POST /checkout',
+ start: '1000',
+ end: '1100'
+ },
+ {
+ logsReturnTo:
'/trace/manage?serviceName=checkout&operationName=POST%20%2Fcheckout'
+ }
+ );
+
+ const logParams = new URL(result.logsHref,
'https://example.com').searchParams;
+ expect(logParams.get('traceId')).toBeNull();
+ expect(logParams.get('spanId')).toBeNull();
+ expect(logParams.get('serviceName')).toBe('checkout');
+ expect(logParams.get('serviceNamespace')).toBe('payments');
+ expect(logParams.get('environment')).toBe('prod');
+ expect(logParams.get('operationName')).toBe('POST /checkout');
+
expect(logParams.get('attributeFilter')).toBe('http.route="/checkout/:id"');
+
expect(logParams.get('returnTo')).toBe('/trace/manage?serviceName=checkout&operationName=POST+%2Fcheckout');
+ });
+
it('does not round decimal trace detail time bounds into handoff urls', ()
=> {
const result = buildTraceHandoffLinks(
{
diff --git a/web-next/lib/trace-manage/view-model.ts
b/web-next/lib/trace-manage/view-model.ts
index 1b5992073a..d1b1cf91ed 100644
--- a/web-next/lib/trace-manage/view-model.ts
+++ b/web-next/lib/trace-manage/view-model.ts
@@ -451,6 +451,17 @@ function buildMetricFilterExpression(name: string, value:
string | undefined) {
return `${trimmedName}="${escapeMetricFilterValue(trimmedValue)}"`;
}
+function buildLogAttributeFilterExpression(name: string, value: string |
undefined) {
+ const trimmedName = name.trim();
+ const trimmedValue = value?.trim();
+ if (!/^[A-Za-z0-9_.:-]+$/.test(trimmedName) || !trimmedValue || trimmedValue
=== '-') return undefined;
+ return `${trimmedName}="${escapeMetricFilterValue(trimmedValue)}"`;
+}
+
+function mergeTraceLogAttributeFilters(currentFilter: string | null |
undefined, nextFilter: string | undefined) {
+ return [currentFilter?.trim(), nextFilter?.trim()].filter(Boolean).join('
and ') || undefined;
+}
+
function buildTraceMetricsResourceFilter(detail: TraceDetail | null,
selectedSpan: TraceSpanNode | null) {
const expressions = [
buildMetricFilterExpression('k8s.namespace.name', readTraceSignalAttribute(
@@ -488,6 +499,19 @@ function buildTraceMetricsResourceFilter(detail:
TraceDetail | null, selectedSpa
.join(' and ');
}
+function buildTraceLogsOperationAttributeFilter(
+ detail: TraceDetail | null,
+ selectedSpan: TraceSpanNode | null,
+ operationName: string | undefined,
+ traceId: string | undefined,
+ spanId: string | undefined
+) {
+ if (traceId || spanId) return undefined;
+ const httpRoute = readTraceSignalAttribute(detail, selectedSpan,
'http.route', 'http_route');
+ if (httpRoute) return buildLogAttributeFilterExpression('http.route',
httpRoute);
+ return buildLogAttributeFilterExpression('span.name',
firstText(selectedSpan?.spanName, detail?.rootSpanName, operationName));
+}
+
function durationNanosToWholeMillis(value?: number | null) {
if (value == null || !Number.isFinite(value) || value < 0) return undefined;
const millis = value / 1_000_000;
@@ -895,6 +919,15 @@ export function buildTraceHandoffLinks(
if (traceId) logsParams.set('traceId', traceId);
if (spanId) logsParams.set('spanId', spanId);
appendSignalRouteContext(logsParams, logsContext);
+ const logOperationAttributeFilter = buildTraceLogsOperationAttributeFilter(
+ detail,
+ selectedSpan,
+ operationName,
+ traceId,
+ spanId
+ );
+ const mergedLogAttributeFilter =
mergeTraceLogAttributeFilters(logsParams.get('attributeFilter'),
logOperationAttributeFilter);
+ if (mergedLogAttributeFilter) logsParams.set('attributeFilter',
mergedLogAttributeFilter);
const metricsParams = new URLSearchParams();
if (traceId) metricsParams.set('traceId', traceId);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]