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 6f059af2d7 Share trace related log context
6f059af2d7 is described below

commit 6f059af2d75d089681503114ea2fe94114af8b36
Author: Logic <[email protected]>
AuthorDate: Wed Jun 10 08:28:21 2026 +0800

    Share trace related log context
---
 web-next/app/trace/manage/trace-manage-page.tsx | 32 +--------
 web-next/lib/trace-manage/controller.test.ts    | 40 +++++++++++-
 web-next/lib/trace-manage/controller.ts         | 86 ++++++++++++++++++++++++-
 3 files changed, 124 insertions(+), 34 deletions(-)

diff --git a/web-next/app/trace/manage/trace-manage-page.tsx 
b/web-next/app/trace/manage/trace-manage-page.tsx
index ad6edeb2fe..5153e6e5aa 100644
--- a/web-next/app/trace/manage/trace-manage-page.tsx
+++ b/web-next/app/trace/manage/trace-manage-page.tsx
@@ -45,7 +45,7 @@ import { resolveAppliedTimeContext, sanitizeTimeContext, type 
TimeContext } from
 import type { LogEntry, OtlpRelatedMetrics, PageResult, TraceDetail, 
TraceListItem, TraceOverview, TraceSpanNode } from '@/lib/types';
 import { ObservabilityStatusState, ObservabilityWaterfall, type 
ObservabilityWaterfallTick } from '../../../components/observability';
 import { OverlayDialog } from '../../../components/workbench/overlay-dialog';
-import { loadTraceDetailBundle } from '../../../lib/trace-manage/controller';
+import { buildTraceRelatedLogsUrlFromHref, loadTraceDetailBundle } from 
'../../../lib/trace-manage/controller';
 import { buildResetTraceManageRoute, buildTraceManageRoute } from 
'./route-state';
 
 type TraceManageData = {
@@ -302,35 +302,7 @@ function buildTraceRelatedMetricsApiUrl(metricsHref: 
string | null | undefined)
 }
 
 function buildTraceRelatedLogsApiUrl(logsHref: string | null | undefined) {
-  if (!logsHref) return null;
-  const href = new URL(logsHref, 'http://localhost');
-  const sourceParams = href.searchParams;
-  const params = new URLSearchParams();
-  [
-    'traceId',
-    'spanId',
-    'serviceName',
-    'serviceNamespace',
-    'environment',
-    'entityId',
-    'entityName',
-    'collector',
-    'template',
-    'source',
-    'resourceFilter',
-    'attributeFilter',
-    'start',
-    'end',
-    'timeRange',
-    'tz'
-  ].forEach(key => {
-    const value = sourceParams.get(key)?.trim();
-    if (value) params.set(key, value);
-  });
-  if (!params.get('traceId') && !params.get('serviceName') && 
!params.get('entityId')) return null;
-  params.set('pageIndex', '0');
-  params.set('pageSize', '3');
-  return `/logs/list?${params.toString()}`;
+  return buildTraceRelatedLogsUrlFromHref(logsHref, { pageSize: '3' });
 }
 
 function buildTraceRelatedLogHref(logsHref: string | null | undefined, entry: 
LogEntry) {
diff --git a/web-next/lib/trace-manage/controller.test.ts 
b/web-next/lib/trace-manage/controller.test.ts
index ace6572071..a58889b4bf 100644
--- a/web-next/lib/trace-manage/controller.test.ts
+++ b/web-next/lib/trace-manage/controller.test.ts
@@ -1,5 +1,5 @@
 import { describe, expect, it, vi } from 'vitest';
-import { loadRelatedLogs, loadTraceDetailBundle } from './controller';
+import { buildTraceRelatedLogsUrlFromHref, loadRelatedLogs, 
loadTraceDetailBundle } from './controller';
 
 describe('trace controller', () => {
   it('loads detail and spans together for a trace id', async () => {
@@ -22,7 +22,43 @@ describe('trace controller', () => {
 
     const result = await loadRelatedLogs(apiGet, 't-1');
 
-    
expect(apiGet).toHaveBeenCalledWith('/logs/list?pageIndex=0&pageSize=5&traceId=t-1');
+    const url = new URL(String(apiGet.mock.calls[0]?.[0]), 
'https://example.test');
+    expect(url.pathname).toBe('/logs/list');
+    expect(url.searchParams.get('traceId')).toBe('t-1');
+    expect(url.searchParams.get('pageIndex')).toBe('0');
+    expect(url.searchParams.get('pageSize')).toBe('5');
     expect(result).toEqual([{ traceId: 't-1', body: 'log' }]);
   });
+
+  it('loads related logs with selected span and entity context', async () => {
+    const apiGet = vi.fn().mockResolvedValue({ content: [{ traceId: 't-1', 
spanId: 's-1', body: 'span log' }] });
+
+    const result = await loadRelatedLogs(apiGet, {
+      traceId: 't-1',
+      spanId: 's-1',
+      serviceName: 'checkout',
+      serviceNamespace: 'payments',
+      environment: 'prod',
+      entityId: 42,
+      entityName: 'checkout',
+      collector: 'collector-a',
+      template: 'spring-boot',
+      source: 'otlp',
+      start: 1713200000000,
+      end: 1713203600000,
+      pageSize: 3
+    });
+
+    expect(apiGet).toHaveBeenCalledWith(
+      
'/logs/list?traceId=t-1&spanId=s-1&serviceName=checkout&serviceNamespace=payments&environment=prod&entityId=42&entityName=checkout&collector=collector-a&template=spring-boot&source=otlp&start=1713200000000&end=1713203600000&pageIndex=0&pageSize=3'
+    );
+    expect(result).toEqual([{ traceId: 't-1', spanId: 's-1', body: 'span log' 
}]);
+  });
+
+  it('builds related log urls from trace handoff hrefs', () => {
+    expect(buildTraceRelatedLogsUrlFromHref(
+      
'/log/manage?traceId=t-1&spanId=s-1&serviceName=checkout&serviceNamespace=payments&returnTo=%2Ftrace%2Fmanage',
+      { pageSize: '3' }
+    
)).toBe('/logs/list?traceId=t-1&spanId=s-1&serviceName=checkout&serviceNamespace=payments&pageIndex=0&pageSize=3');
+  });
 });
diff --git a/web-next/lib/trace-manage/controller.ts 
b/web-next/lib/trace-manage/controller.ts
index 2d3a6bfe2b..65716e91c0 100644
--- a/web-next/lib/trace-manage/controller.ts
+++ b/web-next/lib/trace-manage/controller.ts
@@ -2,6 +2,84 @@ import type { LogEntry, PageResult, TraceDetail, TraceSpanNode 
} from '@/lib/typ
 
 type ApiGetter = <T>(url: string) => Promise<T>;
 
+export type TraceRelatedLogsQuery = {
+  traceId?: string | null;
+  spanId?: string | null;
+  serviceName?: string | null;
+  serviceNamespace?: string | null;
+  environment?: string | null;
+  entityId?: string | number | null;
+  entityName?: string | null;
+  collector?: string | null;
+  template?: string | null;
+  source?: string | null;
+  resourceFilter?: string | null;
+  attributeFilter?: string | null;
+  start?: string | number | null;
+  end?: string | number | null;
+  timeRange?: string | null;
+  tz?: string | null;
+  pageIndex?: string | number | null;
+  pageSize?: string | number | null;
+};
+
+const RELATED_LOG_CONTEXT_KEYS: Array<keyof TraceRelatedLogsQuery> = [
+  'traceId',
+  'spanId',
+  'serviceName',
+  'serviceNamespace',
+  'environment',
+  'entityId',
+  'entityName',
+  'collector',
+  'template',
+  'source',
+  'resourceFilter',
+  'attributeFilter',
+  'start',
+  'end',
+  'timeRange',
+  'tz'
+];
+
+function appendRelatedLogParam(params: URLSearchParams, key: string, value: 
string | number | null | undefined) {
+  const normalized = String(value ?? '').trim();
+  if (normalized) params.set(key, normalized);
+}
+
+export function buildTraceRelatedLogsUrl(query: TraceRelatedLogsQuery = {}) {
+  const params = new URLSearchParams();
+  RELATED_LOG_CONTEXT_KEYS.forEach(key => {
+    appendRelatedLogParam(params, key, query[key]);
+  });
+  if (!params.get('traceId') && !params.get('serviceName') && 
!params.get('entityId')) {
+    return null;
+  }
+  appendRelatedLogParam(params, 'pageIndex', query.pageIndex ?? '0');
+  appendRelatedLogParam(params, 'pageSize', query.pageSize ?? '5');
+  return `/logs/list?${params.toString()}`;
+}
+
+export function buildTraceRelatedLogsUrlFromHref(
+  logsHref: string | null | undefined,
+  options: Pick<TraceRelatedLogsQuery, 'pageIndex' | 'pageSize'> = {}
+) {
+  if (!logsHref) return null;
+  const href = new URL(logsHref, 'http://localhost');
+  const query = 
RELATED_LOG_CONTEXT_KEYS.reduce<TraceRelatedLogsQuery>((context, key) => {
+    const value = href.searchParams.get(key);
+    if (value?.trim()) {
+      return { ...context, [key]: value };
+    }
+    return context;
+  }, {});
+  return buildTraceRelatedLogsUrl({
+    ...query,
+    pageIndex: options.pageIndex,
+    pageSize: options.pageSize
+  });
+}
+
 export async function loadTraceDetailBundle(apiGet: ApiGetter, traceId: 
string): Promise<{
   detail: TraceDetail;
   spans: TraceSpanNode[];
@@ -14,7 +92,11 @@ export async function loadTraceDetailBundle(apiGet: 
ApiGetter, traceId: string):
   return { detail, spans };
 }
 
-export async function loadRelatedLogs(apiGet: ApiGetter, traceId: string): 
Promise<LogEntry[]> {
-  const result = await 
apiGet<PageResult<LogEntry>>(`/logs/list?pageIndex=0&pageSize=5&traceId=${encodeURIComponent(traceId)}`);
+export async function loadRelatedLogs(apiGet: ApiGetter, query: string | 
TraceRelatedLogsQuery): Promise<LogEntry[]> {
+  const url = typeof query === 'string'
+    ? buildTraceRelatedLogsUrl({ traceId: query })
+    : buildTraceRelatedLogsUrl(query);
+  if (!url) return [];
+  const result = await apiGet<PageResult<LogEntry>>(url);
   return result.content || [];
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to