@cratis/chronicle 6.32.1 → 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 (189) 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/readModels/IReadModels.d.ts +14 -0
  110. package/dist/readModels/IReadModels.d.ts.map +1 -1
  111. package/dist/readModels/ReadModels.d.ts +6 -0
  112. package/dist/readModels/ReadModels.d.ts.map +1 -1
  113. package/dist/readModels/ReadModels.js +110 -0
  114. package/dist/readModels/ReadModels.js.map +1 -1
  115. package/dist/readModels/for_ReadModels/given/a_stored_read_model.d.ts +20 -0
  116. package/dist/readModels/for_ReadModels/given/a_stored_read_model.d.ts.map +1 -0
  117. package/dist/readModels/for_ReadModels/given/a_stored_read_model.js +79 -0
  118. package/dist/readModels/for_ReadModels/given/a_stored_read_model.js.map +1 -0
  119. package/dist/reducers/Reducers.d.ts.map +1 -1
  120. package/dist/reducers/Reducers.js +28 -25
  121. package/dist/reducers/Reducers.js.map +1 -1
  122. package/dist/seeding/EventSeeding.d.ts.map +1 -1
  123. package/dist/seeding/EventSeeding.js +4 -3
  124. package/dist/seeding/EventSeeding.js.map +1 -1
  125. package/dist/telemetry/ChronicleTelemetryOptions.d.ts +11 -0
  126. package/dist/telemetry/ChronicleTelemetryOptions.d.ts.map +1 -0
  127. package/dist/telemetry/ChronicleTelemetryOptions.js +4 -0
  128. package/dist/telemetry/ChronicleTelemetryOptions.js.map +1 -0
  129. package/dist/telemetry/observeOperation.d.ts +6 -0
  130. package/dist/telemetry/observeOperation.d.ts.map +1 -0
  131. package/dist/telemetry/observeOperation.js +23 -0
  132. package/dist/telemetry/observeOperation.js.map +1 -0
  133. package/dist/telemetry/runInBackgroundContext.d.ts +3 -0
  134. package/dist/telemetry/runInBackgroundContext.d.ts.map +1 -0
  135. package/dist/telemetry/runInBackgroundContext.js +9 -0
  136. package/dist/telemetry/runInBackgroundContext.js.map +1 -0
  137. package/dist/telemetry/spanAttributes.d.ts +14 -0
  138. package/dist/telemetry/spanAttributes.d.ts.map +1 -0
  139. package/dist/telemetry/spanAttributes.js +47 -0
  140. package/dist/telemetry/spanAttributes.js.map +1 -0
  141. package/dist/tsconfig.tsbuildinfo +1 -1
  142. package/eventSequences/EventLog.ts +4 -2
  143. package/eventSequences/EventSequence.ts +64 -81
  144. package/eventStoreSubscriptions/EventStoreSubscriptions.ts +5 -5
  145. package/for_ChronicleClient/when_starting_background_work/with_a_correlated_caller.spec.ts +150 -0
  146. package/for_ChronicleOptions/when_configuring_telemetry/with_event_source_identifiers.spec.ts +44 -0
  147. package/index.ts +7 -1
  148. package/logging/ChronicleLogEntry.ts +14 -0
  149. package/logging/ChronicleLogLevel.ts +11 -0
  150. package/logging/DiagChronicleLogger.ts +13 -0
  151. package/logging/IChronicleLogger.ts +10 -0
  152. package/logging/createLogger.ts +50 -0
  153. package/logging/for_ChronicleLogger/when_logging/with_client_owned_sinks.spec.ts +96 -0
  154. package/package.json +7 -1
  155. package/projections/Projections.ts +4 -3
  156. package/reactors/Reactors.ts +27 -25
  157. package/reactors/for_Reactors/when_delivering/with_activation.spec.ts +14 -14
  158. package/readModels/IReadModels.ts +15 -0
  159. package/readModels/ReadModels.ts +118 -1
  160. package/readModels/for_ReadModels/given/a_stored_read_model.ts +62 -0
  161. package/readModels/for_ReadModels/when_releasing_a_document/with_a_default_subject.spec.ts +39 -0
  162. package/readModels/for_ReadModels/when_releasing_a_document/with_a_frozen_document.spec.ts +28 -0
  163. package/readModels/for_ReadModels/when_releasing_a_document/with_a_malformed_response.spec.ts +24 -0
  164. package/readModels/for_ReadModels/when_releasing_a_document/with_a_reported_failure.spec.ts +38 -0
  165. package/readModels/for_ReadModels/when_releasing_a_document/with_an_empty_released_value.spec.ts +19 -0
  166. package/readModels/for_ReadModels/when_releasing_a_document/with_an_unknown_subject_property.spec.ts +42 -0
  167. package/readModels/for_ReadModels/when_releasing_a_document/with_bookkeeping_fields.spec.ts +27 -0
  168. package/readModels/for_ReadModels/when_releasing_a_document/with_conflicting_or_missing_properties.spec.ts +32 -0
  169. package/readModels/for_ReadModels/when_releasing_a_document/with_empty_subject_metadata.spec.ts +56 -0
  170. package/readModels/for_ReadModels/when_releasing_a_document/with_invalid_subject_metadata.spec.ts +27 -0
  171. package/readModels/for_ReadModels/when_releasing_a_document/with_mixed_subjects.spec.ts +38 -0
  172. package/readModels/for_ReadModels/when_releasing_a_document/with_property_subjects.spec.ts +52 -0
  173. package/readModels/for_ReadModels/when_releasing_a_document/with_whitespace_subjects.spec.ts +24 -0
  174. package/readModels/for_ReadModels/when_releasing_a_document/without_subjects.spec.ts +24 -0
  175. package/reducers/Reducers.ts +29 -26
  176. package/seeding/EventSeeding.ts +6 -4
  177. package/telemetry/ChronicleTelemetryOptions.ts +8 -0
  178. package/telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.ts +36 -0
  179. package/telemetry/for_ChronicleTelemetry/given/an_event_sequence.fixture.ts +29 -0
  180. package/telemetry/for_ChronicleTelemetry/when_appending/with_shared_and_legacy_attributes.spec.ts +58 -0
  181. package/telemetry/for_ChronicleTelemetry/when_appending/without_an_sdk.spec.ts +15 -0
  182. package/telemetry/for_ChronicleTelemetry/when_documenting/with_public_names.spec.ts +15 -0
  183. package/telemetry/for_ChronicleTelemetry/when_recording_failures/with_sensitive_exception_details.spec.ts +34 -0
  184. package/telemetry/for_ChronicleTelemetry/when_recording_identifiers/with_per_client_privacy.spec.ts +92 -0
  185. package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_legacy_overlap.spec.ts +40 -0
  186. package/telemetry/for_ChronicleTelemetry/when_recording_sequence_numbers/with_integer_precision.spec.ts +23 -0
  187. package/telemetry/observeOperation.ts +25 -0
  188. package/telemetry/runInBackgroundContext.ts +10 -0
  189. package/telemetry/spanAttributes.ts +49 -0
@@ -2,7 +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 { ChronicleConnection } from '../connection/index.js';
5
- import { SpanStatusCode } from '@opentelemetry/api';
5
+ import { SpanStatusCode, type Span } from '@opentelemetry/api';
6
6
  import type {
7
7
  AppendedEventResponse as ContractsAppendedEvent,
8
8
  AppendManyResponse as ContractsAppendManyResponse,
@@ -37,7 +37,10 @@ import type { WaitForCompletionOptions } from './WaitForCompletionOptions.js';
37
37
 
38
38
  /** Default timeout for {@link AppendResult.waitForCompletion}, matching the C# client's default. */
39
39
  const DEFAULT_WAIT_FOR_COMPLETION_TIMEOUT_MS = 5000;
40
- import { ChronicleTracer } from '../Tracing.js';
40
+ import { observeOperation } from '../telemetry/observeOperation.js';
41
+ import { setTelemetryAttribute, setEventSourceId, setSequenceNumber, recordSafeException } from '../telemetry/spanAttributes.js';
42
+ import type { ChronicleTelemetryOptions } from '../telemetry/ChronicleTelemetryOptions.js';
43
+ import { WellKnownTelemetryNames as names } from '../WellKnownTelemetryNames.js';
41
44
  import { ChronicleMetrics } from '../Metrics.js';
42
45
  import { identityProvider, Identity } from '../identity/index.js';
43
46
  import { causationManager, CausationType } from '../auditing/index.js';
@@ -60,7 +63,8 @@ export class EventSequence implements IEventSequence {
60
63
  private readonly _namespace: string,
61
64
  private readonly _connection: ChronicleConnection,
62
65
  private readonly _unitOfWorkManager: IUnitOfWorkManager,
63
- private readonly _resolveConstraintMessage?: (violation: ConstraintViolation) => ConstraintViolation
66
+ private readonly _resolveConstraintMessage?: (violation: ConstraintViolation) => ConstraintViolation,
67
+ private readonly _telemetry?: ChronicleTelemetryOptions
64
68
  ) {
65
69
  this.transactional = new TransactionalEventSequence(this, this._unitOfWorkManager);
66
70
  }
@@ -76,14 +80,13 @@ export class EventSequence implements IEventSequence {
76
80
  'chronicle.event_type_id': eventType.id.value
77
81
  };
78
82
 
79
- return ChronicleTracer.startActiveSpan('chronicle.event_sequences.append', async span => {
80
- span.setAttribute('chronicle.event_store', this._eventStoreName);
81
- span.setAttribute('chronicle.namespace', this._namespace);
82
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
83
- span.setAttribute('chronicle.event_source_id', eventSourceId);
84
- span.setAttribute('chronicle.event_type_id', eventType.id.value);
85
- span.setAttribute('chronicle.event_type_generation', eventType.generation.value);
86
- const startTime = Date.now();
83
+ return observeOperation(names.spans.append, async span => {
84
+ this.setSequenceAttributes(span);
85
+ setEventSourceId(span, eventSourceId, this._telemetry);
86
+ setTelemetryAttribute(span, 'eventTypeId', eventType.id.value);
87
+ setTelemetryAttribute(span, 'eventTypeGeneration', eventType.generation.value);
88
+ if (options?.sourceType) span.setAttribute(names.attributes.eventSourceType, options.sourceType);
89
+ const startTime = performance.now();
87
90
  try {
88
91
  const response = await this._connection.eventSequences.append({
89
92
  EventStore: this._eventStoreName,
@@ -113,14 +116,14 @@ export class EventSequence implements IEventSequence {
113
116
  });
114
117
 
115
118
  const appendResponse = ensureCommandResponse('append event', response);
116
- const duration = Date.now() - startTime;
119
+ const duration = performance.now() - startTime;
117
120
  const result = this.mapAppendResponse(
118
121
  appendResponse.SequenceNumber,
119
122
  appendResponse.ConstraintViolations ?? [],
120
123
  appendResponse.Errors ?? [],
121
124
  appendResponse.ConcurrencyViolation
122
125
  );
123
- span.setAttribute('chronicle.sequence_number', result.sequenceNumber.value.toString());
126
+ setSequenceNumber(span, result.sequenceNumber.value);
124
127
  span.setStatus({ code: SpanStatusCode.OK });
125
128
 
126
129
  ChronicleMetrics.eventsAppended.add(1, metricAttributes);
@@ -147,8 +150,7 @@ export class EventSequence implements IEventSequence {
147
150
 
148
151
  return result;
149
152
  } catch (error) {
150
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
151
- span.recordException(error as Error);
153
+ recordSafeException(span, error);
152
154
  ChronicleMetrics.appendErrors.add(1, {
153
155
  'chronicle.event_store': this._eventStoreName,
154
156
  'chronicle.namespace': this._namespace,
@@ -158,7 +160,7 @@ export class EventSequence implements IEventSequence {
158
160
  } finally {
159
161
  span.end();
160
162
  }
161
- });
163
+ }, correlationId);
162
164
  }
163
165
 
164
166
  /** @inheritdoc */
@@ -181,15 +183,13 @@ export class EventSequence implements IEventSequence {
181
183
  'chronicle.events_count': eventsForEventSourceIds.length
182
184
  };
183
185
 
184
- return ChronicleTracer.startActiveSpan('chronicle.event_sequences.append_many', async span => {
185
- span.setAttribute('chronicle.event_store', this._eventStoreName);
186
- span.setAttribute('chronicle.namespace', this._namespace);
187
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
186
+ return observeOperation(names.spans.appendMany, async span => {
187
+ this.setSequenceAttributes(span);
188
188
  if (distinctEventSourceIds.length === 1) {
189
- span.setAttribute('chronicle.event_source_id', distinctEventSourceIds[0]);
189
+ setEventSourceId(span, distinctEventSourceIds[0], this._telemetry);
190
190
  }
191
- span.setAttribute('chronicle.events_count', eventsForEventSourceIds.length);
192
- const startTime = Date.now();
191
+ setTelemetryAttribute(span, 'eventCount', eventsForEventSourceIds.length);
192
+ const startTime = performance.now();
193
193
  try {
194
194
  const response = await this._connection.eventSequences.appendManyForEventSources({
195
195
  EventStore: this._eventStoreName,
@@ -210,7 +210,7 @@ export class EventSequence implements IEventSequence {
210
210
  });
211
211
 
212
212
  const appendManyResponse = ensureCommandResponse('append many events', response);
213
- const duration = Date.now() - startTime;
213
+ const duration = performance.now() - startTime;
214
214
  // Mirrors the C# client: every per-event AppendResult in a batch carries all
215
215
  // constraint violations and the first concurrency violation of the whole batch —
216
216
  // the wire response doesn't correlate either back to a specific event index.
@@ -269,8 +269,7 @@ export class EventSequence implements IEventSequence {
269
269
 
270
270
  return result;
271
271
  } catch (error) {
272
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
273
- span.recordException(error as Error);
272
+ recordSafeException(span, error);
274
273
  ChronicleMetrics.appendErrors.add(1, {
275
274
  'chronicle.event_store': this._eventStoreName,
276
275
  'chronicle.namespace': this._namespace,
@@ -280,7 +279,7 @@ export class EventSequence implements IEventSequence {
280
279
  } finally {
281
280
  span.end();
282
281
  }
283
- });
282
+ }, correlationId);
284
283
  }
285
284
 
286
285
  /** @inheritdoc */
@@ -300,13 +299,9 @@ export class EventSequence implements IEventSequence {
300
299
  eventStreamId?: string,
301
300
  filterEventTypes?: Constructor[]
302
301
  ): Promise<EventSequenceNumber> {
303
- return ChronicleTracer.startActiveSpan('chronicle.event_sequences.get_tail_sequence_number', async span => {
304
- span.setAttribute('chronicle.event_store', this._eventStoreName);
305
- span.setAttribute('chronicle.namespace', this._namespace);
306
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
307
- if (eventSourceId !== undefined) {
308
- span.setAttribute('chronicle.event_source_id', eventSourceId);
309
- }
302
+ return observeOperation(names.spans.getTailSequenceNumber, async span => {
303
+ this.setSequenceAttributes(span);
304
+ setEventSourceId(span, eventSourceId, this._telemetry);
310
305
  try {
311
306
  const response = await this._connection.eventSequences.tailSequenceNumber({
312
307
  EventStore: this._eventStoreName,
@@ -324,12 +319,11 @@ export class EventSequence implements IEventSequence {
324
319
 
325
320
  const data = ensureQuerySuccess('get tail sequence number', response);
326
321
  const result = new EventSequenceNumber(data?.SequenceNumber ?? 0n);
327
- span.setAttribute('chronicle.sequence_number', result.value.toString());
322
+ setSequenceNumber(span, result.value);
328
323
  span.setStatus({ code: SpanStatusCode.OK });
329
324
  return result;
330
325
  } catch (error) {
331
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
332
- span.recordException(error as Error);
326
+ recordSafeException(span, error);
333
327
  throw error;
334
328
  } finally {
335
329
  span.end();
@@ -345,11 +339,9 @@ export class EventSequence implements IEventSequence {
345
339
 
346
340
  /** @inheritdoc */
347
341
  async hasEventsFor(eventSourceId: string): Promise<boolean> {
348
- return ChronicleTracer.startActiveSpan('chronicle.event_sequences.has_events_for', async span => {
349
- span.setAttribute('chronicle.event_store', this._eventStoreName);
350
- span.setAttribute('chronicle.namespace', this._namespace);
351
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
352
- span.setAttribute('chronicle.event_source_id', eventSourceId);
342
+ return observeOperation(names.spans.hasEventsFor, async span => {
343
+ this.setSequenceAttributes(span);
344
+ setEventSourceId(span, eventSourceId, this._telemetry);
353
345
  try {
354
346
  const response = await this._connection.eventSequences.hasEventsForEventSourceId({
355
347
  EventStore: this._eventStoreName,
@@ -359,12 +351,11 @@ export class EventSequence implements IEventSequence {
359
351
  });
360
352
 
361
353
  const result = ensureQuerySuccess('has events for event source', response)?.HasEvents ?? false;
362
- span.setAttribute('chronicle.has_events', result);
354
+ setTelemetryAttribute(span, 'hasEvents', result);
363
355
  span.setStatus({ code: SpanStatusCode.OK });
364
356
  return result;
365
357
  } catch (error) {
366
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
367
- span.recordException(error as Error);
358
+ recordSafeException(span, error);
368
359
  throw error;
369
360
  } finally {
370
361
  span.end();
@@ -380,11 +371,9 @@ export class EventSequence implements IEventSequence {
380
371
  eventStreamId?: string,
381
372
  eventSourceType?: string
382
373
  ): Promise<AppendedEvent[]> {
383
- return ChronicleTracer.startActiveSpan('chronicle.event_sequences.get_for_event_source_id_and_event_types', async span => {
384
- span.setAttribute('chronicle.event_store', this._eventStoreName);
385
- span.setAttribute('chronicle.namespace', this._namespace);
386
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
387
- span.setAttribute('chronicle.event_source_id', eventSourceId);
374
+ return observeOperation(names.spans.getForEventSourceIdAndEventTypes, async span => {
375
+ this.setSequenceAttributes(span);
376
+ setEventSourceId(span, eventSourceId, this._telemetry);
388
377
  try {
389
378
  const response = await this._connection.eventSequences.forEventSourceIdAndEventTypes({
390
379
  EventStore: this._eventStoreName,
@@ -403,8 +392,7 @@ export class EventSequence implements IEventSequence {
403
392
  span.setStatus({ code: SpanStatusCode.OK });
404
393
  return result;
405
394
  } catch (error) {
406
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
407
- span.recordException(error as Error);
395
+ recordSafeException(span, error);
408
396
  throw error;
409
397
  } finally {
410
398
  span.end();
@@ -418,11 +406,10 @@ export class EventSequence implements IEventSequence {
418
406
  eventSourceId?: string,
419
407
  filterEventTypes?: Constructor[]
420
408
  ): Promise<AppendedEvent[]> {
421
- return ChronicleTracer.startActiveSpan('chronicle.event_sequences.get_from_sequence_number', async span => {
422
- span.setAttribute('chronicle.event_store', this._eventStoreName);
423
- span.setAttribute('chronicle.namespace', this._namespace);
424
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
425
- span.setAttribute('chronicle.sequence_number', sequenceNumber.value.toString());
409
+ return observeOperation(names.spans.getFromSequenceNumber, async span => {
410
+ this.setSequenceAttributes(span);
411
+ setSequenceNumber(span, sequenceNumber.value);
412
+ setEventSourceId(span, eventSourceId, this._telemetry);
426
413
  try {
427
414
  const response = await this._connection.eventSequences.fromSequenceNumber({
428
415
  EventStore: this._eventStoreName,
@@ -437,8 +424,7 @@ export class EventSequence implements IEventSequence {
437
424
  span.setStatus({ code: SpanStatusCode.OK });
438
425
  return result;
439
426
  } catch (error) {
440
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
441
- span.recordException(error as Error);
427
+ recordSafeException(span, error);
442
428
  throw error;
443
429
  } finally {
444
430
  span.end();
@@ -452,11 +438,9 @@ export class EventSequence implements IEventSequence {
452
438
  const causationChain = causationManager.getCurrentChain();
453
439
  const identity = identityProvider.getCurrent();
454
440
 
455
- return ChronicleTracer.startActiveSpan('chronicle.event_sequences.redact', async span => {
456
- span.setAttribute('chronicle.event_store', this._eventStoreName);
457
- span.setAttribute('chronicle.namespace', this._namespace);
458
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
459
- span.setAttribute('chronicle.sequence_number', sequenceNumber.value.toString());
441
+ return observeOperation(names.spans.redact, async span => {
442
+ this.setSequenceAttributes(span);
443
+ setSequenceNumber(span, sequenceNumber.value);
460
444
  try {
461
445
  ensureCommandSuccess('redact event', await this._connection.eventSequences.redact({
462
446
  EventStore: this._eventStoreName,
@@ -473,8 +457,7 @@ export class EventSequence implements IEventSequence {
473
457
  }));
474
458
  span.setStatus({ code: SpanStatusCode.OK });
475
459
  } catch (error) {
476
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
477
- span.recordException(error as Error);
460
+ recordSafeException(span, error);
478
461
  throw error;
479
462
  } finally {
480
463
  span.end();
@@ -489,11 +472,9 @@ export class EventSequence implements IEventSequence {
489
472
  const identity = identityProvider.getCurrent();
490
473
  const wireEventTypeIds = (eventTypes ?? []).map(constructor => getEventTypeFor(constructor as unknown as Function).id.value);
491
474
 
492
- return ChronicleTracer.startActiveSpan('chronicle.event_sequences.redact_for_event_source', async span => {
493
- span.setAttribute('chronicle.event_store', this._eventStoreName);
494
- span.setAttribute('chronicle.namespace', this._namespace);
495
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
496
- span.setAttribute('chronicle.event_source_id', eventSourceId);
475
+ return observeOperation(names.spans.redactForEventSource, async span => {
476
+ this.setSequenceAttributes(span);
477
+ setEventSourceId(span, eventSourceId, this._telemetry);
497
478
  try {
498
479
  ensureCommandSuccess('redact event source', await this._connection.eventSequences.redactForEventSource({
499
480
  EventStore: this._eventStoreName,
@@ -511,8 +492,7 @@ export class EventSequence implements IEventSequence {
511
492
  }));
512
493
  span.setStatus({ code: SpanStatusCode.OK });
513
494
  } catch (error) {
514
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
515
- span.recordException(error as Error);
495
+ recordSafeException(span, error);
516
496
  throw error;
517
497
  } finally {
518
498
  span.end();
@@ -538,12 +518,10 @@ export class EventSequence implements IEventSequence {
538
518
 
539
519
  /** @inheritdoc */
540
520
  async completeStream(eventStreamType: string, eventStreamId: string): Promise<CompleteStreamResult> {
541
- return ChronicleTracer.startActiveSpan('chronicle.event_sequences.complete_stream', async span => {
542
- span.setAttribute('chronicle.event_store', this._eventStoreName);
543
- span.setAttribute('chronicle.namespace', this._namespace);
544
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
545
- span.setAttribute('chronicle.event_stream_type', eventStreamType);
546
- span.setAttribute('chronicle.event_stream_id', eventStreamId);
521
+ return observeOperation(names.spans.completeStream, async span => {
522
+ this.setSequenceAttributes(span);
523
+ setTelemetryAttribute(span, 'eventStreamType', eventStreamType);
524
+ setTelemetryAttribute(span, 'eventStreamId', eventStreamId);
547
525
  try {
548
526
  const response = await this._connection.eventSequences.completeStream({
549
527
  EventStore: this._eventStoreName,
@@ -561,8 +539,7 @@ export class EventSequence implements IEventSequence {
561
539
  span.setStatus({ code: SpanStatusCode.OK });
562
540
  return result;
563
541
  } catch (error) {
564
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
565
- span.recordException(error as Error);
542
+ recordSafeException(span, error);
566
543
  throw error;
567
544
  } finally {
568
545
  span.end();
@@ -570,6 +547,12 @@ export class EventSequence implements IEventSequence {
570
547
  });
571
548
  }
572
549
 
550
+ private setSequenceAttributes(span: Span): void {
551
+ setTelemetryAttribute(span, 'eventStore', this._eventStoreName);
552
+ setTelemetryAttribute(span, 'namespace', this._namespace);
553
+ setTelemetryAttribute(span, 'eventSequenceId', this.id.value);
554
+ }
555
+
573
556
  private toClientCompleteStreamError(error: number): CompleteStreamError {
574
557
  // Mirrors the C# client's switch: any wire value other than DefaultStreamCannotBeCompleted
575
558
  // (including UNRECOGNIZED) is treated as AlreadyCompleted.
@@ -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 { ChronicleConnection } from '../connection/index.js';
6
6
  import { EventStoreName } from '../EventStoreName.js';
7
7
  import { EventType, EventTypeGeneration, EventTypeId, IEventTypes } from '../events/index.js';
@@ -15,15 +15,15 @@ import { IEventStoreSubscriptions } from './IEventStoreSubscriptions.js';
15
15
  * Represents an implementation of {@link IEventStoreSubscriptions}.
16
16
  */
17
17
  export class EventStoreSubscriptions implements IEventStoreSubscriptions {
18
- private readonly _logger = diag.createComponentLogger({
19
- namespace: '@cratis/chronicle/EventStoreSubscriptions'
20
- });
18
+ private readonly _logger: ReturnType<typeof createLogger>;
21
19
 
22
20
  constructor(
23
21
  private readonly _eventTypes: IEventTypes,
24
22
  private readonly _targetEventStore: string,
25
23
  private readonly _connection: ChronicleConnection
26
- ) {}
24
+ ) {
25
+ this._logger = createLogger('@cratis/chronicle/EventStoreSubscriptions', _connection.logger);
26
+ }
27
27
 
28
28
  /** @inheritdoc */
29
29
  async subscribe(
@@ -0,0 +1,150 @@
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 } 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, EventStoresDefinition, ServerDefinition, ReactorsDefinition, ReducersDefinition, ReadModelsDefinition } from '@cratis/chronicle.contracts';
9
+ import { ChronicleClient } from '../../ChronicleClient.js';
10
+ import { ChronicleOptions } from '../../ChronicleOptions.js';
11
+ import type { IClientArtifactsProvider } from '../../artifacts/IClientArtifactsProvider.js';
12
+ import { CorrelationId, correlationIdManager } from '../../correlation/index.js';
13
+ import type { ChronicleLogEntry } from '../../logging/ChronicleLogEntry.js';
14
+ import { reactor } from '../../reactors/reactor.js';
15
+ import { reducer } from '../../reducers/reducer.js';
16
+ import { telemetrySession } from '../../telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.js';
17
+
18
+ const should = chai.should();
19
+ const telemetry = telemetrySession();
20
+ class BackgroundReactor {}
21
+ reactor('background-reactor')(BackgroundReactor);
22
+ class BackgroundReducer {}
23
+ reducer('background-reducer')(BackgroundReducer);
24
+ const artifacts: IClientArtifactsProvider = {
25
+ eventTypes: [], readModels: [], reactors: [BackgroundReactor], reducers: [BackgroundReducer],
26
+ seeders: [], constraints: [], projections: [], webhooks: [], eventTypeMigrations: [], globalForHandlers: []
27
+ };
28
+ let server: ReturnType<typeof createServer>;
29
+ let client: ChronicleClient;
30
+ let received: { method: string; metadata: Metadata }[];
31
+ let logs: ChronicleLogEntry[];
32
+ let hostTraceId: string;
33
+ const correlation = new CorrelationId('a4450131-7cd4-40ba-9438-69c680efc1f3');
34
+
35
+ function deferred() {
36
+ let resolve!: () => void;
37
+ const promise = new Promise<void>(complete => { resolve = complete; });
38
+ return { promise, resolve };
39
+ }
40
+
41
+ beforeEach(async () => {
42
+ received = [];
43
+ logs = [];
44
+ const reobserved = deferred();
45
+ const reconnected = deferred();
46
+ const endKeepAlive = deferred();
47
+ const counts = new Map<string, number>();
48
+ server = createServer().use(async function* (call, callContext) {
49
+ const method = call.method.path;
50
+ received.push({ method, metadata: Metadata(callContext.metadata) });
51
+ const count = (counts.get(method) ?? 0) + 1;
52
+ counts.set(method, count);
53
+ const observations = [...counts].filter(([path]) => path.endsWith('/Observe'));
54
+ if (observations.length === 2 && observations.every(([, total]) => total >= 2)) reobserved.resolve();
55
+ if (observations.length === 2 && observations.every(([, total]) => total >= 3)) reconnected.resolve();
56
+ return yield* call.next(call.request, callContext);
57
+ });
58
+ const unused = async () => { throw new Error('Unexpected RPC'); };
59
+ const success = async () => ({ IsAuthorized: true, ValidationResults: [], ExceptionMessages: [] });
60
+ let connections = 0;
61
+ server.add(ConnectionServiceDefinition, {
62
+ ...Object.fromEntries(Object.keys(ConnectionServiceDefinition.methods).map(name => [name, unused])),
63
+ checkCompatibility: async () => ({ IsCompatible: true, Incompatibilities: [], ServerVersion: 'test' }),
64
+ connectionKeepAlive: async () => ({}),
65
+ connect: async function* (request, callContext) {
66
+ const first = ++connections === 1;
67
+ yield { ConnectionId: request.ConnectionId };
68
+ if (first) await endKeepAlive.promise;
69
+ else await new Promise<void>(resolve => callContext.signal.addEventListener('abort', () => resolve(), { once: true }));
70
+ }
71
+ } as ServiceImplementation<typeof ConnectionServiceDefinition>);
72
+ server.add(ServerDefinition, {
73
+ ...Object.fromEntries(Object.keys(ServerDefinition.methods).map(name => [name, unused])),
74
+ getVersionInfo: async () => ({})
75
+ } as ServiceImplementation<typeof ServerDefinition>);
76
+ server.add(EventStoresDefinition, {
77
+ allEventStores: unused, ensureEventStore: success, observeEventStores: unused
78
+ } as ServiceImplementation<typeof EventStoresDefinition>);
79
+ server.add(ReadModelsDefinition, {
80
+ ...Object.fromEntries(Object.keys(ReadModelsDefinition.methods).map(name => [name, unused])),
81
+ registerMany: success
82
+ } as ServiceImplementation<typeof ReadModelsDefinition>);
83
+ server.add(ReactorsDefinition, {
84
+ ...Object.fromEntries(Object.keys(ReactorsDefinition.methods).map(name => [name, unused])),
85
+ observe: async function* (requests) {
86
+ for await (const _request of requests) {
87
+ yield { Events: [], Partition: '', ReplayState: 0 };
88
+ throw new ServerError(Status.UNAVAILABLE, 'End stream to exercise re-observation');
89
+ }
90
+ }
91
+ } as ServiceImplementation<typeof ReactorsDefinition>);
92
+ server.add(ReducersDefinition, {
93
+ ...Object.fromEntries(Object.keys(ReducersDefinition.methods).map(name => [name, unused])),
94
+ observe: async function* (requests) {
95
+ for await (const _request of requests) {
96
+ yield { Events: [], Partition: '', ReplayState: 0 };
97
+ throw new ServerError(Status.UNAVAILABLE, 'End stream to exercise re-observation');
98
+ }
99
+ }
100
+ } as ServiceImplementation<typeof ReducersDefinition>);
101
+ const port = await server.listen('127.0.0.1:0');
102
+ const span = telemetry.traces.getTracer('host').startSpan('host');
103
+ hostTraceId = span.spanContext().traceId;
104
+ const active = propagation.setBaggage(trace.setSpan(context.active(), span), propagation.createBaggage({
105
+ 'cratis.correlation_id': { value: correlation.toString() }, email: { value: 'private@example.com' }
106
+ }));
107
+ try {
108
+ await context.with(active, () => correlationIdManager.run(correlation, async () => {
109
+ client = new ChronicleClient(ChronicleOptions.fromConnectionString(`chronicle://127.0.0.1:${port}?disableTls=true&apiKey=test`, {
110
+ discoveryPatterns: [], clientArtifactsProvider: artifacts, logger: { log: entry => logs.push(entry) }
111
+ }));
112
+ await client.getEventStore('store');
113
+ context.active().should.equal(active);
114
+ correlationIdManager.scoped!.should.equal(correlation);
115
+ }));
116
+ await reobserved.promise;
117
+ endKeepAlive.resolve();
118
+ await reconnected.promise;
119
+ } finally { span.end(); endKeepAlive.resolve(); }
120
+ });
121
+
122
+ afterEach(async () => {
123
+ client?.dispose();
124
+ await server?.shutdown();
125
+ });
126
+
127
+ describe('when a correlated getEventStore starts background streams and their recovery loops', () => {
128
+ it('should not propagate the caller trace or baggage on observations or keep-alive RPCs', () => {
129
+ const background = received.filter(({ method }) => /\/(Observe|Connect|ConnectionKeepAlive)$/.test(method));
130
+ background.length.should.be.greaterThanOrEqual(10);
131
+ for (const { metadata } of background) {
132
+ should.equal(metadata.get('traceparent'), undefined);
133
+ should.equal(metadata.get('baggage'), undefined);
134
+ }
135
+ });
136
+ it('should retain the caller context on the foreground operation', () => {
137
+ const foreground = received.find(({ method }) => method.endsWith('/EnsureEventStore'))!;
138
+ foreground.metadata.get('traceparent')!.should.contain(hostTraceId);
139
+ should.equal(foreground.metadata.get('baggage'), `cratis.correlation_id=${correlation}`);
140
+ });
141
+ it('should not label background-loop diagnostics with the initiating business correlation or trace', () => {
142
+ const background = logs.filter(entry => /observation|keep-alive|Reconnect/.test(entry.message));
143
+ background.length.should.be.greaterThan(0);
144
+ for (const entry of background) {
145
+ entry.attributes.should.not.have.property('cratis.correlation_id');
146
+ entry.attributes.should.not.have.property('trace_id');
147
+ entry.attributes.should.not.have.property('span_id');
148
+ }
149
+ });
150
+ });
@@ -0,0 +1,44 @@
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 { runInNewContext } from 'node:vm';
6
+ import { ChronicleOptions } from '../../ChronicleOptions.js';
7
+ import type { ChronicleTelemetryOptions } from '../../telemetry/ChronicleTelemetryOptions.js';
8
+
9
+ chai.should();
10
+
11
+ for (const policy of [
12
+ { mode: 'unknown' }, {}, null,
13
+ { mode: 'hmac' }, { mode: 'hmac', key: '' }, { mode: 'hmac', key: 'secret' },
14
+ { mode: 'hmac', key: [] }, { mode: 'hmac', key: new Uint8Array() },
15
+ { mode: 'hmac', key: Buffer.alloc(0) }, { mode: 'hmac', key: new Uint16Array([1]) },
16
+ { mode: 'hmac', key: new DataView(new ArrayBuffer(1)) },
17
+ { mode: 'hmac', key: runInNewContext('new Uint8Array()') },
18
+ { mode: 'hmac', key: runInNewContext('new Uint16Array([1])') }
19
+ ]) {
20
+ describe(`when configuring event source privacy with ${JSON.stringify(policy)}`, () => {
21
+ it('should reject invalid telemetry configuration before a client can make business calls', () => {
22
+ const telemetry = { eventSourceId: policy } as unknown as ChronicleTelemetryOptions;
23
+ (() => ChronicleOptions.development({ telemetry })).should.throw(TypeError);
24
+ (() => ChronicleOptions.fromConnectionString('chronicle://localhost:35000', { telemetry })).should.throw(TypeError);
25
+ });
26
+ });
27
+ }
28
+
29
+ describe('when configuring an HMAC key created in another realm', () => {
30
+ it('should accept a non-empty byte array without relying on its realm constructor', () => {
31
+ const key = runInNewContext('new Uint8Array([1, 2, 3])') as Uint8Array;
32
+ (key instanceof Uint8Array).should.be.false;
33
+ (() => ChronicleOptions.development({ telemetry: { eventSourceId: { mode: 'hmac', key } } })).should.not.throw();
34
+ (() => ChronicleOptions.fromConnectionString('chronicle://localhost:35000', { telemetry: { eventSourceId: { mode: 'hmac', key } } })).should.not.throw();
35
+ });
36
+ });
37
+
38
+ for (const eventSourceId of [undefined, { mode: 'raw' }, { mode: 'hmac', key: new Uint8Array([1]) }, { mode: 'hmac', key: Buffer.from('key') }] as const) {
39
+ describe('when configuring a supported event source privacy policy', () => {
40
+ it('should accept omitted identifiers, raw identifiers and non-empty byte keys', () => {
41
+ (() => ChronicleOptions.development({ telemetry: { eventSourceId } })).should.not.throw();
42
+ });
43
+ });
44
+ }
package/index.ts CHANGED
@@ -37,7 +37,13 @@ export { EventStoreNamespaceName } from './EventStoreNamespaceName.js';
37
37
  export { Guid } from '@cratis/fundamentals';
38
38
  export { ReplayState } from '@cratis/chronicle.contracts';
39
39
  export { ChronicleInstrumentationName, ChronicleTracer } from './Tracing.js';
40
- export { ChronicleMeter, ChronicleMeterName, ChronicleMetrics } from './Metrics.js';
40
+ export { ChronicleMeter, ChronicleMeterName, ChronicleMetrics, ChronicleConventionMetrics } from './Metrics.js';
41
+ export { WellKnownTelemetryNames } from './WellKnownTelemetryNames.js';
42
+ export type { ChronicleTelemetryOptions } from './telemetry/ChronicleTelemetryOptions.js';
43
+ export type { IChronicleLogger } from './logging/IChronicleLogger.js';
44
+ export type { ChronicleLogEntry } from './logging/ChronicleLogEntry.js';
45
+ export { ChronicleLogLevel } from './logging/ChronicleLogLevel.js';
46
+ export { DiagChronicleLogger } from './logging/DiagChronicleLogger.js';
41
47
 
42
48
  export * from './events/index.js';
43
49
  export * from './eventSequences/index.js';
@@ -0,0 +1,14 @@
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 type { Attributes } from '@opentelemetry/api';
5
+ import type { ChronicleLogLevel } from './ChronicleLogLevel.js';
6
+
7
+ /** Structured diagnostic without event payloads, error messages or error stacks. */
8
+ export interface ChronicleLogEntry {
9
+ readonly category: string;
10
+ readonly level: ChronicleLogLevel;
11
+ readonly message: string;
12
+ /** Safe diagnostic fields, including correlation and valid trace/span identifiers when available. */
13
+ readonly attributes: Readonly<Attributes>;
14
+ }
@@ -0,0 +1,11 @@
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
+ /** Severity of a Chronicle diagnostic. */
5
+ export enum ChronicleLogLevel {
6
+ Verbose = 'verbose',
7
+ Debug = 'debug',
8
+ Info = 'info',
9
+ Warn = 'warn',
10
+ Error = 'error'
11
+ }
@@ -0,0 +1,13 @@
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 { diag } from '@opentelemetry/api';
5
+ import type { IChronicleLogger } from './IChronicleLogger.js';
6
+ import type { ChronicleLogEntry } from './ChronicleLogEntry.js';
7
+
8
+ /** Compatibility adapter used when no application logger is supplied during the minor-release overlap. */
9
+ export class DiagChronicleLogger implements IChronicleLogger {
10
+ log(entry: ChronicleLogEntry): void {
11
+ diag.createComponentLogger({ namespace: entry.category })[entry.level](entry.message, entry.attributes);
12
+ }
13
+ }
@@ -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 type { ChronicleLogEntry } from './ChronicleLogEntry.js';
5
+
6
+ /** Application-owned diagnostic sink, configured separately for each Chronicle client. */
7
+ export interface IChronicleLogger {
8
+ /** Receives one sanitized diagnostic. Implementations should not throw. */
9
+ log(entry: ChronicleLogEntry): void;
10
+ }