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]

Reply via email to