Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should be an internal implementation and shouldn't be a new API.
Please mark this class and methods as internal.

{
public static OpenTelemetry.Logs.OpenTelemetryLoggerOptions AddLiveMetrics(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action<Azure.Monitor.OpenTelemetry.LiveMetrics.LiveMetricsExporterOptions> configure = null) { throw null; }
public static OpenTelemetry.Trace.TracerProviderBuilder AddLiveMetrics(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action<Azure.Monitor.OpenTelemetry.LiveMetrics.LiveMetricsExporterOptions> 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<Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DerivedMetricInfoAggregation>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this a public API?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This will go away.

Builds are currently failing because our api files are out of sync.
I'm addressing this in #42919

{
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<Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentFilterConjunctionGroupInfoTelemetryType>
{
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<Azure.Monitor.OpenTelemetry.LiveMetrics.Models.DocumentIngressDocumentType>
{
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<Azure.Monitor.OpenTelemetry.LiveMetrics.Models.FilterInfoPredicate>
{
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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public LiveMetricsExporterOptions() { }
}
public static partial class LiveMetricsExtensions
{
public static OpenTelemetry.Logs.OpenTelemetryLoggerOptions AddLiveMetrics(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action<Azure.Monitor.OpenTelemetry.LiveMetrics.LiveMetricsExporterOptions> configure = null) { throw null; }
public static OpenTelemetry.Trace.TracerProviderBuilder AddLiveMetrics(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action<Azure.Monitor.OpenTelemetry.LiveMetrics.LiveMetricsExporterOptions> configure = null, string name = null) { throw null; }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

FormattedMessage, if available, gives a string like "Hello World".
Body gives a string like "Hello {name}".

It seems that Application Insights would always build the fully formatted string. I don't think we should do that here. In a future PR, I think we could explore building a string that contains the template and the key-value-pairs. (ex: "Hello {name}. name: World").

I don't want to do this extra work until I have the unit test project up and working for this project. I propose leaving this as a TODO for now.

// TODO: Properties = new Dictionary<string, string>(), - UX supports up to 10 custom properties
};
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsHttpSuccess(Activity activity, string? responseCode)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, Manager> _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))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does it mean if a customer provides an empty connection string? Do we still continue in this case?

{
lock (_lockObj)
{
if (!_runners.TryGetValue(key, out runner))
{
runner = new Manager(options, new DefaultPlatform());

_runners.Add(key, runner);
}
}
}

return runner;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
});
}

/// <summary>
/// TODO: Add documentation.
/// </summary>
/// <param name="loggerOptions"></param>
/// <param name="configure"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static OpenTelemetryLoggerOptions AddLiveMetrics(
this OpenTelemetryLoggerOptions loggerOptions,
Action<LiveMetricsExporterOptions> 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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should use ServiceCollections rather than getting an instance from singleton. This way we could avoid the complete ManagerFactory. Please note live metrics support is only available through distro.

return new LiveMetricsLogProcessor(manager);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -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<LogRecord>
{
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();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If manager is shared for the same options, what happens to others which are using this manager if you dispose one of them?

}
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);
}
}
}
Loading