@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,67 @@
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
+ /** Public telemetry names. Legacy names remain available during the minor-release overlap. */
5
+ export const WellKnownTelemetryNames = {
6
+ scope: 'Cratis.Chronicle.Client',
7
+ legacyScope: '@cratis/chronicle',
8
+ attributes: {
9
+ correlationId: 'cratis.correlation_id',
10
+ eventStore: 'cratis.event_store.name',
11
+ namespace: 'cratis.event_store.namespace',
12
+ eventSequenceId: 'cratis.event_sequence.id',
13
+ sequenceNumber: 'cratis.event_sequence.number',
14
+ eventTypeId: 'cratis.event_type.id',
15
+ eventTypeGeneration: 'cratis.event_type.generation',
16
+ eventSourceType: 'cratis.event_source.type',
17
+ eventSourceId: 'cratis.event_source.id',
18
+ eventCount: 'cratis.event.count',
19
+ errorType: 'error.type',
20
+ exceptionType: 'exception.type'
21
+ },
22
+ legacyAttributes: {
23
+ eventStore: 'chronicle.event_store',
24
+ namespace: 'chronicle.namespace',
25
+ eventSequenceId: 'chronicle.event_sequence_id',
26
+ sequenceNumber: 'chronicle.sequence_number',
27
+ eventTypeId: 'chronicle.event_type_id',
28
+ eventTypeGeneration: 'chronicle.event_type_generation',
29
+ eventSourceId: 'chronicle.event_source_id',
30
+ eventCount: 'chronicle.events_count',
31
+ hasEvents: 'chronicle.has_events',
32
+ eventStreamType: 'chronicle.event_stream_type',
33
+ eventStreamId: 'chronicle.event_stream_id'
34
+ },
35
+ spans: {
36
+ append: 'chronicle.event_sequences.append',
37
+ appendMany: 'chronicle.event_sequences.append_many',
38
+ getTailSequenceNumber: 'chronicle.event_sequences.get_tail_sequence_number',
39
+ hasEventsFor: 'chronicle.event_sequences.has_events_for',
40
+ getForEventSourceIdAndEventTypes: 'chronicle.event_sequences.get_for_event_source_id_and_event_types',
41
+ getFromSequenceNumber: 'chronicle.event_sequences.get_from_sequence_number',
42
+ redact: 'chronicle.event_sequences.redact',
43
+ redactForEventSource: 'chronicle.event_sequences.redact_for_event_source',
44
+ completeStream: 'chronicle.event_sequences.complete_stream',
45
+ getEventStore: 'chronicle.client.get_event_store',
46
+ getEventStores: 'chronicle.client.get_event_stores',
47
+ getNamespaces: 'chronicle.event_store.get_namespaces'
48
+ },
49
+ metrics: {
50
+ eventsAppended: 'cratis.chronicle.event_sequence.appended',
51
+ batchAppendsPerformed: 'cratis.chronicle.event_sequence.batch_appends',
52
+ eventStoreRetrievals: 'cratis.chronicle.event_store.retrievals',
53
+ appendDuration: 'cratis.chronicle.event_sequence.append_duration',
54
+ appendManyDuration: 'cratis.chronicle.event_sequence.append_many_duration',
55
+ constraintViolations: 'cratis.chronicle.event_sequence.constraint_violations',
56
+ appendErrors: 'cratis.chronicle.event_sequence.append_errors'
57
+ },
58
+ legacyMetrics: {
59
+ eventsAppended: 'chronicle.events.appended',
60
+ batchAppendsPerformed: 'chronicle.events.batch_appends',
61
+ eventStoreRetrievals: 'chronicle.client.event_store_retrievals',
62
+ appendDuration: 'chronicle.events.append_duration',
63
+ appendManyDuration: 'chronicle.events.append_many_duration',
64
+ constraintViolations: 'chronicle.events.constraint_violations',
65
+ appendErrors: 'chronicle.events.append_errors'
66
+ }
67
+ } as const;
@@ -35,7 +35,7 @@ describe('artifact schema preflight', () => {
35
35
  it('blocks every Kernel registration when preflight fails', async () => {
36
36
  const register = vi.fn();
37
37
  const connection = new Proxy({}, {
38
- get: () => new Proxy({}, { get: () => register })
38
+ get: (_target, property) => property === 'logger' ? undefined : new Proxy({}, { get: () => register })
39
39
  }) as ChronicleConnection;
40
40
  const lifecycle = { onDisconnected: vi.fn(), onConnected: vi.fn() } as unknown as ConnectionLifecycle;
41
41
  const store = new EventStore(new EventStoreName('Test'), EventStoreNamespaceName.default, connection, lifecycle, 'sink');
@@ -1,7 +1,8 @@
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
 
4
- import { diag } from '@opentelemetry/api';
4
+ import { createLogger } from '../logging/createLogger.js';
5
+ import type { IChronicleLogger } from '../logging/IChronicleLogger.js';
5
6
  import type { Constructor } from '@cratis/fundamentals';
6
7
  import type { ActivatedArtifact } from './ActivatedArtifact.js';
7
8
  import type { ArtifactActivationContext } from './ArtifactActivationContext.js';
@@ -9,12 +10,11 @@ import type { ArtifactInvocationContext } from './ArtifactInvocationContext.js';
9
10
  import { ArtifactCompletionFailed } from './ArtifactCompletionFailed.js';
10
11
  import type { ClientArtifactsActivator } from './ClientArtifactsActivator.js';
11
12
 
12
- const logger = diag.createComponentLogger({ namespace: '@cratis/chronicle/artifacts' });
13
-
14
13
  /** Activates once per delivery and always releases the lease before acknowledgement. */
15
14
  export async function withActivatedArtifact<T, R>(
16
15
  type: Constructor<T>, context: ArtifactActivationContext, activator: ClientArtifactsActivator,
17
- callback: (artifact: ActivatedArtifact<T>) => Promise<R>
16
+ callback: (artifact: ActivatedArtifact<T>) => Promise<R>,
17
+ logger?: IChronicleLogger
18
18
  ): Promise<R> {
19
19
  const artifact = await activator(type, context);
20
20
  try {
@@ -38,7 +38,9 @@ export async function withActivatedArtifact<T, R>(
38
38
  try {
39
39
  await artifact.dispose?.();
40
40
  } catch (error) {
41
- logger.error('Error disposing activated artifact', { kind: context.kind, artifactId: context.artifactId, error: String(error) });
41
+ createLogger('@cratis/chronicle/artifacts', logger).error('Error disposing activated artifact', {
42
+ kind: context.kind, artifactId: context.artifactId, error
43
+ });
42
44
  }
43
45
  }
44
46
  }
@@ -2,7 +2,10 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { status, type Channel, type ChannelCredentials, type ChannelOptions } from '@grpc/grpc-js';
5
- import { diag } from '@opentelemetry/api';
5
+ import { createLogger } from '../logging/createLogger.js';
6
+ import type { IChronicleLogger } from '../logging/IChronicleLogger.js';
7
+ import { traceContextMiddleware } from './TraceContextMiddleware.js';
8
+ import { runInBackgroundContext } from '../telemetry/runInBackgroundContext.js';
6
9
  import {
7
10
  ConnectionServiceDefinition,
8
11
  ConstraintsDefinition,
@@ -46,6 +49,9 @@ import { ITokenProvider, NoOpTokenProvider, OAuthTokenProvider } from './TokenPr
46
49
  * Configuration options for Chronicle connection.
47
50
  */
48
51
  export interface ChronicleConnectionOptions {
52
+ /** Optional client-owned diagnostic sink; absent preserves the diag adapter. */
53
+ logger?: IChronicleLogger;
54
+
49
55
  /**
50
56
  * The connection string used to connect to Chronicle.
51
57
  */
@@ -120,7 +126,7 @@ export class ChronicleConnection implements ChronicleServices {
120
126
  this._addressResolver = new ChronicleServerAddressResolver();
121
127
  this._loadBalancerStrategy = createLoadBalancerStrategy(this._connectionString.loadBalancer, this._connectionString.skipTlsValidation);
122
128
 
123
- this._clientsReady = this.createClients();
129
+ this._clientsReady = runInBackgroundContext(() => this.createClients());
124
130
  // Building the initial channel is async (address resolution + load balancer
125
131
  // selection), so the constructor cannot await it. Real failures still surface to
126
132
  // callers that await connect()/resetChannel(); this only prevents an unhandled
@@ -128,6 +134,11 @@ export class ChronicleConnection implements ChronicleServices {
128
134
  this._clientsReady.catch(() => {});
129
135
  }
130
136
 
137
+ /** The diagnostic sink shared by services owned by this connection. */
138
+ get logger(): IChronicleLogger | undefined {
139
+ return this._options.logger;
140
+ }
141
+
131
142
  get connectionString(): ChronicleConnectionString {
132
143
  return this._connectionString;
133
144
  }
@@ -243,7 +254,7 @@ export class ChronicleConnection implements ChronicleServices {
243
254
  }
244
255
 
245
256
  this._isConnected = false;
246
- this._clientsReady = this.createClients();
257
+ this._clientsReady = runInBackgroundContext(() => this.createClients());
247
258
  await this._clientsReady;
248
259
  }
249
260
 
@@ -283,7 +294,8 @@ export class ChronicleConnection implements ChronicleServices {
283
294
 
284
295
  this._channel = createChannel(serverAddress, credentials, channelOptions);
285
296
 
286
- const factory = createClientFactory().use(this.createAuthMiddleware(tokenProvider));
297
+ // nice-grpc invokes the last middleware first: auth retries re-enter propagation.
298
+ const factory = createClientFactory().use(traceContextMiddleware).use(this.createAuthMiddleware(tokenProvider));
287
299
  this._connections = factory.create(ConnectionServiceDefinition, this._channel);
288
300
  this._compatibility = new CompatibilityPreflight(this._connections, this._options.connectTimeout ?? 10_000);
289
301
  const eventSequenceFactory = factory.use(this._compatibility.middleware());
@@ -360,7 +372,7 @@ export class ChronicleConnection implements ChronicleServices {
360
372
 
361
373
  private createAuthMiddleware(tokenProvider: ITokenProvider): ClientMiddleware {
362
374
  const connectionString = this._connectionString;
363
- const logger = diag.createComponentLogger({ namespace: '@cratis/chronicle/ChronicleConnection' });
375
+ const logger = createLogger('@cratis/chronicle/ChronicleConnection', this.logger);
364
376
  const loggedFailures = new WeakSet<Error>();
365
377
 
366
378
  return async function* authMiddleware(call, options) {
@@ -373,7 +385,7 @@ export class ChronicleConnection implements ChronicleServices {
373
385
  tokenFailure = error instanceof Error ? error : new Error(String(error));
374
386
  if (!loggedFailures.has(tokenFailure)) {
375
387
  loggedFailures.add(tokenFailure);
376
- logger.warn('Failed to obtain OAuth2 token; sending RPC without authorization', { error: tokenFailure.message });
388
+ logger.warn('Failed to obtain OAuth2 token; sending RPC without authorization', { error: tokenFailure });
377
389
  }
378
390
  }
379
391
  if (!token) tokenFailure ??= tokenProvider.lastTokenFailure;
@@ -6,6 +6,7 @@ import { chronicleDescriptorSet, type ConnectionServiceClient } from '@cratis/ch
6
6
  import { ClientError, Status, type ClientMiddleware } from 'nice-grpc-common';
7
7
  import { IncompatibleChronicleServer } from './IncompatibleChronicleServer.js';
8
8
  import { clientVersion } from './clientVersion.js';
9
+ import { runInBackgroundContext } from '../telemetry/runInBackgroundContext.js';
9
10
 
10
11
  const require = createRequire(import.meta.url);
11
12
  const protocolVersion = (require('@cratis/chronicle.contracts/package.json') as { version: string }).version;
@@ -19,7 +20,7 @@ export class CompatibilityPreflight {
19
20
  /** Shares in-flight checks and caches verdicts for this channel; transient failures can be retried. */
20
21
  verify(): Promise<void> {
21
22
  if (!this._verification) {
22
- const verification = this.check().catch(error => {
23
+ const verification = runInBackgroundContext(() => this.check()).catch(error => {
23
24
  if (!(error instanceof IncompatibleChronicleServer) && this._verification === verification) {
24
25
  this._verification = undefined;
25
26
  }
@@ -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
 
4
- import { diag } from '@opentelemetry/api';
4
+ import { createLogger } from '../logging/createLogger.js';
5
+ import { runInBackgroundContext } from '../telemetry/runInBackgroundContext.js';
6
+ import type { IChronicleLogger } from '../logging/IChronicleLogger.js';
5
7
  import type { ConnectRequest } from '@cratis/chronicle.contracts';
6
8
 
7
9
  /**
@@ -49,9 +51,7 @@ export class KernelKeepAlive {
49
51
  /** How long a single answer may take before the connection is considered dead. */
50
52
  static readonly defaultAnswerTimeoutMs = 5000;
51
53
 
52
- private readonly _logger = diag.createComponentLogger({
53
- namespace: '@cratis/chronicle/KernelKeepAlive'
54
- });
54
+ private readonly _logger: ReturnType<typeof createLogger>;
55
55
 
56
56
  private readonly _idleTimeoutMs: number;
57
57
  private readonly _answerTimeoutMs: number;
@@ -65,8 +65,10 @@ export class KernelKeepAlive {
65
65
  constructor(
66
66
  private readonly connections: IKeepAliveConnections,
67
67
  private readonly onConnectionLost: (reason: string, error: unknown) => void,
68
- timeouts: KeepAliveTimeouts = {}
68
+ timeouts: KeepAliveTimeouts = {},
69
+ logger?: IChronicleLogger
69
70
  ) {
71
+ this._logger = createLogger('@cratis/chronicle/KernelKeepAlive', logger);
70
72
  this._idleTimeoutMs = timeouts.idleTimeoutMs ?? KernelKeepAlive.defaultIdleTimeoutMs;
71
73
  this._answerTimeoutMs = timeouts.answerTimeoutMs ?? KernelKeepAlive.defaultAnswerTimeoutMs;
72
74
  }
@@ -79,7 +81,11 @@ export class KernelKeepAlive {
79
81
  * @param request - The connect request identifying this client.
80
82
  * @param signal - Aborted to stop the keep-alive entirely.
81
83
  */
82
- async start(request: ConnectRequest, signal: AbortSignal): Promise<void> {
84
+ start(request: ConnectRequest, signal: AbortSignal): Promise<void> {
85
+ return runInBackgroundContext(() => this.openStream(request, signal));
86
+ }
87
+
88
+ private async openStream(request: ConnectRequest, signal: AbortSignal): Promise<void> {
83
89
  const stream = this.connections.connect(request, { signal });
84
90
  const iterator = stream[Symbol.asyncIterator]();
85
91
 
@@ -136,7 +142,7 @@ export class KernelKeepAlive {
136
142
  private connectionLost(reason: string, error: unknown): void {
137
143
  this._logger.warn('Kernel keep-alive lost the connection', {
138
144
  reason,
139
- error: error instanceof Error ? error.message : String(error)
145
+ error
140
146
  });
141
147
  this.onConnectionLost(reason, error);
142
148
  }
@@ -2,6 +2,7 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { fetchOAuthAccessToken, OAuthTokenHttpError, type OAuthTokenResponse } from './fetchOAuthAccessToken.js';
5
+ import { runInBackgroundContext } from '../telemetry/runInBackgroundContext.js';
5
6
 
6
7
  // Refresh once the token has less than this long left before it expires.
7
8
  const TOKEN_REFRESH_MARGIN_MS = 60_000;
@@ -99,7 +100,8 @@ export class OAuthTokenProvider implements ITokenProvider {
99
100
  return undefined;
100
101
  }
101
102
 
102
- this._refreshPromise = this.fetchAndCacheAccessToken();
103
+ // Acquisition is shared client-owned work, not part of the caller's business trace.
104
+ this._refreshPromise = runInBackgroundContext(() => this.fetchAndCacheAccessToken());
103
105
  try {
104
106
  return await this._refreshPromise;
105
107
  } finally {
@@ -0,0 +1,30 @@
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 { context, propagation } from '@opentelemetry/api';
5
+ import { Metadata, type ClientMiddleware, type ClientMiddlewareCall, type CallOptions } from 'nice-grpc-common';
6
+ import { correlationIdManager } from '../correlation/index.js';
7
+ import { WellKnownTelemetryNames } from '../WellKnownTelemetryNames.js';
8
+
9
+ /** Injects host-configured propagation for every RPC without mutating caller metadata or baggage. */
10
+ export const traceContextMiddleware: ClientMiddleware = async function* <Request, Response>(call: ClientMiddlewareCall<Request, Response>, options: CallOptions) {
11
+ const metadata = options.metadata ? Metadata(options.metadata) : Metadata();
12
+ // These headers belong to the active context, never to a stale caller-supplied carrier.
13
+ for (const field of new Set(['traceparent', 'tracestate', 'baggage', ...propagation.fields()])) metadata.delete(field);
14
+ const active = context.active();
15
+ const key = WellKnownTelemetryNames.attributes.correlationId;
16
+ const correlation = correlationIdManager.scoped?.toString() ?? propagation.getBaggage(active)?.getEntry(key)?.value;
17
+ const filtered = propagation.setBaggage(active, propagation.createBaggage(correlation ? { [key]: { value: correlation } } : {}));
18
+ propagation.inject(filtered, metadata, { set: (carrier, name, value) => carrier.set(name, value) });
19
+ const iterator: AsyncGenerator<Response, Response | void, undefined> = context.with(filtered, () => call.next(call.request, { ...options, metadata }));
20
+ // Async generators execute on each advance, not at creation. Bind all advances,
21
+ // including cancellation/error cleanup, so gRPC instrumentation cannot restore
22
+ // the caller's forbidden baggage. context.with restores the consumer's scope.
23
+ const downstream: AsyncIterableIterator<Response, Response | void, undefined> = {
24
+ [Symbol.asyncIterator]() { return this; },
25
+ next: value => context.with(filtered, () => iterator.next(value)),
26
+ return: value => context.with(filtered, () => iterator.return(value)),
27
+ throw: error => context.with(filtered, () => iterator.throw(error))
28
+ };
29
+ return yield* downstream;
30
+ };
@@ -0,0 +1,73 @@
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, type Context } from '@opentelemetry/api';
6
+ import { createServer, type ServiceImplementation } from 'nice-grpc';
7
+ import { ConnectionServiceDefinition } from '@cratis/chronicle.contracts';
8
+ import { ChronicleConnection } from '../../ChronicleConnection.js';
9
+ import { CorrelationId, correlationIdManager } from '../../../correlation/index.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
+ let server: ReturnType<typeof createServer>;
15
+ let connection: ChronicleConnection;
16
+ let probes: { url: string; method: string; active: Context; correlation: CorrelationId | undefined; headers: Record<string, string> }[];
17
+
18
+ beforeEach(async () => {
19
+ probes = [];
20
+ server = createServer();
21
+ const unused = async () => { throw new Error('Unexpected RPC'); };
22
+ server.add(ConnectionServiceDefinition, {
23
+ ...Object.fromEntries(Object.keys(ConnectionServiceDefinition.methods).map(name => [name, unused])),
24
+ checkCompatibility: async () => ({ IsCompatible: true, Incompatibilities: [], ServerVersion: 'test' })
25
+ } as ServiceImplementation<typeof ConnectionServiceDefinition>);
26
+ const port = await server.listen('127.0.0.1:0');
27
+ vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, options) => {
28
+ await Promise.resolve();
29
+ const active = context.active();
30
+ const headers: Record<string, string> = {};
31
+ propagation.inject(active, headers);
32
+ const url = String(input);
33
+ probes.push({ url, method: options?.method ?? 'GET', active, correlation: correlationIdManager.scoped, headers });
34
+ return new Response(url.includes(`:${port}/`) ? '0' : '1');
35
+ });
36
+ const correlation = new CorrelationId('constructing-business');
37
+ const span = telemetry.traces.getTracer('host').startSpan('constructing-caller');
38
+ const active = propagation.setBaggage(trace.setSpan(context.active(), span), propagation.createBaggage({
39
+ 'cratis.correlation_id': { value: correlation.toString() }, email: { value: 'private@example.com' }
40
+ }));
41
+ try {
42
+ await context.with(active, () => correlationIdManager.run(correlation, async () => {
43
+ connection = new ChronicleConnection({
44
+ connectionString: `chronicle://127.0.0.1:${port},127.0.0.1:1?disableTls=true&apiKey=test`
45
+ });
46
+ await connection.connect();
47
+ await connection.resetChannel();
48
+ context.active().should.equal(active);
49
+ correlationIdManager.scoped!.should.equal(correlation);
50
+ }));
51
+ } finally { span.end(); }
52
+ });
53
+
54
+ afterEach(async () => {
55
+ connection?.dispose();
56
+ await server?.shutdown();
57
+ vi.restoreAllMocks();
58
+ });
59
+
60
+ describe('when constructing and resetting a multi-host connection inside a business context', () => {
61
+ it('should detach both count probes and reservation on every client creation', () => {
62
+ probes.should.have.lengthOf(6);
63
+ probes.map(probe => probe.method).should.deep.equal(['GET', 'GET', 'POST', 'GET', 'GET', 'POST']);
64
+ probes.filter(probe => probe.url.endsWith('/connections/count')).should.have.lengthOf(4);
65
+ probes.filter(probe => probe.url.endsWith('/connections/reserve')).should.have.lengthOf(2);
66
+ for (const probe of probes) {
67
+ should.equal(trace.getSpanContext(probe.active), undefined);
68
+ should.equal(propagation.getBaggage(probe.active), undefined);
69
+ should.equal(probe.correlation, undefined);
70
+ probe.headers.should.deep.equal({});
71
+ }
72
+ });
73
+ });
@@ -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 { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
7
+ import type { IncomingHttpHeaders, Server } from 'node:http';
8
+ import type { AddressInfo } from 'node:net';
9
+ import { createRequire, syncBuiltinESMExports } from 'node:module';
10
+ import type { 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 correlations: Array<CorrelationId | undefined> = [];
17
+ const instrumentation = new HttpInstrumentation({
18
+ enabled: false,
19
+ disableIncomingRequestInstrumentation: true,
20
+ requestHook: () => { correlations.push(correlationIdManager.scoped); }
21
+ });
22
+ let server: Server;
23
+ let Provider: typeof OAuthTokenProvider;
24
+ let endpoint: string;
25
+ let lifetime: number;
26
+ let requests: number;
27
+ let received: IncomingHttpHeaders[];
28
+
29
+ beforeAll(async () => {
30
+ instrumentation.setTracerProvider(telemetry.traces);
31
+ instrumentation.enable();
32
+ // Vitest does not install the host's ESM loader hooks. Trigger the real require hook
33
+ // and synchronize the patched built-in exports before loading the provider's transport.
34
+ const { createServer } = createRequire(import.meta.url)('node:http') as typeof import('node:http');
35
+ syncBuiltinESMExports();
36
+ Provider = (await import('../../TokenProvider.js')).OAuthTokenProvider;
37
+ server = createServer((request, response) => {
38
+ received.push(request.headers);
39
+ request.resume();
40
+ response.writeHead(200, { 'content-type': 'application/json' });
41
+ response.end(JSON.stringify({ access_token: `token-${++requests}`, expires_in: lifetime }));
42
+ });
43
+ await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve));
44
+ endpoint = `http://127.0.0.1:${(server.address() as AddressInfo).port}/connect/token`;
45
+ });
46
+
47
+ afterAll(async () => {
48
+ instrumentation.disable();
49
+ syncBuiltinESMExports();
50
+ if (server) await new Promise<void>((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
51
+ });
52
+
53
+ for (const acquisition of ['initial', 'expiring', 'forced'] as const) {
54
+ describe(`when ${acquisition} token acquisition is shared by correlated callers`, () => {
55
+ let callerTraceIds: string[];
56
+ let tokens: Array<string | undefined>;
57
+ beforeEach(async () => {
58
+ received = [];
59
+ requests = 0;
60
+ lifetime = acquisition === 'expiring' ? 30 : 3600;
61
+ const provider = new Provider(endpoint, 'client', 'secret');
62
+ if (acquisition !== 'initial') await provider.getAccessToken();
63
+ received.length = 0;
64
+ correlations.length = 0;
65
+ telemetry.spans.reset();
66
+ callerTraceIds = [];
67
+ const pending = ['first', 'second'].map((caller, index) => {
68
+ const correlation = new CorrelationId(`${caller}-business`);
69
+ const span = telemetry.traces.getTracer('host').startSpan(caller);
70
+ callerTraceIds.push(span.spanContext().traceId);
71
+ const active = propagation.setBaggage(trace.setSpan(context.active(), span), propagation.createBaggage({
72
+ 'cratis.correlation_id': { value: correlation.toString() },
73
+ email: { value: 'private@example.com' },
74
+ 'cratis.tenant.id': { value: 'private-tenant' }
75
+ }));
76
+ return context.with(active, () => correlationIdManager.run(correlation, async () => {
77
+ try {
78
+ const token = await (index === 0 && acquisition === 'forced' ? provider.refresh() : provider.getAccessToken());
79
+ context.active().should.equal(active);
80
+ correlationIdManager.scoped!.should.equal(correlation);
81
+ return token;
82
+ } finally { span.end(); }
83
+ }));
84
+ });
85
+ tokens = await Promise.all(pending);
86
+ });
87
+ it('should send one shared request without caller trace or baggage', () => {
88
+ received.should.have.lengthOf(1);
89
+ should.equal(received[0].baggage, undefined);
90
+ for (const traceId of callerTraceIds) String(received[0].traceparent).should.not.contain(traceId);
91
+ const token = acquisition === 'initial' ? 'token-1' : 'token-2';
92
+ tokens.should.deep.equal([token, token]);
93
+ });
94
+ it('should let enabled HTTP instrumentation create a detached root span', () => {
95
+ const spans = telemetry.spans.getFinishedSpans().filter(span => span.instrumentationScope.name === '@opentelemetry/instrumentation-http');
96
+ spans.should.have.lengthOf(1);
97
+ should.equal(spans[0].parentSpanContext, undefined);
98
+ String(received[0].traceparent).should.contain(spans[0].spanContext().traceId);
99
+ correlations.should.deep.equal([undefined]);
100
+ });
101
+ });
102
+ }