@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
@@ -2,7 +2,9 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import * as os from 'os';
5
- import { diag } from '@opentelemetry/api';
5
+ import { createLogger } from './logging/createLogger.js';
6
+ import { setTelemetryAttribute, recordSafeException } from './telemetry/spanAttributes.js';
7
+ import { WellKnownTelemetryNames } from './WellKnownTelemetryNames.js';
6
8
  import { SpanStatusCode } from '@opentelemetry/api';
7
9
  import { ChronicleOptions } from './ChronicleOptions.js';
8
10
  import { ChronicleConnection } from './connection/index.js';
@@ -19,7 +21,8 @@ import { EventStoreNamespaceName } from './EventStoreNamespaceName.js';
19
21
  import { IChronicleClient } from './IChronicleClient.js';
20
22
  import { IEventStore } from './IEventStore.js';
21
23
  import { ChronicleMetrics } from './Metrics.js';
22
- import { ChronicleTracer } from './Tracing.js';
24
+ import { observeOperation } from './telemetry/observeOperation.js';
25
+ import { runInBackgroundContext } from './telemetry/runInBackgroundContext.js';
23
26
  import { TypeDiscoverer } from './types/index.js';
24
27
  import { takeUnregisteredModelBoundMappings } from './types/modelBoundPropertyMetadata.js';
25
28
  import { reachableReadModelTypes } from './readModels/rootReadModelTypes.js';
@@ -45,9 +48,7 @@ export class ChronicleClient implements IChronicleClient {
45
48
  private readonly _stores: Map<string, EventStore> = new Map();
46
49
  private readonly _lifecycle = new ConnectionLifecycle();
47
50
 
48
- private readonly _logger = diag.createComponentLogger({
49
- namespace: '@cratis/chronicle/ChronicleClient'
50
- });
51
+ private readonly _logger: ReturnType<typeof createLogger>;
51
52
 
52
53
  private _watchdogHandle?: ReturnType<typeof setInterval>;
53
54
  private _connectOperation?: Promise<void>;
@@ -64,6 +65,7 @@ export class ChronicleClient implements IChronicleClient {
64
65
  * @param options - The options to configure the client, including the connection string.
65
66
  */
66
67
  constructor(readonly options: ChronicleOptions) {
68
+ this._logger = createLogger('@cratis/chronicle/ChronicleClient', options.logger);
67
69
  // When TLS is disabled, pass pre-built insecure credentials directly.
68
70
  // @cratis/chronicle.contracts ≤15.24.3 always composes call credentials with
69
71
  // channel credentials, which gRPC forbids for insecure channels. Passing
@@ -72,7 +74,7 @@ export class ChronicleClient implements IChronicleClient {
72
74
  const connectionOptions = options.connectionString.disableTls
73
75
  ? { connectionString: options.connectionString, credentials: options.connectionString.createCredentials() }
74
76
  : { connectionString: options.connectionString };
75
- this._connection = new ChronicleConnection(connectionOptions);
77
+ this._connection = new ChronicleConnection({ ...connectionOptions, logger: options.logger });
76
78
 
77
79
  this._logger.info('Created Chronicle client', {
78
80
  serverAddress: `${options.connectionString.serverAddress.host}:${options.connectionString.serverAddress.port}`,
@@ -80,9 +82,11 @@ export class ChronicleClient implements IChronicleClient {
80
82
  });
81
83
 
82
84
  if (options.discoveryPatterns.length > 0) {
83
- this._discoveryOperation = TypeDiscoverer.default.discover(options.discoveryPatterns);
84
- this._discoveryOperation.catch(error => {
85
- this._logger.error('Artifact file discovery failed', { error: String(error) });
85
+ runInBackgroundContext(() => {
86
+ this._discoveryOperation = TypeDiscoverer.default.discover(options.discoveryPatterns);
87
+ this._discoveryOperation.catch(error => {
88
+ this._logger.error('Artifact file discovery failed', { error });
89
+ });
86
90
  });
87
91
  }
88
92
 
@@ -120,9 +124,9 @@ export class ChronicleClient implements IChronicleClient {
120
124
  ? new EventStoreNamespaceName(namespace)
121
125
  : namespace;
122
126
 
123
- return ChronicleTracer.startActiveSpan('chronicle.client.get_event_store', async span => {
124
- span.setAttribute('chronicle.event_store', storeName.value);
125
- span.setAttribute('chronicle.namespace', namespaceName.value);
127
+ return observeOperation(WellKnownTelemetryNames.spans.getEventStore, async span => {
128
+ setTelemetryAttribute(span, 'eventStore', storeName.value);
129
+ setTelemetryAttribute(span, 'namespace', namespaceName.value);
126
130
  try {
127
131
  const store = await this.withReconnect('get_event_store', async () => {
128
132
  await this.ensureConnected();
@@ -142,7 +146,7 @@ export class ChronicleClient implements IChronicleClient {
142
146
  });
143
147
  ensureCommandSuccess('ensure event store', await this._connection.eventStores.ensureEventStore({ Name: storeName.value }));
144
148
 
145
- const created = new EventStore(storeName, namespaceName, this._connection, this._lifecycle, this.options.defaultSinkTypeId, this.options.clientArtifactsProvider, this.options.reactorResultHandler, this.options.artifactActivator, this.options.readModelNamingPolicy);
149
+ const created = new EventStore(storeName, namespaceName, this._connection, this._lifecycle, this.options.defaultSinkTypeId, this.options.clientArtifactsProvider, this.options.reactorResultHandler, this.options.artifactActivator, this.options.readModelNamingPolicy, this.options.telemetry);
146
150
  this._stores.set(key, created);
147
151
 
148
152
  await this.registerArtifactsForStore(created, 'new-store');
@@ -156,12 +160,11 @@ export class ChronicleClient implements IChronicleClient {
156
160
  span.setStatus({ code: SpanStatusCode.OK });
157
161
  return store;
158
162
  } catch (error) {
159
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
160
- span.recordException(error as Error);
163
+ recordSafeException(span, error);
161
164
  this._logger.error('Failed getting event store', {
162
165
  eventStore: storeName.value,
163
166
  namespace: namespaceName.value,
164
- error: this.toErrorMessage(error)
167
+ error
165
168
  });
166
169
  throw error;
167
170
  } finally {
@@ -172,7 +175,7 @@ export class ChronicleClient implements IChronicleClient {
172
175
 
173
176
  /** @inheritdoc */
174
177
  async getEventStores(): Promise<EventStoreName[]> {
175
- return ChronicleTracer.startActiveSpan('chronicle.client.get_event_stores', async span => {
178
+ return observeOperation(WellKnownTelemetryNames.spans.getEventStores, async span => {
176
179
  try {
177
180
  const response = await this.withReconnect('get_event_stores', async () => {
178
181
  await this.ensureConnected();
@@ -185,10 +188,9 @@ export class ChronicleClient implements IChronicleClient {
185
188
  span.setStatus({ code: SpanStatusCode.OK });
186
189
  return result;
187
190
  } catch (error) {
188
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
189
- span.recordException(error as Error);
191
+ recordSafeException(span, error);
190
192
  this._logger.error('Failed retrieving event stores', {
191
- error: this.toErrorMessage(error)
193
+ error
192
194
  });
193
195
  throw error;
194
196
  } finally {
@@ -210,7 +212,7 @@ export class ChronicleClient implements IChronicleClient {
210
212
  if (this._lifecycle.isConnected) {
211
213
  void this._lifecycle.disconnected(error => {
212
214
  this._logger.error('Disconnected lifecycle callback failed during dispose', {
213
- error: this.toErrorMessage(error)
215
+ error
214
216
  });
215
217
  });
216
218
  }
@@ -244,7 +246,7 @@ export class ChronicleClient implements IChronicleClient {
244
246
  await this.startKernelKeepAlive();
245
247
  await this._lifecycle.connected(error => {
246
248
  this._logger.error('Connected lifecycle callback failed', {
247
- error: this.toErrorMessage(error)
249
+ error
248
250
  });
249
251
  });
250
252
  return;
@@ -280,7 +282,7 @@ export class ChronicleClient implements IChronicleClient {
280
282
  }
281
283
 
282
284
  if (!this._connectOperation) {
283
- this._connectOperation = this.connectWithRetry().finally(() => {
285
+ this._connectOperation = runInBackgroundContext(() => this.connectWithRetry()).finally(() => {
284
286
  this._connectOperation = undefined;
285
287
  });
286
288
  }
@@ -292,16 +294,16 @@ export class ChronicleClient implements IChronicleClient {
292
294
  if (this._connectionFailure) throw this._connectionFailure;
293
295
 
294
296
  if (!this._reconnectOperation) {
295
- this._reconnectOperation = (async () => {
297
+ this._reconnectOperation = runInBackgroundContext(async () => {
296
298
  this._logger.warn('Reconnecting to Chronicle kernel', {
297
299
  reason,
298
- error: this.toErrorMessage(error)
300
+ error
299
301
  });
300
302
 
301
303
  if (this._lifecycle.isConnected) {
302
304
  await this._lifecycle.disconnected(disconnectError => {
303
305
  this._logger.error('Disconnected lifecycle callback failed', {
304
- error: this.toErrorMessage(disconnectError)
306
+ error: disconnectError
305
307
  });
306
308
  });
307
309
  }
@@ -317,7 +319,7 @@ export class ChronicleClient implements IChronicleClient {
317
319
  await this.startKernelKeepAlive();
318
320
  await this._lifecycle.connected(connectedError => {
319
321
  this._logger.error('Connected lifecycle callback failed after reconnect', {
320
- error: this.toErrorMessage(connectedError)
322
+ error: connectedError
321
323
  });
322
324
  });
323
325
  return;
@@ -331,7 +333,7 @@ export class ChronicleClient implements IChronicleClient {
331
333
  await this.backOff(attempt, 'Reconnect attempt failed, retrying', reconnectError);
332
334
  }
333
335
  }
334
- })().finally(() => {
336
+ }).finally(() => {
335
337
  this._reconnectOperation = undefined;
336
338
  });
337
339
  }
@@ -351,7 +353,7 @@ export class ChronicleClient implements IChronicleClient {
351
353
  this._logger.warn(message, {
352
354
  attempt,
353
355
  delayMs,
354
- error: this.toErrorMessage(error)
356
+ error
355
357
  });
356
358
 
357
359
  await new Promise(resolve => setTimeout(resolve, delayMs));
@@ -437,9 +439,9 @@ export class ChronicleClient implements IChronicleClient {
437
439
  }
438
440
 
439
441
  private startConnectionWatchdog(): void {
440
- this._watchdogHandle = setInterval(() => {
442
+ this._watchdogHandle = runInBackgroundContext(() => setInterval(() => {
441
443
  void this.runHealthCheck().catch(error => this.backgroundConnectionFailed('watchdog-health-check', error));
442
- }, ChronicleClient._healthCheckIntervalMs);
444
+ }, ChronicleClient._healthCheckIntervalMs));
443
445
 
444
446
  this._watchdogHandle.unref?.();
445
447
  }
@@ -477,7 +479,7 @@ export class ChronicleClient implements IChronicleClient {
477
479
  }
478
480
 
479
481
  void this.reconnect(reason, error).catch(failure => this.backgroundConnectionFailed(reason, failure));
480
- });
482
+ }, {}, this.options.logger);
481
483
 
482
484
  await keepAlive.start(
483
485
  {
@@ -511,7 +513,7 @@ export class ChronicleClient implements IChronicleClient {
511
513
  this.failConnection(error instanceof Error ? error : new Error(String(error)));
512
514
  this._logger.error('Background connection recovery failed; create a new client after correcting the server', {
513
515
  reason,
514
- error: this.toErrorMessage(error)
516
+ error
515
517
  });
516
518
  }
517
519
 
@@ -1,14 +1,19 @@
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 { types } from 'node:util';
4
5
  import { DefaultClientArtifactsProvider, IClientArtifactsProvider } from './artifacts/index.js';
5
6
  import { ChronicleConnectionString } from './connection/index.js';
6
7
  import { WellKnownSinks } from './sinks/index.js';
7
8
  import type { ReactorResultHandler } from './reactors/ReactorResultHandler.js';
8
9
  import type { ClientArtifactsActivator } from './artifacts/ClientArtifactsActivator.js';
9
10
  import type { ReadModelNamingPolicy } from './readModels/ReadModelNamingPolicy.js';
11
+ import type { ChronicleTelemetryOptions } from './telemetry/ChronicleTelemetryOptions.js';
12
+ import type { IChronicleLogger } from './logging/IChronicleLogger.js';
10
13
 
11
14
  type ChronicleOptionsConstructorParams = {
15
+ telemetry?: ChronicleTelemetryOptions;
16
+ logger?: IChronicleLogger;
12
17
  connectionString: ChronicleConnectionString;
13
18
  programIdentifier?: string;
14
19
  softwareVersion?: string;
@@ -22,6 +27,8 @@ type ChronicleOptionsConstructorParams = {
22
27
  };
23
28
 
24
29
  type ChronicleOptionsFactoryParams = {
30
+ telemetry?: ChronicleTelemetryOptions;
31
+ logger?: IChronicleLogger;
25
32
  clientArtifactsProvider?: IClientArtifactsProvider;
26
33
  discoveryPatterns?: string[];
27
34
  defaultSinkTypeId?: string;
@@ -34,6 +41,12 @@ type ChronicleOptionsFactoryParams = {
34
41
  * Represents configuration options for the Chronicle client.
35
42
  */
36
43
  export class ChronicleOptions {
44
+ /** Per-client telemetry privacy settings. Event source identifiers are omitted by default. */
45
+ readonly telemetry?: ChronicleTelemetryOptions;
46
+
47
+ /** Application diagnostic sink. Absent uses the OpenTelemetry diag compatibility adapter. */
48
+ readonly logger?: IChronicleLogger;
49
+
37
50
  /**
38
51
  * The connection string used to connect to the Chronicle Kernel.
39
52
  */
@@ -101,6 +114,17 @@ export class ChronicleOptions {
101
114
  this.reactorResultHandler = options.reactorResultHandler;
102
115
  this.artifactActivator = options.artifactActivator;
103
116
  this.readModelNamingPolicy = options.readModelNamingPolicy;
117
+ const policy = options.telemetry?.eventSourceId;
118
+ if (policy !== undefined) {
119
+ if (policy?.mode !== 'raw' && policy?.mode !== 'hmac') {
120
+ throw new TypeError('telemetry.eventSourceId.mode must be raw or hmac.');
121
+ }
122
+ if (policy.mode === 'hmac' && (!types.isUint8Array(policy.key) || policy.key.byteLength === 0)) {
123
+ throw new TypeError('telemetry.eventSourceId.key must be a non-empty Uint8Array for hmac mode.');
124
+ }
125
+ }
126
+ this.telemetry = options.telemetry;
127
+ this.logger = options.logger;
104
128
  }
105
129
 
106
130
  private static defaultDiscoveryPatterns(): string[] {
@@ -144,7 +168,9 @@ export class ChronicleOptions {
144
168
  defaultSinkTypeId: options?.defaultSinkTypeId,
145
169
  reactorResultHandler: options?.reactorResultHandler,
146
170
  artifactActivator: options?.artifactActivator,
147
- readModelNamingPolicy: options?.readModelNamingPolicy
171
+ readModelNamingPolicy: options?.readModelNamingPolicy,
172
+ telemetry: options?.telemetry,
173
+ logger: options?.logger
148
174
  });
149
175
  }
150
176
 
package/EventStore.ts CHANGED
@@ -1,7 +1,10 @@
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 { ChronicleTelemetryOptions } from './telemetry/ChronicleTelemetryOptions.js';
6
+ import { setTelemetryAttribute, recordSafeException } from './telemetry/spanAttributes.js';
7
+ import { WellKnownTelemetryNames } from './WellKnownTelemetryNames.js';
5
8
  import { SpanStatusCode } from '@opentelemetry/api';
6
9
  import { ChronicleConnection } from './connection/index.js';
7
10
  import { ConnectionLifecycle } from './connection/ConnectionLifecycle.js';
@@ -31,7 +34,7 @@ import { IReadModels } from './readModels/IReadModels.js';
31
34
  import { ReadModels } from './readModels/ReadModels.js';
32
35
  import { EventSeeding } from './seeding/EventSeeding.js';
33
36
  import { IEventSeeding } from './seeding/IEventSeeding.js';
34
- import { ChronicleTracer } from './Tracing.js';
37
+ import { observeOperation } from './telemetry/observeOperation.js';
35
38
  import { DefaultClientArtifactsProvider } from './artifacts/DefaultClientArtifactsProvider.js';
36
39
  import type { IClientArtifactsProvider } from './artifacts/IClientArtifactsProvider.js';
37
40
  import { validateArtifactSchemas } from './artifacts/validateArtifactSchemas.js';
@@ -59,9 +62,7 @@ import { Observers } from './observation/Observers.js';
59
62
  * via gRPC using the provided {@link ChronicleConnection}.
60
63
  */
61
64
  export class EventStore implements IEventStore {
62
- private readonly _logger = diag.createComponentLogger({
63
- namespace: '@cratis/chronicle/EventStore'
64
- });
65
+ private readonly _logger: ReturnType<typeof createLogger>;
65
66
 
66
67
  readonly eventLog: IEventLog;
67
68
  readonly eventTypes: IEventTypes;
@@ -93,15 +94,17 @@ export class EventStore implements IEventStore {
93
94
  private readonly _artifacts: IClientArtifactsProvider = DefaultClientArtifactsProvider.default,
94
95
  reactorResultHandler?: ReactorResultHandler,
95
96
  artifactActivator?: ClientArtifactsActivator,
96
- readModelNamingPolicy?: ReadModelNamingPolicy
97
+ readModelNamingPolicy?: ReadModelNamingPolicy,
98
+ private readonly _telemetry?: ChronicleTelemetryOptions
97
99
  ) {
100
+ this._logger = createLogger('@cratis/chronicle/EventStore', _connection.logger);
98
101
  this.unitOfWorkManager = new UnitOfWorkManager(this);
99
102
 
100
103
  const artifacts = this._artifacts;
101
104
  this._constraints = new Constraints(name.value, _connection, artifacts);
102
105
  this.constraints = this._constraints;
103
106
  const resolveConstraintMessage = this._constraints.resolveMessageFor.bind(this._constraints);
104
- this.eventLog = new EventLog(name.value, namespace.value, _connection, this.unitOfWorkManager, resolveConstraintMessage);
107
+ this.eventLog = new EventLog(name.value, namespace.value, _connection, this.unitOfWorkManager, resolveConstraintMessage, _telemetry);
105
108
  this._sequences.set(EventSequenceId.eventLog.value, this.eventLog);
106
109
 
107
110
  this.eventTypes = new EventTypes(name.value, _connection, artifacts);
@@ -180,7 +183,7 @@ export class EventStore implements IEventStore {
180
183
 
181
184
  const sequence = new EventSequence(
182
185
  id, this.name.value, this.namespace.value, this._connection, this.unitOfWorkManager,
183
- this._constraints.resolveMessageFor.bind(this._constraints)
186
+ this._constraints.resolveMessageFor.bind(this._constraints), this._telemetry
184
187
  );
185
188
  this._sequences.set(id.value, sequence);
186
189
  return sequence;
@@ -188,16 +191,15 @@ export class EventStore implements IEventStore {
188
191
 
189
192
  /** @inheritdoc */
190
193
  async getNamespaces(): Promise<EventStoreNamespaceName[]> {
191
- return ChronicleTracer.startActiveSpan('chronicle.event_store.get_namespaces', async span => {
192
- span.setAttribute('chronicle.event_store', this.name.value);
194
+ return observeOperation(WellKnownTelemetryNames.spans.getNamespaces, async span => {
195
+ setTelemetryAttribute(span, 'eventStore', this.name.value);
193
196
  try {
194
197
  const response = await this._connection.namespaces.allNamespaces({ EventStore: this.name.value });
195
198
  const result = ensureQuerySuccess('get namespaces', response).map(namespace => new EventStoreNamespaceName(namespace.Name));
196
199
  span.setStatus({ code: SpanStatusCode.OK });
197
200
  return result;
198
201
  } catch (error) {
199
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
200
- span.recordException(error as Error);
202
+ recordSafeException(span, error);
201
203
  throw error;
202
204
  } finally {
203
205
  span.end();
package/Metrics.ts CHANGED
@@ -1,109 +1,83 @@
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 { metrics } from '@opentelemetry/api';
4
+ import { metrics, type Attributes, type Counter, type Histogram } from '@opentelemetry/api';
5
+ import { clientVersion } from './connection/clientVersion.js';
6
+ import { WellKnownTelemetryNames as names } from './WellKnownTelemetryNames.js';
5
7
 
6
- /**
7
- * The name of the Chronicle metrics instrumentation library.
8
- * This matches {@link ChronicleInstrumentationName} from Tracing so that all Chronicle
9
- * telemetry is grouped under a single instrumentation scope in your observability backend.
10
- */
11
- export const ChronicleMeterName = '@cratis/chronicle';
8
+ /** The versioned instrumentation scope shared by Chronicle client traces and metrics. */
9
+ export const ChronicleMeterName = names.scope;
12
10
 
13
- /**
14
- * The OpenTelemetry meter used by the Chronicle client for all metric instrumentation.
15
- *
16
- * If your application has configured an OpenTelemetry SDK (e.g. via {@link https://www.npmjs.com/package/@opentelemetry/sdk-node}),
17
- * Chronicle metrics will automatically flow through it.
18
- * If no SDK is configured, the no-op meter is used and no overhead is incurred.
19
- */
20
- export const ChronicleMeter = metrics.getMeter(ChronicleMeterName);
11
+ /** The Chronicle meter. Applications own SDK configuration and export. */
12
+ export const ChronicleMeter = metrics.getMeter(ChronicleMeterName, clientVersion);
21
13
 
22
- /**
23
- * Pre-built metric instruments for all Chronicle client operations.
24
- * These are created once and shared across all invocations.
25
- */
26
- export const ChronicleMetrics = {
27
- /**
28
- * Counts the number of individual events appended to an event sequence.
29
- *
30
- * Attributes: `chronicle.event_store`, `chronicle.namespace`,
31
- * `chronicle.event_sequence_id`, `chronicle.event_type_id`
32
- */
33
- eventsAppended: ChronicleMeter.createCounter('chronicle.events.appended', {
34
- description: 'Number of individual events appended to an event sequence.',
35
- unit: '{event}'
36
- }),
14
+ // Resolve instruments at recording time: the OTel metrics API does not proxy a meter
15
+ // acquired before the application installs its provider. SDKs cache these instruments.
16
+ function counter(name: string, description: string, unit: string): Counter {
17
+ return { add: (value, attributes, context) => metrics.getMeter(ChronicleMeterName, clientVersion)
18
+ .createCounter(name, { description, unit }).add(value, attributes, context) };
19
+ }
37
20
 
38
- /**
39
- * Counts the number of batch-append operations performed on an event sequence.
40
- * Each call to appendMany counts as one batch regardless of how many events it contains.
41
- *
42
- * Attributes: `chronicle.event_store`, `chronicle.namespace`,
43
- * `chronicle.event_sequence_id`, `chronicle.events_count`
44
- */
45
- batchAppendsPerformed: ChronicleMeter.createCounter('chronicle.events.batch_appends', {
46
- description: 'Number of batch-append operations performed on an event sequence.',
47
- unit: '{operation}'
48
- }),
21
+ function duration(name: string, unit: 's' | 'ms'): Histogram {
22
+ const scale = unit === 's' ? 0.001 : 1;
23
+ return { record: (value, attributes, context) => metrics.getMeter(ChronicleMeterName, clientVersion)
24
+ .createHistogram(name, {
25
+ description: 'Duration of completed event append RPCs, including returned rejections.',
26
+ unit,
27
+ advice: { explicitBucketBoundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000].map(value => value * scale) }
28
+ }).record(value, attributes, context) };
29
+ }
49
30
 
50
- /**
51
- * Counts the number of event store retrieval operations.
52
- *
53
- * Attributes: `chronicle.event_store`, `chronicle.namespace`
54
- */
55
- eventStoreRetrievals: ChronicleMeter.createCounter('chronicle.client.event_store_retrievals', {
56
- description: 'Number of event store retrieval operations.',
57
- unit: '{operation}'
58
- }),
31
+ /** Shared-convention instruments. Durations are seconds; batch size is not a dimension. */
32
+ export const ChronicleConventionMetrics = {
33
+ eventsAppended: counter(names.metrics.eventsAppended, 'Number of individual events appended to an event sequence.', '{event}'),
34
+ batchAppendsPerformed: counter(names.metrics.batchAppendsPerformed, 'Number of batch-append operations.', '{operation}'),
35
+ eventStoreRetrievals: counter(names.metrics.eventStoreRetrievals, 'Number of event store retrieval operations.', '{operation}'),
36
+ appendDuration: duration(names.metrics.appendDuration, 's'),
37
+ appendManyDuration: duration(names.metrics.appendManyDuration, 's'),
38
+ constraintViolations: counter(names.metrics.constraintViolations, 'Number of constraint violations encountered during event appends.', '{violation}'),
39
+ appendErrors: counter(names.metrics.appendErrors, 'Number of errors encountered during event appends.', '{error}')
40
+ };
59
41
 
60
- /**
61
- * Measures the duration of append operations in milliseconds.
62
- *
63
- * Attributes: `chronicle.event_store`, `chronicle.namespace`,
64
- * `chronicle.event_sequence_id`, `chronicle.event_type_id`
65
- */
66
- appendDuration: ChronicleMeter.createHistogram('chronicle.events.append_duration', {
67
- description: 'Duration of individual event append operations.',
68
- unit: 'ms',
69
- advice: {
70
- explicitBucketBoundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000]
71
- }
72
- }),
42
+ /** Only the existing bounded-by-configuration dimensions belong on the new instruments. */
43
+ function sharedAttributes(attributes: Attributes = {}): Attributes {
44
+ const result: Attributes = {};
45
+ for (const name of ['eventStore', 'namespace', 'eventSequenceId', 'eventTypeId'] as const) {
46
+ const value = attributes[names.attributes[name]] ?? attributes[names.legacyAttributes[name]];
47
+ if (value !== undefined) result[names.attributes[name]] = value;
48
+ }
49
+ return result;
50
+ }
73
51
 
74
- /**
75
- * Measures the duration of batch-append operations in milliseconds.
76
- *
77
- * Attributes: `chronicle.event_store`, `chronicle.namespace`,
78
- * `chronicle.event_sequence_id`, `chronicle.events_count`
79
- */
80
- appendManyDuration: ChronicleMeter.createHistogram('chronicle.events.append_many_duration', {
81
- description: 'Duration of batch event append operations.',
82
- unit: 'ms',
83
- advice: {
84
- explicitBucketBoundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000]
85
- }
86
- }),
52
+ function bridgeCounter(legacy: Counter, shared: Counter): Counter {
53
+ return { add: (value, attributes, context) => {
54
+ legacy.add(value, attributes, context);
55
+ shared.add(value, sharedAttributes(attributes), context);
56
+ } };
57
+ }
87
58
 
88
- /**
89
- * Counts the number of append constraint violations encountered.
90
- *
91
- * Attributes: `chronicle.event_store`, `chronicle.namespace`,
92
- * `chronicle.event_sequence_id`
93
- */
94
- constraintViolations: ChronicleMeter.createCounter('chronicle.events.constraint_violations', {
95
- description: 'Number of constraint violations encountered during event appends.',
96
- unit: '{violation}'
97
- }),
59
+ function bridgeDuration(legacy: Histogram, shared: Histogram): Histogram {
60
+ return { record: (milliseconds, attributes, context) => {
61
+ legacy.record(milliseconds, attributes, context);
62
+ shared.record(milliseconds / 1000, sharedAttributes(attributes), context);
63
+ } };
64
+ }
98
65
 
99
- /**
100
- * Counts the number of append errors encountered.
101
- *
102
- * Attributes: `chronicle.event_store`, `chronicle.namespace`,
103
- * `chronicle.event_sequence_id`
104
- */
105
- appendErrors: ChronicleMeter.createCounter('chronicle.events.append_errors', {
106
- description: 'Number of errors encountered during event appends.',
107
- unit: '{error}'
108
- })
66
+ /**
67
+ * Compatibility instruments: existing callers still record milliseconds and legacy attributes.
68
+ * Each measurement also records its shared-convention equivalent, without adding sensitive dimensions.
69
+ */
70
+ export const ChronicleMetrics = {
71
+ eventsAppended: bridgeCounter(counter(names.legacyMetrics.eventsAppended,
72
+ 'Number of individual events appended to an event sequence.', '{event}'), ChronicleConventionMetrics.eventsAppended),
73
+ batchAppendsPerformed: bridgeCounter(counter(names.legacyMetrics.batchAppendsPerformed,
74
+ 'Number of batch-append operations.', '{operation}'), ChronicleConventionMetrics.batchAppendsPerformed),
75
+ eventStoreRetrievals: bridgeCounter(counter(names.legacyMetrics.eventStoreRetrievals,
76
+ 'Number of event store retrieval operations.', '{operation}'), ChronicleConventionMetrics.eventStoreRetrievals),
77
+ appendDuration: bridgeDuration(duration(names.legacyMetrics.appendDuration, 'ms'), ChronicleConventionMetrics.appendDuration),
78
+ appendManyDuration: bridgeDuration(duration(names.legacyMetrics.appendManyDuration, 'ms'), ChronicleConventionMetrics.appendManyDuration),
79
+ constraintViolations: bridgeCounter(counter(names.legacyMetrics.constraintViolations,
80
+ 'Number of constraint violations encountered during event appends.', '{violation}'), ChronicleConventionMetrics.constraintViolations),
81
+ appendErrors: bridgeCounter(counter(names.legacyMetrics.appendErrors,
82
+ 'Number of errors encountered during event appends.', '{error}'), ChronicleConventionMetrics.appendErrors)
109
83
  };
package/Tracing.ts CHANGED
@@ -2,12 +2,14 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { trace } from '@opentelemetry/api';
5
+ import { clientVersion } from './connection/clientVersion.js';
6
+ import { WellKnownTelemetryNames } from './WellKnownTelemetryNames.js';
5
7
 
6
8
  /**
7
9
  * The name of the Chronicle instrumentation library.
8
10
  * Use this name to filter or configure Chronicle client spans in your observability backend.
9
11
  */
10
- export const ChronicleInstrumentationName = '@cratis/chronicle';
12
+ export const ChronicleInstrumentationName = WellKnownTelemetryNames.scope;
11
13
 
12
14
  /**
13
15
  * The OpenTelemetry tracer used by the Chronicle client for all instrumentation.
@@ -18,4 +20,4 @@ export const ChronicleInstrumentationName = '@cratis/chronicle';
18
20
  * Chronicle spans will automatically flow through it.
19
21
  * If no SDK is configured, the no-op tracer is used and no overhead is incurred.
20
22
  */
21
- export const ChronicleTracer = trace.getTracer(ChronicleInstrumentationName);
23
+ export const ChronicleTracer = trace.getTracer(ChronicleInstrumentationName, clientVersion);
@@ -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;