Skip to content

Commit 267f876

Browse files
committed
wip multiple exporters
1 parent 3c95a74 commit 267f876

4 files changed

Lines changed: 171 additions & 57 deletions

File tree

npm/app/__tests__/config.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('configuration and validation', () => {
5151
process.env.ROTEL_OTLP_EXPORTER_TLS_KEY_FILE = "key.file";
5252
process.env.ROTEL_OTLP_EXPORTER_TLS_CA_FILE = "ca.file";
5353
process.env.ROTEL_OTLP_EXPORTER_TLS_SKIP_VERIFY = "true";
54-
let c = Config._load_otlp_exporter_options_from_env(null);
54+
let c = Config._load_otlp_exporter_options_from_env("OTLP_EXPORTER_", null);
5555
expect(c?.endpoint).toBe("https://api.foo.com");
5656
expect(c?.protocol).toBe("http");
5757
expect(c?.headers).toStrictEqual({"[x-api-key": "123]"})
@@ -68,8 +68,18 @@ describe('configuration and validation', () => {
6868
expect(c?.tls_skip_verify).toBe(true);
6969
});
7070

71+
it('Load DatadogExporter config from ENV', () => {
72+
process.env.ROTEL_DATADOG_EXPORTER_REGION = "us1";
73+
process.env.ROTEL_DATADOG_EXPORTER_CUSTOM_ENDPOINT = "http://localhost:5555";
74+
process.env.ROTEL_DATADOG_EXPORTER_API_KEY = "123abc";
75+
let c = Config._load_datadog_exporter_options_from_env("DATADOG_EXPORTER_");
76+
expect(c.region).toBe("us1");
77+
expect(c.custom_endpoint).toBe("http://localhost:5555");
78+
expect(c.api_key).toBe("123abc");
79+
});
80+
7181
it('fails validation', () => {
72-
82+
process.env.ROTEL_ENABLED = "true";
7383
const c1 = new Config();
7484
c1.options.exporter = {protocol: "X.500"};
7585
expect(c1.validate()).toBe(false)
@@ -78,4 +88,6 @@ describe('configuration and validation', () => {
7888
c2.options.log_format = "ascii";
7989
expect(c2.validate()).toBe(false)
8090
});
81-
});
91+
});
92+
93+

npm/app/config.ts

Lines changed: 141 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,19 @@ export interface OTLPExporterEndpoint {
2323

2424
// TODO: when we have more, include a key that defines this exporter type
2525
export interface OTLPExporter extends OTLPExporterEndpoint {
26-
traces?: OTLPExporterEndpoint;
26+
_type?: string
27+
traces?: OTLPExporterEndpoint | DatadogExporter;
2728
metrics?: OTLPExporterEndpoint;
2829
logs?: OTLPExporterEndpoint;
2930
}
3031

32+
export interface DatadogExporter {
33+
_type?: string
34+
region?: string
35+
custom_endpoint?: string
36+
api_key?: string
37+
}
38+
3139
export interface Options {
3240
enabled?: boolean;
3341
pid_file?: string;
@@ -39,7 +47,11 @@ export interface Options {
3947
otlp_receiver_traces_disabled?: boolean;
4048
otlp_receiver_metrics_disabled?: boolean;
4149
otlp_receiver_logs_disabled?: boolean;
42-
exporter?: OTLPExporter;
50+
exporter?: OTLPExporter | DatadogExporter;
51+
exporters?: Record<string, OTLPExporter | DatadogExporter | undefined>
52+
exporters_metrics?: string[] | undefined
53+
exporters_traces?: string[] | undefined
54+
exporters_logs?: string[] | undefined
4355
}
4456

4557
export class Config {
@@ -84,26 +96,74 @@ export class Config {
8496
otlp_receiver_logs_disabled: as_bool(rotel_env("OTLP_RECEIVER_LOGS_DISABLED")),
8597
};
8698

87-
const exporter_type = as_lower(rotel_env("EXPORTER"));
88-
if (exporter_type === null || exporter_type === "otlp") {
89-
let exporter: OTLPExporter = Config._load_otlp_exporter_options_from_env(null) as OTLPExporter;
90-
if (exporter === null) {
91-
// make sure we always construct the top-level exporter config
92-
exporter = {};
93-
}
94-
env.exporter = exporter;
99+
const exporters = as_lower(rotel_env("EXPORTERS"));
100+
if (exporters !== null && exporters !== undefined) {
101+
env["exporters"] = {};
102+
for (const exporterStr of exporters.split(",")) {
103+
let name = exporterStr;
104+
let value = exporterStr;
105+
if (exporterStr.includes(":")) {
106+
[name, value] = exporterStr.split(":", 2);
107+
}
95108

96-
const traces_endpoint = Config._load_otlp_exporter_options_from_env("TRACES");
97-
if (traces_endpoint !== null) {
98-
exporter.traces = traces_endpoint;
99-
}
100-
const metrics_endpoint = Config._load_otlp_exporter_options_from_env("METRICS");
101-
if (metrics_endpoint !== null) {
102-
exporter.metrics = metrics_endpoint;
109+
let exporter: OTLPExporter | DatadogExporter | undefined = undefined;
110+
let pfx = "EXPORTER_" + name.toUpperCase + "_"
111+
switch(value) {
112+
case "otlp":
113+
exporter = Config._load_otlp_exporter_options_from_env(pfx, "OTLPExporter") as OTLPExporter;
114+
exporter._type = "otlp"
115+
if (exporter === null || exporter === undefined) {
116+
exporter = {};
117+
exporter._type = "otlp";
118+
}
119+
case "datadog":
120+
const datadogExporter: DatadogExporter = {
121+
_type: "datadog",
122+
region: rotel_env(pfx + "REGION"),
123+
custom_endpoint: rotel_env(pfx + "CUSTOM_ENDPOINT"),
124+
api_key: rotel_env(pfx + "API_KEY"),
125+
};
126+
exporter = datadogExporter;
127+
}
128+
if (exporter !== undefined) {
129+
env.exporters[name] = exporter;
130+
}
103131
}
104-
const logs_endpoint = Config._load_otlp_exporter_options_from_env("LOGS");
105-
if (logs_endpoint != null) {
106-
exporter.logs = logs_endpoint;
132+
env.exporters_traces = as_list(rotel_env("EXPORTERS_TRACES"))
133+
env.exporters_metrics = as_list(rotel_env("EXPORTERS_METRICS"))
134+
env.exporters_logs = as_list(rotel_env("EXPORTERS_LOGS"))
135+
} else {
136+
const exporter_type = as_lower(rotel_env("EXPORTER"));
137+
if (exporter_type === null || exporter_type === "otlp") {
138+
let exporter: OTLPExporter = Config._load_otlp_exporter_options_from_env("OTLP_EXPORTER_", null) as OTLPExporter;
139+
if (exporter === null) {
140+
// make sure we always construct the top-level exporter config
141+
exporter = {};
142+
}
143+
exporter._type = "otlp";
144+
env.exporter = exporter;
145+
146+
const traces_endpoint = Config._load_otlp_exporter_options_from_env("OTLP_EXPORTER_TRACES_", "TRACES");
147+
if (traces_endpoint !== null) {
148+
exporter.traces = traces_endpoint;
149+
}
150+
const metrics_endpoint = Config._load_otlp_exporter_options_from_env("OTLP_EXPORTER_METRICS_", "METRICS");
151+
if (metrics_endpoint !== null) {
152+
exporter.metrics = metrics_endpoint;
153+
}
154+
const logs_endpoint = Config._load_otlp_exporter_options_from_env("OTLP_EXPORTER_LOGS_", "LOGS");
155+
if (logs_endpoint != null) {
156+
exporter.logs = logs_endpoint;
157+
}
158+
} else if (exporter_type === "datadog") {
159+
const pfx = "DATADOG_EXPORTER_"
160+
var c: DatadogExporter = {
161+
_type: "datadog",
162+
region: rotel_env(pfx + "REGION"),
163+
custom_endpoint: rotel_env(pfx + "CUSTOM_ENDPOINT"),
164+
api_key: rotel_env(pfx + "API_KEY"),
165+
}
166+
env.exporter = c;
107167
}
108168
}
109169

@@ -117,11 +177,20 @@ export class Config {
117177
return final_env;
118178
}
119179

120-
static _load_otlp_exporter_options_from_env(endpoint_type: string | null): OTLPExporter | OTLPExporterEndpoint | undefined {
121-
let pfx = "OTLP_EXPORTER_";
122-
if (endpoint_type !== null) {
123-
pfx += `${endpoint_type}_`;
124-
}
180+
static _load_datadog_exporter_options_from_env(pfx: string): DatadogExporter {
181+
const datadogExporter: DatadogExporter = {
182+
region: rotel_env(pfx + "REGION"),
183+
custom_endpoint: rotel_env(pfx + "CUSTOM_ENDPOINT"),
184+
api_key: rotel_env(pfx + "API_KEY"),
185+
};
186+
return datadogExporter;
187+
}
188+
189+
static _load_otlp_exporter_options_from_env(pfx: string, endpoint_type: string | null): OTLPExporter | OTLPExporterEndpoint | undefined {
190+
// let pfx = "OTLP_EXPORTER_";
191+
// if (endpoint_type !== null) {
192+
// pfx += `${endpoint_type}_`;
193+
// }
125194
const endpoint: OTLPExporterEndpoint = {
126195
endpoint: rotel_env(pfx + "ENDPOINT"),
127196
protocol: as_lower(rotel_env(pfx + "PROTOCOL")),
@@ -170,20 +239,30 @@ export class Config {
170239

171240
const exporter = opts.exporter;
172241
if (exporter !== undefined) {
173-
_set_otlp_exporter_agent_env(updates, null, exporter);
174-
175-
const traces = exporter.traces;
176-
if (traces !== undefined) {
177-
_set_otlp_exporter_agent_env(updates, "TRACES", traces);
178-
}
242+
console.log("exporter._type in build_agent_environment is " + exporter._type);
243+
switch (exporter._type) {
244+
case "otlp" || undefined:
245+
const otlpExporter: OTLPExporter = <OTLPExporter>exporter;
246+
_set_otlp_exporter_agent_env(updates, null, exporter);
247+
248+
const traces = otlpExporter.traces;
249+
if (traces !== undefined) {
250+
_set_otlp_exporter_agent_env(updates, "TRACES", traces);
251+
}
179252

180-
const metrics = exporter.metrics;
181-
if (metrics !== undefined) {
182-
_set_otlp_exporter_agent_env(updates, "METRICS", metrics);
183-
}
184-
const logs = exporter.logs;
185-
if (logs !== undefined) {
186-
_set_otlp_exporter_agent_env(updates, "LOGS", logs);
253+
const metrics = otlpExporter.metrics;
254+
if (metrics !== undefined) {
255+
_set_otlp_exporter_agent_env(updates, "METRICS", metrics);
256+
}
257+
const logs = otlpExporter.logs;
258+
if (logs !== undefined) {
259+
_set_otlp_exporter_agent_env(updates, "LOGS", logs);
260+
}
261+
break;
262+
case "datadog":
263+
const datadogExporter: DatadogExporter = <DatadogExporter>exporter;
264+
_set_datadog_exporter_agent_env(updates, "DATADOG_EXPORTER_" , exporter);
265+
break;
187266
}
188267
}
189268

@@ -217,10 +296,19 @@ export class Config {
217296

218297
const exporter = this.options.exporter;
219298
if (exporter !== undefined) {
220-
const protocol = exporter.protocol;
221-
if (protocol !== undefined && protocol !== 'grpc' && protocol !== 'http') {
222-
console.error("exporter protocol must be 'grpc' or 'http'");
223-
return false;
299+
if (exporter._type === undefined) {
300+
exporter._type = "otlp"
301+
}
302+
switch (exporter._type) {
303+
case "otlp":
304+
const otlpExporter: OTLPExporter = <OTLPExporter>exporter;
305+
const protocol = otlpExporter.protocol;
306+
console.log("protocol is " + protocol);
307+
if (protocol !== undefined && protocol !== 'grpc' && protocol !== 'http') {
308+
console.error("exporter protocol must be 'grpc' or 'http'");
309+
return false;
310+
}
311+
224312
}
225313
}
226314

@@ -234,6 +322,15 @@ export class Config {
234322
}
235323
}
236324

325+
function _set_datadog_exporter_agent_env(updates: Record<string, any>, pfx: string, exporter: DatadogExporter) {
326+
Object.assign(updates, {
327+
[pfx + "EXPORTER"]: "datadog",
328+
[pfx + "REGION"]: exporter.region,
329+
[pfx + "CUSTOM_ENDPOINT"]: exporter.custom_endpoint,
330+
[pfx + "API_KEY"]: exporter.api_key,
331+
})
332+
}
333+
237334
function _set_otlp_exporter_agent_env(updates: Record<string, any>, endpoint_type: string | null, exporter: OTLPExporter | OTLPExporterEndpoint | null): void {
238335
let pfx = "OTLP_EXPORTER_";
239336
if (endpoint_type !== null) {
@@ -317,6 +414,8 @@ function as_bool(value: string | null | undefined): boolean | undefined {
317414
}
318415

319416
function rotel_env(base_key: string): string | undefined {
417+
// let key = rotel_expand_env_key(base_key);
418+
// console.log("key is " + key);
320419
const envVar = process.env[rotel_expand_env_key(base_key)];
321420
return envVar !== undefined ? envVar : undefined;
322421
}

npm/app/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
},
2222
"homepage": "https://github.com/streamfold/rotel-nodejs#readme",
2323
"devDependencies": {
24-
"@babel/preset-typescript": "^7.26.0",
2524
"@babel/preset-env": "^7.26.0",
25+
"@babel/preset-typescript": "^7.26.0",
2626
"@opentelemetry/api": "^1.9.0",
2727
"@opentelemetry/exporter-trace-otlp-grpc": "^0.200.0",
2828
"@opentelemetry/resources": "^2.0.0",
@@ -57,5 +57,8 @@
5757
"lib/*"
5858
],
5959
"root": true
60+
},
61+
"dependencies": {
62+
"yarn": "^1.22.22"
6063
}
6164
}

npm/app/yarn.lock

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,6 +1595,11 @@
15951595
dependencies:
15961596
"@sinonjs/commons" "^3.0.0"
15971597

1598+
"@streamfold/rotel-darwin-arm64@0.0.7-alpha":
1599+
version "0.0.7-alpha"
1600+
resolved "https://registry.npmjs.org/@streamfold/rotel-darwin-arm64/-/rotel-darwin-arm64-0.0.7-alpha.tgz"
1601+
integrity sha512-OQpkxS6GgAoeM/queJkNilqFMtXmjmjJNUhTgKb5Vuyg8Y5lNAW8zyXrQ9kvc8TcUGgN0olGElcimAd8gYigrQ==
1602+
15981603
"@types/babel__core@^7.1.14":
15991604
version "7.20.5"
16001605
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz"
@@ -3625,24 +3630,14 @@ rimraf@^3.0.2:
36253630
dependencies:
36263631
glob "^7.1.3"
36273632

3628-
rotel-agent-darwin-arm64@0.0.12-alpha:
3629-
version "0.0.12-alpha"
3630-
resolved "https://registry.npmjs.org/rotel-agent-darwin-arm64/-/rotel-agent-darwin-arm64-0.0.12-alpha.tgz"
3631-
integrity sha512-b5kXW731JQopiM3EEO2mQmR6wfUoN1F7y2Fpg/8HCv6/S0PvTQPPrfh516wBRwxTPErCoXnq1fBAYICwK6C5Og==
3632-
36333633
run-parallel@^1.1.9:
36343634
version "1.2.0"
36353635
resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
36363636
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
36373637
dependencies:
36383638
queue-microtask "^1.2.2"
36393639

3640-
semver@^6.3.0:
3641-
version "6.3.1"
3642-
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
3643-
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
3644-
3645-
semver@^6.3.1:
3640+
semver@^6.3.0, semver@^6.3.1:
36463641
version "6.3.1"
36473642
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
36483643
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
@@ -3985,6 +3980,11 @@ yargs@^17.3.1, yargs@^17.7.2:
39853980
y18n "^5.0.5"
39863981
yargs-parser "^21.1.1"
39873982

3983+
yarn@^1.22.22:
3984+
version "1.22.22"
3985+
resolved "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz"
3986+
integrity sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==
3987+
39883988
yocto-queue@^0.1.0:
39893989
version "0.1.0"
39903990
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"

0 commit comments

Comments
 (0)