@fougere/observability 0.9.2-alpha.0 → 0.10.0-alpha.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.
Files changed (69) hide show
  1. package/dist/OtlpExporter.d.ts +12 -0
  2. package/dist/OtlpExporter.d.ts.map +1 -0
  3. package/dist/{otlp.js → OtlpExporter.js} +9 -4
  4. package/dist/OtlpExporter.js.map +1 -0
  5. package/dist/{otlp.d.ts → OtlpOptions.d.ts} +2 -12
  6. package/dist/OtlpOptions.d.ts.map +1 -0
  7. package/dist/OtlpOptions.js +2 -0
  8. package/dist/OtlpOptions.js.map +1 -0
  9. package/dist/extension.d.ts +19 -0
  10. package/dist/extension.d.ts.map +1 -1
  11. package/dist/extension.js +32 -9
  12. package/dist/extension.js.map +1 -1
  13. package/dist/index.d.ts +90 -6
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +106 -13
  16. package/dist/index.js.map +1 -1
  17. package/dist/logs/CapturedLog.d.ts +7 -0
  18. package/dist/logs/CapturedLog.d.ts.map +1 -0
  19. package/dist/logs/CapturedLog.js +2 -0
  20. package/dist/logs/CapturedLog.js.map +1 -0
  21. package/dist/logs/LogExporter.d.ts +12 -0
  22. package/dist/logs/LogExporter.d.ts.map +1 -0
  23. package/dist/{logs.js → logs/LogExporter.js} +4 -5
  24. package/dist/logs/LogExporter.js.map +1 -0
  25. package/dist/logs/LogsOptions.d.ts +13 -0
  26. package/dist/logs/LogsOptions.d.ts.map +1 -0
  27. package/dist/logs/LogsOptions.js +2 -0
  28. package/dist/logs/LogsOptions.js.map +1 -0
  29. package/dist/metrics/Bucketed.d.ts +16 -0
  30. package/dist/metrics/Bucketed.d.ts.map +1 -0
  31. package/dist/metrics/Bucketed.js +2 -0
  32. package/dist/metrics/Bucketed.js.map +1 -0
  33. package/dist/metrics/Metrics.d.ts +42 -0
  34. package/dist/metrics/Metrics.d.ts.map +1 -0
  35. package/dist/{metrics.js → metrics/Metrics.js} +97 -28
  36. package/dist/metrics/Metrics.js.map +1 -0
  37. package/dist/metrics/MetricsSnapshot.d.ts +14 -0
  38. package/dist/metrics/MetricsSnapshot.d.ts.map +1 -0
  39. package/dist/metrics/MetricsSnapshot.js +2 -0
  40. package/dist/metrics/MetricsSnapshot.js.map +1 -0
  41. package/dist/otlp/OtlpExporter.d.ts +12 -0
  42. package/dist/otlp/OtlpExporter.d.ts.map +1 -0
  43. package/dist/otlp/OtlpExporter.js +64 -0
  44. package/dist/otlp/OtlpExporter.js.map +1 -0
  45. package/dist/otlp/OtlpOptions.d.ts +16 -0
  46. package/dist/otlp/OtlpOptions.d.ts.map +1 -0
  47. package/dist/otlp/OtlpOptions.js +2 -0
  48. package/dist/otlp/OtlpOptions.js.map +1 -0
  49. package/package.json +14 -7
  50. package/src/{otlp.ts → OtlpExporter.ts} +12 -18
  51. package/src/OtlpOptions.ts +16 -0
  52. package/src/extension.ts +53 -9
  53. package/src/index.ts +189 -14
  54. package/src/logs/CapturedLog.ts +7 -0
  55. package/src/{logs.ts → logs/LogExporter.ts} +5 -23
  56. package/src/logs/LogsOptions.ts +12 -0
  57. package/src/metrics/Bucketed.ts +15 -0
  58. package/src/{metrics.ts → metrics/Metrics.ts} +100 -53
  59. package/src/metrics/MetricsSnapshot.ts +14 -0
  60. package/src/otlp/OtlpExporter.ts +87 -0
  61. package/src/otlp/OtlpOptions.ts +16 -0
  62. package/dist/logs.d.ts +0 -28
  63. package/dist/logs.d.ts.map +0 -1
  64. package/dist/logs.js.map +0 -1
  65. package/dist/metrics.d.ts +0 -59
  66. package/dist/metrics.d.ts.map +0 -1
  67. package/dist/metrics.js.map +0 -1
  68. package/dist/otlp.d.ts.map +0 -1
  69. package/dist/otlp.js.map +0 -1
@@ -0,0 +1,13 @@
1
+ export interface LogsOptions {
2
+ /** Which service these lines belong to. */
3
+ service: string;
4
+ /** Collector endpoint. Default: the OTLP/HTTP convention on localhost. */
5
+ url?: string;
6
+ /** How often a batch leaves. Default: every second. */
7
+ flushMs?: number;
8
+ /** Told when a batch could not be sent. Default: silence. */
9
+ onError?: (err: unknown) => void;
10
+ /** Drop anything below this level before it leaves the process. */
11
+ minimum?: 'debug' | 'info' | 'warn' | 'error';
12
+ }
13
+ //# sourceMappingURL=LogsOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogsOptions.d.ts","sourceRoot":"","sources":["../../src/logs/LogsOptions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAC/C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=LogsOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogsOptions.js","sourceRoot":"","sources":["../../src/logs/LogsOptions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ export interface Bucketed {
2
+ frond: string | undefined;
3
+ entity: string;
4
+ operation: string;
5
+ error: string | undefined;
6
+ count: number;
7
+ sum: number;
8
+ /** One more than the bounds: the last holds everything above the highest bound. */
9
+ buckets: number[];
10
+ /** The same measurement over `selfMs` — what the op did rather than what it waited for. */
11
+ selfSum: number;
12
+ selfBuckets: number[];
13
+ /** Every statement these calls ran. Against `count`, it is statements per call. */
14
+ statements: number;
15
+ }
16
+ //# sourceMappingURL=Bucketed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Bucketed.d.ts","sourceRoot":"","sources":["../../src/metrics/Bucketed.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2FAA2F;IAC3F,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Bucketed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Bucketed.js","sourceRoot":"","sources":["../../src/metrics/Bucketed.ts"],"names":[],"mappings":""}
@@ -0,0 +1,42 @@
1
+ import { type App } from '@fougere/core';
2
+ import { type SpanSink } from '../index.js';
3
+ import type { MetricsSnapshot } from './MetricsSnapshot.js';
4
+ export interface Metrics {
5
+ /** Hand to `onSpan` — it reads the same span the tracer reads. */
6
+ sink: SpanSink;
7
+ /** What to publish now. */
8
+ snapshot(): MetricsSnapshot;
9
+ }
10
+ /**
11
+ * `app` is optional and only feeds the topology: what fronds this process found, and which of them
12
+ * run elsewhere.
13
+ */
14
+ export declare function metrics(app?: App, bounds?: readonly number[]): Metrics;
15
+ /**
16
+ * Serve the topology on `rpc.topology` — read from inside the process it describes.
17
+ *
18
+ * The declared half is read at every call rather than once here: `reloadFougere()` builds the app
19
+ * again, and a config read at boot would keep answering for the app that was released.
20
+ */
21
+ export declare function serveTopology(app: App, measured: Metrics): void;
22
+ /** One snapshot, as OTLP/JSON spells metrics. Cumulative, which is what Prometheus reads. */
23
+ export declare function metricsPayload(service: string, snapshot: MetricsSnapshot): {
24
+ resourceMetrics: {
25
+ resource: {
26
+ attributes: {
27
+ key: string;
28
+ value: {
29
+ stringValue: string;
30
+ };
31
+ }[];
32
+ };
33
+ scopeMetrics: {
34
+ scope: {
35
+ name: string;
36
+ };
37
+ /** Only what has points. */
38
+ metrics: Record<string, any>[];
39
+ }[];
40
+ }[];
41
+ };
42
+ //# sourceMappingURL=Metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Metrics.d.ts","sourceRoot":"","sources":["../../src/metrics/Metrics.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,GAAG,EAAuD,MAAM,eAAe,CAAC;AAClH,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAkB5D,MAAM,WAAW,OAAO;IACtB,kEAAkE;IAClE,IAAI,EAAE,QAAQ,CAAC;IACf,2BAA2B;IAC3B,QAAQ,IAAI,eAAe,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAE,SAAS,MAAM,EAAW,GAAG,OAAO,CA2D9E;AA0CD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAM/D;AAED,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe;;;YAgBrD,UAAU;;;oBAbkC,WAAW;;;;;;gBAgBtD,IAAI;;YACb,4BAA4B;;;;EA8HvC"}
@@ -1,14 +1,24 @@
1
- import { activeCalls } from './index.js';
1
+ import { declaredTopologyOf } from '@fougere/core';
2
+ import { activeCalls } from '../index.js';
2
3
  /**
3
- * Bucket bounds in SECONDS, the OpenTelemetry recommendation for request durations.
4
- * Dense under 100 ms because that is where a healthy op lives, and open above 10 s.
4
+ * Bucket bounds in SECONDS, and the operator's to choose — this is the default, not the rule.
5
+ *
6
+ * The OpenTelemetry recommendation for request durations: dense under 100 ms because that is
7
+ * where a healthy op lives, and open above 10 s. A system whose ops all cross two processes has
8
+ * its floor elsewhere and wastes half these buckets.
9
+ *
10
+ * Deliberately NOT derived per operation, though `reach` could: an explicit-bucket histogram only
11
+ * aggregates across series that share its bounds, so a panel asking for the p95 of the whole
12
+ * service — which sums over every op — would stop meaning anything, silently. One list per
13
+ * process is what keeps that reading honest. The number that IS per-op is the load threshold,
14
+ * where nothing aggregates.
5
15
  */
6
16
  const BOUNDS = [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10];
7
17
  /**
8
18
  * `app` is optional and only feeds the topology: what fronds this process found, and which of them
9
19
  * run elsewhere.
10
20
  */
11
- export function metrics(app) {
21
+ export function metrics(app, bounds = BOUNDS) {
12
22
  const since = Date.now();
13
23
  const series = new Map();
14
24
  /** Every frond this process has actually CALLED — the other half of the topology. */
@@ -17,6 +27,11 @@ export function metrics(app) {
17
27
  const edges = new Map();
18
28
  return {
19
29
  sink: (span) => {
30
+ // The one reader that has to choose: a statement is a step, not a call this process
31
+ // answered. Counted here it would publish `select.post` as an operation nothing
32
+ // serves, and every topology and saturation figure would count it too.
33
+ if (span.kind !== 'operation')
34
+ return;
20
35
  const key = `${span.entity}\0${span.operation}\0${span.error ?? ''}`;
21
36
  let row = series.get(key);
22
37
  if (!row) {
@@ -27,7 +42,10 @@ export function metrics(app) {
27
42
  error: span.error,
28
43
  count: 0,
29
44
  sum: 0,
30
- buckets: new Array(BOUNDS.length + 1).fill(0),
45
+ buckets: new Array(bounds.length + 1).fill(0),
46
+ selfSum: 0,
47
+ selfBuckets: new Array(bounds.length + 1).fill(0),
48
+ statements: 0,
31
49
  };
32
50
  series.set(key, row);
33
51
  }
@@ -44,13 +62,17 @@ export function metrics(app) {
44
62
  const seconds = span.ms / 1000;
45
63
  row.count += 1;
46
64
  row.sum += seconds;
47
- row.buckets[bucketOf(seconds)] += 1;
65
+ row.buckets[bucketOf(seconds, bounds)] += 1;
66
+ const self = span.selfMs / 1000;
67
+ row.selfSum += self;
68
+ row.selfBuckets[bucketOf(self, bounds)] += 1;
69
+ row.statements += span.statements;
48
70
  },
49
71
  snapshot: () => ({
50
72
  since,
51
73
  series: [...series.values()],
52
74
  active: activeCalls(),
53
- bounds: BOUNDS,
75
+ bounds: [...bounds],
54
76
  topology: topologyOf(app, seen),
55
77
  edges: [...edges.values()],
56
78
  }),
@@ -63,34 +85,45 @@ export function metrics(app) {
63
85
  function topologyOf(app, seen) {
64
86
  // What the app SERVES: a frond an extension brought is instrumentation, and reporting it
65
87
  // would describe this package to itself.
88
+ //
89
+ // A frond named in `remotes:` is scanned when its code sits in the same project, and stays
90
+ // in `app.fronds` — the boot says `declared remote — not hosted locally` and keeps it. This
91
+ // half reports what RUNS here, so it is not one of them: it used to answer `local` for a
92
+ // frond every call reached over HTTP. Not a config-derived node either — it is named below
93
+ // only once it has answered, like any other remote.
66
94
  const local = new Map((app?.fronds ?? [])
67
- .filter((frond) => !frond.brought)
95
+ .filter((frond) => !frond.brought && !app?.remotes[frond.name])
68
96
  .map((frond) => [frond.name, frond]));
69
97
  const here = [...local.values()].map((frond) => ({
70
98
  frond: frond.name,
71
99
  placement: 'local',
72
100
  entities: frond.entities.length,
73
- doors: frond.handlers.length,
101
+ facades: frond.handlers.length,
74
102
  }));
75
103
  // A frond we called but never scanned is hosted elsewhere. Its shape is not ours to
76
104
  // report — it is published by the process that owns it, under its own service name.
77
105
  const elsewhere = [...seen]
78
106
  .filter((name) => !local.has(name))
79
- .map((name) => ({ frond: name, placement: 'remote', entities: 0, doors: 0 }));
107
+ .map((name) => ({ frond: name, placement: 'remote', entities: 0, facades: 0 }));
80
108
  return [...here, ...elsewhere];
81
109
  }
82
110
  /** The first bound this duration does not exceed, or the overflow bucket. */
83
- function bucketOf(seconds) {
84
- for (let i = 0; i < BOUNDS.length; i++)
85
- if (seconds <= BOUNDS[i])
111
+ function bucketOf(seconds, bounds) {
112
+ for (let i = 0; i < bounds.length; i++)
113
+ if (seconds <= bounds[i])
86
114
  return i;
87
- return BOUNDS.length;
115
+ return bounds.length;
88
116
  }
89
- /** Serve the topology on `rpc.topology` — read from inside the process it describes. */
117
+ /**
118
+ * Serve the topology on `rpc.topology` — read from inside the process it describes.
119
+ *
120
+ * The declared half is read at every call rather than once here: `reloadFougere()` builds the app
121
+ * again, and a config read at boot would keep answering for the app that was released.
122
+ */
90
123
  export function serveTopology(app, measured) {
91
124
  app.serveRpc('topology', () => {
92
125
  const { since, active, topology, edges } = measured.snapshot();
93
- return { since, active, fronds: topology, edges };
126
+ return { since, active, fronds: topology, edges, declared: declaredTopologyOf(app) };
94
127
  });
95
128
  }
96
129
  /** One snapshot, as OTLP/JSON spells metrics. Cumulative, which is what Prometheus reads. */
@@ -98,6 +131,14 @@ export function metricsPayload(service, snapshot) {
98
131
  const since = `${snapshot.since * 1e6}`;
99
132
  const now = `${Date.now() * 1e6}`;
100
133
  const attr = (key, value) => ({ key, value: { stringValue: value } });
134
+ /** What an operation's three series are all sliced by — said once, so they stay comparable. */
135
+ const dimensions = (row) => [
136
+ ...(row.frond ? [attr('fougere.frond', row.frond)] : []),
137
+ attr('fougere.entity', row.entity),
138
+ attr('fougere.operation', row.operation),
139
+ attr('fougere.outcome', row.error ? 'error' : 'ok'),
140
+ ...(row.error ? [attr('fougere.error.code', row.error)] : []),
141
+ ];
101
142
  return {
102
143
  resourceMetrics: [
103
144
  {
@@ -116,13 +157,7 @@ export function metricsPayload(service, snapshot) {
116
157
  // only temporality Prometheus reads without a collector in between.
117
158
  aggregationTemporality: 2,
118
159
  dataPoints: snapshot.series.map((row) => ({
119
- attributes: [
120
- ...(row.frond ? [attr('fougere.frond', row.frond)] : []),
121
- attr('fougere.entity', row.entity),
122
- attr('fougere.operation', row.operation),
123
- attr('fougere.outcome', row.error ? 'error' : 'ok'),
124
- ...(row.error ? [attr('fougere.error.code', row.error)] : []),
125
- ],
160
+ attributes: dimensions(row),
126
161
  startTimeUnixNano: since,
127
162
  timeUnixNano: now,
128
163
  count: `${row.count}`,
@@ -132,6 +167,40 @@ export function metricsPayload(service, snapshot) {
132
167
  })),
133
168
  },
134
169
  },
170
+ {
171
+ name: 'fougere.operation.self',
172
+ description: 'How long an operation took on its own, with what it waited for taken out.',
173
+ unit: 's',
174
+ histogram: {
175
+ aggregationTemporality: 2,
176
+ dataPoints: snapshot.series.map((row) => ({
177
+ attributes: dimensions(row),
178
+ startTimeUnixNano: since,
179
+ timeUnixNano: now,
180
+ count: `${row.count}`,
181
+ sum: row.selfSum,
182
+ bucketCounts: row.selfBuckets.map((n) => `${n}`),
183
+ explicitBounds: snapshot.bounds,
184
+ })),
185
+ },
186
+ },
187
+ {
188
+ name: 'fougere.operation.statements',
189
+ description: 'Statements run under an operation. Against its count, statements per call.',
190
+ unit: '{statement}',
191
+ sum: {
192
+ aggregationTemporality: 2,
193
+ isMonotonic: true,
194
+ dataPoints: snapshot.series
195
+ .filter((row) => row.statements > 0)
196
+ .map((row) => ({
197
+ attributes: dimensions(row),
198
+ startTimeUnixNano: since,
199
+ timeUnixNano: now,
200
+ asInt: `${row.statements}`,
201
+ })),
202
+ },
203
+ },
135
204
  {
136
205
  name: 'fougere.operations.active',
137
206
  description: 'Operations running right now — the saturation signal.',
@@ -184,13 +253,13 @@ export function metricsPayload(service, snapshot) {
184
253
  },
185
254
  },
186
255
  {
187
- name: 'fougere.frond.doors',
188
- description: 'How many doors a frond serves — its surface, as scanned.',
189
- unit: '{door}',
256
+ name: 'fougere.frond.facades',
257
+ description: 'How many facades a frond serves — its surface, as scanned.',
258
+ unit: '{facade}',
190
259
  gauge: {
191
260
  dataPoints: snapshot.topology.map((f) => ({
192
261
  attributes: [attr('fougere.frond', f.frond)],
193
- asInt: `${f.doors}`,
262
+ asInt: `${f.facades}`,
194
263
  timeUnixNano: now,
195
264
  })),
196
265
  },
@@ -202,4 +271,4 @@ export function metricsPayload(service, snapshot) {
202
271
  ],
203
272
  };
204
273
  }
205
- //# sourceMappingURL=metrics.js.map
274
+ //# sourceMappingURL=Metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Metrics.js","sourceRoot":"","sources":["../../src/metrics/Metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Bucketed } from './Bucketed.js';
2
+ import { type Edge, type FrondPlacement } from '@fougere/core';
3
+ export interface MetricsSnapshot {
4
+ /** When this process started counting — cumulative metrics are read against it. */
5
+ since: number;
6
+ series: Bucketed[];
7
+ active: number;
8
+ bounds: number[];
9
+ /** The shape of the system as this process discovered it — declared nowhere. */
10
+ topology: FrondPlacement[];
11
+ /** Who calls whom, as observed here. Bounded by fronds², so it is a safe dimension. */
12
+ edges: Edge[];
13
+ }
14
+ //# sourceMappingURL=MetricsSnapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricsSnapshot.d.ts","sourceRoot":"","sources":["../../src/metrics/MetricsSnapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,gFAAgF;IAChF,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,uFAAuF;IACvF,KAAK,EAAE,IAAI,EAAE,CAAC;CACf"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=MetricsSnapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricsSnapshot.js","sourceRoot":"","sources":["../../src/metrics/MetricsSnapshot.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import type { SpanSink } from '../index.js';
2
+ import type { OtlpOptions } from './OtlpOptions.js';
3
+ export interface OtlpExporter {
4
+ /** Hand to `onSpan`. */
5
+ sink: SpanSink;
6
+ /** Send what is buffered now — a process about to exit has to call this. */
7
+ flush(): Promise<void>;
8
+ /** Stop the timer and send what is left. */
9
+ stop(): Promise<void>;
10
+ }
11
+ export declare function otlp(options: OtlpOptions): OtlpExporter;
12
+ //# sourceMappingURL=OtlpExporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OtlpExporter.d.ts","sourceRoot":"","sources":["../../src/otlp/OtlpExporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgB,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,wBAAwB;IACxB,IAAI,EAAE,QAAQ,CAAC;IACf,4EAA4E;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,4CAA4C;IAC5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAYD,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,CA2BvD"}
@@ -0,0 +1,64 @@
1
+ import { Beat } from '../Beat.js';
2
+ import { Endpoint } from '../Endpoint.js';
3
+ import { metricsPayload } from '../metrics/Metrics.js';
4
+ /** OTLP status codes: 0 unset, 1 ok, 2 error. */
5
+ const OK = 1;
6
+ const ERROR = 2;
7
+ /** OTLP span kinds, of the six only these two are ours. */
8
+ const INTERNAL = 1;
9
+ const CLIENT = 3;
10
+ export function otlp(options) {
11
+ const url = options.url ?? 'http://localhost:4318/v1/traces';
12
+ const traces = Endpoint.at(url, options.onError);
13
+ const metrics = Endpoint.at(options.metricsUrl ?? url.replace(/\/v1\/traces$/, '/v1/metrics'), options.onError);
14
+ let buffer = [];
15
+ async function flush() {
16
+ const batch = buffer;
17
+ buffer = [];
18
+ // Metrics go on every beat even when no span finished: a gauge that stops being
19
+ // published reads as "gone", not as "idle".
20
+ await Promise.all([
21
+ batch.length > 0 ? traces.post(payload(options.service, batch)) : Promise.resolve(),
22
+ options.metrics ? metrics.post(metricsPayload(options.service, options.metrics.snapshot())) : Promise.resolve(),
23
+ ]);
24
+ }
25
+ const beat = Beat.every(options.flushMs, flush);
26
+ return {
27
+ sink: (span) => { buffer.push(span); },
28
+ flush,
29
+ stop: () => beat.stop(),
30
+ };
31
+ }
32
+ /** One batch, as OTLP/JSON spells it — trace and span ids stay hex, times are nanos. */
33
+ function payload(service, spans) {
34
+ return {
35
+ resourceSpans: [
36
+ {
37
+ resource: { attributes: [{ key: 'service.name', value: { stringValue: service } }] },
38
+ scopeSpans: [
39
+ {
40
+ scope: { name: '@fougere/observability' },
41
+ spans: spans.map((span) => ({
42
+ traceId: span.traceId,
43
+ spanId: span.spanId,
44
+ ...(span.parentId ? { parentSpanId: span.parentId } : {}),
45
+ name: `${span.entity}.${span.operation}`,
46
+ // 1 INTERNAL, 3 CLIENT: a statement left this process for an engine, and a
47
+ // viewer draws the two differently. `selfMs` is NOT sent — a collector
48
+ // derives it from the tree it already holds.
49
+ kind: span.kind === 'statement' ? CLIENT : INTERNAL,
50
+ startTimeUnixNano: nanos(span.startedAt),
51
+ endTimeUnixNano: nanos(span.startedAt + span.ms),
52
+ status: span.error ? { code: ERROR, message: span.error } : { code: OK },
53
+ })),
54
+ },
55
+ ],
56
+ },
57
+ ],
58
+ };
59
+ }
60
+ /** Epoch milliseconds → the int64 nanoseconds OTLP wants, as a string. */
61
+ function nanos(ms) {
62
+ return `${Math.round(ms * 1e6)}`;
63
+ }
64
+ //# sourceMappingURL=OtlpExporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OtlpExporter.js","sourceRoot":"","sources":["../../src/otlp/OtlpExporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { type Metrics } from '../metrics/Metrics.js';
2
+ export interface OtlpOptions {
3
+ /** Which service these spans belong to — what a dashboard groups by. */
4
+ service: string;
5
+ /** Collector endpoint. Default: the OTLP/HTTP convention on localhost. */
6
+ url?: string;
7
+ /** How often a full batch leaves. Default: every second. */
8
+ flushMs?: number;
9
+ /** Told when a batch could not be sent. Default: silence — a trace must never break a call. */
10
+ onError?: (err: unknown) => void;
11
+ /** Publish these metrics on the same beat. */
12
+ metrics?: Metrics;
13
+ /** Where metrics go when it is not the same collector as traces. */
14
+ metricsUrl?: string;
15
+ }
16
+ //# sourceMappingURL=OtlpOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OtlpOptions.d.ts","sourceRoot":"","sources":["../../src/otlp/OtlpOptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,8CAA8C;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=OtlpOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OtlpOptions.js","sourceRoot":"","sources":["../../src/otlp/OtlpOptions.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/observability",
3
- "version": "0.9.2-alpha.0",
3
+ "version": "0.10.0-alpha.0",
4
4
  "description": "Optional observability for Fougere: one span per operation, W3C Trace Context across any transport.",
5
5
  "keywords": [
6
6
  "fougere",
@@ -31,15 +31,17 @@
31
31
  "src"
32
32
  ],
33
33
  "peerDependencies": {
34
- "@fougere/core": "^0.9.2-alpha.0"
34
+ "@fougere/adapter-sql": "^0.10.0-alpha.0",
35
+ "@fougere/core": "^0.10.0-alpha.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "vitest": "^4.1.0",
38
- "@fougere/container": "0.9.2-alpha.0",
39
- "@fougere/core": "0.9.2-alpha.0",
40
- "@fougere/schema": "0.9.2-alpha.0",
41
- "@fougere/transport-http": "0.9.2-alpha.0",
42
- "@fougere/compiler": "0.9.2-alpha.0"
39
+ "@fougere/adapter-sql": "0.10.0-alpha.0",
40
+ "@fougere/schema": "0.10.0-alpha.0",
41
+ "@fougere/compiler": "0.10.0-alpha.0",
42
+ "@fougere/core": "0.10.0-alpha.0",
43
+ "@fougere/container": "0.10.0-alpha.0",
44
+ "@fougere/transport-http": "0.10.0-alpha.0"
43
45
  },
44
46
  "publishConfig": {
45
47
  "access": "public"
@@ -51,6 +53,11 @@
51
53
  "default": "./dist/context/als.js"
52
54
  }
53
55
  },
56
+ "peerDependenciesMeta": {
57
+ "@fougere/adapter-sql": {
58
+ "optional": true
59
+ }
60
+ },
54
61
  "scripts": {
55
62
  "build": "rm -rf dist && tsc",
56
63
  "test": "vitest run",
@@ -1,23 +1,8 @@
1
- /** OTLP export — the spans this process finished, in the one shape every collector reads. */
2
1
  import { Beat } from './Beat.js';
3
2
  import { Endpoint } from './Endpoint.js';
4
3
  import type { FinishedSpan, SpanSink } from './index.js';
5
- import { metricsPayload, type Metrics } from './metrics.js';
6
-
7
- export interface OtlpOptions {
8
- /** Which service these spans belong to — what a dashboard groups by. */
9
- service: string;
10
- /** Collector endpoint. Default: the OTLP/HTTP convention on localhost. */
11
- url?: string;
12
- /** How often a full batch leaves. Default: every second. */
13
- flushMs?: number;
14
- /** Told when a batch could not be sent. Default: silence — a trace must never break a call. */
15
- onError?: (err: unknown) => void;
16
- /** Publish these metrics on the same beat. */
17
- metrics?: Metrics;
18
- /** Where metrics go when it is not the same collector as traces. */
19
- metricsUrl?: string;
20
- }
4
+ import { metricsPayload } from './metrics/Metrics.js';
5
+ import type { OtlpOptions } from './OtlpOptions.js';
21
6
 
22
7
  export interface OtlpExporter {
23
8
  /** Hand to `onSpan`. */
@@ -30,8 +15,14 @@ export interface OtlpExporter {
30
15
 
31
16
  /** OTLP status codes: 0 unset, 1 ok, 2 error. */
32
17
  const OK = 1;
18
+
33
19
  const ERROR = 2;
34
20
 
21
+ /** OTLP span kinds, of the six only these two are ours. */
22
+ const INTERNAL = 1;
23
+
24
+ const CLIENT = 3;
25
+
35
26
  export function otlp(options: OtlpOptions): OtlpExporter {
36
27
  const url = options.url ?? 'http://localhost:4318/v1/traces';
37
28
  const traces = Endpoint.at(url, options.onError);
@@ -75,7 +66,10 @@ function payload(service: string, spans: FinishedSpan[]) {
75
66
  spanId: span.spanId,
76
67
  ...(span.parentId ? { parentSpanId: span.parentId } : {}),
77
68
  name: `${span.entity}.${span.operation}`,
78
- kind: 1,
69
+ // 1 INTERNAL, 3 CLIENT: a statement left this process for an engine, and a
70
+ // viewer draws the two differently. `selfMs` is NOT sent — a collector
71
+ // derives it from the tree it already holds.
72
+ kind: span.kind === 'statement' ? CLIENT : INTERNAL,
79
73
  startTimeUnixNano: nanos(span.startedAt),
80
74
  endTimeUnixNano: nanos(span.startedAt + span.ms),
81
75
  status: span.error ? { code: ERROR, message: span.error } : { code: OK },
@@ -0,0 +1,16 @@
1
+ import { type Metrics } from './metrics/Metrics.js';
2
+
3
+ export interface OtlpOptions {
4
+ /** Which service these spans belong to — what a dashboard groups by. */
5
+ service: string;
6
+ /** Collector endpoint. Default: the OTLP/HTTP convention on localhost. */
7
+ url?: string;
8
+ /** How often a full batch leaves. Default: every second. */
9
+ flushMs?: number;
10
+ /** Told when a batch could not be sent. Default: silence — a trace must never break a call. */
11
+ onError?: (err: unknown) => void;
12
+ /** Publish these metrics on the same beat. */
13
+ metrics?: Metrics;
14
+ /** Where metrics go when it is not the same collector as traces. */
15
+ metricsUrl?: string;
16
+ }