@intentius/chant-lexicon-otel 0.101.0 → 0.104.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 +1 -1
- package/dist/attribution.d.ts +69 -0
- package/dist/attribution.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/components/connectors.d.ts.map +1 -1
- package/dist/components/processors.d.ts +1 -1
- package/dist/components/processors.d.ts.map +1 -1
- package/dist/components/sampling.d.ts +3 -0
- package/dist/components/sampling.d.ts.map +1 -1
- package/dist/composites/node-agent.d.ts +4 -3
- package/dist/composites/node-agent.d.ts.map +1 -1
- package/dist/config-hygiene.d.ts +33 -0
- package/dist/config-hygiene.d.ts.map +1 -0
- package/dist/define.d.ts +18 -2
- package/dist/define.d.ts.map +1 -1
- package/dist/genai.d.ts.map +1 -1
- package/dist/import/generator.d.ts +5 -1
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/import/parser.d.ts +1 -1
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/init-templates.d.ts.map +1 -1
- package/dist/integrity.json +15 -5
- package/dist/lint/audit-catalog.d.ts +3 -2
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/otel-helpers.d.ts +2 -2
- package/dist/lint/post-synth/otel-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/otel118.d.ts +8 -0
- package/dist/lint/post-synth/otel118.d.ts.map +1 -0
- package/dist/lint/post-synth/otel119.d.ts +8 -0
- package/dist/lint/post-synth/otel119.d.ts.map +1 -0
- package/dist/lint/post-synth/otel120.d.ts +8 -0
- package/dist/lint/post-synth/otel120.d.ts.map +1 -0
- package/dist/lint/post-synth/otel121.d.ts +8 -0
- package/dist/lint/post-synth/otel121.d.ts.map +1 -0
- package/dist/lint/post-synth/otel122.d.ts +8 -0
- package/dist/lint/post-synth/otel122.d.ts.map +1 -0
- package/dist/lint/post-synth/otel123.d.ts +8 -0
- package/dist/lint/post-synth/otel123.d.ts.map +1 -0
- package/dist/lint/post-synth/otel124.d.ts +8 -0
- package/dist/lint/post-synth/otel124.d.ts.map +1 -0
- package/dist/lint/post-synth/otel125.d.ts +8 -0
- package/dist/lint/post-synth/otel125.d.ts.map +1 -0
- package/dist/lint/post-synth/otel126.d.ts +8 -0
- package/dist/lint/post-synth/otel126.d.ts.map +1 -0
- package/dist/lint/post-synth/otel127.d.ts +8 -0
- package/dist/lint/post-synth/otel127.d.ts.map +1 -0
- package/dist/lint/rules/literal-credential.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/metric-names.d.ts +29 -0
- package/dist/metric-names.d.ts.map +1 -1
- package/dist/model.d.ts +24 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/okf/index.md +10 -0
- package/dist/okf/rules/OTEL118.md +11 -0
- package/dist/okf/rules/OTEL119.md +11 -0
- package/dist/okf/rules/OTEL120.md +11 -0
- package/dist/okf/rules/OTEL121.md +11 -0
- package/dist/okf/rules/OTEL122.md +11 -0
- package/dist/okf/rules/OTEL123.md +11 -0
- package/dist/okf/rules/OTEL124.md +11 -0
- package/dist/okf/rules/OTEL125.md +11 -0
- package/dist/okf/rules/OTEL126.md +11 -0
- package/dist/okf/rules/OTEL127.md +11 -0
- package/dist/platform.d.ts +11 -4
- package/dist/platform.d.ts.map +1 -1
- package/dist/rules/literal-credential.ts +2 -3
- package/dist/rules/otel-helpers.ts +15 -4
- package/dist/rules/otel118.ts +17 -0
- package/dist/rules/otel119.ts +17 -0
- package/dist/rules/otel120.ts +17 -0
- package/dist/rules/otel121.ts +17 -0
- package/dist/rules/otel122.ts +17 -0
- package/dist/rules/otel123.ts +17 -0
- package/dist/rules/otel124.ts +17 -0
- package/dist/rules/otel125.ts +17 -0
- package/dist/rules/otel126.ts +17 -0
- package/dist/rules/otel127.ts +17 -0
- package/dist/semconv.d.ts +2 -1
- package/dist/semconv.d.ts.map +1 -1
- package/dist/skill-defs.d.ts +1 -1
- package/dist/skill-defs.d.ts.map +1 -1
- package/dist/skills/chant-otel.md +7 -3
- package/dist/validate-config.d.ts +29 -3
- package/dist/validate-config.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/attribution.ts +385 -0
- package/src/codegen/docs.ts +8 -1
- package/src/components/k8s-receivers.test.ts +5 -2
- package/src/components/processors.ts +1 -1
- package/src/components/sampling.ts +3 -0
- package/src/composites/composites.test.ts +16 -0
- package/src/composites/node-agent.ts +3 -2
- package/src/config-hygiene.ts +470 -0
- package/src/connectors.test.ts +3 -0
- package/src/define.ts +25 -4
- package/src/import/generator.test.ts +42 -4
- package/src/import/generator.ts +7 -2
- package/src/import/parser.test.ts +11 -0
- package/src/import/parser.ts +16 -4
- package/src/import/roundtrip.test.ts +90 -14
- package/src/index.ts +7 -0
- package/src/init-templates.ts +4 -2
- package/src/lint/audit-catalog.ts +87 -2
- package/src/lint/post-synth/hygiene.test.ts +334 -0
- package/src/lint/post-synth/index.ts +20 -0
- package/src/lint/post-synth/otel-helpers.ts +15 -4
- package/src/lint/post-synth/otel118.test.ts +308 -0
- package/src/lint/post-synth/otel118.ts +17 -0
- package/src/lint/post-synth/otel119.ts +17 -0
- package/src/lint/post-synth/otel120.ts +17 -0
- package/src/lint/post-synth/otel121.ts +17 -0
- package/src/lint/post-synth/otel122.ts +17 -0
- package/src/lint/post-synth/otel123.ts +17 -0
- package/src/lint/post-synth/otel124.ts +17 -0
- package/src/lint/post-synth/otel125.ts +17 -0
- package/src/lint/post-synth/otel126.ts +17 -0
- package/src/lint/post-synth/otel127.ts +17 -0
- package/src/lint/post-synth/post-synth.test.ts +31 -0
- package/src/lint/rules/literal-credential.ts +2 -3
- package/src/metric-names.test.ts +24 -2
- package/src/metric-names.ts +66 -1
- package/src/model.ts +48 -0
- package/src/platform.test.ts +61 -0
- package/src/platform.ts +35 -22
- package/src/plugin.test.ts +10 -0
- package/src/semconv.ts +6 -2
- package/src/skills/chant-otel.md +7 -3
- package/src/validate-config.ts +85 -9
package/src/metric-names.test.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { describe, expect, test } from "vitest";
|
|
2
|
-
import { PrometheusExporter, SpanMetricsConnector } from "./components";
|
|
2
|
+
import { PrometheusExporter, ServiceGraphConnector, SpanMetricsConnector } from "./components";
|
|
3
3
|
import { genAiComponents, genAiMetrics } from "./genai";
|
|
4
|
-
import { prometheusLabel, prometheusMetricName, spanMetricsNames } from "./metric-names";
|
|
4
|
+
import { prometheusLabel, prometheusMetricName, serviceGraphNames, spanMetricsNames } from "./metric-names";
|
|
5
5
|
|
|
6
6
|
describe("spanMetricsNames", () => {
|
|
7
|
+
test("takes a span_metrics declaration, the collector's newer name, as a spanmetrics one", () => {
|
|
8
|
+
const declared = { componentType: "span_metrics", props: { namespace: "spans" } } as never;
|
|
9
|
+
expect(spanMetricsNames(declared).namespace).toBe("spans");
|
|
10
|
+
expect(() => spanMetricsNames({ componentType: "count", props: {} } as never)).toThrow("expected a spanmetrics connector");
|
|
11
|
+
});
|
|
12
|
+
|
|
7
13
|
test("defaults: the traces.span.metrics namespace and a millisecond histogram", () => {
|
|
8
14
|
const n = spanMetricsNames(new SpanMetricsConnector({}));
|
|
9
15
|
expect(n.namespace).toBe("traces.span.metrics");
|
|
@@ -78,3 +84,19 @@ describe("prometheus naming", () => {
|
|
|
78
84
|
expect(prometheusMetricName("calls", "sum", "{call}")).toBe("calls_total");
|
|
79
85
|
});
|
|
80
86
|
});
|
|
87
|
+
|
|
88
|
+
describe("serviceGraphNames", () => {
|
|
89
|
+
test("the fixed names, in seconds, with the edge labels and the client_/server_ dimensions", () => {
|
|
90
|
+
const n = serviceGraphNames(new ServiceGraphConnector({ dimensions: ["http.method"], virtual_node_extra_label: true }));
|
|
91
|
+
expect(n.requests.prometheus).toBe("traces_service_graph_request_total");
|
|
92
|
+
expect(n.failed.prometheus).toBe("traces_service_graph_request_failed_total");
|
|
93
|
+
expect(n.serverDuration.prometheus).toBe("traces_service_graph_request_server_seconds");
|
|
94
|
+
expect(n.clientDuration.prometheus).toBe("traces_service_graph_request_client_seconds");
|
|
95
|
+
expect(n.requests.dimensions).toEqual(["client", "server", "connection_type", "failed", "client_http.method", "server_http.method", "virtual_node"]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("the exporter's namespace prefixes the names", () => {
|
|
99
|
+
expect(serviceGraphNames({}, new PrometheusExporter({ endpoint: "0.0.0.0:8889", namespace: "otel" })).requests.prometheus).toBe("otel_traces_service_graph_request_total");
|
|
100
|
+
expect(() => serviceGraphNames(new SpanMetricsConnector({}))).toThrow(/expected a servicegraph connector/);
|
|
101
|
+
});
|
|
102
|
+
});
|
package/src/metric-names.ts
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* builds queries from it does not load the collector components.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import { canonicalComponentType } from "./model";
|
|
23
|
+
|
|
22
24
|
/** A metric a collector component emits, as the collector names it and as Prometheus exposes it. */
|
|
23
25
|
export interface CollectorMetric {
|
|
24
26
|
/** The OTLP metric name. */
|
|
@@ -114,7 +116,7 @@ type Declared = { componentType?: unknown; props?: unknown };
|
|
|
114
116
|
function configOf(connector: SpanMetricsNamingConfig | Declared): SpanMetricsNamingConfig {
|
|
115
117
|
const d = connector as Declared;
|
|
116
118
|
if (typeof d.componentType === "string") {
|
|
117
|
-
if (d.componentType !== "spanmetrics") throw new Error(`spanMetricsNames: expected a spanmetrics connector, got ${d.componentType}`);
|
|
119
|
+
if (canonicalComponentType("connector", d.componentType) !== "spanmetrics") throw new Error(`spanMetricsNames: expected a spanmetrics connector, got ${d.componentType}`);
|
|
118
120
|
return (d.props ?? {}) as SpanMetricsNamingConfig;
|
|
119
121
|
}
|
|
120
122
|
return connector as SpanMetricsNamingConfig;
|
|
@@ -174,3 +176,66 @@ export function spanMetricsNames(
|
|
|
174
176
|
errorStatus: SPAN_STATUS_ERROR,
|
|
175
177
|
};
|
|
176
178
|
}
|
|
179
|
+
|
|
180
|
+
/** The prefix of every metric the `servicegraph` connector emits; the connector takes no namespace. */
|
|
181
|
+
export const SERVICEGRAPH_NAMESPACE = "traces_service_graph";
|
|
182
|
+
|
|
183
|
+
/** The parts of a servicegraph config the metric labels depend on. */
|
|
184
|
+
export interface ServiceGraphNamingConfig {
|
|
185
|
+
dimensions?: string[];
|
|
186
|
+
virtual_node_extra_label?: boolean;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The metrics one `servicegraph` connector emits. */
|
|
190
|
+
export interface ServiceGraphNames {
|
|
191
|
+
/** Requests per edge. */
|
|
192
|
+
requests: CollectorMetric;
|
|
193
|
+
/** Failed requests per edge. */
|
|
194
|
+
failed: CollectorMetric;
|
|
195
|
+
/** Server-side request duration histogram, in seconds. */
|
|
196
|
+
serverDuration: CollectorMetric;
|
|
197
|
+
/** Client-side request duration histogram, in seconds. */
|
|
198
|
+
clientDuration: CollectorMetric;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The Prometheus names of the metrics a `servicegraph` connector emits at
|
|
203
|
+
* `COLLECTOR_PIN`, with its default feature gates: latency in seconds, under
|
|
204
|
+
* `_request_server` and `_request_client`. Every metric carries `client`,
|
|
205
|
+
* `server`, `connection_type` and `failed`, `client_<d>` and `server_<d>` for
|
|
206
|
+
* each configured dimension, and `virtual_node` when
|
|
207
|
+
* `virtual_node_extra_label` is set (connector/servicegraphconnector/connector.go).
|
|
208
|
+
*/
|
|
209
|
+
export function serviceGraphNames(
|
|
210
|
+
connector: ServiceGraphNamingConfig | Declared,
|
|
211
|
+
exporter?: PrometheusNaming | { props?: PrometheusNaming },
|
|
212
|
+
): ServiceGraphNames {
|
|
213
|
+
const d = connector as Declared;
|
|
214
|
+
if (typeof d.componentType === "string" && d.componentType !== "servicegraph") {
|
|
215
|
+
throw new Error(`serviceGraphNames: expected a servicegraph connector, got ${d.componentType}`);
|
|
216
|
+
}
|
|
217
|
+
const c = (typeof d.componentType === "string" ? (d.props ?? {}) : connector) as ServiceGraphNamingConfig;
|
|
218
|
+
const naming: PrometheusNaming =
|
|
219
|
+
exporter && "props" in exporter && typeof exporter.props === "object" ? (exporter.props as PrometheusNaming) : ((exporter ?? {}) as PrometheusNaming);
|
|
220
|
+
const dimensions = [
|
|
221
|
+
"client",
|
|
222
|
+
"server",
|
|
223
|
+
"connection_type",
|
|
224
|
+
"failed",
|
|
225
|
+
...(c.dimensions ?? []).flatMap((dim) => [`client_${dim}`, `server_${dim}`]),
|
|
226
|
+
...(c.virtual_node_extra_label ? ["virtual_node"] : []),
|
|
227
|
+
];
|
|
228
|
+
const metric = (n: string, type: "sum" | "histogram", unit?: string): CollectorMetric => ({
|
|
229
|
+
name: `${SERVICEGRAPH_NAMESPACE}_${n}`,
|
|
230
|
+
prometheus: prometheusMetricName(`${SERVICEGRAPH_NAMESPACE}_${n}`, type, unit, naming),
|
|
231
|
+
type,
|
|
232
|
+
...(unit ? { unit } : {}),
|
|
233
|
+
dimensions,
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
requests: metric("request_total", "sum"),
|
|
237
|
+
failed: metric("request_failed_total", "sum"),
|
|
238
|
+
serverDuration: metric("request_server", "histogram", "s"),
|
|
239
|
+
clientDuration: metric("request_client", "histogram", "s"),
|
|
240
|
+
};
|
|
241
|
+
}
|
package/src/model.ts
CHANGED
|
@@ -108,3 +108,51 @@ export function looksLikeCollectorConfig(value: unknown): value is CollectorConf
|
|
|
108
108
|
if (typeof pipelines !== "object" || pipelines === null) return false;
|
|
109
109
|
return "receivers" in v || "exporters" in v;
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A built-in type the collector renamed, keeping the old name as a
|
|
114
|
+
* deprecated alias. `type` is the new name and `builtin` the name chant's
|
|
115
|
+
* class declares and emits. `since` is the first release of `repo` whose
|
|
116
|
+
* `metadata.yaml` for the component lists `builtin` as `deprecated_type`.
|
|
117
|
+
*/
|
|
118
|
+
export interface ComponentTypeAlias {
|
|
119
|
+
kind: ComponentKind;
|
|
120
|
+
type: string;
|
|
121
|
+
builtin: string;
|
|
122
|
+
since: string;
|
|
123
|
+
repo: "core" | "contrib";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The renamed built-ins. chant emits `builtin`, which the pinned collector
|
|
128
|
+
* (`COLLECTOR_PIN`, v0.130.0) knows and newer ones still accept. A config
|
|
129
|
+
* written for a newer collector may use `type`, so code that reads a config
|
|
130
|
+
* (the importer, the checks, the k8s composites) accepts both.
|
|
131
|
+
*/
|
|
132
|
+
export const COMPONENT_TYPE_ALIASES: readonly ComponentTypeAlias[] = [
|
|
133
|
+
{ kind: "exporter", type: "otlp_grpc", builtin: "otlp", since: "v0.148.0", repo: "core" },
|
|
134
|
+
{ kind: "exporter", type: "otlp_http", builtin: "otlphttp", since: "v0.148.0", repo: "core" },
|
|
135
|
+
{ kind: "processor", type: "k8s_attributes", builtin: "k8sattributes", since: "v0.148.0", repo: "contrib" },
|
|
136
|
+
{ kind: "connector", type: "signal_to_metrics", builtin: "signaltometrics", since: "v0.148.0", repo: "contrib" },
|
|
137
|
+
{ kind: "receiver", type: "file_log", builtin: "filelog", since: "v0.149.0", repo: "contrib" },
|
|
138
|
+
{ kind: "connector", type: "span_metrics", builtin: "spanmetrics", since: "v0.151.0", repo: "contrib" },
|
|
139
|
+
{ kind: "connector", type: "service_graph", builtin: "servicegraph", since: "v0.151.0", repo: "contrib" },
|
|
140
|
+
{ kind: "receiver", type: "host_metrics", builtin: "hostmetrics", since: "v0.151.0", repo: "contrib" },
|
|
141
|
+
{ kind: "receiver", type: "kubelet_stats", builtin: "kubeletstats", since: "v0.152.0", repo: "contrib" },
|
|
142
|
+
{ kind: "processor", type: "resource_detection", builtin: "resourcedetection", since: "v0.153.0", repo: "contrib" },
|
|
143
|
+
{ kind: "exporter", type: "load_balancing", builtin: "loadbalancing", since: "v0.153.0", repo: "contrib" },
|
|
144
|
+
{ kind: "processor", type: "delta_to_cumulative", builtin: "deltatocumulative", since: "v0.158.0", repo: "contrib" },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const ALIAS_TO_BUILTIN: ReadonlyMap<string, string> = new Map(COMPONENT_TYPE_ALIASES.map((a) => [`${a.kind}:${a.type}`, a.builtin]));
|
|
148
|
+
|
|
149
|
+
/** The type chant declares for `kind` + `type`: the old name of a renamed built-in, otherwise `type` itself. */
|
|
150
|
+
export function canonicalComponentType(kind: ComponentKind, type: string): string {
|
|
151
|
+
return ALIAS_TO_BUILTIN.get(`${kind}:${type}`) ?? type;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The type of component id `id`, with a renamed built-in's new name read as the old one: `span_metrics/genai` is `spanmetrics`. */
|
|
155
|
+
export function canonicalTypeOf(kind: ComponentKind, id: string): string {
|
|
156
|
+
const slash = id.indexOf("/");
|
|
157
|
+
return canonicalComponentType(kind, slash === -1 ? id : id.slice(0, slash));
|
|
158
|
+
}
|
package/src/platform.test.ts
CHANGED
|
@@ -44,6 +44,67 @@ describe("collectorEndpoints", () => {
|
|
|
44
44
|
expect(collectorEndpoints(config).ports).toEqual([{ name: "otlp-edge-grpc", port: 14317 }]);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
test("a receiver's client endpoint is not a port: kubeletstats and k8s_cluster beside otlp add none (#3102)", () => {
|
|
48
|
+
const config: CollectorConfig = {
|
|
49
|
+
receivers: {
|
|
50
|
+
otlp: { protocols: { grpc: { endpoint: "0.0.0.0:4317" } } },
|
|
51
|
+
kubeletstats: { auth_type: "serviceAccount", endpoint: "https://${env:K8S_NODE_NAME}:10250" },
|
|
52
|
+
k8s_cluster: { auth_type: "serviceAccount" },
|
|
53
|
+
prometheus: { config: { scrape_configs: [] }, target_allocator: { endpoint: "http://ta:8080" } },
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
expect(collectorEndpoints(config).ports).toEqual([{ name: "otlp-grpc", port: 4317 }]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("zipkin and jaeger listeners are ports", () => {
|
|
60
|
+
const config: CollectorConfig = {
|
|
61
|
+
receivers: {
|
|
62
|
+
zipkin: { endpoint: "0.0.0.0:9411" },
|
|
63
|
+
jaeger: { protocols: { grpc: { endpoint: "0.0.0.0:14250" }, thrift_http: { endpoint: "0.0.0.0:14268" } } },
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
expect(collectorEndpoints(config).ports).toEqual([
|
|
67
|
+
{ name: "zipkin", port: 9411 },
|
|
68
|
+
{ name: "jaeger-grpc", port: 14250 },
|
|
69
|
+
{ name: "jaeger-thrift-h", port: 14268 },
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("the prometheus exporter's scrape endpoint is a port, after the receivers' (#3122)", () => {
|
|
74
|
+
const config: CollectorConfig = {
|
|
75
|
+
receivers: { otlp: { protocols: { grpc: { endpoint: "0.0.0.0:4317" } } } },
|
|
76
|
+
exporters: { prometheus: { endpoint: "0.0.0.0:8889" }, otlp: { endpoint: "tempo:4317" } },
|
|
77
|
+
};
|
|
78
|
+
expect(collectorEndpoints(config).ports).toEqual([
|
|
79
|
+
{ name: "otlp-grpc", port: 4317 },
|
|
80
|
+
{ name: "prometheus", port: 8889 },
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("contrib push receivers that listen are ports, and a UDP listener says so (#3122)", () => {
|
|
85
|
+
const config: CollectorConfig = {
|
|
86
|
+
receivers: {
|
|
87
|
+
statsd: { endpoint: "0.0.0.0:8125" },
|
|
88
|
+
"carbon/tcp": { endpoint: "0.0.0.0:2003" },
|
|
89
|
+
"carbon/udp": { endpoint: "0.0.0.0:2003", transport: "udp" },
|
|
90
|
+
syslog: { protocol: "rfc5424", tcp: { listen_address: "0.0.0.0:54526" }, udp: { listen_address: "0.0.0.0:54526" } },
|
|
91
|
+
fluentforward: { endpoint: "0.0.0.0:8006" },
|
|
92
|
+
splunk_hec: { endpoint: "0.0.0.0:8088" },
|
|
93
|
+
influxdb: { endpoint: "0.0.0.0:8086" },
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
expect(collectorEndpoints(config).ports).toEqual([
|
|
97
|
+
{ name: "statsd", port: 8125, protocol: "UDP" },
|
|
98
|
+
{ name: "carbon-tcp", port: 2003 },
|
|
99
|
+
{ name: "carbon-udp", port: 2003, protocol: "UDP" },
|
|
100
|
+
{ name: "syslog-tcp", port: 54526 },
|
|
101
|
+
{ name: "syslog-udp", port: 54526, protocol: "UDP" },
|
|
102
|
+
{ name: "fluentforward", port: 8006 },
|
|
103
|
+
{ name: "splunk-hec", port: 8088 },
|
|
104
|
+
{ name: "influxdb", port: 8086 },
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
|
|
47
108
|
test("port names are cut to 15 characters", () => {
|
|
48
109
|
const config: CollectorConfig = {
|
|
49
110
|
receivers: { "otlp/very_long_name": { protocols: { http: { endpoint: "0.0.0.0:4318" } } } },
|
package/src/platform.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { DebugExporter } from "./components/exporters";
|
|
|
17
17
|
import { HealthCheckExtension } from "./components/extensions";
|
|
18
18
|
import { Pipeline } from "./pipeline";
|
|
19
19
|
import { SIGNALS, parseComponentId, type CollectorConfig, type Signal } from "./model";
|
|
20
|
+
import { listenerPaths } from "./validate-config";
|
|
20
21
|
|
|
21
22
|
/** The contrib collector image at the version the built-in components are typed against. */
|
|
22
23
|
export const COLLECTOR_IMAGE = `otel/opentelemetry-collector-contrib:${COLLECTOR_PIN.version.replace(/^v/, "")}`;
|
|
@@ -65,10 +66,12 @@ export interface CollectorPort {
|
|
|
65
66
|
/** A name usable as a k8s port name: lowercase, `-` separated, at most 15 characters. */
|
|
66
67
|
name: string;
|
|
67
68
|
port: number;
|
|
69
|
+
/** Set when the listener is UDP. A port without it is TCP. */
|
|
70
|
+
protocol?: "UDP";
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
export interface CollectorEndpoints {
|
|
71
|
-
/** Ports the receivers listen on, in config order, one entry per port. */
|
|
74
|
+
/** Ports the receivers, then the exporters, listen on, in config order, one entry per port and protocol. */
|
|
72
75
|
ports: CollectorPort[];
|
|
73
76
|
/**
|
|
74
77
|
* The `health_check` extension, when the service enables one and it listens
|
|
@@ -95,36 +98,46 @@ function portName(parts: string[]): string {
|
|
|
95
98
|
return name.slice(0, 15).replace(/-$/, "");
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
/**
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
for (const
|
|
102
|
-
if (
|
|
103
|
-
|
|
101
|
+
/** The value at a key path, if every key on the way is there. */
|
|
102
|
+
function valueAt(value: unknown, path: string[]): unknown {
|
|
103
|
+
let cur: unknown = value;
|
|
104
|
+
for (const key of path) {
|
|
105
|
+
if (!cur || typeof cur !== "object" || Array.isArray(cur)) return undefined;
|
|
106
|
+
cur = (cur as Record<string, unknown>)[key];
|
|
104
107
|
}
|
|
108
|
+
return cur;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
108
112
|
|
|
109
113
|
/**
|
|
110
|
-
* The ports a built collector config listens on: every receiver
|
|
111
|
-
*
|
|
112
|
-
*
|
|
114
|
+
* The ports a built collector config listens on: the address every receiver
|
|
115
|
+
* and exporter that listens sets (the types OTEL117 knows: otlp, jaeger,
|
|
116
|
+
* statsd, syslog, the `prometheus` exporter's scrape endpoint and the rest),
|
|
117
|
+
* named after the component and the key above the address (`otlp-grpc`,
|
|
118
|
+
* `syslog-udp`), and the `health_check` port if the service enables it. A
|
|
119
|
+
* component whose `endpoint` is a server it connects to, such as
|
|
120
|
+
* kubeletstats' kubelet, adds no port, and neither does a type the table
|
|
121
|
+
* doesn't list. A UDP listener gets `protocol: "UDP"`.
|
|
113
122
|
*/
|
|
114
123
|
export function collectorEndpoints(config: CollectorConfig): CollectorEndpoints {
|
|
115
124
|
const ports: CollectorPort[] = [];
|
|
116
|
-
const seen = new Set<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
for (const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
const seen = new Set<string>();
|
|
126
|
+
const sections: Array<["receiver" | "exporter", Record<string, unknown> | undefined]> = [
|
|
127
|
+
["receiver", config.receivers],
|
|
128
|
+
["exporter", config.exporters],
|
|
129
|
+
];
|
|
130
|
+
for (const [kind, section] of sections) {
|
|
131
|
+
for (const [id, body] of Object.entries(section ?? {})) {
|
|
132
|
+
const parsed = parseComponentId(id);
|
|
133
|
+
const base = parsed ? [parsed.type, ...(parsed.name ? [parsed.name] : [])] : [id];
|
|
134
|
+
for (const { path, transport } of listenerPaths(kind, parsed?.type ?? id, body)) {
|
|
135
|
+
const p = portOf(valueAt(body, path));
|
|
136
|
+
if (!p || seen.has(`${p.port}/${transport}`)) continue;
|
|
137
|
+
seen.add(`${p.port}/${transport}`);
|
|
138
|
+
const protocol = path.slice(0, -1).filter((k) => k !== "protocols");
|
|
139
|
+
ports.push({ name: portName([...base, ...protocol]), port: p.port, ...(transport === "udp" ? { protocol: "UDP" as const } : {}) });
|
|
140
|
+
}
|
|
128
141
|
}
|
|
129
142
|
}
|
|
130
143
|
|
package/src/plugin.test.ts
CHANGED
|
@@ -34,6 +34,16 @@ describe("otel plugin", () => {
|
|
|
34
34
|
"OTEL115",
|
|
35
35
|
"OTEL116",
|
|
36
36
|
"OTEL117",
|
|
37
|
+
"OTEL118",
|
|
38
|
+
"OTEL119",
|
|
39
|
+
"OTEL120",
|
|
40
|
+
"OTEL121",
|
|
41
|
+
"OTEL122",
|
|
42
|
+
"OTEL123",
|
|
43
|
+
"OTEL124",
|
|
44
|
+
"OTEL125",
|
|
45
|
+
"OTEL126",
|
|
46
|
+
"OTEL127",
|
|
37
47
|
]);
|
|
38
48
|
for (const id of ids) expect(id.startsWith("OTEL")).toBe(true);
|
|
39
49
|
});
|
package/src/semconv.ts
CHANGED
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
* A collector config has no metadata channel, so a config cannot say which
|
|
6
6
|
* semconv version its attribute keys follow. What it can show is that it uses
|
|
7
7
|
* a vocabulary: a `spanmetrics` dimension named `gen_ai.request.model`, an
|
|
8
|
-
* OTTL statement deleting `gen_ai.input.messages
|
|
8
|
+
* OTTL statement deleting `gen_ai.input.messages`, a `k8sattributes`
|
|
9
|
+
* processor extracting `k8s.pod.name`. `semconvUsage()` finds
|
|
9
10
|
* those and pairs each vocabulary with the pin this package's keys follow, the
|
|
10
11
|
* same way `collectorTopology()` pairs a component type with the pin of the
|
|
11
12
|
* definition this process has. Reading a parsed YAML file and reading the
|
|
12
13
|
* declaration give the same answer.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
|
-
import { GENAI_SEMCONV_PIN, type SchemaPin } from "./define";
|
|
16
|
+
import { GENAI_SEMCONV_PIN, SEMCONV_PIN, type SchemaPin } from "./define";
|
|
16
17
|
import { SECTION_OF, type CollectorConfig, type ComponentKind } from "./model";
|
|
17
18
|
|
|
18
19
|
/** One attribute vocabulary and the version of its conventions this package follows. */
|
|
@@ -26,9 +27,12 @@ export interface SemconvVocabulary {
|
|
|
26
27
|
|
|
27
28
|
// `gen_ai.` as a key, in OTTL (`attributes["gen_ai.x"]`) or in an RE2 pattern (`gen_ai\.`).
|
|
28
29
|
const GEN_AI_REF = /(^|[^A-Za-z0-9_])gen_ai(\\\\|\\)?\./;
|
|
30
|
+
// `k8s.` with the dot, so the component types `k8s_cluster` and `k8sattributes` don't count.
|
|
31
|
+
const K8S_REF = /(^|[^A-Za-z0-9_])k8s(\\\\|\\)?\./;
|
|
29
32
|
|
|
30
33
|
export const SEMCONV_VOCABULARIES: ReadonlyArray<SemconvVocabulary> = Object.freeze([
|
|
31
34
|
{ namespace: "gen_ai", pin: GENAI_SEMCONV_PIN, matches: (text: string) => GEN_AI_REF.test(text) },
|
|
35
|
+
{ namespace: "k8s", pin: SEMCONV_PIN, matches: (text: string) => K8S_REF.test(text) },
|
|
32
36
|
]);
|
|
33
37
|
|
|
34
38
|
/** A vocabulary a config uses, with the components that use it. */
|
package/src/skills/chant-otel.md
CHANGED
|
@@ -17,7 +17,7 @@ export default { lexicons: ["otel"] };
|
|
|
17
17
|
|
|
18
18
|
`chant init --lexicon otel` scaffolds a collector; `--template k8s-agent` scaffolds a per-node Kubernetes agent and `--template genai` a GenAI collector.
|
|
19
19
|
|
|
20
|
-
For a per-node Kubernetes agent, `NodeAgent({ exporters, metricExporters?, clusterName?, kubeletStats? })` declares the whole config: OTLP, host metrics, container logs, `k8sattributes` filtered to the node, and `memory_limiter` first and `batch` last on every pipeline. Pass `Object.values(agent.members)` wherever an entity list is taken, such as the k8s lexicon's `OtelCollector` `config
|
|
20
|
+
For a per-node Kubernetes agent, `NodeAgent({ exporters, metricExporters?, clusterName?, kubeletStats? })` declares the whole config: OTLP, host metrics, container logs, `k8sattributes` filtered to the node, and `memory_limiter` first and `batch` last on every pipeline. Pass `Object.values(agent.members)` wherever an entity list is taken, such as the k8s lexicon's `OtelCollector` `config`, which adds what the config reads from the node: `K8S_NODE_NAME` from `spec.nodeName`, the host root at `/hostfs` and `/var/log/pods` read-only, group 0 for the log files, and the RBAC.
|
|
21
21
|
|
|
22
22
|
## Declaring components
|
|
23
23
|
|
|
@@ -78,10 +78,14 @@ It deletes prompt, completion, system-instruction and tool-call content from spa
|
|
|
78
78
|
|
|
79
79
|
- Reference components by entity where you can. A string id (`"otlp/backend"`) is allowed for a component declared elsewhere, and OTEL101 fails the build if nothing declares it.
|
|
80
80
|
- Put `memory_limiter` first in `processors` (OTEL105).
|
|
81
|
-
- Never write a credential literally. Use `"${env:NAME}"` and the collector reads it at start-up (OTEL002).
|
|
81
|
+
- Never write a credential literally. Use `"${env:NAME}"` and the collector reads it at start-up (OTEL002 in source, OTEL120 in the config). Don't send one over `http://` or `tls.insecure: true` (OTEL121).
|
|
82
82
|
- Every pipeline needs at least one receiver and one exporter (OTEL102). A declared component no pipeline uses is a warning (OTEL103).
|
|
83
83
|
- Don't split metrics by a per-request id (`gen_ai.conversation.id`, `gen_ai.response.id`, `gen_ai.tool.call.id`, `session.id`, `user.id`, `enduser.id`) or a content key: each value starts new time series (OTEL116).
|
|
84
84
|
- Give every listener its own port. The collector's own metrics already use localhost:8888, so a `prometheus` exporter there makes it exit with "address already in use" (OTEL117).
|
|
85
|
+
- Keep `zpages` and `pprof` on localhost (OTEL122). `health_check` may listen on 0.0.0.0 for probes.
|
|
86
|
+
- Put `batch` in every pipeline that sends to a remote `otlp` or `otlphttp` exporter (OTEL125), and leave the exporter's `sending_queue` and `retry_on_failure` on (OTEL124).
|
|
87
|
+
- Use a `drop` policy rather than `invert_match`, and `readers` rather than `service.telemetry.metrics.address` (OTEL119).
|
|
88
|
+
- In a workspace, or with `telemetry.attribution: true`, leave `service.name`, `service.version`, `deployment.environment.name`, `vcs.ref.head.revision` and `chant.*` resource attributes alone: `insert` rather than `upsert`, and `override: false` on `resourcedetection` (OTEL118).
|
|
85
89
|
- Declared extensions are enabled in declaration order unless a `Service` lists `extensions` itself.
|
|
86
90
|
|
|
87
91
|
## Starting from an existing config
|
|
@@ -97,4 +101,4 @@ The importer writes `receivers.ts`, `processors.ts`, `exporters.ts`, `connectors
|
|
|
97
101
|
|
|
98
102
|
## Reading the result
|
|
99
103
|
|
|
100
|
-
`collectorTopologyOf(entities)` returns the pipelines, each component's endpoints and schema pin, for each exporter the signals it carries, the connector `edges` between pipelines, and under `semconv` the semantic-conventions version
|
|
104
|
+
`collectorTopologyOf(entities)` returns the pipelines, each component's endpoints and schema pin, for each exporter the signals it carries, the connector `edges` between pipelines, and under `semconv` the semantic-conventions version the config's `gen_ai.` keys (`GENAI_SEMCONV_PIN`) and `k8s.` keys (`SEMCONV_PIN`) follow, as plain data.
|
package/src/validate-config.ts
CHANGED
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
12
12
|
import { definitionFor, definitionOf, isOTelComponent, isUsablePin, runValidator } from "./define";
|
|
13
13
|
import { componentConfig } from "./collector";
|
|
14
|
-
import { isComponentId, parseComponentId, pipelineSignal, SIGNALS, type CollectorConfig, type ConnectorSignalPair } from "./model";
|
|
14
|
+
import { canonicalComponentType, isComponentId, parseComponentId, pipelineSignal, SIGNALS, type CollectorConfig, type ConnectorSignalPair } from "./model";
|
|
15
15
|
import { isPipelineEntity } from "./pipeline";
|
|
16
16
|
import { signalToMetricsEntries, type SignalToMetricsConnectorConfig } from "./components/connectors";
|
|
17
17
|
import { genAiCardinalityRisk } from "./genai";
|
|
18
18
|
import { collectorTopology, type TopologyEdge } from "./topology";
|
|
19
|
+
import { configHygieneIssues } from "./config-hygiene";
|
|
19
20
|
// OTEL112 reads the built-in connectors' signal pairs from the registry.
|
|
20
21
|
import "./components/connectors";
|
|
21
22
|
|
|
@@ -34,7 +35,17 @@ export type CollectorIssueCode =
|
|
|
34
35
|
| "OTEL114"
|
|
35
36
|
| "OTEL115"
|
|
36
37
|
| "OTEL116"
|
|
37
|
-
| "OTEL117"
|
|
38
|
+
| "OTEL117"
|
|
39
|
+
| "OTEL118"
|
|
40
|
+
| "OTEL119"
|
|
41
|
+
| "OTEL120"
|
|
42
|
+
| "OTEL121"
|
|
43
|
+
| "OTEL122"
|
|
44
|
+
| "OTEL123"
|
|
45
|
+
| "OTEL124"
|
|
46
|
+
| "OTEL125"
|
|
47
|
+
| "OTEL126"
|
|
48
|
+
| "OTEL127";
|
|
38
49
|
|
|
39
50
|
export interface CollectorIssue {
|
|
40
51
|
code: CollectorIssueCode;
|
|
@@ -67,8 +78,9 @@ function describePairs(pairs: ReadonlyArray<ConnectorSignalPair>): string {
|
|
|
67
78
|
* each connector's signals against its definition (OTEL112), cycles through
|
|
68
79
|
* connectors (OTEL113), connector ids shared with a receiver or exporter
|
|
69
80
|
* (OTEL114), `routing` connector targets (OTEL115), the attributes
|
|
70
|
-
* connectors split metrics by (OTEL116),
|
|
71
|
-
* on the same address (OTEL117)
|
|
81
|
+
* connectors split metrics by (OTEL116), started components that listen
|
|
82
|
+
* on the same address (OTEL117), and the deprecation, exposure, delivery and
|
|
83
|
+
* name-list checks in `config-hygiene.ts` (OTEL119-OTEL127).
|
|
72
84
|
*/
|
|
73
85
|
export function validateCollectorConfig(config: CollectorConfig): CollectorIssue[] {
|
|
74
86
|
const issues: CollectorIssue[] = [];
|
|
@@ -229,6 +241,7 @@ export function validateCollectorConfig(config: CollectorConfig): CollectorIssue
|
|
|
229
241
|
...routingTargetIssues(config),
|
|
230
242
|
...metricAttributeIssues(config),
|
|
231
243
|
...listenerIssues(config),
|
|
244
|
+
...configHygieneIssues(config),
|
|
232
245
|
);
|
|
233
246
|
|
|
234
247
|
return issues;
|
|
@@ -449,7 +462,7 @@ function metricAttributeUses(type: string, config: Record<string, unknown>): Met
|
|
|
449
462
|
function metricAttributeIssues(config: CollectorConfig): CollectorIssue[] {
|
|
450
463
|
const issues: CollectorIssue[] = [];
|
|
451
464
|
for (const [id, raw] of Object.entries(config.connectors ?? {})) {
|
|
452
|
-
const type = parseComponentId(id)?.type ?? id;
|
|
465
|
+
const type = canonicalComponentType("connector", parseComponentId(id)?.type ?? id);
|
|
453
466
|
const body = typeof raw === "object" && raw !== null ? (raw as Record<string, unknown>) : {};
|
|
454
467
|
for (const { key, field } of metricAttributeUses(type, body)) {
|
|
455
468
|
const risk = genAiCardinalityRisk(key);
|
|
@@ -470,7 +483,7 @@ function metricAttributeIssues(config: CollectorConfig): CollectorIssue[] {
|
|
|
470
483
|
}
|
|
471
484
|
|
|
472
485
|
/** One address a started component listens on. */
|
|
473
|
-
interface Listener {
|
|
486
|
+
export interface Listener {
|
|
474
487
|
/** Who listens: `receiver "otlp" (protocols.grpc.endpoint)`. */
|
|
475
488
|
owner: string;
|
|
476
489
|
component?: string;
|
|
@@ -486,12 +499,14 @@ interface Listener {
|
|
|
486
499
|
* default applies only when the key's parent is in the config: an otlp or
|
|
487
500
|
* jaeger protocol that isn't listed doesn't start. A type not listed here is
|
|
488
501
|
* not checked, since its `endpoint` may be a server it connects to (the
|
|
489
|
-
* kubeletstats receiver's is the kubelet).
|
|
502
|
+
* kubeletstats receiver's is the kubelet). `transportKey` names a sibling key
|
|
503
|
+
* that switches the transport (`transport: udp` on carbon).
|
|
490
504
|
*/
|
|
491
505
|
interface ListenerSpec {
|
|
492
506
|
path: string[];
|
|
493
507
|
default?: string;
|
|
494
508
|
transport?: "udp";
|
|
509
|
+
transportKey?: string;
|
|
495
510
|
present?: boolean;
|
|
496
511
|
}
|
|
497
512
|
|
|
@@ -508,6 +523,38 @@ const LISTENERS: Record<"receiver" | "exporter" | "extension", Record<string, Li
|
|
|
508
523
|
{ path: ["protocols", "thrift_binary", "endpoint"], default: "localhost:6832", present: true, transport: "udp" },
|
|
509
524
|
{ path: ["protocols", "thrift_compact", "endpoint"], default: "localhost:6831", present: true, transport: "udp" },
|
|
510
525
|
],
|
|
526
|
+
opencensus: [{ path: ["endpoint"], default: "localhost:55678" }],
|
|
527
|
+
otelarrow: [{ path: ["protocols", "grpc", "endpoint"], default: "0.0.0.0:4317" }],
|
|
528
|
+
skywalking: [
|
|
529
|
+
{ path: ["protocols", "grpc", "endpoint"], default: "localhost:11800", present: true },
|
|
530
|
+
{ path: ["protocols", "http", "endpoint"], default: "localhost:12800", present: true },
|
|
531
|
+
],
|
|
532
|
+
loki: [
|
|
533
|
+
{ path: ["protocols", "grpc", "endpoint"], default: "localhost:3600", present: true },
|
|
534
|
+
{ path: ["protocols", "http", "endpoint"], default: "localhost:3500", present: true },
|
|
535
|
+
],
|
|
536
|
+
statsd: [{ path: ["endpoint"], default: "localhost:8125", transport: "udp", transportKey: "transport" }],
|
|
537
|
+
carbon: [{ path: ["endpoint"], default: "localhost:2003", transportKey: "transport" }],
|
|
538
|
+
awsxray: [{ path: ["endpoint"], default: "localhost:2000", transport: "udp", transportKey: "transport" }],
|
|
539
|
+
collectd: [{ path: ["endpoint"], default: "localhost:8081" }],
|
|
540
|
+
datadog: [{ path: ["endpoint"], default: "localhost:8126" }],
|
|
541
|
+
influxdb: [{ path: ["endpoint"], default: "localhost:8086" }],
|
|
542
|
+
signalfx: [{ path: ["endpoint"], default: "localhost:9943" }],
|
|
543
|
+
splunk_hec: [{ path: ["endpoint"], default: "localhost:8088" }],
|
|
544
|
+
sapm: [{ path: ["endpoint"], default: "localhost:7276" }],
|
|
545
|
+
wavefront: [{ path: ["endpoint"], default: "localhost:2003" }],
|
|
546
|
+
prometheusremotewrite: [{ path: ["endpoint"], default: "localhost:9090" }],
|
|
547
|
+
awsfirehose: [{ path: ["endpoint"], default: "localhost:4433" }],
|
|
548
|
+
faro: [{ path: ["endpoint"], default: "localhost:8080" }],
|
|
549
|
+
libhoney: [{ path: ["http", "endpoint"], default: "localhost:8080" }],
|
|
550
|
+
fluentforward: [{ path: ["endpoint"] }],
|
|
551
|
+
webhookevent: [{ path: ["endpoint"] }],
|
|
552
|
+
tcplog: [{ path: ["listen_address"] }],
|
|
553
|
+
udplog: [{ path: ["listen_address"], transport: "udp" }],
|
|
554
|
+
syslog: [
|
|
555
|
+
{ path: ["tcp", "listen_address"], present: true },
|
|
556
|
+
{ path: ["udp", "listen_address"], present: true, transport: "udp" },
|
|
557
|
+
],
|
|
511
558
|
},
|
|
512
559
|
exporter: {
|
|
513
560
|
prometheus: [{ path: ["endpoint"] }],
|
|
@@ -519,6 +566,34 @@ const LISTENERS: Record<"receiver" | "exporter" | "extension", Record<string, Li
|
|
|
519
566
|
},
|
|
520
567
|
};
|
|
521
568
|
|
|
569
|
+
/** A config key path a component listens on, and the transport it listens with. */
|
|
570
|
+
export interface ListenerPath {
|
|
571
|
+
path: string[];
|
|
572
|
+
transport: "tcp" | "udp";
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** The transport a spec listens with in this component config: its `transportKey` value, else its default. */
|
|
576
|
+
function specTransport(spec: ListenerSpec, body: unknown): "tcp" | "udp" {
|
|
577
|
+
if (spec.transportKey && typeof body === "object" && body !== null) {
|
|
578
|
+
const value = (body as Record<string, unknown>)[spec.transportKey];
|
|
579
|
+
if (typeof value === "string" && /^udp[46]?$/.test(value)) return "udp";
|
|
580
|
+
if (typeof value === "string" && /^tcp[46]?$/.test(value)) return "tcp";
|
|
581
|
+
}
|
|
582
|
+
return spec.transport ?? "tcp";
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* The config key paths to the addresses a receiver or exporter of this type
|
|
587
|
+
* listens on, from the table OTEL117 checks, with the transport each listens
|
|
588
|
+
* with in `body`. Empty for a type not in the table, whose `endpoint` may be
|
|
589
|
+
* a server it connects to. `collectorEndpoints` reads the ports from here, so
|
|
590
|
+
* the ports a platform composite publishes and the listeners OTEL117 compares
|
|
591
|
+
* are the same set.
|
|
592
|
+
*/
|
|
593
|
+
export function listenerPaths(kind: "receiver" | "exporter", type: string, body: unknown): ListenerPath[] {
|
|
594
|
+
return (LISTENERS[kind][type] ?? []).map((spec) => ({ path: spec.path, transport: specTransport(spec, body) }));
|
|
595
|
+
}
|
|
596
|
+
|
|
522
597
|
/** The collector's own metrics endpoint when `service.telemetry.metrics` names no reader. */
|
|
523
598
|
const DEFAULT_TELEMETRY_METRICS = { host: "localhost", port: 8888 };
|
|
524
599
|
|
|
@@ -540,7 +615,8 @@ function at(value: unknown, path: string[]): { found: boolean; value: unknown }
|
|
|
540
615
|
return { found: true, value: cur };
|
|
541
616
|
}
|
|
542
617
|
|
|
543
|
-
|
|
618
|
+
/** The addresses a component of this kind listens on, from the OTEL117 table and its defaults. */
|
|
619
|
+
export function componentListeners(kind: "receiver" | "exporter" | "extension", id: string, body: unknown): Listener[] {
|
|
544
620
|
const type = parseComponentId(id)?.type ?? id;
|
|
545
621
|
const out: Listener[] = [];
|
|
546
622
|
for (const spec of LISTENERS[kind][type] ?? []) {
|
|
@@ -548,7 +624,7 @@ function componentListeners(kind: "receiver" | "exporter" | "extension", id: str
|
|
|
548
624
|
const { found, value } = at(body, spec.path);
|
|
549
625
|
const address = splitAddress(found && value !== undefined && value !== null ? value : spec.default);
|
|
550
626
|
if (!address) continue;
|
|
551
|
-
out.push({ owner: `${kind} "${id}" (${spec.path.join(".")})`, component: id, ...address, transport: spec
|
|
627
|
+
out.push({ owner: `${kind} "${id}" (${spec.path.join(".")})`, component: id, ...address, transport: specTransport(spec, body) });
|
|
552
628
|
}
|
|
553
629
|
return out;
|
|
554
630
|
}
|