@cratis/chronicle 6.33.0 → 6.34.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 (162) hide show
  1. package/ChronicleClient.ts +35 -33
  2. package/ChronicleOptions.ts +27 -1
  3. package/EventStore.ts +14 -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 +35 -33
  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 +14 -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/index.d.ts +7 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +4 -1
  82. package/dist/index.js.map +1 -1
  83. package/dist/logging/ChronicleLogEntry.d.ts +11 -0
  84. package/dist/logging/ChronicleLogEntry.d.ts.map +1 -0
  85. package/dist/logging/ChronicleLogEntry.js +4 -0
  86. package/dist/logging/ChronicleLogEntry.js.map +1 -0
  87. package/dist/logging/ChronicleLogLevel.d.ts +9 -0
  88. package/dist/logging/ChronicleLogLevel.d.ts.map +1 -0
  89. package/dist/logging/ChronicleLogLevel.js +12 -0
  90. package/dist/logging/ChronicleLogLevel.js.map +1 -0
  91. package/dist/logging/DiagChronicleLogger.d.ts +7 -0
  92. package/dist/logging/DiagChronicleLogger.d.ts.map +1 -0
  93. package/dist/logging/DiagChronicleLogger.js +10 -0
  94. package/dist/logging/DiagChronicleLogger.js.map +1 -0
  95. package/dist/logging/IChronicleLogger.d.ts +7 -0
  96. package/dist/logging/IChronicleLogger.d.ts.map +1 -0
  97. package/dist/logging/IChronicleLogger.js +4 -0
  98. package/dist/logging/IChronicleLogger.js.map +1 -0
  99. package/dist/logging/createLogger.d.ts +10 -0
  100. package/dist/logging/createLogger.d.ts.map +1 -0
  101. package/dist/logging/createLogger.js +55 -0
  102. package/dist/logging/createLogger.js.map +1 -0
  103. package/dist/projections/Projections.d.ts.map +1 -1
  104. package/dist/projections/Projections.js +4 -3
  105. package/dist/projections/Projections.js.map +1 -1
  106. package/dist/reactors/Reactors.d.ts.map +1 -1
  107. package/dist/reactors/Reactors.js +26 -24
  108. package/dist/reactors/Reactors.js.map +1 -1
  109. package/dist/reducers/Reducers.d.ts.map +1 -1
  110. package/dist/reducers/Reducers.js +28 -25
  111. package/dist/reducers/Reducers.js.map +1 -1
  112. package/dist/seeding/EventSeeding.d.ts.map +1 -1
  113. package/dist/seeding/EventSeeding.js +4 -3
  114. package/dist/seeding/EventSeeding.js.map +1 -1
  115. package/dist/telemetry/ChronicleTelemetryOptions.d.ts +11 -0
  116. package/dist/telemetry/ChronicleTelemetryOptions.d.ts.map +1 -0
  117. package/dist/telemetry/ChronicleTelemetryOptions.js +4 -0
  118. package/dist/telemetry/ChronicleTelemetryOptions.js.map +1 -0
  119. package/dist/telemetry/observeOperation.d.ts +6 -0
  120. package/dist/telemetry/observeOperation.d.ts.map +1 -0
  121. package/dist/telemetry/observeOperation.js +23 -0
  122. package/dist/telemetry/observeOperation.js.map +1 -0
  123. package/dist/telemetry/runInBackgroundContext.d.ts +3 -0
  124. package/dist/telemetry/runInBackgroundContext.d.ts.map +1 -0
  125. package/dist/telemetry/runInBackgroundContext.js +9 -0
  126. package/dist/telemetry/runInBackgroundContext.js.map +1 -0
  127. package/dist/telemetry/spanAttributes.d.ts +14 -0
  128. package/dist/telemetry/spanAttributes.d.ts.map +1 -0
  129. package/dist/telemetry/spanAttributes.js +47 -0
  130. package/dist/telemetry/spanAttributes.js.map +1 -0
  131. package/dist/tsconfig.tsbuildinfo +1 -1
  132. package/eventSequences/EventLog.ts +4 -2
  133. package/eventSequences/EventSequence.ts +64 -81
  134. package/eventStoreSubscriptions/EventStoreSubscriptions.ts +5 -5
  135. package/for_ChronicleClient/when_starting_background_work/with_a_correlated_caller.spec.ts +150 -0
  136. package/for_ChronicleOptions/when_configuring_telemetry/with_event_source_identifiers.spec.ts +44 -0
  137. package/index.ts +7 -1
  138. package/logging/ChronicleLogEntry.ts +14 -0
  139. package/logging/ChronicleLogLevel.ts +11 -0
  140. package/logging/DiagChronicleLogger.ts +13 -0
  141. package/logging/IChronicleLogger.ts +10 -0
  142. package/logging/createLogger.ts +50 -0
  143. package/logging/for_ChronicleLogger/when_logging/with_client_owned_sinks.spec.ts +96 -0
  144. package/package.json +7 -1
  145. package/projections/Projections.ts +4 -3
  146. package/reactors/Reactors.ts +27 -25
  147. package/reactors/for_Reactors/when_delivering/with_activation.spec.ts +14 -14
  148. package/reducers/Reducers.ts +29 -26
  149. package/seeding/EventSeeding.ts +6 -4
  150. package/telemetry/ChronicleTelemetryOptions.ts +8 -0
  151. package/telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.ts +36 -0
  152. package/telemetry/for_ChronicleTelemetry/given/an_event_sequence.fixture.ts +29 -0
  153. package/telemetry/for_ChronicleTelemetry/when_appending/with_shared_and_legacy_attributes.spec.ts +58 -0
  154. package/telemetry/for_ChronicleTelemetry/when_appending/without_an_sdk.spec.ts +15 -0
  155. package/telemetry/for_ChronicleTelemetry/when_documenting/with_public_names.spec.ts +15 -0
  156. package/telemetry/for_ChronicleTelemetry/when_recording_failures/with_sensitive_exception_details.spec.ts +34 -0
  157. package/telemetry/for_ChronicleTelemetry/when_recording_identifiers/with_per_client_privacy.spec.ts +92 -0
  158. package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_legacy_overlap.spec.ts +40 -0
  159. package/telemetry/for_ChronicleTelemetry/when_recording_sequence_numbers/with_integer_precision.spec.ts +23 -0
  160. package/telemetry/observeOperation.ts +25 -0
  161. package/telemetry/runInBackgroundContext.ts +10 -0
  162. package/telemetry/spanAttributes.ts +49 -0
@@ -4,7 +4,8 @@
4
4
  import { EventContext as WireContext, EventObservationState, EventType, ObservationState, type ReactorMessage } from '@cratis/chronicle.contracts';
5
5
  import { ReplayState } from '../../../index.js';
6
6
  import { chai, describe, it, vi } from 'vitest';
7
- import { diag, DiagLogLevel, type DiagLogger } from '@opentelemetry/api';
7
+ import type { IChronicleLogger } from '../../../logging/IChronicleLogger.js';
8
+ import type { ChronicleLogEntry } from '../../../logging/ChronicleLogEntry.js';
8
9
  import type { ChronicleConnection } from '../../../connection/index.js';
9
10
  import { ConnectionLifecycle } from '../../../connection/ConnectionLifecycle.js';
10
11
  import type { IClientArtifactsProvider } from '../../../artifacts/IClientArtifactsProvider.js';
@@ -98,7 +99,7 @@ function delivery(events: ReturnType<typeof event>[], replayState = ReplayState.
98
99
  }
99
100
 
100
101
  async function observe(type: Function, batches: ReturnType<typeof delivery>[], namespace = 'tenant-a',
101
- activator?: ClientArtifactsActivator, appendResult = true, onAppendMany?: () => void) {
102
+ activator?: ClientArtifactsActivator, appendResult = true, onAppendMany?: () => void, logger?: IChronicleLogger) {
102
103
  const acknowledgements: NonNullable<ReactorMessage['Content']>['Value1'][] = [];
103
104
  const append = vi.fn().mockResolvedValue({ isSuccess: appendResult });
104
105
  const appendMany = vi.fn().mockImplementation(async () => {
@@ -122,7 +123,7 @@ async function observe(type: Function, batches: ReturnType<typeof delivery>[], n
122
123
  await lifecycle.disconnected(error => { throw error; });
123
124
  finished();
124
125
  }
125
- const connection = { reactors: { observe: stream } } as unknown as ChronicleConnection;
126
+ const connection = { logger, reactors: { observe: stream } } as unknown as ChronicleConnection;
126
127
  const artifacts = { reactors: [type], eventTypes: [ActivationEvent] } as unknown as IClientArtifactsProvider;
127
128
  await new Reactors(artifacts, connection, 'store', namespace, lifecycle, eventLog, undefined, store, activator).register();
128
129
  await complete;
@@ -462,16 +463,15 @@ describe('when delivering reactor batches', () => {
462
463
  });
463
464
 
464
465
  it('should log disposal failure without changing successful acknowledgement', async () => {
465
- const errors: string[] = [];
466
- diag.setLogger({ error: (...values: unknown[]) => { errors.push(values.map(String).join(' ')); } } as DiagLogger, DiagLogLevel.ERROR);
467
- try {
468
- const activator: ClientArtifactsActivator = type => ({ instance: new type(), dispose: async () => { throw new Error('cleanup'); } });
469
- const result = await observe(ActivationReactor, [delivery([event('live', 1n)])], 'tenant-a', activator);
470
- result.acknowledgements[0]?.State.should.equal(ObservationState.Success);
471
- result.acknowledgements[0]?.LastSuccessfulObservation.should.equal(1n);
472
- errors.some(value => value.includes('Error disposing activated artifact')).should.be.true;
473
- } finally {
474
- diag.disable();
475
- }
466
+ const entries: ChronicleLogEntry[] = [];
467
+ const activator: ClientArtifactsActivator = type => ({ instance: new type(), dispose: async () => { throw new Error('private cleanup details'); } });
468
+ const result = await observe(ActivationReactor, [delivery([event('private payload', 1n)])], 'tenant-a', activator,
469
+ true, undefined, { log: entry => entries.push(entry) });
470
+ result.acknowledgements[0]?.State.should.equal(ObservationState.Success);
471
+ result.acknowledgements[0]?.LastSuccessfulObservation.should.equal(1n);
472
+ entries.filter(entry => entry.attributes['exception.type'] === 'Error').should.have.lengthOf(1);
473
+ JSON.stringify(entries).should.not.contain('private cleanup details');
474
+ JSON.stringify(entries).should.not.contain('private payload');
475
+ JSON.stringify(entries).should.not.contain('book-1');
476
476
  });
477
477
  });
@@ -2,7 +2,8 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import 'reflect-metadata';
5
- import { diag } from '@opentelemetry/api';
5
+ import { createLogger } from '../logging/createLogger.js';
6
+ import { runInBackgroundContext } from '../telemetry/runInBackgroundContext.js';
6
7
  import { Constructor } from '@cratis/fundamentals';
7
8
  import { ObservationState, ReadModelObserverType, ReducerMessage, ReplayState } from '@cratis/chronicle.contracts';
8
9
  import { IClientArtifactsProvider } from '../artifacts/index.js';
@@ -102,7 +103,7 @@ export class Reducers implements IReducers {
102
103
  /** How long to wait before re-establishing an observation whose stream ended. */
103
104
  private static readonly _reobserveDelayMs = 2000;
104
105
 
105
- private readonly _logger = diag.createComponentLogger({ namespace: '@cratis/chronicle/reducers' });
106
+ private readonly _logger: ReturnType<typeof createLogger>;
106
107
  private readonly _lifecycle: ConnectionLifecycle;
107
108
  private readonly _reducers = new Map<string, Constructor>();
108
109
  private readonly _queues = new Map<string, AsyncQueue<ReducerMessage>>();
@@ -133,6 +134,7 @@ export class Reducers implements IReducers {
133
134
  private readonly _artifactActivator?: ClientArtifactsActivator,
134
135
  private readonly _readModelNamingPolicy?: ReadModelNamingPolicy
135
136
  ) {
137
+ this._logger = createLogger('@cratis/chronicle/reducers', _connection.logger);
136
138
  this._lifecycle = lifecycle;
137
139
  lifecycle.onDisconnected(async () => {
138
140
  this._logger.info('Disconnected — stopping all reducer observations');
@@ -235,21 +237,23 @@ export class Reducers implements IReducers {
235
237
  }
236
238
 
237
239
  private startObservation(id: string, reducerType: Constructor): void {
238
- if (this._disposed) return;
239
- const metadata = getReducerMetadata(reducerType)!;
240
- const eventSequenceId = metadata.eventSequenceId ?? EventSequenceId.eventLog.value;
241
- const dispatcher = new ReducerEventDispatcher(reducerType, this._clientArtifacts.eventTypes);
242
- const readModelName = this.getReducerReadModelIdentifier(reducerType);
243
-
244
- this._logger.info('Starting reducer observation', {
245
- reducerId: id,
246
- eventSequenceId,
247
- readModel: readModelName,
248
- handlerCount: dispatcher.handlers.length,
249
- handlers: dispatcher.handlers.map(e => e.methodName)
250
- });
240
+ runInBackgroundContext(() => {
241
+ if (this._disposed) return;
242
+ const metadata = getReducerMetadata(reducerType)!;
243
+ const eventSequenceId = metadata.eventSequenceId ?? EventSequenceId.eventLog.value;
244
+ const dispatcher = new ReducerEventDispatcher(reducerType, this._clientArtifacts.eventTypes);
245
+ const readModelName = this.getReducerReadModelIdentifier(reducerType);
251
246
 
252
- void this.runObservation(id, reducerType, eventSequenceId, readModelName, dispatcher);
247
+ this._logger.info('Starting reducer observation', {
248
+ reducerId: id,
249
+ eventSequenceId,
250
+ readModel: readModelName,
251
+ handlerCount: dispatcher.handlers.length,
252
+ handlers: dispatcher.handlers.map(e => e.methodName)
253
+ });
254
+
255
+ void this.runObservation(id, reducerType, eventSequenceId, readModelName, dispatcher);
256
+ });
253
257
  }
254
258
 
255
259
  private async runObservation(
@@ -262,7 +266,7 @@ export class Reducers implements IReducers {
262
266
  try {
263
267
  await this.observeReducer(id, reducerType, eventSequenceId, readModelName, dispatcher);
264
268
  } catch (error) {
265
- this._logger.error('Reducer observation loop exited with error', { reducerId: id, error: String(error) });
269
+ this._logger.error('Reducer observation loop exited with error', { reducerId: id, error });
266
270
  }
267
271
 
268
272
  this.scheduleReobserve(id, reducerType);
@@ -285,14 +289,14 @@ export class Reducers implements IReducers {
285
289
  return;
286
290
  }
287
291
 
288
- const handle = setTimeout(() => {
292
+ const handle = runInBackgroundContext(() => setTimeout(() => {
289
293
  if (!this._registered || this._disposed) {
290
294
  return;
291
295
  }
292
296
 
293
297
  this._logger.info('Re-establishing reducer observation', { reducerId: id });
294
298
  this.startObservation(id, reducerType);
295
- }, Reducers._reobserveDelayMs);
299
+ }, Reducers._reobserveDelayMs));
296
300
 
297
301
  handle.unref?.();
298
302
  }
@@ -359,7 +363,6 @@ export class Reducers implements IReducers {
359
363
 
360
364
  this._logger.debug('Received reduce operation', {
361
365
  reducerId: id,
362
- partition: operation.Partition,
363
366
  count: operation.Events.length,
364
367
  hasInitialState: operation.InitialState !== '',
365
368
  replayState: operation.ReplayState
@@ -375,12 +378,12 @@ export class Reducers implements IReducers {
375
378
  replayState: operation.ReplayState
376
379
  }, this._artifactActivator, artifact => runActivated(artifact, () =>
377
380
  notifyReplayLifecycle(artifact.instance, operation.ReplayState, operation.Partition),
378
- { delivery: ArtifactDelivery.ReplayNotification, replayState: operation.ReplayState }));
381
+ { delivery: ArtifactDelivery.ReplayNotification, replayState: operation.ReplayState }), this._connection.logger);
379
382
  } else if (reducerInstance) {
380
383
  await notifyReplayLifecycle(reducerInstance, operation.ReplayState, operation.Partition);
381
384
  }
382
385
  } catch (err) {
383
- this._logger.error('Error notifying reducer of replay lifecycle transition', { reducerId: id, error: String(err) });
386
+ this._logger.error('Error notifying reducer of replay lifecycle transition', { reducerId: id, error: err });
384
387
  if (err instanceof ArtifactCompletionFailed && err.processingError !== undefined) {
385
388
  exceptionMessages.push(String(err.processingError));
386
389
  }
@@ -424,7 +427,7 @@ export class Reducers implements IReducers {
424
427
  { delivery: ArtifactDelivery.Events, eventContext: context, methodName: entry.methodName });
425
428
  lastSuccessfullyObservedEvent = event.Context!.SequenceNumber;
426
429
  } catch (err) {
427
- this._logger.error('Error handling event in reducer', { reducerId: id, error: String(err) });
430
+ this._logger.error('Error handling event in reducer', { reducerId: id, error: err });
428
431
  exceptionMessages.push(String(err));
429
432
  exceptionStackTrace = err instanceof Error ? (err.stack ?? '') : '';
430
433
  state = ObservationState.Failed;
@@ -445,9 +448,9 @@ export class Reducers implements IReducers {
445
448
  readModels: this._eventStore.readModels, eventSequenceId, partition: operation.Partition,
446
449
  signal: controller.signal, delivery: ArtifactDelivery.Events,
447
450
  eventContext: toClientEventContext(firstInvocableEvent.Context!)
448
- }, this._artifactActivator, processEvents);
451
+ }, this._artifactActivator, processEvents, this._connection.logger);
449
452
  } catch (err) {
450
- this._logger.error('Error activating reducer', { reducerId: id, error: String(err) });
453
+ this._logger.error('Error activating reducer', { reducerId: id, error: err });
451
454
  exceptionMessages.push(String(err));
452
455
  const completionStack = err instanceof Error ? (err.stack ?? '') : '';
453
456
  exceptionStackTrace = err instanceof ArtifactCompletionFailed && exceptionStackTrace
@@ -483,7 +486,7 @@ export class Reducers implements IReducers {
483
486
  if (!this._queues.has(id)) {
484
487
  this._logger.debug('Reducer observation stream closed cleanly', { reducerId: id });
485
488
  } else {
486
- this._logger.error('Reducer observation stream ended unexpectedly', { reducerId: id, error: String(err) });
489
+ this._logger.error('Reducer observation stream ended unexpectedly', { reducerId: id, error: err });
487
490
  }
488
491
  } finally {
489
492
  // Only retire our own queue: a reconnect can already have replaced it,
@@ -1,7 +1,7 @@
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
5
  import { IClientArtifactsProvider } from '../artifacts/index.js';
6
6
  import { ChronicleConnection } from '../connection/index.js';
7
7
  import { ensureCommandSuccess } from '../connection/callResults.js';
@@ -27,7 +27,7 @@ interface SeedingEntry {
27
27
  * with the Chronicle Kernel.
28
28
  */
29
29
  export class EventSeeding implements IEventSeeding {
30
- private readonly _logger = diag.createComponentLogger({ namespace: '@cratis/chronicle/EventSeeding' });
30
+ private readonly _logger: ReturnType<typeof createLogger>;
31
31
  private readonly _entries: SeedingEntry[] = [];
32
32
 
33
33
  /**
@@ -40,7 +40,9 @@ export class EventSeeding implements IEventSeeding {
40
40
  private readonly _eventStoreName: string,
41
41
  private readonly _connection: ChronicleConnection,
42
42
  private readonly _clientArtifacts: IClientArtifactsProvider
43
- ) {}
43
+ ) {
44
+ this._logger = createLogger('@cratis/chronicle/EventSeeding', _connection.logger);
45
+ }
44
46
 
45
47
  /** @inheritdoc */
46
48
  for<TEvent extends object>(eventSourceId: string, events: Iterable<TEvent>): IEventSeedingBuilder {
@@ -69,7 +71,7 @@ export class EventSeeding implements IEventSeeding {
69
71
  } catch (error) {
70
72
  this._logger.warn('Failed to activate or execute event seeder - skipping seeder', {
71
73
  seederType: seederType.name,
72
- error: String(error)
74
+ error
73
75
  });
74
76
  }
75
77
  }
@@ -0,0 +1,8 @@
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
+ /** Per-client privacy settings; instrumentation itself is always available. */
5
+ export interface ChronicleTelemetryOptions {
6
+ /** Absent means no event source identifiers. HMAC uses SHA-256 and a deployment-owned key. */
7
+ eventSourceId?: { mode: 'raw' } | { mode: 'hmac'; key: Uint8Array };
8
+ }
@@ -0,0 +1,36 @@
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 { beforeAll, beforeEach, afterAll } from 'vitest';
5
+ import { context, metrics, propagation, trace } from '@opentelemetry/api';
6
+ import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
7
+ import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';
8
+ import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
9
+ import { InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader, AggregationTemporality } from '@opentelemetry/sdk-metrics';
10
+
11
+ /** In-process providers only; no export outside the spec process. */
12
+ export function telemetrySession() {
13
+ const spans = new InMemorySpanExporter();
14
+ const traces = new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(spans)] });
15
+ const metricExporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
16
+ const reader = new PeriodicExportingMetricReader({ exporter: metricExporter, exportIntervalMillis: 60000 });
17
+ const meters = new MeterProvider({ readers: [reader] });
18
+ const contextManager = new AsyncLocalStorageContextManager();
19
+ beforeAll(() => {
20
+ context.setGlobalContextManager(contextManager.enable());
21
+ trace.setGlobalTracerProvider(traces);
22
+ metrics.setGlobalMeterProvider(meters);
23
+ propagation.setGlobalPropagator(new CompositePropagator({ propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()] }));
24
+ });
25
+ beforeEach(() => spans.reset());
26
+ afterAll(async () => {
27
+ await traces.shutdown();
28
+ await meters.shutdown();
29
+ context.disable();
30
+ trace.disable();
31
+ metrics.disable();
32
+ propagation.disable();
33
+ contextManager.disable();
34
+ });
35
+ return { spans, traces, reader };
36
+ }
@@ -0,0 +1,29 @@
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 { vi } from 'vitest';
5
+ import { EventSequence } from '../../../eventSequences/EventSequence.js';
6
+ import { EventSequenceId } from '../../../eventSequences/EventSequenceId.js';
7
+ import { eventType } from '../../../events/eventTypeDecorator.js';
8
+ import type { ChronicleConnection } from '../../../connection/ChronicleConnection.js';
9
+ import type { IUnitOfWorkManager } from '../../../transactions/IUnitOfWorkManager.js';
10
+ import type { ChronicleTelemetryOptions } from '../../ChronicleTelemetryOptions.js';
11
+
12
+ export class Recorded { constructor(readonly value = 'sensitive payload') {} }
13
+ eventType('telemetry-recorded')(Recorded);
14
+
15
+ export function eventSequence(telemetry?: ChronicleTelemetryOptions) {
16
+ const services = {
17
+ append: vi.fn().mockResolvedValue({ Response: { SequenceNumber: 42n } }),
18
+ appendManyForEventSources: vi.fn().mockResolvedValue({ Response: { SequenceNumbers: [42n, 43n] } }),
19
+ tailSequenceNumber: vi.fn().mockResolvedValue({ IsAuthorized: true, Data: { SequenceNumber: 42n } }),
20
+ hasEventsForEventSourceId: vi.fn().mockResolvedValue({ IsAuthorized: true, Data: { HasEvents: true } }),
21
+ forEventSourceIdAndEventTypes: vi.fn().mockResolvedValue({ IsAuthorized: true, Data: [] }),
22
+ fromSequenceNumber: vi.fn().mockResolvedValue({ IsAuthorized: true, Data: [] }),
23
+ redact: vi.fn().mockResolvedValue({}),
24
+ redactForEventSource: vi.fn().mockResolvedValue({})
25
+ };
26
+ const connection = { eventSequences: services } as unknown as ChronicleConnection;
27
+ return { services, sequence: new EventSequence(EventSequenceId.eventLog, 'store', 'namespace', connection,
28
+ {} as IUnitOfWorkManager, undefined, telemetry) };
29
+ }
@@ -0,0 +1,58 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { SpanKind, context, propagation } from '@opentelemetry/api';
6
+ import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
7
+ import { telemetrySession } from '../given/a_telemetry_session.fixture.js';
8
+ import { eventSequence, Recorded } from '../given/an_event_sequence.fixture.js';
9
+ import { clientVersion } from '../../../connection/clientVersion.js';
10
+ import { CorrelationId, correlationIdManager } from '../../../correlation/index.js';
11
+
12
+ const should = chai.should();
13
+ const telemetry = telemetrySession();
14
+ const correlation = '95bd1c83-de97-45f7-84a0-fb462af14d0c';
15
+
16
+ describe('when appending with a business correlation override', () => {
17
+ let span: ReadableSpan;
18
+ let injectedCorrelation: string | undefined;
19
+ beforeEach(async () => {
20
+ const { sequence, services } = eventSequence();
21
+ services.append.mockImplementation(async () => {
22
+ await Promise.resolve();
23
+ injectedCorrelation = propagation.getBaggage(context.active())?.getEntry('cratis.correlation_id')?.value;
24
+ return { Response: { SequenceNumber: 42n } };
25
+ });
26
+ await correlationIdManager.run(new CorrelationId('outer'), () => sequence.append('sensitive-id', new Recorded(), { correlationId: correlation }));
27
+ span = telemetry.spans.getFinishedSpans()[0];
28
+ });
29
+ it('should produce one client span with the unchanged operation name', () => {
30
+ telemetry.spans.getFinishedSpans().should.have.lengthOf(1);
31
+ span.kind.should.equal(SpanKind.CLIENT);
32
+ span.name.should.equal('chronicle.event_sequences.append');
33
+ });
34
+ it('should use the versioned shared instrumentation scope', () => {
35
+ span.instrumentationScope.name.should.equal('Cratis.Chronicle.Client');
36
+ should.equal(span.instrumentationScope.version, clientVersion);
37
+ });
38
+ it('should emit both registries on the same span', () => {
39
+ span.attributes.should.include({
40
+ 'cratis.event_store.name': 'store', 'chronicle.event_store': 'store',
41
+ 'cratis.event_store.namespace': 'namespace', 'chronicle.namespace': 'namespace',
42
+ 'cratis.event_sequence.id': 'event-log', 'chronicle.event_sequence_id': 'event-log',
43
+ 'cratis.event_type.id': 'telemetry-recorded', 'chronicle.event_type_id': 'telemetry-recorded',
44
+ 'cratis.event_type.generation': 1, 'chronicle.event_type_generation': 1,
45
+ 'cratis.event_sequence.number': 42, 'chronicle.sequence_number': '42'
46
+ });
47
+ });
48
+ it('should scope the resolved correlation consistently for propagation', () => {
49
+ should.equal(span.attributes['cratis.correlation_id'], correlation);
50
+ should.equal(injectedCorrelation, correlation);
51
+ should.equal(correlationIdManager.scoped, undefined);
52
+ });
53
+ it('should not record identifiers or payloads by default', () => {
54
+ JSON.stringify(span.attributes).should.not.contain('sensitive');
55
+ should.equal(span.attributes['cratis.event_source.id'], undefined);
56
+ should.equal(span.attributes['chronicle.event_source_id'], undefined);
57
+ });
58
+ });
@@ -0,0 +1,15 @@
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 { chai, describe, it } from 'vitest';
5
+ import { eventSequence, Recorded } from '../given/an_event_sequence.fixture.js';
6
+
7
+ chai.should();
8
+ describe('when appending without an OpenTelemetry SDK', () => {
9
+ it('should preserve append behavior without telemetry setup', async () => {
10
+ const { sequence, services } = eventSequence();
11
+ const result = await sequence.append('source', new Recorded());
12
+ result.sequenceNumber.value.should.equal(42n);
13
+ services.append.mock.calls.should.have.lengthOf(1);
14
+ });
15
+ });
@@ -0,0 +1,15 @@
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 { readFileSync } from 'node:fs';
5
+ import { chai, describe, it } from 'vitest';
6
+ import { WellKnownTelemetryNames } from '../../../WellKnownTelemetryNames.js';
7
+ import { telemetryReference } from '../../../scripts/telemetry-reference.mjs';
8
+
9
+ chai.should();
10
+ describe('when publishing the telemetry reference', () => {
11
+ it('should keep the documented registry identical to the public constants', () => {
12
+ const documentation = readFileSync(new URL('../../../../Documentation/observability.md', import.meta.url), 'utf8');
13
+ documentation.should.contain(telemetryReference(WellKnownTelemetryNames));
14
+ });
15
+ });
@@ -0,0 +1,34 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { SpanStatusCode } from '@opentelemetry/api';
6
+ import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
7
+ import { telemetrySession } from '../given/a_telemetry_session.fixture.js';
8
+ import { eventSequence, Recorded } from '../given/an_event_sequence.fixture.js';
9
+
10
+ chai.should();
11
+ const telemetry = telemetrySession();
12
+
13
+ describe('when an append fails with sensitive exception details', () => {
14
+ const failure = new TypeError('sensitive token in error message');
15
+ let span: ReadableSpan;
16
+ let thrown: unknown;
17
+ beforeEach(async () => {
18
+ const { sequence, services } = eventSequence();
19
+ services.append.mockRejectedValue(failure);
20
+ try { await sequence.append('private', new Recorded()); } catch (error) { thrown = error; }
21
+ span = telemetry.spans.getFinishedSpans()[0];
22
+ });
23
+ it('should rethrow the original failure', () => { (thrown === failure).should.be.true; });
24
+ it('should record only failure types and error status', () => {
25
+ span.status.should.deep.equal({ code: SpanStatusCode.ERROR });
26
+ span.attributes.should.include({ 'error.type': 'TypeError' });
27
+ span.events.should.have.lengthOf(1);
28
+ span.events[0].attributes!.should.deep.equal({ 'exception.type': 'TypeError' });
29
+ });
30
+ it('should never export the exception message or stack', () => {
31
+ JSON.stringify({ attributes: span.attributes, status: span.status, events: span.events }).should.not.contain('sensitive');
32
+ span.events[0].attributes!.should.not.have.property('exception.stacktrace');
33
+ });
34
+ });
@@ -0,0 +1,92 @@
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 { createHmac } from 'node:crypto';
5
+ import { beforeEach, chai, describe, it } from 'vitest';
6
+ import { telemetrySession } from '../given/a_telemetry_session.fixture.js';
7
+ import { eventSequence, Recorded } from '../given/an_event_sequence.fixture.js';
8
+ import { EventSequenceNumber } from '../../../eventSequences/EventSequenceNumber.js';
9
+ import { ChronicleOptions } from '../../../ChronicleOptions.js';
10
+ import type { ChronicleTelemetryOptions } from '../../ChronicleTelemetryOptions.js';
11
+
12
+ const should = chai.should();
13
+ const telemetry = telemetrySession();
14
+ const source = 'sensitive-source';
15
+ const key = new Uint8Array([10, 20, 30, 40]);
16
+
17
+ for (const [label, policy, expected] of [
18
+ ['absent', undefined, undefined],
19
+ ['raw', { eventSourceId: { mode: 'raw' } }, source],
20
+ ['hmac', { eventSourceId: { mode: 'hmac', key } }, createHmac('sha256', key).update(source).digest('hex')]
21
+ ] as const) {
22
+ describe(`when recording event source identifiers with ${label} privacy`, () => {
23
+ beforeEach(async () => {
24
+ const { sequence, services } = eventSequence(policy as ChronicleTelemetryOptions | undefined);
25
+ await sequence.append(source, new Recorded());
26
+ await sequence.appendMany(source, [new Recorded(), new Recorded()]);
27
+ await sequence.appendMany([{ eventSourceId: source, event: new Recorded() }, { eventSourceId: source, event: new Recorded() }]);
28
+ await sequence.getTailSequenceNumber(source);
29
+ await sequence.hasEventsFor(source);
30
+ await sequence.getForEventSourceIdAndEventTypes(source, [Recorded]);
31
+ await sequence.getFromSequenceNumber(new EventSequenceNumber(1n), source);
32
+ await sequence.redactForEventSource(source, 'private reason');
33
+ services.append.mock.calls[0][0].EventSourceId.should.equal(source);
34
+ });
35
+ it('should apply the policy equally to both names on every operation', () => {
36
+ telemetry.spans.getFinishedSpans().should.have.lengthOf(8);
37
+ for (const span of telemetry.spans.getFinishedSpans()) {
38
+ should.equal(span.attributes['cratis.event_source.id'], expected);
39
+ should.equal(span.attributes['chronicle.event_source_id'], expected);
40
+ }
41
+ });
42
+ });
43
+ }
44
+
45
+ describe('when a validated telemetry policy is later mutated by an untyped caller', () => {
46
+ beforeEach(async () => {
47
+ const options = ChronicleOptions.development({ telemetry: { eventSourceId: { mode: 'hmac', key } } });
48
+ Object.assign(options.telemetry!.eventSourceId!, { key: undefined });
49
+ const { sequence, services } = eventSequence(options.telemetry);
50
+ await sequence.append(source, new Recorded());
51
+ await sequence.getTailSequenceNumber(source);
52
+ await sequence.redactForEventSource(source, 'reason');
53
+ services.append.mock.calls.should.have.lengthOf(1);
54
+ services.tailSequenceNumber.mock.calls.should.have.lengthOf(1);
55
+ services.redactForEventSource.mock.calls.should.have.lengthOf(1);
56
+ });
57
+ it('should omit the failed enrichment without failing business calls or leaving spans open', () => {
58
+ telemetry.spans.getFinishedSpans().should.have.lengthOf(3);
59
+ for (const span of telemetry.spans.getFinishedSpans()) {
60
+ span.attributes.should.not.have.property('cratis.event_source.id');
61
+ span.attributes.should.not.have.property('chronicle.event_source_id');
62
+ }
63
+ });
64
+ });
65
+
66
+ describe('when appending a batch with multiple event sources', () => {
67
+ beforeEach(async () => {
68
+ await eventSequence({ eventSourceId: { mode: 'raw' } }).sequence.appendMany([
69
+ { eventSourceId: 'first', event: new Recorded() }, { eventSourceId: 'second', event: new Recorded() }
70
+ ]);
71
+ });
72
+ it('should record the count without an identifier array', () => {
73
+ const attributes = telemetry.spans.getFinishedSpans()[0].attributes;
74
+ attributes.should.include({ 'cratis.event.count': 2, 'chronicle.events_count': 2 });
75
+ should.equal(attributes['cratis.event_source.id'], undefined);
76
+ should.equal(attributes['chronicle.event_source_id'], undefined);
77
+ });
78
+ });
79
+
80
+ describe('when clients use different identifier policies concurrently', () => {
81
+ beforeEach(async () => {
82
+ await Promise.all([
83
+ eventSequence().sequence.append(source, new Recorded()),
84
+ eventSequence({ eventSourceId: { mode: 'raw' } }).sequence.append(source, new Recorded()),
85
+ eventSequence({ eventSourceId: { mode: 'hmac', key } }).sequence.append(source, new Recorded())
86
+ ]);
87
+ });
88
+ it('should keep each policy local to its client', () => {
89
+ const identifiers = telemetry.spans.getFinishedSpans().map(span => span.attributes['cratis.event_source.id']);
90
+ identifiers.should.have.members([undefined, source, createHmac('sha256', key).update(source).digest('hex')]);
91
+ });
92
+ });
@@ -0,0 +1,40 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { telemetrySession } from '../given/a_telemetry_session.fixture.js';
6
+ import { ChronicleMetrics } from '../../../Metrics.js';
7
+ import { clientVersion } from '../../../connection/clientVersion.js';
8
+ import type { ScopeMetrics } from '@opentelemetry/sdk-metrics';
9
+
10
+ chai.should();
11
+ const telemetry = telemetrySession();
12
+ describe('when recording metrics after installing the SDK', () => {
13
+ let scope: ScopeMetrics;
14
+ beforeEach(async () => {
15
+ // Metrics was imported before provider installation: recording must still reach the SDK.
16
+ const attributes = { 'chronicle.event_store': 'store', 'chronicle.namespace': 'namespace',
17
+ 'chronicle.event_sequence_id': 'event-log', 'chronicle.events_count': 237 };
18
+ ChronicleMetrics.batchAppendsPerformed.add(1, attributes);
19
+ ChronicleMetrics.appendManyDuration.record(250, attributes);
20
+ scope = (await telemetry.reader.collect()).resourceMetrics.scopeMetrics[0];
21
+ });
22
+ it('should use the shared scope and installed version', () => {
23
+ scope.scope.should.include({ name: 'Cratis.Chronicle.Client', version: clientVersion });
24
+ });
25
+ it('should retain legacy milliseconds while emitting seconds', () => {
26
+ const legacy = scope.metrics.find(metric => metric.descriptor.name === 'chronicle.events.append_many_duration')!;
27
+ const shared = scope.metrics.find(metric => metric.descriptor.name === 'cratis.chronicle.event_sequence.append_many_duration')!;
28
+ legacy.descriptor.unit.should.equal('ms');
29
+ shared.descriptor.unit.should.equal('s');
30
+ legacy.dataPoints[0].value.should.include({ min: 250, max: 250 });
31
+ shared.dataPoints[0].value.should.include({ min: 0.25, max: 0.25 });
32
+ });
33
+ it('should exclude batch size from only the new metric dimensions', () => {
34
+ const legacy = scope.metrics.find(metric => metric.descriptor.name === 'chronicle.events.batch_appends')!;
35
+ const shared = scope.metrics.find(metric => metric.descriptor.name === 'cratis.chronicle.event_sequence.batch_appends')!;
36
+ legacy.dataPoints[0].attributes.should.have.property('chronicle.events_count', 237);
37
+ shared.dataPoints[0].attributes.should.deep.equal({ 'cratis.event_store.name': 'store',
38
+ 'cratis.event_store.namespace': 'namespace', 'cratis.event_sequence.id': 'event-log' });
39
+ });
40
+ });
@@ -0,0 +1,23 @@
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 { beforeEach, chai, describe, it } from 'vitest';
5
+ import { telemetrySession } from '../given/a_telemetry_session.fixture.js';
6
+ import { eventSequence } from '../given/an_event_sequence.fixture.js';
7
+
8
+ const should = chai.should();
9
+ const telemetry = telemetrySession();
10
+ for (const [number, expected] of [[42n, 42], [9007199254740991n, Number.MAX_SAFE_INTEGER], [9007199254740992n, undefined], [18446744073709551615n, undefined]] as const) {
11
+ describe(`when reading sequence number ${number}`, () => {
12
+ beforeEach(async () => {
13
+ const { sequence, services } = eventSequence();
14
+ services.tailSequenceNumber.mockResolvedValue({ IsAuthorized: true, Data: { SequenceNumber: number } });
15
+ await sequence.getTailSequenceNumber();
16
+ });
17
+ it('should keep the legacy string and emit only exactly representable shared integers', () => {
18
+ const attributes = telemetry.spans.getFinishedSpans()[0].attributes;
19
+ should.equal(attributes['cratis.event_sequence.number'], expected);
20
+ should.equal(attributes['chronicle.sequence_number'], number.toString());
21
+ });
22
+ });
23
+ }
@@ -0,0 +1,25 @@
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, SpanKind, type Span } from '@opentelemetry/api';
5
+ import { ChronicleTracer } from '../Tracing.js';
6
+ import { correlationIdManager } from '../correlation/index.js';
7
+ import { CorrelationId } from '../correlation/CorrelationId.js';
8
+ import { WellKnownTelemetryNames } from '../WellKnownTelemetryNames.js';
9
+
10
+ /** Opens one client span, scoping the resolved business correlation without generating a new one. */
11
+ export function observeOperation<T>(name: string, action: (span: Span) => Promise<T>, correlationId?: { toString(): string }): Promise<T> {
12
+ const resolved = correlationId ? new CorrelationId(correlationId.toString()) : correlationIdManager.scoped;
13
+ const run = () => {
14
+ const active = context.active();
15
+ const key = WellKnownTelemetryNames.attributes.correlationId;
16
+ const correlation = resolved?.toString() ?? propagation.getBaggage(active)?.getEntry(key)?.value;
17
+ const parent = correlation ? propagation.setBaggage(active,
18
+ (propagation.getBaggage(active) ?? propagation.createBaggage()).setEntry(key, { value: correlation })) : active;
19
+ return ChronicleTracer.startActiveSpan(name, {
20
+ kind: SpanKind.CLIENT,
21
+ attributes: correlation ? { [key]: correlation } : {}
22
+ }, parent, action);
23
+ };
24
+ return resolved ? correlationIdManager.run(resolved, run) : run();
25
+ }
@@ -0,0 +1,10 @@
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, ROOT_CONTEXT } from '@opentelemetry/api';
5
+ import { correlationIdManager } from '../correlation/index.js';
6
+
7
+ /** Starts client-owned work without retaining the initiating request's trace or business correlation. */
8
+ export function runInBackgroundContext<T>(action: () => T): T {
9
+ return context.with(ROOT_CONTEXT, () => correlationIdManager.runWithout(action));
10
+ }