@friggframework/core 2.0.0--canary.622.eb2c9bd.0 → 2.0.0--canary.622.ce0381c.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 +1 -1
- package/application/commands/usage-commands.js +13 -33
- package/application/index.js +5 -9
- package/core/create-handler.js +2 -2
- package/handlers/integration-event-dispatcher.js +1 -1
- package/index.js +1 -1
- package/integrations/integration-base.js +5 -5
- package/modules/requester/requester.js +2 -2
- package/package.json +5 -5
- package/reporting/reporting-router.js +1 -1
- package/reporting/use-cases/list-integrations-report.js +3 -3
- package/telemetry/README.md +25 -11
- package/telemetry/bind-telemetry-context.js +1 -1
- package/telemetry/canonical-counters.js +1 -1
- package/telemetry/exporters/console-exporter.js +19 -0
- package/telemetry/exporters/datadog-exporter.js +11 -0
- package/telemetry/exporters/honeycomb-exporter.js +13 -0
- package/telemetry/exporters/otlp-exporter.js +42 -0
- package/telemetry/exporters/passthrough-exporter.js +25 -0
- package/telemetry/exporters/resolve-exporter.js +39 -0
- package/telemetry/exporters/telemetry-exporter-interface.js +17 -0
- package/telemetry/index.js +8 -4
- package/telemetry/instrument-handler.js +1 -1
- package/telemetry/no-op-telemetry.js +61 -56
- package/telemetry/north-star.js +2 -2
- package/telemetry/otel-telemetry.js +160 -157
- package/telemetry/plugin-subscribers-singleton.js +2 -2
- package/telemetry/plugin-subscribers.js +1 -1
- package/telemetry/telemetry-config.js +4 -2
- package/telemetry/telemetry-context.js +1 -1
- package/telemetry/telemetry-event-bus.js +2 -2
- package/telemetry/telemetry-service-interface.js +49 -0
- package/telemetry/telemetry-service.js +10 -12
- package/telemetry/telemetry-singleton.js +3 -3
- package/telemetry/usage-rollup-singleton.js +1 -1
- package/telemetry/usage-rollup-subscriber.js +3 -3
- package/telemetry/usage-windows.js +1 -1
- package/usage/README.md +5 -5
- package/usage/repositories/usage-repository-documentdb.js +6 -6
- package/usage/repositories/usage-repository-interface.js +5 -5
- package/usage/repositories/usage-repository-postgres.js +6 -6
- package/usage/tracked-metrics.js +1 -1
- package/telemetry/exporters/exporter-factory.js +0 -79
package/README.md
CHANGED
|
@@ -232,7 +232,7 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
232
232
|
|
|
233
233
|
// Read the durable usage store (never an APM):
|
|
234
234
|
const frigg = createFriggCommands({ integrationClass: HubSpotIntegration });
|
|
235
|
-
await frigg.usage.
|
|
235
|
+
await frigg.usage.getTotalsByDimension({ metric: 'records.synced', groupBy: 'integrationType' });
|
|
236
236
|
```
|
|
237
237
|
|
|
238
238
|
**See:** [`telemetry/README.md`](telemetry/README.md) for the full guide
|
|
@@ -1,31 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
* Usage Commands
|
|
3
|
-
*
|
|
4
|
-
* Application Layer — the read/write surface over the durable usage store
|
|
5
|
-
* (ADR-011 §5). Exposed as `frigg.usage.*` on the unified command object so
|
|
6
|
-
* reports and integration code query usage without touching the repository.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* const frigg = createFriggCommands({ integrationClass: MyIntegration });
|
|
10
|
-
* await frigg.usage.totals({ metric: 'records.synced', groupBy: 'integrationType', since });
|
|
11
|
-
* await frigg.usage.series({ metric: 'records.synced', integrationType: 'hubspot', bucket: 'day' });
|
|
12
|
-
*/
|
|
1
|
+
// `frigg.usage.*` — the read/write surface over the durable usage store.
|
|
13
2
|
const {
|
|
14
3
|
createUsageRepository,
|
|
15
4
|
} = require('../../usage/repositories/usage-repository-factory');
|
|
16
5
|
const { computeUsageWindows } = require('../../telemetry/usage-windows');
|
|
17
6
|
const { resolveNorthStarEntry } = require('../../telemetry/north-star');
|
|
18
7
|
|
|
19
|
-
function createUsageCommands({ usageRepository
|
|
8
|
+
function createUsageCommands({ usageRepository } = {}) {
|
|
20
9
|
const repository = usageRepository || createUsageRepository();
|
|
21
10
|
|
|
22
11
|
return {
|
|
23
|
-
|
|
24
|
-
* Record a usage counter for a point in time. Callers pass `at` (a Date,
|
|
25
|
-
* default now) — NOT a raw window key — and both the day and hour windows
|
|
26
|
-
* are derived, matching how the auto-rollup persists so series() reads
|
|
27
|
-
* back consistently at either granularity.
|
|
28
|
-
*/
|
|
12
|
+
// Increments both the day and hour window rows for `at`.
|
|
29
13
|
async recordUsageCounter({
|
|
30
14
|
integrationId,
|
|
31
15
|
integrationType,
|
|
@@ -33,7 +17,8 @@ function createUsageCommands({ usageRepository, northStar = null } = {}) {
|
|
|
33
17
|
value = 1,
|
|
34
18
|
at = new Date(),
|
|
35
19
|
}) {
|
|
36
|
-
|
|
20
|
+
const windows = computeUsageWindows(at);
|
|
21
|
+
for (const window of windows) {
|
|
37
22
|
await repository.increment({
|
|
38
23
|
integrationId,
|
|
39
24
|
integrationType,
|
|
@@ -44,25 +29,20 @@ function createUsageCommands({ usageRepository, northStar = null } = {}) {
|
|
|
44
29
|
}
|
|
45
30
|
},
|
|
46
31
|
|
|
47
|
-
async
|
|
48
|
-
return repository.
|
|
32
|
+
async getTotalsByDimension(args) {
|
|
33
|
+
return repository.getTotalsByDimension(args);
|
|
49
34
|
},
|
|
50
35
|
|
|
51
|
-
async
|
|
52
|
-
return repository.
|
|
36
|
+
async getTimeSeries(args) {
|
|
37
|
+
return repository.getTimeSeries(args);
|
|
53
38
|
},
|
|
54
39
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* then returns its totals from the durable usage store. Returns `null`
|
|
59
|
-
* when no North Star is configured, so callers can branch without
|
|
60
|
-
* knowing the counter key. Trends read via `series({ metric })`.
|
|
61
|
-
*/
|
|
62
|
-
async northStar({ integrationType, since, groupBy = 'integrationType', bucket } = {}) {
|
|
40
|
+
// Caller supplies the North Star config (Definition.telemetry.northStar);
|
|
41
|
+
// resolves the counter for the type (byType > default), null if none.
|
|
42
|
+
async getNorthStarTotals({ northStar, integrationType, since, groupBy = 'integrationType', bucket } = {}) {
|
|
63
43
|
const entry = resolveNorthStarEntry(northStar, integrationType);
|
|
64
44
|
if (!entry) return null;
|
|
65
|
-
const totals = await repository.
|
|
45
|
+
const totals = await repository.getTotalsByDimension({
|
|
66
46
|
metric: entry.name,
|
|
67
47
|
groupBy,
|
|
68
48
|
since,
|
package/application/index.js
CHANGED
|
@@ -17,11 +17,6 @@ const { createUsageCommands } = require('./commands/usage-commands');
|
|
|
17
17
|
*
|
|
18
18
|
* @param {Object} params
|
|
19
19
|
* @param {Object} params.integrationClass - Integration class (required)
|
|
20
|
-
* @param {Object} [params.northStar] - Resolved North Star config
|
|
21
|
-
* (`Definition.telemetry.northStar`) so `frigg.usage.northStar(...)` can read
|
|
22
|
-
* it (ADR-011 Decision 5). Injected by the caller that owns the app definition
|
|
23
|
-
* (the handler/report runner) — the application layer never reaches up to load
|
|
24
|
-
* it. Omitted → usage reads report no North Star.
|
|
25
20
|
* @returns {Object} Unified commands object with all CRUD operations
|
|
26
21
|
*
|
|
27
22
|
* @example
|
|
@@ -29,7 +24,7 @@ const { createUsageCommands } = require('./commands/usage-commands');
|
|
|
29
24
|
* const user = await commands.createUser({ username: 'user@example.com' });
|
|
30
25
|
* const credential = await commands.createCredential({ userId: user.id, ... });
|
|
31
26
|
*/
|
|
32
|
-
function createFriggCommands({ integrationClass
|
|
27
|
+
function createFriggCommands({ integrationClass }) {
|
|
33
28
|
// All commands use Frigg's default repositories and use cases
|
|
34
29
|
const integrationCommands = createIntegrationCommands({ integrationClass });
|
|
35
30
|
|
|
@@ -57,9 +52,10 @@ function createFriggCommands({ integrationClass, northStar = null }) {
|
|
|
57
52
|
// Process commands
|
|
58
53
|
...processCommands,
|
|
59
54
|
|
|
60
|
-
// Usage read/write
|
|
61
|
-
//
|
|
62
|
-
|
|
55
|
+
// Usage read/write — nested to match `frigg.usage.*`. The North Star
|
|
56
|
+
// read takes its config as a call argument, so nothing telemetry-specific
|
|
57
|
+
// is threaded through this general factory.
|
|
58
|
+
usage: createUsageCommands(),
|
|
63
59
|
};
|
|
64
60
|
}
|
|
65
61
|
|
package/core/create-handler.js
CHANGED
|
@@ -153,8 +153,8 @@ const createHandler = (optionByName = {}) => {
|
|
|
153
153
|
? usageRollup
|
|
154
154
|
: getUsageRollupSubscriber();
|
|
155
155
|
|
|
156
|
-
// Wire adopter-declared telemetry subscribers once per cold start
|
|
157
|
-
//
|
|
156
|
+
// Wire adopter-declared telemetry subscribers once per cold start.
|
|
157
|
+
// Memoized in the singleton, so this is a cheap
|
|
158
158
|
// no-op after the first invocation.
|
|
159
159
|
getPluginTelemetrySubscribers();
|
|
160
160
|
|
|
@@ -25,7 +25,7 @@ class IntegrationEventDispatcher {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Resolve + invoke a handler, auto-instrumented
|
|
28
|
+
* Resolve + invoke a handler, auto-instrumented. This
|
|
29
29
|
* is the seam for queue/webhook/defined-route dispatch; the `this.on` path
|
|
30
30
|
* (user actions, lifecycle) is instrumented in IntegrationBase.send().
|
|
31
31
|
*/
|
package/index.js
CHANGED
|
@@ -79,7 +79,7 @@ class IntegrationBase {
|
|
|
79
79
|
// Tier 3 Integration Extensions — see packages/core/integrations/EXTENSIONS.md
|
|
80
80
|
// Shape: { [bindingName]: { extension, handlers?: { [eventName]: methodName } } }
|
|
81
81
|
extensions: {},
|
|
82
|
-
// Usage-counter opt-in
|
|
82
|
+
// Usage-counter opt-in. Declaring a canonical key opts into the
|
|
83
83
|
// cross-integration comparison report + durable rollup; custom keys are
|
|
84
84
|
// comparable within this integration type. Shape:
|
|
85
85
|
// usage: { canonical: ['records.synced', ...], custom: { 'deals.enriched': { unit, label } } }
|
|
@@ -108,7 +108,7 @@ class IntegrationBase {
|
|
|
108
108
|
this.messages = { errors: [], warnings: [] };
|
|
109
109
|
this._isHydrated = false;
|
|
110
110
|
|
|
111
|
-
// Telemetry
|
|
111
|
+
// Telemetry: every instance carries the service so integration
|
|
112
112
|
// code can call `this.telemetry.*`. Extracted before the record check so
|
|
113
113
|
// passing only `telemetry` never triggers a hollow hydration. Bound to
|
|
114
114
|
// this instance so emissions auto-carry `integration_type` for the usage
|
|
@@ -223,7 +223,7 @@ class IntegrationBase {
|
|
|
223
223
|
|
|
224
224
|
this._isHydrated = Boolean(this.id);
|
|
225
225
|
|
|
226
|
-
//
|
|
226
|
+
// Log the instance-open exactly once per hydrated
|
|
227
227
|
// instance, carrying the standard identifier set — so an integration is
|
|
228
228
|
// visible in telemetry even on a path that never dispatches a handler.
|
|
229
229
|
// High-cardinality ids ride the bus context (3rd arg), never metric
|
|
@@ -249,7 +249,7 @@ class IntegrationBase {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
/**
|
|
252
|
-
* Standard telemetry identifier set
|
|
252
|
+
* Standard telemetry identifier set. Assembled from the
|
|
253
253
|
* hydrated record (integrationId, userId, version), the static Definition
|
|
254
254
|
* (integrationType, version fallback), and the environment (stage, appName).
|
|
255
255
|
* High-cardinality ids (integrationId, userId) ride span baggage only — never
|
|
@@ -812,7 +812,7 @@ class IntegrationBase {
|
|
|
812
812
|
`Event ${event} is not defined in the Integration event object`
|
|
813
813
|
);
|
|
814
814
|
}
|
|
815
|
-
// Auto-instrument
|
|
815
|
+
// Auto-instrument. This is the seam for user
|
|
816
816
|
// actions, config-options, and lifecycle events dispatched via `this.on`
|
|
817
817
|
// (the queue/webhook/route paths go through IntegrationEventDispatcher).
|
|
818
818
|
return instrumentHandler(
|
|
@@ -44,7 +44,7 @@ class Requester extends Delegate {
|
|
|
44
44
|
// Instance methods can use this.fetch without differentiating
|
|
45
45
|
this.fetch = get(params, 'fetch', fetch);
|
|
46
46
|
|
|
47
|
-
// Telemetry
|
|
47
|
+
// Telemetry. Defaults to the process singleton; overridable
|
|
48
48
|
// for tests. Outbound requests are instrumented in `_request`.
|
|
49
49
|
//
|
|
50
50
|
// Attribution boundary: the `frigg.apimodule.requests` OTel metric fires
|
|
@@ -103,7 +103,7 @@ class Requester extends Delegate {
|
|
|
103
103
|
};
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
|
-
* Instrumenting entry point
|
|
106
|
+
* Instrumenting entry point. Wraps the whole logical request —
|
|
107
107
|
* including retry/refresh recursion — in a single span + one
|
|
108
108
|
* `frigg.apimodule.requests` counter, emitted on the `i === 0` boundary so
|
|
109
109
|
* retries are never double-counted. The full URL rides the span only; the
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.622.
|
|
4
|
+
"version": "2.0.0--canary.622.ce0381c.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@friggframework/eslint-config": "2.0.0--canary.622.
|
|
49
|
-
"@friggframework/prettier-config": "2.0.0--canary.622.
|
|
50
|
-
"@friggframework/test": "2.0.0--canary.622.
|
|
48
|
+
"@friggframework/eslint-config": "2.0.0--canary.622.ce0381c.0",
|
|
49
|
+
"@friggframework/prettier-config": "2.0.0--canary.622.ce0381c.0",
|
|
50
|
+
"@friggframework/test": "2.0.0--canary.622.ce0381c.0",
|
|
51
51
|
"@prisma/client": "^6.19.3",
|
|
52
52
|
"@types/lodash": "4.17.15",
|
|
53
53
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -87,5 +87,5 @@
|
|
|
87
87
|
"publishConfig": {
|
|
88
88
|
"access": "public"
|
|
89
89
|
},
|
|
90
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "ce0381c83a8fdebf49352eb9cd6b38b360130025"
|
|
91
91
|
}
|
|
@@ -40,7 +40,7 @@ function buildTypeLabels() {
|
|
|
40
40
|
|
|
41
41
|
function createReportingRouter() {
|
|
42
42
|
const reportingRepository = createReportingRepository();
|
|
43
|
-
//
|
|
43
|
+
// Enrich the report with feature-usage columns read from
|
|
44
44
|
// the Frigg-owned usage store. Best-effort — a failure to construct it never
|
|
45
45
|
// blocks the structural report.
|
|
46
46
|
let usageRepository = null;
|
|
@@ -26,7 +26,7 @@ class ListIntegrationsReport {
|
|
|
26
26
|
throw new Error('reportingRepository is required');
|
|
27
27
|
}
|
|
28
28
|
this.reportingRepository = reportingRepository;
|
|
29
|
-
// Optional
|
|
29
|
+
// Optional usage store (the ADR-010 hand-off). When present, the
|
|
30
30
|
// report's byType buckets are enriched with feature-usage columns read
|
|
31
31
|
// ONLY from this store — never an external APM.
|
|
32
32
|
this.usageRepository = usageRepository || null;
|
|
@@ -87,7 +87,7 @@ class ListIntegrationsReport {
|
|
|
87
87
|
bucket.byStatus[statusKey] = (bucket.byStatus[statusKey] ?? 0) + 1;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// Additive usage columns
|
|
90
|
+
// Additive usage columns. Read per-type totals for each
|
|
91
91
|
// canonical counter; a type with no rows reads as 0. Guarded so a usage
|
|
92
92
|
// store failure never breaks the structural report.
|
|
93
93
|
if (this.usageRepository) {
|
|
@@ -118,7 +118,7 @@ class ListIntegrationsReport {
|
|
|
118
118
|
try {
|
|
119
119
|
const totalsByMetric = await Promise.all(
|
|
120
120
|
metrics.map(async (metric) => {
|
|
121
|
-
const totals = await this.usageRepository.
|
|
121
|
+
const totals = await this.usageRepository.getTotalsByDimension({
|
|
122
122
|
metric,
|
|
123
123
|
groupBy: 'integrationType',
|
|
124
124
|
});
|
package/telemetry/README.md
CHANGED
|
@@ -55,6 +55,15 @@ const Definition = {
|
|
|
55
55
|
defaults to `none` (no per-event cost, no data written to CloudWatch). Point
|
|
56
56
|
`exporter` at an OTLP backend to turn export on.
|
|
57
57
|
|
|
58
|
+
**Sampling (`sampleRatio`, `0..1`, default `1`):** the fraction of **traces**
|
|
59
|
+
exported — a cost knob for high-traffic fleets (`0.1` ≈ keep 10%). Whole traces
|
|
60
|
+
are sampled (trace-ID-based + parent-based, so a distributed trace is never
|
|
61
|
+
half-kept), and it does **not** thin the durable **usage counters** — those stay
|
|
62
|
+
exact at any ratio (they ride the event bus, not the sampled trace pipeline). It
|
|
63
|
+
is **not** error-aware: a low ratio drops failed-run traces too, so for "keep all
|
|
64
|
+
errors, sample the rest" use tail-based sampling at an OTel Collector, not this
|
|
65
|
+
knob. Typical: `1.0` in dev, lower (e.g. `0.1`) in high-volume prod.
|
|
66
|
+
|
|
58
67
|
### Environment variables
|
|
59
68
|
|
|
60
69
|
| Variable | Purpose |
|
|
@@ -154,7 +163,7 @@ const { createFriggCommands } = require('@friggframework/core');
|
|
|
154
163
|
const frigg = createFriggCommands({ integrationClass: HubSpotIntegration });
|
|
155
164
|
|
|
156
165
|
// Apples-to-apples comparison across integration types:
|
|
157
|
-
await frigg.usage.
|
|
166
|
+
await frigg.usage.getTotalsByDimension({
|
|
158
167
|
metric: 'records.synced',
|
|
159
168
|
groupBy: 'integrationType', // or 'metric'
|
|
160
169
|
since: daysAgo(30),
|
|
@@ -163,7 +172,7 @@ await frigg.usage.totals({
|
|
|
163
172
|
// → [{ integrationType: 'hubspot', value: 4200 }, { integrationType: 'salesforce', value: 1180 }]
|
|
164
173
|
|
|
165
174
|
// Trend series for one type (aggregated across its instances):
|
|
166
|
-
await frigg.usage.
|
|
175
|
+
await frigg.usage.getTimeSeries({
|
|
167
176
|
metric: 'records.synced',
|
|
168
177
|
integrationType: 'hubspot',
|
|
169
178
|
from: daysAgo(7),
|
|
@@ -211,19 +220,24 @@ Read it as a first-class metric without knowing the configured key — the North
|
|
|
211
220
|
Star resolves per integration type (`byType` wins over `default`):
|
|
212
221
|
|
|
213
222
|
```js
|
|
214
|
-
//
|
|
215
|
-
|
|
223
|
+
// The caller passes the North Star config it already holds (from the app
|
|
224
|
+
// definition); this resolves the counter for the type and returns its totals.
|
|
225
|
+
await frigg.usage.getNorthStarTotals({
|
|
226
|
+
northStar: definition.telemetry.northStar,
|
|
227
|
+
integrationType: 'hubspot',
|
|
228
|
+
since: daysAgo(30),
|
|
229
|
+
});
|
|
216
230
|
// → { metric: 'contacts_synced', totals: [{ integrationType: 'hubspot', value: 900 }] }
|
|
217
|
-
// → null when
|
|
231
|
+
// → null when `northStar` is absent or has no entry for the type
|
|
218
232
|
```
|
|
219
233
|
|
|
220
234
|
Or read it like any counter once you know the key:
|
|
221
|
-
`frigg.usage.
|
|
235
|
+
`frigg.usage.getTotalsByDimension({ metric: 'contacts_synced' })`; trends via `frigg.usage.getTimeSeries({ metric })`.
|
|
222
236
|
|
|
223
|
-
>
|
|
224
|
-
>
|
|
225
|
-
>
|
|
226
|
-
> returns `null`.
|
|
237
|
+
> The North Star read takes its config as a **call argument** — nothing
|
|
238
|
+
> telemetry-specific is threaded through `createFriggCommands`. The caller that
|
|
239
|
+
> owns the app definition (e.g. a report runner) passes `telemetry.northStar` in;
|
|
240
|
+
> omit it and `getNorthStarTotals()` returns `null`.
|
|
227
241
|
|
|
228
242
|
## Plugin / extension tap
|
|
229
243
|
|
|
@@ -286,7 +300,7 @@ this.telemetry.count / auto-instrumented seam
|
|
|
286
300
|
│ (discards on SQS redelivery — approximate contract)
|
|
287
301
|
└─ your plugin taps
|
|
288
302
|
|
|
289
|
-
frigg.usage.
|
|
303
|
+
frigg.usage.getTotalsByDimension / getTimeSeries ◄── UsageCounter store ──► reporting usage columns
|
|
290
304
|
```
|
|
291
305
|
|
|
292
306
|
- **Flush is Lambda-safe:** `create-handler` awaits a bounded `forceFlush` in a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wrap a telemetry service so an integration instance's emissions automatically
|
|
3
|
-
* carry its `integration_type
|
|
3
|
+
* carry its `integration_type`. This lets developers write
|
|
4
4
|
* `this.telemetry.count('records.synced', n, { entity })` with no per-call
|
|
5
5
|
* boilerplate, while the usage rollup still attributes the counter to the right
|
|
6
6
|
* integration type.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical usage-counter vocabulary
|
|
2
|
+
* Canonical usage-counter vocabulary.
|
|
3
3
|
*
|
|
4
4
|
* These core-owned, versioned keys are the ONLY metrics guaranteed comparable
|
|
5
5
|
* *across* integration types — they power ADR-010's apples-to-apples comparison
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const {
|
|
2
|
+
TelemetryExporterInterface,
|
|
3
|
+
} = require('./telemetry-exporter-interface');
|
|
4
|
+
|
|
5
|
+
/** Console span/metric exporters — local-dev visibility, no network. */
|
|
6
|
+
class ConsoleExporter extends TelemetryExporterInterface {
|
|
7
|
+
build() {
|
|
8
|
+
const {
|
|
9
|
+
ConsoleSpanExporter,
|
|
10
|
+
} = require('@opentelemetry/sdk-trace-base');
|
|
11
|
+
const { ConsoleMetricExporter } = require('@opentelemetry/sdk-metrics');
|
|
12
|
+
return {
|
|
13
|
+
traceExporter: new ConsoleSpanExporter(),
|
|
14
|
+
metricExporter: new ConsoleMetricExporter(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { ConsoleExporter };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { OtlpExporter } = require('./otlp-exporter');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Datadog ingests OTLP/HTTP directly, so today it behaves exactly like
|
|
5
|
+
* `OtlpExporter` (endpoint + headers). It is its own class so Datadog-specific
|
|
6
|
+
* presets (e.g. a default `DD-API-KEY` header) can be added later without
|
|
7
|
+
* touching the OTLP exporter or the registry.
|
|
8
|
+
*/
|
|
9
|
+
class DatadogExporter extends OtlpExporter {}
|
|
10
|
+
|
|
11
|
+
module.exports = { DatadogExporter };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { OtlpExporter } = require('./otlp-exporter');
|
|
2
|
+
|
|
3
|
+
/** Honeycomb: an OTLP preset — default endpoint + `x-honeycomb-team` from apiKey. */
|
|
4
|
+
class HoneycombExporter extends OtlpExporter {
|
|
5
|
+
constructor({ endpoint, apiKey, headers } = {}) {
|
|
6
|
+
super({
|
|
7
|
+
endpoint: endpoint || 'https://api.honeycomb.io',
|
|
8
|
+
headers: apiKey ? { 'x-honeycomb-team': apiKey } : headers,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { HoneycombExporter };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const {
|
|
2
|
+
TelemetryExporterInterface,
|
|
3
|
+
} = require('./telemetry-exporter-interface');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OTLP/HTTP exporter to a configured endpoint (+ optional headers). With no
|
|
7
|
+
* endpoint the OTel SDK falls back to the standard `OTEL_EXPORTER_OTLP_ENDPOINT`
|
|
8
|
+
* env var, so `url` is left unset in that case.
|
|
9
|
+
*/
|
|
10
|
+
class OtlpExporter extends TelemetryExporterInterface {
|
|
11
|
+
constructor({ endpoint, headers } = {}) {
|
|
12
|
+
super();
|
|
13
|
+
this.endpoint = endpoint;
|
|
14
|
+
this.headers = headers;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
build() {
|
|
18
|
+
const {
|
|
19
|
+
OTLPTraceExporter,
|
|
20
|
+
} = require('@opentelemetry/exporter-trace-otlp-http');
|
|
21
|
+
const {
|
|
22
|
+
OTLPMetricExporter,
|
|
23
|
+
} = require('@opentelemetry/exporter-metrics-otlp-http');
|
|
24
|
+
return {
|
|
25
|
+
traceExporter: new OTLPTraceExporter(this._options('/v1/traces')),
|
|
26
|
+
metricExporter: new OTLPMetricExporter(this._options('/v1/metrics')),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_options(path) {
|
|
31
|
+
return {
|
|
32
|
+
...(this.endpoint ? { url: joinPath(this.endpoint, path) } : {}),
|
|
33
|
+
...(this.headers ? { headers: this.headers } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function joinPath(base, path) {
|
|
39
|
+
return `${String(base).replace(/\/$/, '')}${path}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { OtlpExporter };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const {
|
|
2
|
+
TelemetryExporterInterface,
|
|
3
|
+
} = require('./telemetry-exporter-interface');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adopter/test-supplied exporter instances, used as-is (e.g. in-memory exporters
|
|
7
|
+
* in tests, or an advanced adopter wiring its own OTel exporter). Pre-built
|
|
8
|
+
* instances win over a `type`.
|
|
9
|
+
*/
|
|
10
|
+
class PassthroughExporter extends TelemetryExporterInterface {
|
|
11
|
+
constructor({ traceExporter = null, metricExporter = null } = {}) {
|
|
12
|
+
super();
|
|
13
|
+
this.traceExporter = traceExporter;
|
|
14
|
+
this.metricExporter = metricExporter;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
build() {
|
|
18
|
+
return {
|
|
19
|
+
traceExporter: this.traceExporter,
|
|
20
|
+
metricExporter: this.metricExporter,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { PassthroughExporter };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const { PassthroughExporter } = require('./passthrough-exporter');
|
|
2
|
+
const { ConsoleExporter } = require('./console-exporter');
|
|
3
|
+
const { OtlpExporter } = require('./otlp-exporter');
|
|
4
|
+
const { HoneycombExporter } = require('./honeycomb-exporter');
|
|
5
|
+
const { DatadogExporter } = require('./datadog-exporter');
|
|
6
|
+
|
|
7
|
+
// type → exporter adapter. Add a destination by adding a class + one entry.
|
|
8
|
+
const EXPORTERS = {
|
|
9
|
+
console: ConsoleExporter,
|
|
10
|
+
otlp: OtlpExporter,
|
|
11
|
+
datadog: DatadogExporter,
|
|
12
|
+
honeycomb: HoneycombExporter,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the exporter adapter for a telemetry descriptor. Pre-built exporter
|
|
17
|
+
* instances (tests / advanced adopters) win over `type`; an unknown or absent
|
|
18
|
+
* type falls back to plain OTLP (which itself falls back to the standard OTLP
|
|
19
|
+
* env var). Replaces the former switch-based factory with polymorphic adapters.
|
|
20
|
+
*
|
|
21
|
+
* @returns {import('./telemetry-exporter-interface').TelemetryExporterInterface}
|
|
22
|
+
*/
|
|
23
|
+
function resolveExporter(descriptor = {}) {
|
|
24
|
+
if (descriptor.traceExporter || descriptor.metricExporter) {
|
|
25
|
+
return new PassthroughExporter(descriptor);
|
|
26
|
+
}
|
|
27
|
+
// Own-property check so a `type` matching an Object.prototype member
|
|
28
|
+
// ('constructor', 'toString', …) can't resolve an inherited key — unknown
|
|
29
|
+
// types must fall through to plain OTLP.
|
|
30
|
+
const Exporter = Object.prototype.hasOwnProperty.call(
|
|
31
|
+
EXPORTERS,
|
|
32
|
+
descriptor.type
|
|
33
|
+
)
|
|
34
|
+
? EXPORTERS[descriptor.type]
|
|
35
|
+
: OtlpExporter;
|
|
36
|
+
return new Exporter(descriptor);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { resolveExporter, EXPORTERS };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port for a telemetry exporter: builds the OTel trace + metric exporter
|
|
3
|
+
* instances for one destination. Adapters (console, otlp, honeycomb, datadog, a
|
|
4
|
+
* passthrough for pre-built instances) implement `build()`. OpenTelemetry SDK
|
|
5
|
+
* packages are `require`d lazily inside `build()` so merely loading an adapter
|
|
6
|
+
* pulls in no OTel modules.
|
|
7
|
+
*/
|
|
8
|
+
class TelemetryExporterInterface {
|
|
9
|
+
/** @returns {{ traceExporter: object|null, metricExporter: object|null }} */
|
|
10
|
+
build() {
|
|
11
|
+
throw new Error(
|
|
12
|
+
'build() must be implemented by a TelemetryExporter subclass'
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { TelemetryExporterInterface };
|
package/telemetry/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
const { createTelemetry, isNoOpExporter } = require('./telemetry-service');
|
|
2
|
-
const {
|
|
2
|
+
const {
|
|
3
|
+
TelemetryServiceInterface,
|
|
4
|
+
} = require('./telemetry-service-interface');
|
|
5
|
+
const { NoOpTelemetry } = require('./no-op-telemetry');
|
|
3
6
|
const { createTelemetryEventBus } = require('./telemetry-event-bus');
|
|
4
7
|
const { resolveTelemetryConfig } = require('./telemetry-config');
|
|
5
8
|
const {
|
|
6
9
|
getTelemetry,
|
|
7
10
|
resetTelemetryForTests,
|
|
8
11
|
} = require('./telemetry-singleton');
|
|
9
|
-
const {
|
|
12
|
+
const { resolveExporter } = require('./exporters/resolve-exporter');
|
|
10
13
|
const { instrumentHandler } = require('./instrument-handler');
|
|
11
14
|
const { bindTelemetryContext } = require('./bind-telemetry-context');
|
|
12
15
|
const { createUsageRollupSubscriber } = require('./usage-rollup-subscriber');
|
|
@@ -32,13 +35,14 @@ const {
|
|
|
32
35
|
|
|
33
36
|
module.exports = {
|
|
34
37
|
createTelemetry,
|
|
35
|
-
|
|
38
|
+
TelemetryServiceInterface,
|
|
39
|
+
NoOpTelemetry,
|
|
36
40
|
createTelemetryEventBus,
|
|
37
41
|
resolveTelemetryConfig,
|
|
38
42
|
getTelemetry,
|
|
39
43
|
resetTelemetryForTests,
|
|
40
44
|
isNoOpExporter,
|
|
41
|
-
|
|
45
|
+
resolveExporter,
|
|
42
46
|
instrumentHandler,
|
|
43
47
|
bindTelemetryContext,
|
|
44
48
|
createUsageRollupSubscriber,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wraps a single integration handler invocation with a span + the
|
|
3
|
-
* `frigg.handler.invocations` counter
|
|
3
|
+
* `frigg.handler.invocations` counter. Shared by the two
|
|
4
4
|
* dispatch seams — `IntegrationBase.send()` and `IntegrationEventDispatcher` —
|
|
5
5
|
* so both paths are instrumented identically.
|
|
6
6
|
*
|