@cratis/chronicle 6.33.0 → 6.35.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 (243) hide show
  1. package/ChronicleClient.ts +43 -35
  2. package/ChronicleOptions.ts +27 -1
  3. package/EventStore.ts +22 -12
  4. package/Metrics.ts +70 -96
  5. package/Tracing.ts +4 -2
  6. package/WellKnownTelemetryNames.ts +67 -0
  7. package/artifacts/validateArtifactSchemas.spec.ts +1 -1
  8. package/artifacts/withActivatedArtifact.ts +7 -5
  9. package/connection/ChronicleConnection.ts +18 -6
  10. package/connection/CompatibilityPreflight.ts +2 -1
  11. package/connection/KernelKeepAlive.ts +13 -7
  12. package/connection/TokenProvider.ts +3 -1
  13. package/connection/TraceContextMiddleware.ts +30 -0
  14. package/connection/for_ChronicleConnection/when_creating_clients/with_correlated_multi_host_callers.spec.ts +73 -0
  15. package/connection/for_OAuthTokenProvider/when_acquiring_tokens/with_http_instrumentation.spec.ts +102 -0
  16. package/connection/for_TraceContextMiddleware/when_calling/with_active_context.spec.ts +235 -0
  17. package/connection/for_TraceContextMiddleware/when_calling/with_grpc_instrumentation.spec.ts +102 -0
  18. package/correlation/CorrelationIdManager.ts +10 -0
  19. package/dist/ChronicleClient.d.ts.map +1 -1
  20. package/dist/ChronicleClient.js +45 -35
  21. package/dist/ChronicleClient.js.map +1 -1
  22. package/dist/ChronicleOptions.d.ts +8 -0
  23. package/dist/ChronicleOptions.d.ts.map +1 -1
  24. package/dist/ChronicleOptions.js +19 -1
  25. package/dist/ChronicleOptions.js.map +1 -1
  26. package/dist/EventStore.d.ts +3 -1
  27. package/dist/EventStore.d.ts.map +1 -1
  28. package/dist/EventStore.js +22 -12
  29. package/dist/EventStore.js.map +1 -1
  30. package/dist/Metrics.d.ts +23 -64
  31. package/dist/Metrics.d.ts.map +1 -1
  32. package/dist/Metrics.js +62 -92
  33. package/dist/Metrics.js.map +1 -1
  34. package/dist/Tracing.d.ts +1 -1
  35. package/dist/Tracing.d.ts.map +1 -1
  36. package/dist/Tracing.js +4 -2
  37. package/dist/Tracing.js.map +1 -1
  38. package/dist/WellKnownTelemetryNames.d.ts +65 -0
  39. package/dist/WellKnownTelemetryNames.d.ts.map +1 -0
  40. package/dist/WellKnownTelemetryNames.js +67 -0
  41. package/dist/WellKnownTelemetryNames.js.map +1 -0
  42. package/dist/artifacts/withActivatedArtifact.d.ts +2 -1
  43. package/dist/artifacts/withActivatedArtifact.d.ts.map +1 -1
  44. package/dist/artifacts/withActivatedArtifact.js +5 -4
  45. package/dist/artifacts/withActivatedArtifact.js.map +1 -1
  46. package/dist/connection/ChronicleConnection.d.ts +5 -0
  47. package/dist/connection/ChronicleConnection.d.ts.map +1 -1
  48. package/dist/connection/ChronicleConnection.js +13 -6
  49. package/dist/connection/ChronicleConnection.js.map +1 -1
  50. package/dist/connection/CompatibilityPreflight.d.ts.map +1 -1
  51. package/dist/connection/CompatibilityPreflight.js +2 -1
  52. package/dist/connection/CompatibilityPreflight.js.map +1 -1
  53. package/dist/connection/KernelKeepAlive.d.ts +3 -1
  54. package/dist/connection/KernelKeepAlive.d.ts.map +1 -1
  55. package/dist/connection/KernelKeepAlive.js +10 -7
  56. package/dist/connection/KernelKeepAlive.js.map +1 -1
  57. package/dist/connection/TokenProvider.d.ts.map +1 -1
  58. package/dist/connection/TokenProvider.js +3 -1
  59. package/dist/connection/TokenProvider.js.map +1 -1
  60. package/dist/connection/TraceContextMiddleware.d.ts +4 -0
  61. package/dist/connection/TraceContextMiddleware.d.ts.map +1 -0
  62. package/dist/connection/TraceContextMiddleware.js +30 -0
  63. package/dist/connection/TraceContextMiddleware.js.map +1 -0
  64. package/dist/correlation/CorrelationIdManager.d.ts +4 -0
  65. package/dist/correlation/CorrelationIdManager.d.ts.map +1 -1
  66. package/dist/correlation/CorrelationIdManager.js +8 -0
  67. package/dist/correlation/CorrelationIdManager.js.map +1 -1
  68. package/dist/eventSequences/EventLog.d.ts +2 -1
  69. package/dist/eventSequences/EventLog.d.ts.map +1 -1
  70. package/dist/eventSequences/EventLog.js +2 -2
  71. package/dist/eventSequences/EventLog.js.map +1 -1
  72. package/dist/eventSequences/EventSequence.d.ts +4 -1
  73. package/dist/eventSequences/EventSequence.d.ts.map +1 -1
  74. package/dist/eventSequences/EventSequence.js +63 -80
  75. package/dist/eventSequences/EventSequence.js.map +1 -1
  76. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.d.ts.map +1 -1
  77. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js +3 -4
  78. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js.map +1 -1
  79. package/dist/events/handles.d.ts +12 -0
  80. package/dist/events/handles.d.ts.map +1 -0
  81. package/dist/events/handles.js +33 -0
  82. package/dist/events/handles.js.map +1 -0
  83. package/dist/events/index.d.ts +1 -0
  84. package/dist/events/index.d.ts.map +1 -1
  85. package/dist/events/index.js +1 -0
  86. package/dist/events/index.js.map +1 -1
  87. package/dist/index.d.ts +7 -1
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +4 -1
  90. package/dist/index.js.map +1 -1
  91. package/dist/logging/ChronicleLogEntry.d.ts +11 -0
  92. package/dist/logging/ChronicleLogEntry.d.ts.map +1 -0
  93. package/dist/logging/ChronicleLogEntry.js +4 -0
  94. package/dist/logging/ChronicleLogEntry.js.map +1 -0
  95. package/dist/logging/ChronicleLogLevel.d.ts +9 -0
  96. package/dist/logging/ChronicleLogLevel.d.ts.map +1 -0
  97. package/dist/logging/ChronicleLogLevel.js +12 -0
  98. package/dist/logging/ChronicleLogLevel.js.map +1 -0
  99. package/dist/logging/DiagChronicleLogger.d.ts +7 -0
  100. package/dist/logging/DiagChronicleLogger.d.ts.map +1 -0
  101. package/dist/logging/DiagChronicleLogger.js +10 -0
  102. package/dist/logging/DiagChronicleLogger.js.map +1 -0
  103. package/dist/logging/IChronicleLogger.d.ts +7 -0
  104. package/dist/logging/IChronicleLogger.d.ts.map +1 -0
  105. package/dist/logging/IChronicleLogger.js +4 -0
  106. package/dist/logging/IChronicleLogger.js.map +1 -0
  107. package/dist/logging/createLogger.d.ts +10 -0
  108. package/dist/logging/createLogger.d.ts.map +1 -0
  109. package/dist/logging/createLogger.js +55 -0
  110. package/dist/logging/createLogger.js.map +1 -0
  111. package/dist/observation/EventHandler.d.ts +7 -0
  112. package/dist/observation/EventHandler.d.ts.map +1 -0
  113. package/dist/observation/EventHandler.js +4 -0
  114. package/dist/observation/EventHandler.js.map +1 -0
  115. package/dist/observation/createHandlerInstanceValidator.d.ts +4 -0
  116. package/dist/observation/createHandlerInstanceValidator.d.ts.map +1 -0
  117. package/dist/observation/createHandlerInstanceValidator.js +40 -0
  118. package/dist/observation/createHandlerInstanceValidator.js.map +1 -0
  119. package/dist/observation/getEventHandlers.d.ts +5 -0
  120. package/dist/observation/getEventHandlers.d.ts.map +1 -0
  121. package/dist/observation/getEventHandlers.js +40 -0
  122. package/dist/observation/getEventHandlers.js.map +1 -0
  123. package/dist/observation/getInstanceMethods.d.ts +4 -0
  124. package/dist/observation/getInstanceMethods.d.ts.map +1 -0
  125. package/dist/observation/getInstanceMethods.js +29 -0
  126. package/dist/observation/getInstanceMethods.js.map +1 -0
  127. package/dist/projections/Projections.d.ts.map +1 -1
  128. package/dist/projections/Projections.js +4 -3
  129. package/dist/projections/Projections.js.map +1 -1
  130. package/dist/reactors/ReactorDispatcher.d.ts.map +1 -1
  131. package/dist/reactors/ReactorDispatcher.js +27 -32
  132. package/dist/reactors/ReactorDispatcher.js.map +1 -1
  133. package/dist/reactors/Reactors.d.ts +1 -0
  134. package/dist/reactors/Reactors.d.ts.map +1 -1
  135. package/dist/reactors/Reactors.js +49 -34
  136. package/dist/reactors/Reactors.js.map +1 -1
  137. package/dist/reactors/index.d.ts +1 -0
  138. package/dist/reactors/index.d.ts.map +1 -1
  139. package/dist/reactors/index.js +1 -0
  140. package/dist/reactors/index.js.map +1 -1
  141. package/dist/reducers/ReducerEventDispatcher.d.ts +1 -0
  142. package/dist/reducers/ReducerEventDispatcher.d.ts.map +1 -1
  143. package/dist/reducers/ReducerEventDispatcher.js +5 -17
  144. package/dist/reducers/ReducerEventDispatcher.js.map +1 -1
  145. package/dist/reducers/ReducerEventHandler.d.ts +3 -6
  146. package/dist/reducers/ReducerEventHandler.d.ts.map +1 -1
  147. package/dist/reducers/Reducers.d.ts +1 -0
  148. package/dist/reducers/Reducers.d.ts.map +1 -1
  149. package/dist/reducers/Reducers.js +47 -32
  150. package/dist/reducers/Reducers.js.map +1 -1
  151. package/dist/reducers/index.d.ts +1 -0
  152. package/dist/reducers/index.d.ts.map +1 -1
  153. package/dist/reducers/index.js +1 -0
  154. package/dist/reducers/index.js.map +1 -1
  155. package/dist/seeding/EventSeeding.d.ts.map +1 -1
  156. package/dist/seeding/EventSeeding.js +4 -3
  157. package/dist/seeding/EventSeeding.js.map +1 -1
  158. package/dist/telemetry/ChronicleTelemetryOptions.d.ts +11 -0
  159. package/dist/telemetry/ChronicleTelemetryOptions.d.ts.map +1 -0
  160. package/dist/telemetry/ChronicleTelemetryOptions.js +4 -0
  161. package/dist/telemetry/ChronicleTelemetryOptions.js.map +1 -0
  162. package/dist/telemetry/observeOperation.d.ts +6 -0
  163. package/dist/telemetry/observeOperation.d.ts.map +1 -0
  164. package/dist/telemetry/observeOperation.js +23 -0
  165. package/dist/telemetry/observeOperation.js.map +1 -0
  166. package/dist/telemetry/runInBackgroundContext.d.ts +3 -0
  167. package/dist/telemetry/runInBackgroundContext.d.ts.map +1 -0
  168. package/dist/telemetry/runInBackgroundContext.js +9 -0
  169. package/dist/telemetry/runInBackgroundContext.js.map +1 -0
  170. package/dist/telemetry/spanAttributes.d.ts +14 -0
  171. package/dist/telemetry/spanAttributes.d.ts.map +1 -0
  172. package/dist/telemetry/spanAttributes.js +47 -0
  173. package/dist/telemetry/spanAttributes.js.map +1 -0
  174. package/dist/testing/ReactorScenario.d.ts +1 -0
  175. package/dist/testing/ReactorScenario.d.ts.map +1 -1
  176. package/dist/testing/ReactorScenario.js +9 -1
  177. package/dist/testing/ReactorScenario.js.map +1 -1
  178. package/dist/testing/ReducerReadModelProcessor.d.ts.map +1 -1
  179. package/dist/testing/ReducerReadModelProcessor.js +1 -0
  180. package/dist/testing/ReducerReadModelProcessor.js.map +1 -1
  181. package/dist/tsconfig.tsbuildinfo +1 -1
  182. package/eventSequences/EventLog.ts +4 -2
  183. package/eventSequences/EventSequence.ts +64 -81
  184. package/eventStoreSubscriptions/EventStoreSubscriptions.ts +5 -5
  185. package/events/for_handles/when_decorating_a_method.legacy.spec.ts +91 -0
  186. package/events/for_handles/when_decorating_a_method.standard.spec.ts +102 -0
  187. package/events/handles.ts +37 -0
  188. package/events/index.ts +1 -0
  189. package/for_ChronicleClient/when_registering_artifacts/with_invalid_handler_mappings.spec.ts +142 -0
  190. package/for_ChronicleClient/when_starting_background_work/with_a_correlated_caller.spec.ts +150 -0
  191. package/for_ChronicleOptions/when_configuring_telemetry/with_event_source_identifiers.spec.ts +44 -0
  192. package/index.ts +7 -1
  193. package/logging/ChronicleLogEntry.ts +14 -0
  194. package/logging/ChronicleLogLevel.ts +11 -0
  195. package/logging/DiagChronicleLogger.ts +13 -0
  196. package/logging/IChronicleLogger.ts +10 -0
  197. package/logging/createLogger.ts +50 -0
  198. package/logging/for_ChronicleLogger/when_logging/with_client_owned_sinks.spec.ts +96 -0
  199. package/observation/EventHandler.ts +9 -0
  200. package/observation/createHandlerInstanceValidator.ts +42 -0
  201. package/observation/for_getEventHandlers/when_discovering_handlers.spec.ts +137 -0
  202. package/observation/for_getEventHandlers/when_reobserving_explicit_handlers.spec.ts +73 -0
  203. package/observation/getEventHandlers.ts +43 -0
  204. package/observation/getInstanceMethods.ts +29 -0
  205. package/package.json +8 -2
  206. package/projections/Projections.ts +4 -3
  207. package/reactors/ReactorDispatcher.ts +26 -30
  208. package/reactors/Reactors.ts +55 -37
  209. package/reactors/decorators.standard.spec.ts +8 -0
  210. package/reactors/for_ReactorDispatcher/when_selecting_explicit_handlers.spec.ts +64 -0
  211. package/reactors/for_Reactors/when_activating_a_field_override.spec.ts +62 -0
  212. package/reactors/for_Reactors/when_delivering/with_activation.spec.ts +14 -14
  213. package/reactors/for_Reactors/when_delivering_to_instance_handlers.spec.ts +108 -0
  214. package/reactors/for_Reactors/when_observing_explicit_handlers.legacy.spec.ts +77 -0
  215. package/reactors/for_Reactors/when_registering_explicit_handlers.spec.ts +53 -0
  216. package/reactors/index.ts +1 -0
  217. package/reducers/ReducerEventDispatcher.ts +5 -16
  218. package/reducers/ReducerEventHandler.ts +4 -6
  219. package/reducers/Reducers.ts +50 -35
  220. package/reducers/for_ReducerEventDispatcher/when_invoking_an_explicit_handler.spec.ts +51 -0
  221. package/reducers/for_ReducerEventDispatcher/when_invoking_instance_handlers.spec.ts +69 -0
  222. package/reducers/for_Reducers/when_activating_a_field_override.spec.ts +63 -0
  223. package/reducers/for_Reducers/when_observing_explicit_handlers.legacy.spec.ts +73 -0
  224. package/reducers/for_Reducers/when_registering_explicit_handlers.spec.ts +57 -0
  225. package/reducers/index.ts +1 -0
  226. package/seeding/EventSeeding.ts +6 -4
  227. package/telemetry/ChronicleTelemetryOptions.ts +8 -0
  228. package/telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.ts +36 -0
  229. package/telemetry/for_ChronicleTelemetry/given/an_event_sequence.fixture.ts +29 -0
  230. package/telemetry/for_ChronicleTelemetry/when_appending/with_shared_and_legacy_attributes.spec.ts +58 -0
  231. package/telemetry/for_ChronicleTelemetry/when_appending/without_an_sdk.spec.ts +15 -0
  232. package/telemetry/for_ChronicleTelemetry/when_documenting/with_public_names.spec.ts +15 -0
  233. package/telemetry/for_ChronicleTelemetry/when_recording_failures/with_sensitive_exception_details.spec.ts +34 -0
  234. package/telemetry/for_ChronicleTelemetry/when_recording_identifiers/with_per_client_privacy.spec.ts +92 -0
  235. package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_legacy_overlap.spec.ts +40 -0
  236. package/telemetry/for_ChronicleTelemetry/when_recording_sequence_numbers/with_integer_precision.spec.ts +23 -0
  237. package/telemetry/observeOperation.ts +25 -0
  238. package/telemetry/runInBackgroundContext.ts +10 -0
  239. package/telemetry/spanAttributes.ts +49 -0
  240. package/testing/ReactorScenario.ts +9 -1
  241. package/testing/ReducerReadModelProcessor.ts +1 -0
  242. package/testing/for_ReactorScenario/when_delivering_to_an_explicit_handler.spec.ts +100 -0
  243. package/testing/for_ReducerReadModelProcessor/when_an_instance_field_hides_a_handler.spec.ts +29 -0
@@ -0,0 +1,235 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { afterEach, beforeEach, chai, describe, it, vi } from 'vitest';
5
+ import { context, propagation, trace } from '@opentelemetry/api';
6
+ import { createServer, ServerError, Status, type ServiceImplementation } from 'nice-grpc';
7
+ import { Metadata } from 'nice-grpc-common';
8
+ import { ConnectionServiceDefinition, EventSequencesDefinition, EventStoresDefinition, ReactorsDefinition } from '@cratis/chronicle.contracts';
9
+ import { ChronicleConnection } from '../../ChronicleConnection.js';
10
+ import { OAuthTokenProvider } from '../../TokenProvider.js';
11
+ import { CorrelationId, correlationIdManager } from '../../../correlation/index.js';
12
+ import { telemetrySession } from '../../../telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.js';
13
+
14
+ const should = chai.should();
15
+ const telemetry = telemetrySession();
16
+ const correlation = 'f5a978af-cd69-4a98-9f1e-7f656cc8ba2a';
17
+ let server: ReturnType<typeof createServer>;
18
+ let connection: ChronicleConnection;
19
+ let received: Metadata[];
20
+ let original: Metadata;
21
+ let traceparent: string;
22
+ let address: string;
23
+ let rejectNext: boolean;
24
+
25
+ beforeEach(async () => {
26
+ received = [];
27
+ rejectNext = false;
28
+ server = createServer().use(async function* (call, callContext) {
29
+ received.push(Metadata(callContext.metadata));
30
+ return yield* call.next(call.request, callContext);
31
+ });
32
+ const unused = async () => { throw new Error('Unexpected RPC'); };
33
+ server.add(ConnectionServiceDefinition, {
34
+ ...Object.fromEntries(Object.keys(ConnectionServiceDefinition.methods).map(name => [name, unused])),
35
+ checkCompatibility: async () => ({ IsCompatible: true, Incompatibilities: [], ServerVersion: 'test' }),
36
+ connectionKeepAlive: async () => ({})
37
+ } as ServiceImplementation<typeof ConnectionServiceDefinition>);
38
+ server.add(EventStoresDefinition, {
39
+ allEventStores: async () => {
40
+ if (rejectNext) {
41
+ rejectNext = false;
42
+ throw new ServerError(Status.UNAUTHENTICATED, 'Refresh required');
43
+ }
44
+ return { IsAuthorized: true, Data: [] };
45
+ },
46
+ ensureEventStore: unused,
47
+ observeEventStores: async function* () { yield { IsAuthorized: true, Data: [] }; }
48
+ } as ServiceImplementation<typeof EventStoresDefinition>);
49
+ server.add(EventSequencesDefinition, {
50
+ ...Object.fromEntries(Object.keys(EventSequencesDefinition.methods).map(name => [name, unused])),
51
+ append: async () => ({ Response: { SequenceNumber: 0n } })
52
+ } as ServiceImplementation<typeof EventSequencesDefinition>);
53
+ server.add(ReactorsDefinition, {
54
+ ...Object.fromEntries(Object.keys(ReactorsDefinition.methods).map(name => [name, unused])),
55
+ observe: async function* (requests) {
56
+ for await (const _request of requests) {
57
+ yield { Events: [], Partition: '', ReplayState: 0 };
58
+ return;
59
+ }
60
+ }
61
+ } as ServiceImplementation<typeof ReactorsDefinition>);
62
+ const port = await server.listen('127.0.0.1:0');
63
+ address = `chronicle://127.0.0.1:${port}?disableTls=true`;
64
+ connection = new ChronicleConnection({ connectionString: `${address}&apiKey=api-secret` });
65
+ original = Metadata({ authorization: 'Bearer caller-token', 'x-custom': 'preserved',
66
+ 'custom-bin': new Uint8Array([1, 2, 3]), baggage: 'email=private', traceparent: 'stale' });
67
+ });
68
+
69
+ afterEach(async () => {
70
+ connection?.dispose();
71
+ await server?.shutdown();
72
+ vi.restoreAllMocks();
73
+ });
74
+
75
+ async function inContext(action: (parent: string) => Promise<void>, correlationValue = correlation) {
76
+ const span = telemetry.traces.getTracer('host').startSpan('host');
77
+ const spanContext = span.spanContext();
78
+ const parent = `00-${spanContext.traceId}-${spanContext.spanId}-01`;
79
+ traceparent = parent;
80
+ const active = propagation.setBaggage(trace.setSpan(context.active(), span), propagation.createBaggage({
81
+ 'cratis.correlation_id': { value: correlationValue }, email: { value: 'private@example.com' },
82
+ 'cratis.event_source.id': { value: 'private-source' }, 'cratis.tenant.id': { value: 'private-tenant' }
83
+ }));
84
+ try {
85
+ await context.with(active, async () => {
86
+ await Promise.resolve();
87
+ await action(parent);
88
+ propagation.getBaggage(context.active())!.getEntry('email')!.value.should.equal('private@example.com');
89
+ });
90
+ } finally { span.end(); }
91
+ }
92
+
93
+ describe('when calling unary RPCs with active trace context and sensitive baggage', () => {
94
+ beforeEach(async () => {
95
+ await inContext(async () => {
96
+ await connection.connect();
97
+ await connection.eventStores.allEventStores({}, { metadata: original });
98
+ await connection.connections.connectionKeepAlive({ ConnectionId: 'test' });
99
+ await connection.reconnect();
100
+ await connection.eventStores.allEventStores({}, { metadata: original });
101
+ });
102
+ });
103
+ it('should detach connect and reconnect compatibility checks from caller context', () => {
104
+ received.should.have.lengthOf(5);
105
+ for (const index of [0, 3]) {
106
+ should.equal(received[index].get('traceparent'), undefined);
107
+ should.equal(received[index].get('baggage'), undefined);
108
+ should.equal(received[index].get('api-key'), 'api-secret');
109
+ }
110
+ });
111
+ it('should inject trace context and only correlation baggage on business calls and keep-alive', () => {
112
+ for (const index of [1, 2, 4]) {
113
+ should.equal(received[index].get('traceparent'), traceparent);
114
+ should.equal(received[index].get('baggage'), `cratis.correlation_id=${correlation}`);
115
+ should.equal(received[index].get('api-key'), 'api-secret');
116
+ }
117
+ });
118
+ it('should preserve authentication and unrelated string and binary metadata', () => {
119
+ const metadata = received[1];
120
+ should.equal(metadata.get('authorization'), 'Bearer caller-token');
121
+ should.equal(metadata.get('x-custom'), 'preserved');
122
+ Array.from(metadata.get('custom-bin')!).should.deep.equal([1, 2, 3]);
123
+ });
124
+ it('should not mutate the caller carrier', () => {
125
+ should.equal(original.get('traceparent'), 'stale');
126
+ should.equal(original.get('baggage'), 'email=private');
127
+ should.equal(original.get('api-key'), undefined);
128
+ });
129
+ });
130
+
131
+ describe('when calling an event sequence without a cached compatibility verdict', () => {
132
+ beforeEach(async () => {
133
+ await connection.resetChannel();
134
+ await correlationIdManager.run(new CorrelationId(correlation), () => inContext(async () => {
135
+ await connection.eventSequences.append({}, { metadata: original });
136
+ correlationIdManager.scoped!.toString().should.equal(correlation);
137
+ }));
138
+ });
139
+ it('should detach the shared compatibility check while preserving the business caller context', () => {
140
+ received.should.have.lengthOf(2);
141
+ should.equal(received[0].get('traceparent'), undefined);
142
+ should.equal(received[0].get('baggage'), undefined);
143
+ should.equal(received[1].get('traceparent'), traceparent);
144
+ should.equal(received[1].get('baggage'), `cratis.correlation_id=${correlation}`);
145
+ should.equal(received[1].get('authorization'), 'Bearer caller-token');
146
+ });
147
+ });
148
+
149
+ describe('when opening server and bidirectional streams', () => {
150
+ beforeEach(async () => {
151
+ await connection.connect();
152
+ received.length = 0;
153
+ await inContext(async () => {
154
+ for await (const _response of connection.eventStores.observeEventStores({}, { metadata: original })) { break; }
155
+ async function* requests() { yield { Content: undefined }; }
156
+ for await (const _response of connection.reactors.observe(requests(), { metadata: original })) { break; }
157
+ });
158
+ });
159
+ it('should propagate the stream-opening context and preserve authentication', () => {
160
+ received.should.have.lengthOf(2);
161
+ for (const metadata of received) {
162
+ should.equal(metadata.get('traceparent'), traceparent);
163
+ should.equal(metadata.get('baggage'), `cratis.correlation_id=${correlation}`);
164
+ should.equal(metadata.get('authorization'), 'Bearer caller-token');
165
+ }
166
+ });
167
+ });
168
+
169
+ describe('when authentication refresh retries a unary call', () => {
170
+ beforeEach(async () => {
171
+ connection.dispose();
172
+ vi.spyOn(OAuthTokenProvider.prototype, 'getAccessToken').mockResolvedValue('old-token');
173
+ vi.spyOn(OAuthTokenProvider.prototype, 'refresh').mockResolvedValue('refreshed-token');
174
+ connection = new ChronicleConnection({ connectionString: address });
175
+ await connection.connect();
176
+ received.length = 0;
177
+ rejectNext = true;
178
+ await inContext(async () => {
179
+ await connection.eventStores.allEventStores({}, { metadata: original });
180
+ });
181
+ });
182
+ it('should keep trace context and filtered baggage on both attempts with the refreshed authorization', () => {
183
+ received.should.have.lengthOf(2);
184
+ for (const metadata of received) {
185
+ should.equal(metadata.get('traceparent'), traceparent);
186
+ should.equal(metadata.get('baggage'), `cratis.correlation_id=${correlation}`);
187
+ }
188
+ should.equal(received[0].get('authorization'), 'Bearer old-token');
189
+ should.equal(received[1].get('authorization'), 'Bearer refreshed-token');
190
+ });
191
+ });
192
+
193
+ describe('when callers in two contexts use the same connection concurrently', () => {
194
+ const expected = new Map<string, { parent: string; correlationId: string }>();
195
+ beforeEach(async () => {
196
+ await connection.connect();
197
+ received.length = 0;
198
+ expected.clear();
199
+ const correlations = [correlation, '7144abf3-94bd-4429-93e5-3faf23f0bad1'];
200
+ let ready = 0;
201
+ let release!: () => void;
202
+ const bothReady = new Promise<void>(resolve => { release = resolve; });
203
+ await Promise.all(correlations.map((correlationId, index) =>
204
+ correlationIdManager.run(new CorrelationId(correlationId), () => inContext(async parent => {
205
+ const id = index.toString();
206
+ expected.set(id, { parent, correlationId });
207
+ if (++ready === 2) release();
208
+ await bothReady;
209
+ await connection.eventStores.allEventStores({}, { metadata: Metadata({ 'x-call': id }) });
210
+ correlationIdManager.scoped!.toString().should.equal(correlationId);
211
+ }, correlationId))));
212
+ });
213
+ it('should propagate each caller trace and business correlation without cross-contamination', () => {
214
+ received.should.have.lengthOf(2);
215
+ for (const metadata of received) {
216
+ const caller = expected.get(metadata.get('x-call')!)!;
217
+ should.equal(metadata.get('traceparent'), caller.parent);
218
+ should.equal(metadata.get('baggage'), `cratis.correlation_id=${caller.correlationId}`);
219
+ }
220
+ new Set(received.map(metadata => metadata.get('traceparent'))).size.should.equal(2);
221
+ });
222
+ });
223
+
224
+ describe('when calling without an active span or baggage', () => {
225
+ beforeEach(async () => {
226
+ await connection.connect();
227
+ received.length = 0;
228
+ await connection.eventStores.allEventStores({}, { metadata: original });
229
+ });
230
+ it('should remove stale propagation without losing other metadata', () => {
231
+ should.equal(received[0].get('traceparent'), undefined);
232
+ should.equal(received[0].get('baggage'), undefined);
233
+ should.equal(received[0].get('authorization'), 'Bearer caller-token');
234
+ });
235
+ });
@@ -0,0 +1,102 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { afterAll, beforeAll, beforeEach, chai, describe, it } from 'vitest';
5
+ import { context, propagation, trace } from '@opentelemetry/api';
6
+ import { GrpcInstrumentation } from '@opentelemetry/instrumentation-grpc';
7
+ import type { ServiceImplementation } from 'nice-grpc';
8
+ import { Metadata } from 'nice-grpc-common';
9
+ import type { ChronicleConnection } from '../../ChronicleConnection.js';
10
+ import { telemetrySession } from '../../../telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.js';
11
+
12
+ const should = chai.should();
13
+ const telemetry = telemetrySession();
14
+ const instrumentation = new GrpcInstrumentation({ enabled: false });
15
+ const correlation = 'f5a978af-cd69-4a98-9f1e-7f656cc8ba2a';
16
+ let server: ReturnType<typeof import('nice-grpc').createServer>;
17
+ let connection: ChronicleConnection;
18
+ const received: Metadata[] = [];
19
+ let hostTraceId: string;
20
+
21
+ beforeAll(async () => {
22
+ instrumentation.setTracerProvider(telemetry.traces);
23
+ instrumentation.enable();
24
+ // Load grpc-js only after the real instrumentation's require hooks are installed.
25
+ const { createServer } = await import('nice-grpc');
26
+ const { ConnectionServiceDefinition, EventStoresDefinition, ReactorsDefinition } = await import('@cratis/chronicle.contracts');
27
+ const { ChronicleConnection } = await import('../../ChronicleConnection.js');
28
+ server = createServer().use(async function* (call, callContext) {
29
+ received.push(Metadata(callContext.metadata));
30
+ return yield* call.next(call.request, callContext);
31
+ });
32
+ const unused = async () => { throw new Error('Unexpected RPC'); };
33
+ server.add(ConnectionServiceDefinition, {
34
+ ...Object.fromEntries(Object.keys(ConnectionServiceDefinition.methods).map(name => [name, unused])),
35
+ checkCompatibility: async () => ({ IsCompatible: true, Incompatibilities: [], ServerVersion: 'test' })
36
+ } as ServiceImplementation<typeof ConnectionServiceDefinition>);
37
+ server.add(EventStoresDefinition, {
38
+ allEventStores: async () => ({ IsAuthorized: true, Data: [] }),
39
+ ensureEventStore: unused,
40
+ observeEventStores: async function* () {
41
+ yield { IsAuthorized: true, Data: [] };
42
+ yield { IsAuthorized: true, Data: [] };
43
+ }
44
+ } as ServiceImplementation<typeof EventStoresDefinition>);
45
+ server.add(ReactorsDefinition, {
46
+ ...Object.fromEntries(Object.keys(ReactorsDefinition.methods).map(name => [name, unused])),
47
+ observe: async function* (requests) {
48
+ for await (const _request of requests) {
49
+ yield { Events: [], Partition: '', ReplayState: 0 };
50
+ }
51
+ }
52
+ } as ServiceImplementation<typeof ReactorsDefinition>);
53
+ const port = await server.listen('127.0.0.1:0');
54
+ connection = new ChronicleConnection({ connectionString: `chronicle://127.0.0.1:${port}?disableTls=true&apiKey=test` });
55
+ await connection.connect();
56
+ });
57
+
58
+ afterAll(async () => {
59
+ connection?.dispose();
60
+ await server?.shutdown();
61
+ instrumentation.disable();
62
+ });
63
+
64
+ describe('when gRPC auto-instrumentation reinjects active context for unary and streaming calls', () => {
65
+ beforeEach(async () => {
66
+ received.length = 0;
67
+ const span = telemetry.traces.getTracer('host').startSpan('host');
68
+ hostTraceId = span.spanContext().traceId;
69
+ const active = propagation.setBaggage(trace.setSpan(context.active(), span), propagation.createBaggage({
70
+ 'cratis.correlation_id': { value: correlation },
71
+ email: { value: 'private@example.com' },
72
+ 'cratis.event_source.id': { value: 'private-source' },
73
+ 'cratis.tenant.id': { value: 'private-tenant' }
74
+ }));
75
+ try {
76
+ await context.with(active, async () => {
77
+ await connection.eventStores.allEventStores({});
78
+ for await (const _response of connection.eventStores.observeEventStores({})) {
79
+ context.active().should.equal(active);
80
+ await Promise.resolve();
81
+ }
82
+ async function* requests() { yield { Content: undefined }; yield { Content: undefined }; }
83
+ for await (const _response of connection.reactors.observe(requests())) {
84
+ context.active().should.equal(active);
85
+ await Promise.resolve();
86
+ }
87
+ propagation.getBaggage(context.active())!.getEntry('email')!.value.should.equal('private@example.com');
88
+ });
89
+ } finally { span.end(); }
90
+ });
91
+ it('should never let auto-instrumentation restore forbidden baggage on the wire', () => {
92
+ received.should.have.lengthOf(3);
93
+ for (const metadata of received) {
94
+ should.equal(metadata.get('baggage'), `cratis.correlation_id=${correlation}`);
95
+ metadata.get('traceparent')!.should.contain(hostTraceId);
96
+ }
97
+ });
98
+ it('should actually execute the enabled gRPC instrumentation', () => {
99
+ telemetry.spans.getFinishedSpans().filter(span => span.instrumentationScope.name === '@opentelemetry/instrumentation-grpc')
100
+ .length.should.be.greaterThan(0);
101
+ });
102
+ });
@@ -18,11 +18,21 @@ export class CorrelationIdManager implements ICorrelationIdAccessor, ICorrelatio
18
18
  return this._storage.getStore() ?? CorrelationId.create();
19
19
  }
20
20
 
21
+ /** The explicitly scoped identifier, without generating an identifier when no scope exists. */
22
+ get scoped(): CorrelationId | undefined {
23
+ return this._storage.getStore();
24
+ }
25
+
21
26
  /** Runs an async or synchronous operation with a correlation id, restoring the caller afterward. */
22
27
  run<T>(correlationId: CorrelationId, callback: () => T): T {
23
28
  return this._storage.run(correlationId, callback);
24
29
  }
25
30
 
31
+ /** Runs work without inheriting a business correlation, restoring the caller afterward. */
32
+ runWithout<T>(callback: () => T): T {
33
+ return this._storage.exit(callback);
34
+ }
35
+
26
36
  /** @inheritdoc */
27
37
  setCurrent(correlationId: CorrelationId): void {
28
38
  this._storage.enterWith(correlationId);
@@ -1 +1 @@
1
- {"version":3,"file":"ChronicleClient.d.ts","sourceRoot":"","sources":["../ChronicleClient.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAUzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAO/C;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,YAAW,gBAAgB;IA0BxC,QAAQ,CAAC,OAAO,EAAE,gBAAgB;IAzB9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;IACtD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAU;IAE/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IAExD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAErB;IAEH,OAAO,CAAC,eAAe,CAAC,CAAiC;IACzD,OAAO,CAAC,iBAAiB,CAAC,CAAgB;IAC1C,OAAO,CAAC,mBAAmB,CAAC,CAAgB;IAC5C,OAAO,CAAC,mBAAmB,CAAC,CAAgB;IAC5C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,yBAAyB,CAAC,CAAkB;IACpD,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,kBAAkB,CAAC,CAAQ;IACnC,OAAO,CAAC,gCAAgC,CAAK;IAE7C;;;OAGG;IACH,YAAqB,OAAO,EAAE,gBAAgB,EA8C7C;IAED,kBAAkB;IACZ,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,uBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CAwDrH;IAED,kBAAkB;IACZ,cAAc,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAwBhD;IAED,kBAAkB;IAClB,OAAO,IAAI,IAAI,CAqBd;YAEa,gBAAgB;YA4ChB,eAAe;YAuBf,SAAS;IAmDvB;;;;OAIG;YACW,OAAO;YAcP,aAAa;IAyB3B,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,uBAAuB;YAQjB,cAAc;YAkBd,oBAAoB;IAmClC,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,0BAA0B;YAQpB,yBAAyB;CA4B1C"}
1
+ {"version":3,"file":"ChronicleClient.d.ts","sourceRoot":"","sources":["../ChronicleClient.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAUzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQ/C;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,YAAW,gBAAgB;IAwBxC,QAAQ,CAAC,OAAO,EAAE,gBAAgB;IAvB9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;IACtD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAU;IAE/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IAExD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAE1D,OAAO,CAAC,eAAe,CAAC,CAAiC;IACzD,OAAO,CAAC,iBAAiB,CAAC,CAAgB;IAC1C,OAAO,CAAC,mBAAmB,CAAC,CAAgB;IAC5C,OAAO,CAAC,mBAAmB,CAAC,CAAgB;IAC5C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,yBAAyB,CAAC,CAAkB;IACpD,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,kBAAkB,CAAC,CAAQ;IACnC,OAAO,CAAC,gCAAgC,CAAK;IAE7C;;;OAGG;IACH,YAAqB,OAAO,EAAE,gBAAgB,EAiD7C;IAED,kBAAkB;IACZ,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,uBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CA6DrH;IAED,kBAAkB;IACZ,cAAc,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAuBhD;IAED,kBAAkB;IAClB,OAAO,IAAI,IAAI,CAqBd;YAEa,gBAAgB;YA4ChB,eAAe;YAuBf,SAAS;IAmDvB;;;;OAIG;YACW,OAAO;YAcP,aAAa;IAyB3B,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,uBAAuB;YAQjB,cAAc;YAkBd,oBAAoB;IAmClC,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,0BAA0B;YAQpB,yBAAyB;CA4B1C"}
@@ -1,7 +1,9 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
  import * as os from 'os';
4
- import { diag } from '@opentelemetry/api';
4
+ import { createLogger } from './logging/createLogger.js';
5
+ import { setTelemetryAttribute, recordSafeException } from './telemetry/spanAttributes.js';
6
+ import { WellKnownTelemetryNames } from './WellKnownTelemetryNames.js';
5
7
  import { SpanStatusCode } from '@opentelemetry/api';
6
8
  import { ChronicleConnection } from './connection/index.js';
7
9
  import { IncompatibleChronicleServer } from './connection/IncompatibleChronicleServer.js';
@@ -15,7 +17,8 @@ import { EventStore } from './EventStore.js';
15
17
  import { EventStoreName } from './EventStoreName.js';
16
18
  import { EventStoreNamespaceName } from './EventStoreNamespaceName.js';
17
19
  import { ChronicleMetrics } from './Metrics.js';
18
- import { ChronicleTracer } from './Tracing.js';
20
+ import { observeOperation } from './telemetry/observeOperation.js';
21
+ import { runInBackgroundContext } from './telemetry/runInBackgroundContext.js';
19
22
  import { TypeDiscoverer } from './types/index.js';
20
23
  import { takeUnregisteredModelBoundMappings } from './types/modelBoundPropertyMetadata.js';
21
24
  import { reachableReadModelTypes } from './readModels/rootReadModelTypes.js';
@@ -39,9 +42,7 @@ export class ChronicleClient {
39
42
  _connection;
40
43
  _stores = new Map();
41
44
  _lifecycle = new ConnectionLifecycle();
42
- _logger = diag.createComponentLogger({
43
- namespace: '@cratis/chronicle/ChronicleClient'
44
- });
45
+ _logger;
45
46
  _watchdogHandle;
46
47
  _connectOperation;
47
48
  _reconnectOperation;
@@ -57,6 +58,7 @@ export class ChronicleClient {
57
58
  */
58
59
  constructor(options) {
59
60
  this.options = options;
61
+ this._logger = createLogger('@cratis/chronicle/ChronicleClient', options.logger);
60
62
  // When TLS is disabled, pass pre-built insecure credentials directly.
61
63
  // @cratis/chronicle.contracts ≤15.24.3 always composes call credentials with
62
64
  // channel credentials, which gRPC forbids for insecure channels. Passing
@@ -65,15 +67,17 @@ export class ChronicleClient {
65
67
  const connectionOptions = options.connectionString.disableTls
66
68
  ? { connectionString: options.connectionString, credentials: options.connectionString.createCredentials() }
67
69
  : { connectionString: options.connectionString };
68
- this._connection = new ChronicleConnection(connectionOptions);
70
+ this._connection = new ChronicleConnection({ ...connectionOptions, logger: options.logger });
69
71
  this._logger.info('Created Chronicle client', {
70
72
  serverAddress: `${options.connectionString.serverAddress.host}:${options.connectionString.serverAddress.port}`,
71
73
  disableTls: options.connectionString.disableTls
72
74
  });
73
75
  if (options.discoveryPatterns.length > 0) {
74
- this._discoveryOperation = TypeDiscoverer.default.discover(options.discoveryPatterns);
75
- this._discoveryOperation.catch(error => {
76
- this._logger.error('Artifact file discovery failed', { error: String(error) });
76
+ runInBackgroundContext(() => {
77
+ this._discoveryOperation = TypeDiscoverer.default.discover(options.discoveryPatterns);
78
+ this._discoveryOperation.catch(error => {
79
+ this._logger.error('Artifact file discovery failed', { error });
80
+ });
77
81
  });
78
82
  }
79
83
  this._lifecycle.onConnected(async () => {
@@ -104,9 +108,9 @@ export class ChronicleClient {
104
108
  : typeof namespace === 'string'
105
109
  ? new EventStoreNamespaceName(namespace)
106
110
  : namespace;
107
- return ChronicleTracer.startActiveSpan('chronicle.client.get_event_store', async (span) => {
108
- span.setAttribute('chronicle.event_store', storeName.value);
109
- span.setAttribute('chronicle.namespace', namespaceName.value);
111
+ return observeOperation(WellKnownTelemetryNames.spans.getEventStore, async (span) => {
112
+ setTelemetryAttribute(span, 'eventStore', storeName.value);
113
+ setTelemetryAttribute(span, 'namespace', namespaceName.value);
110
114
  try {
111
115
  const store = await this.withReconnect('get_event_store', async () => {
112
116
  await this.ensureConnected();
@@ -123,10 +127,18 @@ export class ChronicleClient {
123
127
  eventStore: storeName.value
124
128
  });
125
129
  ensureCommandSuccess('ensure event store', await this._connection.eventStores.ensureEventStore({ Name: storeName.value }));
126
- const created = new EventStore(storeName, namespaceName, this._connection, this._lifecycle, this.options.defaultSinkTypeId, this.options.clientArtifactsProvider, this.options.reactorResultHandler, this.options.artifactActivator, this.options.readModelNamingPolicy);
130
+ const created = new EventStore(storeName, namespaceName, this._connection, this._lifecycle, this.options.defaultSinkTypeId, this.options.clientArtifactsProvider, this.options.reactorResultHandler, this.options.artifactActivator, this.options.readModelNamingPolicy, this.options.telemetry);
127
131
  this._stores.set(key, created);
128
- await this.registerArtifactsForStore(created, 'new-store');
129
- return created;
132
+ try {
133
+ await this.registerArtifactsForStore(created, 'new-store');
134
+ return created;
135
+ }
136
+ catch (error) {
137
+ if (this._stores.get(key) === created)
138
+ this._stores.delete(key);
139
+ created.disposeObservations();
140
+ throw error;
141
+ }
130
142
  });
131
143
  ChronicleMetrics.eventStoreRetrievals.add(1, {
132
144
  'chronicle.event_store': storeName.value,
@@ -136,12 +148,11 @@ export class ChronicleClient {
136
148
  return store;
137
149
  }
138
150
  catch (error) {
139
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
140
- span.recordException(error);
151
+ recordSafeException(span, error);
141
152
  this._logger.error('Failed getting event store', {
142
153
  eventStore: storeName.value,
143
154
  namespace: namespaceName.value,
144
- error: this.toErrorMessage(error)
155
+ error
145
156
  });
146
157
  throw error;
147
158
  }
@@ -152,7 +163,7 @@ export class ChronicleClient {
152
163
  }
153
164
  /** @inheritdoc */
154
165
  async getEventStores() {
155
- return ChronicleTracer.startActiveSpan('chronicle.client.get_event_stores', async (span) => {
166
+ return observeOperation(WellKnownTelemetryNames.spans.getEventStores, async (span) => {
156
167
  try {
157
168
  const response = await this.withReconnect('get_event_stores', async () => {
158
169
  await this.ensureConnected();
@@ -166,10 +177,9 @@ export class ChronicleClient {
166
177
  return result;
167
178
  }
168
179
  catch (error) {
169
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
170
- span.recordException(error);
180
+ recordSafeException(span, error);
171
181
  this._logger.error('Failed retrieving event stores', {
172
- error: this.toErrorMessage(error)
182
+ error
173
183
  });
174
184
  throw error;
175
185
  }
@@ -190,7 +200,7 @@ export class ChronicleClient {
190
200
  if (this._lifecycle.isConnected) {
191
201
  void this._lifecycle.disconnected(error => {
192
202
  this._logger.error('Disconnected lifecycle callback failed during dispose', {
193
- error: this.toErrorMessage(error)
203
+ error
194
204
  });
195
205
  });
196
206
  }
@@ -218,7 +228,7 @@ export class ChronicleClient {
218
228
  await this.startKernelKeepAlive();
219
229
  await this._lifecycle.connected(error => {
220
230
  this._logger.error('Connected lifecycle callback failed', {
221
- error: this.toErrorMessage(error)
231
+ error
222
232
  });
223
233
  });
224
234
  return;
@@ -249,7 +259,7 @@ export class ChronicleClient {
249
259
  return;
250
260
  }
251
261
  if (!this._connectOperation) {
252
- this._connectOperation = this.connectWithRetry().finally(() => {
262
+ this._connectOperation = runInBackgroundContext(() => this.connectWithRetry()).finally(() => {
253
263
  this._connectOperation = undefined;
254
264
  });
255
265
  }
@@ -259,15 +269,15 @@ export class ChronicleClient {
259
269
  if (this._connectionFailure)
260
270
  throw this._connectionFailure;
261
271
  if (!this._reconnectOperation) {
262
- this._reconnectOperation = (async () => {
272
+ this._reconnectOperation = runInBackgroundContext(async () => {
263
273
  this._logger.warn('Reconnecting to Chronicle kernel', {
264
274
  reason,
265
- error: this.toErrorMessage(error)
275
+ error
266
276
  });
267
277
  if (this._lifecycle.isConnected) {
268
278
  await this._lifecycle.disconnected(disconnectError => {
269
279
  this._logger.error('Disconnected lifecycle callback failed', {
270
- error: this.toErrorMessage(disconnectError)
280
+ error: disconnectError
271
281
  });
272
282
  });
273
283
  }
@@ -282,7 +292,7 @@ export class ChronicleClient {
282
292
  await this.startKernelKeepAlive();
283
293
  await this._lifecycle.connected(connectedError => {
284
294
  this._logger.error('Connected lifecycle callback failed after reconnect', {
285
- error: this.toErrorMessage(connectedError)
295
+ error: connectedError
286
296
  });
287
297
  });
288
298
  return;
@@ -297,7 +307,7 @@ export class ChronicleClient {
297
307
  await this.backOff(attempt, 'Reconnect attempt failed, retrying', reconnectError);
298
308
  }
299
309
  }
300
- })().finally(() => {
310
+ }).finally(() => {
301
311
  this._reconnectOperation = undefined;
302
312
  });
303
313
  }
@@ -314,7 +324,7 @@ export class ChronicleClient {
314
324
  this._logger.warn(message, {
315
325
  attempt,
316
326
  delayMs,
317
- error: this.toErrorMessage(error)
327
+ error
318
328
  });
319
329
  await new Promise(resolve => setTimeout(resolve, delayMs));
320
330
  return delayMs;
@@ -394,9 +404,9 @@ export class ChronicleClient {
394
404
  return String(error);
395
405
  }
396
406
  startConnectionWatchdog() {
397
- this._watchdogHandle = setInterval(() => {
407
+ this._watchdogHandle = runInBackgroundContext(() => setInterval(() => {
398
408
  void this.runHealthCheck().catch(error => this.backgroundConnectionFailed('watchdog-health-check', error));
399
- }, ChronicleClient._healthCheckIntervalMs);
409
+ }, ChronicleClient._healthCheckIntervalMs));
400
410
  this._watchdogHandle.unref?.();
401
411
  }
402
412
  async runHealthCheck() {
@@ -430,7 +440,7 @@ export class ChronicleClient {
430
440
  return;
431
441
  }
432
442
  void this.reconnect(reason, error).catch(failure => this.backgroundConnectionFailed(reason, failure));
433
- });
443
+ }, {}, this.options.logger);
434
444
  await keepAlive.start({
435
445
  ConnectionId: this._lifecycle.connectionId,
436
446
  ClientVersion: clientVersion,
@@ -457,7 +467,7 @@ export class ChronicleClient {
457
467
  this.failConnection(error instanceof Error ? error : new Error(String(error)));
458
468
  this._logger.error('Background connection recovery failed; create a new client after correcting the server', {
459
469
  reason,
460
- error: this.toErrorMessage(error)
470
+ error
461
471
  });
462
472
  }
463
473
  async registerArtifactsForStore(store, reason) {