diff --git a/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/ILogPropertyCollector.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/ILogPropertyCollector.cs
index bb2d6823f8a..befaee3e7f2 100644
--- a/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/ILogPropertyCollector.cs
+++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/ILogPropertyCollector.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Compliance.Classification;
namespace Microsoft.Extensions.Telemetry.Logging;
@@ -23,4 +25,17 @@ public interface ILogPropertyCollector
/// or when a property of the same name has already been added.
///
void Add(string propertyName, object? propertyValue);
+
+ ///
+ /// Adds a property to the current log record.
+ ///
+ /// The name of the property to add.
+ /// The value of the property to add.
+ /// The data classification of the property value.
+ /// is .
+ /// is empty or contains exclusively whitespace,
+ /// or when a property of the same name has already been added.
+ ///
+ [Experimental]
+ void Add(string propertyName, object? propertyValue, DataClassification classification);
}
diff --git a/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LogMethodHelper.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LogMethodHelper.cs
index bfa1c1ce49d..ff64387a742 100644
--- a/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LogMethodHelper.cs
+++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LogMethodHelper.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Compliance.Classification;
#if NET6_0_OR_GREATER
using Microsoft.Extensions.Logging;
#endif
@@ -37,6 +38,10 @@ public void Add(string propertyName, object? propertyValue)
Add(new KeyValuePair(fullName, propertyValue));
}
+ ///
+ [Experimental]
+ public void Add(string propertyName, object? propertyValue, DataClassification classification) => Add(propertyName, propertyValue);
+
///
/// Resets state of this container as described in .
///
diff --git a/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.cs b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.cs
new file mode 100644
index 00000000000..f7a649d8f1a
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Logging/LoggerMessageState.cs
@@ -0,0 +1,288 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Compliance.Classification;
+#if NET6_0_OR_GREATER
+using Microsoft.Extensions.Logging;
+#endif
+using Microsoft.Extensions.ObjectPool;
+using Microsoft.Extensions.Telemetry.Enrichment;
+using Microsoft.Shared.Diagnostics;
+using Microsoft.Shared.Pools;
+
+namespace Microsoft.Extensions.Telemetry.Logging;
+
+///
+/// Utility type to support generated logging methods.
+///
+///
+/// This type is not intended to be directly invoked by application code,
+/// it is intended to be invoked by generated logging method code.
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+[Experimental]
+public sealed class LoggerMessageState : ILogPropertyCollector, IEnrichmentPropertyBag, IResettable
+{
+ private const string Separator = "_";
+
+ private static readonly ObjectPool _states = PoolFactory.CreateResettingPool();
+
+ ///
+ public void Add(string propertyName, object? propertyValue)
+ {
+ _ = Throw.IfNull(propertyName);
+
+ string fullName = ParameterName.Length > 0 ? ParameterName + Separator + propertyName : propertyName;
+ Properties.Add(new KeyValuePair(fullName, propertyValue));
+ }
+
+ ///
+ public void Add(string propertyName, object? propertyValue, DataClassification classification)
+ {
+ _ = Throw.IfNull(propertyName);
+
+ string fullName = ParameterName.Length > 0 ? ParameterName + Separator + propertyName : propertyName;
+ Properties.Add(new KeyValuePair(fullName, propertyValue));
+ ClassifiedProperties.Add(new(propertyName, propertyValue, classification));
+ }
+
+ ///
+ /// Adds many properties.
+ ///
+ public void AddRange(IEnumerable> properties)
+ {
+ _ = Throw.IfNull(properties);
+ Properties.AddRange(properties);
+ }
+
+ ///
+ /// Resets state of this container as described in .
+ ///
+ ///
+ /// if the object successfully reset and can be reused.
+ ///
+ public bool TryReset()
+ {
+ Properties.Clear();
+ ClassifiedProperties.Clear();
+ ParameterName = string.Empty;
+ return true;
+ }
+
+ ///
+ /// Gets or sets the name of the logging method parameter for which to collect properties.
+ ///
+ public string ParameterName { get; set; } = string.Empty;
+
+ ///
+ /// Enumerates an enumerable into a string.
+ ///
+ /// The enumerable object.
+ ///
+ /// A string representation of the enumerable.
+ ///
+ public static string Stringify(IEnumerable? enumerable)
+ {
+ if (enumerable == null)
+ {
+ return "null";
+ }
+
+ var sb = PoolFactory.SharedStringBuilderPool.Get();
+ _ = sb.Append('[');
+
+ bool first = true;
+ foreach (object? e in enumerable)
+ {
+ if (!first)
+ {
+ _ = sb.Append(',');
+ }
+
+ if (e == null)
+ {
+ _ = sb.Append("null");
+ }
+ else
+ {
+ _ = sb.Append(FormattableString.Invariant($"\"{e}\""));
+ }
+
+ first = false;
+ }
+
+ _ = sb.Append(']');
+ var result = sb.ToString();
+ PoolFactory.SharedStringBuilderPool.Return(sb);
+ return result;
+ }
+
+ ///
+ /// Enumerates an enumerable of key/value pairs into a string.
+ ///
+ /// Type of keys.
+ /// Type of values.
+ /// The enumerable object.
+ ///
+ /// A string representation of the enumerable.
+ ///
+ public static string Stringify(IEnumerable>? enumerable)
+ {
+ if (enumerable == null)
+ {
+ return "null";
+ }
+
+ var sb = PoolFactory.SharedStringBuilderPool.Get();
+ _ = sb.Append('{');
+
+ bool first = true;
+ foreach (var kvp in enumerable)
+ {
+ if (!first)
+ {
+ _ = sb.Append(',');
+ }
+
+ if (typeof(TKey).IsValueType || kvp.Key is not null)
+ {
+ _ = sb.Append(FormattableString.Invariant($"\"{kvp.Key}\"="));
+ }
+ else
+ {
+ _ = sb.Append("null=");
+ }
+
+ if (typeof(TValue).IsValueType || kvp.Value is not null)
+ {
+ _ = sb.Append(FormattableString.Invariant($"\"{kvp.Value}\""));
+ }
+ else
+ {
+ _ = sb.Append("null");
+ }
+
+ first = false;
+ }
+
+ _ = sb.Append('}');
+ var result = sb.ToString();
+ PoolFactory.SharedStringBuilderPool.Return(sb);
+ return result;
+ }
+
+ ///
+ /// Gets the list of properties added to this instance.
+ ///
+ [SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "Not intended for application use")]
+ public List> Properties { get; } = new();
+
+ ///
+ /// Gets an additional list of properties which must receive redaction before being used.
+ ///
+ [SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "Not intended for application use")]
+ public List ClassifiedProperties { get; } = new();
+
+ ///
+ /// Gets or sets an optional formatting delegate to be used by a logging call.
+ ///
+ public object? Formatter { get; set; }
+
+ ///
+ /// Gets or sets optional logging state to be used by a logging call.
+ ///
+ public object? State { get; set; }
+
+ ///
+ /// Gets an instance of this class from the global pool.
+ ///
+ /// A usable instance.
+ public static LoggerMessageState Rent() => _states.Get();
+
+ ///
+ /// Returns an instance of this class to the global pool.
+ ///
+ /// The state instance.
+ public static void Return(LoggerMessageState state) => _states.Return(state);
+
+ ///
+ void IEnrichmentPropertyBag.Add(string key, object value)
+ {
+ _ = Throw.IfNullOrEmpty(key);
+ Properties.Add(new KeyValuePair(key, value));
+ }
+
+ ///
+ void IEnrichmentPropertyBag.Add(string key, string value)
+ {
+ _ = Throw.IfNullOrEmpty(key);
+ Properties.Add(new KeyValuePair(key, value));
+ }
+
+ ///
+ void IEnrichmentPropertyBag.Add(ReadOnlySpan> properties)
+ {
+ foreach (var p in properties)
+ {
+ // we're going from KVP to KVP which is strictly correct, so ignore the complaint
+#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
+ Properties.Add(p);
+#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types.
+ }
+ }
+
+ ///
+ void IEnrichmentPropertyBag.Add(ReadOnlySpan> properties)
+ {
+ foreach (var p in properties)
+ {
+ Properties.Add(new KeyValuePair(p.Key, p.Value));
+ }
+ }
+
+#if NET6_0_OR_GREATER
+ ///
+ /// Gets log define options configured to skip the log level enablement check.
+ ///
+ public static LogDefineOptions SkipEnabledCheckOptions { get; } = new() { SkipEnabledCheck = true };
+#endif
+
+ ///
+ /// Represents a captured property that needs redaction.
+ ///
+ [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Not for customer use and hidden from docs")]
+ [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not needed")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public readonly struct ClassifiedProperty
+ {
+ ///
+ /// Gets the name of the property.
+ ///
+ public readonly string Name { get; }
+
+ ///
+ /// Gets the property's value.
+ ///
+ public readonly object? Value { get; }
+
+ ///
+ /// Gets the property's data classification.
+ ///
+ public readonly DataClassification Classification { get; }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ public ClassifiedProperty(string name, object? value, DataClassification classification)
+ {
+ Name = name;
+ Value = value;
+ Classification = classification;
+ }
+ }
+}
diff --git a/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.csproj b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.csproj
index 4edcb8dc6b3..99e60983dfd 100644
--- a/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.csproj
+++ b/src/Libraries/Microsoft.Extensions.Telemetry.Abstractions/Microsoft.Extensions.Telemetry.Abstractions.csproj
@@ -22,6 +22,10 @@
+
+
+
+
diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLoggerFactory.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLoggerFactory.cs
new file mode 100644
index 00000000000..6618aab9572
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/ExtendedLoggerFactory.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.Compliance.Redaction;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Telemetry.Enrichment;
+
+namespace Microsoft.Extensions.Telemetry.Logging;
+
+internal sealed class ExtendedLoggerFactory : ILoggerFactory
+{
+ private readonly LoggerFactory _loggerFactory;
+ private readonly ConcurrentDictionary _cache = new();
+ private readonly ILogEnricher[] _enrichers;
+ private readonly LoggingOptions _loggingOptions;
+ private readonly IRedactorProvider? _redactorProvider;
+
+ public ExtendedLoggerFactory(
+ IEnumerable providers,
+ IOptionsMonitor filterOption,
+ IOptions loggingOptions,
+ IEnumerable enrichers,
+ IRedactorProvider? redactorProvider = null,
+ IOptions? options = null,
+ IExternalScopeProvider? scopeProvider = null)
+ {
+ _loggerFactory = new LoggerFactory(providers, filterOption, options, scopeProvider);
+ _enrichers = enrichers.ToArray();
+ _loggingOptions = loggingOptions.Value;
+ _redactorProvider = redactorProvider;
+ }
+
+ public ILogger CreateLogger(string categoryName)
+ {
+ return _cache.GetOrAdd(categoryName, (name, lfp)
+ => new Logger(
+ lfp._loggerFactory.CreateLogger(name),
+ _enrichers,
+ _loggingOptions.IncludeStackTrace,
+ _loggingOptions.MaxStackTraceLength,
+ _redactorProvider), this);
+ }
+
+ public void AddProvider(ILoggerProvider provider) => _loggerFactory.AddProvider(provider);
+ public void Dispose() => _loggerFactory.Dispose();
+}
diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/Logger.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/Logger.cs
index aaa4a1b88ae..a8f17ebf320 100644
--- a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/Logger.cs
+++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/Logger.cs
@@ -3,177 +3,70 @@
using System;
using System.Collections.Generic;
-using System.Linq.Expressions;
-using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Text;
+using Microsoft.Extensions.Compliance.Redaction;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Telemetry.Logging;
+using Microsoft.Extensions.Telemetry.Enrichment;
using Microsoft.Shared.Pools;
-using OpenTelemetry.Logs;
namespace Microsoft.Extensions.Telemetry.Logging;
internal sealed class Logger : ILogger
{
- internal static readonly Func>?, LogRecord> CreateLogRecord = GetLogCreator();
-
private const string ExceptionStackTrace = "stackTrace";
- private readonly string _categoryName;
- private readonly LoggerProvider _provider;
-
- ///
- /// Call OpenTelemetry's LogRecord constructor.
- ///
- ///
- /// Reflection is used because the constructor has 'internal' modifier and cannot be called directly.
- /// This will be replaced with a direct call in one of the two conditions below.
- /// - LogRecord will make its internalsVisible to R9 library.
- /// - LogRecord constructor will become public.
- ///
- private static Func>?, LogRecord> GetLogCreator()
- {
- var logRecordConstructor = typeof(LogRecord).GetConstructor(
-#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
- BindingFlags.Instance | BindingFlags.NonPublic,
-#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
- null,
+ private readonly ILogger _nextLogger;
+ private readonly ILogEnricher[] _enrichers;
+ private readonly bool _includeStackTraces;
+ private readonly int _maxStackTraceLength;
+ private readonly IRedactorProvider? _redactorProvider;
- new[]
- {
- typeof(IExternalScopeProvider),
- typeof(DateTime),
- typeof(string),
- typeof(LogLevel),
- typeof(EventId),
- typeof(string),
- typeof(object),
- typeof(Exception),
- typeof(IReadOnlyList>)
- },
- null)!;
-
- var val = new[]
- {
- Expression.Parameter(typeof(IExternalScopeProvider)),
- Expression.Parameter(typeof(DateTime)),
- Expression.Parameter(typeof(string)),
- Expression.Parameter(typeof(LogLevel)),
- Expression.Parameter(typeof(EventId)),
- Expression.Parameter(typeof(string)),
- Expression.Parameter(typeof(object)),
- Expression.Parameter(typeof(Exception)),
- Expression.Parameter(typeof(IReadOnlyList>))
- };
-
- var lambdaLogRecord = Expression.Lambda>?,
- LogRecord>>(Expression.New(logRecordConstructor, val), val);
-
- return lambdaLogRecord.Compile();
- }
+ // TODO From Noah: For stack traces I'd recommend using new StackTrace(Exception, fNeedFileInfo:false)
+ // or you might incur major perf penalties and contention if a service logs a bunch of stack traces when a PDB is present on disk
- internal static TimeProvider TimeProvider => TimeProvider.System;
+ public Logger(ILogger nextLogger, ILogEnricher[] enrichers, bool includeStackTraces, int maxStackTraceLength, IRedactorProvider? redactorProvider)
+ {
+ _nextLogger = nextLogger;
+ _enrichers = enrichers;
+ _includeStackTraces = includeStackTraces;
+ _maxStackTraceLength = maxStackTraceLength;
+ _redactorProvider = redactorProvider;
+ }
- internal Logger(string categoryName, LoggerProvider provider)
+ public IDisposable? BeginScope(TState state)
+ where TState : notnull
{
- _categoryName = categoryName;
- _provider = provider;
+ return _nextLogger.BeginScope(state);
}
- internal IExternalScopeProvider? ScopeProvider { get; set; }
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ // TODO: is this the best we can do here? Should/could the enabled state be tracked in this instance so we can avoid the interface call?
+ return _nextLogger.IsEnabled(logLevel);
+ }
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
{
- if (!IsEnabled(logLevel))
- {
- return;
- }
-
- LogMethodHelper propertyBag;
- LogMethodHelper? rentedHelper = null;
-
try
{
- if (state is LogMethodHelper helper && _provider.CanUsePropertyBagPool)
+ if (typeof(TState) == typeof(LoggerMessageState))
{
- propertyBag = helper;
+ ModernPath(logLevel, eventId, (LoggerMessageState?)(object?)state, exception, (Func)(object)formatter);
}
- else
+ else if (typeof(TState) == typeof(LogMethodHelper))
{
- rentedHelper = GetHelper();
- propertyBag = rentedHelper;
-
- switch (state)
- {
- case IReadOnlyList> stateList:
- rentedHelper.AddRange(stateList);
- break;
-
- case IEnumerable> stateList:
- rentedHelper.AddRange(stateList);
- break;
-
- case null:
- break;
-
- default:
- rentedHelper.Add("{OriginalFormat}", state);
- break;
- }
+ // temporary support for legacy generated code, until generator is updated
+ R9Path(logLevel, eventId, (LogMethodHelper?)(object?)state, exception, (Func)(object)formatter);
}
-
- foreach (var enricher in _provider.Enrichers)
- {
- enricher.Enrich(propertyBag);
- }
-
- if (exception != null && _provider.IncludeStackTrace)
+ else
{
- propertyBag.Add(ExceptionStackTrace, GetExceptionStackTrace(exception, _provider.MaxStackTraceLength));
+ LegacyPath(logLevel, eventId, state, exception, formatter);
}
-
- var record = CreateLogRecord(
- _provider.IncludeScopes ? ScopeProvider : null,
- TimeProvider.GetUtcNow().UtcDateTime,
- _categoryName,
- logLevel,
- eventId,
- _provider.UseFormattedMessage ? formatter(state, exception) : null,
-
- // This parameter needs to be null for OpenTelemetry.Exporter.Geneva to pick up LogRecord.StateValues (the last parameter).
- // This is equivalent to using OpenTelemetryLogger with ParseStateValues option set to true.
- null,
- exception,
- propertyBag);
-
- _provider.Processor?.OnEnd(record);
}
catch (Exception ex)
{
LoggingEventSource.Log.LogException(ex);
throw;
}
- finally
- {
- ReturnHelper(rentedHelper);
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
-
-#pragma warning disable CS8633
-#pragma warning disable CS8766
- public IDisposable? BeginScope(TState state)
- where TState : notnull
-#pragma warning restore CS8633
-#pragma warning restore CS8766
- {
- ScopeProvider ??= new LoggerExternalScopeProvider();
-
- return ScopeProvider.Push(state);
}
private static string GetExceptionStackTrace(Exception exception, int maxStackTraceLength)
@@ -219,7 +112,7 @@ private static void GetInnerExceptionTrace(Exception exception, StringBuilder st
_ = stringBuilder.Append("InnerException type:");
_ = stringBuilder.Append(innerException.GetType());
_ = stringBuilder.Append(" message:");
- _ = stringBuilder.Append(innerException.Message);
+ _ = stringBuilder.Append(innerException.Message); // TODO: this should probably be using the exception summarizer
_ = stringBuilder.Append(" stack:");
_ = stringBuilder.Append(innerException.StackTrace);
@@ -227,18 +120,107 @@ private static void GetInnerExceptionTrace(Exception exception, StringBuilder st
}
}
- private LogMethodHelper GetHelper()
+ private void ModernPath(LogLevel logLevel, EventId eventId, LoggerMessageState? state, Exception? exception, Func formatter)
{
- return _provider.CanUsePropertyBagPool
- ? LogMethodHelper.GetHelper()
- : new LogMethodHelper();
+ if (!IsEnabled(logLevel) || state == null)
+ {
+ return;
+ }
+
+ // redact
+ if (_redactorProvider != null)
+ {
+ foreach (var cp in state.ClassifiedProperties)
+ {
+ var redactor = _redactorProvider.GetRedactor(cp.Classification);
+ state.Add(cp.Name, redactor.Redact(cp.Value));
+ }
+ }
+
+ // enrich
+ foreach (var enricher in _enrichers)
+ {
+ enricher.Enrich(state);
+ }
+
+ // one last dedicated bit of enrichment
+ if (exception != null && _includeStackTraces)
+ {
+ state.Add(ExceptionStackTrace, GetExceptionStackTrace(exception, _maxStackTraceLength));
+ }
+
+ _nextLogger.Log(logLevel, eventId, state, exception, formatter);
+ }
+
+ private void R9Path(LogLevel logLevel, EventId eventId, LogMethodHelper? state, Exception? exception, Func formatter)
+ {
+ if (!IsEnabled(logLevel) || state == null)
+ {
+ return;
+ }
+
+ // no redaction, it's assumed to be done in the generated code
+
+ // enrich
+ foreach (var enricher in _enrichers)
+ {
+ enricher.Enrich(state);
+ }
+
+ // one last dedicated bit of enrichment
+ if (exception != null && _includeStackTraces)
+ {
+ state.Add(ExceptionStackTrace, GetExceptionStackTrace(exception, _maxStackTraceLength));
+ }
+
+ _nextLogger.Log(logLevel, eventId, state, exception, formatter);
}
- private void ReturnHelper(LogMethodHelper? helper)
+ private void LegacyPath(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
{
- if (_provider.CanUsePropertyBagPool && helper != null)
+ var lms = LoggerMessageState.Rent();
+
+ switch (state)
{
- LogMethodHelper.ReturnHelper(helper);
+ case IReadOnlyList> stateList:
+ lms.AddRange(stateList);
+ break;
+
+ case IEnumerable> stateList:
+ lms.AddRange(stateList);
+ break;
+
+ case null:
+ break;
+
+ default:
+ lms.Add("{OriginalFormat}", state);
+ break;
}
+
+ // no redaction possible
+
+ // enrich
+ foreach (var enricher in _enrichers)
+ {
+ enricher.Enrich(lms);
+ }
+
+ // one last dedicated bit of enrichment
+ if (exception != null && _includeStackTraces)
+ {
+ lms.Add(ExceptionStackTrace, GetExceptionStackTrace(exception, _maxStackTraceLength));
+ }
+
+ lms.Formatter = formatter;
+ lms.State = state;
+ _nextLogger.Log(logLevel, eventId, lms, exception, (S, e) =>
+ {
+ var fmt = (Func)S.Formatter!;
+ var st = (TState)S.State!;
+ return fmt(st, e);
+ });
+
+ LoggerMessageState.Return(lms);
}
}
diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerProvider.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerProvider.cs
deleted file mode 100644
index b913abc9027..00000000000
--- a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggerProvider.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using Microsoft.Extensions.Telemetry.Enrichment;
-using Microsoft.Extensions.Telemetry.Internal;
-using Microsoft.Shared.Diagnostics;
-using OpenTelemetry;
-using OpenTelemetry.Logs;
-
-namespace Microsoft.Extensions.Telemetry.Logging;
-
-///
-/// OpenTelemetry Logger provider class.
-///
-[ProviderAlias("R9")]
-[Experimental]
-public sealed class LoggerProvider : BaseProvider, ILoggerProvider, ISupportExternalScope
-{
- private const int ProcessorShutdownGracePeriodInMs = 5000;
- private readonly ConcurrentDictionary _loggers = new();
- private bool _disposed;
- private IExternalScopeProvider? _scopeProvider;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Logger options.
- /// Collection of enrichers.
- /// Collection of processors.
- internal LoggerProvider(
- IOptions loggingOptions,
- IEnumerable enrichers,
- IEnumerable> processors)
- {
- var options = Throw.IfMemberNull(loggingOptions, loggingOptions.Value);
-
- // Accessing Sdk class https://github.com/open-telemetry/opentelemetry-dotnet/blob/7fd37833711e27a02e169de09f3816d1d9557be4/src/OpenTelemetry/Sdk.cs
- // is just to activate OpenTelemetry .NET SDK defaults along with its Self-Diagnostics.
- _ = Sdk.SuppressInstrumentation;
-
- SelfDiagnostics.EnsureInitialized();
-
- var allProcessors = processors.ToList();
-
- Processor = allProcessors.Count switch
- {
- 0 => null,
- 1 => allProcessors[0],
- _ => new CompositeProcessor(allProcessors)
- };
-
- Enrichers = enrichers.ToArray();
- UseFormattedMessage = options.UseFormattedMessage;
- IncludeScopes = options.IncludeScopes;
- IncludeStackTrace = options.IncludeStackTrace;
- MaxStackTraceLength = options.MaxStackTraceLength;
-
- if (!allProcessors.Exists(p => p is BatchExportProcessor))
- {
- CanUsePropertyBagPool = true;
- }
- }
-
- internal bool CanUsePropertyBagPool { get; }
- internal bool UseFormattedMessage { get; }
- internal bool IncludeScopes { get; }
- internal BaseProcessor? Processor { get; }
- internal ILogEnricher[] Enrichers { get; }
- internal bool IncludeStackTrace { get; }
- internal int MaxStackTraceLength { get; }
-
- ///
- /// Sets external scope information source for logger provider.
- ///
- /// scope provider object.
- public void SetScopeProvider(IExternalScopeProvider scopeProvider)
- {
- _scopeProvider = scopeProvider;
-
- foreach (KeyValuePair entry in _loggers)
- {
- if (entry.Value is Logger logger)
- {
- logger.ScopeProvider = _scopeProvider;
- }
- }
- }
-
- ///
- /// Creates a new Microsoft.Extensions.Logging.ILogger instance.
- ///
- /// The category name for message produced by the logger.
- /// ILogger object.
- public ILogger CreateLogger(string categoryName)
- {
- return _loggers.GetOrAdd(categoryName, static (name, t) => new Logger(name, t)
- {
- ScopeProvider = t._scopeProvider,
- }, this);
- }
-
- ///
- /// Performs tasks related to freeing up resources.
- ///
- /// Parameter indicating whether resources need disposing.
- protected override void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- _ = Processor?.Shutdown(ProcessorShutdownGracePeriodInMs);
- Processor?.Dispose();
- }
-
- _disposed = true;
-
- base.Dispose(disposing);
- }
-}
diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingExtensions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingExtensions.cs
index 220972508e4..a4109acc3b9 100644
--- a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingExtensions.cs
@@ -2,16 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options.Validation;
-using Microsoft.Extensions.Telemetry.Enrichment;
using Microsoft.Shared.Diagnostics;
-using OpenTelemetry;
-using OpenTelemetry.Logs;
namespace Microsoft.Extensions.Telemetry.Logging;
@@ -21,85 +18,46 @@ namespace Microsoft.Extensions.Telemetry.Logging;
public static class LoggingExtensions
{
///
- /// Configure logging.
+ /// Configure extended logging, enabling redaction and enrichment.
///
/// Logging builder.
/// Configuration section that contains .
/// Logging .
- public static ILoggingBuilder AddOpenTelemetryLogging(this ILoggingBuilder builder, IConfigurationSection section)
+ [Experimental]
+ public static ILoggingBuilder AddExtendedLogging(this ILoggingBuilder builder, IConfigurationSection section)
{
_ = Throw.IfNull(builder);
_ = Throw.IfNull(section);
- builder.Services.TryAddLoggerProvider();
+ builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
_ = builder.Services.AddValidatedOptions().Bind(section);
return builder;
}
///
- /// Configure logging.
+ /// Configure extended logging, enabling redaction and enrichment.
///
/// Logging builder.
/// Logging configuration options.
/// Logging .
- public static ILoggingBuilder AddOpenTelemetryLogging(this ILoggingBuilder builder, Action configure)
+ [Experimental]
+ public static ILoggingBuilder AddExtendedLogging(this ILoggingBuilder builder, Action configure)
{
_ = Throw.IfNull(builder);
_ = Throw.IfNull(configure);
- builder.Services.TryAddLoggerProvider();
-
+ builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
_ = builder.Services.AddValidatedOptions().Configure(configure);
return builder;
}
///
- /// Configure logging with default options.
+ /// Configure extended logging with default options, enabling redaction and enrichment.
///
/// Logging builder.
/// Logging .
- public static ILoggingBuilder AddOpenTelemetryLogging(this ILoggingBuilder builder) => builder.AddOpenTelemetryLogging(_ => { });
-
- ///
- /// Adds a logging processor to the builder.
- ///
- /// The builder to add the processor to.
- /// Log processor to add.
- /// Returns for chaining.
- public static ILoggingBuilder AddProcessor(this ILoggingBuilder builder, BaseProcessor processor)
- {
- _ = Throw.IfNull(builder);
- _ = Throw.IfNull(processor);
-
- _ = builder.Services.AddSingleton(processor);
-
- return builder;
- }
-
- ///
- /// Adds a logging processor to the builder.
- ///
- /// Type of processor to add.
- /// The builder to add the processor to.
- /// Returns for chaining.
- public static ILoggingBuilder AddProcessor(this ILoggingBuilder builder)
- where T : BaseProcessor
- {
- _ = Throw.IfNull(builder);
-
- _ = builder.Services.AddSingleton, T>();
-
- return builder;
- }
-
- private static void TryAddLoggerProvider(this IServiceCollection services)
- {
- services.TryAddEnumerable(ServiceDescriptor.Singleton(
- sp => new LoggerProvider(
- sp.GetRequiredService>(),
- sp.GetServices(),
- sp.GetServices>())));
- }
+ [Experimental]
+ public static ILoggingBuilder AddExtendedLogging(this ILoggingBuilder builder) => builder.AddExtendedLogging(_ => { });
}
diff --git a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingOptions.cs b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingOptions.cs
index 1188d2f7e11..680045076cd 100644
--- a/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingOptions.cs
+++ b/src/Libraries/Microsoft.Extensions.Telemetry/Logging/LoggingOptions.cs
@@ -15,27 +15,6 @@ public class LoggingOptions
private const int MinDefinedStackTraceLength = 2048;
private const int DefaultStackTraceLength = 4096;
- ///
- /// Gets or sets a value indicating whether to include log scopes in
- /// captured log state.
- ///
- ///
- /// The default value is .
- ///
- public bool IncludeScopes { get; set; }
-
- ///
- /// Gets or sets a value indicating whether to format the message included in captured log state.
- ///
- ///
- /// The default value is .
- ///
- ///
- /// When set to the placeholders in the message will be replaced by the actual values,
- /// otherwise the message template will be included as-is without replacements.
- ///
- public bool UseFormattedMessage { get; set; }
-
///
/// Gets or sets a value indicating whether to include stack trace when exception is logged.
///