@voltro/plugin-datadog 0.1.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/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +7253 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +200 -0
- package/package.json +51 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { LogRecord } from '@voltro/logger';
|
|
2
|
+
import { MetricSample } from '@voltro/runtime';
|
|
3
|
+
import { VoltroPlugin } from '@voltro/protocol';
|
|
4
|
+
|
|
5
|
+
export declare interface DatadogLog {
|
|
6
|
+
readonly message: string;
|
|
7
|
+
readonly status: string;
|
|
8
|
+
readonly ddsource: string;
|
|
9
|
+
readonly [attribute: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare interface DatadogPayload {
|
|
13
|
+
readonly series: ReadonlyArray<DatadogSeries>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export declare const datadogPlugin: (options?: DatadogPluginOptions) => VoltroPlugin;
|
|
17
|
+
|
|
18
|
+
export declare interface DatadogPluginOptions {
|
|
19
|
+
/** Datadog API key. Default `DD_API_KEY` env. Needed for metrics + logs
|
|
20
|
+
* (agentless). Traces use the Agent and don't need it. */
|
|
21
|
+
readonly apiKey?: string;
|
|
22
|
+
/** Datadog site host. Default `DD_SITE` env, else `datadoghq.com` (US1). */
|
|
23
|
+
readonly site?: string;
|
|
24
|
+
/** Metrics push interval in ms. Default 30000. */
|
|
25
|
+
readonly intervalMs?: number;
|
|
26
|
+
/** Prepended to every metric name (e.g. `voltro.`). */
|
|
27
|
+
readonly prefix?: string;
|
|
28
|
+
/** Tags added to every series + log (`["env:prod", "service:api"]`). */
|
|
29
|
+
readonly tags?: ReadonlyArray<string>;
|
|
30
|
+
/** Per-replica identity, emitted as the Datadog v2
|
|
31
|
+
* `resources: [{ type: 'host', name }]` field on every pushed series.
|
|
32
|
+
* Default `os.hostname()`. Without a per-replica value, N replicas
|
|
33
|
+
* submitting the same metric+tags merge into ONE Datadog series and the
|
|
34
|
+
* cumulative per-process counters interleave — `.as_count()`/`diff()`
|
|
35
|
+
* rates go silently wrong. */
|
|
36
|
+
readonly host?: string;
|
|
37
|
+
/** Forward the framework log sink to Datadog logs intake. Default false. */
|
|
38
|
+
readonly logs?: boolean;
|
|
39
|
+
/** Logs flush interval in ms. Default 5000. */
|
|
40
|
+
readonly logsIntervalMs?: number;
|
|
41
|
+
/** Route the framework's OTel spans to the Datadog Agent's OTLP receiver.
|
|
42
|
+
* Default false. */
|
|
43
|
+
readonly traces?: boolean;
|
|
44
|
+
/** Datadog Agent OTLP base url (`/v1/traces` is appended). Default
|
|
45
|
+
* `DD_TRACE_AGENT_URL` env, else `http://localhost:4318`. */
|
|
46
|
+
readonly agentUrl?: string;
|
|
47
|
+
/** Continuous profiler via dd-trace (profiler-only mode). Default false. */
|
|
48
|
+
readonly profiling?: boolean;
|
|
49
|
+
/** `service` resource/tag (DD unified service tagging). Default `DD_SERVICE` env. */
|
|
50
|
+
readonly service?: string;
|
|
51
|
+
/** `env` resource/tag. Default `DD_ENV` env. */
|
|
52
|
+
readonly env?: string;
|
|
53
|
+
/** `version` resource/tag. Default `DD_VERSION` env. */
|
|
54
|
+
readonly version?: string;
|
|
55
|
+
/** Disambiguates multiple instances. */
|
|
56
|
+
readonly name?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export declare interface DatadogPoint {
|
|
60
|
+
readonly timestamp: number;
|
|
61
|
+
readonly value: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Datadog v2 series resource — `{ type: 'host', name }` carries the
|
|
65
|
+
* per-replica identity Datadog uses to keep same-name+tags series apart. */
|
|
66
|
+
export declare interface DatadogResource {
|
|
67
|
+
readonly type: string;
|
|
68
|
+
readonly name: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export declare interface DatadogSeries {
|
|
72
|
+
readonly metric: string;
|
|
73
|
+
readonly type: 0 | 1 | 2 | 3;
|
|
74
|
+
readonly points: ReadonlyArray<DatadogPoint>;
|
|
75
|
+
readonly tags?: ReadonlyArray<string>;
|
|
76
|
+
readonly resources?: ReadonlyArray<DatadogResource>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export declare const toDatadogLog: (r: LogRecord, opts?: ToLogsOptions) => DatadogLog;
|
|
80
|
+
|
|
81
|
+
export declare const toDatadogLogs: (records: ReadonlyArray<LogRecord>, opts?: ToLogsOptions) => ReadonlyArray<DatadogLog>;
|
|
82
|
+
|
|
83
|
+
export declare const toDatadogSeries: (samples: ReadonlyArray<MetricSample>, opts: ToSeriesOptions) => DatadogPayload;
|
|
84
|
+
|
|
85
|
+
declare interface ToLogsOptions {
|
|
86
|
+
readonly service?: string;
|
|
87
|
+
/** Comma-joined into `ddtags`. */
|
|
88
|
+
readonly tags?: ReadonlyArray<string>;
|
|
89
|
+
readonly hostname?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
declare interface ToSeriesOptions {
|
|
93
|
+
/** Unix SECONDS for every point in this batch. */
|
|
94
|
+
readonly timestamp: number;
|
|
95
|
+
/** Prepended to every metric name (e.g. `voltro.`). Default none. */
|
|
96
|
+
readonly prefix?: string;
|
|
97
|
+
/** Tags added to every series. */
|
|
98
|
+
readonly globalTags?: ReadonlyArray<string>;
|
|
99
|
+
/** Per-replica identity, emitted as the v2 `resources: [{ type: 'host', name }]`
|
|
100
|
+
* field on EVERY series. Without it, N replicas submitting the same
|
|
101
|
+
* metric+tags merge into ONE Datadog series and the cumulative per-process
|
|
102
|
+
* counters interleave into a meaningless sawtooth. */
|
|
103
|
+
readonly host?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { hostname as e } from "node:os";
|
|
2
|
+
import { Effect as t } from "effect";
|
|
3
|
+
import { pluginEnv as n } from "@voltro/env";
|
|
4
|
+
import { addSink as r, createLogger as i, makeBufferedSink as a } from "@voltro/logger";
|
|
5
|
+
import { definePlugin as o } from "@voltro/protocol";
|
|
6
|
+
import { snapshotMetrics as s } from "@voltro/runtime";
|
|
7
|
+
//#region src/format.ts
|
|
8
|
+
var c = 3, l = (e) => Object.entries(e).map(([e, t]) => `${e}:${t}`), u = (e, t) => {
|
|
9
|
+
let n = t.prefix ?? "", r = t.globalTags ?? [], i = t.host === void 0 ? void 0 : [{
|
|
10
|
+
type: "host",
|
|
11
|
+
name: t.host
|
|
12
|
+
}], a = [], o = (e, o, s) => {
|
|
13
|
+
a.push({
|
|
14
|
+
metric: `${n}${e}`,
|
|
15
|
+
type: c,
|
|
16
|
+
points: [{
|
|
17
|
+
timestamp: t.timestamp,
|
|
18
|
+
value: o
|
|
19
|
+
}],
|
|
20
|
+
tags: [...r, ...s],
|
|
21
|
+
...i ? { resources: i } : {}
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
for (let t of e) {
|
|
25
|
+
let e = l(t.labels);
|
|
26
|
+
if (t.type === "counter" || t.type === "gauge") o(t.name, t.value ?? 0, e);
|
|
27
|
+
else if (t.type === "histogram") {
|
|
28
|
+
let n = t.count ?? 0, r = t.sum ?? 0;
|
|
29
|
+
o(`${t.name}.count`, n, e), o(`${t.name}.sum`, r, e), o(`${t.name}.avg`, n > 0 ? r / n : 0, e);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { series: a };
|
|
33
|
+
}, d = (e, t = 16) => {
|
|
34
|
+
let n = e.length > t ? e.slice(-t) : e;
|
|
35
|
+
try {
|
|
36
|
+
return BigInt(`0x${n}`).toString(10);
|
|
37
|
+
} catch {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
}, f = (e, t = {}) => {
|
|
41
|
+
let n = typeof e.fields.traceId == "string" ? e.fields.traceId : void 0, r = typeof e.fields.spanId == "string" ? e.fields.spanId : void 0, { traceId: i, spanId: a, ...o } = e.fields;
|
|
42
|
+
return {
|
|
43
|
+
message: e.message,
|
|
44
|
+
status: e.level,
|
|
45
|
+
ddsource: "voltro",
|
|
46
|
+
...t.service ? { service: t.service } : {},
|
|
47
|
+
...t.tags && t.tags.length > 0 ? { ddtags: t.tags.join(",") } : {},
|
|
48
|
+
...t.hostname ? { hostname: t.hostname } : {},
|
|
49
|
+
...e.scope ? { scope: e.scope } : {},
|
|
50
|
+
...o,
|
|
51
|
+
...n ? {
|
|
52
|
+
trace_id: n,
|
|
53
|
+
"dd.trace_id": d(n)
|
|
54
|
+
} : {},
|
|
55
|
+
...r ? { "dd.span_id": d(r) } : {}
|
|
56
|
+
};
|
|
57
|
+
}, p = (e, t = {}) => e.map((e) => f(e, t)), m = n([
|
|
58
|
+
{
|
|
59
|
+
name: "DD_API_KEY",
|
|
60
|
+
required: !1,
|
|
61
|
+
secret: !0,
|
|
62
|
+
description: "Datadog API key for agentless metrics + logs intake; traces/profiling work without it."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "DD_SITE",
|
|
66
|
+
required: !1,
|
|
67
|
+
secret: !1,
|
|
68
|
+
description: "Datadog site host (e.g. datadoghq.com, datadoghq.eu); defaults to datadoghq.com."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "DD_SERVICE",
|
|
72
|
+
required: !1,
|
|
73
|
+
secret: !1,
|
|
74
|
+
description: "Unified service-tagging `service` resource/tag."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "DD_ENV",
|
|
78
|
+
required: !1,
|
|
79
|
+
secret: !1,
|
|
80
|
+
description: "Unified service-tagging `env` resource/tag."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "DD_VERSION",
|
|
84
|
+
required: !1,
|
|
85
|
+
secret: !1,
|
|
86
|
+
description: "Unified service-tagging `version` resource/tag."
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "DD_TRACE_AGENT_URL",
|
|
90
|
+
required: !1,
|
|
91
|
+
secret: !1,
|
|
92
|
+
description: "Datadog Agent OTLP base URL for traces; defaults to http://localhost:4318."
|
|
93
|
+
}
|
|
94
|
+
]), h = i({ scope: "plugin:datadog" }), g = (n = {}) => {
|
|
95
|
+
let i = m.read("DD_API_KEY", n.apiKey), c = m.read("DD_SITE", n.site) ?? "datadoghq.com", l = n.intervalMs ?? 3e4, d = n.logsIntervalMs ?? 5e3, f = m.read("DD_SERVICE", n.service), g = m.read("DD_ENV", n.env), _ = m.read("DD_VERSION", n.version), v = m.read("DD_TRACE_AGENT_URL", n.agentUrl) ?? "http://localhost:4318", y = n.host ?? e(), b = `https://api.${c}/api/v2/series`, x = `https://http-intake.logs.${c}/api/v2/logs`, S, C, w, T = async () => {
|
|
96
|
+
try {
|
|
97
|
+
let e = await t.runPromise(s);
|
|
98
|
+
if (e.length === 0) return;
|
|
99
|
+
let r = u(e, {
|
|
100
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
101
|
+
host: y,
|
|
102
|
+
...n.prefix === void 0 ? {} : { prefix: n.prefix },
|
|
103
|
+
...n.tags === void 0 ? {} : { globalTags: n.tags }
|
|
104
|
+
}), a = await fetch(b, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: {
|
|
107
|
+
"content-type": "application/json",
|
|
108
|
+
"DD-API-KEY": i
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify(r)
|
|
111
|
+
});
|
|
112
|
+
a.ok || h.warn("datadog series POST failed", {
|
|
113
|
+
status: a.status,
|
|
114
|
+
series: r.series.length
|
|
115
|
+
});
|
|
116
|
+
} catch (e) {
|
|
117
|
+
h.warn("datadog metrics flush errored", { error: String(e) });
|
|
118
|
+
}
|
|
119
|
+
}, E = async (e) => {
|
|
120
|
+
let t = p(e, {
|
|
121
|
+
...f ? { service: f } : {},
|
|
122
|
+
...n.tags ? { tags: n.tags } : {}
|
|
123
|
+
}), r = await fetch(x, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"content-type": "application/json",
|
|
127
|
+
"DD-API-KEY": i
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify(t)
|
|
130
|
+
});
|
|
131
|
+
r.ok || h.warn("datadog logs POST failed", {
|
|
132
|
+
status: r.status,
|
|
133
|
+
count: e.length
|
|
134
|
+
});
|
|
135
|
+
}, D = () => ({
|
|
136
|
+
...f ? { "service.name": f } : {},
|
|
137
|
+
...g ? { "deployment.environment": g } : {},
|
|
138
|
+
..._ ? { "service.version": _ } : {}
|
|
139
|
+
}), O = !!i, k = !!n.logs && !!i, A = (() => {
|
|
140
|
+
try {
|
|
141
|
+
return new URL(v).host;
|
|
142
|
+
} catch {
|
|
143
|
+
return "localhost";
|
|
144
|
+
}
|
|
145
|
+
})(), j = [`network:outbound:${c}`, ...n.traces || n.profiling ? [`network:outbound:${A}`] : []];
|
|
146
|
+
return o({
|
|
147
|
+
name: n.name ? `@voltro/plugin-datadog#${n.name}` : "@voltro/plugin-datadog",
|
|
148
|
+
description: "Deep Datadog integration — metrics + logs + traces (OTLP→Agent) + profiling, trace-correlated.",
|
|
149
|
+
permissions: j,
|
|
150
|
+
declaredEnv: m.declared,
|
|
151
|
+
contributeObservability: async (e) => {
|
|
152
|
+
if (n.profiling) try {
|
|
153
|
+
(await import("dd-trace")).default.init({
|
|
154
|
+
profiling: !0,
|
|
155
|
+
tracing: !1,
|
|
156
|
+
...f ? { service: f } : {},
|
|
157
|
+
...g ? { env: g } : {},
|
|
158
|
+
..._ ? { version: _ } : {}
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
e.logger.warn("datadog profiling requested but dd-trace is not installed");
|
|
162
|
+
}
|
|
163
|
+
let t = D();
|
|
164
|
+
if (!n.traces) return Object.keys(t).length > 0 ? { resourceAttributes: t } : void 0;
|
|
165
|
+
try {
|
|
166
|
+
let { BatchSpanProcessor: n } = await import("@opentelemetry/sdk-trace-base"), { OTLPTraceExporter: r } = await import("@opentelemetry/exporter-trace-otlp-http"), i = new r({ url: `${v.replace(/\/$/, "")}/v1/traces` });
|
|
167
|
+
return e.logger.info("datadog traces → agent OTLP", { url: `${v}/v1/traces` }), {
|
|
168
|
+
spanProcessors: [new n(i)],
|
|
169
|
+
resourceAttributes: t
|
|
170
|
+
};
|
|
171
|
+
} catch {
|
|
172
|
+
return e.logger.warn("datadog traces requested but @opentelemetry/sdk-trace-base + @opentelemetry/exporter-trace-otlp-http are not installed — skipping span export"), Object.keys(t).length > 0 ? { resourceAttributes: t } : void 0;
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
onActivate: (e) => t.sync(() => {
|
|
176
|
+
if (O && (S = setInterval(() => void T(), l), typeof S.unref == "function" && S.unref()), k && (C = a({
|
|
177
|
+
intervalMs: d,
|
|
178
|
+
send: E,
|
|
179
|
+
maxBuffer: 5e3,
|
|
180
|
+
onError: (e) => h.warn("datadog logs flush errored", { error: String(e) })
|
|
181
|
+
}), w = r((e) => C?.push(e))), !O && !n.traces && !n.profiling) {
|
|
182
|
+
e.logger.warn("datadog inactive — no DD_API_KEY and no traces/profiling enabled");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
e.logger.info("datadog active", {
|
|
186
|
+
metrics: O,
|
|
187
|
+
logs: k,
|
|
188
|
+
traces: !!n.traces,
|
|
189
|
+
profiling: !!n.profiling,
|
|
190
|
+
service: f,
|
|
191
|
+
env: g
|
|
192
|
+
});
|
|
193
|
+
}),
|
|
194
|
+
onDeactivate: () => t.promise(async () => {
|
|
195
|
+
S && clearInterval(S), S = void 0, w?.(), w = void 0, await C?.dispose(), C = void 0;
|
|
196
|
+
})
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
//#endregion
|
|
200
|
+
export { g as datadogPlugin, f as toDatadogLog, p as toDatadogLogs, u as toDatadogSeries };
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voltro/plugin-datadog",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deep Datadog integration — agentless metrics push (unified Metrics-API → /api/v2/series) + opt-in log forwarding (dd.trace_id-correlated → /api/v2/logs) + traces (framework OTel spans → Datadog Agent OTLP) + the dd-trace continuous profiler, all correlated by the same trace id. Inert without DD_API_KEY.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"voltro",
|
|
7
|
+
"typescript",
|
|
8
|
+
"framework"
|
|
9
|
+
],
|
|
10
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
11
|
+
"homepage": "https://voltro.dev",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"email": "support@voltro.dev"
|
|
14
|
+
},
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Voltro UG",
|
|
17
|
+
"url": "https://voltro.dev"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@voltro/env": "0.1.0",
|
|
36
|
+
"@voltro/logger": "0.1.0",
|
|
37
|
+
"@voltro/protocol": "0.1.0",
|
|
38
|
+
"@voltro/runtime": "0.1.0"
|
|
39
|
+
},
|
|
40
|
+
"optionalDependencies": {
|
|
41
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.220.0",
|
|
42
|
+
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
43
|
+
"dd-trace": "^6.2.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"effect": "^3.21.4"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|