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 1d3d4e3c8f Filter metrics console by operation context
1d3d4e3c8f is described below
commit 1d3d4e3c8fb91b0d1a9fc98d02321d725a4cb8e8
Author: Logic <[email protected]>
AuthorDate: Wed Jun 10 08:23:11 2026 +0800
Filter metrics console by operation context
---
.../controller/OtlpIngestionController.java | 5 +-
.../service/OtlpIngestionWorkspaceService.java | 3 +-
.../impl/OtlpIngestionWorkspaceServiceImpl.java | 213 ++++++++++++++++-----
.../controller/OtlpIngestionControllerTest.java | 7 +-
.../OtlpIngestionWorkspaceServiceImplTest.java | 86 +++++++++
.../app/ingestion/otlp/metrics/route-state.test.ts | 3 +-
web-next/app/ingestion/otlp/metrics/route-state.ts | 1 +
web-next/lib/otlp-metrics/controller.test.ts | 12 +-
web-next/lib/otlp-metrics/controller.ts | 1 +
9 files changed, 271 insertions(+), 60 deletions(-)
diff --git
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionController.java
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionController.java
index bfc2f34217..fa1cdb9b39 100644
---
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionController.java
+++
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionController.java
@@ -91,10 +91,11 @@ public class OtlpIngestionController {
@RequestParam(value = "aggregation", required = false) String
aggregation,
@RequestParam(value = "temporalAggregation", required = false)
String temporalAggregation,
@RequestParam(value = "step", required = false) String step,
- @RequestParam(value = "limit", required = false) String limit) {
+ @RequestParam(value = "limit", required = false) String limit,
+ @RequestParam(value = "operationName", required = false) String
operationName) {
return
ResponseEntity.ok(Message.success(otlpIngestionWorkspaceService.getMetricsConsole(
entityId, entityType, start, end, serviceName,
serviceNamespace, environment, query, filter, groupBy, aggregation,
- temporalAggregation, step, limit)));
+ temporalAggregation, step, limit, operationName)));
}
@GetMapping("/metrics/inventory")
diff --git
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/OtlpIngestionWorkspaceService.java
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/OtlpIngestionWorkspaceService.java
index 37ea8d7491..6066eb9531 100644
---
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/OtlpIngestionWorkspaceService.java
+++
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/OtlpIngestionWorkspaceService.java
@@ -39,7 +39,8 @@ public interface OtlpIngestionWorkspaceService {
OtlpMetricsConsoleDto getMetricsConsole(Long entityId, String entityType,
Long start, Long end, String serviceName,
String serviceNamespace, String
environment, String query,
String filter, String groupBy,
String aggregation,
- String temporalAggregation, String
step, String limit);
+ String temporalAggregation, String
step, String limit,
+ String operationName);
OtlpMetricsInventoryDto getMetricsInventory(Long entityId, String
entityType, Long start, Long end,
String serviceName, String
serviceNamespace, String environment,
diff --git
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
index 99ba05223e..94214dd978 100644
---
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
+++
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImpl.java
@@ -681,11 +681,20 @@ public class OtlpIngestionWorkspaceServiceImpl implements
OtlpIngestionWorkspace
);
}
- @Override
public OtlpMetricsConsoleDto getMetricsConsole(Long entityId, String
entityType, Long start, Long end,
String serviceName, String
serviceNamespace, String environment,
String query, String
filter, String groupBy, String aggregation,
String temporalAggregation,
String step, String limit) {
+ return getMetricsConsole(entityId, entityType, start, end,
serviceName, serviceNamespace, environment, query,
+ filter, groupBy, aggregation, temporalAggregation, step,
limit, null);
+ }
+
+ @Override
+ public OtlpMetricsConsoleDto getMetricsConsole(Long entityId, String
entityType, Long start, Long end,
+ String serviceName, String
serviceNamespace, String environment,
+ String query, String
filter, String groupBy, String aggregation,
+ String temporalAggregation,
String step, String limit,
+ String operationName) {
long resolvedEnd = end == null || end <= 0 ?
System.currentTimeMillis() : end;
long resolvedStart = start == null || start <= 0 || start >=
resolvedEnd
? resolvedEnd - DEFAULT_CONSOLE_LOOKBACK_MILLIS
@@ -701,6 +710,8 @@ public class OtlpIngestionWorkspaceServiceImpl implements
OtlpIngestionWorkspace
entityId, entityType, resolvedStart, resolvedEnd, serviceName,
serviceNamespace, environment
);
String resolvedQuery = trimToNull(query);
+ String normalizedOperationName = trimToNull(operationName);
+ List<String> resolvedQueries;
if (!StringUtils.hasText(resolvedQuery)) {
OtlpMetricsConsoleDto autoResolvedConsole =
queryDefaultMetricsConsole(
context,
@@ -712,17 +723,19 @@ public class OtlpIngestionWorkspaceServiceImpl implements
OtlpIngestionWorkspace
resolvedStart,
resolvedEnd,
resolvedStep,
- resolvedSeriesLimit
+ resolvedSeriesLimit,
+ normalizedOperationName
);
if (autoResolvedConsole != null) {
return autoResolvedConsole;
}
- resolvedQuery = buildDefaultMetricsQuery(context, filter, groupBy,
aggregation, temporalAggregation);
+ resolvedQueries = buildDefaultMetricsQueries(context, filter,
groupBy, aggregation, temporalAggregation,
+ normalizedOperationName);
} else {
- resolvedQuery = buildMetricsQueryForExplicitMetric(context,
resolvedQuery, filter, groupBy, aggregation,
- temporalAggregation);
+ resolvedQueries = buildMetricsQueriesForExplicitMetric(context,
resolvedQuery, filter, groupBy, aggregation,
+ temporalAggregation, normalizedOperationName);
}
- if (!StringUtils.hasText(resolvedQuery)) {
+ if (CollectionUtils.isEmpty(resolvedQueries)) {
return new OtlpMetricsConsoleDto(
context,
null,
@@ -734,6 +747,7 @@ public class OtlpIngestionWorkspaceServiceImpl implements
OtlpIngestionWorkspace
message("observability.otlp.metrics-console.no-context")
);
}
+ resolvedQuery = resolvedQueries.getFirst();
if (!metricQueryRepository.hasPromqlExecutor()) {
return new OtlpMetricsConsoleDto(
context,
@@ -746,29 +760,54 @@ public class OtlpIngestionWorkspaceServiceImpl implements
OtlpIngestionWorkspace
message("observability.otlp.metrics-console.promql-unavailable")
);
}
- MetricsQueryExecution execution =
executeMetricsConsoleQuery(resolvedQuery, resolvedStart, resolvedEnd,
- resolvedStep, resolvedSeriesLimit);
- if (execution.errorMessage() != null) {
+ MetricsQueryExecution firstEmptyExecution = null;
+ String firstEmptyQuery = null;
+ String lastErrorMessage = null;
+ for (String candidateQuery : resolvedQueries) {
+ MetricsQueryExecution execution =
executeMetricsConsoleQuery(candidateQuery, resolvedStart, resolvedEnd,
+ resolvedStep, resolvedSeriesLimit);
+ if (execution.errorMessage() != null) {
+ lastErrorMessage = execution.errorMessage();
+ continue;
+ }
+ if (execution.stats().getNonEmptySeries() > 0) {
+ return new OtlpMetricsConsoleDto(
+ context,
+ candidateQuery,
+ execution.datasource(),
+ WarehouseConstants.PROMQL,
+ execution.results(),
+ execution.stats(),
+ deriveMetricsEmptyStateReason(execution.results(),
execution.stats()),
+ execution.results() == null ? null :
trimToNull(execution.results().getMsg())
+ );
+ }
+ if (firstEmptyExecution == null) {
+ firstEmptyExecution = execution;
+ firstEmptyQuery = candidateQuery;
+ }
+ }
+ if (firstEmptyExecution == null) {
return new OtlpMetricsConsoleDto(
context,
resolvedQuery,
- execution.datasource(),
+ null,
WarehouseConstants.PROMQL,
null,
new OtlpMetricsConsoleDto.Stats(0, 0, null),
"load_failed",
- execution.errorMessage()
+ lastErrorMessage
);
}
return new OtlpMetricsConsoleDto(
context,
- resolvedQuery,
- execution.datasource(),
+ firstEmptyQuery,
+ firstEmptyExecution.datasource(),
WarehouseConstants.PROMQL,
- execution.results(),
- execution.stats(),
- deriveMetricsEmptyStateReason(execution.results(),
execution.stats()),
- execution.results() == null ? null :
trimToNull(execution.results().getMsg())
+ firstEmptyExecution.results(),
+ firstEmptyExecution.stats(),
+ deriveMetricsEmptyStateReason(firstEmptyExecution.results(),
firstEmptyExecution.stats()),
+ firstEmptyExecution.results() == null ? null :
trimToNull(firstEmptyExecution.results().getMsg())
);
}
@@ -1280,6 +1319,10 @@ public class OtlpIngestionWorkspaceServiceImpl
implements OtlpIngestionWorkspace
return values;
}
+ private List<String> metricsOperationLabels(String operationName) {
+ return StringUtils.hasText(trimToNull(operationName)) ?
List.of("operation_name", "http_route") : List.of();
+ }
+
private String relatedMetricFamily(String metricName) {
String normalized = trimToNull(metricName);
if (!StringUtils.hasText(normalized)) {
@@ -1908,7 +1951,8 @@ public class OtlpIngestionWorkspaceServiceImpl implements
OtlpIngestionWorkspace
long
resolvedStart,
long resolvedEnd,
String
resolvedStep,
- int
resolvedSeriesLimit) {
+ int
resolvedSeriesLimit,
+ String
operationName) {
if (!metricQueryRepository.hasPromqlExecutor()) {
return null;
}
@@ -1932,32 +1976,30 @@ public class OtlpIngestionWorkspaceServiceImpl
implements OtlpIngestionWorkspace
String lastErrorMessage = null;
for (OtlpMetricsConsoleDto.Context candidateContext :
candidateContexts) {
for (String metricName : candidateMetricNames(candidateContext)) {
- String candidateQuery =
buildMetricsQueryForMetric(candidateContext, metricName, filter, groupBy,
- aggregation, temporalAggregation);
- if (!StringUtils.hasText(candidateQuery)) {
- continue;
- }
- MetricsQueryExecution execution =
executeMetricsConsoleQuery(candidateQuery, resolvedStart,
- resolvedEnd, resolvedStep, resolvedSeriesLimit);
- if (execution.errorMessage() != null) {
- lastErrorMessage = execution.errorMessage();
- continue;
- }
- OtlpMetricsConsoleDto console = new OtlpMetricsConsoleDto(
- candidateContext,
- candidateQuery,
- execution.datasource(),
- WarehouseConstants.PROMQL,
- execution.results(),
- execution.stats(),
- deriveMetricsEmptyStateReason(execution.results(),
execution.stats()),
- execution.results() == null ? null :
trimToNull(execution.results().getMsg())
- );
- if (execution.stats().getNonEmptySeries() > 0) {
- return console;
- }
- if (firstEmptyConsole == null) {
- firstEmptyConsole = console;
+ for (String candidateQuery :
buildMetricsQueriesForMetric(candidateContext, metricName, filter, groupBy,
+ aggregation, temporalAggregation, operationName)) {
+ MetricsQueryExecution execution =
executeMetricsConsoleQuery(candidateQuery, resolvedStart,
+ resolvedEnd, resolvedStep, resolvedSeriesLimit);
+ if (execution.errorMessage() != null) {
+ lastErrorMessage = execution.errorMessage();
+ continue;
+ }
+ OtlpMetricsConsoleDto console = new OtlpMetricsConsoleDto(
+ candidateContext,
+ candidateQuery,
+ execution.datasource(),
+ WarehouseConstants.PROMQL,
+ execution.results(),
+ execution.stats(),
+ deriveMetricsEmptyStateReason(execution.results(),
execution.stats()),
+ execution.results() == null ? null :
trimToNull(execution.results().getMsg())
+ );
+ if (execution.stats().getNonEmptySeries() > 0) {
+ return console;
+ }
+ if (firstEmptyConsole == null) {
+ firstEmptyConsole = console;
+ }
}
}
}
@@ -1984,15 +2026,26 @@ public class OtlpIngestionWorkspaceServiceImpl
implements OtlpIngestionWorkspace
String groupBy,
String aggregation,
String temporalAggregation) {
+ return buildDefaultMetricsQueries(context, filter, groupBy,
aggregation, temporalAggregation, null).stream()
+ .findFirst()
+ .orElse(null);
+ }
+
+ private List<String>
buildDefaultMetricsQueries(OtlpMetricsConsoleDto.Context context,
+ String filter,
+ String groupBy,
+ String aggregation,
+ String temporalAggregation,
+ String operationName) {
if (context == null || !StringUtils.hasText(context.getServiceName()))
{
- return null;
+ return List.of();
}
String preferredMetricName =
candidateMetricNames(context).stream().findFirst().orElse(null);
if (!StringUtils.hasText(preferredMetricName)) {
- return null;
+ return List.of();
}
- return buildMetricsQueryForMetric(context, preferredMetricName,
filter, groupBy, aggregation,
- temporalAggregation);
+ return buildMetricsQueriesForMetric(context, preferredMetricName,
filter, groupBy, aggregation,
+ temporalAggregation, operationName);
}
private List<String> candidateMetricNames(OtlpMetricsConsoleDto.Context
context) {
@@ -2051,6 +2104,44 @@ public class OtlpIngestionWorkspaceServiceImpl
implements OtlpIngestionWorkspace
String groupBy,
String aggregation,
String temporalAggregation) {
+ return buildMetricsQueriesForMetric(context, metricName, filter,
groupBy, aggregation, temporalAggregation, null)
+ .stream()
+ .findFirst()
+ .orElse(null);
+ }
+
+ private List<String>
buildMetricsQueriesForMetric(OtlpMetricsConsoleDto.Context context,
+ String metricName,
+ String filter,
+ String groupBy,
+ String aggregation,
+ String
temporalAggregation,
+ String operationName) {
+ List<String> operationLabels = metricsOperationLabels(operationName);
+ if (operationLabels.isEmpty()) {
+ String query = buildMetricsQueryForMetric(context, metricName,
filter, groupBy, aggregation,
+ temporalAggregation, null, null);
+ return StringUtils.hasText(query) ? List.of(query) : List.of();
+ }
+ List<String> queries = new ArrayList<>();
+ for (String operationLabel : operationLabels) {
+ String query = buildMetricsQueryForMetric(context, metricName,
filter, groupBy, aggregation,
+ temporalAggregation, operationLabel, operationName);
+ if (StringUtils.hasText(query)) {
+ queries.add(query);
+ }
+ }
+ return queries;
+ }
+
+ private String buildMetricsQueryForMetric(OtlpMetricsConsoleDto.Context
context,
+ String metricName,
+ String filter,
+ String groupBy,
+ String aggregation,
+ String temporalAggregation,
+ String operationLabel,
+ String operationName) {
if (context == null || !StringUtils.hasText(context.getServiceName()))
{
return null;
}
@@ -2080,6 +2171,11 @@ public class OtlpIngestionWorkspaceServiceImpl
implements OtlpIngestionWorkspace
matchers.add("hertzbeat_entity_type=\"" +
escapePromqlLabelValue(context.getEntityType()) + "\"");
scopedLabels.add("hertzbeat_entity_type");
}
+ String normalizedOperationName = trimToNull(operationName);
+ if (StringUtils.hasText(operationLabel) &&
StringUtils.hasText(normalizedOperationName)) {
+ matchers.add(operationLabel + "=\"" +
escapePromqlLabelValue(normalizedOperationName) + "\"");
+ scopedLabels.add(operationLabel);
+ }
matchers.addAll(parseMetricsFilterMatchers(filter, scopedLabels));
String selector = "{" + String.join(", ", matchers) + "}";
return normalizeAggregation(aggregation)
@@ -2128,13 +2224,26 @@ public class OtlpIngestionWorkspaceServiceImpl
implements OtlpIngestionWorkspace
String groupBy,
String aggregation,
String
temporalAggregation) {
+ return buildMetricsQueriesForExplicitMetric(context, query, filter,
groupBy, aggregation, temporalAggregation, null)
+ .stream()
+ .findFirst()
+ .orElse(null);
+ }
+
+ private List<String>
buildMetricsQueriesForExplicitMetric(OtlpMetricsConsoleDto.Context context,
+ String query,
+ String filter,
+ String groupBy,
+ String
aggregation,
+ String
temporalAggregation,
+ String
operationName) {
String normalizedQuery = trimToNull(query);
if (!StringUtils.hasText(normalizedQuery) ||
!SIMPLE_METRIC_NAME.matcher(normalizedQuery).matches()) {
- return query;
+ return StringUtils.hasText(normalizedQuery) ?
List.of(normalizedQuery) : List.of();
}
- String generatedQuery = buildMetricsQueryForMetric(context,
normalizedQuery, filter, groupBy, aggregation,
- temporalAggregation);
- return StringUtils.hasText(generatedQuery) ? generatedQuery : query;
+ List<String> generatedQueries = buildMetricsQueriesForMetric(context,
normalizedQuery, filter, groupBy, aggregation,
+ temporalAggregation, operationName);
+ return generatedQueries.isEmpty() ? List.of(normalizedQuery) :
generatedQueries;
}
private List<String> parseMetricsFilterMatchers(String filter) {
diff --git
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionControllerTest.java
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionControllerTest.java
index 61cb651a81..9fd83b2165 100644
---
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionControllerTest.java
+++
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/controller/OtlpIngestionControllerTest.java
@@ -232,7 +232,7 @@ class OtlpIngestionControllerTest {
null
);
when(otlpIngestionWorkspaceService.getMetricsConsole(42L, "service",
1000L, 2000L, "checkout", "commerce", "prod",
- null, "span.kind=\"server\"", null, null, "rate", "60", "1"))
+ null, "span.kind=\"server\"", null, null, "rate", "60", "1",
"POST /checkout"))
.thenReturn(console);
mockMvc.perform(get("/api/ingestion/otlp/metrics/console")
@@ -246,7 +246,8 @@ class OtlpIngestionControllerTest {
.param("filter", "span.kind=\"server\"")
.param("temporalAggregation", "rate")
.param("step", "60")
- .param("limit", "1"))
+ .param("limit", "1")
+ .param("operationName", "POST /checkout"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data.context.entityId").value(42))
@@ -258,7 +259,7 @@ class OtlpIngestionControllerTest {
verify(otlpIngestionWorkspaceService)
.getMetricsConsole(42L, "service", 1000L, 2000L, "checkout",
"commerce", "prod",
- null, "span.kind=\"server\"", null, null, "rate",
"60", "1");
+ null, "span.kind=\"server\"", null, null, "rate",
"60", "1", "POST /checkout");
}
@Test
diff --git
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
index ddaa9e1d8d..35e0b65fdd 100644
---
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
+++
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionWorkspaceServiceImplTest.java
@@ -822,6 +822,92 @@ class OtlpIngestionWorkspaceServiceImplTest {
);
}
+ @Test
+ void metricsConsoleUsesOperationNameWithHttpRouteFallback() {
+ observabilitySignalIntakeGateway.recordOtlpMetricIntake(
+ Map.of(
+ "service.name", "checkout",
+ "service.namespace", "commerce",
+ "deployment.environment.name", "prod"
+ ),
+ 2_000L,
+ "http_server_request_duration_count",
+ "sum",
+ "1",
+ 14.0,
+ Map.of("http.route", "POST /checkout")
+ );
+ String operationNameQuery =
groupedMetricPromql("__name__=\"http_server_request_duration_count\", "
+ + "service_name=\"checkout\", service_namespace=\"commerce\",
deployment_environment_name=\"prod\", "
+ + "operation_name=\"POST /checkout\"");
+ String httpRouteQuery =
groupedMetricPromql("__name__=\"http_server_request_duration_count\", "
+ + "service_name=\"checkout\", service_namespace=\"commerce\",
deployment_environment_name=\"prod\", "
+ + "http_route=\"POST /checkout\"");
+ DatasourceQueryData emptyQueryData = new
DatasourceQueryData("otlp-metrics-console", 200, null, List.of());
+ DatasourceQueryData httpRouteData = new DatasourceQueryData(
+ "otlp-metrics-console",
+ 200,
+ null,
+ List.of(new DatasourceQueryData.SchemaData(
+ new DatasourceQueryData.MetricSchema(
+ List.of(
+ new
DatasourceQueryData.MetricField("__ts__", "time", null),
+ new
DatasourceQueryData.MetricField("__value__", "number", null)
+ ),
+ Map.of("__name__",
"http_server_request_duration_count"),
+ Map.of()
+ ),
+ Collections.singletonList(new Object[] {2_000L, 14.0})
+ ))
+ );
+ when(metricQueryRepository.hasPromqlExecutor()).thenReturn(true);
+ when(metricQueryRepository.queryPromqlRange(
+ eq("otlp-metrics-console"),
+ anyString(),
+ anyLong(),
+ anyLong(),
+ anyString()
+ )).thenAnswer(invocation -> {
+ String query = invocation.getArgument(1);
+ return promqlSuccess(httpRouteQuery.equals(query) ? httpRouteData
: emptyQueryData);
+ });
+
+ OtlpMetricsConsoleDto console =
otlpIngestionWorkspaceService.getMetricsConsole(
+ null,
+ null,
+ 1_000L,
+ 2_000L,
+ "checkout",
+ "commerce",
+ "prod",
+ "http_server_request_duration_count",
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ "POST /checkout"
+ );
+
+ assertEquals(httpRouteQuery, console.getQuery());
+ assertEquals(1, console.getStats().getNonEmptySeries());
+ verify(metricQueryRepository).queryPromqlRange(
+ eq("otlp-metrics-console"),
+ eq(operationNameQuery),
+ anyLong(),
+ anyLong(),
+ anyString()
+ );
+ verify(metricQueryRepository).queryPromqlRange(
+ eq("otlp-metrics-console"),
+ eq(httpRouteQuery),
+ anyLong(),
+ anyLong(),
+ anyString()
+ );
+ }
+
@Test
void metricsConsoleTranslatesFriendlyLabelOperatorsToPromqlMatchers() {
observabilitySignalIntakeGateway.recordOtlpMetricIntake(
diff --git a/web-next/app/ingestion/otlp/metrics/route-state.test.ts
b/web-next/app/ingestion/otlp/metrics/route-state.test.ts
index aed2102c54..7c7f0f20a3 100644
--- a/web-next/app/ingestion/otlp/metrics/route-state.test.ts
+++ b/web-next/app/ingestion/otlp/metrics/route-state.test.ts
@@ -30,6 +30,7 @@ describe('otlp metrics route state', () => {
returnTo: `/log/manage?returnLabel=${localizedReturnLabel}`,
traceId: 'trace-123',
spanId: 'span-456',
+ operationName: 'POST /checkout',
query: 'http_server_duration_milliseconds_count',
filter: 'service.name="checkout"',
aggregation: 'sum',
@@ -50,7 +51,7 @@ describe('otlp metrics route state', () => {
});
expect(route).toBe(
-
'/ingestion/otlp/metrics?entityId=7&entityType=service&entityName=checkout&returnTo=%2Flog%2Fmanage&traceId=trace-123&spanId=span-456&query=http_server_duration_milliseconds_count&filter=service.name%3D%22checkout%22&aggregation=sum&temporalAggregation=rate&groupBy=service_name&legendFormat=%7B%7Bservice.name%7D%7D+-+p95&formula=A+*+1000&step=60&limit=25&timeRange=last-1h&serviceName=checkout&serviceNamespace=payments&environment=prod&collector=collector-a&template=spring-boot&star
[...]
+
'/ingestion/otlp/metrics?entityId=7&entityType=service&entityName=checkout&returnTo=%2Flog%2Fmanage&traceId=trace-123&spanId=span-456&operationName=POST+%2Fcheckout&query=http_server_duration_milliseconds_count&filter=service.name%3D%22checkout%22&aggregation=sum&temporalAggregation=rate&groupBy=service_name&legendFormat=%7B%7Bservice.name%7D%7D+-+p95&formula=A+*+1000&step=60&limit=25&timeRange=last-1h&serviceName=checkout&serviceNamespace=payments&environment=prod&collector=collec
[...]
);
expect(route).not.toContain('returnLabel=');
expect(route).not.toContain(encodeURIComponent(localizedReturnLabel));
diff --git a/web-next/app/ingestion/otlp/metrics/route-state.ts
b/web-next/app/ingestion/otlp/metrics/route-state.ts
index 4f9d30028a..a960dc4770 100644
--- a/web-next/app/ingestion/otlp/metrics/route-state.ts
+++ b/web-next/app/ingestion/otlp/metrics/route-state.ts
@@ -28,6 +28,7 @@ const METRICS_ROUTE_KEYS: Array<keyof OtlpMetricsQueryState>
= [
'returnTo',
'traceId',
'spanId',
+ 'operationName',
'query',
'series',
'filter',
diff --git a/web-next/lib/otlp-metrics/controller.test.ts
b/web-next/lib/otlp-metrics/controller.test.ts
index bd23320b9b..987335a7bd 100644
--- a/web-next/lib/otlp-metrics/controller.test.ts
+++ b/web-next/lib/otlp-metrics/controller.test.ts
@@ -46,11 +46,21 @@ describe('otlp metrics controller', () => {
limit: '25',
traceId: 'trace-1',
spanId: 'span-1',
+ operationName: 'POST /checkout',
serviceName: 'checkout',
start: '1000',
end: '2000'
})
-
).toBe('/ingestion/otlp/metrics/console?query=http_server_duration_milliseconds_count&filter=service.name%3D%22checkout%22&aggregation=sum&temporalAggregation=rate&groupBy=service_name&step=60&limit=25&traceId=trace-1&spanId=span-1&serviceName=checkout&start=1000&end=2000');
+
).toBe('/ingestion/otlp/metrics/console?query=http_server_duration_milliseconds_count&filter=service.name%3D%22checkout%22&aggregation=sum&temporalAggregation=rate&groupBy=service_name&step=60&limit=25&traceId=trace-1&spanId=span-1&operationName=POST+%2Fcheckout&serviceName=checkout&start=1000&end=2000');
+ });
+
+ it('reads operation context from trace and log handoffs into metrics console
state', () => {
+ const query = queryStateFromParams(new URLSearchParams(
+
'query=http.server.duration&serviceName=checkout&operationName=POST+%2Fcheckout&traceId=trace-1&spanId=span-1'
+ ));
+
+ expect(query.operationName).toBe('POST /checkout');
+
expect(buildOtlpMetricsConsoleUrl(query)).toContain('operationName=POST+%2Fcheckout');
});
it('builds and loads metrics inventory from entity and service context
only', async () => {
diff --git a/web-next/lib/otlp-metrics/controller.ts
b/web-next/lib/otlp-metrics/controller.ts
index d3277fe20e..c3f43acc76 100644
--- a/web-next/lib/otlp-metrics/controller.ts
+++ b/web-next/lib/otlp-metrics/controller.ts
@@ -99,6 +99,7 @@ export function queryStateFromParams(params:
SearchParamReader): OtlpMetricsQuer
returnTo: stripReturnLabelFromHref(params.get('returnTo')) || undefined,
traceId: params.get('traceId') || undefined,
spanId: params.get('spanId') || undefined,
+ operationName: params.get('operationName') || undefined,
inspector: readMetricsInspectorView(params.get('inspector')),
warningThreshold:
readFiniteNumberRouteParam(params.get('warningThreshold')),
criticalThreshold:
readFiniteNumberRouteParam(params.get('criticalThreshold')),
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]