@btravstack/observability 0.8.0 → 0.10.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 CHANGED
@@ -51,17 +51,15 @@ import {
51
51
  import { Module, Provider } from "@btravstack/di";
52
52
 
53
53
  // The application depends on the port, like any other service.
54
- const placeOrder = Provider(PlaceOrder)(
55
- { orders: OrderRepository, logger: Logger },
56
- {
57
- sync: ({ orders, logger }) => ({
58
- execute: (id, quantity) =>
59
- orders
60
- .save({ id, quantity })
61
- .tap(() => logger.info("order placed", { id, quantity })),
62
- }),
63
- },
64
- );
54
+ const placeOrder = Provider(PlaceOrder)({
55
+ inject: { orders: OrderRepository, logger: Logger },
56
+ sync: ({ orders, logger }) => ({
57
+ execute: (id, quantity) =>
58
+ orders
59
+ .save({ id, quantity })
60
+ .tap(() => logger.info("order placed", { id, quantity })),
61
+ }),
62
+ });
65
63
 
66
64
  // The starter provides it. `LOG_LEVEL` is read inside the graph and validated
67
65
  // once — `verbose` is a startup failure naming the variable, not a silent
package/dist/index.cjs CHANGED
@@ -150,8 +150,29 @@ var LoggerConfig = class extends (0, _btravstack_di.Port)("LoggerConfig") {};
150
150
  */
151
151
  const observability = (options = {}) => (0, _btravstack_di.Module)("Observability")({
152
152
  needs: [_btravstack_config.Env],
153
- provides: [_btravstack_config.Config.provider(LoggerConfig)(loggerSchema(options.level)), (0, _btravstack_di.Provider)(_btravstack_core.Logger)({ config: LoggerConfig }, { sync: ({ config }) => createLogger(options.sink ?? jsonSink(), config.level) })],
154
- exports: [_btravstack_core.Logger, LoggerConfig]
153
+ provides: [
154
+ _btravstack_config.Config.provider(LoggerConfig)(loggerSchema(options.level)),
155
+ (0, _btravstack_di.Provider)(_btravstack_core.Logger)({
156
+ inject: { config: LoggerConfig },
157
+ sync: ({ config }) => createLogger(options.sink ?? jsonSink(), config.level)
158
+ }),
159
+ _btravstack_di.Provider.member(_btravstack_core.Observers)({
160
+ inject: { logger: _btravstack_core.Logger },
161
+ sync: ({ logger }) => ({ component, name, attributes, details }) => ({ outcome, attributes: settled, cause }) => {
162
+ if (outcome !== "error") return;
163
+ logger.error(`${component}.${name} failed`, {
164
+ ...attributes,
165
+ ...details,
166
+ ...settled
167
+ }, cause);
168
+ }
169
+ })
170
+ ],
171
+ exports: [
172
+ _btravstack_core.Logger,
173
+ LoggerConfig,
174
+ _btravstack_core.Observers
175
+ ]
155
176
  });
156
177
  /**
157
178
  * The kernel's nine lifecycle events, as log lines on `logger` — the adapter
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as Sink, r as createLogger, t as Line } from "./logger-B1-_76ej.cjs";
2
2
  import { ConfigField, ConfigInvalid, Env } from "@btravstack/config";
3
- import { EventSink, Level, Logger, LoggerService } from "@btravstack/core";
3
+ import { EventSink, Level, Logger, LoggerService, Observers } from "@btravstack/core";
4
4
  import { Module } from "@btravstack/di";
5
5
  //#region src/config.d.ts
6
6
  /**
@@ -63,7 +63,7 @@ type ObservabilityOptions = {
63
63
  * `currentUnit()` per call — so a request's lines are attributable with no
64
64
  * argument threaded through the call stack.
65
65
  */
66
- declare const observability: (options?: ObservabilityOptions) => Module<Logger | LoggerConfig, ConfigInvalid, Env>;
66
+ declare const observability: (options?: ObservabilityOptions) => Module<Logger | LoggerConfig | Observers, ConfigInvalid, Env>;
67
67
  /**
68
68
  * The kernel's nine lifecycle events, as log lines on `logger` — the adapter
69
69
  * between `StartOptions.onEvent` and an application's own stream.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { n as Sink, r as createLogger, t as Line } from "./logger-B1-_76ej.mjs";
2
2
  import { ConfigField, ConfigInvalid, Env } from "@btravstack/config";
3
- import { EventSink, Level, Logger, LoggerService } from "@btravstack/core";
3
+ import { EventSink, Level, Logger, LoggerService, Observers } from "@btravstack/core";
4
4
  import { Module } from "@btravstack/di";
5
5
  //#region src/config.d.ts
6
6
  /**
@@ -63,7 +63,7 @@ type ObservabilityOptions = {
63
63
  * `currentUnit()` per call — so a request's lines are attributable with no
64
64
  * argument threaded through the call stack.
65
65
  */
66
- declare const observability: (options?: ObservabilityOptions) => Module<Logger | LoggerConfig, ConfigInvalid, Env>;
66
+ declare const observability: (options?: ObservabilityOptions) => Module<Logger | LoggerConfig | Observers, ConfigInvalid, Env>;
67
67
  /**
68
68
  * The kernel's nine lifecycle events, as log lines on `logger` — the adapter
69
69
  * between `StartOptions.onEvent` and an application's own stream.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Config, ConfigFieldInvalid, Env } from "@btravstack/config";
2
- import { LEVELS, Logger, currentUnit } from "@btravstack/core";
2
+ import { LEVELS, Logger, Observers, currentUnit } from "@btravstack/core";
3
3
  import { Module, Port, Provider } from "@btravstack/di";
4
4
  //#region src/config.ts
5
5
  const isLevel = (value) => LEVELS.includes(value);
@@ -149,8 +149,29 @@ var LoggerConfig = class extends Port("LoggerConfig") {};
149
149
  */
150
150
  const observability = (options = {}) => Module("Observability")({
151
151
  needs: [Env],
152
- provides: [Config.provider(LoggerConfig)(loggerSchema(options.level)), Provider(Logger)({ config: LoggerConfig }, { sync: ({ config }) => createLogger(options.sink ?? jsonSink(), config.level) })],
153
- exports: [Logger, LoggerConfig]
152
+ provides: [
153
+ Config.provider(LoggerConfig)(loggerSchema(options.level)),
154
+ Provider(Logger)({
155
+ inject: { config: LoggerConfig },
156
+ sync: ({ config }) => createLogger(options.sink ?? jsonSink(), config.level)
157
+ }),
158
+ Provider.member(Observers)({
159
+ inject: { logger: Logger },
160
+ sync: ({ logger }) => ({ component, name, attributes, details }) => ({ outcome, attributes: settled, cause }) => {
161
+ if (outcome !== "error") return;
162
+ logger.error(`${component}.${name} failed`, {
163
+ ...attributes,
164
+ ...details,
165
+ ...settled
166
+ }, cause);
167
+ }
168
+ })
169
+ ],
170
+ exports: [
171
+ Logger,
172
+ LoggerConfig,
173
+ Observers
174
+ ]
154
175
  });
155
176
  /**
156
177
  * The kernel's nine lifecycle events, as log lines on `logger` — the adapter
package/dist/otel.cjs CHANGED
@@ -35,8 +35,52 @@ var OtelSdk = class extends (0, _btravstack_di.Port)("OtelSdk") {};
35
35
  */
36
36
  const otel = (options) => (0, _btravstack_di.Module)("Otel")({
37
37
  provides: [
38
- _btravstack_di.Provider.member(_btravstack_core.Instrumentations)({ value: () => Promise.resolve(void 0) }),
39
- (0, _btravstack_di.Provider)(OtelSdk)({ offered: _btravstack_core.Instrumentations }, {
38
+ _btravstack_di.Provider.member(_btravstack_core.Instrumentations)({
39
+ inject: {},
40
+ value: () => Promise.resolve(void 0)
41
+ }),
42
+ _btravstack_di.Provider.member(_btravstack_core.Observers)({
43
+ inject: {},
44
+ sync: () => {
45
+ const tracer = _opentelemetry_api.trace.getTracer("@btravstack/observability");
46
+ const counters = /* @__PURE__ */ new Map();
47
+ const durations = /* @__PURE__ */ new Map();
48
+ const instrument = (cache, key, make) => {
49
+ const existing = cache.get(key);
50
+ if (existing !== void 0) return existing;
51
+ const minted = make();
52
+ cache.set(key, minted);
53
+ return minted;
54
+ };
55
+ return ({ component, name, attributes, details, traced }) => {
56
+ const meter = _opentelemetry_api.metrics.getMeter("@btravstack/observability");
57
+ const span = traced === false ? void 0 : tracer.startSpan(`${component}.${name}`);
58
+ span?.setAttributes({
59
+ ...attributes,
60
+ ...details
61
+ });
62
+ const startedAt = performance.now();
63
+ const operations = instrument(counters, component, () => meter.createCounter(`btravstack.${component}.operations`, { description: `${component} operations, by operation and outcome` }));
64
+ const duration = instrument(durations, component, () => meter.createHistogram(`btravstack.${component}.duration`, {
65
+ description: `${component} operation duration`,
66
+ unit: "ms"
67
+ }));
68
+ return ({ outcome, attributes: settled }) => {
69
+ const all = {
70
+ ...attributes,
71
+ ...settled,
72
+ outcome
73
+ };
74
+ operations.add(1, all);
75
+ duration.record(performance.now() - startedAt, all);
76
+ if (outcome === "error") span?.setStatus({ code: _btravstack_core.SPAN_STATUS.error });
77
+ span?.end();
78
+ };
79
+ };
80
+ }
81
+ }),
82
+ (0, _btravstack_di.Provider)(OtelSdk)({
83
+ inject: { offered: _btravstack_core.Instrumentations },
40
84
  acquire: ({ offered }) => (0, unthrown.fromSafePromise)(Promise.all(offered.map((load) => load())).then((loaded) => {
41
85
  const sdk = new _opentelemetry_sdk_node.NodeSDK({
42
86
  ...options,
@@ -47,13 +91,23 @@ const otel = (options) => (0, _btravstack_di.Module)("Otel")({
47
91
  })),
48
92
  release: (sdk) => sdk.shutdown()
49
93
  }),
50
- (0, _btravstack_di.Provider)(_btravstack_core.Tracer)({ sdk: OtelSdk }, { sync: () => {
51
- const tracer = _opentelemetry_api.trace.getTracer("@btravstack/observability");
52
- return { startSpan: (name) => tracer.startSpan(name) };
53
- } }),
54
- (0, _btravstack_di.Provider)(_btravstack_core.Meter)({ sdk: OtelSdk }, { sync: () => _opentelemetry_api.metrics.getMeter("@btravstack/observability") })
94
+ (0, _btravstack_di.Provider)(_btravstack_core.Tracer)({
95
+ inject: { sdk: OtelSdk },
96
+ sync: () => {
97
+ const tracer = _opentelemetry_api.trace.getTracer("@btravstack/observability");
98
+ return { startSpan: (name) => tracer.startSpan(name) };
99
+ }
100
+ }),
101
+ (0, _btravstack_di.Provider)(_btravstack_core.Meter)({
102
+ inject: { sdk: OtelSdk },
103
+ sync: () => _opentelemetry_api.metrics.getMeter("@btravstack/observability")
104
+ })
55
105
  ],
56
- exports: [_btravstack_core.Tracer, _btravstack_core.Meter]
106
+ exports: [
107
+ _btravstack_core.Tracer,
108
+ _btravstack_core.Meter,
109
+ _btravstack_core.Observers
110
+ ]
57
111
  });
58
112
  /** The span a unit rides, ended when the unit's fork closes. */
59
113
  var UnitSpan = class extends (0, _btravstack_di.Port)("UnitSpan") {};
@@ -72,7 +126,8 @@ var UnitSpan = class extends (0, _btravstack_di.Port)("UnitSpan") {};
72
126
  */
73
127
  const UnitSpanModule = (0, _btravstack_di.Module)("UnitSpan")({
74
128
  needs: [_btravstack_core.Tracer],
75
- provides: [(0, _btravstack_di.Provider)(UnitSpan)({ tracer: _btravstack_core.Tracer }, {
129
+ provides: [(0, _btravstack_di.Provider)(UnitSpan)({
130
+ inject: { tracer: _btravstack_core.Tracer },
76
131
  sync: ({ tracer }) => {
77
132
  const unit = (0, _btravstack_core.currentUnit)();
78
133
  const span = tracer.startSpan("unit");
package/dist/otel.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Meter, Span, Tracer } from "@btravstack/core";
1
+ import { Meter, Observers, Span, Tracer } from "@btravstack/core";
2
2
  import { Module, Scope } from "@btravstack/di";
3
3
  import { NodeSDKConfiguration } from "@opentelemetry/sdk-node";
4
4
  //#region src/otel.d.ts
@@ -29,7 +29,7 @@ import { NodeSDKConfiguration } from "@opentelemetry/sdk-node";
29
29
  * plan time and in `Needs`. Guice's `newSetBinder` declares the empty set for
30
30
  * the same reason.
31
31
  */
32
- declare const otel: (options?: Partial<NodeSDKConfiguration>) => Module<Tracer | Meter, never, Scope>;
32
+ declare const otel: (options?: Partial<NodeSDKConfiguration>) => Module<Tracer | Meter | Observers, never, Scope>;
33
33
  declare const UnitSpan_base: import("@btravstack/di").PortClass<"UnitSpan">;
34
34
  /** The span a unit rides, ended when the unit's fork closes. */
35
35
  declare class UnitSpan extends UnitSpan_base<Span> {}
package/dist/otel.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Meter, Span, Tracer } from "@btravstack/core";
1
+ import { Meter, Observers, Span, Tracer } from "@btravstack/core";
2
2
  import { Module, Scope } from "@btravstack/di";
3
3
  import { NodeSDKConfiguration } from "@opentelemetry/sdk-node";
4
4
  //#region src/otel.d.ts
@@ -29,7 +29,7 @@ import { NodeSDKConfiguration } from "@opentelemetry/sdk-node";
29
29
  * plan time and in `Needs`. Guice's `newSetBinder` declares the empty set for
30
30
  * the same reason.
31
31
  */
32
- declare const otel: (options?: Partial<NodeSDKConfiguration>) => Module<Tracer | Meter, never, Scope>;
32
+ declare const otel: (options?: Partial<NodeSDKConfiguration>) => Module<Tracer | Meter | Observers, never, Scope>;
33
33
  declare const UnitSpan_base: import("@btravstack/di").PortClass<"UnitSpan">;
34
34
  /** The span a unit rides, ended when the unit's fork closes. */
35
35
  declare class UnitSpan extends UnitSpan_base<Span> {}
package/dist/otel.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Instrumentations, Meter, Tracer, currentUnit } from "@btravstack/core";
1
+ import { Instrumentations, Meter, Observers, SPAN_STATUS, Tracer, currentUnit } from "@btravstack/core";
2
2
  import { Module, Port, Provider } from "@btravstack/di";
3
3
  import { metrics, trace } from "@opentelemetry/api";
4
4
  import { NodeSDK } from "@opentelemetry/sdk-node";
@@ -34,8 +34,52 @@ var OtelSdk = class extends Port("OtelSdk") {};
34
34
  */
35
35
  const otel = (options) => Module("Otel")({
36
36
  provides: [
37
- Provider.member(Instrumentations)({ value: () => Promise.resolve(void 0) }),
38
- Provider(OtelSdk)({ offered: Instrumentations }, {
37
+ Provider.member(Instrumentations)({
38
+ inject: {},
39
+ value: () => Promise.resolve(void 0)
40
+ }),
41
+ Provider.member(Observers)({
42
+ inject: {},
43
+ sync: () => {
44
+ const tracer = trace.getTracer("@btravstack/observability");
45
+ const counters = /* @__PURE__ */ new Map();
46
+ const durations = /* @__PURE__ */ new Map();
47
+ const instrument = (cache, key, make) => {
48
+ const existing = cache.get(key);
49
+ if (existing !== void 0) return existing;
50
+ const minted = make();
51
+ cache.set(key, minted);
52
+ return minted;
53
+ };
54
+ return ({ component, name, attributes, details, traced }) => {
55
+ const meter = metrics.getMeter("@btravstack/observability");
56
+ const span = traced === false ? void 0 : tracer.startSpan(`${component}.${name}`);
57
+ span?.setAttributes({
58
+ ...attributes,
59
+ ...details
60
+ });
61
+ const startedAt = performance.now();
62
+ const operations = instrument(counters, component, () => meter.createCounter(`btravstack.${component}.operations`, { description: `${component} operations, by operation and outcome` }));
63
+ const duration = instrument(durations, component, () => meter.createHistogram(`btravstack.${component}.duration`, {
64
+ description: `${component} operation duration`,
65
+ unit: "ms"
66
+ }));
67
+ return ({ outcome, attributes: settled }) => {
68
+ const all = {
69
+ ...attributes,
70
+ ...settled,
71
+ outcome
72
+ };
73
+ operations.add(1, all);
74
+ duration.record(performance.now() - startedAt, all);
75
+ if (outcome === "error") span?.setStatus({ code: SPAN_STATUS.error });
76
+ span?.end();
77
+ };
78
+ };
79
+ }
80
+ }),
81
+ Provider(OtelSdk)({
82
+ inject: { offered: Instrumentations },
39
83
  acquire: ({ offered }) => fromSafePromise(Promise.all(offered.map((load) => load())).then((loaded) => {
40
84
  const sdk = new NodeSDK({
41
85
  ...options,
@@ -46,13 +90,23 @@ const otel = (options) => Module("Otel")({
46
90
  })),
47
91
  release: (sdk) => sdk.shutdown()
48
92
  }),
49
- Provider(Tracer)({ sdk: OtelSdk }, { sync: () => {
50
- const tracer = trace.getTracer("@btravstack/observability");
51
- return { startSpan: (name) => tracer.startSpan(name) };
52
- } }),
53
- Provider(Meter)({ sdk: OtelSdk }, { sync: () => metrics.getMeter("@btravstack/observability") })
93
+ Provider(Tracer)({
94
+ inject: { sdk: OtelSdk },
95
+ sync: () => {
96
+ const tracer = trace.getTracer("@btravstack/observability");
97
+ return { startSpan: (name) => tracer.startSpan(name) };
98
+ }
99
+ }),
100
+ Provider(Meter)({
101
+ inject: { sdk: OtelSdk },
102
+ sync: () => metrics.getMeter("@btravstack/observability")
103
+ })
54
104
  ],
55
- exports: [Tracer, Meter]
105
+ exports: [
106
+ Tracer,
107
+ Meter,
108
+ Observers
109
+ ]
56
110
  });
57
111
  /** The span a unit rides, ended when the unit's fork closes. */
58
112
  var UnitSpan = class extends Port("UnitSpan") {};
@@ -71,7 +125,8 @@ var UnitSpan = class extends Port("UnitSpan") {};
71
125
  */
72
126
  const UnitSpanModule = Module("UnitSpan")({
73
127
  needs: [Tracer],
74
- provides: [Provider(UnitSpan)({ tracer: Tracer }, {
128
+ provides: [Provider(UnitSpan)({
129
+ inject: { tracer: Tracer },
75
130
  sync: ({ tracer }) => {
76
131
  const unit = currentUnit();
77
132
  const span = tracer.startSpan("unit");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/observability",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Observability for @btravstack/core: a strict Logger port correlated with the kernel's units, a dependency-free JSON sink, and the lifecycle events as log lines",
5
5
  "keywords": [
6
6
  "dependency-injection",
@@ -77,19 +77,19 @@
77
77
  "typescript": "7.0.2",
78
78
  "unthrown": "5.7.0",
79
79
  "vitest": "4.1.11",
80
- "@btravstack/config": "0.8.0",
81
- "@btravstack/core": "0.8.0",
82
- "@btravstack/di": "0.8.0",
83
- "@btravstack/testing": "0.8.0"
80
+ "@btravstack/config": "0.10.0",
81
+ "@btravstack/core": "0.10.0",
82
+ "@btravstack/di": "0.10.0",
83
+ "@btravstack/testing": "0.10.0"
84
84
  },
85
85
  "peerDependencies": {
86
86
  "@opentelemetry/api": "^1.9.0",
87
87
  "@opentelemetry/sdk-node": "^0.221.0",
88
88
  "pino": "^10",
89
89
  "unthrown": "^5.0.0",
90
- "@btravstack/config": "^0.8.0",
91
- "@btravstack/core": "^0.8.0",
92
- "@btravstack/di": "^0.8.0"
90
+ "@btravstack/config": "^0.10.0",
91
+ "@btravstack/core": "^0.10.0",
92
+ "@btravstack/di": "^0.10.0"
93
93
  },
94
94
  "peerDependenciesMeta": {
95
95
  "@opentelemetry/api": {