@intentius/chant-lexicon-otel 0.124.30 → 0.124.32

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.
Files changed (96) hide show
  1. package/dist/attribution.js +391 -0
  2. package/dist/catalog.js +42 -0
  3. package/dist/codegen/docs-cli.js +3 -0
  4. package/dist/codegen/docs.js +101 -0
  5. package/dist/codegen/generate-cli.js +7 -0
  6. package/dist/codegen/generate.js +43 -0
  7. package/dist/codegen/package.js +28 -0
  8. package/dist/collector.js +150 -0
  9. package/dist/components/common.js +50 -0
  10. package/dist/components/connectors.js +242 -0
  11. package/dist/components/exporters.js +57 -0
  12. package/dist/components/extensions.js +58 -0
  13. package/dist/components/filtering.js +106 -0
  14. package/dist/components/index.js +9 -0
  15. package/dist/components/k8s-receivers.js +36 -0
  16. package/dist/components/processors.js +103 -0
  17. package/dist/components/receivers.js +61 -0
  18. package/dist/components/sampling.js +257 -0
  19. package/dist/composites/catalog.js +368 -0
  20. package/dist/composites/genai-pipeline.js +28 -0
  21. package/dist/composites/index.js +8 -0
  22. package/dist/composites/node-agent.js +156 -0
  23. package/dist/composites/otlp-collector.js +57 -0
  24. package/dist/composites/red-metrics.js +95 -0
  25. package/dist/composites/tail-sampling-tier.js +132 -0
  26. package/dist/config-hygiene.js +464 -0
  27. package/dist/configmap.js +86 -0
  28. package/dist/define.js +204 -0
  29. package/dist/detect.js +22 -0
  30. package/dist/genai.js +569 -0
  31. package/dist/import/embedded.js +57 -0
  32. package/dist/import/generator.js +459 -0
  33. package/dist/import/parser.js +199 -0
  34. package/dist/index.js +34 -0
  35. package/dist/init-templates.js +119 -0
  36. package/dist/integrity.json +2 -2
  37. package/dist/lint/audit-catalog.js +49 -0
  38. package/dist/lint/post-synth/index.js +52 -0
  39. package/dist/lint/post-synth/otel-helpers.js +103 -0
  40. package/dist/lint/post-synth/otel101.js +13 -0
  41. package/dist/lint/post-synth/otel102.js +13 -0
  42. package/dist/lint/post-synth/otel103.js +13 -0
  43. package/dist/lint/post-synth/otel104.js +13 -0
  44. package/dist/lint/post-synth/otel105.js +13 -0
  45. package/dist/lint/post-synth/otel106.js +13 -0
  46. package/dist/lint/post-synth/otel107.js +13 -0
  47. package/dist/lint/post-synth/otel108.js +13 -0
  48. package/dist/lint/post-synth/otel109.js +13 -0
  49. package/dist/lint/post-synth/otel112.js +13 -0
  50. package/dist/lint/post-synth/otel113.js +13 -0
  51. package/dist/lint/post-synth/otel114.js +13 -0
  52. package/dist/lint/post-synth/otel115.js +13 -0
  53. package/dist/lint/post-synth/otel116.js +13 -0
  54. package/dist/lint/post-synth/otel117.js +13 -0
  55. package/dist/lint/post-synth/otel118.js +13 -0
  56. package/dist/lint/post-synth/otel119.js +13 -0
  57. package/dist/lint/post-synth/otel120.js +13 -0
  58. package/dist/lint/post-synth/otel121.js +13 -0
  59. package/dist/lint/post-synth/otel122.js +13 -0
  60. package/dist/lint/post-synth/otel123.js +13 -0
  61. package/dist/lint/post-synth/otel124.js +13 -0
  62. package/dist/lint/post-synth/otel125.js +13 -0
  63. package/dist/lint/post-synth/otel126.js +13 -0
  64. package/dist/lint/post-synth/otel127.js +13 -0
  65. package/dist/lint/rules/component-id-syntax.js +66 -0
  66. package/dist/lint/rules/index.js +6 -0
  67. package/dist/lint/rules/literal-credential.js +91 -0
  68. package/dist/lint/rules/otel-ast.js +30 -0
  69. package/dist/lsp/completions.js +11 -0
  70. package/dist/lsp/hover.js +23 -0
  71. package/dist/manifest.json +1 -1
  72. package/dist/metric-names.js +244 -0
  73. package/dist/model.js +90 -0
  74. package/dist/op/activities/collector-audit.js +213 -0
  75. package/dist/op/activities/collector-health.js +176 -0
  76. package/dist/op/activities/exec.js +16 -0
  77. package/dist/op/activities/github.js +110 -0
  78. package/dist/op/activities/index.js +16 -0
  79. package/dist/op/activities/otelcol.js +173 -0
  80. package/dist/op/activity-contracts.js +64 -0
  81. package/dist/op/audit-op.js +33 -0
  82. package/dist/op/builders.js +38 -0
  83. package/dist/package-cli.js +14 -0
  84. package/dist/pipeline.js +31 -0
  85. package/dist/platform.js +144 -0
  86. package/dist/plugin.js +126 -0
  87. package/dist/semconv.js +50 -0
  88. package/dist/serializer.js +26 -0
  89. package/dist/skill-defs.js +34 -0
  90. package/dist/topology.js +104 -0
  91. package/dist/validate-cli.js +7 -0
  92. package/dist/validate-config.js +706 -0
  93. package/dist/validate.js +85 -0
  94. package/dist/yaml.js +157 -0
  95. package/package.json +8 -8
  96. package/src/codegen/package.ts +2 -2
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Built-in Kubernetes receivers: k8s_cluster (cluster-level object metrics,
3
+ * one instance per cluster) and kubeletstats (node, pod and container
4
+ * resource metrics from each node's kubelet, one instance per node).
5
+ */
6
+ import { defineBuiltin } from "../define.js";
7
+ /**
8
+ * Cluster-level metrics and entity events from the Kubernetes API. Run one
9
+ * instance per cluster (a single-replica Deployment, or behind
10
+ * `k8s_leader_elector`); on every node it reports each object once per node.
11
+ */
12
+ export const K8sClusterReceiver = defineBuiltin({
13
+ kind: "receiver",
14
+ type: "k8s_cluster",
15
+ description: "Cluster-level metrics from the Kubernetes API; run one instance per cluster",
16
+ validate: (c) => c.distribution !== undefined && c.distribution !== "kubernetes" && c.distribution !== "openshift"
17
+ ? [`distribution "${String(c.distribution)}" is not kubernetes or openshift`]
18
+ : [],
19
+ });
20
+ /** Node, pod, container and volume resource metrics from the local kubelet. Run one instance per node. */
21
+ export const KubeletStatsReceiver = defineBuiltin({
22
+ kind: "receiver",
23
+ type: "kubeletstats",
24
+ description: "Node, pod and container resource metrics from the kubelet; run one instance per node",
25
+ validate: (c) => {
26
+ const problems = [];
27
+ if ((c.auth_type ?? "tls") === "tls" && (!c.cert_file || !c.key_file)) {
28
+ problems.push(`auth_type ${c.auth_type ? "tls" : "defaults to tls, which"} needs cert_file and key_file; in a pod set auth_type: serviceAccount`);
29
+ }
30
+ if (c.metric_groups !== undefined && c.metric_groups.length === 0) {
31
+ problems.push("metric_groups is empty, so no kubelet metric is collected");
32
+ }
33
+ return problems;
34
+ },
35
+ endpoints: (c) => (c.endpoint ? [c.endpoint] : []),
36
+ });
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Built-in processors: batch, memory_limiter, resource, attributes,
3
+ * k8sattributes, resourcedetection, deltatocumulative.
4
+ */
5
+ import { defineBuiltin } from "../define.js";
6
+ import { goDurationMs } from "./common.js";
7
+ /** Batches telemetry before export, to cut request count and compress better. */
8
+ export const BatchProcessor = defineBuiltin({
9
+ kind: "processor",
10
+ type: "batch",
11
+ description: "Groups telemetry into batches before export",
12
+ validate: (c) => c.send_batch_max_size !== undefined && c.send_batch_max_size !== 0 && c.send_batch_size !== undefined && c.send_batch_max_size < c.send_batch_size
13
+ ? [`send_batch_max_size (${c.send_batch_max_size}) is below send_batch_size (${c.send_batch_size}); the collector rejects this`]
14
+ : [],
15
+ });
16
+ /** Refuses data when the collector nears its memory limit. Belongs first in every pipeline. */
17
+ export const MemoryLimiterProcessor = defineBuiltin({
18
+ kind: "processor",
19
+ type: "memory_limiter",
20
+ description: "Refuses data when the collector nears a memory limit; runs first in a pipeline",
21
+ validate: (c) => {
22
+ const problems = [];
23
+ if (c.limit_mib === undefined && c.limit_percentage === undefined) {
24
+ problems.push("set limit_mib or limit_percentage; with neither the collector refuses to start");
25
+ }
26
+ if (c.limit_mib !== undefined && c.spike_limit_mib !== undefined && c.spike_limit_mib >= c.limit_mib) {
27
+ problems.push(`spike_limit_mib (${c.spike_limit_mib}) must be below limit_mib (${c.limit_mib})`);
28
+ }
29
+ if (c.limit_percentage !== undefined && (c.limit_percentage <= 0 || c.limit_percentage > 100)) {
30
+ problems.push(`limit_percentage (${c.limit_percentage}) must be between 1 and 100`);
31
+ }
32
+ return problems;
33
+ },
34
+ });
35
+ /** Inserts, updates or deletes resource attributes. */
36
+ export const ResourceProcessor = defineBuiltin({
37
+ kind: "processor",
38
+ type: "resource",
39
+ description: "Inserts, updates or deletes resource attributes",
40
+ validate: (c) => actionProblems(c.attributes, "attributes"),
41
+ });
42
+ /** Inserts, updates, hashes or deletes span, log and metric attributes. */
43
+ export const AttributesProcessor = defineBuiltin({
44
+ kind: "processor",
45
+ type: "attributes",
46
+ description: "Inserts, updates, hashes or deletes span, log and metric attributes",
47
+ validate: (c) => actionProblems(c.actions, "actions"),
48
+ });
49
+ function actionProblems(actions, field) {
50
+ if (!actions || actions.length === 0)
51
+ return [`${field} is empty, so the processor changes nothing`];
52
+ const problems = [];
53
+ actions.forEach((a, i) => {
54
+ if (a.action !== "extract" && !a.key)
55
+ problems.push(`${field}[${i}] (${a.action}) needs a key`);
56
+ if (a.action === "extract" && !a.pattern)
57
+ problems.push(`${field}[${i}] (extract) needs a pattern`);
58
+ if ((a.action === "insert" || a.action === "update" || a.action === "upsert") &&
59
+ a.value === undefined &&
60
+ a.from_attribute === undefined &&
61
+ a.from_context === undefined) {
62
+ problems.push(`${field}[${i}] (${a.action}) needs value, from_attribute or from_context`);
63
+ }
64
+ if (a.action === "convert" && !a.converted_type)
65
+ problems.push(`${field}[${i}] (convert) needs converted_type`);
66
+ });
67
+ return problems;
68
+ }
69
+ /** Adds Kubernetes pod, namespace and node metadata to telemetry. */
70
+ export const K8sAttributesProcessor = defineBuiltin({
71
+ kind: "processor",
72
+ type: "k8sattributes",
73
+ description: "Adds Kubernetes pod, namespace and node metadata to telemetry",
74
+ });
75
+ /** Detects the host's resource attributes (cloud, container, host) and adds them. */
76
+ export const ResourceDetectionProcessor = defineBuiltin({
77
+ kind: "processor",
78
+ type: "resourcedetection",
79
+ description: "Detects cloud, container and host resource attributes",
80
+ validate: (c) => (c.detectors?.length ? [] : ["detectors is empty, so nothing is detected"]),
81
+ });
82
+ /**
83
+ * Turns delta metrics into cumulative ones by keeping a running total per
84
+ * stream. Exporters that read only cumulative data, such as
85
+ * `prometheusremotewrite`, need it after connectors that emit deltas (`sum`,
86
+ * `count`, `signaltometrics`). Cumulative metrics pass through unchanged.
87
+ */
88
+ export const DeltaToCumulativeProcessor = defineBuiltin({
89
+ kind: "processor",
90
+ type: "deltatocumulative",
91
+ description: "Converts delta metrics to cumulative by keeping a running total per stream",
92
+ validate: (c) => {
93
+ // config.go Validate.
94
+ const problems = [];
95
+ const stale = goDurationMs(c.max_stale, 5 * 60000);
96
+ if (stale !== undefined && stale <= 0)
97
+ problems.push(`max_stale must be a positive duration (got ${JSON.stringify(c.max_stale)})`);
98
+ if (c.max_streams !== undefined && (!Number.isInteger(c.max_streams) || c.max_streams < 0)) {
99
+ problems.push(`max_streams must be a whole number, 0 or more (got ${c.max_streams})`);
100
+ }
101
+ return problems;
102
+ },
103
+ });
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Built-in receivers: otlp, prometheus, hostmetrics, filelog.
3
+ */
4
+ import { defineBuiltin } from "../define.js";
5
+ /** Receives OTLP over gRPC (default port 4317) and HTTP (default port 4318). */
6
+ export const OtlpReceiver = defineBuiltin({
7
+ kind: "receiver",
8
+ type: "otlp",
9
+ description: "Receives traces, metrics and logs over OTLP gRPC (4317) and HTTP (4318)",
10
+ validate: (c) => !c.protocols || (!("grpc" in c.protocols) && !("http" in c.protocols))
11
+ ? ["protocols enables neither grpc nor http, so the receiver listens on nothing"]
12
+ : [],
13
+ endpoints: (c) => {
14
+ const out = [];
15
+ if (c.protocols && "grpc" in c.protocols)
16
+ out.push(c.protocols.grpc?.endpoint ?? "localhost:4317");
17
+ if (c.protocols && "http" in c.protocols)
18
+ out.push(c.protocols.http?.endpoint ?? "localhost:4318");
19
+ return out;
20
+ },
21
+ // The HTTP receiver takes protobuf and JSON bodies on the same port.
22
+ protocols: (c) => [
23
+ ...(c.protocols && "grpc" in c.protocols ? ["grpc"] : []),
24
+ ...(c.protocols && "http" in c.protocols ? ["http/protobuf", "http/json"] : []),
25
+ ],
26
+ });
27
+ /** Scrapes Prometheus endpoints with Prometheus's own scrape config. */
28
+ export const PrometheusReceiver = defineBuiltin({
29
+ kind: "receiver",
30
+ type: "prometheus",
31
+ description: "Scrapes Prometheus endpoints using Prometheus scrape_configs",
32
+ validate: (c) => {
33
+ const problems = [];
34
+ const seen = new Set();
35
+ for (const job of c.config?.scrape_configs ?? []) {
36
+ if (seen.has(job.job_name))
37
+ problems.push(`scrape job "${job.job_name}" is declared twice`);
38
+ seen.add(job.job_name);
39
+ }
40
+ if ((c.config?.scrape_configs ?? []).length === 0 && !c.target_allocator) {
41
+ problems.push("config.scrape_configs is empty and no target_allocator is set, so nothing is scraped");
42
+ }
43
+ return problems;
44
+ },
45
+ endpoints: (c) => (c.config?.scrape_configs ?? []).flatMap((job) => (job.static_configs ?? []).flatMap((s) => s.targets)),
46
+ });
47
+ /** Scrapes CPU, memory, disk, filesystem, network and process metrics from the host. */
48
+ export const HostMetricsReceiver = defineBuiltin({
49
+ kind: "receiver",
50
+ type: "hostmetrics",
51
+ description: "Scrapes host metrics (cpu, memory, disk, filesystem, network, processes)",
52
+ validate: (c) => (Object.keys(c.scrapers ?? {}).length === 0 ? ["scrapers is empty, so no host metric is collected"] : []),
53
+ });
54
+ /** Tails log files and parses them with stanza operators. */
55
+ export const FileLogReceiver = defineBuiltin({
56
+ kind: "receiver",
57
+ type: "filelog",
58
+ description: "Tails log files and parses each line with stanza operators",
59
+ validate: (c) => (c.include?.length ? [] : ["include is empty, so no file is read"]),
60
+ endpoints: (c) => c.include ?? [],
61
+ });
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Built-in sampling components: the tail_sampling and probabilistic_sampler
3
+ * processors, and the loadbalancing exporter that routes every span of a trace
4
+ * to the same collector so tail sampling sees whole traces.
5
+ *
6
+ * Field shapes follow `processor/tailsamplingprocessor`,
7
+ * `processor/probabilisticsamplerprocessor` and
8
+ * `exporter/loadbalancingexporter` at `COLLECTOR_PIN`.
9
+ */
10
+ import { defineBuiltin } from "../define.js";
11
+ /**
12
+ * Holds each trace for `decision_wait`, then samples it when a policy says so.
13
+ * Tail sampling needs every span of a trace in one collector: run it on a
14
+ * gateway behind the `loadbalancing` exporter, not on a per-node agent.
15
+ */
16
+ export const TailSamplingProcessor = defineBuiltin({
17
+ kind: "processor",
18
+ type: "tail_sampling",
19
+ description: "Samples whole traces by policy after waiting for their spans: errors, latency, attributes, rate",
20
+ validate: (c) => {
21
+ const problems = [];
22
+ if (!c.decision_wait)
23
+ problems.push("decision_wait is not set; declare how long to wait for a trace's spans");
24
+ if (!Array.isArray(c.policies) || c.policies.length === 0) {
25
+ problems.push("policies is empty, so no trace is ever sampled");
26
+ }
27
+ else {
28
+ policyListProblems(c.policies, "policies", problems);
29
+ }
30
+ return problems;
31
+ },
32
+ });
33
+ const STATUS_CODES = new Set(["OK", "ERROR", "UNSET"]);
34
+ function policyListProblems(policies, path, problems) {
35
+ const seen = new Set();
36
+ policies.forEach((p, i) => {
37
+ const at = `${path}[${i}]`;
38
+ if (!p || typeof p !== "object") {
39
+ problems.push(`${at} is not a policy`);
40
+ return;
41
+ }
42
+ if (!p.name)
43
+ problems.push(`${at} has no name`);
44
+ else if (seen.has(p.name))
45
+ problems.push(`${at}: policy name "${p.name}" is repeated`);
46
+ else
47
+ seen.add(p.name);
48
+ const label = p.name ? `${at} (${p.name})` : at;
49
+ policyProblems(p, label, problems);
50
+ });
51
+ }
52
+ function policyProblems(p, at, problems) {
53
+ const cfg = p[p.type];
54
+ if (p.type !== "always_sample" && (typeof cfg !== "object" || cfg === null)) {
55
+ problems.push(`${at}: type ${p.type} needs a ${p.type} block`);
56
+ return;
57
+ }
58
+ switch (p.type) {
59
+ case "latency":
60
+ if (!(p.latency.threshold_ms > 0) && !(p.latency.upper_threshold_ms && p.latency.upper_threshold_ms > 0)) {
61
+ problems.push(`${at}: latency needs threshold_ms or upper_threshold_ms above 0`);
62
+ }
63
+ if (p.latency.upper_threshold_ms !== undefined && p.latency.upper_threshold_ms <= p.latency.threshold_ms) {
64
+ problems.push(`${at}: latency upper_threshold_ms must be above threshold_ms`);
65
+ }
66
+ break;
67
+ case "numeric_attribute":
68
+ if (!p.numeric_attribute.key)
69
+ problems.push(`${at}: numeric_attribute needs a key`);
70
+ if (p.numeric_attribute.min_value !== undefined &&
71
+ p.numeric_attribute.max_value !== undefined &&
72
+ p.numeric_attribute.max_value < p.numeric_attribute.min_value) {
73
+ problems.push(`${at}: numeric_attribute max_value is below min_value`);
74
+ }
75
+ break;
76
+ case "probabilistic": {
77
+ const pct = p.probabilistic.sampling_percentage;
78
+ if (typeof pct !== "number" || pct <= 0 || pct > 100) {
79
+ problems.push(`${at}: probabilistic sampling_percentage must be above 0 and at most 100`);
80
+ }
81
+ break;
82
+ }
83
+ case "status_code": {
84
+ const codes = p.status_code.status_codes ?? [];
85
+ if (codes.length === 0)
86
+ problems.push(`${at}: status_code needs at least one status code`);
87
+ for (const code of codes) {
88
+ if (!STATUS_CODES.has(code))
89
+ problems.push(`${at}: status code "${code}" is not OK, ERROR or UNSET`);
90
+ }
91
+ break;
92
+ }
93
+ case "string_attribute":
94
+ if (!p.string_attribute.key)
95
+ problems.push(`${at}: string_attribute needs a key`);
96
+ if (!p.string_attribute.values?.length)
97
+ problems.push(`${at}: string_attribute needs at least one value`);
98
+ break;
99
+ case "rate_limiting":
100
+ if (!(p.rate_limiting.spans_per_second > 0))
101
+ problems.push(`${at}: rate_limiting spans_per_second must be above 0`);
102
+ break;
103
+ case "span_count":
104
+ if (p.span_count.max_spans !== undefined && p.span_count.max_spans < p.span_count.min_spans) {
105
+ problems.push(`${at}: span_count max_spans is below min_spans`);
106
+ }
107
+ break;
108
+ case "trace_state":
109
+ if (!p.trace_state.key)
110
+ problems.push(`${at}: trace_state needs a key`);
111
+ if (!p.trace_state.values?.length)
112
+ problems.push(`${at}: trace_state needs at least one value`);
113
+ break;
114
+ case "boolean_attribute":
115
+ if (!p.boolean_attribute.key)
116
+ problems.push(`${at}: boolean_attribute needs a key`);
117
+ break;
118
+ case "ottl_condition":
119
+ if (!p.ottl_condition.span?.length && !p.ottl_condition.spanevent?.length) {
120
+ problems.push(`${at}: ottl_condition needs a span or spanevent condition`);
121
+ }
122
+ break;
123
+ case "and":
124
+ subPolicyProblems(p.and.and_sub_policy, `${at}.and.and_sub_policy`, problems);
125
+ break;
126
+ case "drop":
127
+ subPolicyProblems(p.drop.drop_sub_policy, `${at}.drop.drop_sub_policy`, problems);
128
+ break;
129
+ case "composite": {
130
+ const comp = p.composite;
131
+ if (!(comp.max_total_spans_per_second > 0))
132
+ problems.push(`${at}: composite max_total_spans_per_second must be above 0`);
133
+ const subs = comp.composite_sub_policy ?? [];
134
+ if (subs.length === 0) {
135
+ problems.push(`${at}: composite has no composite_sub_policy`);
136
+ break;
137
+ }
138
+ policyListProblems(subs, `${at}.composite.composite_sub_policy`, problems);
139
+ const names = new Set(subs.map((s) => s.name));
140
+ for (const n of comp.policy_order ?? []) {
141
+ if (!names.has(n))
142
+ problems.push(`${at}: policy_order names "${n}", which is not a composite_sub_policy`);
143
+ }
144
+ let total = 0;
145
+ for (const r of comp.rate_allocation ?? []) {
146
+ if (!names.has(r.policy))
147
+ problems.push(`${at}: rate_allocation names "${r.policy}", which is not a composite_sub_policy`);
148
+ total += r.percent;
149
+ }
150
+ if (total > 100)
151
+ problems.push(`${at}: rate_allocation percents add up to ${total}, above 100`);
152
+ break;
153
+ }
154
+ default:
155
+ break;
156
+ }
157
+ }
158
+ function subPolicyProblems(subs, path, problems) {
159
+ if (!subs || subs.length === 0) {
160
+ problems.push(`${path} is empty`);
161
+ return;
162
+ }
163
+ policyListProblems(subs, path, problems);
164
+ }
165
+ /** Head sampling: keeps a fixed percentage of traces or logs, decided per item without waiting for the rest of the trace. */
166
+ export const ProbabilisticSamplerProcessor = defineBuiltin({
167
+ kind: "processor",
168
+ type: "probabilistic_sampler",
169
+ description: "Head sampling: keeps a fixed percentage of traces or logs",
170
+ validate: (c) => {
171
+ const problems = [];
172
+ const pct = c.sampling_percentage;
173
+ if (typeof pct !== "number" || Number.isNaN(pct)) {
174
+ problems.push("sampling_percentage is not set; the collector's default of 0 keeps nothing");
175
+ }
176
+ else if (pct < 0 || pct > 100) {
177
+ problems.push(`sampling_percentage (${pct}) must be between 0 and 100`);
178
+ }
179
+ if (c.sampling_precision !== undefined && (c.sampling_precision < 1 || c.sampling_precision > 14)) {
180
+ problems.push(`sampling_precision (${c.sampling_precision}) must be between 1 and 14`);
181
+ }
182
+ if (c.attribute_source === "record" && !c.from_attribute) {
183
+ problems.push("attribute_source is record but from_attribute is not set");
184
+ }
185
+ if (c.hash_seed !== undefined && c.mode !== undefined && c.mode !== "hash_seed") {
186
+ problems.push(`hash_seed is only read in hash_seed mode, not ${c.mode}`);
187
+ }
188
+ return problems;
189
+ },
190
+ });
191
+ const RESOLVER_KEYS = ["static", "dns", "k8s", "aws_cloud_map"];
192
+ /**
193
+ * Sends each trace (or service, or metric stream) to one backend chosen by a
194
+ * consistent hash over the resolver's backends. Put it on the agents in front
195
+ * of a multi-replica gateway that runs `tail_sampling`.
196
+ */
197
+ export const LoadBalancingExporter = defineBuiltin({
198
+ kind: "exporter",
199
+ type: "loadbalancing",
200
+ description: "Routes all spans of a trace to one backend collector by trace id, for tail sampling behind it",
201
+ validate: (c) => {
202
+ const problems = [];
203
+ const r = (c.resolver ?? {});
204
+ const set = RESOLVER_KEYS.filter((k) => r[k] !== undefined && r[k] !== null);
205
+ if (set.length === 0)
206
+ problems.push("resolver has none of static, dns, k8s or aws_cloud_map");
207
+ if (set.length > 1)
208
+ problems.push(`resolver sets ${set.join(" and ")}; the collector takes exactly one`);
209
+ const res = c.resolver;
210
+ if (res?.static && !res.static.hostnames?.length)
211
+ problems.push("resolver.static.hostnames is empty");
212
+ if (res?.dns && !res.dns.hostname)
213
+ problems.push("resolver.dns.hostname is empty");
214
+ if (res?.k8s) {
215
+ if (!res.k8s.service)
216
+ problems.push("resolver.k8s.service is empty");
217
+ if (res.k8s.ports && res.k8s.ports.length === 0)
218
+ problems.push("resolver.k8s.ports is empty; omit it for the default 4317");
219
+ }
220
+ if (res?.aws_cloud_map && (!res.aws_cloud_map.namespace || !res.aws_cloud_map.service_name)) {
221
+ problems.push("resolver.aws_cloud_map needs namespace and service_name");
222
+ }
223
+ if (c.routing_key === "attributes" && !c.routing_attributes?.length) {
224
+ problems.push("routing_key is attributes but routing_attributes is empty");
225
+ }
226
+ if (c.routing_attributes?.length && c.routing_key !== "attributes") {
227
+ problems.push("routing_attributes is only read with routing_key: attributes");
228
+ }
229
+ const otlp = c.protocol?.otlp;
230
+ if (otlp && "endpoint" in otlp)
231
+ problems.push("protocol.otlp.endpoint is ignored; backends come from the resolver");
232
+ return problems;
233
+ },
234
+ endpoints: (c) => loadBalancingEndpoints(c.resolver),
235
+ });
236
+ /**
237
+ * The backends a `loadbalancing` resolver names, as `host:port` where it can:
238
+ * static hostnames as given, a DNS name with its port, a Kubernetes Service
239
+ * once per port, a Cloud Map service as `aws-cloud-map://namespace/service`.
240
+ */
241
+ export function loadBalancingEndpoints(resolver) {
242
+ if (!resolver)
243
+ return [];
244
+ if (resolver.static)
245
+ return [...(resolver.static.hostnames ?? [])];
246
+ if (resolver.dns)
247
+ return resolver.dns.hostname ? [`${resolver.dns.hostname}:${resolver.dns.port ?? "4317"}`] : [];
248
+ if (resolver.k8s) {
249
+ if (!resolver.k8s.service)
250
+ return [];
251
+ return (resolver.k8s.ports?.length ? resolver.k8s.ports : [4317]).map((p) => `${resolver.k8s.service}:${p}`);
252
+ }
253
+ if (resolver.aws_cloud_map) {
254
+ return [`aws-cloud-map://${resolver.aws_cloud_map.namespace}/${resolver.aws_cloud_map.service_name}`];
255
+ }
256
+ return [];
257
+ }