Span
Elastic Stack Serverless
在 Elastic Cloud Hosted 中,*APM Server* 接收来自 Elastic APM 代理的数据,并将其转换为 Elasticsearch 文档。在 Elastic Cloud Serverless 中,实际上没有运行 APM Server,而是由*托管接收服务*接收和转换数据。
**Span** 包含有关特定代码路径执行的信息。 它们测量从活动的开始到结束,并且可以与其他 Span 具有父/子关系。
代理会自动检测各种库,以从您的应用程序中捕获这些 Span,但您也可以使用 Agent API 来对特定代码路径进行自定义检测。
除其他外,Span 可以包含
- 引用其父事务的
transaction.id
属性。 - 引用其父 Span 或事务的
parent.id
属性。 - 其开始时间和持续时间。
name
、type
、subtype
和action
- 请参阅Span 名称/类型对齐表,了解 APM 代理的 Span 名称模式和示例。此外,一些 APM 代理会针对公共 Span 类型/子类型规范进行测试。- 可选的
stack trace
。堆栈跟踪由堆栈帧组成,堆栈帧表示调用堆栈上的函数调用。 它们包括函数名称、文件名和路径、行号等属性。
大多数代理将关键字字段(例如 span.id
)限制为 1024 个字符,非关键字字段(例如 span.start.us
)限制为 10,000 个字符。
出于性能原因,APM 代理可以选择有目的地对 Span 进行采样或省略。 这在防止极端情况(例如具有超过 100 个 Span 的长时间运行的事务)时非常有用,否则这些情况会使代理和 APM Server 或托管接收服务过载。 发生这种情况时,“应用程序”UI 将显示丢弃的 Span 数。
要配置每个事务记录的 Span 数,请参阅相关的代理文档
- Android:*尚未支持*
- Go:
ELASTIC_APM_TRANSACTION_MAX_SPANS
- iOS:*尚未支持*
- Java:
transaction_max_spans
- .NET:
TransactionMaxSpans
- Node.js:
transactionMaxSpans
- PHP:
transaction_max_spans
- Python:
transaction_max_spans
- Ruby:
transaction_max_spans
代理将 Span 与其事务分开流式传输到 APM Server 或托管接收服务。 因此,无法预料的错误可能会导致 Span 丢失。 代理知道一个事务应该有多少个 Span;如果预期的 Span 数不等于 APM Server 或托管接收服务接收到的 Span 数,则“应用程序”UI 将计算差异并显示消息。
Span 与事务一起存储在以下数据流中
- 应用程序跟踪:
traces-apm-<namespace>
- RUM 和 iOS 代理应用程序跟踪:
traces-apm.rum-<namespace>
请参阅数据流以了解更多信息。
此示例显示了 Span 文档在 Elasticsearch 中索引时的外观。
展开 Elasticsearch 文档
[
{
"@timestamp": "2017-05-30T18:53:27.154Z",
"agent": {
"name": "elastic-node",
"version": "3.14.0"
},
"ecs": {
"version": "1.12.0"
},
"event": {
"outcome": "unknown"
},
"http": {
"request": {
"method": "GET"
},
"response": {
"status_code": 200
}
},
"labels": {
"span_tag": "something"
},
"observer": {
"hostname": "ix.lan",
"type": "apm-server",
"version": "8.0.0"
},
"parent": {
"id": "945254c567a5417e"
},
"processor": {
"event": "span",
"name": "transaction"
},
"service": {
"environment": "staging",
"name": "1234_service-12a3"
},
"span": {
"action": "query",
"db": {
"instance": "customers",
"statement": "SELECT * FROM product_types WHERE user_id=?",
"type": "sql",
"user": {
"name": "readonly_user"
}
},
"duration": {
"us": 3781
},
"http": {
"method": "GET",
"response": {
"status_code": 200
}
},
"http.url.original": "http://localhost:8000",
"id": "0aaaaaaaaaaaaaaa",
"name": "SELECT FROM product_types",
"stacktrace": [
{
"abs_path": "net.js",
"context": {
"post": [
" ins.currentTransaction = prev",
" return result",
"}"
],
"pre": [
" var trans = this.currentTransaction",
""
]
},
"exclude_from_grouping": false,
"filename": "net.js",
"function": "onread",
"library_frame": true,
"line": {
"column": 4,
"context": "line3",
"number": 547
},
"module": "some module",
"vars": {
"key": "value"
}
},
{
"exclude_from_grouping": false,
"filename": "my2file.js",
"line": {
"number": 10
}
}
],
"start": {
"us": 2830
},
"subtype": "postgresql",
"sync": false,
"type": "db"
},
"timestamp": {
"us": 1496170407154000
},
"trace": {
"id": "945254c567a5417eaaaaaaaaaaaaaaaa"
},
"transaction": {
"id": "945254c567a5417e"
},
"url": {
"original": "http://localhost:8000"
}
},
{
"@timestamp": "2017-05-30T18:53:42.281Z",
"agent": {
"name": "js-base",
"version": "1.3"
},
"destination": {
"address": "0:0::0:1",
"ip": "0:0::0:1",
"port": 5432
},
"ecs": {
"version": "1.12.0"
},
"event": {
"outcome": "unknown"
},
"observer": {
"ephemeral_id": "2f13d8fa-83cd-4356-8123-aabfb47a1808",
"hostname": "goat",
"id": "17ad47dd-5671-4c89-979f-ef4533565ba2",
"type": "apm-server",
"version": "8.0.0"
},
"parent": {
"id": "85925e55b43f4342"
},
"processor": {
"event": "span",
"name": "transaction"
},
"service": {
"environment": "staging",
"name": "serviceabc"
},
"span": {
"action": "query.custom",
"db": {
"instance": "customers",
"statement": "SELECT * FROM product_types WHERE user_id=?",
"type": "sql",
"user": {
"name": "readonly_user"
}
},
"destination": {
"service": {
"name": "postgresql",
"resource": "postgresql",
"type": "db"
}
},
"duration": {
"us": 3781
},
"id": "15aaaaaaaaaaaaaa",
"name": "SELECT FROM product_types",
"start": {
"us": 2830
},
"subtype": "postgresql",
"type": "db.postgresql.query"
},
"timestamp": {
"us": 1496170422281000
},
"trace": {
"id": "85925e55b43f4342aaaaaaaaaaaaaaaa"
},
"transaction": {
"id": "85925e55b43f4342"
}
},
{
"@timestamp": "2017-05-30T18:53:27.154Z",
"agent": {
"name": "elastic-node",
"version": "3.14.0"
},
"ecs": {
"version": "1.12.0"
},
"event": {
"outcome": "unknown"
},
"observer": {
"ephemeral_id": "2f13d8fa-83cd-4356-8123-aabfb47a1808",
"hostname": "goat",
"id": "17ad47dd-5671-4c89-979f-ef4533565ba2",
"type": "apm-server",
"version": "8.0.0"
},
"parent": {
"id": "945254c567a5417e"
},
"processor": {
"event": "span",
"name": "transaction"
},
"service": {
"environment": "staging",
"name": "1234_service-12a3"
},
"span": {
"duration": {
"us": 32592
},
"id": "1aaaaaaaaaaaaaaa",
"name": "GET /api/types",
"start": {
"us": 0
},
"subtype": "external",
"type": "request"
},
"timestamp": {
"us": 1496170407154000
},
"trace": {
"id": "945254c567a5417eaaaaaaaaaaaaaaaa"
},
"transaction": {
"id": "945254c567a5417e"
}
},
{
"@timestamp": "2017-05-30T18:53:27.154Z",
"agent": {
"name": "elastic-node",
"version": "3.14.0"
},
"ecs": {
"version": "1.12.0"
},
"event": {
"outcome": "unknown"
},
"observer": {
"ephemeral_id": "2f13d8fa-83cd-4356-8123-aabfb47a1808",
"hostname": "goat",
"id": "17ad47dd-5671-4c89-979f-ef4533565ba2",
"type": "apm-server",
"version": "8.0.0"
},
"parent": {
"id": "945254c567a5417e"
},
"processor": {
"event": "span",
"name": "transaction"
},
"service": {
"environment": "staging",
"name": "1234_service-12a3"
},
"span": {
"action": "post",
"duration": {
"us": 3564
},
"id": "2aaaaaaaaaaaaaaa",
"name": "GET /api/types",
"start": {
"us": 1845
},
"subtype": "http",
"type": "request"
},
"timestamp": {
"us": 1496170407154000
},
"trace": {
"id": "945254c567a5417eaaaaaaaaaaaaaaaa"
},
"transaction": {
"id": "945254c567a5417e"
}
},
{
"@timestamp": "2017-05-30T18:53:27.154Z",
"agent": {
"name": "elastic-node",
"version": "3.14.0"
},
"child": {
"id": [
"4aaaaaaaaaaaaaaa"
]
},
"ecs": {
"version": "1.12.0"
},
"event": {
"outcome": "unknown"
},
"observer": {
"ephemeral_id": "2f13d8fa-83cd-4356-8123-aabfb47a1808",
"hostname": "goat",
"id": "17ad47dd-5671-4c89-979f-ef4533565ba2",
"type": "apm-server",
"version": "8.0.0"
},
"parent": {
"id": "945254c567a5417e"
},
"processor": {
"event": "span",
"name": "transaction"
},
"service": {
"environment": "staging",
"name": "1234_service-12a3"
},
"span": {
"duration": {
"us": 13980
},
"id": "3aaaaaaaaaaaaaaa",
"name": "GET /api/types",
"start": {
"us": 0
},
"type": "request"
},
"timestamp": {
"us": 1496170407154000
},
"trace": {
"id": "945254c567a5417eaaaaaaaaaaaaaaaa"
},
"transaction": {
"id": "945254c567a5417e"
}
}
]
在某些情况下,APM 代理可能会在一个事务中收集大量非常相似或相同的 Span。 例如,如果 Span 在循环内捕获,或者在使用多个查询而不是联接来获取相关数据的未优化 SQL 查询中捕获,则可能会发生这种情况。
在这种情况下,每个事务的 Span 上限(默认情况下为 500 个 Span)可能会很快达到,从而导致代理停止捕获给定事务的可能更相关的 Span。
捕获相似或相同的 Span 通常没有帮助,特别是如果它们的持续时间非常短。 它们还会使 UI 变得混乱,并导致处理和存储开销。
为了解决这个问题,APM 代理可以将相似的 Span 压缩为单个 Span。 压缩后的 Span 会保留大部分原始 Span 信息,包括总持续时间和它代表的 Span 数。
无论采用何种压缩策略,如果满足以下条件,Span 都有资格进行压缩
- 它尚未传播其跟踪上下文。
- 它是一个 *出口* Span(例如数据库查询 Span)。
- 其结果不是
"failure"
。
APM 代理选择两种策略来决定是否可以压缩相邻的 Span。 在这两种策略中,只需要将一个先前的 Span 保存在内存中。 这确保了代理不需要大量的内存来启用 Span 压缩。
如果两个相邻的 Span 具有相同的以下项,则代理使用同类策略
- Span 类型
- Span 子类型
destination.service.resource
(例如数据库名称)
如果两个相邻的 Span 具有相同的以下项,则代理使用完全匹配策略
- Span 名称
- Span 类型
- Span 子类型
destination.service.resource
(例如数据库名称)
您可以在代理的配置设置中指定最大 Span 持续时间。 持续时间长于指定值的 Span 将不会被压缩。
对于“同类”策略,默认的最大 Span 持续时间为 0 毫秒,这意味着默认情况下禁用“同类”策略。 对于“完全匹配”策略,默认限制为 50 毫秒。
以下代理提供对 Span 压缩的支持,并且可以使用下面列出的选项进行配置
代理 | 同类配置 | 完全匹配配置 |
---|---|---|
Go 代理 | ELASTIC_APM_SPAN_COMPRESSION_SAME_KIND_MAX_DURATION |
|
Java 代理 | span_compression_same_kind_max_duration |
span_compression_exact_match_max_duration |
.NET 代理 | SpanCompressionSameKindMaxDuration |
|
Node.js 代理 | spanCompressionSameKindMaxDuration |
|
Python 代理 | span_compression_same_kind_max_duration |
OpenTelemetry Span 映射到 Elastic APM 事务和 Span 如下
- 根 Span(例如入口点)映射到 APM 事务。
- 子 Span(例如内部操作和数据库查询)映射到 APM Span。
下表总结了 OpenTelemetry Span 类型和 Elastic APM 实体之间的映射。
OpenTelemetry Span 类型 | 映射到 APM | 示例 |
---|---|---|
SERVER |
事务 | 传入 HTTP 请求 (GET /users/{id} ) |
CONSUMER |
事务 | 消息队列消费者事件 |
CLIENT |
Span | 传出数据库查询 (SELECT * FROM users ) |
PRODUCER |
Span | 向队列发送消息 |
INTERNAL |
Span | 内部函数执行 |
以下示例显示了 OpenTelemetry Span
[
{
"traceId": "abcd1234",
"spanId": "root5678",
"parentId": null,
"name": "GET /users/{id}",
"kind": "SERVER"
},
{
"traceId": "abcd1234",
"spanId": "db1234",
"parentId": "root5678",
"name": "SELECT FROM users",
"kind": "CLIENT"
}
]
之前的 OTel Span 由 Elastic APM 存储如下
Transaction: GET /users/{id}
├── Span: SELECT FROM users