@cratis/chronicle 6.42.2 → 6.44.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.
- package/ChronicleClient.ts +7 -6
- package/ChronicleOptions.ts +1 -1
- package/EventStore.ts +1 -1
- package/Metrics.ts +3 -3
- package/WellKnownTelemetryNames.ts +9 -8
- package/dist/ChronicleClient.d.ts.map +1 -1
- package/dist/ChronicleClient.js +7 -6
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/ChronicleOptions.d.ts +1 -1
- package/dist/ChronicleOptions.d.ts.map +1 -1
- package/dist/ChronicleOptions.js +1 -1
- package/dist/ChronicleOptions.js.map +1 -1
- package/dist/EventStore.js +1 -1
- package/dist/EventStore.js.map +1 -1
- package/dist/Metrics.d.ts +3 -3
- package/dist/Metrics.js +3 -3
- package/dist/WellKnownTelemetryNames.d.ts +9 -8
- package/dist/WellKnownTelemetryNames.d.ts.map +1 -1
- package/dist/WellKnownTelemetryNames.js +9 -8
- package/dist/WellKnownTelemetryNames.js.map +1 -1
- package/dist/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +31 -56
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/projections/ProjectionDefinitionCompiler.d.ts.map +1 -1
- package/dist/projections/ProjectionDefinitionCompiler.js +30 -21
- package/dist/projections/ProjectionDefinitionCompiler.js.map +1 -1
- package/dist/projections/modelBound/FromAllEventsMetadata.d.ts +8 -0
- package/dist/projections/modelBound/FromAllEventsMetadata.d.ts.map +1 -0
- package/dist/projections/modelBound/FromAllEventsMetadata.js +4 -0
- package/dist/projections/modelBound/FromAllEventsMetadata.js.map +1 -0
- package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts +4 -2
- package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -1
- package/dist/projections/modelBound/childrenAndNestedBuilder.js +15 -8
- package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -1
- package/dist/projections/modelBound/fromAll.d.ts +5 -2
- package/dist/projections/modelBound/fromAll.d.ts.map +1 -1
- package/dist/projections/modelBound/fromAll.js +8 -3
- package/dist/projections/modelBound/fromAll.js.map +1 -1
- package/dist/projections/modelBound/fromAllEvents.d.ts +32 -0
- package/dist/projections/modelBound/fromAllEvents.d.ts.map +1 -0
- package/dist/projections/modelBound/fromAllEvents.js +44 -0
- package/dist/projections/modelBound/fromAllEvents.js.map +1 -0
- package/dist/projections/modelBound/index.d.ts +2 -0
- package/dist/projections/modelBound/index.d.ts.map +1 -1
- package/dist/projections/modelBound/index.js +1 -0
- package/dist/projections/modelBound/index.js.map +1 -1
- package/dist/telemetry/ChronicleTelemetryOptions.d.ts +5 -4
- package/dist/telemetry/ChronicleTelemetryOptions.d.ts.map +1 -1
- package/dist/telemetry/observeOperation.d.ts +1 -2
- package/dist/telemetry/observeOperation.d.ts.map +1 -1
- package/dist/telemetry/observeOperation.js +2 -3
- package/dist/telemetry/observeOperation.js.map +1 -1
- package/dist/telemetry/spanAttributes.d.ts +4 -4
- package/dist/telemetry/spanAttributes.d.ts.map +1 -1
- package/dist/telemetry/spanAttributes.js +4 -7
- package/dist/telemetry/spanAttributes.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/modelBoundPropertyMetadata.js +1 -1
- package/dist/types/modelBoundPropertyMetadata.js.map +1 -1
- package/eventSequences/EventSequence.ts +31 -56
- package/for_ChronicleOptions/when_configuring_telemetry/with_span_names.spec.ts +21 -21
- package/package.json +2 -2
- package/projections/ProjectionDefinitionCompiler.ts +28 -21
- package/projections/for_ProjectionDefinitionCompiler/against_a_kernel/when_subscribing_with_from_all_events.integration.spec.ts +202 -0
- package/projections/for_ProjectionDefinitionCompiler/when_compiling_from_all_events.spec.ts +235 -0
- package/projections/for_ProjectionDefinitionCompiler/when_compiling_from_all_events.standard.spec.ts +107 -0
- package/projections/modelBound/FromAllEventsMetadata.ts +10 -0
- package/projections/modelBound/childrenAndNestedBuilder.ts +27 -8
- package/projections/modelBound/fromAll.ts +8 -3
- package/projections/modelBound/fromAllEvents.ts +48 -0
- package/projections/modelBound/index.ts +2 -0
- package/telemetry/ChronicleTelemetryOptions.ts +5 -4
- package/telemetry/for_ChronicleTelemetry/given/a_span_name_registry.fixture.ts +2 -2
- package/telemetry/for_ChronicleTelemetry/when_appending/{with_shared_and_legacy_attributes.spec.ts → with_canonical_attributes.spec.ts} +8 -9
- package/telemetry/for_ChronicleTelemetry/when_exposing_names/with_convention_names.spec.ts +65 -0
- package/telemetry/for_ChronicleTelemetry/when_naming_spans/{with_per_client_modes.spec.ts → with_convention_names.spec.ts} +45 -40
- package/telemetry/for_ChronicleTelemetry/when_recording_identifiers/with_per_client_privacy.spec.ts +4 -3
- package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_client_operations.spec.ts +102 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_failed_appends.spec.ts +47 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_millisecond_adapters.spec.ts +99 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_rejected_appends.spec.ts +88 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_product_attributes/with_convention_names.spec.ts +51 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_sequence_numbers/with_integer_precision.spec.ts +1 -1
- package/telemetry/observeOperation.ts +1 -4
- package/telemetry/spanAttributes.ts +5 -8
- package/types/modelBoundPropertyMetadata.ts +1 -1
- package/telemetry/for_ChronicleTelemetry/when_exposing_names/with_legacy_compatibility.spec.ts +0 -44
- package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_legacy_overlap.spec.ts +0 -40
- package/telemetry/for_ChronicleTelemetry/when_recording_product_attributes/with_both_naming_modes.spec.ts +0 -52
|
@@ -234,8 +234,13 @@ function discoverIdentifiedBy(childType: Function | undefined, eventKey: string
|
|
|
234
234
|
* `@childrenFrom`/`@nested` properties the child/nested type itself declares.
|
|
235
235
|
* @param definition - The children definition being populated.
|
|
236
236
|
* @param childType - The resolved child/nested type, when available.
|
|
237
|
+
* @param collectSharedMapping - Collects member mappings into the root's shared All definition.
|
|
237
238
|
*/
|
|
238
|
-
function populateFromType(
|
|
239
|
+
function populateFromType(
|
|
240
|
+
definition: ChildrenDefinitionLike,
|
|
241
|
+
childType: Function | undefined,
|
|
242
|
+
collectSharedMapping?: (prototype: object, property: string) => void
|
|
243
|
+
): void {
|
|
239
244
|
if (!childType) {
|
|
240
245
|
return;
|
|
241
246
|
}
|
|
@@ -275,7 +280,10 @@ function populateFromType(definition: ChildrenDefinitionLike, childType: Functio
|
|
|
275
280
|
}
|
|
276
281
|
|
|
277
282
|
const prototype = childType.prototype;
|
|
278
|
-
|
|
283
|
+
const properties = TypeIntrospector.getTrackedProperties(childType);
|
|
284
|
+
// Like .NET, collect this type's members before descending into its children.
|
|
285
|
+
for (const property of properties) collectSharedMapping?.(prototype, property);
|
|
286
|
+
for (const property of properties) {
|
|
279
287
|
applyPropertyMappings(prototype, property, fromByEventType);
|
|
280
288
|
|
|
281
289
|
// A clear on the member carrying @nested removes that nested object; a clear on
|
|
@@ -299,11 +307,11 @@ function populateFromType(definition: ChildrenDefinitionLike, childType: Functio
|
|
|
299
307
|
|
|
300
308
|
const childrenFromList = getChildrenFromMetadata(prototype, property);
|
|
301
309
|
if (childrenFromList.length > 0) {
|
|
302
|
-
definition.Children[property] = buildChildrenEntry(childType, property, childrenFromList);
|
|
310
|
+
definition.Children[property] = buildChildrenEntry(childType, property, childrenFromList, collectSharedMapping);
|
|
303
311
|
}
|
|
304
312
|
|
|
305
313
|
if (isNested(prototype, property)) {
|
|
306
|
-
definition.Nested[property] = buildNestedEntry(childType, property);
|
|
314
|
+
definition.Nested[property] = buildNestedEntry(childType, property, collectSharedMapping);
|
|
307
315
|
}
|
|
308
316
|
}
|
|
309
317
|
|
|
@@ -317,9 +325,15 @@ function populateFromType(definition: ChildrenDefinitionLike, childType: Functio
|
|
|
317
325
|
* @param type - The class declaring the property.
|
|
318
326
|
* @param property - The property name.
|
|
319
327
|
* @param metadataList - The `@childrenFrom` metadata entries declared on the property (one per creating event type).
|
|
328
|
+
* @param collectSharedMapping - Collects member mappings into the root's shared All definition.
|
|
320
329
|
* @returns The wire-shaped children definition.
|
|
321
330
|
*/
|
|
322
|
-
export function buildChildrenEntry(
|
|
331
|
+
export function buildChildrenEntry(
|
|
332
|
+
type: Function,
|
|
333
|
+
property: string,
|
|
334
|
+
metadataList: ChildrenFromMetadata[],
|
|
335
|
+
collectSharedMapping?: (prototype: object, property: string) => void
|
|
336
|
+
): ChildrenDefinitionLike {
|
|
323
337
|
const childType = resolveChildElementType(type, property);
|
|
324
338
|
const definition = createEmptyChildrenDefinition(type, childType);
|
|
325
339
|
|
|
@@ -338,7 +352,7 @@ export function buildChildrenEntry(type: Function, property: string, metadataLis
|
|
|
338
352
|
});
|
|
339
353
|
}
|
|
340
354
|
|
|
341
|
-
populateFromType(definition, childType);
|
|
355
|
+
populateFromType(definition, childType, collectSharedMapping);
|
|
342
356
|
|
|
343
357
|
// For non-aggregate-only creating events, populate an unmapped child identifier from
|
|
344
358
|
// the creating event's key. IdentifiedBy selects the child property; it is not itself
|
|
@@ -381,9 +395,14 @@ export function buildChildrenEntry(type: Function, property: string, metadataLis
|
|
|
381
395
|
* Builds the `ChildrenDefinition` for a `@nested` single-object property.
|
|
382
396
|
* @param type - The class declaring the property.
|
|
383
397
|
* @param property - The property name.
|
|
398
|
+
* @param collectSharedMapping - Collects member mappings into the root's shared All definition.
|
|
384
399
|
* @returns The wire-shaped nested definition.
|
|
385
400
|
*/
|
|
386
|
-
export function buildNestedEntry(
|
|
401
|
+
export function buildNestedEntry(
|
|
402
|
+
type: Function,
|
|
403
|
+
property: string,
|
|
404
|
+
collectSharedMapping?: (prototype: object, property: string) => void
|
|
405
|
+
): ChildrenDefinitionLike {
|
|
387
406
|
const nestedType = resolveNestedType(type, property);
|
|
388
407
|
const definition = createEmptyChildrenDefinition(type, nestedType);
|
|
389
408
|
definition.IdentifiedBy = notSetPropertyPath;
|
|
@@ -395,6 +414,6 @@ export function buildNestedEntry(type: Function, property: string): ChildrenDefi
|
|
|
395
414
|
definition.RemovedWith.push({ Key: eventType, Value: { Key: '$eventSourceId', ParentKey: '' } });
|
|
396
415
|
}
|
|
397
416
|
|
|
398
|
-
populateFromType(definition, nestedType);
|
|
417
|
+
populateFromType(definition, nestedType, collectSharedMapping);
|
|
399
418
|
return definition;
|
|
400
419
|
}
|
|
@@ -5,6 +5,7 @@ import 'reflect-metadata';
|
|
|
5
5
|
import { TypeIntrospector } from '../../types/index.js';
|
|
6
6
|
import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
|
|
7
7
|
import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
|
|
8
|
+
import { getFromAllEventsMetadata } from './fromAllEvents.js';
|
|
8
9
|
|
|
9
10
|
/** Metadata stored by the fromAll property decorator. */
|
|
10
11
|
export interface FromAllMetadata {
|
|
@@ -21,7 +22,9 @@ const METADATA_KEY = 'chronicle:projection:fromAll';
|
|
|
21
22
|
* event type the projection is built from - the convention-based counterpart to declaring the same
|
|
22
23
|
* mapping on every individual `fromEvent`. Equivalent to {@link fromEvery} for model-bound
|
|
23
24
|
* projections.
|
|
24
|
-
* @deprecated
|
|
25
|
+
* @deprecated @fromAll remains a supported, deprecated alias for restricted mappings; prefer @fromEvery.
|
|
26
|
+
* Contrary to the earlier 'next major' announcement, its behavior will not change;
|
|
27
|
+
* use @fromAllEvents to opt into all-event subscriptions.
|
|
25
28
|
* @param property - Optional event property name. If not specified, uses the model property name.
|
|
26
29
|
* @param contextProperty - Optional event context property name.
|
|
27
30
|
* @returns A property decorator.
|
|
@@ -35,11 +38,13 @@ export function fromAll(property?: string, contextProperty?: string): ChronicleP
|
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
|
-
* Retrieves fromAll metadata stored on the given property
|
|
41
|
+
* Retrieves fromAll metadata stored on the given property, falling back to fromAllEvents metadata
|
|
42
|
+
* for compatibility with existing model discovery. This reader does not imply an all-event subscription.
|
|
39
43
|
* @param target - The class prototype.
|
|
40
44
|
* @param propertyKey - The property name.
|
|
41
45
|
* @returns The fromAll metadata, or undefined if not decorated.
|
|
42
46
|
*/
|
|
43
47
|
export function getFromAllMetadata(target: object, propertyKey: string): FromAllMetadata | undefined {
|
|
44
|
-
return getPropertyMetadata<FromAllMetadata>(METADATA_KEY, target, propertyKey)
|
|
48
|
+
return getPropertyMetadata<FromAllMetadata>(METADATA_KEY, target, propertyKey)
|
|
49
|
+
?? getFromAllEventsMetadata(target, propertyKey);
|
|
45
50
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 'reflect-metadata';
|
|
5
|
+
import { TypeIntrospector } from '../../types/index.js';
|
|
6
|
+
import { decorateModelBoundProperty } from '../../types/modelBoundProperty.js';
|
|
7
|
+
import { ChroniclePropertyDecorator, getPropertyMetadata } from '../../types/propertyDecoratorMetadata.js';
|
|
8
|
+
import type { FromAllEventsMetadata } from './FromAllEventsMetadata.js';
|
|
9
|
+
|
|
10
|
+
const METADATA_KEY = 'chronicle:projection:fromAllEvents';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* On a root property, subscribes the projection to every event type, including events not declared
|
|
14
|
+
* with `fromEvent`. The model has one shared All block: opting in makes every root
|
|
15
|
+
* `@fromEvery`/`@fromAll` mapping (including inherited ones) an all-event mapping that also applies
|
|
16
|
+
* to children. Restricted and all-event shared mappings cannot be mixed in one model.
|
|
17
|
+
* `@fromEvery`/`@fromAll` mappings on child or nested types are not collected, unlike .NET,
|
|
18
|
+
* which also collects child [FromEvery] mappings.
|
|
19
|
+
* On a child or nested member, contributes its bare property name to the root All block and sets
|
|
20
|
+
* IncludeChildren for the shared block, including the root's restricted shared mappings, without
|
|
21
|
+
* widening the root subscription. With kernel 19.26.2, when the child's creating event is explicitly
|
|
22
|
+
* subscribed at the root, it updates the root's shared mappings and creates the child without
|
|
23
|
+
* populating its shared mapped values. Opt into `@fromAllEvents` at the root to populate child values
|
|
24
|
+
* in this case. Takes precedence over `fromEvery` and the deprecated `fromAll` alias on the same property.
|
|
25
|
+
* Opting in changes the definition and may trigger replay; unrelated events can create rows or clear
|
|
26
|
+
* mapped properties. Use `fromEvery` without `fromAllEvents` on the model or its children to keep
|
|
27
|
+
* shared mappings restricted to already-subscribed events and the root.
|
|
28
|
+
* @param property - Optional event property name. If not specified, uses the model property name.
|
|
29
|
+
* @param contextProperty - Optional event context property name.
|
|
30
|
+
* @returns A property decorator.
|
|
31
|
+
*/
|
|
32
|
+
export function fromAllEvents(property?: string, contextProperty?: string): ChroniclePropertyDecorator {
|
|
33
|
+
return decorateModelBoundProperty((target: object, propertyKey: string | symbol) => {
|
|
34
|
+
TypeIntrospector.trackProperty((target as { constructor: Function }).constructor, propertyKey.toString());
|
|
35
|
+
const metadata: FromAllEventsMetadata = { property, contextProperty };
|
|
36
|
+
Reflect.defineMetadata(METADATA_KEY, metadata, target, propertyKey.toString());
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Retrieves the explicit all-event subscription metadata stored on the given property.
|
|
42
|
+
* @param target - The class prototype.
|
|
43
|
+
* @param propertyKey - The property name.
|
|
44
|
+
* @returns The fromAllEvents metadata, or undefined if not decorated.
|
|
45
|
+
*/
|
|
46
|
+
export function getFromAllEventsMetadata(target: object, propertyKey: string): FromAllEventsMetadata | undefined {
|
|
47
|
+
return getPropertyMetadata<FromAllEventsMetadata>(METADATA_KEY, target, propertyKey);
|
|
48
|
+
}
|
|
@@ -37,6 +37,8 @@ export { fromEvery, getFromEveryMetadata } from './fromEvery.js';
|
|
|
37
37
|
export type { FromEveryMetadata } from './fromEvery.js';
|
|
38
38
|
export { fromAll, getFromAllMetadata } from './fromAll.js';
|
|
39
39
|
export type { FromAllMetadata } from './fromAll.js';
|
|
40
|
+
export { fromAllEvents, getFromAllEventsMetadata } from './fromAllEvents.js';
|
|
41
|
+
export type { FromAllEventsMetadata } from './FromAllEventsMetadata.js';
|
|
40
42
|
export { noAutoMap, isNoAutoMap, isPropertyNoAutoMap } from './noAutoMap.js';
|
|
41
43
|
export { eventSequence, eventLog, getEventSequenceMetadata } from './eventSequence.js';
|
|
42
44
|
export { variantOf, getVariantOfMetadata } from './variantOf.js';
|
|
@@ -1,12 +1,13 @@
|
|
|
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
|
-
/** Per-client
|
|
4
|
+
/** Per-client privacy and compatibility settings; instrumentation itself is always available. */
|
|
5
5
|
export interface ChronicleTelemetryOptions {
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Retained naming selector. Both 'legacy' and 'convention' are accepted as no-ops;
|
|
8
|
+
* built-in instrumentation always uses conventionSpans, including when this option is absent.
|
|
9
|
+
* @deprecated No longer needed; built-in spans always use convention names. This option has no removal deadline.
|
|
10
|
+
* The minor-release naming cutoff replaces the earlier "next major" plan after ADR 0001's one-minor overlap.
|
|
10
11
|
* See https://github.com/Cratis/Chronicle.TypeScript/issues/171.
|
|
11
12
|
*/
|
|
12
13
|
spanNames?: 'legacy' | 'convention';
|
|
@@ -15,7 +15,7 @@ export const legacySpans = {
|
|
|
15
15
|
getEventStore: 'chronicle.client.get_event_store',
|
|
16
16
|
getEventStores: 'chronicle.client.get_event_stores',
|
|
17
17
|
getNamespaces: 'chronicle.event_store.get_namespaces'
|
|
18
|
-
};
|
|
18
|
+
} as const;
|
|
19
19
|
|
|
20
20
|
export const conventionSpans = {
|
|
21
21
|
append: 'cratis.chronicle.client.event_sequence.append',
|
|
@@ -30,4 +30,4 @@ export const conventionSpans = {
|
|
|
30
30
|
getEventStore: 'cratis.chronicle.client.event_store.get',
|
|
31
31
|
getEventStores: 'cratis.chronicle.client.event_store.list',
|
|
32
32
|
getNamespaces: 'cratis.chronicle.client.event_store.get_namespaces'
|
|
33
|
-
};
|
|
33
|
+
} as const;
|
|
@@ -26,24 +26,23 @@ describe('when appending with a business correlation override', () => {
|
|
|
26
26
|
await correlationIdManager.run(new CorrelationId('outer'), () => sequence.append('sensitive-id', new Recorded(), { correlationId: correlation }));
|
|
27
27
|
span = telemetry.spans.getFinishedSpans()[0];
|
|
28
28
|
});
|
|
29
|
-
it('should produce one client span with the
|
|
29
|
+
it('should produce one client span with the convention operation name', () => {
|
|
30
30
|
telemetry.spans.getFinishedSpans().should.have.lengthOf(1);
|
|
31
31
|
span.kind.should.equal(SpanKind.CLIENT);
|
|
32
|
-
span.name.should.equal('chronicle.
|
|
32
|
+
span.name.should.equal('cratis.chronicle.client.event_sequence.append');
|
|
33
33
|
});
|
|
34
34
|
it('should use the versioned shared instrumentation scope', () => {
|
|
35
35
|
span.instrumentationScope.name.should.equal('Cratis.Chronicle.Client');
|
|
36
36
|
should.equal(span.instrumentationScope.version, clientVersion);
|
|
37
37
|
});
|
|
38
|
-
it('should emit
|
|
38
|
+
it('should emit canonical attributes with the exact-string sequence number exception', () => {
|
|
39
39
|
span.attributes.should.include({
|
|
40
|
-
'cratis.event_store.name': 'store', '
|
|
41
|
-
'cratis.
|
|
42
|
-
'cratis.
|
|
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'
|
|
40
|
+
'cratis.event_store.name': 'store', 'cratis.event_store.namespace': 'namespace',
|
|
41
|
+
'cratis.event_sequence.id': 'event-log', 'cratis.event_type.id': 'telemetry-recorded',
|
|
42
|
+
'cratis.event_type.generation': 1, 'cratis.event_sequence.number': 42
|
|
46
43
|
});
|
|
44
|
+
Object.keys(span.attributes).filter(key => key.startsWith('chronicle.')).should.deep.equal(['chronicle.sequence_number']);
|
|
45
|
+
span.attributes.should.have.property('chronicle.sequence_number', '42');
|
|
47
46
|
});
|
|
48
47
|
it('should scope the resolved correlation consistently for propagation', () => {
|
|
49
48
|
should.equal(span.attributes['cratis.correlation_id'], correlation);
|
|
@@ -0,0 +1,65 @@
|
|
|
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, expectTypeOf, it } from 'vitest';
|
|
5
|
+
import { ChronicleOptions, ChronicleMetrics, ChronicleConventionMetrics, WellKnownTelemetryNames as names } from '../../../index.js';
|
|
6
|
+
import type { ChronicleTelemetryOptions } from '../../../index.js';
|
|
7
|
+
import { legacySpans, conventionSpans } from '../given/a_span_name_registry.fixture.js';
|
|
8
|
+
|
|
9
|
+
const legacyAttributes = {
|
|
10
|
+
eventStore: 'chronicle.event_store', namespace: 'chronicle.namespace',
|
|
11
|
+
eventSequenceId: 'chronicle.event_sequence_id', sequenceNumber: 'chronicle.sequence_number',
|
|
12
|
+
eventTypeId: 'chronicle.event_type_id', eventTypeGeneration: 'chronicle.event_type_generation',
|
|
13
|
+
eventSourceId: 'chronicle.event_source_id', eventCount: 'chronicle.events_count',
|
|
14
|
+
hasEvents: 'chronicle.has_events', eventStreamType: 'chronicle.event_stream_type', eventStreamId: 'chronicle.event_stream_id'
|
|
15
|
+
} as const;
|
|
16
|
+
const legacyMetrics = {
|
|
17
|
+
eventsAppended: 'chronicle.events.appended', batchAppendsPerformed: 'chronicle.events.batch_appends',
|
|
18
|
+
eventStoreRetrievals: 'chronicle.client.event_store_retrievals', appendDuration: 'chronicle.events.append_duration',
|
|
19
|
+
appendManyDuration: 'chronicle.events.append_many_duration', constraintViolations: 'chronicle.events.constraint_violations',
|
|
20
|
+
appendErrors: 'chronicle.events.append_errors'
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
chai.should();
|
|
24
|
+
describe('when exposing canonical names alongside retained compatibility APIs', () => {
|
|
25
|
+
it('should preserve every legacy span value and literal type', () => {
|
|
26
|
+
names.spans.should.deep.equal(legacySpans);
|
|
27
|
+
expectTypeOf(names.spans).toEqualTypeOf<typeof legacySpans>();
|
|
28
|
+
});
|
|
29
|
+
it('should expose a matching canonical span for every operation', () => {
|
|
30
|
+
names.conventionSpans.should.deep.equal(conventionSpans);
|
|
31
|
+
expectTypeOf(names.conventionSpans).toEqualTypeOf<typeof conventionSpans>();
|
|
32
|
+
Object.keys(names.conventionSpans).should.deep.equal(Object.keys(names.spans));
|
|
33
|
+
});
|
|
34
|
+
it('should preserve the current and historical scopes', () => {
|
|
35
|
+
names.scope.should.equal('Cratis.Chronicle.Client');
|
|
36
|
+
names.legacyScope.should.equal('@cratis/chronicle');
|
|
37
|
+
expectTypeOf(names.legacyScope).toEqualTypeOf<'@cratis/chronicle'>();
|
|
38
|
+
});
|
|
39
|
+
it('should preserve every legacy attribute value and literal type', () => {
|
|
40
|
+
names.legacyAttributes.should.deep.equal(legacyAttributes);
|
|
41
|
+
expectTypeOf(names.legacyAttributes).toEqualTypeOf<typeof legacyAttributes>();
|
|
42
|
+
});
|
|
43
|
+
it('should preserve every legacy metric value and literal type', () => {
|
|
44
|
+
names.legacyMetrics.should.deep.equal(legacyMetrics);
|
|
45
|
+
expectTypeOf(names.legacyMetrics).toEqualTypeOf<typeof legacyMetrics>();
|
|
46
|
+
});
|
|
47
|
+
it('should retain both typed selector values through the public factories', () => {
|
|
48
|
+
expectTypeOf<ChronicleTelemetryOptions['spanNames']>().toEqualTypeOf<'legacy' | 'convention' | undefined>();
|
|
49
|
+
for (const spanNames of ['legacy', 'convention'] as const) {
|
|
50
|
+
ChronicleOptions.development({ telemetry: { spanNames } }).telemetry!.spanNames!.should.equal(spanNames);
|
|
51
|
+
ChronicleOptions.fromConnectionString('chronicle://localhost:35000', { telemetry: { spanNames } })
|
|
52
|
+
.telemetry!.spanNames!.should.equal(spanNames);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
it('should retain the public metric recording signatures', () => {
|
|
56
|
+
expectTypeOf(ChronicleMetrics).toEqualTypeOf<typeof ChronicleConventionMetrics>();
|
|
57
|
+
Object.keys(ChronicleMetrics).should.have.members(Object.keys(names.legacyMetrics));
|
|
58
|
+
});
|
|
59
|
+
it('should retain the product-only attributes alongside the shared registry', () => {
|
|
60
|
+
names.attributes.should.include({
|
|
61
|
+
hasEvents: 'cratis.chronicle.event_sequence.has_events',
|
|
62
|
+
eventStreamType: 'cratis.chronicle.event_stream.type', eventStreamId: 'cratis.chronicle.event_stream.id'
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -6,6 +6,7 @@ import { SpanKind } from '@opentelemetry/api';
|
|
|
6
6
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
7
7
|
import type { ChronicleClient } from '../../../ChronicleClient.js';
|
|
8
8
|
import type { IEventSequence } from '../../../eventSequences/IEventSequence.js';
|
|
9
|
+
import type { ChronicleTelemetryOptions } from '../../ChronicleTelemetryOptions.js';
|
|
9
10
|
import { EventSequenceId } from '../../../eventSequences/EventSequenceId.js';
|
|
10
11
|
import { EventSequenceNumber } from '../../../eventSequences/EventSequenceNumber.js';
|
|
11
12
|
import { clientVersion } from '../../../connection/clientVersion.js';
|
|
@@ -30,14 +31,14 @@ const operations = {
|
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
for (const spanNames of [undefined, 'legacy', 'convention'] as const) {
|
|
33
|
-
const
|
|
34
|
+
const options: ChronicleTelemetryOptions | undefined = spanNames === undefined ? undefined : { spanNames };
|
|
34
35
|
for (const sequenceId of [EventSequenceId.eventLog, new EventSequenceId('dynamic')]) {
|
|
35
36
|
for (const operation of Object.keys(operations) as (keyof typeof operations)[]) {
|
|
36
|
-
describe(`when naming ${operation} on ${sequenceId.value} with ${spanNames ?? 'default'}
|
|
37
|
+
describe(`when naming ${operation} on ${sequenceId.value} with ${spanNames ?? 'default'} configuration`, () => {
|
|
37
38
|
let instance: ChronicleClient;
|
|
38
39
|
let span: ReadableSpan;
|
|
39
40
|
beforeEach(async () => {
|
|
40
|
-
instance = client(
|
|
41
|
+
instance = client(options);
|
|
41
42
|
const store = await instance.getEventStore('store', 'namespace');
|
|
42
43
|
const sequence = sequenceId === EventSequenceId.eventLog ? store.eventLog : store.getEventSequence(sequenceId);
|
|
43
44
|
telemetry.spans.reset();
|
|
@@ -45,32 +46,35 @@ for (const spanNames of [undefined, 'legacy', 'convention'] as const) {
|
|
|
45
46
|
span = telemetry.spans.getFinishedSpans()[0];
|
|
46
47
|
});
|
|
47
48
|
afterEach(() => instance.dispose());
|
|
48
|
-
it('should emit exactly one client span
|
|
49
|
+
it('should emit exactly one convention client span and no legacy span', () => {
|
|
49
50
|
telemetry.spans.getFinishedSpans().should.have.lengthOf(1);
|
|
50
|
-
span.name.should.equal(
|
|
51
|
+
span.name.should.equal(conventionSpans[operation]).and.not.equal(legacySpans[operation]);
|
|
51
52
|
span.kind.should.equal(SpanKind.CLIENT);
|
|
52
53
|
});
|
|
53
54
|
it('should retain the versioned shared instrumentation scope', () => {
|
|
54
55
|
span.instrumentationScope.name.should.equal('Cratis.Chronicle.Client');
|
|
55
56
|
should.equal(span.instrumentationScope.version, clientVersion);
|
|
56
57
|
});
|
|
57
|
-
it('should
|
|
58
|
+
it('should emit canonical attributes with only the exact-string sequence number exception', () => {
|
|
58
59
|
span.attributes.should.include({
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'chronicle.event_sequence_id': sequenceId.value, 'cratis.event_sequence.id': sequenceId.value
|
|
60
|
+
'cratis.event_store.name': 'store', 'cratis.event_store.namespace': 'namespace',
|
|
61
|
+
'cratis.event_sequence.id': sequenceId.value
|
|
62
62
|
});
|
|
63
|
+
const hasSequenceNumber = ['append', 'getTailSequenceNumber', 'getFromSequenceNumber', 'redact'].includes(operation);
|
|
64
|
+
Object.keys(span.attributes).filter(key => key.startsWith('chronicle.')).should.deep.equal(
|
|
65
|
+
hasSequenceNumber ? ['chronicle.sequence_number'] : []);
|
|
66
|
+
if (hasSequenceNumber) span.attributes.should.have.property('chronicle.sequence_number', '42');
|
|
63
67
|
});
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
for (const operation of ['getEventStore', 'getEventStores', 'getNamespaces'] as const) {
|
|
69
|
-
describe(`when naming ${operation} with ${spanNames ?? 'default'}
|
|
73
|
+
describe(`when naming ${operation} with ${spanNames ?? 'default'} configuration`, () => {
|
|
70
74
|
let instance: ChronicleClient;
|
|
71
75
|
let span: ReadableSpan;
|
|
72
76
|
beforeEach(async () => {
|
|
73
|
-
instance = client(
|
|
77
|
+
instance = client(options);
|
|
74
78
|
if (operation === 'getNamespaces') {
|
|
75
79
|
const store = await instance.getEventStore('store', 'namespace');
|
|
76
80
|
telemetry.spans.reset();
|
|
@@ -83,56 +87,57 @@ for (const spanNames of [undefined, 'legacy', 'convention'] as const) {
|
|
|
83
87
|
span = telemetry.spans.getFinishedSpans()[0];
|
|
84
88
|
});
|
|
85
89
|
afterEach(() => instance.dispose());
|
|
86
|
-
it('should emit exactly one client span
|
|
90
|
+
it('should emit exactly one convention client span and no legacy span', () => {
|
|
87
91
|
telemetry.spans.getFinishedSpans().should.have.lengthOf(1);
|
|
88
|
-
span.name.should.equal(
|
|
92
|
+
span.name.should.equal(conventionSpans[operation]).and.not.equal(legacySpans[operation]);
|
|
89
93
|
span.kind.should.equal(SpanKind.CLIENT);
|
|
90
94
|
span.instrumentationScope.name.should.equal('Cratis.Chronicle.Client');
|
|
91
95
|
should.equal(span.instrumentationScope.version, clientVersion);
|
|
92
96
|
});
|
|
97
|
+
it('should not emit legacy attributes', () => {
|
|
98
|
+
Object.keys(span.attributes).filter(key => key.startsWith('chronicle.')).should.be.empty;
|
|
99
|
+
});
|
|
93
100
|
if (operation !== 'getEventStores') {
|
|
94
|
-
it('should retain
|
|
95
|
-
span.attributes.should.include({ '
|
|
101
|
+
it('should retain the canonical event store attribute', () => {
|
|
102
|
+
span.attributes.should.include({ 'cratis.event_store.name': 'store' });
|
|
96
103
|
});
|
|
97
104
|
}
|
|
98
105
|
});
|
|
99
106
|
}
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
describe('when
|
|
109
|
+
describe('when legacy and convention configurations coexist in one process', () => {
|
|
103
110
|
let legacy: ChronicleClient;
|
|
104
111
|
let convention: ChronicleClient;
|
|
105
|
-
let spans: ReadableSpan[];
|
|
106
112
|
beforeEach(async () => {
|
|
107
|
-
legacy = client({ spanNames: 'legacy' });
|
|
113
|
+
legacy = client({ spanNames: 'legacy', eventSourceId: { mode: 'raw' } });
|
|
108
114
|
convention = client({ spanNames: 'convention' });
|
|
109
115
|
const [legacyStore, conventionStore] = await Promise.all([
|
|
110
|
-
legacy.getEventStore('legacy-
|
|
116
|
+
legacy.getEventStore('legacy-client'), convention.getEventStore('convention-client')
|
|
117
|
+
]);
|
|
118
|
+
telemetry.spans.reset();
|
|
119
|
+
await Promise.all([
|
|
120
|
+
legacyStore.eventLog.append('legacy-source', new Recorded()),
|
|
121
|
+
conventionStore.eventLog.append('convention-source', new Recorded())
|
|
111
122
|
]);
|
|
112
|
-
await Promise.all([convention.getEventStores(), legacy.getEventStores()]);
|
|
113
|
-
await Promise.all([legacyStore.getNamespaces(), conventionStore.getNamespaces()]);
|
|
114
|
-
await conventionStore.eventLog.append('source', new Recorded());
|
|
115
|
-
await legacyStore.eventLog.append('source', new Recorded());
|
|
116
|
-
const id = new EventSequenceId('dynamic');
|
|
117
|
-
await legacyStore.getEventSequence(id).append('source', new Recorded());
|
|
118
|
-
await conventionStore.getEventSequence(id).append('source', new Recorded());
|
|
119
|
-
await legacy.getEventStore('legacy-store');
|
|
120
|
-
await convention.getEventStore('convention-store');
|
|
121
|
-
spans = telemetry.spans.getFinishedSpans();
|
|
122
123
|
});
|
|
123
124
|
afterEach(() => { legacy.dispose(); convention.dispose(); });
|
|
124
|
-
it('should
|
|
125
|
-
spans.should.
|
|
126
|
-
|
|
127
|
-
legacySpans.getEventStore, legacySpans.getNamespaces, legacySpans.append, legacySpans.append, legacySpans.getEventStore
|
|
125
|
+
it('should emit one canonical span per operation regardless of the retained selector', () => {
|
|
126
|
+
telemetry.spans.getFinishedSpans().map(span => span.name).should.deep.equal([
|
|
127
|
+
conventionSpans.append, conventionSpans.append
|
|
128
128
|
]);
|
|
129
|
-
spans.filter(span => span.attributes['cratis.event_store.name'] === 'convention-store').map(span => span.name).should.deep.equal([
|
|
130
|
-
conventionSpans.getEventStore, conventionSpans.getNamespaces, conventionSpans.append, conventionSpans.append, conventionSpans.getEventStore
|
|
131
|
-
]);
|
|
132
|
-
spans.map(span => span.name).should.include(legacySpans.getEventStores).and.include(conventionSpans.getEventStores);
|
|
133
129
|
});
|
|
134
|
-
it('should
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
it('should keep privacy local to each client', () => {
|
|
131
|
+
const spans = telemetry.spans.getFinishedSpans();
|
|
132
|
+
spans.find(span => span.attributes['cratis.event_store.name'] === 'legacy-client')!
|
|
133
|
+
.attributes.should.have.property('cratis.event_source.id', 'legacy-source');
|
|
134
|
+
spans.find(span => span.attributes['cratis.event_store.name'] === 'convention-client')!
|
|
135
|
+
.attributes.should.not.have.property('cratis.event_source.id');
|
|
136
|
+
});
|
|
137
|
+
it('should not restore legacy metric instruments for either client', async () => {
|
|
138
|
+
const scopes = (await telemetry.reader.collect()).resourceMetrics.scopeMetrics;
|
|
139
|
+
scopes.should.have.lengthOf(1);
|
|
140
|
+
scopes[0].metrics.should.not.be.empty;
|
|
141
|
+
scopes[0].metrics.filter(metric => metric.descriptor.name.startsWith('chronicle.')).should.be.empty;
|
|
137
142
|
});
|
|
138
143
|
});
|
package/telemetry/for_ChronicleTelemetry/when_recording_identifiers/with_per_client_privacy.spec.ts
CHANGED
|
@@ -32,11 +32,11 @@ for (const [label, policy, expected] of [
|
|
|
32
32
|
await sequence.redactForEventSource(source, 'private reason');
|
|
33
33
|
services.append.mock.calls[0][0].EventSourceId.should.equal(source);
|
|
34
34
|
});
|
|
35
|
-
it('should apply the policy
|
|
35
|
+
it('should apply the policy only to the canonical name on every operation', () => {
|
|
36
36
|
telemetry.spans.getFinishedSpans().should.have.lengthOf(8);
|
|
37
37
|
for (const span of telemetry.spans.getFinishedSpans()) {
|
|
38
38
|
should.equal(span.attributes['cratis.event_source.id'], expected);
|
|
39
|
-
should.equal(span.attributes['chronicle.event_source_id'],
|
|
39
|
+
should.equal(span.attributes['chronicle.event_source_id'], undefined);
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
});
|
|
@@ -71,7 +71,8 @@ describe('when appending a batch with multiple event sources', () => {
|
|
|
71
71
|
});
|
|
72
72
|
it('should record the count without an identifier array', () => {
|
|
73
73
|
const attributes = telemetry.spans.getFinishedSpans()[0].attributes;
|
|
74
|
-
attributes.should.
|
|
74
|
+
attributes.should.have.property('cratis.event.count', 2);
|
|
75
|
+
attributes.should.not.have.property('chronicle.events_count');
|
|
75
76
|
should.equal(attributes['cratis.event_source.id'], undefined);
|
|
76
77
|
should.equal(attributes['chronicle.event_source_id'], undefined);
|
|
77
78
|
});
|
package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_client_operations.spec.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { beforeAll, chai, describe, it, vi } from 'vitest';
|
|
5
|
+
import type { ScopeMetrics } from '@opentelemetry/sdk-metrics';
|
|
6
|
+
import { telemetrySession } from '../given/a_telemetry_session.fixture.js';
|
|
7
|
+
import { client } from '../given/a_client.fixture.js';
|
|
8
|
+
import { eventSequence, Recorded } from '../given/an_event_sequence.fixture.js';
|
|
9
|
+
import { WellKnownTelemetryNames as names } from '../../../WellKnownTelemetryNames.js';
|
|
10
|
+
import { clientVersion } from '../../../connection/clientVersion.js';
|
|
11
|
+
|
|
12
|
+
chai.should();
|
|
13
|
+
const telemetry = telemetrySession();
|
|
14
|
+
describe('when recording metrics from successful client operations', () => {
|
|
15
|
+
let scopes: ScopeMetrics[];
|
|
16
|
+
let scope: ScopeMetrics;
|
|
17
|
+
const sequenceAttributes = {
|
|
18
|
+
'cratis.event_store.name': 'store', 'cratis.event_store.namespace': 'namespace',
|
|
19
|
+
'cratis.event_sequence.id': 'event-log'
|
|
20
|
+
};
|
|
21
|
+
const eventAttributes = { ...sequenceAttributes, 'cratis.event_type.id': 'telemetry-recorded' };
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
const instance = client();
|
|
25
|
+
const { sequence, services } = eventSequence();
|
|
26
|
+
let milliseconds = 1000;
|
|
27
|
+
const clock = vi.spyOn(performance, 'now').mockImplementation(() => milliseconds);
|
|
28
|
+
services.append.mockImplementation(async () => {
|
|
29
|
+
milliseconds += 250;
|
|
30
|
+
return { Response: { SequenceNumber: 42n } };
|
|
31
|
+
});
|
|
32
|
+
services.appendManyForEventSources.mockImplementation(async () => {
|
|
33
|
+
milliseconds += 1250;
|
|
34
|
+
return { Response: { SequenceNumbers: [43n, 44n] } };
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
await instance.getEventStore('store', 'namespace');
|
|
38
|
+
await sequence.append('private-source', new Recorded());
|
|
39
|
+
await sequence.appendMany('private-source', [new Recorded(), new Recorded()]);
|
|
40
|
+
scopes = (await telemetry.reader.collect()).resourceMetrics.scopeMetrics;
|
|
41
|
+
scope = scopes[0];
|
|
42
|
+
} finally {
|
|
43
|
+
clock.mockRestore();
|
|
44
|
+
instance.dispose();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should use the canonical scope and installed client version', () => {
|
|
49
|
+
scopes.should.have.lengthOf(1);
|
|
50
|
+
scope.scope.should.include({ name: 'Cratis.Chronicle.Client', version: clientVersion });
|
|
51
|
+
});
|
|
52
|
+
it('should emit only the expected canonical instruments with no legacy names', () => {
|
|
53
|
+
scope.metrics.map(metric => metric.descriptor.name).should.have.members([
|
|
54
|
+
names.metrics.eventsAppended, names.metrics.batchAppendsPerformed, names.metrics.eventStoreRetrievals,
|
|
55
|
+
names.metrics.appendDuration, names.metrics.appendManyDuration
|
|
56
|
+
]);
|
|
57
|
+
scope.metrics.filter(metric => metric.descriptor.name.startsWith('chronicle.')).should.be.empty;
|
|
58
|
+
});
|
|
59
|
+
it('should record the single append duration in seconds exactly once', () => {
|
|
60
|
+
const metric = scope.metrics.find(metric => metric.descriptor.name === names.metrics.appendDuration)!;
|
|
61
|
+
metric.descriptor.unit.should.equal('s');
|
|
62
|
+
metric.dataPoints.should.have.lengthOf(1);
|
|
63
|
+
metric.dataPoints[0].attributes.should.deep.equal(eventAttributes);
|
|
64
|
+
metric.dataPoints[0].value.should.include({ min: 0.25, max: 0.25, sum: 0.25, count: 1 });
|
|
65
|
+
});
|
|
66
|
+
it('should record the batch append duration in seconds exactly once', () => {
|
|
67
|
+
const metric = scope.metrics.find(metric => metric.descriptor.name === names.metrics.appendManyDuration)!;
|
|
68
|
+
metric.descriptor.unit.should.equal('s');
|
|
69
|
+
metric.dataPoints.should.have.lengthOf(1);
|
|
70
|
+
metric.dataPoints[0].attributes.should.deep.equal(sequenceAttributes);
|
|
71
|
+
metric.dataPoints[0].value.should.include({ min: 1.25, max: 1.25, sum: 1.25, count: 1 });
|
|
72
|
+
});
|
|
73
|
+
it('should count individual events with the dimensions known to each append path', () => {
|
|
74
|
+
const metric = scope.metrics.find(metric => metric.descriptor.name === names.metrics.eventsAppended)!;
|
|
75
|
+
metric.dataPoints.map(point => ({ attributes: point.attributes, value: point.value })).should.have.deep.members([
|
|
76
|
+
{ attributes: eventAttributes, value: 1 }, { attributes: sequenceAttributes, value: 2 }
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
it('should count the batch without a batch-size dimension', () => {
|
|
80
|
+
const metric = scope.metrics.find(metric => metric.descriptor.name === names.metrics.batchAppendsPerformed)!;
|
|
81
|
+
metric.dataPoints.should.have.lengthOf(1);
|
|
82
|
+
metric.dataPoints[0].attributes.should.deep.equal(sequenceAttributes);
|
|
83
|
+
metric.dataPoints[0].value.should.equal(1);
|
|
84
|
+
});
|
|
85
|
+
it('should count the event-store retrieval with only store and namespace dimensions', () => {
|
|
86
|
+
const metric = scope.metrics.find(metric => metric.descriptor.name === names.metrics.eventStoreRetrievals)!;
|
|
87
|
+
metric.dataPoints.should.have.lengthOf(1);
|
|
88
|
+
metric.dataPoints[0].attributes.should.deep.equal({
|
|
89
|
+
'cratis.event_store.name': 'store', 'cratis.event_store.namespace': 'namespace'
|
|
90
|
+
});
|
|
91
|
+
metric.dataPoints[0].value.should.equal(1);
|
|
92
|
+
});
|
|
93
|
+
it('should exclude legacy, batch-size and sensitive dimensions from every measurement', () => {
|
|
94
|
+
for (const metric of scope.metrics) {
|
|
95
|
+
for (const point of metric.dataPoints) {
|
|
96
|
+
Object.keys(point.attributes).filter(key => ![
|
|
97
|
+
'cratis.event_store.name', 'cratis.event_store.namespace', 'cratis.event_sequence.id', 'cratis.event_type.id'
|
|
98
|
+
].includes(key)).should.be.empty;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|