

# 配置 HTTP 端点可见性
<a name="Application-Signals-EndpointVisibility"></a>

本页面为 Application Signals 用户提供指导。如果您希望 HTTP 服务操作按具体端点名称分别显示，但发现多个端点被汇总到同一指标中，则可参考以下内容。默认情况下，Application Signals 为保持指标的低基数（Low-Cardinality），会将 HTTP 服务的 `Operation` 指标维度截断为 URL 的第一个路径段（例如，`/api/v1/users` 被截断为 `GET /api`）。因此，当服务包含多个共享相同前缀的端点时，用户可能无法清楚地了解每个端点的运行状况。您可以按照以下步骤配置服务操作端点，自定义所需粒度。

**重要**  
修改端点可见性是一项重大更改，因为会影响服务操作对应的 Application Signals 指标维度。切记相应更新 SLO 阈值、警报和/或控制面。

## 适用于 OpenTelemetry 的 AWS Distro（ADOT）解决方案
<a name="Application-Signals-EndpointVisibility-ADOT"></a>

对于适用于 OpenTelemetry 的 AWS Distro（ADOT）的客户，请设置环境变量 `OTEL_AWS_HTTP_OPERATION_PATHS`，填入以逗号分隔的 HTTP 服务 URL 路径模板列表：

```
export OTEL_AWS_HTTP_OPERATION_PATHS="/path/to/endpoint, /another/{placeholder}/endpoint"
```

该变量会比对 HTTP 服务端追踪跨度的 `url.path` 属性，通过最长匹配前缀规则判定操作名称。通配符模式可匹配任意单个 URL 路径段，支持的语法包括 `{placeholder}`、`:placeholder` 或 `*`。设置变量后，请重启应用程序，使新的端点分组生效。

该变量在 ADOT Java、Python、Node.js 和 .NET 版本中均受支持。

**示例**

以下列 API 服务流量为例：

```
GET  /api/users
GET  /api/users/42
GET  /api/users/42/orders
POST /api/users/99/orders
POST /api/users/42/orders
GET  /api/users/42/orders/7/items
GET  /api/products
```

默认情况下，Application Signals 会将这些请求全部分组为 `GET /api` 或 `POST /api` 服务指标，导致无法区分各端点的性能差异。

要解决此问题，请使用所需的路径模板设置环境变量：

```
export OTEL_AWS_HTTP_OPERATION_PATHS="/api/users/{userId}/orders/{orderId}/items, /api/users/{userId}/orders, /api/users/{userId}, /api/users, /api/products"
```

使用此配置后，Application Signals 会显示不同的操作。多个请求可匹配到同一个配置模板：


**端点可见性配置结果示例**  

| 传入请求 | 默认操作 | 配置后 | 
| --- | --- | --- | 
| GET /api/users | GET /api | GET /api/users | 
| GET /api/users/42 | GET /api | GET /api/users/{userId} | 
| GET /api/users/42/orders | GET /api | GET /api/users/{userId}/orders | 
| POST /api/users/99/orders | POST /api | POST /api/users/{userId}/orders | 
| POST /api/users/42/orders | POST /api | POST /api/users/{userId}/orders | 
| GET /api/users/42/orders/7/items | GET /api | GET /api/users/{userId}/orders/{orderId}/items | 
| GET /api/products | GET /api | GET /api/products | 

## 原生 OpenTelemetry 解决方案
<a name="Application-Signals-EndpointVisibility-NativeOTel"></a>

如若使用原生 OpenTelemetry SDK（未搭载 ADOT），可通过 OpenTelemetry 采集器中的转换处理器或直接在应用程序代码中覆盖追踪跨度名称。

**注意**  
您必须为采集器配置 OTLP 导出器，才能完整保留 OpenTelemetry 追踪跨度名称，供 CloudWatch 解析生成 Application Signals 的操作名称。有关更多信息，请参阅[将 OTLP 数据发送到 CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPSimplesetup.html)。

**选项 1（推荐）：采集器侧转换**

使用[转换处理器](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor)，根据 URL 模式设置追踪跨度名称。该转换处理器使用 OTTL（OpenTelemetry Transformation Language，OpenTelemetry 转换语言），可直接修改追踪跨度 `name` 字段。

规则需按由浅至深排序：各语句按顺序执行，列表中靠后的精准匹配规则会覆盖前置的通用匹配规则。在以下示例中，系统会匹配追踪跨度属性 `url.path` 的值，并将生成的追踪跨度名称设置为“请求方法 \+ 目标 URL 模式”格式。

```
processors:
  transform/operation_names:
    trace_statements:
      - context: span
        conditions:
          - IsMatch(attributes["url.path"], "^/api/contests(/|$)")
        statements:
          - set(name, Concat([attributes["http.request.method"], "/api/contests"], " "))

      - context: span
        conditions:
          - IsMatch(attributes["url.path"], "^/api/contests/[^/]+$")
        statements:
          - set(name, Concat([attributes["http.request.method"], "/api/contests/{id}"], " "))

      - context: span
        conditions:
          - IsMatch(attributes["url.path"], "^/api/contests/[^/]+/leaderboard(/|$)")
        statements:
          - set(name, Concat([attributes["http.request.method"], "/api/contests/{id}/leaderboard"], " "))

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resourcedetection, transform/operation_names, batch]
      exporters: [otlphttp/xray]
```

**选项 2：在应用程序代码中设置追踪跨度名称**

您可以使用 OpenTelemetry API 在应用程序代码中手动为服务端追踪跨度设置名称。将名称设置为 `{HTTP_METHOD} {route_template}`，其中 route\_template 使用参数化占位符。注意：该方式属于硬编码兜底方案；如需生效，您必须手动修改每一个 HTTP 请求处理器内的追踪跨度名称。

**Java**

```
import io.opentelemetry.api.trace.Span;

// Inside your request handler
Span.current().updateName("GET /api/contests/{id}/leaderboard");
```

**Python**

```
from opentelemetry import trace

# Inside your request handler
span = trace.get_current_span()
span.update_name("GET /api/contests/{id}/leaderboard")
```

**Go**

```
import "go.opentelemetry.io/otel/trace"

// Inside your request handler
span := trace.SpanFromContext(ctx)
span.SetName("GET /api/contests/{id}/leaderboard")
```

**Node.js**

```
import { trace } from '@opentelemetry/api';

// Inside your request handler
const span = trace.getActiveSpan();
if (span) {
  span.updateName('GET /api/contests/{id}/leaderboard');
}
```