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]