Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,19 @@ to `console` methods by using `setAutoCollectConsole(true, true)`.

Note that by default `enableWebInstrumentation` will use the connection string for SDK initialization. If you want to use a different one, you can set it as `enableWebInstrumentation(true, "your-connection-string")`.

The TelemetryClient object contains a `config` property with many optional settings. These can be set as follows:
The TelemetryClient constructor accepts optional settings (e.g., `{ useGlobalProviders?: boolean }`, defaults to `true`) and exposes a `config` property with many optional settings. Constructor options example:
```javascript
const client = new appInsights.TelemetryClient(<connectionString>, { useGlobalProviders: false });
```
Client `config` properties can be set as follows:
```
client.config.PROPERTYNAME = VALUE;
```
These properties are client specific, so you can configure `appInsights.defaultClient`
separately from clients created with `new appInsights.TelemetryClient()`.

> *Important:* OpenTelemetry instrumentations rely on the global provider registry. Both `appInsights.setup().start()` and `new TelemetryClient()` default to `useGlobalProviders: true` so auto-instrumentation works out of the box. Set `{ useGlobalProviders: false }` only when you need an isolated client (e.g., per-tenant/manual-only or tests); auto-instrumentation and all auto-collect “enable*” configs (requests, dependencies, console, etc.) will not target that client, you must emit manually or attach your own processors/exporters. Multiple clients in one process share global providers, so use the opt-out to avoid mixing their telemetry.

| Property | Description |
| ------------------------------- |------------------------------------------------------------------------------------------------------------|
| proxyHttpUrl | A proxy server for SDK HTTP traffic (Optional, Default pulled from `http_proxy` environment variable) |
Expand Down
8 changes: 0 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@
"@opentelemetry/api-logs": "^0.208.0",
"@opentelemetry/core": "^2.2.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.208.0",
"@opentelemetry/exporter-metrics-otlp-http": "^0.208.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.208.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
"@opentelemetry/otlp-exporter-base": "^0.208.0",
"@opentelemetry/resources": "^2.2.0",
"@opentelemetry/sdk-logs": "^0.208.0",
Expand Down
8 changes: 2 additions & 6 deletions src/shared/configuration/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@

import { AzureMonitorExporterOptions } from "@azure/monitor-opentelemetry-exporter";
import { diag } from "@opentelemetry/api";
import {
Resource,
defaultResource,
} from "@opentelemetry/resources";
import { Resource, defaultResource } from "@opentelemetry/resources";
import { JsonConfig } from "./jsonConfig";
import { AzureMonitorOpenTelemetryOptions, OTLPExporterConfig, InstrumentationOptions } from "../../types";
import { logLevelParser } from "../util/logLevelParser";
Expand Down Expand Up @@ -169,7 +166,6 @@ export class ApplicationInsightsConfig {

private _getDefaultResource(): Resource {
// Create a basic resource with default attributes
const resource = defaultResource();
return resource;
return defaultResource();
}
}
7 changes: 4 additions & 3 deletions src/shared/util/attributeLogRecordProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LogRecordProcessor, SdkLogRecord } from "@opentelemetry/sdk-logs";
import { LogRecordProcessor, ReadableLogRecord } from "@opentelemetry/sdk-logs";

export class AttributeLogProcessor implements LogRecordProcessor {
private _attributes: { [key: string]: string };
Expand All @@ -7,8 +7,9 @@ export class AttributeLogProcessor implements LogRecordProcessor {
}

// Override onEmit to apply log record attributes before exporting
onEmit(record: SdkLogRecord) {
record.setAttributes(this._attributes);
onEmit(record: ReadableLogRecord) {
const attributes = (record as any).attributes || ((record as any).attributes = {});
Object.assign(attributes, this._attributes);
}

shutdown(): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion src/shim/applicationinsights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export let defaultClient: TelemetryClient;
*/
export function setup(setupString?: string) {
if (!defaultClient) {
defaultClient = new TelemetryClient(setupString);
defaultClient = new TelemetryClient(setupString, { useGlobalProviders: true });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit confused here, I though the title meant not to use the global providers for telemetry client, but here we are setting it to True.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only scenario where we want to enable it, the default client when the SDK is initialized using

let appInsights = require("applicationinsights");
appInsights.setup("<YOUR_CONNECTION_STRING>").start();

} else {
defaultClient.pushWarningToLog("Setup has already been called once. To set up a new client, please use TelemetryClient instead.")
}
Expand Down
17 changes: 15 additions & 2 deletions src/shim/correlationContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,24 @@
activeSpan = trace.getTracer(CONTEXT_NAME).startSpan(CONTEXT_NAME) as Span;
}
const traceStateObj: TraceState = new TraceState(activeSpan?.spanContext()?.traceState?.serialize());
const parentSpanId = this._getParentSpanId(activeSpan);

return this.spanToContextObject(activeSpan?.spanContext(), activeSpan?.parentSpanContext?.spanId, activeSpan?.name, traceStateObj);
return this.spanToContextObject(activeSpan?.spanContext(), parentSpanId, activeSpan?.name, traceStateObj);
}
return null;
}

private static _getParentSpanId(span: Span | null): string | undefined {
if (!span) {
return undefined;
}
const spanAny = span as any;
if (typeof spanAny.parentSpanContext === "function") {
return spanAny.parentSpanContext()?.spanId;
}
return spanAny.parentSpanId || spanAny.parentSpanContext?.spanId;
}

/**
* Helper to generate objects conforming to the CorrelationContext interface
* @param operationId String assigned to a series of related telemetry items - equivalent to OpenTelemetry traceId
Expand Down Expand Up @@ -93,8 +105,8 @@
},
// Headers are not being used so custom properties will always be stubbed out
customProperties: {
getProperty(prop: string) { return "" },

Check warning on line 108 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22.x)

'prop' is defined but never used

Check warning on line 108 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 20.x)

'prop' is defined but never used

Check warning on line 108 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 18.x)

'prop' is defined but never used

Check warning on line 108 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 16.x)

'prop' is defined but never used

Check warning on line 108 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (18.x, x86)

'prop' is defined but never used

Check warning on line 108 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (22.x, x86)

'prop' is defined but never used

Check warning on line 108 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (16.x, x86)

'prop' is defined but never used

Check warning on line 108 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (20.x, x86)

'prop' is defined but never used
setProperty(prop: string) { return "" },

Check warning on line 109 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22.x)

'prop' is defined but never used

Check warning on line 109 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 20.x)

'prop' is defined but never used

Check warning on line 109 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 18.x)

'prop' is defined but never used

Check warning on line 109 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 16.x)

'prop' is defined but never used

Check warning on line 109 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (18.x, x86)

'prop' is defined but never used

Check warning on line 109 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (22.x, x86)

'prop' is defined but never used

Check warning on line 109 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (16.x, x86)

'prop' is defined but never used

Check warning on line 109 in src/shim/correlationContextManager.ts

View workflow job for this annotation

GitHub Actions / build (20.x, x86)

'prop' is defined but never used
} as ICustomProperties,
}
}
Expand Down Expand Up @@ -181,9 +193,10 @@

if (span) {
trace.setSpanContext(context.active(), span.spanContext());
const parentSpanId = this._getParentSpanId(span);
return this.spanToContextObject(
span.spanContext(),
span.parentSpanContext?.spanId,
parentSpanId,
);
}

Expand Down
5 changes: 2 additions & 3 deletions src/shim/logsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license.

import { Logger as OtelLogger, LogRecord } from "@opentelemetry/api-logs";
import { SdkLogRecord as SDKLogRecord } from "@opentelemetry/sdk-logs";
import { Attributes, diag } from "@opentelemetry/api";
import { IdGenerator, RandomIdGenerator } from "@opentelemetry/sdk-trace-base";

Expand Down Expand Up @@ -71,7 +70,7 @@ export class LogApi {
*/
public trackTrace(telemetry: Contracts.TraceTelemetry): void {
try {
const logRecord = this._traceToLogRecord(telemetry) as SDKLogRecord;
const logRecord = this._traceToLogRecord(telemetry);
this._logger.emit(logRecord);
} catch (err) {
diag.error("Failed to send telemetry.", err);
Expand All @@ -89,7 +88,7 @@ export class LogApi {
try {
const logRecord = this._exceptionToLogRecord(
telemetry
) as SDKLogRecord;
);
this._logger.emit(logRecord);
} catch (err) {
diag.error("Failed to send telemetry.", err);
Expand Down
76 changes: 52 additions & 24 deletions src/shim/telemetryClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { Attributes, context, metrics, SpanKind, SpanOptions, SpanStatusCode, diag, trace } from "@opentelemetry/api";
import { Attributes, Meter, Tracer, context, metrics, SpanKind, SpanOptions, SpanStatusCode, diag, trace } from "@opentelemetry/api";
import { logs } from "@opentelemetry/api-logs";
import {
SEMATTRS_DB_STATEMENT,
Expand All @@ -24,7 +24,8 @@ import { AttributeLogProcessor } from "../shared/util/attributeLogRecordProcesso
import { LogApi } from "./logsApi";
import { flushAzureMonitor, shutdownAzureMonitor, useAzureMonitor } from "../main";
import { AzureMonitorOpenTelemetryOptions } from "../types";
import { UNSUPPORTED_MSG, StatsbeatFeature } from "./types";
import { TelemetryClientProvider } from "./telemetryClientProvider";
import { TelemetryClientOptions, UNSUPPORTED_MSG, StatsbeatFeature } from "./types";
import { StatsbeatFeaturesManager } from "../shared/util/statsbeatFeaturesManager";

/**
Expand All @@ -41,13 +42,17 @@ export class TelemetryClient {
private _logApi: LogApi;
private _isInitialized: boolean;
private _options: AzureMonitorOpenTelemetryOptions;
private _telemetryClientProvider?: TelemetryClientProvider;
private _useGlobalProviders: boolean;
private _manualTracer?: Tracer;
private _manualMeter?: Meter;
private _configWarnings: string[] = [];

/**
* Constructs a new instance of TelemetryClient
* @param setupString the Connection String or Instrumentation Key to use (read from environment variable if not specified)
*/
constructor(input?: string) {
constructor(input?: string, options?: TelemetryClientOptions) {
TelemetryClient._instanceCount++;

// Set statsbeat feature if this is the second or subsequent TelemetryClient instance
Expand All @@ -60,41 +65,38 @@ export class TelemetryClient {
this.commonProperties = {};
this.context = new Context();
this._isInitialized = false;
this._useGlobalProviders = options?.useGlobalProviders ?? true;
}

public initialize() {
if (this._isInitialized) {
return;
}
this._isInitialized = true;
// Parse shim config to Azure Monitor options
this._options = this.config.parseConfig();

try {
// Create attribute processors with context tags and common properties
this._attributeSpanProcessor = new AttributeSpanProcessor({ ...this.context.tags, ...this.commonProperties });
this._attributeLogProcessor = new AttributeLogProcessor({ ...this.context.tags, ...this.commonProperties });
this._options.spanProcessors = [...(this._options.spanProcessors || []), this._attributeSpanProcessor];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition, thanks for noticing this omission!

this._options.logRecordProcessors = [...(this._options.logRecordProcessors || []), this._attributeLogProcessor];

// Add processors to Azure Monitor options before initialization
if (!this._options.spanProcessors) {
this._options.spanProcessors = [];
}
this._options.spanProcessors.push(this._attributeSpanProcessor);

if (!this._options.logRecordProcessors) {
this._options.logRecordProcessors = [];
if (this._useGlobalProviders) {
useAzureMonitor(this._options);
} else {
this._telemetryClientProvider = new TelemetryClientProvider(this._options);
}
this._options.logRecordProcessors.push(this._attributeLogProcessor);

// Initialize Azure Monitor with processors included
useAzureMonitor(this._options);

// LoggerProvider would be initialized when client is instantiated
// Get Logger from global provider
this._logApi = new LogApi(logs.getLogger("ApplicationInsightsLogger"));
const logger = this._useGlobalProviders
? logs.getLogger("ApplicationInsightsLogger")
: this._telemetryClientProvider.getLogger("ApplicationInsightsLogger");
this._logApi = new LogApi(logger);

// Warn if any config warnings were generated during parsing
for (let i = 0; i < this._configWarnings.length; i++) {
diag.warn(this._configWarnings[i]);
}
}
}
catch (error) {
diag.error(`Failed to initialize TelemetryClient ${error}`);
}
Expand Down Expand Up @@ -167,14 +169,34 @@ export class TelemetryClient {
}
// Create custom metric
try {
const meter = metrics.getMeterProvider().getMeter("ApplicationInsightsMetrics");
const meter = this._getMeterInstance();
const histogram = meter.createHistogram(telemetry.name);
histogram.record(telemetry.value, { ...telemetry.properties, ...this.commonProperties, ...this.context.tags });
} catch (error) {
diag.error(`Failed to record metric: ${error}`);
}
}

private _getTracerInstance(): Tracer {
if (this._telemetryClientProvider) {
if (!this._manualTracer) {
this._manualTracer = this._telemetryClientProvider.getTracer("ApplicationInsightsTracer");
}
return this._manualTracer;
}
return trace.getTracer("ApplicationInsightsTracer");
}

private _getMeterInstance(): Meter {
if (this._telemetryClientProvider) {
if (!this._manualMeter) {
this._manualMeter = this._telemetryClientProvider.getMeter("ApplicationInsightsMetrics");
}
return this._manualMeter;
}
return metrics.getMeterProvider().getMeter("ApplicationInsightsMetrics");
}

/**
* Log a request. Note that the default client will attempt to collect HTTP requests automatically so only use this for requests
* that aren't automatically captured or if you've disabled automatic request collection.
Expand Down Expand Up @@ -209,7 +231,7 @@ export class TelemetryClient {
attributes: attributes,
startTime: startTime,
};
const span: any = trace.getTracer("ApplicationInsightsTracer")
const span: any = this._getTracerInstance()
.startSpan(telemetry.name, options, ctx);

if (telemetry.id) {
Expand Down Expand Up @@ -285,7 +307,7 @@ export class TelemetryClient {
attributes: attributes,
startTime: startTime,
};
const span: any = trace.getTracer("ApplicationInsightsTracer")
const span: any = this._getTracerInstance()
.startSpan(telemetry.name, options, ctx);

if (telemetry.id) {
Expand Down Expand Up @@ -381,13 +403,19 @@ export class TelemetryClient {
* Immediately send all queued telemetry.
*/
public async flush(): Promise<void> {
if (this._telemetryClientProvider) {
return this._telemetryClientProvider.flush();
}
return flushAzureMonitor();
}

/**
* Shutdown client
*/
public async shutdown(): Promise<void> {
if (this._telemetryClientProvider) {
return this._telemetryClientProvider.shutdown();
}
return shutdownAzureMonitor();
}

Expand Down
Loading
Loading