Skip to content

Commit fe58190

Browse files
authored
[Azure Monitor] Add Standard Metrics support (Azure#26281)
Adding support for Standard Metrics Adding Stastbeat features Adding unit tests Add support for APPLICATIONINSIGHTS_CONFIGURATION_CONTENT
1 parent f271114 commit fe58190

File tree

20 files changed

+3806
-2189
lines changed

20 files changed

+3806
-2189
lines changed

common/config/rush/pnpm-lock.yaml

Lines changed: 2237 additions & 2117 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/monitor/monitor-opentelemetry/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"@microsoft/api-extractor": "^7.31.1",
7373
"@types/mocha": "^7.0.2",
7474
"@types/node": "^14.0.0",
75+
"@types/sinon": "10.0.15",
7576
"dotenv": "^16.0.0",
7677
"eslint": "^8.0.0",
7778
"eslint-plugin-node": "^11.1.0",

sdk/monitor/monitor-opentelemetry/src/client.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import { TraceHandler } from "./traces/handler";
1111
import { Logger as InternalLogger } from "./shared/logging";
1212
import { AzureMonitorOpenTelemetryOptions } from "./shared/types";
1313
import { LogHandler } from "./logs";
14+
import {
15+
AZURE_MONITOR_STATSBEAT_FEATURES,
16+
StatsbeatFeature,
17+
StatsbeatInstrumentation,
18+
} from "./types";
1419

1520
/**
1621
* Azure Monitor OpenTelemetry Client
@@ -35,6 +40,7 @@ export class AzureMonitorOpenTelemetryClient {
3540
"Connection String not found, please provide it before starting Azure Monitor OpenTelemetry Client."
3641
);
3742
}
43+
this._setStatsbeatFeatures();
3844
this._metricHandler = new MetricHandler(this._config);
3945
this._traceHandler = new TraceHandler(this._config, this._metricHandler);
4046
this._logHandler = new LogHandler(this._config, this._metricHandler);
@@ -100,5 +106,37 @@ export class AzureMonitorOpenTelemetryClient {
100106
public async shutdown(): Promise<void> {
101107
this._traceHandler.shutdown();
102108
this._metricHandler.shutdown();
109+
this._logHandler.shutdown();
110+
}
111+
112+
private _setStatsbeatFeatures() {
113+
let instrumentationBitMap = 0;
114+
if (this._config.instrumentationOptions?.azureSdk?.enabled) {
115+
instrumentationBitMap |= StatsbeatInstrumentation.AZURE_CORE_TRACING;
116+
}
117+
if (this._config.instrumentationOptions?.mongoDb?.enabled) {
118+
instrumentationBitMap |= StatsbeatInstrumentation.MONGODB;
119+
}
120+
if (this._config.instrumentationOptions?.mySql?.enabled) {
121+
instrumentationBitMap |= StatsbeatInstrumentation.MYSQL;
122+
}
123+
if (this._config.instrumentationOptions?.postgreSql?.enabled) {
124+
instrumentationBitMap |= StatsbeatInstrumentation.POSTGRES;
125+
}
126+
if (this._config.instrumentationOptions?.redis?.enabled) {
127+
instrumentationBitMap |= StatsbeatInstrumentation.REDIS;
128+
}
129+
130+
let featureBitMap = 0;
131+
featureBitMap |= StatsbeatFeature.DISTRO;
132+
133+
try {
134+
process.env[AZURE_MONITOR_STATSBEAT_FEATURES] = JSON.stringify({
135+
instrumentation: instrumentationBitMap,
136+
feature: featureBitMap,
137+
});
138+
} catch (error) {
139+
InternalLogger.getInstance().error("Failed call to JSON.stringify.", error);
140+
}
103141
}
104142
}

sdk/monitor/monitor-opentelemetry/src/logs/handler.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { LoggerProviderConfig } from "@opentelemetry/sdk-logs/build/src/types";
1111
import { AzureMonitorOpenTelemetryConfig } from "../shared/config";
1212
import { MetricHandler } from "../metrics/handler";
13+
import { AzureLogRecordProcessor } from "./logRecordProcessor";
1314

1415
/**
1516
* Azure Monitor OpenTelemetry Log Handler
@@ -21,13 +22,14 @@ export class LogHandler {
2122
private _logRecordProcessor: SimpleLogRecordProcessor;
2223
private _config: AzureMonitorOpenTelemetryConfig;
2324
private _metricHandler?: MetricHandler;
25+
private _azureLogProccessor: AzureLogRecordProcessor;
2426

2527
/**
2628
* Initializes a new instance of the TraceHandler class.
2729
* @param _config - Distro configuration.
2830
* @param _metricHandler - MetricHandler.
2931
*/
30-
constructor(config: AzureMonitorOpenTelemetryConfig, metricHandler?: MetricHandler) {
32+
constructor(config: AzureMonitorOpenTelemetryConfig, metricHandler: MetricHandler) {
3133
this._config = config;
3234
this._metricHandler = metricHandler;
3335
const loggerProviderConfig: LoggerProviderConfig = {
@@ -37,10 +39,9 @@ export class LogHandler {
3739
this._exporter = new ConsoleLogRecordExporter();
3840
this._logRecordProcessor = new SimpleLogRecordProcessor(this._exporter);
3941
this._loggerProvider.addLogRecordProcessor(this._logRecordProcessor);
42+
this._azureLogProccessor = new AzureLogRecordProcessor(this._metricHandler);
43+
this._loggerProvider.addLogRecordProcessor(this._azureLogProccessor);
4044
this._logger = this._loggerProvider.getLogger("AzureMonitorLogger", undefined) as OtelLogger;
41-
if (this._metricHandler) {
42-
// TODO: Use metric handler to track standard metrics
43-
}
4445
}
4546

4647
/**
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
import { MetricHandler } from "../metrics/handler";
5+
import { LogRecord, LogRecordProcessor } from "@opentelemetry/sdk-logs";
6+
7+
/**
8+
* Azure Monitor LogRecord Processor.
9+
* @internal
10+
*/
11+
export class AzureLogRecordProcessor implements LogRecordProcessor {
12+
private readonly _metricHandler: MetricHandler;
13+
14+
constructor(metricHandler: MetricHandler) {
15+
this._metricHandler = metricHandler;
16+
}
17+
18+
public onEmit(logRecord: LogRecord): void {
19+
this._metricHandler.recordLog(logRecord);
20+
}
21+
22+
public forceFlush(): Promise<void> {
23+
return Promise.resolve();
24+
}
25+
26+
public shutdown(): Promise<void> {
27+
return Promise.resolve();
28+
}
29+
}

sdk/monitor/monitor-opentelemetry/src/metrics/handler.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
import { AzureMonitorOpenTelemetryConfig } from "../shared/config";
1313
import { PerformanceCounterMetrics } from "./performanceCounters";
1414
import { StandardMetrics } from "./standardMetrics";
15+
import { ReadableSpan, Span } from "@opentelemetry/sdk-trace-base";
16+
import { LogRecord } from "@opentelemetry/sdk-logs";
1517

1618
/**
1719
* Azure Monitor OpenTelemetry Metric Handler
@@ -67,18 +69,17 @@ export class MetricHandler {
6769
return this._meter;
6870
}
6971

70-
/**
71-
*Get StandardMetric handler
72-
*/
73-
public getStandardMetrics(): StandardMetrics | undefined {
74-
return this._standardMetrics;
72+
public markSpanAsProcessed(span: Span): void {
73+
this._standardMetrics?.markSpanAsProcessed(span);
7574
}
7675

77-
/**
78-
*Get PerformanceCounter handler
79-
*/
80-
public getPerformanceCounterMetrics(): PerformanceCounterMetrics | undefined {
81-
return this._perfCounterMetrics;
76+
public recordSpan(span: ReadableSpan): void {
77+
this._standardMetrics?.recordSpan(span);
78+
this._perfCounterMetrics?.recordSpan(span);
79+
}
80+
81+
public recordLog(logRecord: LogRecord): void {
82+
this._standardMetrics?.recordLog(logRecord);
8283
}
8384

8485
/**

sdk/monitor/monitor-opentelemetry/src/metrics/standardMetrics.ts

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
} from "@opentelemetry/sdk-metrics";
1010
import { AzureMonitorOpenTelemetryConfig } from "../shared/config";
1111
import { AzureMonitorMetricExporter } from "@azure/monitor-opentelemetry-exporter";
12-
import { Attributes, Histogram, Meter, SpanKind, ValueType } from "@opentelemetry/api";
13-
import { ReadableSpan, Span } from "@opentelemetry/sdk-trace-base";
12+
import { Attributes, Counter, Histogram, Meter, SpanKind, ValueType } from "@opentelemetry/api";
13+
import { ReadableSpan, Span, TimedEvent } from "@opentelemetry/sdk-trace-base";
1414
import {
1515
SemanticAttributes,
1616
SemanticResourceAttributes,
@@ -20,20 +20,24 @@ import {
2020
MetricRequestDimensions,
2121
StandardMetricBaseDimensions,
2222
} from "./types";
23+
import { LogRecord } from "@opentelemetry/sdk-logs";
24+
import { Resource } from "@opentelemetry/resources";
2325

2426
/**
2527
* Azure Monitor Standard Metrics
2628
* @internal
2729
*/
2830
export class StandardMetrics {
31+
private _config: AzureMonitorOpenTelemetryConfig;
2932
private _collectionInterval = 60000; // 60 seconds
3033
private _meterProvider: MeterProvider;
3134
private _azureExporter: AzureMonitorMetricExporter;
3235
private _metricReader: PeriodicExportingMetricReader;
3336
private _meter: Meter;
3437
private _incomingRequestDurationHistogram: Histogram;
3538
private _outgoingRequestDurationHistogram: Histogram;
36-
private _config: AzureMonitorOpenTelemetryConfig;
39+
private _exceptionsCounter: Counter;
40+
private _tracesCounter: Counter;
3741

3842
/**
3943
* Initializes a new instance of the StandardMetrics class.
@@ -54,11 +58,24 @@ export class StandardMetrics {
5458
this._metricReader = new PeriodicExportingMetricReader(metricReaderOptions);
5559
this._meterProvider.addMetricReader(this._metricReader);
5660
this._meter = this._meterProvider.getMeter("AzureMonitorStandardMetricsMeter");
57-
this._incomingRequestDurationHistogram = this._meter.createHistogram("REQUEST_DURATION", {
58-
valueType: ValueType.DOUBLE,
61+
this._incomingRequestDurationHistogram = this._meter.createHistogram(
62+
"azureMonitor.http.requestDuration",
63+
{
64+
valueType: ValueType.DOUBLE,
65+
}
66+
);
67+
this._outgoingRequestDurationHistogram = this._meter.createHistogram(
68+
"azureMonitor.http.dependencyDuration",
69+
{
70+
valueType: ValueType.DOUBLE,
71+
}
72+
);
73+
74+
this._exceptionsCounter = this._meter.createCounter("azureMonitor.exceptionCount", {
75+
valueType: ValueType.INT,
5976
});
60-
this._outgoingRequestDurationHistogram = this._meter.createHistogram("DEPENDENCY_DURATION", {
61-
valueType: ValueType.DOUBLE,
77+
this._tracesCounter = this._meter.createCounter("azureMonitor.traceCount", {
78+
valueType: ValueType.INT,
6279
});
6380
}
6481

@@ -108,10 +125,36 @@ export class StandardMetrics {
108125
this._getDependencyDimensions(span)
109126
);
110127
}
128+
if (span.events) {
129+
span.events.forEach((event: TimedEvent) => {
130+
event.attributes = event.attributes || {};
131+
if (event.name === "exception") {
132+
event.attributes["_MS.ProcessedByMetricExtractors"] = "(Name:'Exceptions', Ver:'1.1')";
133+
this._exceptionsCounter.add(1, this._getExceptionDimensions(span.resource));
134+
} else {
135+
event.attributes["_MS.ProcessedByMetricExtractors"] = "(Name:'Traces', Ver:'1.1')";
136+
this._tracesCounter.add(1, this._getTraceDimensions(span.resource));
137+
}
138+
});
139+
}
140+
}
141+
142+
/**
143+
* Record LogRecord metrics, add attribute so data is not aggregated again in ingestion
144+
* @internal
145+
*/
146+
public recordLog(logRecord: LogRecord): void {
147+
if (this._isExceptionTelemetry(logRecord)) {
148+
logRecord.setAttribute("_MS.ProcessedByMetricExtractors", "(Name:'Exceptions', Ver:'1.1')");
149+
this._exceptionsCounter.add(1, this._getExceptionDimensions(logRecord.resource));
150+
} else if (this._isTraceTelemetry(logRecord)) {
151+
logRecord.setAttribute("_MS.ProcessedByMetricExtractors", "(Name:'Traces', Ver:'1.1')");
152+
this._tracesCounter.add(1, this._getTraceDimensions(logRecord.resource));
153+
}
111154
}
112155

113156
private _getRequestDimensions(span: ReadableSpan): Attributes {
114-
const dimensions: MetricRequestDimensions = this._getBaseDimensions(span);
157+
const dimensions: MetricRequestDimensions = this._getBaseDimensions(span.resource);
115158
dimensions.metricId = "requests/duration";
116159
const statusCode = String(span.attributes["http.status_code"]);
117160
dimensions.requestResultCode = statusCode;
@@ -120,7 +163,7 @@ export class StandardMetrics {
120163
}
121164

122165
private _getDependencyDimensions(span: ReadableSpan): Attributes {
123-
const dimensions: MetricDependencyDimensions = this._getBaseDimensions(span);
166+
const dimensions: MetricDependencyDimensions = this._getBaseDimensions(span.resource);
124167
dimensions.metricId = "dependencies/duration";
125168
const statusCode = String(span.attributes["http.status_code"]);
126169
dimensions.dependencyTarget = this._getDependencyTarget(span.attributes);
@@ -130,11 +173,23 @@ export class StandardMetrics {
130173
return dimensions as Attributes;
131174
}
132175

133-
private _getBaseDimensions(span: ReadableSpan): StandardMetricBaseDimensions {
176+
private _getExceptionDimensions(resource: Resource): Attributes {
177+
const dimensions: StandardMetricBaseDimensions = this._getBaseDimensions(resource);
178+
dimensions.metricId = "exceptions/count";
179+
return dimensions as Attributes;
180+
}
181+
182+
private _getTraceDimensions(resource: Resource): Attributes {
183+
const dimensions: StandardMetricBaseDimensions = this._getBaseDimensions(resource);
184+
dimensions.metricId = "traces/count";
185+
return dimensions as Attributes;
186+
}
187+
188+
private _getBaseDimensions(resource: Resource): StandardMetricBaseDimensions {
134189
const dimensions: StandardMetricBaseDimensions = {};
135190
dimensions.IsAutocollected = "True";
136-
if (span.resource) {
137-
const spanResourceAttributes = span.resource.attributes;
191+
if (resource) {
192+
const spanResourceAttributes = resource.attributes;
138193
const serviceName = spanResourceAttributes[SemanticResourceAttributes.SERVICE_NAME];
139194
const serviceNamespace = spanResourceAttributes[SemanticResourceAttributes.SERVICE_NAMESPACE];
140195
if (serviceName) {
@@ -173,4 +228,32 @@ export class StandardMetrics {
173228
}
174229
return "";
175230
}
231+
232+
private _isExceptionTelemetry(logRecord: LogRecord) {
233+
const baseType = logRecord.attributes["_MS.baseType"];
234+
// If Application Insights Legacy logs
235+
if (baseType && baseType === "ExceptionData") {
236+
return true;
237+
} else if (
238+
logRecord.attributes[SemanticAttributes.EXCEPTION_MESSAGE] ||
239+
logRecord.attributes[SemanticAttributes.EXCEPTION_TYPE]
240+
) {
241+
return true;
242+
}
243+
return false;
244+
}
245+
246+
private _isTraceTelemetry(logRecord: LogRecord) {
247+
const baseType = logRecord.attributes["_MS.baseType"];
248+
// If Application Insights Legacy logs
249+
if (baseType && baseType === "MessageData") {
250+
return true;
251+
} else if (
252+
!logRecord.attributes[SemanticAttributes.EXCEPTION_MESSAGE] &&
253+
!logRecord.attributes[SemanticAttributes.EXCEPTION_TYPE]
254+
) {
255+
return true;
256+
}
257+
return false;
258+
}
176259
}

0 commit comments

Comments
 (0)