@friggframework/core 2.0.0--canary.622.faffe5f.0 → 2.0.0--canary.623.71305f2.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 +0 -40
- package/application/index.js +10 -15
- package/core/create-handler.js +10 -114
- package/generated/prisma-mongodb/edge.js +4 -16
- package/generated/prisma-mongodb/index-browser.js +1 -13
- package/generated/prisma-mongodb/index.d.ts +96 -1495
- package/generated/prisma-mongodb/index.js +4 -16
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +0 -24
- package/generated/prisma-mongodb/wasm.js +4 -16
- package/generated/prisma-postgresql/edge.js +4 -16
- package/generated/prisma-postgresql/index-browser.js +1 -13
- package/generated/prisma-postgresql/index.d.ts +91 -1541
- package/generated/prisma-postgresql/index.js +4 -16
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +0 -23
- package/generated/prisma-postgresql/wasm.js +4 -16
- package/handlers/app-definition-loader.js +3 -26
- package/handlers/integration-event-dispatcher.js +15 -34
- package/handlers/routers/integration-webhook-routers.js +7 -23
- package/index.js +9 -11
- package/integrations/integration-base.js +23 -88
- package/modules/module.js +15 -2
- package/modules/requester/requester.js +46 -111
- package/package.json +5 -12
- package/prisma-mongodb/schema.prisma +0 -24
- package/prisma-postgresql/schema.prisma +0 -23
- package/reporting/README.md +1 -8
- package/reporting/reporting-router.js +1 -16
- package/reporting/use-cases/list-integrations-report.js +6 -61
- package/application/commands/usage-commands.js +0 -77
- package/prisma-postgresql/migrations/20260705000000_create_usage_counter/migration.sql +0 -26
- package/telemetry/README.md +0 -317
- package/telemetry/bind-telemetry-context.js +0 -51
- package/telemetry/canonical-counters.js +0 -52
- package/telemetry/exporters/exporter-factory.js +0 -79
- package/telemetry/index.js +0 -56
- package/telemetry/instrument-handler.js +0 -82
- package/telemetry/no-op-telemetry.js +0 -66
- package/telemetry/north-star.js +0 -103
- package/telemetry/otel-telemetry.js +0 -215
- package/telemetry/plugin-subscribers-singleton.js +0 -51
- package/telemetry/plugin-subscribers.js +0 -77
- package/telemetry/telemetry-config.js +0 -118
- package/telemetry/telemetry-context.js +0 -40
- package/telemetry/telemetry-event-bus.js +0 -58
- package/telemetry/telemetry-service.js +0 -44
- package/telemetry/telemetry-singleton.js +0 -41
- package/telemetry/usage-rollup-singleton.js +0 -74
- package/telemetry/usage-rollup-subscriber.js +0 -116
- package/telemetry/usage-windows.js +0 -14
- package/usage/README.md +0 -54
- package/usage/index.js +0 -19
- package/usage/repositories/usage-repository-documentdb.js +0 -199
- package/usage/repositories/usage-repository-factory.js +0 -28
- package/usage/repositories/usage-repository-interface.js +0 -37
- package/usage/repositories/usage-repository-mongo.js +0 -12
- package/usage/repositories/usage-repository-postgres.js +0 -152
- package/usage/tracked-metrics.js +0 -38
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds OpenTelemetry span/metric exporters from an app-definition descriptor.
|
|
3
|
-
* All OTel packages are lazy-`require`d here so the no-op
|
|
4
|
-
* path (which never calls this) loads zero OTel modules.
|
|
5
|
-
*
|
|
6
|
-
* Supported descriptor `type`s:
|
|
7
|
-
* - 'console' → ConsoleSpanExporter / ConsoleMetricExporter (local dev)
|
|
8
|
-
* - 'otlp' → OTLP/HTTP to descriptor.endpoint (+ headers)
|
|
9
|
-
* - 'honeycomb' → OTLP preset (api.honeycomb.io, x-honeycomb-team header)
|
|
10
|
-
* - 'datadog' → OTLP preset (descriptor.endpoint required)
|
|
11
|
-
*
|
|
12
|
-
* A descriptor may also carry pre-built exporter instances
|
|
13
|
-
* (`traceExporter` / `metricExporter`) — used by tests (in-memory exporters) and
|
|
14
|
-
* advanced adopters. Instances win over `type`.
|
|
15
|
-
*
|
|
16
|
-
* @returns {{ traceExporter: object|null, metricExporter: object|null }}
|
|
17
|
-
*/
|
|
18
|
-
function buildExporters(descriptor = {}) {
|
|
19
|
-
if (descriptor.traceExporter || descriptor.metricExporter) {
|
|
20
|
-
return {
|
|
21
|
-
traceExporter: descriptor.traceExporter || null,
|
|
22
|
-
metricExporter: descriptor.metricExporter || null,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const type = descriptor.type;
|
|
27
|
-
|
|
28
|
-
if (type === 'console') {
|
|
29
|
-
const {
|
|
30
|
-
ConsoleSpanExporter,
|
|
31
|
-
} = require('@opentelemetry/sdk-trace-base');
|
|
32
|
-
const { ConsoleMetricExporter } = require('@opentelemetry/sdk-metrics');
|
|
33
|
-
return {
|
|
34
|
-
traceExporter: new ConsoleSpanExporter(),
|
|
35
|
-
metricExporter: new ConsoleMetricExporter(),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const { endpoint, headers } = resolveOtlpTarget(descriptor);
|
|
40
|
-
const {
|
|
41
|
-
OTLPTraceExporter,
|
|
42
|
-
} = require('@opentelemetry/exporter-trace-otlp-http');
|
|
43
|
-
const {
|
|
44
|
-
OTLPMetricExporter,
|
|
45
|
-
} = require('@opentelemetry/exporter-metrics-otlp-http');
|
|
46
|
-
|
|
47
|
-
// When no endpoint is set the exporters fall back to the standard
|
|
48
|
-
// OTEL_EXPORTER_OTLP_ENDPOINT env var, so leave `url` undefined in that case.
|
|
49
|
-
return {
|
|
50
|
-
traceExporter: new OTLPTraceExporter({
|
|
51
|
-
...(endpoint ? { url: joinPath(endpoint, '/v1/traces') } : {}),
|
|
52
|
-
...(headers ? { headers } : {}),
|
|
53
|
-
}),
|
|
54
|
-
metricExporter: new OTLPMetricExporter({
|
|
55
|
-
...(endpoint ? { url: joinPath(endpoint, '/v1/metrics') } : {}),
|
|
56
|
-
...(headers ? { headers } : {}),
|
|
57
|
-
}),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Resolve endpoint + headers for OTLP and its vendor presets. */
|
|
62
|
-
function resolveOtlpTarget(descriptor) {
|
|
63
|
-
if (descriptor.type === 'honeycomb') {
|
|
64
|
-
return {
|
|
65
|
-
endpoint: descriptor.endpoint || 'https://api.honeycomb.io',
|
|
66
|
-
headers: descriptor.apiKey
|
|
67
|
-
? { 'x-honeycomb-team': descriptor.apiKey }
|
|
68
|
-
: descriptor.headers,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
// 'otlp' and 'datadog' both use a plain OTLP endpoint + optional headers.
|
|
72
|
-
return { endpoint: descriptor.endpoint, headers: descriptor.headers };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function joinPath(base, path) {
|
|
76
|
-
return `${String(base).replace(/\/$/, '')}${path}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
module.exports = { buildExporters };
|
package/telemetry/index.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
const { createTelemetry, isNoOpExporter } = require('./telemetry-service');
|
|
2
|
-
const { createNoOpTelemetry } = require('./no-op-telemetry');
|
|
3
|
-
const { createTelemetryEventBus } = require('./telemetry-event-bus');
|
|
4
|
-
const { resolveTelemetryConfig } = require('./telemetry-config');
|
|
5
|
-
const {
|
|
6
|
-
getTelemetry,
|
|
7
|
-
resetTelemetryForTests,
|
|
8
|
-
} = require('./telemetry-singleton');
|
|
9
|
-
const { buildExporters } = require('./exporters/exporter-factory');
|
|
10
|
-
const { instrumentHandler } = require('./instrument-handler');
|
|
11
|
-
const { bindTelemetryContext } = require('./bind-telemetry-context');
|
|
12
|
-
const { createUsageRollupSubscriber } = require('./usage-rollup-subscriber');
|
|
13
|
-
const {
|
|
14
|
-
getUsageRollupSubscriber,
|
|
15
|
-
resetUsageRollupForTests,
|
|
16
|
-
} = require('./usage-rollup-singleton');
|
|
17
|
-
const { computeUsageWindows } = require('./usage-windows');
|
|
18
|
-
const {
|
|
19
|
-
resolveNorthStarEntry,
|
|
20
|
-
northStarKeys,
|
|
21
|
-
createNorthStarDerivationSubscriber,
|
|
22
|
-
} = require('./north-star');
|
|
23
|
-
const {
|
|
24
|
-
CANONICAL_COUNTERS,
|
|
25
|
-
isCanonicalCounter,
|
|
26
|
-
} = require('./canonical-counters');
|
|
27
|
-
const { wireTelemetrySubscribers } = require('./plugin-subscribers');
|
|
28
|
-
const {
|
|
29
|
-
getPluginTelemetrySubscribers,
|
|
30
|
-
resetPluginTelemetrySubscribersForTests,
|
|
31
|
-
} = require('./plugin-subscribers-singleton');
|
|
32
|
-
|
|
33
|
-
module.exports = {
|
|
34
|
-
createTelemetry,
|
|
35
|
-
createNoOpTelemetry,
|
|
36
|
-
createTelemetryEventBus,
|
|
37
|
-
resolveTelemetryConfig,
|
|
38
|
-
getTelemetry,
|
|
39
|
-
resetTelemetryForTests,
|
|
40
|
-
isNoOpExporter,
|
|
41
|
-
buildExporters,
|
|
42
|
-
instrumentHandler,
|
|
43
|
-
bindTelemetryContext,
|
|
44
|
-
createUsageRollupSubscriber,
|
|
45
|
-
getUsageRollupSubscriber,
|
|
46
|
-
resetUsageRollupForTests,
|
|
47
|
-
computeUsageWindows,
|
|
48
|
-
resolveNorthStarEntry,
|
|
49
|
-
northStarKeys,
|
|
50
|
-
createNorthStarDerivationSubscriber,
|
|
51
|
-
CANONICAL_COUNTERS,
|
|
52
|
-
isCanonicalCounter,
|
|
53
|
-
wireTelemetrySubscribers,
|
|
54
|
-
getPluginTelemetrySubscribers,
|
|
55
|
-
resetPluginTelemetrySubscribersForTests,
|
|
56
|
-
};
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wraps a single integration handler invocation with a span + the
|
|
3
|
-
* `frigg.handler.invocations` counter. Shared by the two
|
|
4
|
-
* dispatch seams — `IntegrationBase.send()` and `IntegrationEventDispatcher` —
|
|
5
|
-
* so both paths are instrumented identically.
|
|
6
|
-
*
|
|
7
|
-
* Cardinality discipline: the metric is keyed by the **bounded event type**
|
|
8
|
-
* (USER_ACTION / CRON / QUEUE / WEBHOOK / LIFE_CYCLE_EVENT), never the specific
|
|
9
|
-
* event/action name. High-cardinality ids (integrationId, userId) ride span
|
|
10
|
-
* baggage via `telemetry.withContext`, never metric attributes. The full event
|
|
11
|
-
* name is kept on the span only.
|
|
12
|
-
*
|
|
13
|
-
* @param {object|null} telemetry Telemetry service (no-op-safe; null → just runs fn).
|
|
14
|
-
* @param {object} context Standard identifiers from `getTelemetryContext()`.
|
|
15
|
-
* @param {{event: string, eventType: string}} descriptor Event name + bounded type.
|
|
16
|
-
* @param {Function} fn The handler invocation.
|
|
17
|
-
*/
|
|
18
|
-
async function instrumentHandler(telemetry, context = {}, descriptor = {}, fn) {
|
|
19
|
-
if (!telemetry || typeof telemetry.span !== 'function') {
|
|
20
|
-
return fn();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const integrationType = context.integrationType || 'unknown';
|
|
24
|
-
const eventType = descriptor.eventType || 'unknown';
|
|
25
|
-
const eventName = descriptor.event;
|
|
26
|
-
|
|
27
|
-
const runInstrumented = () =>
|
|
28
|
-
telemetry.span(`frigg.handler.${eventType}`, async (span) => {
|
|
29
|
-
if (span && typeof span.setAttributes === 'function') {
|
|
30
|
-
span.setAttributes({
|
|
31
|
-
integration_type: integrationType,
|
|
32
|
-
event: eventType,
|
|
33
|
-
event_name: eventName,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
// `event_name` (potentially high-cardinality action id) rides the
|
|
37
|
-
// bus-only context — used by North Star derived-from-trace matching,
|
|
38
|
-
// never a metric label.
|
|
39
|
-
const busContext = { event_name: eventName };
|
|
40
|
-
try {
|
|
41
|
-
const result = await fn();
|
|
42
|
-
telemetry.count(
|
|
43
|
-
'frigg.handler.invocations',
|
|
44
|
-
1,
|
|
45
|
-
{
|
|
46
|
-
integration_type: integrationType,
|
|
47
|
-
event: eventType,
|
|
48
|
-
status: 'ok',
|
|
49
|
-
},
|
|
50
|
-
busContext
|
|
51
|
-
);
|
|
52
|
-
return result;
|
|
53
|
-
} catch (err) {
|
|
54
|
-
telemetry.count(
|
|
55
|
-
'frigg.handler.invocations',
|
|
56
|
-
1,
|
|
57
|
-
{
|
|
58
|
-
integration_type: integrationType,
|
|
59
|
-
event: eventType,
|
|
60
|
-
status: 'error',
|
|
61
|
-
},
|
|
62
|
-
busContext
|
|
63
|
-
);
|
|
64
|
-
throw err;
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (typeof telemetry.withContext === 'function') {
|
|
69
|
-
return telemetry.withContext(
|
|
70
|
-
{
|
|
71
|
-
integrationId: context.integrationId,
|
|
72
|
-
userId: context.userId,
|
|
73
|
-
integrationType: context.integrationType,
|
|
74
|
-
version: context.version,
|
|
75
|
-
},
|
|
76
|
-
runInstrumented
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
return runInstrumented();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
module.exports = { instrumentHandler };
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
const { createTelemetryEventBus } = require('./telemetry-event-bus');
|
|
2
|
-
const {
|
|
3
|
-
runWithTelemetryContext,
|
|
4
|
-
mergeTelemetryContext,
|
|
5
|
-
} = require('./telemetry-context');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* No-op telemetry implementation.
|
|
9
|
-
*
|
|
10
|
-
* This is the default when no OTel exporter is configured, so telemetry "rides
|
|
11
|
-
* for free": integration code can call `this.telemetry.*`
|
|
12
|
-
* unconditionally and it costs nothing on the OTel side. Critically, this module
|
|
13
|
-
* imports **zero** OpenTelemetry packages — the no-op path never loads the OTel
|
|
14
|
-
* SDK (guarded by a require-graph test), protecting Lambda cold-start.
|
|
15
|
-
*
|
|
16
|
-
* The internal event bus is still active here (Decision 7): `count`/`event` are
|
|
17
|
-
* mirrored onto it so the durable usage rollup and plugin/extension taps work
|
|
18
|
-
* even with OTel export disabled. Emitting to a bus with no subscribers is a
|
|
19
|
-
* cheap no-op.
|
|
20
|
-
*
|
|
21
|
-
* @param {object} [options]
|
|
22
|
-
* @param {object} [options.bus] Event bus to mirror emissions onto.
|
|
23
|
-
*/
|
|
24
|
-
function createNoOpTelemetry({ bus = createTelemetryEventBus() } = {}) {
|
|
25
|
-
const noop = {
|
|
26
|
-
count(name, value = 1, attributes = {}, context) {
|
|
27
|
-
const merged = mergeTelemetryContext(context);
|
|
28
|
-
const payload = { name, value, attributes };
|
|
29
|
-
if (merged) payload.context = merged;
|
|
30
|
-
bus.emit('metric', payload);
|
|
31
|
-
},
|
|
32
|
-
event(name, attributes = {}, context) {
|
|
33
|
-
const merged = mergeTelemetryContext(context);
|
|
34
|
-
const payload = { name, attributes };
|
|
35
|
-
if (merged) payload.context = merged;
|
|
36
|
-
bus.emit('event', payload);
|
|
37
|
-
},
|
|
38
|
-
async span(_name, fn) {
|
|
39
|
-
return typeof fn === 'function' ? fn() : undefined;
|
|
40
|
-
},
|
|
41
|
-
startSpan() {
|
|
42
|
-
return {
|
|
43
|
-
setAttributes() {},
|
|
44
|
-
setAttribute() {},
|
|
45
|
-
recordException() {},
|
|
46
|
-
setStatus() {},
|
|
47
|
-
end() {},
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
async withContext(context, fn) {
|
|
51
|
-
return runWithTelemetryContext(context, () =>
|
|
52
|
-
typeof fn === 'function' ? fn() : undefined
|
|
53
|
-
);
|
|
54
|
-
},
|
|
55
|
-
on(eventType, callback) {
|
|
56
|
-
return bus.on(eventType, callback);
|
|
57
|
-
},
|
|
58
|
-
async forceFlush() {},
|
|
59
|
-
isEnabled() {
|
|
60
|
-
return false;
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
return noop;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
module.exports = { createNoOpTelemetry };
|
package/telemetry/north-star.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adopter North Star metric. A North Star simply references
|
|
3
|
-
* a usage-counter key (canonical or custom); its value is then read like any
|
|
4
|
-
* other counter via `frigg.usage.totals({ metric: northStarKey })`. It is
|
|
5
|
-
* populated either by (a) direct emission of that counter, or (b) derived from a
|
|
6
|
-
* framework trace signal — this module implements (b) as a small bus subscriber
|
|
7
|
-
* that emits the North Star counter when a configured signal matches.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** Resolve the North Star counter entry for an integration type (byType wins). */
|
|
11
|
-
function resolveNorthStarEntry(northStar, integrationType) {
|
|
12
|
-
if (!northStar) return null;
|
|
13
|
-
return (
|
|
14
|
-
(northStar.byType && northStar.byType[integrationType]) ||
|
|
15
|
-
northStar.default ||
|
|
16
|
-
null
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** The set of counter keys any North Star references — added to trackedMetrics. */
|
|
21
|
-
function northStarKeys(northStar) {
|
|
22
|
-
const keys = new Set();
|
|
23
|
-
if (!northStar) return keys;
|
|
24
|
-
if (northStar.default?.name) keys.add(northStar.default.name);
|
|
25
|
-
for (const entry of Object.values(northStar.byType || {})) {
|
|
26
|
-
if (entry?.name) keys.add(entry.name);
|
|
27
|
-
}
|
|
28
|
-
return keys;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function matchesDeriveFrom(
|
|
32
|
-
deriveFrom,
|
|
33
|
-
{ name, attributes = {}, context = {} }
|
|
34
|
-
) {
|
|
35
|
-
if (!deriveFrom) return false;
|
|
36
|
-
|
|
37
|
-
if (deriveFrom.userAction) {
|
|
38
|
-
const rule = deriveFrom.userAction;
|
|
39
|
-
return (
|
|
40
|
-
name === 'frigg.handler.invocations' &&
|
|
41
|
-
attributes.event === 'USER_ACTION' &&
|
|
42
|
-
(!rule.action || context.event_name === rule.action)
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (deriveFrom.apiRequest) {
|
|
47
|
-
const rule = deriveFrom.apiRequest;
|
|
48
|
-
return (
|
|
49
|
-
name === 'frigg.apimodule.requests' &&
|
|
50
|
-
(!rule.method || attributes.method === rule.method) &&
|
|
51
|
-
(!rule.endpoint ||
|
|
52
|
-
(typeof context.url === 'string' &&
|
|
53
|
-
context.url.includes(rule.endpoint)))
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Subscribe to the bus and emit the North Star counter whenever a configured
|
|
62
|
-
* `deriveFrom` signal matches (config-only, no integration code). The emitted
|
|
63
|
-
* counter flows back through the bus to the usage rollup like any other metric.
|
|
64
|
-
*/
|
|
65
|
-
function createNorthStarDerivationSubscriber({ telemetry, northStar }) {
|
|
66
|
-
if (!telemetry || !northStar) return { unsubscribe() {} };
|
|
67
|
-
|
|
68
|
-
// Hoisted — the referenced-key set is fixed for the life of the subscriber.
|
|
69
|
-
const keys = northStarKeys(northStar);
|
|
70
|
-
|
|
71
|
-
function onMetric(payload) {
|
|
72
|
-
try {
|
|
73
|
-
// Never react to a North Star counter's own emission (no loop).
|
|
74
|
-
if (keys.has(payload.name)) return;
|
|
75
|
-
|
|
76
|
-
const integrationType =
|
|
77
|
-
payload.context?.integrationType ||
|
|
78
|
-
payload.attributes?.integration_type ||
|
|
79
|
-
null;
|
|
80
|
-
const entry = resolveNorthStarEntry(northStar, integrationType);
|
|
81
|
-
if (!entry || !entry.deriveFrom) return;
|
|
82
|
-
if (!matchesDeriveFrom(entry.deriveFrom, payload)) return;
|
|
83
|
-
|
|
84
|
-
telemetry.count(
|
|
85
|
-
entry.name,
|
|
86
|
-
1,
|
|
87
|
-
integrationType ? { integration_type: integrationType } : {},
|
|
88
|
-
payload.context
|
|
89
|
-
);
|
|
90
|
-
} catch (_) {
|
|
91
|
-
// Derivation must never break emission.
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const unsubscribe = telemetry.on('metric', onMetric);
|
|
96
|
-
return { unsubscribe };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
module.exports = {
|
|
100
|
-
resolveNorthStarEntry,
|
|
101
|
-
northStarKeys,
|
|
102
|
-
createNorthStarDerivationSubscriber,
|
|
103
|
-
};
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
const otelApi = require('@opentelemetry/api');
|
|
2
|
-
const {
|
|
3
|
-
BasicTracerProvider,
|
|
4
|
-
BatchSpanProcessor,
|
|
5
|
-
ParentBasedSampler,
|
|
6
|
-
TraceIdRatioBasedSampler,
|
|
7
|
-
AlwaysOnSampler,
|
|
8
|
-
} = require('@opentelemetry/sdk-trace-base');
|
|
9
|
-
const {
|
|
10
|
-
MeterProvider,
|
|
11
|
-
PeriodicExportingMetricReader,
|
|
12
|
-
} = require('@opentelemetry/sdk-metrics');
|
|
13
|
-
const { resourceFromAttributes } = require('@opentelemetry/resources');
|
|
14
|
-
const { buildExporters } = require('./exporters/exporter-factory');
|
|
15
|
-
const { createTelemetryEventBus } = require('./telemetry-event-bus');
|
|
16
|
-
const {
|
|
17
|
-
runWithTelemetryContext,
|
|
18
|
-
mergeTelemetryContext,
|
|
19
|
-
} = require('./telemetry-context');
|
|
20
|
-
|
|
21
|
-
const TRACER_NAME = 'frigg';
|
|
22
|
-
const METRIC_EXPORT_INTERVAL_MS =
|
|
23
|
-
Number(process.env.OTEL_METRIC_EXPORT_INTERVAL_MS) || 60000;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Register an AsyncLocalStorage-backed OTel context manager once per process.
|
|
27
|
-
* Without it `BasicTracerProvider` uses the Noop context manager, so
|
|
28
|
-
* `startActiveSpan` never sets an active context — spans would be unparented
|
|
29
|
-
* roots, `getActiveSpan()` always undefined, and baggage inert. Guarded so
|
|
30
|
-
* repeated telemetry construction (tests) doesn't re-register.
|
|
31
|
-
*/
|
|
32
|
-
let contextManagerRegistered = false;
|
|
33
|
-
function ensureContextManager() {
|
|
34
|
-
if (contextManagerRegistered) return;
|
|
35
|
-
contextManagerRegistered = true;
|
|
36
|
-
try {
|
|
37
|
-
const {
|
|
38
|
-
AsyncLocalStorageContextManager,
|
|
39
|
-
} = require('@opentelemetry/context-async-hooks');
|
|
40
|
-
const manager = new AsyncLocalStorageContextManager();
|
|
41
|
-
manager.enable();
|
|
42
|
-
otelApi.context.setGlobalContextManager(manager);
|
|
43
|
-
} catch (_) {
|
|
44
|
-
// If registration fails, traces are flat but usage attribution (which
|
|
45
|
-
// rides the separate telemetry-context ALS) is unaffected.
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* The OpenTelemetry-backed telemetry service. Reached only
|
|
51
|
-
* when a real exporter is configured; `createTelemetry` returns the no-op
|
|
52
|
-
* otherwise. `BatchSpanProcessor` is used uniformly — spans are delivered by the
|
|
53
|
-
* bounded `forceFlush()` the Lambda handler awaits before the container freezes
|
|
54
|
-
* (`callbackWaitsForEmptyEventLoop=false`), never by background timers.
|
|
55
|
-
*/
|
|
56
|
-
function createOtelTelemetry({
|
|
57
|
-
exporter,
|
|
58
|
-
resource = {},
|
|
59
|
-
sampleRatio,
|
|
60
|
-
bus = createTelemetryEventBus(),
|
|
61
|
-
} = {}) {
|
|
62
|
-
ensureContextManager();
|
|
63
|
-
const { traceExporter, metricExporter } = buildExporters(exporter);
|
|
64
|
-
|
|
65
|
-
const resourceAttrs = {
|
|
66
|
-
'service.name': resource.service || 'frigg',
|
|
67
|
-
};
|
|
68
|
-
if (resource.stage) {
|
|
69
|
-
resourceAttrs['deployment.environment'] = resource.stage;
|
|
70
|
-
resourceAttrs.stage = resource.stage;
|
|
71
|
-
}
|
|
72
|
-
if (resource.appName) resourceAttrs.appName = resource.appName;
|
|
73
|
-
const res = resourceFromAttributes(resourceAttrs);
|
|
74
|
-
|
|
75
|
-
const sampler =
|
|
76
|
-
typeof sampleRatio === 'number' && sampleRatio < 1
|
|
77
|
-
? new ParentBasedSampler({
|
|
78
|
-
root: new TraceIdRatioBasedSampler(sampleRatio),
|
|
79
|
-
})
|
|
80
|
-
: new AlwaysOnSampler();
|
|
81
|
-
|
|
82
|
-
const tracerProvider = new BasicTracerProvider({
|
|
83
|
-
resource: res,
|
|
84
|
-
sampler,
|
|
85
|
-
spanProcessors: traceExporter
|
|
86
|
-
? [new BatchSpanProcessor(traceExporter)]
|
|
87
|
-
: [],
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const metricReader = metricExporter
|
|
91
|
-
? new PeriodicExportingMetricReader({
|
|
92
|
-
exporter: metricExporter,
|
|
93
|
-
exportIntervalMillis: METRIC_EXPORT_INTERVAL_MS,
|
|
94
|
-
})
|
|
95
|
-
: null;
|
|
96
|
-
const meterProvider = new MeterProvider({
|
|
97
|
-
resource: res,
|
|
98
|
-
readers: metricReader ? [metricReader] : [],
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const tracer = tracerProvider.getTracer(TRACER_NAME);
|
|
102
|
-
const meter = meterProvider.getMeter(TRACER_NAME);
|
|
103
|
-
|
|
104
|
-
// Counters/UpDownCounters are cached — OTel requires one instrument instance
|
|
105
|
-
// per metric name, and re-creating them would drop data points.
|
|
106
|
-
const counters = new Map();
|
|
107
|
-
function getCounter(name) {
|
|
108
|
-
if (!counters.has(name)) counters.set(name, meter.createCounter(name));
|
|
109
|
-
return counters.get(name);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const service = {
|
|
113
|
-
count(name, value = 1, attributes = {}, context) {
|
|
114
|
-
try {
|
|
115
|
-
getCounter(name).add(value, attributes);
|
|
116
|
-
} catch (_) {
|
|
117
|
-
// Telemetry must never break the wrapped path.
|
|
118
|
-
}
|
|
119
|
-
// Mirror onto the internal stream for the usage rollup + plugin taps.
|
|
120
|
-
// The bus `context` merges the ambient handler context (incl.
|
|
121
|
-
// high-cardinality ids) with any explicit per-call context — it rides
|
|
122
|
-
// the bus only, never the OTel metric attributes (Cardinality note).
|
|
123
|
-
const merged = mergeTelemetryContext(context);
|
|
124
|
-
const payload = { name, value, attributes };
|
|
125
|
-
if (merged) payload.context = merged;
|
|
126
|
-
bus.emit('metric', payload);
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
event(name, attributes = {}, context) {
|
|
130
|
-
try {
|
|
131
|
-
const active = otelApi.trace.getActiveSpan();
|
|
132
|
-
if (active) active.addEvent(name, attributes);
|
|
133
|
-
} catch (_) {}
|
|
134
|
-
const merged = mergeTelemetryContext(context);
|
|
135
|
-
const payload = { name, attributes };
|
|
136
|
-
if (merged) payload.context = merged;
|
|
137
|
-
bus.emit('event', payload);
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
startSpan(name, options) {
|
|
141
|
-
return tracer.startSpan(name, options);
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
async span(name, fn, options = {}) {
|
|
145
|
-
return tracer.startActiveSpan(name, options, async (span) => {
|
|
146
|
-
try {
|
|
147
|
-
const result =
|
|
148
|
-
typeof fn === 'function' ? await fn(span) : undefined;
|
|
149
|
-
span.setStatus({ code: otelApi.SpanStatusCode.OK });
|
|
150
|
-
return result;
|
|
151
|
-
} catch (err) {
|
|
152
|
-
span.recordException(err);
|
|
153
|
-
span.setStatus({
|
|
154
|
-
code: otelApi.SpanStatusCode.ERROR,
|
|
155
|
-
message: err && err.message,
|
|
156
|
-
});
|
|
157
|
-
throw err;
|
|
158
|
-
} finally {
|
|
159
|
-
span.end();
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Run `fn` with the given identifiers on (a) the AsyncLocalStorage
|
|
166
|
-
* telemetry context — which the usage rollup reads to attribute emissions
|
|
167
|
-
* per-integration on ANY path — and (b) OTel baggage for trace
|
|
168
|
-
* propagation. High-cardinality ids ride here, never on metric labels.
|
|
169
|
-
*/
|
|
170
|
-
async withContext(attributes = {}, fn) {
|
|
171
|
-
const entries = {};
|
|
172
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
173
|
-
if (value !== undefined && value !== null) {
|
|
174
|
-
entries[key] = { value: String(value) };
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const baggage = otelApi.propagation.createBaggage(entries);
|
|
178
|
-
const ctx = otelApi.propagation.setBaggage(
|
|
179
|
-
otelApi.context.active(),
|
|
180
|
-
baggage
|
|
181
|
-
);
|
|
182
|
-
return runWithTelemetryContext(attributes, () =>
|
|
183
|
-
otelApi.context.with(ctx, () =>
|
|
184
|
-
typeof fn === 'function' ? fn() : undefined
|
|
185
|
-
)
|
|
186
|
-
);
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
on(eventType, callback) {
|
|
190
|
-
return bus.on(eventType, callback);
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
async forceFlush() {
|
|
194
|
-
await Promise.allSettled([
|
|
195
|
-
tracerProvider.forceFlush(),
|
|
196
|
-
meterProvider.forceFlush(),
|
|
197
|
-
]);
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
async shutdown() {
|
|
201
|
-
await Promise.allSettled([
|
|
202
|
-
tracerProvider.shutdown(),
|
|
203
|
-
meterProvider.shutdown(),
|
|
204
|
-
]);
|
|
205
|
-
},
|
|
206
|
-
|
|
207
|
-
isEnabled() {
|
|
208
|
-
return true;
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
return service;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
module.exports = { createOtelTelemetry };
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
const { wireTelemetrySubscribers } = require('./plugin-subscribers');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Process-wide wiring of adopter-declared telemetry subscribers. Built once per
|
|
5
|
-
* cold start and reused for the life of the Lambda
|
|
6
|
-
* container, mirroring the usage-rollup singleton. Independent of the usage
|
|
7
|
-
* rollup: adopter taps wire even when no integration declares `Definition.usage`.
|
|
8
|
-
* Never throws — a bad/absent app definition just means no adopter taps.
|
|
9
|
-
*/
|
|
10
|
-
let wired = null;
|
|
11
|
-
let initialized = false;
|
|
12
|
-
|
|
13
|
-
function getPluginTelemetrySubscribers() {
|
|
14
|
-
if (initialized) return wired;
|
|
15
|
-
initialized = true;
|
|
16
|
-
try {
|
|
17
|
-
const {
|
|
18
|
-
loadAppDefinition,
|
|
19
|
-
} = require('../handlers/app-definition-loader');
|
|
20
|
-
const { telemetry = {} } = loadAppDefinition();
|
|
21
|
-
const subscribers = telemetry.subscribers || [];
|
|
22
|
-
if (subscribers.length === 0) {
|
|
23
|
-
wired = [];
|
|
24
|
-
return wired;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const { getTelemetry } = require('./telemetry-singleton');
|
|
28
|
-
wired = wireTelemetrySubscribers({
|
|
29
|
-
telemetry: getTelemetry(),
|
|
30
|
-
subscribers,
|
|
31
|
-
});
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.warn(
|
|
34
|
-
`[Frigg][telemetry] plugin subscribers disabled: ${
|
|
35
|
-
error && error.message
|
|
36
|
-
}`
|
|
37
|
-
);
|
|
38
|
-
wired = [];
|
|
39
|
-
}
|
|
40
|
-
return wired;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function resetPluginTelemetrySubscribersForTests() {
|
|
44
|
-
wired = null;
|
|
45
|
-
initialized = false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
module.exports = {
|
|
49
|
-
getPluginTelemetrySubscribers,
|
|
50
|
-
resetPluginTelemetrySubscribersForTests,
|
|
51
|
-
};
|