Skip to content

Commit 4692511

Browse files
committed
Add operation filters to trace log handoffs
1 parent bd5edde commit 4692511

2 files changed

Lines changed: 68 additions & 0 deletions

File tree

web-next/lib/trace-manage/view-model.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,7 @@ describe('trace view model', () => {
916916
expect(logParams.get('end')).toBe('1100');
917917
expect(logParams.get('returnTo')).toBe('/trace/manage?traceId=trace-1&spanId=span-root-1');
918918
expect(logParams.get('returnLabel')).toBeNull();
919+
expect(logParams.get('attributeFilter')).toBeNull();
919920

920921
const metricsParams = new URL(result.metricsHref, 'https://example.com').searchParams;
921922
expect(metricsParams.get('traceId')).toBe('trace-1');
@@ -929,6 +930,40 @@ describe('trace view model', () => {
929930
expect(metricsParams.get('returnLabel')).toBeNull();
930931
});
931932

933+
it('adds an executable log attribute filter for operation-level trace handoffs', () => {
934+
const result = buildTraceHandoffLinks(
935+
null,
936+
{
937+
spanName: 'POST /checkout',
938+
serviceName: 'checkout',
939+
spanAttributes: {
940+
'http.route': '/checkout/:id'
941+
}
942+
} as any,
943+
{
944+
serviceName: 'checkout',
945+
serviceNamespace: 'payments',
946+
environment: 'prod',
947+
operationName: 'POST /checkout',
948+
start: '1000',
949+
end: '1100'
950+
},
951+
{
952+
logsReturnTo: '/trace/manage?serviceName=checkout&operationName=POST%20%2Fcheckout'
953+
}
954+
);
955+
956+
const logParams = new URL(result.logsHref, 'https://example.com').searchParams;
957+
expect(logParams.get('traceId')).toBeNull();
958+
expect(logParams.get('spanId')).toBeNull();
959+
expect(logParams.get('serviceName')).toBe('checkout');
960+
expect(logParams.get('serviceNamespace')).toBe('payments');
961+
expect(logParams.get('environment')).toBe('prod');
962+
expect(logParams.get('operationName')).toBe('POST /checkout');
963+
expect(logParams.get('attributeFilter')).toBe('http.route="/checkout/:id"');
964+
expect(logParams.get('returnTo')).toBe('/trace/manage?serviceName=checkout&operationName=POST+%2Fcheckout');
965+
});
966+
932967
it('does not round decimal trace detail time bounds into handoff urls', () => {
933968
const result = buildTraceHandoffLinks(
934969
{

web-next/lib/trace-manage/view-model.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,17 @@ function buildMetricFilterExpression(name: string, value: string | undefined) {
451451
return `${trimmedName}="${escapeMetricFilterValue(trimmedValue)}"`;
452452
}
453453

454+
function buildLogAttributeFilterExpression(name: string, value: string | undefined) {
455+
const trimmedName = name.trim();
456+
const trimmedValue = value?.trim();
457+
if (!/^[A-Za-z0-9_.:-]+$/.test(trimmedName) || !trimmedValue || trimmedValue === '-') return undefined;
458+
return `${trimmedName}="${escapeMetricFilterValue(trimmedValue)}"`;
459+
}
460+
461+
function mergeTraceLogAttributeFilters(currentFilter: string | null | undefined, nextFilter: string | undefined) {
462+
return [currentFilter?.trim(), nextFilter?.trim()].filter(Boolean).join(' and ') || undefined;
463+
}
464+
454465
function buildTraceMetricsResourceFilter(detail: TraceDetail | null, selectedSpan: TraceSpanNode | null) {
455466
const expressions = [
456467
buildMetricFilterExpression('k8s.namespace.name', readTraceSignalAttribute(
@@ -488,6 +499,19 @@ function buildTraceMetricsResourceFilter(detail: TraceDetail | null, selectedSpa
488499
.join(' and ');
489500
}
490501

502+
function buildTraceLogsOperationAttributeFilter(
503+
detail: TraceDetail | null,
504+
selectedSpan: TraceSpanNode | null,
505+
operationName: string | undefined,
506+
traceId: string | undefined,
507+
spanId: string | undefined
508+
) {
509+
if (traceId || spanId) return undefined;
510+
const httpRoute = readTraceSignalAttribute(detail, selectedSpan, 'http.route', 'http_route');
511+
if (httpRoute) return buildLogAttributeFilterExpression('http.route', httpRoute);
512+
return buildLogAttributeFilterExpression('span.name', firstText(selectedSpan?.spanName, detail?.rootSpanName, operationName));
513+
}
514+
491515
function durationNanosToWholeMillis(value?: number | null) {
492516
if (value == null || !Number.isFinite(value) || value < 0) return undefined;
493517
const millis = value / 1_000_000;
@@ -895,6 +919,15 @@ export function buildTraceHandoffLinks(
895919
if (traceId) logsParams.set('traceId', traceId);
896920
if (spanId) logsParams.set('spanId', spanId);
897921
appendSignalRouteContext(logsParams, logsContext);
922+
const logOperationAttributeFilter = buildTraceLogsOperationAttributeFilter(
923+
detail,
924+
selectedSpan,
925+
operationName,
926+
traceId,
927+
spanId
928+
);
929+
const mergedLogAttributeFilter = mergeTraceLogAttributeFilters(logsParams.get('attributeFilter'), logOperationAttributeFilter);
930+
if (mergedLogAttributeFilter) logsParams.set('attributeFilter', mergedLogAttributeFilter);
898931

899932
const metricsParams = new URLSearchParams();
900933
if (traceId) metricsParams.set('traceId', traceId);

0 commit comments

Comments
 (0)