@intentius/chant-lexicon-otel 0.95.1 → 0.96.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 +30 -9
- package/dist/catalog.d.ts +2 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/collector.d.ts +4 -1
- package/dist/collector.d.ts.map +1 -1
- package/dist/components/connectors.d.ts +156 -0
- package/dist/components/connectors.d.ts.map +1 -0
- package/dist/components/filtering.d.ts +95 -0
- package/dist/components/filtering.d.ts.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/k8s-receivers.d.ts +74 -0
- package/dist/components/k8s-receivers.d.ts.map +1 -0
- package/dist/components/sampling.d.ts +260 -0
- package/dist/components/sampling.d.ts.map +1 -0
- package/dist/define.d.ts +31 -4
- package/dist/define.d.ts.map +1 -1
- package/dist/genai.d.ts +165 -0
- package/dist/genai.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +8 -7
- package/dist/lint/audit-catalog.d.ts +2 -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 +1 -1
- package/dist/lint/post-synth/otel-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/otel101.d.ts +1 -1
- package/dist/lint/post-synth/otel103.d.ts +1 -1
- package/dist/lint/post-synth/otel112.d.ts +8 -0
- package/dist/lint/post-synth/otel112.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +70 -0
- package/dist/metric-names.d.ts +121 -0
- package/dist/metric-names.d.ts.map +1 -0
- package/dist/model.d.ts +23 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/okf/index.md +15 -0
- package/dist/okf/rules/OTEL112.md +11 -0
- package/dist/okf/types/CountConnector.md +9 -0
- package/dist/okf/types/FilterProcessor.md +9 -0
- package/dist/okf/types/ForwardConnector.md +9 -0
- package/dist/okf/types/K8sClusterReceiver.md +9 -0
- package/dist/okf/types/KubeletStatsReceiver.md +9 -0
- package/dist/okf/types/LoadBalancingExporter.md +9 -0
- package/dist/okf/types/ProbabilisticSamplerProcessor.md +9 -0
- package/dist/okf/types/RedactionProcessor.md +9 -0
- package/dist/okf/types/RoutingConnector.md +9 -0
- package/dist/okf/types/ServiceGraphConnector.md +9 -0
- package/dist/okf/types/SpanMetricsConnector.md +9 -0
- package/dist/okf/types/SumConnector.md +9 -0
- package/dist/okf/types/TailSamplingProcessor.md +9 -0
- package/dist/okf/types/TransformProcessor.md +9 -0
- package/dist/pipeline.d.ts +9 -3
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/rules/otel-helpers.ts +1 -1
- package/dist/rules/otel101.ts +1 -1
- package/dist/rules/otel103.ts +1 -1
- package/dist/rules/otel112.ts +17 -0
- package/dist/semconv.d.ts +33 -0
- package/dist/semconv.d.ts.map +1 -0
- package/dist/serializer.d.ts +1 -1
- package/dist/skills/chant-otel.md +28 -5
- package/dist/topology.d.ts +34 -5
- package/dist/topology.d.ts.map +1 -1
- package/dist/validate-config.d.ts +6 -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/catalog.ts +2 -1
- package/src/codegen/docs.ts +14 -8
- package/src/collector.ts +9 -1
- package/src/components/connectors.ts +276 -0
- package/src/components/filtering.test.ts +192 -0
- package/src/components/filtering.ts +192 -0
- package/src/components/index.ts +4 -0
- package/src/components/k8s-receivers.test.ts +123 -0
- package/src/components/k8s-receivers.ts +107 -0
- package/src/components/sampling.test.ts +464 -0
- package/src/components/sampling.ts +521 -0
- package/src/connectors.test.ts +388 -0
- package/src/define.ts +36 -4
- package/src/genai.test.ts +526 -0
- package/src/genai.ts +466 -0
- package/src/generated/lexicon-otel.json +70 -0
- package/src/index.ts +39 -0
- package/src/lint/audit-catalog.ts +11 -2
- package/src/lint/post-synth/index.ts +2 -0
- package/src/lint/post-synth/otel-helpers.ts +1 -1
- package/src/lint/post-synth/otel101.ts +1 -1
- package/src/lint/post-synth/otel103.ts +1 -1
- package/src/lint/post-synth/otel112.ts +17 -0
- package/src/metric-names.test.ts +80 -0
- package/src/metric-names.ts +176 -0
- package/src/model.ts +26 -4
- package/src/otelcol-validate.test.ts +143 -0
- package/src/pipeline.ts +9 -3
- package/src/plugin.test.ts +1 -0
- package/src/plugin.ts +1 -1
- package/src/semconv.ts +63 -0
- package/src/serializer.ts +1 -1
- package/src/skills/chant-otel.md +28 -5
- package/src/topology.ts +54 -8
- package/src/validate-config.ts +92 -4
- package/src/validate.ts +8 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTEL112: A connector joins pipelines whose signals it does not convert
|
|
3
|
+
*
|
|
4
|
+
* Each connector supports fixed signal pairs: `spanmetrics` and `servicegraph` read traces and write metrics, `count` writes metrics from any signal, `routing` and `forward` keep the signal. Every pipeline that feeds a connector must pair with a pipeline it feeds through one of those pairs, and the other way round, or the collector refuses to start. A traces pipeline that receives from `spanmetrics` fails this check. Custom connectors are checked when their `defineComponent` call lists `connects`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { configDiagnostics } from "./otel-helpers";
|
|
9
|
+
|
|
10
|
+
export const otel112: PostSynthCheck = {
|
|
11
|
+
id: "OTEL112",
|
|
12
|
+
description: "A connector joins pipelines whose signals it does not convert",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return configDiagnostics(ctx, "OTEL112");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic-convention vocabularies a collector config can depend on, and how
|
|
3
|
+
* to tell that it does.
|
|
4
|
+
*
|
|
5
|
+
* A collector config has no metadata channel, so a config cannot say which
|
|
6
|
+
* semconv version its attribute keys follow. What it can show is that it uses
|
|
7
|
+
* a vocabulary: a `spanmetrics` dimension named `gen_ai.request.model`, an
|
|
8
|
+
* OTTL statement deleting `gen_ai.input.messages`. `semconvUsage()` finds
|
|
9
|
+
* those and pairs each vocabulary with the pin this package's keys follow, the
|
|
10
|
+
* same way `collectorTopology()` pairs a component type with the pin of the
|
|
11
|
+
* definition this process has. Reading a parsed YAML file and reading the
|
|
12
|
+
* declaration give the same answer.
|
|
13
|
+
*/
|
|
14
|
+
import { type SchemaPin } from "./define.js";
|
|
15
|
+
import { type CollectorConfig } from "./model.js";
|
|
16
|
+
/** One attribute vocabulary and the version of its conventions this package follows. */
|
|
17
|
+
export interface SemconvVocabulary {
|
|
18
|
+
/** The attribute namespace, e.g. `gen_ai`. */
|
|
19
|
+
namespace: string;
|
|
20
|
+
pin: SchemaPin;
|
|
21
|
+
/** True when a string in a component's config refers to this namespace. */
|
|
22
|
+
matches: (text: string) => boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare const SEMCONV_VOCABULARIES: ReadonlyArray<SemconvVocabulary>;
|
|
25
|
+
/** A vocabulary a config uses, with the components that use it. */
|
|
26
|
+
export interface SemconvUsage extends SchemaPin {
|
|
27
|
+
namespace: string;
|
|
28
|
+
/** Component ids whose config names an attribute of this namespace, in config order. */
|
|
29
|
+
components: string[];
|
|
30
|
+
}
|
|
31
|
+
/** The semconv vocabularies a collector config uses, each with its pin and the components that use it. */
|
|
32
|
+
export declare function semconvUsage(config: CollectorConfig): SemconvUsage[];
|
|
33
|
+
//# sourceMappingURL=semconv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semconv.d.ts","sourceRoot":"","sources":["../src/semconv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EAAc,KAAK,eAAe,EAAsB,MAAM,SAAS,CAAC;AAE/E,wFAAwF;AACxF,MAAM,WAAW,iBAAiB;IAChC,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,SAAS,CAAC;IACf,2EAA2E;IAC3E,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACpC;AAKD,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC,iBAAiB,CAEhE,CAAC;AAEH,mEAAmE;AACnE,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAWD,0GAA0G;AAC1G,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,YAAY,EAAE,CAYpE"}
|
package/dist/serializer.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Emits one collector config file from every otel entity in the build: the
|
|
5
5
|
* YAML `otelcol --config` reads as it is. Section order is fixed (receivers,
|
|
6
|
-
* processors, exporters, extensions, service); components and pipelines keep
|
|
6
|
+
* processors, exporters, connectors, extensions, service); components and pipelines keep
|
|
7
7
|
* the order they are declared in, and each component's config keeps the key
|
|
8
8
|
* order it was written in, since collector docs and diffs read that way.
|
|
9
9
|
*
|
|
@@ -6,7 +6,7 @@ user-invocable: true
|
|
|
6
6
|
|
|
7
7
|
# OpenTelemetry Collector config with chant
|
|
8
8
|
|
|
9
|
-
The otel lexicon (`@intentius/chant-lexicon-otel`) types collector config. Each receiver, processor, exporter and extension is an entity, pipelines reference them, and `chant build` writes one collector config file.
|
|
9
|
+
The otel lexicon (`@intentius/chant-lexicon-otel`) types collector config. Each receiver, processor, exporter, connector and extension is an entity, pipelines reference them, and `chant build` writes one collector config file.
|
|
10
10
|
|
|
11
11
|
## Project setup
|
|
12
12
|
|
|
@@ -40,13 +40,36 @@ Emits `receivers.otlp`, `processors.memory_limiter` and `processors.batch`, `exp
|
|
|
40
40
|
|
|
41
41
|
| Kind | Types |
|
|
42
42
|
|---|---|
|
|
43
|
-
| receivers | otlp, prometheus, hostmetrics, filelog |
|
|
44
|
-
| processors | batch, memory_limiter, resource, attributes, k8sattributes, resourcedetection |
|
|
45
|
-
| exporters | otlp, otlphttp, debug, prometheus, googlecloud |
|
|
43
|
+
| receivers | otlp, prometheus, hostmetrics, filelog, k8s_cluster, kubeletstats |
|
|
44
|
+
| processors | batch, memory_limiter, resource, attributes, k8sattributes, resourcedetection, filter, transform, redaction, tail_sampling, probabilistic_sampler |
|
|
45
|
+
| exporters | otlp, otlphttp, debug, prometheus, googlecloud, loadbalancing |
|
|
46
|
+
| connectors | spanmetrics, servicegraph, routing, forward, count, sum |
|
|
46
47
|
| extensions | health_check, pprof, zpages |
|
|
47
48
|
|
|
48
49
|
Anything else goes through `defineComponent`; see the `chant-otel-custom-components` skill.
|
|
49
50
|
|
|
51
|
+
## Connectors
|
|
52
|
+
|
|
53
|
+
A connector joins two pipelines. List the same entity in `exporters` of the pipeline that feeds it and in `receivers` of the pipeline it feeds:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
export const spanmetrics = new SpanMetricsConnector({ dimensions: [{ name: "http.route" }] });
|
|
57
|
+
export const traces = new Pipeline({ signal: "traces", receivers: [otlp], exporters: [backend, spanmetrics] });
|
|
58
|
+
export const red = new Pipeline({ signal: "metrics", name: "red", receivers: [spanmetrics], exporters: [prom] });
|
|
59
|
+
```
|
|
60
|
+
|
|
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.
|
|
62
|
+
|
|
63
|
+
## GenAI workloads
|
|
64
|
+
|
|
65
|
+
For services that emit GenAI spans, start from the preset instead of writing the processors by hand:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
export const collector = genAiPipeline({ traceExporters: [tempo], metricExporters: [prom] });
|
|
69
|
+
```
|
|
70
|
+
|
|
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.
|
|
72
|
+
|
|
50
73
|
## Rules
|
|
51
74
|
|
|
52
75
|
- 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.
|
|
@@ -57,4 +80,4 @@ Anything else goes through `defineComponent`; see the `chant-otel-custom-compone
|
|
|
57
80
|
|
|
58
81
|
## Reading the result
|
|
59
82
|
|
|
60
|
-
`collectorTopologyOf(entities)` returns the pipelines, each component's endpoints and schema pin,
|
|
83
|
+
`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 (`GENAI_SEMCONV_PIN`) the config's `gen_ai.` keys follow, as plain data.
|
package/dist/topology.d.ts
CHANGED
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `collectorTopology()` reads a collector config (declared, or parsed from a
|
|
5
5
|
* YAML file) and returns its pipelines, its components with their endpoints
|
|
6
|
-
* and schema pins,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* and schema pins, for each exporter the pipelines and signals it carries,
|
|
7
|
+
* the edges connectors make from one pipeline to another, and the
|
|
8
|
+
* semantic-convention versions its attribute keys follow. It is the
|
|
9
|
+
* surface a reader such as `chant workspace graph` uses to say where a
|
|
10
|
+
* member's telemetry is sent, and what a declared telemetry endpoint link can
|
|
11
|
+
* point at.
|
|
10
12
|
*/
|
|
11
13
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
12
14
|
import { type SchemaPin } from "./define.js";
|
|
13
15
|
import { type CollectorConfig, type ComponentKind } from "./model.js";
|
|
16
|
+
import { type SemconvUsage } from "./semconv.js";
|
|
17
|
+
export type { SemconvUsage } from "./semconv.js";
|
|
14
18
|
export interface TopologyPipeline {
|
|
15
19
|
/** The id under `service.pipelines`, e.g. `traces` or `traces/backend`. */
|
|
16
20
|
id: string;
|
|
@@ -32,9 +36,22 @@ export interface TopologyComponent {
|
|
|
32
36
|
schema?: SchemaPin;
|
|
33
37
|
/** Addresses it listens on or sends to, as the config states them. Empty when the config names none. */
|
|
34
38
|
endpoints: string[];
|
|
35
|
-
/** The pipelines that use it. For an extension, empty. */
|
|
39
|
+
/** The pipelines that use it. For a connector, those on either side; for an extension, empty. */
|
|
36
40
|
pipelines: string[];
|
|
37
41
|
}
|
|
42
|
+
/** One hop from a pipeline to another through a connector. */
|
|
43
|
+
export interface TopologyEdge {
|
|
44
|
+
/** The connector's id. */
|
|
45
|
+
connector: string;
|
|
46
|
+
/** The pipeline that lists the connector as an exporter. */
|
|
47
|
+
from: string;
|
|
48
|
+
/** That pipeline's signal. */
|
|
49
|
+
fromSignal: string;
|
|
50
|
+
/** The pipeline that lists the connector as a receiver. */
|
|
51
|
+
to: string;
|
|
52
|
+
/** That pipeline's signal. */
|
|
53
|
+
toSignal: string;
|
|
54
|
+
}
|
|
38
55
|
export interface TopologyExporter {
|
|
39
56
|
id: string;
|
|
40
57
|
type: string;
|
|
@@ -48,6 +65,18 @@ export interface CollectorTopology {
|
|
|
48
65
|
components: TopologyComponent[];
|
|
49
66
|
/** The exporters again, grouped for the question "where does this telemetry go". */
|
|
50
67
|
exporters: TopologyExporter[];
|
|
68
|
+
/**
|
|
69
|
+
* Pipeline-to-pipeline edges through connectors, one per (from, to) pair the
|
|
70
|
+
* connector supports. For a connector whose definition this process lacks,
|
|
71
|
+
* every pair.
|
|
72
|
+
*/
|
|
73
|
+
edges: TopologyEdge[];
|
|
74
|
+
/**
|
|
75
|
+
* The semantic-convention vocabularies the config's attribute keys come
|
|
76
|
+
* from, each with the pin this package follows for it (`GENAI_SEMCONV_PIN`
|
|
77
|
+
* for `gen_ai`) and the components that use it. Empty when none is used.
|
|
78
|
+
*/
|
|
79
|
+
semconv: SemconvUsage[];
|
|
51
80
|
}
|
|
52
81
|
/** The topology of a collector config. */
|
|
53
82
|
export declare function collectorTopology(config: CollectorConfig): CollectorTopology;
|
package/dist/topology.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topology.d.ts","sourceRoot":"","sources":["../src/topology.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"topology.d.ts","sourceRoot":"","sources":["../src/topology.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAgD,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AACjH,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE5D,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kHAAkH;IAClH,OAAO,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,wGAAwG;IACxG,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iGAAiG;IACjG,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAC;IACX,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kEAAkE;IAClE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,oFAAoF;IACpF,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B;;;;OAIG;IACH,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB;;;;OAIG;IACH,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAeD,0CAA0C;AAC1C,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB,CA2D5E;AAED,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,iBAAiB,CAE/G"}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
11
11
|
import { type CollectorConfig } from "./model.js";
|
|
12
|
-
|
|
12
|
+
import "./components/connectors.js";
|
|
13
|
+
export type CollectorIssueCode = "OTEL101" | "OTEL102" | "OTEL103" | "OTEL104" | "OTEL105" | "OTEL106" | "OTEL107" | "OTEL108" | "OTEL109" | "OTEL112";
|
|
13
14
|
export interface CollectorIssue {
|
|
14
15
|
code: CollectorIssueCode;
|
|
15
16
|
severity: "error" | "warning";
|
|
@@ -19,7 +20,10 @@ export interface CollectorIssue {
|
|
|
19
20
|
/** The component id the issue is about, when it is about one. */
|
|
20
21
|
component?: string;
|
|
21
22
|
}
|
|
22
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Check a collector config's references and pipeline shape (OTEL101-OTEL106),
|
|
25
|
+
* and each connector's signals against its definition (OTEL112).
|
|
26
|
+
*/
|
|
23
27
|
export declare function validateCollectorConfig(config: CollectorConfig): CollectorIssue[];
|
|
24
28
|
/**
|
|
25
29
|
* Check declared entities for what only the declaration knows (OTEL107-OTEL109):
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-config.d.ts","sourceRoot":"","sources":["../src/validate-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAG9D,OAAO,
|
|
1
|
+
{"version":3,"file":"validate-config.d.ts","sourceRoot":"","sources":["../src/validate-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAG9D,OAAO,EAA4D,KAAK,eAAe,EAA4B,MAAM,SAAS,CAAC;AAGnI,OAAO,yBAAyB,CAAC;AAEjC,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,EAAE,CA0JjF;AA2CD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,cAAc,EAAE,CAyDpH"}
|
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAMvF,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAEvF,0DAA0D;AAC1D,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAMvF,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAEvF,0DAA0D;AAC1D,eAAO,MAAM,cAAc,UA6B1B,CAAC;AAEF,wBAAsB,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC,CA2CxD"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-otel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OpenTelemetry Collector lexicon for chant — typed receivers, processors, exporters, extensions and pipelines, serialized to collector YAML",
|
|
3
|
+
"version": "0.96.0",
|
|
4
|
+
"description": "OpenTelemetry Collector lexicon for chant — typed receivers, processors, exporters, connectors, extensions and pipelines, serialized to collector YAML",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
7
7
|
"repository": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"typescript": "^5.9.3"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
|
-
"@intentius/chant": "^0.
|
|
71
|
+
"@intentius/chant": "^0.96.0",
|
|
72
72
|
"typescript": "^5.9.3"
|
|
73
73
|
}
|
|
74
74
|
}
|
package/src/catalog.ts
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import type { LexiconEntry } from "@intentius/chant/lsp/lexicon-providers";
|
|
8
8
|
import { componentEntityType, type ComponentClass } from "./define";
|
|
9
|
+
import type { ComponentKind } from "./model";
|
|
9
10
|
import * as components from "./components";
|
|
10
11
|
import { PIPELINE_TYPE, SERVICE_TYPE } from "./pipeline";
|
|
11
12
|
|
|
12
13
|
export interface CatalogEntry {
|
|
13
14
|
className: string;
|
|
14
15
|
entityType: string;
|
|
15
|
-
kind:
|
|
16
|
+
kind: ComponentKind | "pipeline" | "service";
|
|
16
17
|
/** The collector type, for components. */
|
|
17
18
|
type?: string;
|
|
18
19
|
description: string;
|
package/src/codegen/docs.ts
CHANGED
|
@@ -13,8 +13,8 @@ function serviceFromType(resourceType: string): string {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const overview = `The otel lexicon types [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/)
|
|
16
|
-
config: receivers, processors, exporters, extensions and the
|
|
17
|
-
connect them. \`chant build\` emits one collector config file, the YAML
|
|
16
|
+
config: receivers, processors, exporters, connectors, extensions and the
|
|
17
|
+
pipelines that connect them. \`chant build\` emits one collector config file, the YAML
|
|
18
18
|
\`otelcol --config\` reads as it is.
|
|
19
19
|
|
|
20
20
|
\`\`\`ts
|
|
@@ -32,10 +32,15 @@ export const traces = new Pipeline({
|
|
|
32
32
|
});
|
|
33
33
|
\`\`\`
|
|
34
34
|
|
|
35
|
-
The built-in component set is otlp, prometheus, hostmetrics
|
|
36
|
-
receivers; batch, memory_limiter, resource,
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
The built-in component set is otlp, prometheus, hostmetrics, filelog,
|
|
36
|
+
k8s_cluster and kubeletstats receivers; batch, memory_limiter, resource,
|
|
37
|
+
attributes, k8sattributes, resourcedetection, filter, transform, redaction,
|
|
38
|
+
tail_sampling and probabilistic_sampler processors; otlp, otlphttp, debug,
|
|
39
|
+
prometheus, googlecloud and loadbalancing exporters; spanmetrics,
|
|
40
|
+
servicegraph, routing, forward, count and sum connectors; and the
|
|
41
|
+
health_check, pprof and zpages extensions. \`genAiPipeline()\` is a preset for
|
|
42
|
+
GenAI workloads: it removes prompt and completion content and derives agent
|
|
43
|
+
RED and token metrics.
|
|
39
44
|
Their config types follow the collector-contrib release named on the
|
|
40
45
|
Custom components page. A component chant doesn't ship is added with
|
|
41
46
|
\`defineComponent\`, and it is serialized and checked the same way as the
|
|
@@ -43,13 +48,14 @@ built-ins.
|
|
|
43
48
|
|
|
44
49
|
Checks catch a pipeline that uses a component nobody declared (OTEL101), a
|
|
45
50
|
pipeline with no receivers or exporters (OTEL102), a declared component no
|
|
46
|
-
pipeline uses (OTEL103),
|
|
51
|
+
pipeline uses (OTEL103), a connector joining pipelines of signals it can't
|
|
52
|
+
convert (OTEL112), and a literal credential in source (OTEL002).
|
|
47
53
|
`;
|
|
48
54
|
|
|
49
55
|
const outputFormat = `The otel lexicon serializes every otel entity in a build into **one
|
|
50
56
|
collector config file** in YAML, the file \`otelcol --config\` reads.
|
|
51
57
|
|
|
52
|
-
- Sections come out as receivers, processors, exporters, extensions, service,
|
|
58
|
+
- Sections come out as receivers, processors, exporters, connectors, extensions, service,
|
|
53
59
|
separated by a blank line. Empty sections are left out.
|
|
54
60
|
- A component's id is its collector type, or \`type/name\` when its \`name\` is
|
|
55
61
|
set. The export name in source never appears in the output.
|
package/src/collector.ts
CHANGED
|
@@ -12,10 +12,14 @@ import { definitionFor, isOTelComponent, type OTelComponent } from "./define";
|
|
|
12
12
|
import { SECTION_OF, type CollectorConfig, type PipelineConfig } from "./model";
|
|
13
13
|
import { isPipelineEntity, isServiceEntity, type ComponentRef, type PipelineEntity, type ServiceEntity } from "./pipeline";
|
|
14
14
|
import { emitCollectorYaml } from "./yaml";
|
|
15
|
+
import { semconvUsage } from "./semconv";
|
|
15
16
|
|
|
16
17
|
export interface BuiltCollector {
|
|
17
18
|
config: CollectorConfig;
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* One line per custom component naming its schema pin, and one per semconv
|
|
21
|
+
* vocabulary the config uses. Becomes the YAML's `# chant:` header.
|
|
22
|
+
*/
|
|
19
23
|
header: string[];
|
|
20
24
|
/** Non-fatal problems, e.g. two components declaring the same id. */
|
|
21
25
|
warnings: string[];
|
|
@@ -136,6 +140,10 @@ export function buildCollectorConfig(entities: Iterable<Declarable> | Map<string
|
|
|
136
140
|
const digest = pin?.digest ? ` ${pin.digest}` : "";
|
|
137
141
|
header.push(`chant: ${c.componentKind} ${c.componentId} schema ${pin?.source ?? "(unpinned)"}@${pin?.version ?? "(unpinned)"}${digest}`);
|
|
138
142
|
}
|
|
143
|
+
for (const use of semconvUsage(config)) {
|
|
144
|
+
const digest = use.digest ? ` ${use.digest}` : "";
|
|
145
|
+
header.push(`chant: semconv ${use.namespace} ${use.source}@${use.version}${digest} (${use.components.join(", ")})`);
|
|
146
|
+
}
|
|
139
147
|
|
|
140
148
|
return { config, header, warnings, components, pipelines, service };
|
|
141
149
|
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in connectors: spanmetrics, servicegraph, routing, forward, count, sum.
|
|
3
|
+
*
|
|
4
|
+
* A connector is an exporter in one pipeline and a receiver in another. Put
|
|
5
|
+
* the same entity in `exporters` of the pipeline that feeds it and in
|
|
6
|
+
* `receivers` of the pipeline it feeds. `connects` lists the signal pairs each
|
|
7
|
+
* connector's factory registers; OTEL112 checks pipelines against them.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { defineBuiltin } from "../define";
|
|
11
|
+
import type { ConnectorSignalPair } from "../model";
|
|
12
|
+
import type { Duration } from "./common";
|
|
13
|
+
|
|
14
|
+
const SAME_SIGNAL: ConnectorSignalPair[] = [
|
|
15
|
+
{ from: "traces", to: "traces" },
|
|
16
|
+
{ from: "metrics", to: "metrics" },
|
|
17
|
+
{ from: "logs", to: "logs" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
// ── spanmetrics ──────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** A span or resource attribute added as a metric dimension. */
|
|
23
|
+
export interface SpanMetricsDimension {
|
|
24
|
+
name: string;
|
|
25
|
+
/** Value used when the attribute is missing. Without it, spans lacking the attribute get no dimension. */
|
|
26
|
+
default?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SpanMetricsConnectorConfig {
|
|
30
|
+
/** Extra dimensions on every metric, beyond service.name, span.name, span.kind and status.code. */
|
|
31
|
+
dimensions?: SpanMetricsDimension[];
|
|
32
|
+
/** Extra dimensions on the calls metric only. */
|
|
33
|
+
calls_dimensions?: SpanMetricsDimension[];
|
|
34
|
+
/** Default dimensions to leave out. */
|
|
35
|
+
exclude_dimensions?: string[];
|
|
36
|
+
resource_metrics_cache_size?: number;
|
|
37
|
+
/** Resource attributes that key the metrics' resource, so a restart doesn't start a new series. */
|
|
38
|
+
resource_metrics_key_attributes?: string[];
|
|
39
|
+
aggregation_temporality?: "AGGREGATION_TEMPORALITY_CUMULATIVE" | "AGGREGATION_TEMPORALITY_DELTA";
|
|
40
|
+
histogram?: {
|
|
41
|
+
disable?: boolean;
|
|
42
|
+
unit?: "ms" | "s";
|
|
43
|
+
/** Explicit bucket boundaries. Set this or `exponential`, not both. */
|
|
44
|
+
explicit?: { buckets?: Duration[] };
|
|
45
|
+
exponential?: { max_size?: number };
|
|
46
|
+
/** Extra dimensions on the duration histogram only. */
|
|
47
|
+
dimensions?: SpanMetricsDimension[];
|
|
48
|
+
};
|
|
49
|
+
/** How often metrics are emitted (default 60s). */
|
|
50
|
+
metrics_flush_interval?: Duration;
|
|
51
|
+
/** Drop a series not updated for this long. */
|
|
52
|
+
metrics_expiration?: Duration;
|
|
53
|
+
metric_timestamp_cache_size?: number;
|
|
54
|
+
/** Prefix for the emitted metric names (default `traces.span.metrics`). */
|
|
55
|
+
namespace?: string;
|
|
56
|
+
exemplars?: { enabled?: boolean; max_per_data_point?: number };
|
|
57
|
+
/** Also count span events. Needs `dimensions` when enabled. */
|
|
58
|
+
events?: { enabled?: boolean; dimensions?: SpanMetricsDimension[] };
|
|
59
|
+
include_instrumentation_scope?: string[];
|
|
60
|
+
aggregation_cardinality_limit?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Turns spans into request, error and duration (RED) metrics. */
|
|
64
|
+
export const SpanMetricsConnector = defineBuiltin<SpanMetricsConnectorConfig, "connector", "spanmetrics">({
|
|
65
|
+
kind: "connector",
|
|
66
|
+
type: "spanmetrics",
|
|
67
|
+
description: "Turns spans into call-count and duration metrics per service, span name, kind and status",
|
|
68
|
+
connects: [{ from: "traces", to: "metrics" }],
|
|
69
|
+
validate: (c) => {
|
|
70
|
+
const problems: string[] = [];
|
|
71
|
+
if (c.histogram?.explicit && c.histogram?.exponential) {
|
|
72
|
+
problems.push("histogram: set explicit or exponential buckets, not both");
|
|
73
|
+
}
|
|
74
|
+
if (c.events?.enabled && (c.events.dimensions ?? []).length === 0) {
|
|
75
|
+
problems.push("events: enabled needs at least one dimension");
|
|
76
|
+
}
|
|
77
|
+
const reserved = new Set(["service.name", "span.name", "span.kind", "status.code"]);
|
|
78
|
+
for (const d of c.dimensions ?? []) {
|
|
79
|
+
if (reserved.has(d.name)) problems.push(`dimensions: "${d.name}" is already a default dimension`);
|
|
80
|
+
reserved.add(d.name);
|
|
81
|
+
}
|
|
82
|
+
return problems;
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ── servicegraph ─────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export interface ServiceGraphConnectorConfig {
|
|
89
|
+
latency_histogram_buckets?: Duration[];
|
|
90
|
+
/** Span attributes added as dimensions on the edge metrics. */
|
|
91
|
+
dimensions?: string[];
|
|
92
|
+
/** How long an unpaired client or server span waits for its other half. */
|
|
93
|
+
store?: { ttl?: Duration; max_items?: number };
|
|
94
|
+
cache_loop?: Duration;
|
|
95
|
+
store_expiration_loop?: Duration;
|
|
96
|
+
/** Attributes that name the peer when only one side of a call is instrumented. */
|
|
97
|
+
virtual_node_peer_attributes?: string[];
|
|
98
|
+
virtual_node_extra_label?: boolean;
|
|
99
|
+
metrics_flush_interval?: Duration;
|
|
100
|
+
/** Attributes that name the database for a database call edge. */
|
|
101
|
+
database_name_attributes?: string[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Builds service-to-service edge metrics from paired client and server spans. */
|
|
105
|
+
export const ServiceGraphConnector = defineBuiltin<ServiceGraphConnectorConfig, "connector", "servicegraph">({
|
|
106
|
+
kind: "connector",
|
|
107
|
+
type: "servicegraph",
|
|
108
|
+
description: "Builds service-to-service request and latency metrics from paired client and server spans",
|
|
109
|
+
connects: [{ from: "traces", to: "metrics" }],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ── routing ──────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
export interface RoutingTableItem {
|
|
115
|
+
/** The OTTL context the condition runs in. `request` routes on request metadata such as headers. */
|
|
116
|
+
context?: "resource" | "span" | "metric" | "datapoint" | "log" | "request";
|
|
117
|
+
/** An OTTL `route() where ...` statement. Set this or `condition`, not both. */
|
|
118
|
+
statement?: string;
|
|
119
|
+
/** An OTTL condition. Set this or `statement`, not both. */
|
|
120
|
+
condition?: string;
|
|
121
|
+
/** Pipeline ids (`traces/tenant-a`) that receive matching data. Each must list this connector as a receiver. */
|
|
122
|
+
pipelines: string[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface RoutingConnectorConfig {
|
|
126
|
+
table: RoutingTableItem[];
|
|
127
|
+
/** Pipelines for data no route matches. */
|
|
128
|
+
default_pipelines?: string[];
|
|
129
|
+
error_mode?: "ignore" | "silent" | "propagate";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Sends each item to the pipelines whose route matches it. */
|
|
133
|
+
export const RoutingConnector = defineBuiltin<RoutingConnectorConfig, "connector", "routing">({
|
|
134
|
+
kind: "connector",
|
|
135
|
+
type: "routing",
|
|
136
|
+
description: "Routes traces, metrics or logs to pipelines by OTTL condition",
|
|
137
|
+
connects: SAME_SIGNAL,
|
|
138
|
+
validate: (c) => {
|
|
139
|
+
const problems: string[] = [];
|
|
140
|
+
if ((c.table ?? []).length === 0) problems.push("table is empty");
|
|
141
|
+
(c.table ?? []).forEach((item, i) => {
|
|
142
|
+
if (!item.statement && !item.condition) problems.push(`table[${i}]: set a condition or a statement`);
|
|
143
|
+
if (item.statement && item.condition) problems.push(`table[${i}]: set a condition or a statement, not both`);
|
|
144
|
+
if (item.context === "request" && !item.condition) problems.push(`table[${i}]: the request context needs a condition`);
|
|
145
|
+
if ((item.pipelines ?? []).length === 0) problems.push(`table[${i}]: no pipelines`);
|
|
146
|
+
});
|
|
147
|
+
return problems;
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// ── forward ──────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
export interface ForwardConnectorConfig {}
|
|
154
|
+
|
|
155
|
+
/** Passes data unchanged from one pipeline to another of the same signal. */
|
|
156
|
+
export const ForwardConnector = defineBuiltin<ForwardConnectorConfig, "connector", "forward">({
|
|
157
|
+
kind: "connector",
|
|
158
|
+
type: "forward",
|
|
159
|
+
description: "Passes data unchanged to another pipeline of the same signal",
|
|
160
|
+
connects: SAME_SIGNAL,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ── count ────────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
export interface CountMetricInfo {
|
|
166
|
+
description?: string;
|
|
167
|
+
/** OTTL conditions; an item is counted when any matches. None counts every item. */
|
|
168
|
+
conditions?: string[];
|
|
169
|
+
/** Attributes to split the count by. Not supported for `metrics`. */
|
|
170
|
+
attributes?: Array<{ key: string; default_value?: string | number | boolean }>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Each key is the name of a metric to emit. */
|
|
174
|
+
export interface CountConnectorConfig {
|
|
175
|
+
spans?: Record<string, CountMetricInfo>;
|
|
176
|
+
spanevents?: Record<string, CountMetricInfo>;
|
|
177
|
+
metrics?: Record<string, CountMetricInfo>;
|
|
178
|
+
datapoints?: Record<string, CountMetricInfo>;
|
|
179
|
+
logs?: Record<string, CountMetricInfo>;
|
|
180
|
+
profiles?: Record<string, CountMetricInfo>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Counts spans, span events, metrics, data points or log records, as metrics. */
|
|
184
|
+
export const CountConnector = defineBuiltin<CountConnectorConfig, "connector", "count">({
|
|
185
|
+
kind: "connector",
|
|
186
|
+
type: "count",
|
|
187
|
+
description: "Counts spans, span events, metrics, data points or log records and emits the counts as metrics",
|
|
188
|
+
connects: [
|
|
189
|
+
{ from: "traces", to: "metrics" },
|
|
190
|
+
{ from: "metrics", to: "metrics" },
|
|
191
|
+
{ from: "logs", to: "metrics" },
|
|
192
|
+
{ from: "profiles", to: "metrics" },
|
|
193
|
+
],
|
|
194
|
+
validate: (c) => {
|
|
195
|
+
const problems: string[] = [];
|
|
196
|
+
for (const [section, metrics] of Object.entries(c) as Array<[string, Record<string, CountMetricInfo> | undefined]>) {
|
|
197
|
+
for (const [name, info] of Object.entries(metrics ?? {})) {
|
|
198
|
+
if (name === "") problems.push(`${section}: metric name missing`);
|
|
199
|
+
if (section === "metrics" && (info?.attributes ?? []).length > 0) {
|
|
200
|
+
problems.push(`metrics.${name}: attributes are not supported when counting metrics`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return problems;
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ── sum ──────────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
export interface SumMetricInfo {
|
|
211
|
+
/** The attribute whose numeric value is summed. Strings that parse as numbers count; others are skipped. */
|
|
212
|
+
source_attribute: string;
|
|
213
|
+
description?: string;
|
|
214
|
+
/** OTTL conditions; an item is summed when any matches. None sums every item that has the attribute. */
|
|
215
|
+
conditions?: string[];
|
|
216
|
+
/**
|
|
217
|
+
* Attributes to split the sum by. Not supported for `metrics`. An item
|
|
218
|
+
* missing one of them is skipped unless it has a `default_value`. Use at
|
|
219
|
+
* most one: with more, the pinned collector adds each value once per
|
|
220
|
+
* attribute (OTEL107 reports it).
|
|
221
|
+
*/
|
|
222
|
+
attributes?: Array<{ key: string; default_value?: string | number | boolean }>;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Each key is the name of a metric to emit. The connector emits monotonic
|
|
227
|
+
* sums with delta temporality; the `prometheus` exporter accumulates them,
|
|
228
|
+
* while an exporter that needs cumulative input wants a `deltatocumulative`
|
|
229
|
+
* processor in front of it.
|
|
230
|
+
*/
|
|
231
|
+
export interface SumConnectorConfig {
|
|
232
|
+
spans?: Record<string, SumMetricInfo>;
|
|
233
|
+
spanevents?: Record<string, SumMetricInfo>;
|
|
234
|
+
metrics?: Record<string, SumMetricInfo>;
|
|
235
|
+
datapoints?: Record<string, SumMetricInfo>;
|
|
236
|
+
logs?: Record<string, SumMetricInfo>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Sums a numeric attribute of spans, span events, data points or log records, as metrics. */
|
|
240
|
+
export const SumConnector = defineBuiltin<SumConnectorConfig, "connector", "sum">({
|
|
241
|
+
kind: "connector",
|
|
242
|
+
type: "sum",
|
|
243
|
+
description: "Sums a numeric attribute of spans, span events, data points or log records and emits the sums as metrics",
|
|
244
|
+
connects: [
|
|
245
|
+
{ from: "traces", to: "metrics" },
|
|
246
|
+
{ from: "metrics", to: "metrics" },
|
|
247
|
+
{ from: "logs", to: "metrics" },
|
|
248
|
+
],
|
|
249
|
+
validate: (c) => {
|
|
250
|
+
const problems: string[] = [];
|
|
251
|
+
let total = 0;
|
|
252
|
+
for (const [section, metrics] of Object.entries(c) as Array<[string, Record<string, SumMetricInfo> | undefined]>) {
|
|
253
|
+
for (const [name, info] of Object.entries(metrics ?? {})) {
|
|
254
|
+
total++;
|
|
255
|
+
if (name === "") problems.push(`${section}: metric name missing`);
|
|
256
|
+
if (!info?.source_attribute) problems.push(`${section}.${name}: source_attribute is missing`);
|
|
257
|
+
if (section === "metrics" && (info?.attributes ?? []).length > 0) {
|
|
258
|
+
problems.push(`${section}.${name}: attributes are not supported when summing metrics`);
|
|
259
|
+
}
|
|
260
|
+
(info?.attributes ?? []).forEach((a, i) => {
|
|
261
|
+
if (!a.key) problems.push(`${section}.${name}.attributes[${i}]: key is missing`);
|
|
262
|
+
});
|
|
263
|
+
// At the pinned release the connector adds each value once per
|
|
264
|
+
// attribute key (sumconnector summer.increment), so two keys double
|
|
265
|
+
// every sum.
|
|
266
|
+
if ((info?.attributes ?? []).length > 1) {
|
|
267
|
+
problems.push(
|
|
268
|
+
`${section}.${name}: more than one attribute multiplies each sum by the number of attributes in the pinned collector; split by one attribute`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (total === 0) problems.push("no metric is configured, so the connector emits nothing");
|
|
274
|
+
return problems;
|
|
275
|
+
},
|
|
276
|
+
});
|