[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:
Родитель
acdcea72ef
Коммит
e27c896af2
|
@ -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");
|
||||
|
|
Загрузка…
Ссылка в новой задаче