diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/CHANGELOG.md index ef4ce049a5b1..f79b3f6219ac 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/CHANGELOG.md +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/CHANGELOG.md @@ -4,6 +4,10 @@ ### Features Added +* Add Logs to the Documents which are exported to LiveMetrics. + These are displayed in the right-side of the Live Metrics UX. + ([#42889](https://github.com/Azure/azure-sdk-for-net/pull/42889)) + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/api/Azure.Monitor.OpenTelemetry.LiveMetrics.net6.0.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/api/Azure.Monitor.OpenTelemetry.LiveMetrics.net6.0.cs new file mode 100644 index 000000000000..d024be3d7111 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/api/Azure.Monitor.OpenTelemetry.LiveMetrics.net6.0.cs @@ -0,0 +1,106 @@ +namespace Azure.Monitor.OpenTelemetry.LiveMetrics +{ + public partial class LiveMetricsExporterOptions : Azure.Core.ClientOptions + { + public LiveMetricsExporterOptions() { } + public string ConnectionString { get { throw null; } set { } } + public Azure.Core.TokenCredential Credential { get { throw null; } set { } } + public bool EnableLiveMetrics { get { throw null; } set { } } + } + public static partial class LiveMetricsExtensions + { + public static OpenTelemetry.Logs.OpenTelemetryLoggerOptions AddLiveMetrics(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action configure = null) { throw null; } + public static OpenTelemetry.Trace.TracerProviderBuilder AddLiveMetrics(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configure = null, string name = null) { throw null; } + } +} +namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Models +{ + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct DerivedMetricInfoAggregation : System.IEquatable + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public DerivedMetricInfoAggregation(string value) { throw null; } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation Avg { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation Max { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation Min { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation Sum { get { throw null; } } + public bool Equals(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation other) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override bool Equals(object obj) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override int GetHashCode() { throw null; } + public static bool operator ==(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation left, Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation right) { throw null; } + public static implicit operator Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation (string value) { throw null; } + public static bool operator !=(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation left, Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation right) { throw null; } + public override string ToString() { throw null; } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct DocumentFilterConjunctionGroupInfoTelemetryType : System.IEquatable + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public DocumentFilterConjunctionGroupInfoTelemetryType(string value) { throw null; } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType Dependency { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType Event { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType Exception { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType Metric { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType PerformanceCounter { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType Request { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType Trace { get { throw null; } } + public bool Equals(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType other) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override bool Equals(object obj) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override int GetHashCode() { throw null; } + public static bool operator ==(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType left, Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType right) { throw null; } + public static implicit operator Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType (string value) { throw null; } + public static bool operator !=(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType left, Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType right) { throw null; } + public override string ToString() { throw null; } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct DocumentIngressDocumentType : System.IEquatable + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public DocumentIngressDocumentType(string value) { throw null; } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType Event { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType Exception { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType RemoteDependency { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType Request { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType Trace { get { throw null; } } + public bool Equals(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType other) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override bool Equals(object obj) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override int GetHashCode() { throw null; } + public static bool operator ==(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType left, Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType right) { throw null; } + public static implicit operator Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType (string value) { throw null; } + public static bool operator !=(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType left, Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType right) { throw null; } + public override string ToString() { throw null; } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct FilterInfoPredicate : System.IEquatable + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public FilterInfoPredicate(string value) { throw null; } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate Contains { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate DoesNotContain { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate Equal { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate GreaterThan { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate GreaterThanOrEqual { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate LessThan { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate LessThanOrEqual { get { throw null; } } + public static Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate NotEqual { get { throw null; } } + public bool Equals(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate other) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override bool Equals(object obj) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public override int GetHashCode() { throw null; } + public static bool operator ==(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate left, Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate right) { throw null; } + public static implicit operator Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate (string value) { throw null; } + public static bool operator !=(Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate left, Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate right) { throw null; } + public override string ToString() { throw null; } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/api/Azure.Monitor.OpenTelemetry.LiveMetrics.netstandard2.0.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/api/Azure.Monitor.OpenTelemetry.LiveMetrics.netstandard2.0.cs index 392678ed703d..d024be3d7111 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/api/Azure.Monitor.OpenTelemetry.LiveMetrics.netstandard2.0.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/api/Azure.Monitor.OpenTelemetry.LiveMetrics.netstandard2.0.cs @@ -9,6 +9,7 @@ public LiveMetricsExporterOptions() { } } public static partial class LiveMetricsExtensions { + public static OpenTelemetry.Logs.OpenTelemetryLoggerOptions AddLiveMetrics(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action configure = null) { throw null; } public static OpenTelemetry.Trace.TracerProviderBuilder AddLiveMetrics(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configure = null, string name = null) { throw null; } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/DataCollection/DocumentHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/DataCollection/DocumentHelper.cs index 3ae76567b9c3..c130e0ebe5de 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/DataCollection/DocumentHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/DataCollection/DocumentHelper.cs @@ -8,6 +8,7 @@ using System.Text; using Azure.Monitor.OpenTelemetry.Exporter.Internals; using Azure.Monitor.OpenTelemetry.LiveMetrics.Models; +using OpenTelemetry.Logs; using ExceptionDocument = Azure.Monitor.OpenTelemetry.LiveMetrics.Models.Exception; namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Internals.DataCollection @@ -170,6 +171,16 @@ internal static ExceptionDocument CreateException(string exceptionType, string e return exceptionDocumentIngress; } + internal static Models.Trace ConvertToTrace(LogRecord logRecord) + { + return new Models.Trace() + { + DocumentType = DocumentIngressDocumentType.Trace, + Message = logRecord.FormattedMessage ?? logRecord.Body, // TODO: MAY NEED TO BUILD THE FORMATTED MESSAGE IF NOT AVAILABLE + // TODO: Properties = new Dictionary(), - UX supports up to 10 custom properties + }; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsHttpSuccess(Activity activity, string? responseCode) { diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/ManagerFactory.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/ManagerFactory.cs new file mode 100644 index 000000000000..78e630c913e2 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/ManagerFactory.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Azure.Monitor.OpenTelemetry.Exporter.Internals.Platform; + +namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Internals +{ + internal sealed class ManagerFactory + { + public static readonly ManagerFactory Instance = new(); + + internal readonly Dictionary _runners = new(); + private readonly object _lockObj = new(); + + public Manager Get(LiveMetricsExporterOptions options) + { + var key = options.ConnectionString ?? string.Empty; + + if (!_runners.TryGetValue(key, out Manager? runner)) + { + lock (_lockObj) + { + if (!_runners.TryGetValue(key, out runner)) + { + runner = new Manager(options, new DefaultPlatform()); + + _runners.Add(key, runner); + } + } + } + + return runner; + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsExtensions.cs index 22784012e929..9a9e893d69a0 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsExtensions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsExtensions.cs @@ -4,10 +4,10 @@ #nullable disable using System; -using Azure.Monitor.OpenTelemetry.Exporter.Internals.Platform; using Azure.Monitor.OpenTelemetry.LiveMetrics.Internals; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -67,9 +67,36 @@ public static TracerProviderBuilder AddLiveMetrics( } // INITIALIZE INTERNALS - var manager = new Manager(exporterOptions, new DefaultPlatform()); + var manager = ManagerFactory.Instance.Get(exporterOptions); return new LiveMetricsActivityProcessor(manager); }); } + + /// + /// TODO: Add documentation. + /// + /// + /// + /// + /// + public static OpenTelemetryLoggerOptions AddLiveMetrics( + this OpenTelemetryLoggerOptions loggerOptions, + Action configure = null) + { + if (loggerOptions == null) + { + throw new ArgumentNullException(nameof(loggerOptions)); + } + + return loggerOptions.AddProcessor(sp => + { + var options = new LiveMetricsExporterOptions(); + configure?.Invoke(options); + + // INITIALIZE INTERNALS + var manager = ManagerFactory.Instance.Get(options); + return new LiveMetricsLogProcessor(manager); + }); + } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsLogProcessor.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsLogProcessor.cs new file mode 100644 index 000000000000..891913be9e72 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsLogProcessor.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Monitor.OpenTelemetry.LiveMetrics.Internals; +using Azure.Monitor.OpenTelemetry.LiveMetrics.Internals.DataCollection; +using OpenTelemetry; +using OpenTelemetry.Logs; + +namespace Azure.Monitor.OpenTelemetry.LiveMetrics +{ + internal class LiveMetricsLogProcessor : BaseProcessor + { + private bool _disposed; + private LiveMetricsResource? _resource; + private readonly Manager _manager; + + internal LiveMetricsResource? LiveMetricsResource => _resource ??= ParentProvider?.GetResource().CreateAzureMonitorResource(); + + public LiveMetricsLogProcessor(Manager manager) + { + _manager = manager; + } + + public override void OnEnd(LogRecord data) + { + // Check if live metrics is enabled. + if (!_manager.ShouldCollect()) + { + return; + } + + // Resource is not available at initialization and must be set later. + if (_manager.LiveMetricsResource == null && LiveMetricsResource != null) + { + _manager.LiveMetricsResource = LiveMetricsResource; + } + + if (data.Exception is null) + { + AddLogDocument(data); + } + else + { + AddExceptionDocument(data.Exception); + } + } + + protected override void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + try + { + _manager.Dispose(); + } + catch (System.Exception) + { + } + } + + _disposed = true; + } + + base.Dispose(disposing); + } + + private void AddLogDocument(LogRecord logRecord) + { + var logDocument = DocumentHelper.ConvertToTrace(logRecord); + _manager._documentBuffer.WriteDocument(logDocument); + } + + private void AddExceptionDocument(Exception exception) + { + var exceptionDocument = DocumentHelper.CreateException(exceptionType: exception.GetType().Name, exceptionMessage: exception.Message); + _manager._documentBuffer.WriteDocument(exceptionDocument); + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/tests/Azure.Monitor.OpenTelemetry.LiveMetrics.Demo/Program.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/tests/Azure.Monitor.OpenTelemetry.LiveMetrics.Demo/Program.cs index 282d41afc483..2ed9d9104718 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/tests/Azure.Monitor.OpenTelemetry.LiveMetrics.Demo/Program.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/tests/Azure.Monitor.OpenTelemetry.LiveMetrics.Demo/Program.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using OpenTelemetry; using OpenTelemetry.Trace; @@ -13,6 +14,7 @@ internal class Program { private const string ActivitySourceName = "MyCompany.MyProduct.MyLibrary"; private static readonly ActivitySource s_activitySource = new(ActivitySourceName); + private static ILogger _logger; private const string ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000"; @@ -28,6 +30,17 @@ public static async Task Main(string[] args) .AddLiveMetrics(configure => configure.ConnectionString = ConnectionString) .Build(); + using var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddOpenTelemetry(options => + { + //options.IncludeFormattedMessage = true; + options.AddLiveMetrics(configure => configure.ConnectionString = ConnectionString); + }); + }); + + _logger = loggerFactory.CreateLogger("DemoLogger"); + Console.WriteLine("Press any key to stop the loop."); // Loop until a key is pressed @@ -71,7 +84,7 @@ private static async Task GenerateTelemetry() Console.WriteLine("Request Exception"); try { - throw new Exception("Test exception"); + throw new Exception("Test Request Exception"); } catch (Exception ex) { @@ -94,7 +107,7 @@ private static async Task GenerateTelemetry() Console.WriteLine("Dependency Exception"); try { - throw new Exception("Test exception"); + throw new Exception("Test Dependency Exception"); } catch (Exception ex) { @@ -105,6 +118,38 @@ private static async Task GenerateTelemetry() } } + // Logs + if (GetRandomBool(percent: 70)) + { + Console.WriteLine("Log"); + + _logger.Log( + logLevel: LogLevel.Information, + eventId: 0, + exception: null, + message: "Hello {name}.", + args: new object[] { "World" }); + + // Exception + if (GetRandomBool(percent: 40)) + { + Console.WriteLine("Log Exception"); + try + { + throw new Exception("Test Log Exception"); + } + catch (Exception ex) + { + _logger.Log( + logLevel: LogLevel.Error, + eventId: 0, + exception: ex, + message: "Hello {name}.", + args: new object[] { "World" }); + } + } + } + // Release the allocated memory memoryChunk = null; }