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. ///