diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzureMonitorTransmitter.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzureMonitorTransmitter.cs index 896645df562c..f671552203be 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzureMonitorTransmitter.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzureMonitorTransmitter.cs @@ -41,6 +41,26 @@ public AzureMonitorTransmitter(AzureMonitorExporterOptions options, TokenCredent _applicationInsightsRestClient = InitializeRestClient(options, _connectionVars, credential); _fileBlobProvider = InitializeOfflineStorage(options); + + // TODO: uncomment following line for enablement. + // InitializeStatsbeat(_connectionVars); + } + + private static void InitializeStatsbeat(ConnectionVars connectionVars) + { + try + { + // Do not initialize statsbeat for statsbeat. + if (connectionVars != null && connectionVars.InstrumentationKey != ConnectionStringParser.GetValues(Statsbeat.Statsbeat_ConnectionString_EU).InstrumentationKey && connectionVars.InstrumentationKey != ConnectionStringParser.GetValues(Statsbeat.Statsbeat_ConnectionString_NonEU).InstrumentationKey) + { + // TODO: Implement IDisposable for transmitter and dispose statsbeat. + _ = new Statsbeat(connectionVars); + } + } + catch (Exception ex) + { + AzureMonitorExporterEventSource.Log.WriteWarning($"ErrorInitializingStatsBeatfor:{connectionVars.InstrumentationKey}", ex); + } } public string InstrumentationKey => _connectionVars.InstrumentationKey; diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Statsbeat/Statsbeat.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Statsbeat/Statsbeat.cs index 4c30bf8ef5fe..4252341959c6 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Statsbeat/Statsbeat.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Statsbeat/Statsbeat.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -#nullable disable // TODO: remove and fix errors - using System; using System.Collections.Generic; using System.Diagnostics.Metrics; @@ -17,39 +15,37 @@ namespace Azure.Monitor.OpenTelemetry.Exporter.Internals { - internal static class Statsbeat + internal sealed class Statsbeat : IDisposable { - internal const string StatsBeat_ConnectionString_NonEU = ""; + internal const string Statsbeat_ConnectionString_NonEU = "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://NonEU.in.applicationinsights.azure.com/"; - internal const string StatsBeat_ConnectionString_EU = "EU-ConnectionString"; + internal const string Statsbeat_ConnectionString_EU = "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://EU.in.applicationinsights.azure.com/"; private const string AMS_Url = "http://169.254.169.254/metadata/instance/compute?api-version=2017-08-01&format=json"; - internal const int AttachStatsBeatInterval = 86400000; - - private static readonly Meter s_myMeter = new("AttachStatsBeatMeter", "1.0"); + internal const int AttachStatsbeatInterval = 86400000; - private static bool s_isEnabled = true; + private static readonly Meter s_myMeter = new("AttachStatsbeatMeter", "1.0"); - internal static string s_statsBeat_ConnectionString; + internal string? _statsbeat_ConnectionString; - private static string s_resourceProviderId; + private string? _resourceProviderId; - private static string s_resourceProvider; + private string? _resourceProvider; - private static string s_runtimeVersion => SdkVersionUtils.GetVersion(typeof(object)); + private static string? s_runtimeVersion => SdkVersionUtils.GetVersion(typeof(object)); - private static string s_sdkVersion => SdkVersionUtils.GetVersion(typeof(AzureMonitorTraceExporter)); + private static string? s_sdkVersion => SdkVersionUtils.GetVersion(typeof(AzureMonitorTraceExporter)); private static string s_operatingSystem = GetOS(); - private static string s_customer_Ikey; + private readonly string? _customer_Ikey; - internal static MeterProvider s_attachStatsBeatMeterProvider; + internal MeterProvider? _attachStatsbeatMeterProvider; - internal static Regex s_endpoint_pattern = new("^https?://(?:www\\.)?([^/.-]+)"); + internal static Regex s_endpoint_pattern => new("^https?://(?:www\\.)?([^/.-]+)"); - internal static readonly HashSet EU_Endpoints = new() + internal static readonly HashSet s_EU_Endpoints = new() { "francecentral", "francesouth", @@ -64,7 +60,7 @@ internal static class Statsbeat "westeurope", }; - internal static readonly HashSet Non_EU_Endpoints = new() + internal static readonly HashSet s_non_EU_Endpoints = new() { "australiacentral", "australiacentral2", @@ -101,6 +97,35 @@ internal static class Statsbeat "westus3", }; + internal Statsbeat(ConnectionVars connectionStringVars) + { + _statsbeat_ConnectionString = GetStatsbeatConnectionString(connectionStringVars?.IngestionEndpoint); + + // Initialize only if we are able to determine the correct region to send the data to. + if (_statsbeat_ConnectionString == null) + { + throw new InvalidOperationException("Cannot initialize statsbeat"); + } + + _customer_Ikey = connectionStringVars?.InstrumentationKey; + + s_myMeter.CreateObservableGauge("AttachStatsbeat", () => GetAttachStatsbeat()); + + // Configure for attach statsbeat which has collection + // schedule of 24 hrs == 86400000 milliseconds. + // TODO: Follow up in spec to confirm the behavior + // in case if the app exits before 24hrs duration. + var exporterOptions = new AzureMonitorExporterOptions(); + exporterOptions.DisableOfflineStorage = true; + exporterOptions.ConnectionString = _statsbeat_ConnectionString; + + _attachStatsbeatMeterProvider = Sdk.CreateMeterProviderBuilder() + .AddMeter("AttachStatsbeatMeter") + .AddReader(new PeriodicExportingMetricReader(new AzureMonitorMetricExporter(exporterOptions), AttachStatsbeatInterval) + { TemporalityPreference = MetricReaderTemporalityPreference.Delta }) + .Build(); + } + private static string GetOS() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -119,90 +144,54 @@ private static string GetOS() return "unknown"; } - internal static void InitializeAttachStatsbeat(string connectionString) + internal static string? GetStatsbeatConnectionString(string? ingestionEndpoint) { - // check whether it is disabled or already initialized. - if (s_isEnabled && s_attachStatsBeatMeterProvider == null) + var patternMatch = s_endpoint_pattern.Match(ingestionEndpoint); + string? statsbeatConnectionString = null; + if (patternMatch.Success) { - if (s_statsBeat_ConnectionString == null) + var endpoint = patternMatch.Groups[1].Value; + if (s_EU_Endpoints.Contains(endpoint)) { - var parsedConectionString = ConnectionStringParser.GetValues(connectionString); - SetCustomerIkey(parsedConectionString.InstrumentationKey); - SetStatsbeatConnectionString(parsedConectionString.IngestionEndpoint); + statsbeatConnectionString = Statsbeat_ConnectionString_EU; } - - if (!s_isEnabled) + else if (s_non_EU_Endpoints.Contains(endpoint)) { - // TODO: log - return; + statsbeatConnectionString = Statsbeat_ConnectionString_NonEU; } - - s_myMeter.CreateObservableGauge("AttachStatsBeat", () => GetAttachStatsBeat()); - - // Configure for attach statsbeat which has collection - // schedule of 24 hrs == 86400000 milliseconds. - // TODO: Follow up in spec to confirm the behavior - // in case if the app exits before 24hrs duration. - var exporterOptions = new AzureMonitorExporterOptions(); - exporterOptions.DisableOfflineStorage = true; - exporterOptions.ConnectionString = s_statsBeat_ConnectionString; - - s_attachStatsBeatMeterProvider = Sdk.CreateMeterProviderBuilder() - .AddMeter("AttachStatsBeatMeter") - .AddReader(new PeriodicExportingMetricReader(new AzureMonitorMetricExporter(exporterOptions), AttachStatsBeatInterval) - { TemporalityPreference = MetricReaderTemporalityPreference.Delta }) - .Build(); } - } - internal static void SetCustomerIkey(string instrumentationKey) - { - s_customer_Ikey = instrumentationKey; + return statsbeatConnectionString; } - internal static void SetStatsbeatConnectionString(string ingestionEndpoint) + private Measurement GetAttachStatsbeat() { - var patternMatch = s_endpoint_pattern.Match(ingestionEndpoint); - if (patternMatch.Success) + try { - var endpoint = patternMatch.Groups[1].Value; - if (EU_Endpoints.Contains(endpoint)) - { - s_statsBeat_ConnectionString = StatsBeat_ConnectionString_EU; - } - else if (Non_EU_Endpoints.Contains(endpoint)) - { - s_statsBeat_ConnectionString = StatsBeat_ConnectionString_NonEU; - } - else + if (_resourceProvider == null) { - // disable statsbeat - s_isEnabled = false; + SetResourceProviderDetails(); } - } - } - private static Measurement GetAttachStatsBeat() - { - if (s_resourceProvider == null) + return + new Measurement(1, + new("rp", _resourceProvider), + new("rpId", _resourceProviderId), + new("attach", "sdk"), + new("cikey", _customer_Ikey), + new("runtimeVersion", s_runtimeVersion), + new("language", "dotnet"), + new("version", s_sdkVersion), + new("os", s_operatingSystem)); + } + catch (Exception ex) { - SetResourceProviderDetails(); + AzureMonitorExporterEventSource.Log.WriteWarning("ErrorGettingStatsbeatData", ex); + return new Measurement(); } - - // TODO: Add os to the list - return - new Measurement(1, - new("rp", s_resourceProvider), - new("rpId", s_resourceProviderId), - new("attach", "sdk"), - new("cikey", s_customer_Ikey), - new("runtimeVersion", s_runtimeVersion), - new("language", "dotnet"), - new("version", s_sdkVersion), - new("os", s_operatingSystem)); } - private static VmMetadataResponse GetVmMetadataResponse() + private static VmMetadataResponse? GetVmMetadataResponse() { try { @@ -222,17 +211,17 @@ private static VmMetadataResponse GetVmMetadataResponse() } } - private static void SetResourceProviderDetails() + private void SetResourceProviderDetails() { var appSvcWebsiteName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME"); if (appSvcWebsiteName != null) { - s_resourceProvider = "appsvc"; - s_resourceProviderId = appSvcWebsiteName; + _resourceProvider = "appsvc"; + _resourceProviderId = appSvcWebsiteName; var appSvcWebsiteHostName = Environment.GetEnvironmentVariable("WEBSITE_HOME_STAMPNAME"); if (!string.IsNullOrEmpty(appSvcWebsiteHostName)) { - s_resourceProviderId += "/" + appSvcWebsiteHostName; + _resourceProviderId += "/" + appSvcWebsiteHostName; } return; @@ -241,8 +230,8 @@ private static void SetResourceProviderDetails() var functionsWorkerRuntime = Environment.GetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME"); if (functionsWorkerRuntime != null) { - s_resourceProvider = "functions"; - s_resourceProviderId = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME"); + _resourceProvider = "functions"; + _resourceProviderId = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME"); return; } @@ -251,8 +240,8 @@ private static void SetResourceProviderDetails() if (vmMetadata != null) { - s_resourceProvider = "vm"; - s_resourceProviderId = s_resourceProviderId = vmMetadata.vmId + "/" + vmMetadata.subscriptionId; + _resourceProvider = "vm"; + _resourceProviderId = _resourceProviderId = vmMetadata.vmId + "/" + vmMetadata.subscriptionId; // osType takes precedence. s_operatingSystem = vmMetadata.osType.ToLower(CultureInfo.InvariantCulture); @@ -260,8 +249,13 @@ private static void SetResourceProviderDetails() return; } - s_resourceProvider = "unknown"; - s_resourceProviderId = "unknown"; + _resourceProvider = "unknown"; + _resourceProviderId = "unknown"; + } + + public void Dispose() + { + _attachStatsbeatMeterProvider?.Dispose(); } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/StatsbeatTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/StatsbeatTests.cs index b8130eb441e6..407336d0b7d7 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/StatsbeatTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/StatsbeatTests.cs @@ -2,11 +2,6 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Azure.Core; using Azure.Monitor.OpenTelemetry.Exporter.Internals; using Azure.Monitor.OpenTelemetry.Exporter.Internals.ConnectionString; using Xunit; @@ -29,15 +24,11 @@ public class StatsbeatTests [InlineData("westeurope")] public void StatsbeatConnectionStringIsSetBasedOnCustomersConnectionStringEndpointInEU(string euEndpoint) { - var customer_ConnectionString = $"InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://{euEndpoint}.in.applicationinsights.azure.com/"; - var parsedConectionString = ConnectionStringParser.GetValues(customer_ConnectionString); + var customer_ConnectionString = $"InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://{euEndpoint}.in.applicationinsights.azure.com/"; + var connectionStringVars = ConnectionStringParser.GetValues(customer_ConnectionString); + var statsBeatInstance = new Statsbeat(connectionStringVars); - Statsbeat.SetStatsbeatConnectionString(parsedConectionString.IngestionEndpoint); - - Assert.Equal(Statsbeat.StatsBeat_ConnectionString_EU, Statsbeat.s_statsBeat_ConnectionString); - - // Reset Statsbeat Connection String - Statsbeat.s_statsBeat_ConnectionString = null; + Assert.Equal(Statsbeat.Statsbeat_ConnectionString_EU, statsBeatInstance._statsbeat_ConnectionString); } [Theory] @@ -76,29 +67,20 @@ public void StatsbeatConnectionStringIsSetBasedOnCustomersConnectionStringEndpoi [InlineData("westus3")] public void StatsbeatConnectionStringIsSetBasedOnCustomersConnectionStringEndpointInNonEU(string nonEUEndpoint) { - var customer_ConnectionString = $"InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://{nonEUEndpoint}.in.applicationinsights.azure.com/"; - var parsedConectionString = ConnectionStringParser.GetValues(customer_ConnectionString); - - Statsbeat.SetStatsbeatConnectionString(parsedConectionString.IngestionEndpoint); + var customer_ConnectionString = $"InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://{nonEUEndpoint}.in.applicationinsights.azure.com/"; + var connectionStringVars = ConnectionStringParser.GetValues(customer_ConnectionString); + var statsBeatInstance = new Statsbeat(connectionStringVars); - Assert.Equal(Statsbeat.StatsBeat_ConnectionString_NonEU, Statsbeat.s_statsBeat_ConnectionString); - - // Reset Statsbeat Connection String - Statsbeat.s_statsBeat_ConnectionString = null; + Assert.Equal(Statsbeat.Statsbeat_ConnectionString_NonEU, statsBeatInstance._statsbeat_ConnectionString); } [Fact] public void StatsbeatIsNotInitializedForUnknownRegions() { - var customer_ConnectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://foo.in.applicationinsights.azure.com/"; - var parsedConectionString = ConnectionStringParser.GetValues(customer_ConnectionString); - - Statsbeat.SetStatsbeatConnectionString(parsedConectionString.IngestionEndpoint); - - Assert.Null(Statsbeat.s_statsBeat_ConnectionString); + var customer_ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://foo.in.applicationinsights.azure.com/"; - // Reset Statsbeat Connection String - Statsbeat.s_statsBeat_ConnectionString = null; + var connectionStringVars = ConnectionStringParser.GetValues(customer_ConnectionString); + Assert.Throws(() => new Statsbeat(connectionStringVars)); } } }