toad-eye 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -152
- package/dist/agent.d.ts +31 -28
- package/dist/agent.js +116 -35
- package/dist/alerts/channels.js +24 -6
- package/dist/alerts/index.js +46 -2
- package/dist/alerts/manager.d.ts +1 -0
- package/dist/alerts/manager.js +36 -21
- package/dist/budget/tracker.d.ts +16 -6
- package/dist/budget/tracker.js +38 -17
- package/dist/budget/types.d.ts +2 -0
- package/dist/cli.d.ts +8 -1
- package/dist/cli.js +106 -54
- package/dist/core/metrics.d.ts +4 -0
- package/dist/core/metrics.js +20 -1
- package/dist/core/pricing.d.ts +2 -0
- package/dist/core/pricing.js +4 -0
- package/dist/core/spans.d.ts +5 -3
- package/dist/core/spans.js +80 -21
- package/dist/core/tracer.d.ts +6 -3
- package/dist/core/tracer.js +49 -7
- package/dist/drift/baseline.js +7 -1
- package/dist/drift/monitor.js +6 -6
- package/dist/index.d.ts +7 -2
- package/dist/index.js +6 -1
- package/dist/instrumentations/anthropic.d.ts +1 -1
- package/dist/instrumentations/anthropic.js +2 -3
- package/dist/instrumentations/create.js +80 -19
- package/dist/instrumentations/gemini.d.ts +1 -1
- package/dist/instrumentations/gemini.js +6 -7
- package/dist/instrumentations/openai.d.ts +1 -1
- package/dist/instrumentations/openai.js +2 -3
- package/dist/instrumentations/registry.d.ts +3 -1
- package/dist/instrumentations/registry.js +28 -5
- package/dist/instrumentations/types.d.ts +4 -2
- package/dist/types/attributes.d.ts +20 -0
- package/dist/types/attributes.js +23 -1
- package/dist/types/config.d.ts +22 -2
- package/dist/types/index.d.ts +3 -3
- package/dist/types/metrics.d.ts +2 -0
- package/dist/types/metrics.js +3 -0
- package/dist/types/providers.d.ts +6 -1
- package/dist/types/spans.d.ts +21 -2
- package/dist/vercel.d.ts +53 -0
- package/dist/vercel.js +106 -0
- package/package.json +29 -3
- package/templates/grafana/dashboards/agent-workflow.json +207 -0
- package/templates/otel-collector.yml +25 -1
package/dist/vercel.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel AI SDK integration — enriches AI SDK OTel spans with toad-eye
|
|
3
|
+
* cost tracking and metrics. No monkey-patching needed.
|
|
4
|
+
*
|
|
5
|
+
* The Vercel AI SDK already emits OTel spans when `experimental_telemetry`
|
|
6
|
+
* is enabled. This module adds a SpanProcessor that intercepts those spans
|
|
7
|
+
* and calculates cost from token usage.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { initObservability } from 'toad-eye';
|
|
12
|
+
* import { withToadEye } from 'toad-eye/vercel';
|
|
13
|
+
*
|
|
14
|
+
* initObservability({ serviceName: 'my-app' });
|
|
15
|
+
*
|
|
16
|
+
* const result = await generateText({
|
|
17
|
+
* model: openai('gpt-4o'),
|
|
18
|
+
* prompt: 'Hello',
|
|
19
|
+
* experimental_telemetry: withToadEye(),
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { calculateCost } from "./core/pricing.js";
|
|
24
|
+
import { recordRequestCost, recordTokens, recordRequest, recordRequestDuration, } from "./core/metrics.js";
|
|
25
|
+
// Vercel AI SDK span operation names
|
|
26
|
+
const AI_SDK_OPERATIONS = new Set([
|
|
27
|
+
"ai.generateText",
|
|
28
|
+
"ai.streamText",
|
|
29
|
+
"ai.generateObject",
|
|
30
|
+
"ai.streamObject",
|
|
31
|
+
]);
|
|
32
|
+
function isAiSdkSpan(span) {
|
|
33
|
+
const opId = span.attributes["ai.operationId"];
|
|
34
|
+
if (typeof opId === "string")
|
|
35
|
+
return AI_SDK_OPERATIONS.has(opId);
|
|
36
|
+
return AI_SDK_OPERATIONS.has(span.name);
|
|
37
|
+
}
|
|
38
|
+
function getStringAttr(span, key) {
|
|
39
|
+
const val = span.attributes[key];
|
|
40
|
+
return typeof val === "string" ? val : undefined;
|
|
41
|
+
}
|
|
42
|
+
function getNumberAttr(span, key) {
|
|
43
|
+
const val = span.attributes[key];
|
|
44
|
+
return typeof val === "number" ? val : 0;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* OTel SpanProcessor that enriches Vercel AI SDK spans with toad-eye
|
|
48
|
+
* cost attributes and records metrics.
|
|
49
|
+
*/
|
|
50
|
+
export class ToadEyeAISpanProcessor {
|
|
51
|
+
forceFlush() {
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
}
|
|
54
|
+
shutdown() {
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
}
|
|
57
|
+
onStart() {
|
|
58
|
+
// no-op — we process on end
|
|
59
|
+
}
|
|
60
|
+
onEnd(span) {
|
|
61
|
+
if (!isAiSdkSpan(span))
|
|
62
|
+
return;
|
|
63
|
+
const model = getStringAttr(span, "gen_ai.request.model") ??
|
|
64
|
+
getStringAttr(span, "ai.model.id") ??
|
|
65
|
+
"unknown";
|
|
66
|
+
const provider = getStringAttr(span, "gen_ai.system") ??
|
|
67
|
+
getStringAttr(span, "ai.model.provider")?.split(".")[0] ??
|
|
68
|
+
"unknown";
|
|
69
|
+
const inputTokens = getNumberAttr(span, "gen_ai.usage.input_tokens") ||
|
|
70
|
+
getNumberAttr(span, "ai.usage.promptTokens");
|
|
71
|
+
const outputTokens = getNumberAttr(span, "gen_ai.usage.output_tokens") ||
|
|
72
|
+
getNumberAttr(span, "ai.usage.completionTokens");
|
|
73
|
+
// Calculate cost from token usage
|
|
74
|
+
const cost = calculateCost(model, inputTokens, outputTokens);
|
|
75
|
+
// We can't modify a ReadableSpan's attributes directly, but we can
|
|
76
|
+
// record metrics which is the primary value-add
|
|
77
|
+
const durationMs = (span.endTime[0] - span.startTime[0]) * 1000 +
|
|
78
|
+
(span.endTime[1] - span.startTime[1]) / 1_000_000;
|
|
79
|
+
recordRequest(provider, model);
|
|
80
|
+
recordRequestDuration(durationMs, provider, model);
|
|
81
|
+
if (cost > 0) {
|
|
82
|
+
recordRequestCost(cost, provider, model);
|
|
83
|
+
}
|
|
84
|
+
if (inputTokens + outputTokens > 0) {
|
|
85
|
+
recordTokens(inputTokens + outputTokens, provider, model);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Helper for `experimental_telemetry` option — enables telemetry with
|
|
91
|
+
* optional toad-eye metadata.
|
|
92
|
+
*
|
|
93
|
+
* ```ts
|
|
94
|
+
* const result = await generateText({
|
|
95
|
+
* model: openai('gpt-4o'),
|
|
96
|
+
* prompt: 'Hello',
|
|
97
|
+
* experimental_telemetry: withToadEye({ functionId: 'my-feature' }),
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function withToadEye(options) {
|
|
102
|
+
return {
|
|
103
|
+
isEnabled: true,
|
|
104
|
+
...options,
|
|
105
|
+
};
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toad-eye",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Auto-instrumentation, cost tracking, budget guards, and observability for LLM services",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./alerts": {
|
|
14
|
+
"import": "./dist/alerts/index.js",
|
|
15
|
+
"types": "./dist/alerts/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./drift": {
|
|
18
|
+
"import": "./dist/drift/index.js",
|
|
19
|
+
"types": "./dist/drift/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./export": {
|
|
22
|
+
"import": "./dist/export.js",
|
|
23
|
+
"types": "./dist/export.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./vercel": {
|
|
26
|
+
"import": "./dist/vercel.js",
|
|
27
|
+
"types": "./dist/vercel.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
8
30
|
"bin": {
|
|
9
31
|
"toad-eye": "dist/cli.js"
|
|
10
32
|
},
|
|
@@ -51,13 +73,15 @@
|
|
|
51
73
|
"@opentelemetry/resources": "^2.6.0",
|
|
52
74
|
"@opentelemetry/sdk-metrics": "^2.6.0",
|
|
53
75
|
"@opentelemetry/sdk-node": "^0.213.0",
|
|
76
|
+
"@opentelemetry/sdk-trace-base": "^2.6.0",
|
|
77
|
+
"@opentelemetry/sdk-trace-node": "^2.6.0",
|
|
54
78
|
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
55
|
-
"nodemailer": "^8.0.3",
|
|
56
79
|
"yaml": "^2.7.0"
|
|
57
80
|
},
|
|
58
81
|
"peerDependencies": {
|
|
59
82
|
"@anthropic-ai/sdk": ">=0.30.0",
|
|
60
83
|
"@google/generative-ai": ">=0.20.0",
|
|
84
|
+
"nodemailer": ">=8.0.0",
|
|
61
85
|
"openai": ">=4.0.0"
|
|
62
86
|
},
|
|
63
87
|
"peerDependenciesMeta": {
|
|
@@ -69,11 +93,13 @@
|
|
|
69
93
|
},
|
|
70
94
|
"@google/generative-ai": {
|
|
71
95
|
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"nodemailer": {
|
|
98
|
+
"optional": true
|
|
72
99
|
}
|
|
73
100
|
},
|
|
74
101
|
"devDependencies": {
|
|
75
102
|
"@types/nodemailer": "^7.0.11",
|
|
76
|
-
"playwright": "^1.58.2",
|
|
77
103
|
"vitest": "^4.1.0"
|
|
78
104
|
}
|
|
79
105
|
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
{
|
|
2
|
+
"annotations": { "list": [] },
|
|
3
|
+
"editable": true,
|
|
4
|
+
"fiscalYearStartMonth": 0,
|
|
5
|
+
"graphTooltip": 1,
|
|
6
|
+
"id": null,
|
|
7
|
+
"links": [],
|
|
8
|
+
"panels": [
|
|
9
|
+
{
|
|
10
|
+
"title": "Avg Steps per Query",
|
|
11
|
+
"type": "stat",
|
|
12
|
+
"gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 },
|
|
13
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
14
|
+
"targets": [
|
|
15
|
+
{
|
|
16
|
+
"expr": "sum(gen_ai_agent_steps_per_query_sum) / sum(gen_ai_agent_steps_per_query_count) or vector(0)",
|
|
17
|
+
"refId": "A"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"fieldConfig": {
|
|
21
|
+
"defaults": {
|
|
22
|
+
"unit": "short",
|
|
23
|
+
"decimals": 1,
|
|
24
|
+
"thresholds": {
|
|
25
|
+
"steps": [
|
|
26
|
+
{ "color": "green", "value": null },
|
|
27
|
+
{ "color": "yellow", "value": 10 },
|
|
28
|
+
{ "color": "red", "value": 20 }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"overrides": []
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"title": "Total Agent Queries",
|
|
37
|
+
"type": "stat",
|
|
38
|
+
"gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 },
|
|
39
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
40
|
+
"targets": [
|
|
41
|
+
{
|
|
42
|
+
"expr": "sum(gen_ai_agent_steps_per_query_count)",
|
|
43
|
+
"refId": "A"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"fieldConfig": {
|
|
47
|
+
"defaults": { "unit": "short" },
|
|
48
|
+
"overrides": []
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"title": "Total Tool Invocations",
|
|
53
|
+
"type": "stat",
|
|
54
|
+
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
|
|
55
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
56
|
+
"targets": [
|
|
57
|
+
{
|
|
58
|
+
"expr": "sum(gen_ai_agent_tool_usage_total)",
|
|
59
|
+
"refId": "A"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"fieldConfig": {
|
|
63
|
+
"defaults": { "unit": "short" },
|
|
64
|
+
"overrides": []
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"title": "Unique Tools Used",
|
|
69
|
+
"type": "stat",
|
|
70
|
+
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 },
|
|
71
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
72
|
+
"targets": [
|
|
73
|
+
{
|
|
74
|
+
"expr": "count(count by (gen_ai_agent_tool_name) (gen_ai_agent_tool_usage_total))",
|
|
75
|
+
"refId": "A"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"fieldConfig": {
|
|
79
|
+
"defaults": { "unit": "short" },
|
|
80
|
+
"overrides": []
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"title": "Agent Step Type Breakdown",
|
|
85
|
+
"description": "Distribution of think / act / observe / answer steps",
|
|
86
|
+
"type": "piechart",
|
|
87
|
+
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
|
|
88
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
89
|
+
"targets": [
|
|
90
|
+
{
|
|
91
|
+
"expr": "sum by (gen_ai_agent_step_type) (gen_ai_agent_steps_per_query_sum)",
|
|
92
|
+
"legendFormat": "{{gen_ai_agent_step_type}}",
|
|
93
|
+
"refId": "A"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"options": {
|
|
97
|
+
"legend": {
|
|
98
|
+
"displayMode": "table",
|
|
99
|
+
"placement": "right",
|
|
100
|
+
"values": ["value", "percent"]
|
|
101
|
+
},
|
|
102
|
+
"pieType": "donut"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"title": "Tool Usage Frequency",
|
|
107
|
+
"description": "Invocations per tool name",
|
|
108
|
+
"type": "barchart",
|
|
109
|
+
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
|
|
110
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
111
|
+
"targets": [
|
|
112
|
+
{
|
|
113
|
+
"expr": "sort_desc(sum by (gen_ai_agent_tool_name) (gen_ai_agent_tool_usage_total))",
|
|
114
|
+
"legendFormat": "{{gen_ai_agent_tool_name}}",
|
|
115
|
+
"refId": "A"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"title": "Steps per Query Over Time",
|
|
121
|
+
"type": "timeseries",
|
|
122
|
+
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 12 },
|
|
123
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
124
|
+
"targets": [
|
|
125
|
+
{
|
|
126
|
+
"expr": "sum(rate(gen_ai_agent_steps_per_query_sum[5m])) / sum(rate(gen_ai_agent_steps_per_query_count[5m]))",
|
|
127
|
+
"legendFormat": "avg steps/query",
|
|
128
|
+
"refId": "avg"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"expr": "histogram_quantile(0.95, sum by (le) (rate(gen_ai_agent_steps_per_query_bucket[5m])))",
|
|
132
|
+
"legendFormat": "p95 steps/query",
|
|
133
|
+
"refId": "p95"
|
|
134
|
+
}
|
|
135
|
+
],
|
|
136
|
+
"fieldConfig": {
|
|
137
|
+
"defaults": {
|
|
138
|
+
"unit": "short",
|
|
139
|
+
"custom": { "fillOpacity": 10 }
|
|
140
|
+
},
|
|
141
|
+
"overrides": []
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"title": "Tool Invocations Over Time",
|
|
146
|
+
"type": "timeseries",
|
|
147
|
+
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 20 },
|
|
148
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
149
|
+
"targets": [
|
|
150
|
+
{
|
|
151
|
+
"expr": "sum by (gen_ai_agent_tool_name) (rate(gen_ai_agent_tool_usage_total[5m]))",
|
|
152
|
+
"legendFormat": "{{gen_ai_agent_tool_name}}",
|
|
153
|
+
"refId": "A"
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"fieldConfig": {
|
|
157
|
+
"defaults": {
|
|
158
|
+
"unit": "reqps",
|
|
159
|
+
"custom": { "fillOpacity": 20, "stacking": { "mode": "normal" } }
|
|
160
|
+
},
|
|
161
|
+
"overrides": []
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"title": "Steps Distribution Histogram",
|
|
166
|
+
"description": "How many steps do agent queries typically take?",
|
|
167
|
+
"type": "histogram",
|
|
168
|
+
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 20 },
|
|
169
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
170
|
+
"targets": [
|
|
171
|
+
{
|
|
172
|
+
"expr": "sum by (le) (gen_ai_agent_steps_per_query_bucket)",
|
|
173
|
+
"format": "heatmap",
|
|
174
|
+
"refId": "A"
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"fieldConfig": {
|
|
178
|
+
"defaults": {
|
|
179
|
+
"custom": { "fillOpacity": 80 }
|
|
180
|
+
},
|
|
181
|
+
"overrides": []
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
"schemaVersion": 39,
|
|
186
|
+
"tags": ["toad-eye", "agent"],
|
|
187
|
+
"templating": {
|
|
188
|
+
"list": [
|
|
189
|
+
{
|
|
190
|
+
"name": "tool",
|
|
191
|
+
"type": "query",
|
|
192
|
+
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
193
|
+
"query": "label_values(gen_ai_agent_tool_usage_total, gen_ai_agent_tool_name)",
|
|
194
|
+
"includeAll": true,
|
|
195
|
+
"multi": true,
|
|
196
|
+
"current": { "text": "All", "value": "$__all" },
|
|
197
|
+
"regex": ""
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
},
|
|
201
|
+
"time": { "from": "now-6h", "to": "now" },
|
|
202
|
+
"timepicker": {},
|
|
203
|
+
"timezone": "utc",
|
|
204
|
+
"title": "toad-eye / Agent Workflow",
|
|
205
|
+
"uid": "toad-eye-agent-workflow",
|
|
206
|
+
"version": 1
|
|
207
|
+
}
|
|
@@ -8,6 +8,30 @@ processors:
|
|
|
8
8
|
batch:
|
|
9
9
|
timeout: 1s
|
|
10
10
|
|
|
11
|
+
# Tail-based sampling: keeps 100% errors + slow traces, samples healthy traffic.
|
|
12
|
+
# Only applies to traces pipeline — metrics always get 100%.
|
|
13
|
+
# To disable: remove tail_sampling from traces pipeline processors.
|
|
14
|
+
tail_sampling:
|
|
15
|
+
decision_wait: 10s
|
|
16
|
+
num_traces: 100000
|
|
17
|
+
expected_new_traces_per_sec: 100
|
|
18
|
+
policies:
|
|
19
|
+
# Keep all traces with errors
|
|
20
|
+
- name: errors-always
|
|
21
|
+
type: status_code
|
|
22
|
+
status_code:
|
|
23
|
+
status_codes: [ERROR]
|
|
24
|
+
# Keep all slow traces (> 2s)
|
|
25
|
+
- name: slow-traces
|
|
26
|
+
type: latency
|
|
27
|
+
latency:
|
|
28
|
+
threshold_ms: 2000
|
|
29
|
+
# Sample 10% of remaining healthy traffic
|
|
30
|
+
- name: probabilistic-fallback
|
|
31
|
+
type: probabilistic
|
|
32
|
+
probabilistic:
|
|
33
|
+
sampling_percentage: 10
|
|
34
|
+
|
|
11
35
|
exporters:
|
|
12
36
|
otlphttp/jaeger:
|
|
13
37
|
endpoint: http://jaeger:4318
|
|
@@ -21,7 +45,7 @@ service:
|
|
|
21
45
|
pipelines:
|
|
22
46
|
traces:
|
|
23
47
|
receivers: [otlp]
|
|
24
|
-
processors: [batch]
|
|
48
|
+
processors: [tail_sampling, batch]
|
|
25
49
|
exporters: [otlphttp/jaeger]
|
|
26
50
|
metrics:
|
|
27
51
|
receivers: [otlp]
|