@intentius/chant-lexicon-otel 0.100.0 → 0.102.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 +8 -6
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/components/common.d.ts +2 -0
- package/dist/components/common.d.ts.map +1 -1
- package/dist/components/connectors.d.ts +109 -1
- package/dist/components/connectors.d.ts.map +1 -1
- package/dist/components/exporters.d.ts.map +1 -1
- package/dist/components/extensions.d.ts +26 -2
- package/dist/components/extensions.d.ts.map +1 -1
- package/dist/components/processors.d.ts +15 -2
- package/dist/components/processors.d.ts.map +1 -1
- package/dist/components/receivers.d.ts.map +1 -1
- package/dist/composites/catalog.d.ts +3 -0
- package/dist/composites/catalog.d.ts.map +1 -0
- package/dist/composites/index.d.ts +6 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/node-agent.d.ts +111 -0
- package/dist/composites/node-agent.d.ts.map +1 -0
- package/dist/configmap.d.ts +35 -0
- package/dist/configmap.d.ts.map +1 -0
- package/dist/define.d.ts +9 -0
- package/dist/define.d.ts.map +1 -1
- package/dist/genai.d.ts +145 -2
- package/dist/genai.d.ts.map +1 -1
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/init-templates.d.ts +24 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +10 -5
- package/dist/lint/audit-catalog.d.ts +1 -1
- 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 +32 -9
- package/dist/lint/post-synth/otel-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/otel113.d.ts +8 -0
- package/dist/lint/post-synth/otel113.d.ts.map +1 -0
- package/dist/lint/post-synth/otel114.d.ts +8 -0
- package/dist/lint/post-synth/otel114.d.ts.map +1 -0
- package/dist/lint/post-synth/otel115.d.ts +8 -0
- package/dist/lint/post-synth/otel115.d.ts.map +1 -0
- package/dist/lint/post-synth/otel116.d.ts +8 -0
- package/dist/lint/post-synth/otel116.d.ts.map +1 -0
- package/dist/lint/post-synth/otel117.d.ts +8 -0
- package/dist/lint/post-synth/otel117.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +15 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/okf/index.md +8 -0
- package/dist/okf/rules/OTEL113.md +11 -0
- package/dist/okf/rules/OTEL114.md +11 -0
- package/dist/okf/rules/OTEL115.md +11 -0
- package/dist/okf/rules/OTEL116.md +11 -0
- package/dist/okf/rules/OTEL117.md +11 -0
- package/dist/okf/types/DeltaToCumulativeProcessor.md +9 -0
- package/dist/okf/types/K8sLeaderElectorExtension.md +9 -0
- package/dist/okf/types/SignalToMetricsConnector.md +9 -0
- package/dist/platform.d.ts +6 -2
- package/dist/platform.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/otel-helpers.ts +43 -11
- package/dist/rules/otel113.ts +17 -0
- package/dist/rules/otel114.ts +17 -0
- package/dist/rules/otel115.ts +17 -0
- package/dist/rules/otel116.ts +17 -0
- package/dist/rules/otel117.ts +17 -0
- package/dist/skill-defs.d.ts +1 -1
- package/dist/skill-defs.d.ts.map +1 -1
- package/dist/skills/chant-otel.md +11 -5
- package/dist/topology.d.ts +5 -0
- package/dist/topology.d.ts.map +1 -1
- package/dist/validate-config.d.ts +14 -2
- package/dist/validate-config.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/codegen/docs.ts +9 -4
- package/src/components/common.ts +17 -0
- package/src/components/connectors.ts +209 -1
- package/src/components/exporters.ts +2 -0
- package/src/components/extensions.test.ts +70 -0
- package/src/components/extensions.ts +60 -2
- package/src/components/processors.test.ts +54 -0
- package/src/components/processors.ts +41 -2
- package/src/components/receivers.ts +5 -0
- package/src/composites/catalog.test.ts +38 -0
- package/src/composites/catalog.ts +81 -0
- package/src/composites/composites.test.ts +138 -0
- package/src/composites/index.ts +6 -0
- package/src/composites/node-agent.ts +220 -0
- package/src/configmap.ts +67 -0
- package/src/connectors.test.ts +197 -1
- package/src/define.ts +9 -0
- package/src/genai.test.ts +301 -6
- package/src/genai.ts +323 -8
- package/src/generated/lexicon-otel.json +15 -0
- package/src/import/roundtrip.test.ts +21 -1
- package/src/import/testdata/fixtures.ts +37 -5
- package/src/import/testdata/k8s-leader-elector.yaml +35 -0
- package/src/import/testdata/signaltometrics.yaml +83 -0
- package/src/index.ts +16 -0
- package/src/init-templates.test.ts +72 -0
- package/src/init-templates.ts +148 -0
- package/src/lint/audit-catalog.ts +41 -1
- package/src/lint/post-synth/connector-graph.test.ts +207 -0
- package/src/lint/post-synth/index.ts +10 -0
- package/src/lint/post-synth/otel-helpers.ts +43 -11
- package/src/lint/post-synth/otel113.ts +17 -0
- package/src/lint/post-synth/otel114.ts +17 -0
- package/src/lint/post-synth/otel115.ts +17 -0
- package/src/lint/post-synth/otel116.ts +17 -0
- package/src/lint/post-synth/otel117.ts +17 -0
- package/src/lint/post-synth/post-synth.test.ts +236 -1
- package/src/lint/post-synth/testdata/otel113-fail.yaml +23 -0
- package/src/lint/post-synth/testdata/otel113-pass.yaml +21 -0
- package/src/lint/post-synth/testdata/otel114-fail.yaml +25 -0
- package/src/lint/post-synth/testdata/otel114-pass.yaml +23 -0
- package/src/lint/post-synth/testdata/otel115-fail.yaml +29 -0
- package/src/lint/post-synth/testdata/otel115-pass.yaml +29 -0
- package/src/platform.test.ts +26 -0
- package/src/platform.ts +17 -13
- package/src/plugin.test.ts +18 -0
- package/src/plugin.ts +22 -0
- package/src/skills/chant-otel.md +11 -5
- package/src/testdata/genai-configured.yaml +108 -0
- package/src/testdata/genai-default.yaml +92 -0
- package/src/topology.test.ts +12 -0
- package/src/topology.ts +16 -0
- package/src/typecheck.test.ts +112 -0
- package/src/validate-config.ts +426 -2
- package/src/validate.ts +17 -0
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 { receiverListenerPaths } 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/, "")}`;
|
|
@@ -95,35 +96,38 @@ function portName(parts: string[]): string {
|
|
|
95
96
|
return name.slice(0, 15).replace(/-$/, "");
|
|
96
97
|
}
|
|
97
98
|
|
|
98
|
-
/**
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
for (const
|
|
102
|
-
if (
|
|
103
|
-
|
|
99
|
+
/** The value at a key path, if every key on the way is there. */
|
|
100
|
+
function valueAt(value: unknown, path: string[]): unknown {
|
|
101
|
+
let cur: unknown = value;
|
|
102
|
+
for (const key of path) {
|
|
103
|
+
if (!cur || typeof cur !== "object" || Array.isArray(cur)) return undefined;
|
|
104
|
+
cur = (cur as Record<string, unknown>)[key];
|
|
104
105
|
}
|
|
106
|
+
return cur;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
108
110
|
|
|
109
111
|
/**
|
|
110
|
-
* The ports a built collector config listens on:
|
|
112
|
+
* The ports a built collector config listens on: the endpoint of every
|
|
113
|
+
* receiver that listens (otlp, zipkin, jaeger; the receivers OTEL117 knows),
|
|
111
114
|
* named after the receiver and the protocol key above it (`otlp-grpc`,
|
|
112
|
-
* `otlp-http`), and the `health_check` port if the service enables it.
|
|
115
|
+
* `otlp-http`), and the `health_check` port if the service enables it. A
|
|
116
|
+
* receiver whose `endpoint` is a server it connects to, such as
|
|
117
|
+
* kubeletstats' kubelet, adds no port, and neither does a receiver type the
|
|
118
|
+
* table doesn't list.
|
|
113
119
|
*/
|
|
114
120
|
export function collectorEndpoints(config: CollectorConfig): CollectorEndpoints {
|
|
115
121
|
const ports: CollectorPort[] = [];
|
|
116
122
|
const seen = new Set<number>();
|
|
117
123
|
for (const [id, receiverConfig] of Object.entries(config.receivers ?? {})) {
|
|
118
|
-
const found: Array<{ path: string[]; endpoint: unknown }> = [];
|
|
119
|
-
receiverEndpoints(receiverConfig, [], found);
|
|
120
124
|
const parsed = parseComponentId(id);
|
|
121
125
|
const base = parsed ? [parsed.type, ...(parsed.name ? [parsed.name] : [])] : [id];
|
|
122
|
-
for (const
|
|
123
|
-
const p = portOf(
|
|
126
|
+
for (const path of receiverListenerPaths(parsed?.type ?? id)) {
|
|
127
|
+
const p = portOf(valueAt(receiverConfig, path));
|
|
124
128
|
if (!p || seen.has(p.port)) continue;
|
|
125
129
|
seen.add(p.port);
|
|
126
|
-
const protocol = path.filter((k) => k !== "protocols");
|
|
130
|
+
const protocol = path.slice(0, -1).filter((k) => k !== "protocols");
|
|
127
131
|
ports.push({ name: portName([...base, ...protocol]), port: p.port });
|
|
128
132
|
}
|
|
129
133
|
}
|
package/src/plugin.test.ts
CHANGED
|
@@ -29,6 +29,11 @@ describe("otel plugin", () => {
|
|
|
29
29
|
"OTEL108",
|
|
30
30
|
"OTEL109",
|
|
31
31
|
"OTEL112",
|
|
32
|
+
"OTEL113",
|
|
33
|
+
"OTEL114",
|
|
34
|
+
"OTEL115",
|
|
35
|
+
"OTEL116",
|
|
36
|
+
"OTEL117",
|
|
32
37
|
]);
|
|
33
38
|
for (const id of ids) expect(id.startsWith("OTEL")).toBe(true);
|
|
34
39
|
});
|
|
@@ -53,4 +58,17 @@ describe("otel plugin", () => {
|
|
|
53
58
|
expect(otelPlugin.mcpTools!().map((t) => t.name)).toEqual(["otel:diff"]);
|
|
54
59
|
expect(otelPlugin.mcpResources!().map((r) => r.uri)).toEqual(["otel:resource-catalog"]);
|
|
55
60
|
});
|
|
61
|
+
|
|
62
|
+
it("reports the collector topology for the graph, and nothing for a project without one", async () => {
|
|
63
|
+
const { OtlpReceiver, DebugExporter } = await import("./components");
|
|
64
|
+
const { Pipeline } = await import("./pipeline");
|
|
65
|
+
expect(otelPlugin.graphMeta!(new Map())).toBeUndefined();
|
|
66
|
+
expect(otelPlugin.graphMeta!(new Map([["other", { lexicon: "k8s", entityType: "Pod" } as never]]))).toBeUndefined();
|
|
67
|
+
const otlp = new OtlpReceiver({ protocols: { grpc: { endpoint: "0.0.0.0:4317" } } });
|
|
68
|
+
const debug = new DebugExporter({});
|
|
69
|
+
const meta = otelPlugin.graphMeta!(new Map<string, never>([["otlp", otlp as never], ["debug", debug as never], ["p", new Pipeline({ signal: "traces", receivers: [otlp], exporters: [debug] }) as never]]));
|
|
70
|
+
const collector = meta!.collector as { pipelines: { id: string }[]; exporters: { id: string; signals: string[] }[] };
|
|
71
|
+
expect(collector.pipelines.map((p) => p.id)).toEqual(["traces"]);
|
|
72
|
+
expect(collector.exporters.map((e) => [e.id, e.signals])).toEqual([["debug", ["traces"]]]);
|
|
73
|
+
});
|
|
56
74
|
});
|
package/src/plugin.ts
CHANGED
|
@@ -14,7 +14,10 @@ import { OtelCollectorGenerator } from "./import/generator";
|
|
|
14
14
|
import { collectorConfigImporter } from "./import/embedded";
|
|
15
15
|
import { otelSkills } from "./skill-defs";
|
|
16
16
|
import { BUILTIN_CATALOG } from "./catalog";
|
|
17
|
+
import { compositeCatalog } from "./composites/catalog";
|
|
18
|
+
import { DEFAULT_TEMPLATE, GENAI_TEMPLATE, K8S_AGENT_TEMPLATE } from "./init-templates";
|
|
17
19
|
import { COLLECTOR_PIN } from "./define";
|
|
20
|
+
import { collectorTopologyOf } from "./topology";
|
|
18
21
|
|
|
19
22
|
const catalogResource: McpResourceContribution = {
|
|
20
23
|
uri: "otel:resource-catalog",
|
|
@@ -77,6 +80,14 @@ export const otelPlugin: LexiconPlugin = {
|
|
|
77
80
|
return rules;
|
|
78
81
|
},
|
|
79
82
|
|
|
83
|
+
// Where this project's telemetry goes, for `chant workspace graph` (#2559).
|
|
84
|
+
// A project with no collector pipeline and no component reports nothing.
|
|
85
|
+
graphMeta(entities) {
|
|
86
|
+
const topology = collectorTopologyOf(entities.values());
|
|
87
|
+
if (topology.pipelines.length === 0 && topology.components.length === 0) return undefined;
|
|
88
|
+
return { collector: topology };
|
|
89
|
+
},
|
|
90
|
+
|
|
80
91
|
postSynthChecks() {
|
|
81
92
|
return postSynthChecks;
|
|
82
93
|
},
|
|
@@ -87,6 +98,10 @@ export const otelPlugin: LexiconPlugin = {
|
|
|
87
98
|
|
|
88
99
|
skills: otelSkills,
|
|
89
100
|
|
|
101
|
+
composites() {
|
|
102
|
+
return compositeCatalog;
|
|
103
|
+
},
|
|
104
|
+
|
|
90
105
|
mcpTools() {
|
|
91
106
|
return [createDiffTool(otelSerializer, "Compare current collector config output against the previous build", "otel")];
|
|
92
107
|
},
|
|
@@ -95,6 +110,13 @@ export const otelPlugin: LexiconPlugin = {
|
|
|
95
110
|
return [catalogResource];
|
|
96
111
|
},
|
|
97
112
|
|
|
113
|
+
// `chant init --lexicon otel [--template k8s-agent|genai]`; see ./init-templates.ts.
|
|
114
|
+
initTemplates(template?: string) {
|
|
115
|
+
if (template === "k8s-agent") return K8S_AGENT_TEMPLATE;
|
|
116
|
+
if (template === "genai") return GENAI_TEMPLATE;
|
|
117
|
+
return DEFAULT_TEMPLATE;
|
|
118
|
+
},
|
|
119
|
+
|
|
98
120
|
detectTemplate(data: unknown) {
|
|
99
121
|
return detectTemplate(data);
|
|
100
122
|
},
|
package/src/skills/chant-otel.md
CHANGED
|
@@ -15,6 +15,10 @@ The otel lexicon (`@intentius/chant-lexicon-otel`) types collector config. Each
|
|
|
15
15
|
export default { lexicons: ["otel"] };
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
`chant init --lexicon otel` scaffolds a collector; `--template k8s-agent` scaffolds a per-node Kubernetes agent and `--template genai` a GenAI collector.
|
|
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`, 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
|
+
|
|
18
22
|
## Declaring components
|
|
19
23
|
|
|
20
24
|
Every component class takes the collector's own config keys (snake_case, as in the collector docs) plus an optional `name`. The id is `type`, or `type/name` when `name` is set.
|
|
@@ -41,10 +45,10 @@ Emits `receivers.otlp`, `processors.memory_limiter` and `processors.batch`, `exp
|
|
|
41
45
|
| Kind | Types |
|
|
42
46
|
|---|---|
|
|
43
47
|
| receivers | otlp, prometheus, hostmetrics, filelog, k8s_cluster, kubeletstats |
|
|
44
|
-
| processors | batch, memory_limiter, resource, attributes, k8sattributes, resourcedetection, filter, transform, redaction, tail_sampling, probabilistic_sampler |
|
|
48
|
+
| processors | batch, memory_limiter, resource, attributes, k8sattributes, resourcedetection, filter, transform, redaction, tail_sampling, probabilistic_sampler, deltatocumulative |
|
|
45
49
|
| exporters | otlp, otlphttp, debug, prometheus, googlecloud, loadbalancing |
|
|
46
|
-
| connectors | spanmetrics, servicegraph, routing, forward, count, sum |
|
|
47
|
-
| extensions | health_check, pprof, zpages |
|
|
50
|
+
| connectors | spanmetrics, servicegraph, routing, forward, count, sum, signaltometrics |
|
|
51
|
+
| extensions | health_check, pprof, zpages, k8s_leader_elector |
|
|
48
52
|
|
|
49
53
|
Anything else goes through `defineComponent`; see the `chant-otel-custom-components` skill.
|
|
50
54
|
|
|
@@ -58,7 +62,7 @@ export const traces = new Pipeline({ signal: "traces", receivers: [otlp], export
|
|
|
58
62
|
export const red = new Pipeline({ signal: "metrics", name: "red", receivers: [spanmetrics], exporters: [prom] });
|
|
59
63
|
```
|
|
60
64
|
|
|
61
|
-
`spanmetrics` and `servicegraph` turn traces into metrics, `count` and `sum` turn any signal into metrics (`sum` adds up a numeric attribute), `routing` and `forward` keep the signal. A connector on one side only fails OTEL101; a pipeline whose signal the connector can't pair fails OTEL112.
|
|
65
|
+
`spanmetrics` and `servicegraph` turn traces into metrics, `count` and `sum` turn any signal into metrics (`sum` adds up a numeric attribute), `signaltometrics` builds metrics you name, of any type, from any signal with OTTL values, `routing` and `forward` keep the signal. A connector on one side only fails OTEL101; a pipeline whose signal the connector can't pair fails OTEL112. Pipelines that feed each other in a loop through connectors fail OTEL113, a connector id that is also a receiver or exporter id fails OTEL114, and every pipeline a `routing` connector routes to must list it in `receivers` (OTEL115).
|
|
62
66
|
|
|
63
67
|
## GenAI workloads
|
|
64
68
|
|
|
@@ -68,7 +72,7 @@ For services that emit GenAI spans, start from the preset instead of writing the
|
|
|
68
72
|
export const collector = genAiPipeline({ traceExporters: [tempo], metricExporters: [prom] });
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
It deletes prompt, completion, system-instruction and tool-call content from spans, span events and logs, and derives call, error, duration and token metrics (`genai_calls_total`, `genai_duration_seconds`, `genai_tokens_input_total`, `genai_tokens_output_total`) from every GenAI span before sampling. Keep content only when asked, with `keepContent: true`. `genAiComponents()` returns the pieces for hand-built pipelines.
|
|
75
|
+
It deletes prompt, completion, system-instruction and tool-call content from spans, span events and logs, and derives call, error, duration and token metrics (`genai_calls_total`, `genai_duration_seconds`, `genai_tokens_input_total`, `genai_tokens_output_total`) from every GenAI span before sampling. Keep content only when asked, with `keepContent: true`. When a dashboard or backend expects the semantic conventions' names, add `clientMetrics: "derive"` for `gen_ai.client.operation.duration` and `gen_ai.client.token.usage` from spans, or `"passthrough"` when the SDK already records them; never both. Read metric names from `genAiMetrics(options)` (`.client` for the conventions' ones) instead of writing them out. `genAiComponents()` returns the pieces for hand-built pipelines. The token sums and derived client metrics are deltas: `deltaToCumulative: "auto"` puts a `deltatocumulative` processor in front of any metric exporter other than `prometheus` or `debug` (the default with `clientMetrics: "derive"`), `true` always, `false` never.
|
|
72
76
|
|
|
73
77
|
## Rules
|
|
74
78
|
|
|
@@ -76,6 +80,8 @@ It deletes prompt, completion, system-instruction and tool-call content from spa
|
|
|
76
80
|
- Put `memory_limiter` first in `processors` (OTEL105).
|
|
77
81
|
- Never write a credential literally. Use `"${env:NAME}"` and the collector reads it at start-up (OTEL002).
|
|
78
82
|
- Every pipeline needs at least one receiver and one exporter (OTEL102). A declared component no pipeline uses is a warning (OTEL103).
|
|
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
|
+
- 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).
|
|
79
85
|
- Declared extensions are enabled in declaration order unless a `Service` lists `extensions` itself.
|
|
80
86
|
|
|
81
87
|
## Starting from an existing config
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# chant: semconv gen_ai github.com/open-telemetry/semantic-conventions@v1.41.1 (transform/genai_content, redaction/genai_content, filter/genai_spans, spanmetrics/genai, sum/genai_tokens)
|
|
2
|
+
|
|
3
|
+
receivers:
|
|
4
|
+
otlp:
|
|
5
|
+
protocols:
|
|
6
|
+
grpc:
|
|
7
|
+
endpoint: 0.0.0.0:4317
|
|
8
|
+
http:
|
|
9
|
+
endpoint: 0.0.0.0:4318
|
|
10
|
+
|
|
11
|
+
processors:
|
|
12
|
+
memory_limiter:
|
|
13
|
+
check_interval: 1s
|
|
14
|
+
limit_percentage: 80
|
|
15
|
+
spike_limit_percentage: 20
|
|
16
|
+
transform/genai_content:
|
|
17
|
+
error_mode: ignore
|
|
18
|
+
trace_statements:
|
|
19
|
+
- context: span
|
|
20
|
+
statements: ["delete_key(span.attributes, \"gen_ai.system_instructions\")", "delete_key(span.attributes, \"gen_ai.input.messages\")", "delete_key(span.attributes, \"gen_ai.output.messages\")", "delete_key(span.attributes, \"gen_ai.tool.call.arguments\")", "delete_key(span.attributes, \"gen_ai.tool.call.result\")", "delete_key(span.attributes, \"gen_ai.retrieval.query.text\")", "delete_key(span.attributes, \"gen_ai.retrieval.documents\")", "delete_key(span.attributes, \"gen_ai.prompt\")", "delete_key(span.attributes, \"gen_ai.completion\")", "delete_matching_keys(span.attributes, \"^gen_ai\\\\.(prompt|completion)\\\\.[0-9]+\\\\..+$\")"]
|
|
21
|
+
- context: spanevent
|
|
22
|
+
statements: ["delete_key(spanevent.attributes, \"gen_ai.system_instructions\")", "delete_key(spanevent.attributes, \"gen_ai.input.messages\")", "delete_key(spanevent.attributes, \"gen_ai.output.messages\")", "delete_key(spanevent.attributes, \"gen_ai.tool.call.arguments\")", "delete_key(spanevent.attributes, \"gen_ai.tool.call.result\")", "delete_key(spanevent.attributes, \"gen_ai.retrieval.query.text\")", "delete_key(spanevent.attributes, \"gen_ai.retrieval.documents\")", "delete_key(spanevent.attributes, \"gen_ai.prompt\")", "delete_key(spanevent.attributes, \"gen_ai.completion\")", "delete_matching_keys(spanevent.attributes, \"^gen_ai\\\\.(prompt|completion)\\\\.[0-9]+\\\\..+$\")"]
|
|
23
|
+
log_statements:
|
|
24
|
+
- context: log
|
|
25
|
+
statements: ["delete_key(log.attributes, \"gen_ai.system_instructions\")", "delete_key(log.attributes, \"gen_ai.input.messages\")", "delete_key(log.attributes, \"gen_ai.output.messages\")", "delete_key(log.attributes, \"gen_ai.tool.call.arguments\")", "delete_key(log.attributes, \"gen_ai.tool.call.result\")", "delete_key(log.attributes, \"gen_ai.retrieval.query.text\")", "delete_key(log.attributes, \"gen_ai.retrieval.documents\")", "delete_key(log.attributes, \"gen_ai.prompt\")", "delete_key(log.attributes, \"gen_ai.completion\")", "delete_matching_keys(log.attributes, \"^gen_ai\\\\.(prompt|completion)\\\\.[0-9]+\\\\..+$\")", "delete_matching_keys(log.body, \"^(content|message|tool_calls)$\") where IsMap(log.body) and (IsMatch(log.event_name, \"^(gen_ai\\\\.system\\\\.message|gen_ai\\\\.user\\\\.message|gen_ai\\\\.assistant\\\\.message|gen_ai\\\\.tool\\\\.message|gen_ai\\\\.choice)$\") or IsMatch(log.attributes[\"event.name\"], \"^(gen_ai\\\\.system\\\\.message|gen_ai\\\\.user\\\\.message|gen_ai\\\\.assistant\\\\.message|gen_ai\\\\.tool\\\\.message|gen_ai\\\\.choice)$\"))", "set(log.body, \"\") where IsString(log.body) and (IsMatch(log.event_name, \"^(gen_ai\\\\.system\\\\.message|gen_ai\\\\.user\\\\.message|gen_ai\\\\.assistant\\\\.message|gen_ai\\\\.tool\\\\.message|gen_ai\\\\.choice)$\") or IsMatch(log.attributes[\"event.name\"], \"^(gen_ai\\\\.system\\\\.message|gen_ai\\\\.user\\\\.message|gen_ai\\\\.assistant\\\\.message|gen_ai\\\\.tool\\\\.message|gen_ai\\\\.choice)$\"))"]
|
|
26
|
+
redaction/genai_content:
|
|
27
|
+
allow_all_keys: true
|
|
28
|
+
blocked_key_patterns: [^(gen_ai\.system_instructions|gen_ai\.input\.messages|gen_ai\.output\.messages|gen_ai\.tool\.call\.arguments|gen_ai\.tool\.call\.result|gen_ai\.retrieval\.query\.text|gen_ai\.retrieval\.documents|gen_ai\.prompt|gen_ai\.completion)$, "^gen_ai\\.(prompt|completion)\\.[0-9]+\\..+$"]
|
|
29
|
+
blocked_values: ["secret-[a-z]+"]
|
|
30
|
+
filter/sampler:
|
|
31
|
+
traces:
|
|
32
|
+
span: [status.code != STATUS_CODE_ERROR]
|
|
33
|
+
batch: {}
|
|
34
|
+
filter/genai_spans:
|
|
35
|
+
error_mode: ignore
|
|
36
|
+
traces:
|
|
37
|
+
span: ["attributes[\"gen_ai.operation.name\"] == nil"]
|
|
38
|
+
|
|
39
|
+
exporters:
|
|
40
|
+
otlp/tempo:
|
|
41
|
+
endpoint: tempo:4317
|
|
42
|
+
tls:
|
|
43
|
+
insecure: true
|
|
44
|
+
prometheus:
|
|
45
|
+
endpoint: 0.0.0.0:8889
|
|
46
|
+
debug:
|
|
47
|
+
verbosity: basic
|
|
48
|
+
|
|
49
|
+
connectors:
|
|
50
|
+
forward/genai: {}
|
|
51
|
+
forward/sampled: {}
|
|
52
|
+
spanmetrics/genai:
|
|
53
|
+
namespace: agents.support
|
|
54
|
+
dimensions:
|
|
55
|
+
- name: gen_ai.operation.name
|
|
56
|
+
- name: gen_ai.request.model
|
|
57
|
+
- name: gen_ai.tool.name
|
|
58
|
+
- name: error.type
|
|
59
|
+
- name: gen_ai.agent.name
|
|
60
|
+
histogram:
|
|
61
|
+
unit: s
|
|
62
|
+
explicit:
|
|
63
|
+
buckets: [100ms, 250ms, 500ms, 1s, 2s, 5s, 10s, 20s, 40s, 80s]
|
|
64
|
+
metrics_flush_interval: 15s
|
|
65
|
+
sum/genai_tokens:
|
|
66
|
+
spans:
|
|
67
|
+
agents.support.tokens.input:
|
|
68
|
+
source_attribute: gen_ai.usage.input_tokens
|
|
69
|
+
description: Input tokens used by GenAI operations
|
|
70
|
+
conditions: ["attributes[\"gen_ai.usage.input_tokens\"] != nil and not (kind == SPAN_KIND_INTERNAL and (attributes[\"gen_ai.operation.name\"] == \"invoke_agent\" or attributes[\"gen_ai.operation.name\"] == \"invoke_workflow\"))"]
|
|
71
|
+
attributes:
|
|
72
|
+
- key: gen_ai.request.model
|
|
73
|
+
default_value: unknown
|
|
74
|
+
agents.support.tokens.output:
|
|
75
|
+
source_attribute: gen_ai.usage.output_tokens
|
|
76
|
+
description: Output tokens used by GenAI operations
|
|
77
|
+
conditions: ["attributes[\"gen_ai.usage.output_tokens\"] != nil and not (kind == SPAN_KIND_INTERNAL and (attributes[\"gen_ai.operation.name\"] == \"invoke_agent\" or attributes[\"gen_ai.operation.name\"] == \"invoke_workflow\"))"]
|
|
78
|
+
attributes:
|
|
79
|
+
- key: gen_ai.request.model
|
|
80
|
+
default_value: unknown
|
|
81
|
+
|
|
82
|
+
extensions:
|
|
83
|
+
health_check:
|
|
84
|
+
endpoint: 0.0.0.0:13133
|
|
85
|
+
|
|
86
|
+
service:
|
|
87
|
+
extensions: [health_check]
|
|
88
|
+
pipelines:
|
|
89
|
+
traces:
|
|
90
|
+
receivers: [otlp]
|
|
91
|
+
processors: [memory_limiter, transform/genai_content, redaction/genai_content]
|
|
92
|
+
exporters: [forward/genai, forward/sampled]
|
|
93
|
+
traces/sampled:
|
|
94
|
+
receivers: [forward/sampled]
|
|
95
|
+
processors: [filter/sampler, batch]
|
|
96
|
+
exporters: [otlp/tempo]
|
|
97
|
+
traces/genai:
|
|
98
|
+
receivers: [forward/genai]
|
|
99
|
+
processors: [filter/genai_spans]
|
|
100
|
+
exporters: [spanmetrics/genai, sum/genai_tokens]
|
|
101
|
+
metrics/genai:
|
|
102
|
+
receivers: [spanmetrics/genai, sum/genai_tokens]
|
|
103
|
+
processors: [batch]
|
|
104
|
+
exporters: [prometheus]
|
|
105
|
+
logs:
|
|
106
|
+
receivers: [otlp]
|
|
107
|
+
processors: [memory_limiter, transform/genai_content, redaction/genai_content, batch]
|
|
108
|
+
exporters: [debug]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# chant: semconv gen_ai github.com/open-telemetry/semantic-conventions@v1.41.1 (transform/genai_content, redaction/genai_content, filter/genai_spans, spanmetrics/genai, sum/genai_tokens)
|
|
2
|
+
|
|
3
|
+
receivers:
|
|
4
|
+
otlp:
|
|
5
|
+
protocols:
|
|
6
|
+
grpc:
|
|
7
|
+
endpoint: 0.0.0.0:4317
|
|
8
|
+
http:
|
|
9
|
+
endpoint: 0.0.0.0:4318
|
|
10
|
+
|
|
11
|
+
processors:
|
|
12
|
+
memory_limiter:
|
|
13
|
+
check_interval: 1s
|
|
14
|
+
limit_percentage: 80
|
|
15
|
+
spike_limit_percentage: 20
|
|
16
|
+
transform/genai_content:
|
|
17
|
+
error_mode: ignore
|
|
18
|
+
trace_statements:
|
|
19
|
+
- context: span
|
|
20
|
+
statements: ["delete_key(span.attributes, \"gen_ai.system_instructions\")", "delete_key(span.attributes, \"gen_ai.input.messages\")", "delete_key(span.attributes, \"gen_ai.output.messages\")", "delete_key(span.attributes, \"gen_ai.tool.call.arguments\")", "delete_key(span.attributes, \"gen_ai.tool.call.result\")", "delete_key(span.attributes, \"gen_ai.retrieval.query.text\")", "delete_key(span.attributes, \"gen_ai.retrieval.documents\")", "delete_key(span.attributes, \"gen_ai.prompt\")", "delete_key(span.attributes, \"gen_ai.completion\")", "delete_matching_keys(span.attributes, \"^gen_ai\\\\.(prompt|completion)\\\\.[0-9]+\\\\..+$\")"]
|
|
21
|
+
- context: spanevent
|
|
22
|
+
statements: ["delete_key(spanevent.attributes, \"gen_ai.system_instructions\")", "delete_key(spanevent.attributes, \"gen_ai.input.messages\")", "delete_key(spanevent.attributes, \"gen_ai.output.messages\")", "delete_key(spanevent.attributes, \"gen_ai.tool.call.arguments\")", "delete_key(spanevent.attributes, \"gen_ai.tool.call.result\")", "delete_key(spanevent.attributes, \"gen_ai.retrieval.query.text\")", "delete_key(spanevent.attributes, \"gen_ai.retrieval.documents\")", "delete_key(spanevent.attributes, \"gen_ai.prompt\")", "delete_key(spanevent.attributes, \"gen_ai.completion\")", "delete_matching_keys(spanevent.attributes, \"^gen_ai\\\\.(prompt|completion)\\\\.[0-9]+\\\\..+$\")"]
|
|
23
|
+
log_statements:
|
|
24
|
+
- context: log
|
|
25
|
+
statements: ["delete_key(log.attributes, \"gen_ai.system_instructions\")", "delete_key(log.attributes, \"gen_ai.input.messages\")", "delete_key(log.attributes, \"gen_ai.output.messages\")", "delete_key(log.attributes, \"gen_ai.tool.call.arguments\")", "delete_key(log.attributes, \"gen_ai.tool.call.result\")", "delete_key(log.attributes, \"gen_ai.retrieval.query.text\")", "delete_key(log.attributes, \"gen_ai.retrieval.documents\")", "delete_key(log.attributes, \"gen_ai.prompt\")", "delete_key(log.attributes, \"gen_ai.completion\")", "delete_matching_keys(log.attributes, \"^gen_ai\\\\.(prompt|completion)\\\\.[0-9]+\\\\..+$\")", "delete_matching_keys(log.body, \"^(content|message|tool_calls)$\") where IsMap(log.body) and (IsMatch(log.event_name, \"^(gen_ai\\\\.system\\\\.message|gen_ai\\\\.user\\\\.message|gen_ai\\\\.assistant\\\\.message|gen_ai\\\\.tool\\\\.message|gen_ai\\\\.choice)$\") or IsMatch(log.attributes[\"event.name\"], \"^(gen_ai\\\\.system\\\\.message|gen_ai\\\\.user\\\\.message|gen_ai\\\\.assistant\\\\.message|gen_ai\\\\.tool\\\\.message|gen_ai\\\\.choice)$\"))", "set(log.body, \"\") where IsString(log.body) and (IsMatch(log.event_name, \"^(gen_ai\\\\.system\\\\.message|gen_ai\\\\.user\\\\.message|gen_ai\\\\.assistant\\\\.message|gen_ai\\\\.tool\\\\.message|gen_ai\\\\.choice)$\") or IsMatch(log.attributes[\"event.name\"], \"^(gen_ai\\\\.system\\\\.message|gen_ai\\\\.user\\\\.message|gen_ai\\\\.assistant\\\\.message|gen_ai\\\\.tool\\\\.message|gen_ai\\\\.choice)$\"))"]
|
|
26
|
+
redaction/genai_content:
|
|
27
|
+
allow_all_keys: true
|
|
28
|
+
blocked_key_patterns: [^(gen_ai\.system_instructions|gen_ai\.input\.messages|gen_ai\.output\.messages|gen_ai\.tool\.call\.arguments|gen_ai\.tool\.call\.result|gen_ai\.retrieval\.query\.text|gen_ai\.retrieval\.documents|gen_ai\.prompt|gen_ai\.completion)$, "^gen_ai\\.(prompt|completion)\\.[0-9]+\\..+$"]
|
|
29
|
+
batch: {}
|
|
30
|
+
filter/genai_spans:
|
|
31
|
+
error_mode: ignore
|
|
32
|
+
traces:
|
|
33
|
+
span: ["attributes[\"gen_ai.operation.name\"] == nil"]
|
|
34
|
+
|
|
35
|
+
exporters:
|
|
36
|
+
debug:
|
|
37
|
+
verbosity: basic
|
|
38
|
+
|
|
39
|
+
connectors:
|
|
40
|
+
forward/genai: {}
|
|
41
|
+
spanmetrics/genai:
|
|
42
|
+
namespace: genai
|
|
43
|
+
dimensions:
|
|
44
|
+
- name: gen_ai.operation.name
|
|
45
|
+
- name: gen_ai.request.model
|
|
46
|
+
- name: gen_ai.tool.name
|
|
47
|
+
- name: error.type
|
|
48
|
+
histogram:
|
|
49
|
+
unit: s
|
|
50
|
+
explicit:
|
|
51
|
+
buckets: [100ms, 250ms, 500ms, 1s, 2s, 5s, 10s, 20s, 40s, 80s]
|
|
52
|
+
metrics_flush_interval: 15s
|
|
53
|
+
sum/genai_tokens:
|
|
54
|
+
spans:
|
|
55
|
+
genai.tokens.input:
|
|
56
|
+
source_attribute: gen_ai.usage.input_tokens
|
|
57
|
+
description: Input tokens used by GenAI operations
|
|
58
|
+
conditions: ["attributes[\"gen_ai.usage.input_tokens\"] != nil and not (kind == SPAN_KIND_INTERNAL and (attributes[\"gen_ai.operation.name\"] == \"invoke_agent\" or attributes[\"gen_ai.operation.name\"] == \"invoke_workflow\"))"]
|
|
59
|
+
attributes:
|
|
60
|
+
- key: gen_ai.request.model
|
|
61
|
+
default_value: unknown
|
|
62
|
+
genai.tokens.output:
|
|
63
|
+
source_attribute: gen_ai.usage.output_tokens
|
|
64
|
+
description: Output tokens used by GenAI operations
|
|
65
|
+
conditions: ["attributes[\"gen_ai.usage.output_tokens\"] != nil and not (kind == SPAN_KIND_INTERNAL and (attributes[\"gen_ai.operation.name\"] == \"invoke_agent\" or attributes[\"gen_ai.operation.name\"] == \"invoke_workflow\"))"]
|
|
66
|
+
attributes:
|
|
67
|
+
- key: gen_ai.request.model
|
|
68
|
+
default_value: unknown
|
|
69
|
+
|
|
70
|
+
extensions:
|
|
71
|
+
health_check:
|
|
72
|
+
endpoint: 0.0.0.0:13133
|
|
73
|
+
|
|
74
|
+
service:
|
|
75
|
+
extensions: [health_check]
|
|
76
|
+
pipelines:
|
|
77
|
+
traces:
|
|
78
|
+
receivers: [otlp]
|
|
79
|
+
processors: [memory_limiter, transform/genai_content, redaction/genai_content, batch]
|
|
80
|
+
exporters: [debug, forward/genai]
|
|
81
|
+
traces/genai:
|
|
82
|
+
receivers: [forward/genai]
|
|
83
|
+
processors: [filter/genai_spans]
|
|
84
|
+
exporters: [spanmetrics/genai, sum/genai_tokens]
|
|
85
|
+
metrics/genai:
|
|
86
|
+
receivers: [spanmetrics/genai, sum/genai_tokens]
|
|
87
|
+
processors: [batch]
|
|
88
|
+
exporters: [debug]
|
|
89
|
+
logs:
|
|
90
|
+
receivers: [otlp]
|
|
91
|
+
processors: [memory_limiter, transform/genai_content, redaction/genai_content, batch]
|
|
92
|
+
exporters: [debug]
|
package/src/topology.test.ts
CHANGED
|
@@ -56,6 +56,18 @@ describe("collectorTopology", () => {
|
|
|
56
56
|
]);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
test("reports the wire protocols a component speaks, and none for one that states none (#2558)", () => {
|
|
60
|
+
const topo = collectorTopologyOf(entities);
|
|
61
|
+
const protocols = (id: string) => topo.components.find((c) => c.id === id)!.protocols;
|
|
62
|
+
expect(protocols("otlp")).toEqual(["grpc", "http/protobuf", "http/json"]);
|
|
63
|
+
expect(protocols("otlphttp/honeycomb")).toEqual(["http/protobuf"]);
|
|
64
|
+
expect(protocols("health_check")).toEqual([]);
|
|
65
|
+
const json = collectorTopologyOf([new OtlpHttpExporter({ endpoint: "https://x", encoding: "json" })]);
|
|
66
|
+
expect(json.components[0]!.protocols).toEqual(["http/json"]);
|
|
67
|
+
const httpOnly = collectorTopologyOf([new OtlpReceiver({ protocols: { http: {} } })]);
|
|
68
|
+
expect(httpOnly.components[0]!.protocols).toEqual(["http/protobuf", "http/json"]);
|
|
69
|
+
});
|
|
70
|
+
|
|
59
71
|
test("reports receiver listen addresses, defaults included, and extension endpoints", () => {
|
|
60
72
|
const topo = collectorTopologyOf(entities);
|
|
61
73
|
expect(topo.components.find((c) => c.id === "otlp")!.endpoints).toEqual(["0.0.0.0:4317", "localhost:4318"]);
|
package/src/topology.ts
CHANGED
|
@@ -41,6 +41,11 @@ export interface TopologyComponent {
|
|
|
41
41
|
schema?: SchemaPin;
|
|
42
42
|
/** Addresses it listens on or sends to, as the config states them. Empty when the config names none. */
|
|
43
43
|
endpoints: string[];
|
|
44
|
+
/**
|
|
45
|
+
* The wire protocols it speaks, as OTLP SDKs name them (`grpc`, `http/protobuf`, `http/json`).
|
|
46
|
+
* Empty when its definition states none, which a protocol check reads as unknown.
|
|
47
|
+
*/
|
|
48
|
+
protocols: string[];
|
|
44
49
|
/** The pipelines that use it. For a connector, those on either side; for an extension, empty. */
|
|
45
50
|
pipelines: string[];
|
|
46
51
|
}
|
|
@@ -87,6 +92,16 @@ export interface CollectorTopology {
|
|
|
87
92
|
semconv: SemconvUsage[];
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
function protocolsOf(kind: ComponentKind, type: string, config: Record<string, unknown> | null | undefined): string[] {
|
|
96
|
+
const def = definitionOf(kind, type);
|
|
97
|
+
if (!def?.protocols) return [];
|
|
98
|
+
try {
|
|
99
|
+
return def.protocols((config ?? {}) as never);
|
|
100
|
+
} catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
function endpointsOf(kind: ComponentKind, type: string, config: Record<string, unknown> | null | undefined): string[] {
|
|
91
106
|
const def = definitionOf(kind, type);
|
|
92
107
|
const cfg = config ?? {};
|
|
@@ -131,6 +146,7 @@ export function collectorTopology(config: CollectorConfig): CollectorTopology {
|
|
|
131
146
|
builtin: def?.builtin ?? false,
|
|
132
147
|
...(def ? { schema: { ...def.pin } } : {}),
|
|
133
148
|
endpoints: endpointsOf(kind, type, cfg),
|
|
149
|
+
protocols: protocolsOf(kind, type, cfg),
|
|
134
150
|
pipelines: inPipelines,
|
|
135
151
|
});
|
|
136
152
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level guarantees of the otel lexicon's hand-written types: what
|
|
3
|
+
* TypeScript rejects before a build runs. Each `@ts-expect-error` line is a
|
|
4
|
+
* mistake the types must keep catching; tsc fails on the directive if the
|
|
5
|
+
* line ever compiles (`tsconfig.typecheck.json`, scripts/typecheck.ts). The
|
|
6
|
+
* runtime assertions keep vitest's view of the file honest.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, expectTypeOf, test } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
BatchProcessor,
|
|
11
|
+
DebugExporter,
|
|
12
|
+
HealthCheckExtension,
|
|
13
|
+
MemoryLimiterProcessor,
|
|
14
|
+
NodeAgent,
|
|
15
|
+
OtlpExporter,
|
|
16
|
+
OtlpReceiver,
|
|
17
|
+
Pipeline,
|
|
18
|
+
Service,
|
|
19
|
+
SpanMetricsConnector,
|
|
20
|
+
TailSamplingProcessor,
|
|
21
|
+
defineComponent,
|
|
22
|
+
type PipelineEntity,
|
|
23
|
+
type TailSamplingPolicy,
|
|
24
|
+
} from "./index";
|
|
25
|
+
import { generate } from "./codegen/generate";
|
|
26
|
+
|
|
27
|
+
const otlp = new OtlpReceiver({ protocols: { grpc: { endpoint: "0.0.0.0:4317" } } });
|
|
28
|
+
const batch = new BatchProcessor({});
|
|
29
|
+
const debug = new DebugExporter({ verbosity: "basic" });
|
|
30
|
+
const health = new HealthCheckExtension({});
|
|
31
|
+
const spanmetrics = new SpanMetricsConnector({});
|
|
32
|
+
|
|
33
|
+
describe("pipelines take components of the right kind", () => {
|
|
34
|
+
test("receivers, processors and exporters in their own slots; a connector on either side", () => {
|
|
35
|
+
const traces = new Pipeline({ signal: "traces", receivers: [otlp], processors: [batch], exporters: [debug, spanmetrics] });
|
|
36
|
+
const metrics = new Pipeline({ signal: "metrics", receivers: [spanmetrics], exporters: [debug] });
|
|
37
|
+
expectTypeOf(traces).toEqualTypeOf<PipelineEntity>();
|
|
38
|
+
expect(traces.pipelineId).toBe("traces");
|
|
39
|
+
expect(metrics.pipelineId).toBe("metrics");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("a component of the wrong kind is a type error", () => {
|
|
43
|
+
// @ts-expect-error a receiver is not an exporter
|
|
44
|
+
new Pipeline({ signal: "traces", receivers: [otlp], exporters: [otlp] });
|
|
45
|
+
// @ts-expect-error an exporter is not a processor
|
|
46
|
+
new Pipeline({ signal: "traces", receivers: [otlp], processors: [debug], exporters: [debug] });
|
|
47
|
+
// @ts-expect-error an extension is not a receiver
|
|
48
|
+
new Pipeline({ signal: "traces", receivers: [health], exporters: [debug] });
|
|
49
|
+
// @ts-expect-error a processor is not an extension
|
|
50
|
+
new Service({ extensions: [batch] });
|
|
51
|
+
expect(true).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("the signal is one the collector knows", () => {
|
|
55
|
+
// @ts-expect-error "spans" is not a signal; "traces" is
|
|
56
|
+
new Pipeline({ signal: "spans", receivers: [otlp], exporters: [debug] });
|
|
57
|
+
expect(true).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("component configs are typed", () => {
|
|
62
|
+
test("a wrongly typed setting is a type error", () => {
|
|
63
|
+
// @ts-expect-error limit_mib is a number
|
|
64
|
+
new MemoryLimiterProcessor({ check_interval: "1s", limit_mib: "400" });
|
|
65
|
+
// @ts-expect-error verbosity is basic, normal or detailed
|
|
66
|
+
new DebugExporter({ verbosity: "verbose" });
|
|
67
|
+
// @ts-expect-error tls is an object, not a flag
|
|
68
|
+
new OtlpExporter({ endpoint: "tempo:4317", tls: true });
|
|
69
|
+
expect(true).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("tail sampling policies are discriminated by type", () => {
|
|
73
|
+
const errors: TailSamplingPolicy = { name: "errors", type: "status_code", status_code: { status_codes: ["ERROR"] } };
|
|
74
|
+
// @ts-expect-error a latency policy carries its settings under `latency`
|
|
75
|
+
const wrong: TailSamplingPolicy = { name: "slow", type: "latency", probabilistic: { sampling_percentage: 10 } };
|
|
76
|
+
// @ts-expect-error decision_wait is required
|
|
77
|
+
new TailSamplingProcessor({ policies: [errors] });
|
|
78
|
+
expect(wrong.name).toBe("slow");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("a custom component's config type follows its definition", () => {
|
|
82
|
+
const Vendor = defineComponent<{ token: string }>()({
|
|
83
|
+
kind: "exporter",
|
|
84
|
+
type: "vendor",
|
|
85
|
+
pin: { source: "github.com/example/vendorexporter", version: "v1.0.0" },
|
|
86
|
+
});
|
|
87
|
+
new Vendor({ token: "${env:VENDOR_TOKEN}" });
|
|
88
|
+
// @ts-expect-error token is a string
|
|
89
|
+
new Vendor({ token: 42 });
|
|
90
|
+
expect(Vendor.definition.type).toBe("vendor");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("NodeAgent props", () => {
|
|
95
|
+
test("exporters are required and must be exporters", () => {
|
|
96
|
+
const agent = NodeAgent({ exporters: [debug] });
|
|
97
|
+
expectTypeOf(agent.traces).toEqualTypeOf<PipelineEntity>();
|
|
98
|
+
// @ts-expect-error exporters is required
|
|
99
|
+
expect(() => NodeAgent({})).toThrow(/exporters/);
|
|
100
|
+
// @ts-expect-error a receiver is not an exporter
|
|
101
|
+
NodeAgent({ exporters: [otlp] });
|
|
102
|
+
// @ts-expect-error hostMetrics is a switch or its settings
|
|
103
|
+
NodeAgent({ exporters: [debug], hostMetrics: "on" });
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("the packaged types entry", () => {
|
|
108
|
+
test("re-exports the package's own declarations", async () => {
|
|
109
|
+
const result = await generate();
|
|
110
|
+
expect(result.typesDTS).toContain('export * from "../index"');
|
|
111
|
+
});
|
|
112
|
+
});
|