[monitor-opentelemetry] Live Metrics Filtering for Documents (#31312)

### Packages impacted by this PR
monitor-opentelemetry

### Describe the problem that is addressed by this PR
This PR adds support for document filtering in live metrics.

### Are there test cases added in this PR? _(If not, why?)_
Modifications were made to existing unit tests to test documents
functionality, as much of the new code added uses existing filtering
functionality. Also, many cases for documents were manually tested E2E,
such as the single/multi session cases for applying filters to documents
and metrics & confirming that SDK conforms to any configuration sent to
it by the service.

### Checklists
- [x] Added impacted package name to the issue description
- [ ] Does this PR needs any fixes in the SDK Generator?** _(If so,
create an Issue in the
[Autorest/typescript](https://github.com/Azure/autorest.typescript)
repository and link it here)_
- [x] Added a changelog (if necessary)
This commit is contained in:
Harsimar Kaur 2024-10-11 13:31:10 -07:00 коммит произвёл GitHub
Родитель acdcea72ef
Коммит e27c896af2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 283 добавлений и 45 удалений

Просмотреть файл

@ -3,6 +3,7 @@
## 1.7.2 (Unreleased)
### Features Added
- Support for Live Metrics Filtering
### Breaking Changes

Просмотреть файл

@ -45,18 +45,17 @@ export class Filter {
// Just to be safe, handling the multiple filter conjunction group case as an or operation.
let matched = false;
derivedMetricInfo.filterGroups.forEach((filterConjunctionGroup) => {
matched = matched || this.checkFilterConjunctionGroup(filterConjunctionGroup.filters, data);
matched = matched || this.checkFilterConjunctionGroup(filterConjunctionGroup, data);
});
return matched;
}
/* public static checkDocumentFilters(documentStreamInfo: DocumentStreamInfo, data: TelemetryData): boolean {
return true; // to be implemented
}*/
private checkFilterConjunctionGroup(filters: FilterInfo[], data: TelemetryData): boolean {
public checkFilterConjunctionGroup(
filterConjunctionGroupInfo: FilterConjunctionGroupInfo,
data: TelemetryData,
): boolean {
// All of the filters need to match for this to return true (and operation).
for (const filter of filters) {
for (const filter of filterConjunctionGroupInfo.filters) {
if (!this.checkFilter(filter, data)) {
return false;
}

Просмотреть файл

@ -8,6 +8,8 @@ import {
KnownTelemetryType,
FilterInfo,
KnownPredicateType,
DocumentFilterConjunctionGroupInfo,
FilterConjunctionGroupInfo,
} from "../../../generated";
import { getMsFromFilterTimestampString } from "../utils";
@ -23,23 +25,21 @@ const knownStringColumns = new Set<string>([
]);
export class Validator {
public validateTelemetryType(derivedMetricInfo: DerivedMetricInfo): void {
if (derivedMetricInfo.telemetryType === KnownTelemetryType.PerformanceCounter.toString()) {
public validateTelemetryType(telemetryType: string): void {
if (telemetryType === KnownTelemetryType.PerformanceCounter.toString()) {
throw new TelemetryTypeError(
"The telemetry type PerformanceCounter was specified, but this distro does not send performance counters to quickpulse.",
);
} else if (derivedMetricInfo.telemetryType === KnownTelemetryType.Event.toString()) {
} else if (telemetryType === KnownTelemetryType.Event.toString()) {
throw new TelemetryTypeError(
"The telemetry type Event was specified, but this telemetry type is not supported via OpenTelemetry.",
);
} else if (derivedMetricInfo.telemetryType === KnownTelemetryType.Metric.toString()) {
} else if (telemetryType === KnownTelemetryType.Metric.toString()) {
throw new TelemetryTypeError(
"The telemetry type Metric was specified, but this distro does not send custom live metrics to quickpulse.",
);
} else if (!(derivedMetricInfo.telemetryType in KnownTelemetryType)) {
throw new TelemetryTypeError(
`'${derivedMetricInfo.telemetryType}' is not a valid telemetry type.`,
);
} else if (!(telemetryType in KnownTelemetryType)) {
throw new TelemetryTypeError(`'${telemetryType}' is not a valid telemetry type.`);
}
}
@ -51,7 +51,7 @@ export class Validator {
}
}
public validateFilters(derivedMetricInfo: DerivedMetricInfo): void {
public validateMetricFilters(derivedMetricInfo: DerivedMetricInfo): void {
derivedMetricInfo.filterGroups.forEach((filterGroup) => {
filterGroup.filters.forEach((filter) => {
this.validateFieldNames(filter.fieldName, derivedMetricInfo.telemetryType);
@ -60,6 +60,17 @@ export class Validator {
});
}
public validateDocumentFilters(
documentFilterConjuctionGroupInfo: DocumentFilterConjunctionGroupInfo,
): void {
const filterConjunctionGroupInfo: FilterConjunctionGroupInfo =
documentFilterConjuctionGroupInfo.filters;
filterConjunctionGroupInfo.filters.forEach((filter) => {
this.validateFieldNames(filter.fieldName, documentFilterConjuctionGroupInfo.telemetryType);
this.validatePredicateAndComparand(filter);
});
}
private isCustomDimOrAnyField(fieldName: string): boolean {
return fieldName.startsWith("CustomDimensions.") || fieldName === "*";
}

Просмотреть файл

@ -34,6 +34,7 @@ import {
KeyValuePairString,
DerivedMetricInfo,
KnownTelemetryType,
FilterConjunctionGroupInfo,
} from "../../generated";
import {
getCloudRole,
@ -47,6 +48,7 @@ import {
isRequestData,
getSpanExceptionColumns,
isExceptionData,
isDependencyData,
} from "./utils";
import { QuickpulseMetricExporter } from "./export/exporter";
import { QuickpulseSender } from "./export/sender";
@ -150,6 +152,11 @@ export class LiveMetrics {
private derivedMetricProjection: Projection = new Projection();
private validator: Validator = new Validator();
private filter: Filter = new Filter();
// type: Map<telemetryType, Map<id, FilterConjunctionGroupInfo[]>>
private validDocumentFilterConjuctionGroupInfos: Map<
string,
Map<string, FilterConjunctionGroupInfo[]>
> = new Map();
/**
* Initializes a new instance of the StandardMetrics class.
* @param config - Distro configuration.
@ -165,7 +172,7 @@ export class LiveMetrics {
const version = getSdkVersion();
this.baseMonitoringDataPoint = {
version: version,
invariantVersion: 2, // Not changing this to 5 until we have filtering of documents too
invariantVersion: 5, // 5 means we support live metrics filtering of metrics and documents
instance: instance,
roleName: roleName,
machineName: machineName,
@ -255,7 +262,6 @@ export class LiveMetrics {
this.lastSuccessTime = Date.now();
this.isCollectingData =
response.xMsQpsSubscribed && response.xMsQpsSubscribed === "true" ? true : false;
if (response.xMsQpsConfigurationEtag && this.etag !== response.xMsQpsConfigurationEtag) {
this.updateConfiguration(response);
}
@ -393,6 +399,7 @@ export class LiveMetrics {
*/
public deactivateMetrics(): void {
this.documents = [];
this.validDocumentFilterConjuctionGroupInfos.clear();
this.errorTracker.clearRunTimeErrors();
this.errorTracker.clearValidationTimeErrors();
this.validDerivedMetrics.clear();
@ -449,16 +456,22 @@ export class LiveMetrics {
public recordSpan(span: ReadableSpan): void {
if (this.isCollectingData) {
const columns: RequestData | DependencyData = getSpanData(span);
let documentConfiguration: Map<string, FilterConjunctionGroupInfo[]>;
let derivedMetricInfos: DerivedMetricInfo[];
if (isRequestData(columns)) {
documentConfiguration =
this.validDocumentFilterConjuctionGroupInfos.get(KnownTelemetryType.Request) ||
new Map<string, FilterConjunctionGroupInfo[]>();
derivedMetricInfos = this.validDerivedMetrics.get(KnownTelemetryType.Request) || [];
} else {
documentConfiguration =
this.validDocumentFilterConjuctionGroupInfos.get(KnownTelemetryType.Dependency) ||
new Map<string, FilterConjunctionGroupInfo[]>();
derivedMetricInfos = this.validDerivedMetrics.get(KnownTelemetryType.Dependency) || [];
}
this.applyDocumentFilters(documentConfiguration, columns);
this.checkMetricFilterAndCreateProjection(derivedMetricInfos, columns);
const document: Request | RemoteDependency = getSpanDocument(columns);
this.addDocument(document);
const durationMs = hrTimeToMilliseconds(span.duration);
const success = span.status.code !== SpanStatusCode.ERROR;
@ -483,13 +496,16 @@ export class LiveMetrics {
event.attributes,
span.attributes,
);
derivedMetricInfos = this.validDerivedMetrics.get(KnownTelemetryType.Exception) || [];
this.checkMetricFilterAndCreateProjection(derivedMetricInfos, exceptionColumns);
const exceptionDocument: Exception = getLogDocument(
documentConfiguration =
this.validDocumentFilterConjuctionGroupInfos.get(KnownTelemetryType.Exception) ||
new Map<string, FilterConjunctionGroupInfo[]>();
this.applyDocumentFilters(
documentConfiguration,
exceptionColumns,
event.attributes[SEMATTRS_EXCEPTION_TYPE] as string,
) as Exception;
this.addDocument(exceptionDocument);
);
derivedMetricInfos = this.validDerivedMetrics.get(KnownTelemetryType.Exception) || [];
this.checkMetricFilterAndCreateProjection(derivedMetricInfos, exceptionColumns);
this.totalExceptionCount++;
}
});
@ -505,17 +521,27 @@ export class LiveMetrics {
if (this.isCollectingData) {
const columns: TraceData | ExceptionData = getLogData(logRecord);
let derivedMetricInfos: DerivedMetricInfo[];
let documentConfiguration: Map<string, FilterConjunctionGroupInfo[]>;
if (isExceptionData(columns)) {
documentConfiguration =
this.validDocumentFilterConjuctionGroupInfos.get(KnownTelemetryType.Exception) ||
new Map<string, FilterConjunctionGroupInfo[]>();
this.applyDocumentFilters(
documentConfiguration,
columns,
logRecord.attributes[SEMATTRS_EXCEPTION_TYPE] as string,
);
derivedMetricInfos = this.validDerivedMetrics.get(KnownTelemetryType.Exception) || [];
this.totalExceptionCount++;
} else {
// trace
documentConfiguration =
this.validDocumentFilterConjuctionGroupInfos.get(KnownTelemetryType.Trace) ||
new Map<string, FilterConjunctionGroupInfo[]>();
this.applyDocumentFilters(documentConfiguration, columns);
derivedMetricInfos = this.validDerivedMetrics.get(KnownTelemetryType.Trace) || [];
}
this.checkMetricFilterAndCreateProjection(derivedMetricInfos, columns);
const exceptionType = String(logRecord.attributes[SEMATTRS_EXCEPTION_TYPE]) || "";
const document: Trace | Exception = getLogDocument(columns, exceptionType);
this.addDocument(document);
}
}
@ -695,17 +721,109 @@ export class LiveMetrics {
this.etag = response.xMsQpsConfigurationEtag || "";
this.quickpulseExporter.setEtag(this.etag);
this.errorTracker.clearValidationTimeErrors();
this.validDocumentFilterConjuctionGroupInfos.clear();
this.validDerivedMetrics.clear();
this.derivedMetricProjection.clearProjectionMaps();
this.seenMetricIds.clear();
this.parseDocumentFilterConfiguration(response);
this.parseMetricFilterConfiguration(response);
}
private parseDocumentFilterConfiguration(response: PublishResponse | IsSubscribedResponse): void {
response.documentStreams.forEach((documentStreamInfo) => {
documentStreamInfo.documentFilterGroups.forEach((documentFilterGroupInfo) => {
try {
this.validator.validateTelemetryType(documentFilterGroupInfo.telemetryType);
this.validator.validateDocumentFilters(documentFilterGroupInfo);
this.filter.renameExceptionFieldNamesForFiltering(documentFilterGroupInfo.filters);
if (
!this.validDocumentFilterConjuctionGroupInfos.has(documentFilterGroupInfo.telemetryType)
) {
this.validDocumentFilterConjuctionGroupInfos.set(
documentFilterGroupInfo.telemetryType,
new Map<string, FilterConjunctionGroupInfo[]>(),
);
}
const innerMap = this.validDocumentFilterConjuctionGroupInfos.get(
documentFilterGroupInfo.telemetryType,
);
if (!innerMap?.has(documentStreamInfo.id)) {
innerMap?.set(documentStreamInfo.id, [documentFilterGroupInfo.filters]);
} else {
innerMap.get(documentStreamInfo.id)?.push(documentFilterGroupInfo.filters);
}
} catch (error) {
const configError: CollectionConfigurationError = {
collectionConfigurationErrorType: "",
message: "",
fullException: "",
data: [],
};
if (error instanceof TelemetryTypeError) {
configError.collectionConfigurationErrorType = "DocumentTelemetryTypeUnsupported";
} else if (error instanceof UnexpectedFilterCreateError) {
configError.collectionConfigurationErrorType =
KnownCollectionConfigurationErrorType.DocumentStreamFailureToCreateFilterUnexpected;
}
if (error instanceof Error) {
configError.message = error.message;
configError.fullException = error.stack || "";
}
const data: KeyValuePairString[] = [];
data.push({ key: "DocumentStreamInfoId", value: documentStreamInfo.id });
data.push({ key: "ETag", value: this.etag });
configError.data = data;
this.errorTracker.addValidationError(configError);
}
});
});
}
private applyDocumentFilters(
documentConfiguration: Map<string, FilterConjunctionGroupInfo[]>,
data: TelemetryData,
exceptionType?: string,
): void {
const streamIds: Set<string> = new Set<string>();
documentConfiguration.forEach((filterConjunctionGroupInfoList, streamId) => {
filterConjunctionGroupInfoList.forEach((filterConjunctionGroupInfo) => {
// by going though each filterConjuctionGroupInfo, we are implicitly -OR-ing
// different filterConjunctionGroupInfo within documentStreamInfo. If there are multiple
// documentStreamInfos, this logic will -OR- the filtering results of each documentStreamInfo.
if (this.filter.checkFilterConjunctionGroup(filterConjunctionGroupInfo, data)) {
streamIds.add(streamId);
}
});
});
// Emit a document when a telemetry data matches a particular filtering configuration,
// or when filtering configuration is empty.
if (streamIds.size > 0 || documentConfiguration.size === 0) {
let document: Request | RemoteDependency | Trace | Exception;
if (isRequestData(data) || isDependencyData(data)) {
document = getSpanDocument(data);
} else if (isExceptionData(data) && exceptionType) {
document = getLogDocument(data, exceptionType);
} else {
document = getLogDocument(data);
}
document.documentStreamIds = [...streamIds];
this.addDocument(document);
}
}
private parseMetricFilterConfiguration(response: PublishResponse | IsSubscribedResponse): void {
response.metrics.forEach((derivedMetricInfo) => {
try {
if (!this.seenMetricIds.has(derivedMetricInfo.id)) {
this.seenMetricIds.add(derivedMetricInfo.id);
this.validator.validateTelemetryType(derivedMetricInfo);
this.validator.validateTelemetryType(derivedMetricInfo.telemetryType);
this.validator.checkCustomMetricProjection(derivedMetricInfo);
this.validator.validateFilters(derivedMetricInfo);
this.validator.validateMetricFilters(derivedMetricInfo);
derivedMetricInfo.filterGroups.forEach((filterConjunctionGroupInfo) => {
this.filter.renameExceptionFieldNamesForFiltering(filterConjunctionGroupInfo);
});
@ -731,7 +849,7 @@ export class LiveMetrics {
KnownCollectionConfigurationErrorType.MetricTelemetryTypeUnsupported;
} else if (error instanceof UnexpectedFilterCreateError) {
configError.collectionConfigurationErrorType =
KnownCollectionConfigurationErrorType.FilterFailureToCreateUnexpected;
KnownCollectionConfigurationErrorType.MetricFailureToCreateFilterUnexpected;
} else if (error instanceof DuplicateMetricIdError) {
configError.collectionConfigurationErrorType =
KnownCollectionConfigurationErrorType.MetricDuplicateIds;

Просмотреть файл

@ -435,8 +435,8 @@ export function getLogData(log: LogRecord): ExceptionData | TraceData {
}
}
export function getLogDocument(data: TelemetryData, exceptionType: string): Trace | Exception {
if (isExceptionData(data)) {
export function getLogDocument(data: TelemetryData, exceptionType?: string): Trace | Exception {
if (isExceptionData(data) && exceptionType) {
return {
documentType: KnownDocumentType.Exception,
exceptionMessage: data.Message,

Просмотреть файл

@ -15,6 +15,7 @@ import {
Trace,
KnownDocumentType,
KnownAggregationType,
DocumentFilterConjunctionGroupInfo,
} from "../../../../src/generated";
import { Validator } from "../../../../src/metrics/quickpulse/filtering/validator";
import { Filter } from "../../../../src/metrics/quickpulse/filtering/filter";
@ -56,13 +57,25 @@ describe("Live Metrics filtering - Validator", () => {
backEndAggregation: "Sum",
};
assert.throws(() => validator.validateTelemetryType(derivedMetricInfo), TelemetryTypeError);
assert.throws(
() => validator.validateTelemetryType(derivedMetricInfo.telemetryType),
TelemetryTypeError,
);
derivedMetricInfo.telemetryType = "\\Random\\Counter";
assert.throws(() => validator.validateTelemetryType(derivedMetricInfo), TelemetryTypeError);
assert.throws(
() => validator.validateTelemetryType(derivedMetricInfo.telemetryType),
TelemetryTypeError,
);
derivedMetricInfo.telemetryType = "Metric";
assert.throws(() => validator.validateTelemetryType(derivedMetricInfo), TelemetryTypeError);
assert.throws(
() => validator.validateTelemetryType(derivedMetricInfo.telemetryType),
TelemetryTypeError,
);
derivedMetricInfo.telemetryType = "does not exist";
assert.throws(() => validator.validateTelemetryType(derivedMetricInfo), TelemetryTypeError);
assert.throws(
() => validator.validateTelemetryType(derivedMetricInfo.telemetryType),
TelemetryTypeError,
);
});
it("The validator rejects CustomMetrics projections and filters (not supported in Otel)", () => {
@ -98,8 +111,18 @@ describe("Live Metrics filtering - Validator", () => {
() => validator.checkCustomMetricProjection(invalid1),
UnexpectedFilterCreateError,
);
validator.validateTelemetryType(invalid2); // this shouldn't throw an error as the telemetry type is supported
assert.throws(() => validator.validateFilters(invalid2), UnexpectedFilterCreateError);
validator.validateTelemetryType(invalid2.telemetryType); // this shouldn't throw an error as the telemetry type is supported
assert.throws(() => validator.validateMetricFilters(invalid2), UnexpectedFilterCreateError);
const invalidDocFilterConjuctionInfo: DocumentFilterConjunctionGroupInfo = {
telemetryType: KnownTelemetryType.Request,
filters: conjunctionGroup,
};
validator.validateTelemetryType(invalidDocFilterConjuctionInfo.telemetryType);
assert.throws(
() => validator.validateDocumentFilters(invalidDocFilterConjuctionInfo),
UnexpectedFilterCreateError,
);
});
it("The validator rejects invalid filters", () => {
@ -310,6 +333,11 @@ describe("Live Metrics filtering - Validator", () => {
backEndAggregation: "Sum",
};
const documentFilterConjunctionGroupInfo: DocumentFilterConjunctionGroupInfo = {
telemetryType: KnownTelemetryType.Request,
filters: { filters: [] },
};
filterInfoList.forEach((filter) => {
const conjunctionGroup: FilterConjunctionGroupInfo = {
filters: [filter],
@ -317,12 +345,20 @@ describe("Live Metrics filtering - Validator", () => {
derivedMetricInfo.filterGroups = [conjunctionGroup];
assert.throws(
() => validator.validateFilters(derivedMetricInfo),
() => validator.validateMetricFilters(derivedMetricInfo),
UnexpectedFilterCreateError || TelemetryTypeError,
);
documentFilterConjunctionGroupInfo.filters = conjunctionGroup;
assert.throws(
() => validator.validateDocumentFilters(documentFilterConjunctionGroupInfo),
UnexpectedFilterCreateError || TelemetryTypeError,
);
});
derivedMetricInfo.filterGroups = [{ filters: [unknownFieldName] }];
documentFilterConjunctionGroupInfo.filters = { filters: [unknownFieldName] };
const supportedTelemetryTypes: KnownTelemetryType[] = [
KnownTelemetryType.Request,
KnownTelemetryType.Dependency,
@ -333,13 +369,18 @@ describe("Live Metrics filtering - Validator", () => {
supportedTelemetryTypes.forEach((telemetryType) => {
derivedMetricInfo.telemetryType = telemetryType;
assert.throws(
() => validator.validateFilters(derivedMetricInfo),
() => validator.validateMetricFilters(derivedMetricInfo),
UnexpectedFilterCreateError,
);
documentFilterConjunctionGroupInfo.telemetryType = telemetryType;
assert.throws(
() => validator.validateDocumentFilters(documentFilterConjunctionGroupInfo),
UnexpectedFilterCreateError,
);
});
});
it("The validator rejects a derivedMetricInfo if the only filterConjunctionGroupInfo has an invalid filter inside it", () => {
it("The validator rejects a derivedMetricInfo/documentFilterConjuctionGroupInfo if the only filterConjunctionGroupInfo has an invalid filter inside it", () => {
const invalidFilter: FilterInfo = {
fieldName: KnownRequestColumns.Duration,
predicate: KnownPredicateType.NotEqual,
@ -355,6 +396,10 @@ describe("Live Metrics filtering - Validator", () => {
const conjunctionGroup: FilterConjunctionGroupInfo = {
filters: [validFilter, invalidFilter],
};
const documentFilterConjunctionGroupInfo: DocumentFilterConjunctionGroupInfo = {
telemetryType: KnownTelemetryType.Request,
filters: conjunctionGroup,
};
const derivedMetricInfo: DerivedMetricInfo = {
id: "random-id",
@ -365,7 +410,14 @@ describe("Live Metrics filtering - Validator", () => {
backEndAggregation: "Sum",
};
assert.throws(() => validator.validateFilters(derivedMetricInfo), UnexpectedFilterCreateError);
assert.throws(
() => validator.validateMetricFilters(derivedMetricInfo),
UnexpectedFilterCreateError,
);
assert.throws(
() => validator.validateDocumentFilters(documentFilterConjunctionGroupInfo),
UnexpectedFilterCreateError,
);
});
it("The validator accepts valid filters", () => {
@ -520,7 +572,13 @@ describe("Live Metrics filtering - Validator", () => {
};
derivedMetricInfo.filterGroups = [conjunctionGroup];
validator.validateFilters(derivedMetricInfo);
validator.validateMetricFilters(derivedMetricInfo);
const documentFilterConjunctionGroupInfo: DocumentFilterConjunctionGroupInfo = {
telemetryType: KnownTelemetryType.Request,
filters: conjunctionGroup,
};
validator.validateDocumentFilters(documentFilterConjunctionGroupInfo);
});
});
});
@ -719,34 +777,40 @@ describe("Live Metrics filtering - Applying valid filters", () => {
// request contains "hi" in multiple fields & filter is contains hi
// return true
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request1));
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request1));
// request does not contain "hi" in any field & filter is contains hi
// return false
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request2) === false);
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request2) === false);
// request does not contain "hi" in any field & filter is does not contain hi
// return true
conjunctionGroup.filters = [anyFieldNotContains];
derivedMetricInfo.filterGroups = [conjunctionGroup];
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request2));
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request2));
// request contains "cool" in custom dimensions & filter is contains cool
// return true
conjunctionGroup.filters = [anyFieldContainsCool];
derivedMetricInfo.filterGroups = [conjunctionGroup];
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request2));
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request2));
// request contains 200 in duration & filter is contains "200".
// fields are expected to be treated as string
conjunctionGroup.filters = [anyFieldForNumeric];
derivedMetricInfo.filterGroups = [conjunctionGroup];
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request1));
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request1));
// request contains true in Success & filter is contains "true".
// fields are expected to be treated as string
conjunctionGroup.filters = [anyFieldForBoolean];
derivedMetricInfo.filterGroups = [conjunctionGroup];
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request1));
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request1));
});
@ -781,29 +845,35 @@ describe("Live Metrics filtering - Applying valid filters", () => {
// the asked for field is not in the custom dimensions so return false
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request) === false);
// the asked for field is in the custom dimensions but value does not match
request.CustomDimensions.clear();
request.CustomDimensions.set("hi", "bye");
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request) === false);
// the asked for field is in the custom dimensions and value matches
request.CustomDimensions.set("hi", "hi");
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// testing not equal predicate. The CustomDimensions.hi value != hi so return true.
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.NotEqual;
request.CustomDimensions.set("hi", "bye");
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// testing does not contain predicate. The CustomDimensions.hi value does not contain hi so return true.
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.DoesNotContain;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// testing contains predicate. The CustomDimensions.hi value contains hi so return true.
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.Contains;
request.CustomDimensions.set("hi", "hi there");
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
});
it("Can handle filter on known boolean columns", () => {
@ -848,27 +918,33 @@ describe("Live Metrics filtering - Applying valid filters", () => {
// Request Success filter matches
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// Request Success filter does not match
request.Success = false;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request) === false);
// Request Success filter matches for != predicate
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.NotEqual;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// Dependency Success filter matches
derivedMetricInfo.telemetryType = KnownTelemetryType.Dependency;
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.Equal;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, dependency));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, dependency));
// Dependency Success filter does not match
dependency.Success = false;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, dependency) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, dependency) === false);
// Dependency Success filter matches for != predicate
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.NotEqual;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, dependency));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, dependency));
});
it("Can handle filter on known numeric columns", () => {
@ -913,59 +989,72 @@ describe("Live Metrics filtering - Applying valid filters", () => {
// Request ResponseCode filter matches
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// Request ResponseCode filter does not match
request.ResponseCode = 404;
request.Success = false;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request) === false);
// Dependency ResultCode filter matches
derivedMetricInfo.telemetryType = KnownTelemetryType.Dependency;
derivedMetricInfo.filterGroups[0].filters[0].fieldName = KnownDependencyColumns.ResultCode;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, dependency));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, dependency));
// Dependency ResultCode filter does not match
dependency.ResultCode = 404;
dependency.Success = false;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, dependency) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, dependency) === false);
// Dependency duration filter matches
derivedMetricInfo.filterGroups[0].filters[0].fieldName = KnownDependencyColumns.Duration;
derivedMetricInfo.filterGroups[0].filters[0].comparand = "14.6:56:7.89"; // 14 days, 6 hours, 56 minutes, 7.89 seconds (1234567890 ms)
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, dependency));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, dependency));
// Dependency duration filter does not match
dependency.Duration = 400;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, dependency) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, dependency) === false);
// Request duration filter matches
derivedMetricInfo.telemetryType = KnownTelemetryType.Request;
derivedMetricInfo.filterGroups[0].filters[0].fieldName = KnownRequestColumns.Duration;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// Request duration filter does not match
request.Duration = 400;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request) === false);
// != predicate
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.NotEqual;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// < predicate
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.LessThan;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// <= predicate
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.LessThanOrEqual;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// > predicate
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.GreaterThan;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request) === false);
// >= predicate
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.GreaterThanOrEqual;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request) === false);
});
it("Can handle filter on known string columns", () => {
@ -1021,48 +1110,59 @@ describe("Live Metrics filtering - Applying valid filters", () => {
// Request Url filter matches
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// Request Url filter does not match
request.Url = "https://test.com/bye";
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request) === false);
// Dependency Data filter matches
derivedMetricInfo.telemetryType = KnownTelemetryType.Dependency;
derivedMetricInfo.filterGroups[0].filters[0].fieldName = KnownDependencyColumns.Data;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, dependency));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, dependency));
// Dependency Data filter does not match
dependency.Data = "https://test.com/bye";
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, dependency) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, dependency) === false);
// Trace Message filter matches
derivedMetricInfo.telemetryType = KnownTelemetryType.Trace;
derivedMetricInfo.filterGroups[0].filters[0].fieldName = "Message";
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, trace));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, trace));
// Trace Message filter does not match
trace.Message = "bye";
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, trace) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, trace) === false);
// Exception Message filter matches. Note that fieldName is still "Message" here and that's intended (we remove the Exception. prefix when validating config)
derivedMetricInfo.telemetryType = KnownTelemetryType.Exception;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, exception));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, exception));
// Exception Message filter does not match
exception.Message = "Exception Message";
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, exception) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, exception) === false);
// != predicate
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.NotEqual;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, exception));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, exception));
// not contains
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.DoesNotContain;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, exception));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, exception));
// equal
derivedMetricInfo.filterGroups[0].filters[0].predicate = KnownPredicateType.Equal;
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, exception) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, exception) === false);
});
it("Empty filter conjunction group info - should match", () => {
@ -1084,7 +1184,14 @@ describe("Live Metrics filtering - Applying valid filters", () => {
CustomDimensions: new Map<string, string>(),
};
const documentFilterConjunctionGroupInfo: DocumentFilterConjunctionGroupInfo = {
telemetryType: KnownTelemetryType.Request,
filters: { filters: [] },
};
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(
filterClass.checkFilterConjunctionGroup(documentFilterConjunctionGroupInfo.filters, request),
);
});
it("Can handle multiple filters in a filter conjunction group", () => {
@ -1124,10 +1231,12 @@ describe("Live Metrics filtering - Applying valid filters", () => {
// matches both filters
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request));
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request));
// only one filter matches, the entire conjunction group should return false
request.Url = "https://test.com/bye";
assert.ok(filterClass.checkMetricFilters(derivedMetricInfo, request) === false);
assert.ok(filterClass.checkFilterConjunctionGroup(conjunctionGroup, request) === false);
});
});
@ -1535,7 +1644,7 @@ describe("Live Metrics filtering - documents", () => {
const requestDoc: Request = getSpanDocument(request) as Request;
const dependencyDoc: RemoteDependency = getSpanDocument(dependency) as RemoteDependency;
const traceDoc: Trace = getLogDocument(trace, "") as Trace;
const traceDoc: Trace = getLogDocument(trace) as Trace;
const exceptionDoc: Exception = getLogDocument(exception, "Error") as Exception;
assert.equal(requestDoc.url, "https://test.com/hiThere");