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

commit 906f4283b69a5c3e5218d15f922aa2b9111cb726
Author: Logic <[email protected]>
AuthorDate: Wed Aug 26 15:09:49 2026 +0800

    refactor: isolate OTLP guide construction
---
 .../service/impl/OtlpIngestionGuideFactory.java    | 364 +++++++++++++++++++++
 .../impl/OtlpIngestionWorkspaceServiceImpl.java    | 330 +------------------
 .../impl/OtlpIngestionGuideFactoryTest.java        |  67 ++++
 .../OtlpIngestionWorkspaceServiceImplTest.java     |   3 +
 .../impl/OtlpOverviewWorkspaceIsolationTest.java   |   1 +
 .../impl/OtlpWorkspaceEntityIsolationTest.java     |   1 +
 6 files changed, 446 insertions(+), 320 deletions(-)

diff --git 
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionGuideFactory.java
 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionGuideFactory.java
new file mode 100644
index 0000000000..f93701e0ee
--- /dev/null
+++ 
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionGuideFactory.java
@@ -0,0 +1,364 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.observability.ingestion.service.impl;
+
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.List;
+import 
org.apache.hertzbeat.common.observability.dto.ingestion.OtlpIngestionGuideDto;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+/**
+ * Builds the public OTLP endpoints and copy-ready integration snippets.
+ */
+@Component
+class OtlpIngestionGuideFactory {
+
+    private static final String OTLP_HTTP_PATH = "/api/otlp";
+    private static final String OTLP_HOST_PLACEHOLDER = 
"<your-hertzbeat-host>";
+
+    private final boolean sslEnabled;
+    private final int serverPort;
+    private final int otlpGrpcPort;
+
+    OtlpIngestionGuideFactory(@Value("${server.ssl.enabled:false}") boolean 
sslEnabled,
+                              @Value("${server.port:1157}") int serverPort,
+                              @Value("${hertzbeat.otlp.grpc.port:4317}") int 
otlpGrpcPort) {
+        this.sslEnabled = sslEnabled;
+        this.serverPort = serverPort;
+        this.otlpGrpcPort = otlpGrpcPort;
+    }
+
+    OtlpIngestionGuideDto create(HttpServletRequest request) {
+        String unifiedBaseEndpoint = resolveOtlpHttpBaseEndpoint(request);
+        String grpcEndpoint = resolveOtlpGrpcEndpoint(request);
+        String traceEndpoint = unifiedBaseEndpoint + "/v1/traces";
+        String metricsEndpoint = unifiedBaseEndpoint + "/v1/metrics";
+        String logsEndpoint = unifiedBaseEndpoint + "/v1/logs";
+        String collectorSnippet = """
+                receivers:
+                  otlp:
+                    protocols:
+                      http:
+                      grpc:
+
+                processors:
+                  batch:
+
+                exporters:
+                  otlphttp/hertzbeat:
+                    endpoint: %s
+                    headers:
+                      Authorization: "Bearer <api-token>"
+
+                service:
+                  pipelines:
+                    logs:
+                      receivers: [otlp]
+                      processors: [batch]
+                      exporters: [otlphttp/hertzbeat]
+                    traces:
+                      receivers: [otlp]
+                      processors: [batch]
+                      exporters: [otlphttp/hertzbeat]
+                    metrics:
+                      receivers: [otlp]
+                      processors: [batch]
+                      exporters: [otlphttp/hertzbeat]
+                """.formatted(unifiedBaseEndpoint);
+        String collectorGrpcSnippet = """
+                receivers:
+                  otlp:
+                    protocols:
+                      http:
+                      grpc:
+
+                processors:
+                  batch:
+
+                exporters:
+                  otlp/hertzbeat:
+                    endpoint: %s
+                    headers:
+                      Authorization: "Bearer <api-token>"
+                    tls:
+                      insecure: true
+
+                service:
+                  pipelines:
+                    logs:
+                      receivers: [otlp]
+                      processors: [batch]
+                      exporters: [otlp/hertzbeat]
+                    traces:
+                      receivers: [otlp]
+                      processors: [batch]
+                      exporters: [otlp/hertzbeat]
+                    metrics:
+                      receivers: [otlp]
+                      processors: [batch]
+                      exporters: [otlp/hertzbeat]
+                """.formatted(grpcEndpoint);
+        String javaSnippet = """
+                export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
+                export OTEL_EXPORTER_OTLP_ENDPOINT=%s
+                export OTEL_EXPORTER_OTLP_HEADERS=\"Authorization=Bearer 
<api-token>\"
+                export 
OTEL_RESOURCE_ATTRIBUTES=\"service.name=checkout,service.namespace=commerce,deployment.environment.name=prod\"
+                """.formatted(unifiedBaseEndpoint);
+        String javaGrpcSnippet = """
+                export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
+                export OTEL_EXPORTER_OTLP_ENDPOINT=%s
+                export OTEL_EXPORTER_OTLP_HEADERS=\"Authorization=Bearer 
<api-token>\"
+                export 
OTEL_RESOURCE_ATTRIBUTES=\"service.name=checkout,service.namespace=commerce,deployment.environment.name=prod\"
+                """.formatted("http://"; + grpcEndpoint);
+        String pythonSnippet = """
+                from opentelemetry.sdk.resources import Resource
+                resource = Resource.create({
+                    "service.name": "checkout",
+                    "service.namespace": "commerce",
+                    "deployment.environment.name": "prod",
+                })
+
+                # %s
+                # %s
+                
""".formatted(message("observability.otlp.guide.snippet.python.http.comment"), 
unifiedBaseEndpoint);
+        String pythonGrpcSnippet = """
+                from opentelemetry.sdk.resources import Resource
+                resource = Resource.create({
+                    "service.name": "checkout",
+                    "service.namespace": "commerce",
+                    "deployment.environment.name": "prod",
+                })
+
+                # %s
+                # %s
+                
""".formatted(message("observability.otlp.guide.snippet.python.grpc.comment"), 
grpcEndpoint);
+
+        return new OtlpIngestionGuideDto(
+                "OTLP HTTP",
+                "OTLP gRPC",
+                "Authorization",
+                "Bearer <api-token>",
+                grpcEndpoint,
+                List.of(
+                        signalGuide("metrics", "http", "OTLP HTTP", 
metricsEndpoint),
+                        signalGuide("logs", "http", "OTLP HTTP", logsEndpoint),
+                        signalGuide("traces", "http", "OTLP HTTP", 
traceEndpoint),
+                        signalGuide("metrics", "grpc", "OTLP gRPC", 
grpcEndpoint),
+                        signalGuide("logs", "grpc", "OTLP gRPC", grpcEndpoint),
+                        signalGuide("traces", "grpc", "OTLP gRPC", 
grpcEndpoint)
+                ),
+                List.of(
+                        new OtlpIngestionGuideDto.Snippet(
+                                "collector-http", "http", "OpenTelemetry 
Collector", "yaml", collectorSnippet),
+                        new OtlpIngestionGuideDto.Snippet(
+                                "java-http", "http", 
message("observability.otlp.guide.snippet.java-env"),
+                                "bash", javaSnippet),
+                        new OtlpIngestionGuideDto.Snippet(
+                                "python-http", "http", 
message("observability.otlp.guide.snippet.python-resource"),
+                                "python", pythonSnippet),
+                        new OtlpIngestionGuideDto.Snippet(
+                                "collector-grpc", "grpc", "OpenTelemetry 
Collector", "yaml", collectorGrpcSnippet),
+                        new OtlpIngestionGuideDto.Snippet(
+                                "java-grpc", "grpc", 
message("observability.otlp.guide.snippet.java-env"),
+                                "bash", javaGrpcSnippet),
+                        new OtlpIngestionGuideDto.Snippet(
+                                "python-grpc", "grpc", 
message("observability.otlp.guide.snippet.python-resource"),
+                                "python", pythonGrpcSnippet)
+                )
+        );
+    }
+
+    private OtlpIngestionGuideDto.SignalGuide signalGuide(String signal, 
String protocol, String label,
+                                                           String endpoint) {
+        String messagePrefix = "observability.otlp.guide." + signal + "." + 
protocol;
+        return new OtlpIngestionGuideDto.SignalGuide(
+                signal,
+                protocol,
+                label,
+                endpoint,
+                message(messagePrefix + ".description"),
+                message(messagePrefix + ".note")
+        );
+    }
+
+    private String resolveOtlpHttpBaseEndpoint(HttpServletRequest request) {
+        String scheme = firstText(
+                extractForwardedComponent(request, "proto"),
+                headerValue(request, "X-Forwarded-Proto"),
+                sslEnabled ? "https" : "http"
+        );
+        String host = resolveExternalHost(request);
+        if (!StringUtils.hasText(host)) {
+            return scheme + "://" + OTLP_HOST_PLACEHOLDER + OTLP_HTTP_PATH;
+        }
+        return scheme + "://" + appendPortIfNeeded(host, 
resolveExternalPort(request, scheme), scheme) + OTLP_HTTP_PATH;
+    }
+
+    private String resolveOtlpGrpcEndpoint(HttpServletRequest request) {
+        String host = resolveExternalHost(request);
+        if (!StringUtils.hasText(host)) {
+            return OTLP_HOST_PLACEHOLDER + ":" + otlpGrpcPort;
+        }
+        return stripPort(host) + ":" + otlpGrpcPort;
+    }
+
+    private String resolveExternalHost(HttpServletRequest request) {
+        String forwardedHost = firstText(
+                extractForwardedComponent(request, "host"),
+                headerValue(request, "X-Forwarded-Host"),
+                headerValue(request, "Host")
+        );
+        if (StringUtils.hasText(forwardedHost)) {
+            return trimForwardedValue(forwardedHost);
+        }
+        if (request == null || !StringUtils.hasText(request.getServerName())) {
+            return null;
+        }
+        return request.getServerName();
+    }
+
+    private Integer resolveExternalPort(HttpServletRequest request, String 
scheme) {
+        String host = resolveExternalHost(request);
+        String forwardedPort = firstText(
+                extractForwardedComponent(request, "port"),
+                headerValue(request, "X-Forwarded-Port")
+        );
+        if (StringUtils.hasText(forwardedPort)) {
+            try {
+                return Integer.parseInt(trimForwardedValue(forwardedPort));
+            } catch (NumberFormatException ignored) {
+                // Fall through to the request or configured server port.
+            }
+        }
+        Integer explicitHostPort = extractPortFromHost(host);
+        if (explicitHostPort != null) {
+            return explicitHostPort;
+        }
+        if (request != null && request.getServerPort() > 0) {
+            return request.getServerPort();
+        }
+        return "https".equalsIgnoreCase(scheme) ? 443 : serverPort;
+    }
+
+    private String extractForwardedComponent(HttpServletRequest request, 
String key) {
+        String forwarded = headerValue(request, "Forwarded");
+        if (!StringUtils.hasText(forwarded)) {
+            return null;
+        }
+        String firstPart = forwarded.split(",", 2)[0];
+        for (String token : firstPart.split(";")) {
+            String[] pair = token.split("=", 2);
+            if (pair.length == 2 && key.equalsIgnoreCase(pair[0].trim())) {
+                return trimForwardedValue(pair[1]);
+            }
+        }
+        return null;
+    }
+
+    private String headerValue(HttpServletRequest request, String headerName) {
+        return request == null ? null : request.getHeader(headerName);
+    }
+
+    private String firstText(String... candidates) {
+        for (String candidate : candidates) {
+            if (StringUtils.hasText(candidate)) {
+                return trimForwardedValue(candidate);
+            }
+        }
+        return null;
+    }
+
+    private String trimForwardedValue(String value) {
+        if (!StringUtils.hasText(value)) {
+            return value;
+        }
+        String trimmed = value.trim();
+        if (trimmed.startsWith("\"") && trimmed.endsWith("\"") && 
trimmed.length() > 1) {
+            trimmed = trimmed.substring(1, trimmed.length() - 1);
+        }
+        int commaIndex = trimmed.indexOf(',');
+        if (commaIndex > 0) {
+            trimmed = trimmed.substring(0, commaIndex);
+        }
+        return trimmed.trim();
+    }
+
+    private String appendPortIfNeeded(String host, Integer port, String 
scheme) {
+        String normalizedHost = stripPort(host);
+        if (!StringUtils.hasText(normalizedHost) || port == null) {
+            return normalizedHost;
+        }
+        if (("http".equalsIgnoreCase(scheme) && port == 80)
+                || ("https".equalsIgnoreCase(scheme) && port == 443)) {
+            return normalizedHost;
+        }
+        return normalizedHost + ":" + port;
+    }
+
+    private String stripPort(String host) {
+        if (!StringUtils.hasText(host)) {
+            return host;
+        }
+        String trimmed = host.trim();
+        if (trimmed.startsWith("[") && trimmed.contains("]")) {
+            return trimmed.substring(0, trimmed.indexOf(']') + 1);
+        }
+        int colonIndex = trimmed.lastIndexOf(':');
+        if (colonIndex > 0 && trimmed.indexOf(':') == colonIndex) {
+            return trimmed.substring(0, colonIndex);
+        }
+        if (colonIndex > 0) {
+            return "[" + trimmed + "]";
+        }
+        return trimmed;
+    }
+
+    private Integer extractPortFromHost(String host) {
+        if (!StringUtils.hasText(host)) {
+            return null;
+        }
+        String trimmed = host.trim();
+        if (trimmed.startsWith("[") && trimmed.contains("]")) {
+            int lastColon = trimmed.lastIndexOf(':');
+            int closingBracket = trimmed.lastIndexOf(']');
+            if (lastColon > closingBracket) {
+                return parsePort(trimmed.substring(lastColon + 1));
+            }
+            return null;
+        }
+        int colonIndex = trimmed.lastIndexOf(':');
+        if (colonIndex > 0 && trimmed.indexOf(':') == colonIndex) {
+            return parsePort(trimmed.substring(colonIndex + 1));
+        }
+        return null;
+    }
+
+    private Integer parsePort(String value) {
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException ignored) {
+            return null;
+        }
+    }
+
+    private static String message(String key) {
+        return OtlpIngestionMessages.get(key);
+    }
+}
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 69f672bec8..3dcd7e8c7d 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
@@ -66,7 +66,6 @@ import 
org.apache.hertzbeat.warehouse.repository.MetricQueryRepository;
 import org.apache.hertzbeat.warehouse.store.history.tsdb.HistoryDataReader;
 import 
org.apache.hertzbeat.warehouse.store.history.tsdb.greptime.GreptimeProperties;
 import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
@@ -81,8 +80,6 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
     private static final long LOOKBACK_MILLIS = 
Duration.ofHours(24).toMillis();
     private static final long DEFAULT_CONSOLE_LOOKBACK_MILLIS = 
Duration.ofHours(1).toMillis();
     private static final int SAMPLE_LIMIT = 20;
-    private static final String OTLP_HTTP_PATH = "/api/otlp";
-    private static final String OTLP_HOST_PLACEHOLDER = 
"<your-hertzbeat-host>";
     private static final List<String> METRICS_ENTITY_CONTEXT_GROUP_LABELS = 
List.of(
             "__name__",
             "service_name",
@@ -131,6 +128,7 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
     private final EntityTraceQueryService entityTraceQueryService;
     private final ObservabilityWorkspaceQueryGateway workspaceQueryGateway;
     private final ObservabilitySignalIntakeGateway 
observabilitySignalIntakeGateway;
+    private final OtlpIngestionGuideFactory guideFactory;
     private final LogQueryRepository logQueryRepository;
     private final MetricQueryRepository metricQueryRepository;
     private final List<MetricInventoryRepository> metricInventoryRepositories;
@@ -142,6 +140,7 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
                                              
ObservabilityWorkspaceQueryGateway workspaceQueryGateway,
                                              
@Qualifier("telemetryIntakeServiceImpl")
                                              ObservabilitySignalIntakeGateway 
observabilitySignalIntakeGateway,
+                                             OtlpIngestionGuideFactory 
guideFactory,
                                              LogQueryRepository 
logQueryRepository,
                                              MetricQueryRepository 
metricQueryRepository,
                                              List<MetricInventoryRepository> 
metricInventoryRepositories,
@@ -151,6 +150,7 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
         this.entityTraceQueryService = entityTraceQueryService;
         this.workspaceQueryGateway = workspaceQueryGateway;
         this.observabilitySignalIntakeGateway = 
observabilitySignalIntakeGateway;
+        this.guideFactory = guideFactory;
         this.logQueryRepository = logQueryRepository;
         this.metricQueryRepository = metricQueryRepository;
         this.metricInventoryRepositories = 
safeBeanList(metricInventoryRepositories);
@@ -159,15 +159,6 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
         this.greptimeProperties = safeBeanList(greptimeProperties);
     }
 
-    @Value("${server.ssl.enabled:false}")
-    private boolean sslEnabled = false;
-
-    @Value("${server.port:1157}")
-    private int serverPort = 1157;
-
-    @Value("${hertzbeat.otlp.grpc.port:4317}")
-    private int otlpGrpcPort = 4317;
-
     OtlpIngestionOverviewDto getOverview() {
         return getOverview(AuthTokenScopes.DEFAULT_WORKSPACE_ID);
     }
@@ -436,179 +427,7 @@ public class OtlpIngestionWorkspaceServiceImpl implements 
OtlpIngestionWorkspace
 
     @Override
     public OtlpIngestionGuideDto getGuide(HttpServletRequest request) {
-        String unifiedBaseEndpoint = resolveOtlpHttpBaseEndpoint(request);
-        String grpcEndpoint = resolveOtlpGrpcEndpoint(request);
-        String traceEndpoint = unifiedBaseEndpoint + "/v1/traces";
-        String metricsEndpoint = unifiedBaseEndpoint + "/v1/metrics";
-        String logsEndpoint = unifiedBaseEndpoint + "/v1/logs";
-        String collectorSnippet = """
-                receivers:
-                  otlp:
-                    protocols:
-                      http:
-                      grpc:
-
-                processors:
-                  batch:
-
-                exporters:
-                  otlphttp/hertzbeat:
-                    endpoint: %s
-                    headers:
-                      Authorization: "Bearer <api-token>"
-
-                service:
-                  pipelines:
-                    logs:
-                      receivers: [otlp]
-                      processors: [batch]
-                      exporters: [otlphttp/hertzbeat]
-                    traces:
-                      receivers: [otlp]
-                      processors: [batch]
-                      exporters: [otlphttp/hertzbeat]
-                    metrics:
-                      receivers: [otlp]
-                      processors: [batch]
-                      exporters: [otlphttp/hertzbeat]
-                """.formatted(unifiedBaseEndpoint);
-        String collectorGrpcSnippet = """
-                receivers:
-                  otlp:
-                    protocols:
-                      http:
-                      grpc:
-
-                processors:
-                  batch:
-
-                exporters:
-                  otlp/hertzbeat:
-                    endpoint: %s
-                    headers:
-                      Authorization: "Bearer <api-token>"
-                    tls:
-                      insecure: true
-
-                service:
-                  pipelines:
-                    logs:
-                      receivers: [otlp]
-                      processors: [batch]
-                      exporters: [otlp/hertzbeat]
-                    traces:
-                      receivers: [otlp]
-                      processors: [batch]
-                      exporters: [otlp/hertzbeat]
-                    metrics:
-                      receivers: [otlp]
-                      processors: [batch]
-                      exporters: [otlp/hertzbeat]
-                """.formatted(grpcEndpoint);
-        String javaSnippet = """
-                export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
-                export OTEL_EXPORTER_OTLP_ENDPOINT=%s
-                export OTEL_EXPORTER_OTLP_HEADERS=\"Authorization=Bearer 
<api-token>\"
-                export 
OTEL_RESOURCE_ATTRIBUTES=\"service.name=checkout,service.namespace=commerce,deployment.environment.name=prod\"
-                """.formatted(unifiedBaseEndpoint);
-        String javaGrpcSnippet = """
-                export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
-                export OTEL_EXPORTER_OTLP_ENDPOINT=%s
-                export OTEL_EXPORTER_OTLP_HEADERS=\"Authorization=Bearer 
<api-token>\"
-                export 
OTEL_RESOURCE_ATTRIBUTES=\"service.name=checkout,service.namespace=commerce,deployment.environment.name=prod\"
-                """.formatted("http://"; + grpcEndpoint);
-        String pythonSnippet = """
-                from opentelemetry.sdk.resources import Resource
-                resource = Resource.create({
-                    "service.name": "checkout",
-                    "service.namespace": "commerce",
-                    "deployment.environment.name": "prod",
-                })
-
-                # %s
-                # %s
-                
""".formatted(message("observability.otlp.guide.snippet.python.http.comment"), 
unifiedBaseEndpoint);
-        String pythonGrpcSnippet = """
-                from opentelemetry.sdk.resources import Resource
-                resource = Resource.create({
-                    "service.name": "checkout",
-                    "service.namespace": "commerce",
-                    "deployment.environment.name": "prod",
-                })
-
-                # %s
-                # %s
-                
""".formatted(message("observability.otlp.guide.snippet.python.grpc.comment"), 
grpcEndpoint);
-
-        return new OtlpIngestionGuideDto(
-                "OTLP HTTP",
-                "OTLP gRPC",
-                "Authorization",
-                "Bearer <api-token>",
-                grpcEndpoint,
-                List.of(
-                        new OtlpIngestionGuideDto.SignalGuide(
-                                "metrics",
-                                "http",
-                                "OTLP HTTP",
-                                metricsEndpoint,
-                                
message("observability.otlp.guide.metrics.http.description"),
-                                
message("observability.otlp.guide.metrics.http.note")
-                        ),
-                        new OtlpIngestionGuideDto.SignalGuide(
-                                "logs",
-                                "http",
-                                "OTLP HTTP",
-                                logsEndpoint,
-                                
message("observability.otlp.guide.logs.http.description"),
-                                
message("observability.otlp.guide.logs.http.note")
-                        ),
-                        new OtlpIngestionGuideDto.SignalGuide(
-                                "traces",
-                                "http",
-                                "OTLP HTTP",
-                                traceEndpoint,
-                                
message("observability.otlp.guide.traces.http.description"),
-                                
message("observability.otlp.guide.traces.http.note")
-                        ),
-                        new OtlpIngestionGuideDto.SignalGuide(
-                                "metrics",
-                                "grpc",
-                                "OTLP gRPC",
-                                grpcEndpoint,
-                                
message("observability.otlp.guide.metrics.grpc.description"),
-                                
message("observability.otlp.guide.metrics.grpc.note")
-                        ),
-                        new OtlpIngestionGuideDto.SignalGuide(
-                                "logs",
-                                "grpc",
-                                "OTLP gRPC",
-                                grpcEndpoint,
-                                
message("observability.otlp.guide.logs.grpc.description"),
-                                
message("observability.otlp.guide.logs.grpc.note")
-                        ),
-                        new OtlpIngestionGuideDto.SignalGuide(
-                                "traces",
-                                "grpc",
-                                "OTLP gRPC",
-                                grpcEndpoint,
-                                
message("observability.otlp.guide.traces.grpc.description"),
-                                
message("observability.otlp.guide.traces.grpc.note")
-                        )
-                ),
-                List.of(
-                        new OtlpIngestionGuideDto.Snippet("collector-http", 
"http", "OpenTelemetry Collector", "yaml", collectorSnippet),
-                        new OtlpIngestionGuideDto.Snippet("java-http", "http",
-                                
message("observability.otlp.guide.snippet.java-env"), "bash", javaSnippet),
-                        new OtlpIngestionGuideDto.Snippet("python-http", 
"http",
-                                
message("observability.otlp.guide.snippet.python-resource"), "python", 
pythonSnippet),
-                        new OtlpIngestionGuideDto.Snippet("collector-grpc", 
"grpc", "OpenTelemetry Collector", "yaml", collectorGrpcSnippet),
-                        new OtlpIngestionGuideDto.Snippet("java-grpc", "grpc",
-                                
message("observability.otlp.guide.snippet.java-env"), "bash", javaGrpcSnippet),
-                        new OtlpIngestionGuideDto.Snippet("python-grpc", 
"grpc",
-                                
message("observability.otlp.guide.snippet.python-resource"), "python", 
pythonGrpcSnippet)
-                )
-        );
+        return guideFactory.create(request);
     }
 
     OtlpEntityBindingSummaryDto getBindingSummary() {
@@ -1758,87 +1577,6 @@ public class OtlpIngestionWorkspaceServiceImpl 
implements OtlpIngestionWorkspace
         return 
localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
     }
 
-    private String resolveOtlpHttpBaseEndpoint(HttpServletRequest request) {
-        String scheme = firstText(
-                extractForwardedComponent(request, "proto"),
-                headerValue(request, "X-Forwarded-Proto"),
-                sslEnabled ? "https" : "http"
-        );
-        String host = resolveExternalHost(request);
-        if (!StringUtils.hasText(host)) {
-            return scheme + "://" + OTLP_HOST_PLACEHOLDER + OTLP_HTTP_PATH;
-        }
-        return scheme + "://" + appendPortIfNeeded(host, 
resolveExternalPort(request, scheme), scheme) + OTLP_HTTP_PATH;
-    }
-
-    private String resolveOtlpGrpcEndpoint(HttpServletRequest request) {
-        String host = resolveExternalHost(request);
-        if (!StringUtils.hasText(host)) {
-            return OTLP_HOST_PLACEHOLDER + ":" + otlpGrpcPort;
-        }
-        return stripPort(host) + ":" + otlpGrpcPort;
-    }
-
-    private String resolveExternalHost(HttpServletRequest request) {
-        String forwardedHost = firstText(
-                extractForwardedComponent(request, "host"),
-                headerValue(request, "X-Forwarded-Host"),
-                headerValue(request, "Host")
-        );
-        if (StringUtils.hasText(forwardedHost)) {
-            return trimForwardedValue(forwardedHost);
-        }
-        if (request == null || !StringUtils.hasText(request.getServerName())) {
-            return null;
-        }
-        return request.getServerName();
-    }
-
-    private Integer resolveExternalPort(HttpServletRequest request, String 
scheme) {
-        String host = resolveExternalHost(request);
-        String forwardedPort = firstText(
-                extractForwardedComponent(request, "port"),
-                headerValue(request, "X-Forwarded-Port")
-        );
-        if (StringUtils.hasText(forwardedPort)) {
-            try {
-                return Integer.parseInt(trimForwardedValue(forwardedPort));
-            } catch (NumberFormatException ignored) {
-                // Ignore invalid forwarded port and fall back to request or 
server defaults.
-            }
-        }
-        Integer explicitHostPort = extractPortFromHost(host);
-        if (explicitHostPort != null) {
-            return explicitHostPort;
-        }
-        if (request != null && request.getServerPort() > 0) {
-            return request.getServerPort();
-        }
-        return "https".equalsIgnoreCase(scheme) ? 443 : serverPort;
-    }
-
-    private String extractForwardedComponent(HttpServletRequest request, 
String key) {
-        String forwarded = headerValue(request, "Forwarded");
-        if (!StringUtils.hasText(forwarded)) {
-            return null;
-        }
-        String firstPart = forwarded.split(",", 2)[0];
-        for (String token : firstPart.split(";")) {
-            String[] pair = token.split("=", 2);
-            if (pair.length == 2 && key.equalsIgnoreCase(pair[0].trim())) {
-                return trimForwardedValue(pair[1]);
-            }
-        }
-        return null;
-    }
-
-    private String headerValue(HttpServletRequest request, String headerName) {
-        if (request == null) {
-            return null;
-        }
-        return request.getHeader(headerName);
-    }
-
     private String firstText(String... candidates) {
         for (String candidate : candidates) {
             if (StringUtils.hasText(candidate)) {
@@ -1863,62 +1601,14 @@ public class OtlpIngestionWorkspaceServiceImpl 
implements OtlpIngestionWorkspace
         return trimmed.trim();
     }
 
-    private String appendPortIfNeeded(String host, Integer port, String 
scheme) {
-        String normalizedHost = stripPort(host);
-        if (!StringUtils.hasText(normalizedHost) || port == null) {
-            return normalizedHost;
-        }
-        if (("http".equalsIgnoreCase(scheme) && port == 80)
-                || ("https".equalsIgnoreCase(scheme) && port == 443)) {
-            return normalizedHost;
-        }
-        return normalizedHost + ":" + port;
-    }
-
-    private String stripPort(String host) {
-        if (!StringUtils.hasText(host)) {
-            return host;
-        }
-        String trimmed = host.trim();
-        if (trimmed.startsWith("[") && trimmed.contains("]")) {
-            return trimmed.substring(0, trimmed.indexOf(']') + 1);
-        }
-        int colonIndex = trimmed.lastIndexOf(':');
-        if (colonIndex > 0 && trimmed.indexOf(':') == colonIndex) {
-            return trimmed.substring(0, colonIndex);
+    private String metricsIntakeMode(long monitorTotalCount, long 
otlpMetricCount) {
+        if (monitorTotalCount > 0 && otlpMetricCount > 0) {
+            return message("observability.otlp.overview.metrics.mode.mixed");
         }
-        if (colonIndex > 0 && trimmed.indexOf(':') != colonIndex) {
-            return "[" + trimmed + "]";
+        if (otlpMetricCount > 0) {
+            return "OTLP";
         }
-        return trimmed;
-    }
-
-    private Integer extractPortFromHost(String host) {
-        if (!StringUtils.hasText(host)) {
-            return null;
-        }
-        String trimmed = host.trim();
-        if (trimmed.startsWith("[") && trimmed.contains("]")) {
-            int lastColon = trimmed.lastIndexOf(':');
-            int closingBracket = trimmed.lastIndexOf(']');
-            if (lastColon > closingBracket) {
-                try {
-                    return Integer.parseInt(trimmed.substring(lastColon + 1));
-                } catch (NumberFormatException ignored) {
-                    return null;
-                }
-            }
-            return null;
-        }
-        int colonIndex = trimmed.lastIndexOf(':');
-        if (colonIndex > 0 && trimmed.indexOf(':') == colonIndex) {
-            try {
-                return Integer.parseInt(trimmed.substring(colonIndex + 1));
-            } catch (NumberFormatException ignored) {
-                return null;
-            }
-        }
-        return null;
+        return message("observability.otlp.overview.metrics.mode.monitor");
     }
 
     private static String message(String key) {
diff --git 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionGuideFactoryTest.java
 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionGuideFactoryTest.java
new file mode 100644
index 0000000000..50efaa0d4c
--- /dev/null
+++ 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpIngestionGuideFactoryTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.observability.ingestion.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import 
org.apache.hertzbeat.common.observability.dto.ingestion.OtlpIngestionGuideDto;
+import org.junit.jupiter.api.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+class OtlpIngestionGuideFactoryTest {
+
+    private final OtlpIngestionGuideFactory factory = new 
OtlpIngestionGuideFactory(false, 1157, 4317);
+
+    @Test
+    void buildsHttpAndGrpcEndpointsFromForwardedHeaders() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setServerPort(443);
+        request.addHeader("X-Forwarded-Host", "demo.hertzbeat.apache.org");
+        request.addHeader("X-Forwarded-Proto", "https");
+
+        OtlpIngestionGuideDto guide = factory.create(request);
+
+        assertEquals("demo.hertzbeat.apache.org:4317", 
guide.getGrpcAuthorityExample());
+        assertTrue(guide.getSignals().stream().anyMatch(signal -> 
"metrics".equals(signal.getSignal())
+                && 
"https://demo.hertzbeat.apache.org/api/otlp/v1/metrics".equals(signal.getEndpoint())));
+    }
+
+    @Test
+    void preservesBracketedIpv6AndExplicitForwardedPort() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.addHeader("Forwarded", 
"proto=https;host=\"[2001:db8::1]:8443\"");
+
+        OtlpIngestionGuideDto guide = factory.create(request);
+
+        assertEquals("[2001:db8::1]:4317", guide.getGrpcAuthorityExample());
+        assertTrue(guide.getSignals().stream().anyMatch(signal -> 
"logs".equals(signal.getSignal())
+                && 
"https://[2001:db8::1]:8443/api/otlp/v1/logs".equals(signal.getEndpoint())));
+    }
+
+    @Test
+    void usesConfiguredFallbackEndpointsWhenRequestHostIsMissing() {
+        OtlpIngestionGuideFactory secureFactory = new 
OtlpIngestionGuideFactory(true, 8443, 55680);
+
+        OtlpIngestionGuideDto guide = secureFactory.create(null);
+
+        assertEquals("<your-hertzbeat-host>:55680", 
guide.getGrpcAuthorityExample());
+        assertTrue(guide.getSignals().stream().anyMatch(signal -> 
"traces".equals(signal.getSignal())
+                && 
"https://<your-hertzbeat-host>/api/otlp/v1/traces".equals(signal.getEndpoint())));
+    }
+}
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 7f1f024e0f..9cbe81d56d 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
@@ -118,6 +118,7 @@ class OtlpIngestionWorkspaceServiceImplTest {
                 entityTraceQueryService,
                 workspaceQueryGateway,
                 observabilitySignalIntakeGateway,
+                new OtlpIngestionGuideFactory(false, 1157, 4317),
                 logQueryRepository,
                 metricQueryRepository,
                 List.of(metricInventoryRepository),
@@ -313,6 +314,7 @@ class OtlpIngestionWorkspaceServiceImplTest {
                 entityTraceQueryService,
                 workspaceQueryGateway,
                 observabilitySignalIntakeGateway,
+                new OtlpIngestionGuideFactory(false, 1157, 4317),
                 logQueryRepository,
                 metricQueryRepository,
                 List.of(metricInventoryRepository),
@@ -634,6 +636,7 @@ class OtlpIngestionWorkspaceServiceImplTest {
                 entityTraceQueryService,
                 workspaceQueryGateway,
                 mixedSourceGateway,
+                new OtlpIngestionGuideFactory(false, 1157, 4317),
                 logQueryRepository,
                 metricQueryRepository,
                 List.of(metricInventoryRepository),
diff --git 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpOverviewWorkspaceIsolationTest.java
 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpOverviewWorkspaceIsolationTest.java
index ff1729cc91..5a45cc9fdf 100644
--- 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpOverviewWorkspaceIsolationTest.java
+++ 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpOverviewWorkspaceIsolationTest.java
@@ -67,6 +67,7 @@ class OtlpOverviewWorkspaceIsolationTest {
                 traceQueryService,
                 workspaceQueryGateway,
                 intakeGateway,
+                new OtlpIngestionGuideFactory(false, 1157, 4317),
                 logQueryRepository,
                 metricQueryRepository,
                 List.of(metricInventoryRepository),
diff --git 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpWorkspaceEntityIsolationTest.java
 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpWorkspaceEntityIsolationTest.java
index 85ab2be12a..d68e3d28a1 100644
--- 
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpWorkspaceEntityIsolationTest.java
+++ 
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/ingestion/service/impl/OtlpWorkspaceEntityIsolationTest.java
@@ -62,6 +62,7 @@ class OtlpWorkspaceEntityIsolationTest {
                 entityTraceQueryService,
                 workspaceQueryGateway,
                 signalIntakeGateway,
+                new OtlpIngestionGuideFactory(false, 1157, 4317),
                 logQueryRepository,
                 metricQueryRepository,
                 List.of(metricInventoryRepository),


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

Reply via email to