[sdk-logs] [nits] Update source files to use file scoped namespaces (#4476)

This commit is contained in:
Mikel Blanchard 2023-05-10 14:31:54 -07:00 committed by GitHub
parent 660d5b55cf
commit f6fc73e02f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1044 additions and 1056 deletions

View File

@ -19,52 +19,51 @@
using System.Diagnostics;
using OpenTelemetry.Logs;
namespace OpenTelemetry
namespace OpenTelemetry;
/// <summary>
/// Implements a batch log record export processor.
/// </summary>
public class BatchLogRecordExportProcessor : BatchExportProcessor<LogRecord>
{
/// <summary>
/// Implements a batch log record export processor.
/// Initializes a new instance of the <see cref="BatchLogRecordExportProcessor"/> class.
/// </summary>
public class BatchLogRecordExportProcessor : BatchExportProcessor<LogRecord>
/// <param name="exporter">Log record exporter.</param>
/// <param name="maxQueueSize">The maximum queue size. After the size is reached data are dropped. The default value is 2048.</param>
/// <param name="scheduledDelayMilliseconds">The delay interval in milliseconds between two consecutive exports. The default value is 5000.</param>
/// <param name="exporterTimeoutMilliseconds">How long the export can run before it is cancelled. The default value is 30000.</param>
/// <param name="maxExportBatchSize">The maximum batch size of every export. It must be smaller or equal to maxQueueSize. The default value is 512.</param>
public BatchLogRecordExportProcessor(
BaseExporter<LogRecord> exporter,
int maxQueueSize = DefaultMaxQueueSize,
int scheduledDelayMilliseconds = DefaultScheduledDelayMilliseconds,
int exporterTimeoutMilliseconds = DefaultExporterTimeoutMilliseconds,
int maxExportBatchSize = DefaultMaxExportBatchSize)
: base(
exporter,
maxQueueSize,
scheduledDelayMilliseconds,
exporterTimeoutMilliseconds,
maxExportBatchSize)
{
/// <summary>
/// Initializes a new instance of the <see cref="BatchLogRecordExportProcessor"/> class.
/// </summary>
/// <param name="exporter">Log record exporter.</param>
/// <param name="maxQueueSize">The maximum queue size. After the size is reached data are dropped. The default value is 2048.</param>
/// <param name="scheduledDelayMilliseconds">The delay interval in milliseconds between two consecutive exports. The default value is 5000.</param>
/// <param name="exporterTimeoutMilliseconds">How long the export can run before it is cancelled. The default value is 30000.</param>
/// <param name="maxExportBatchSize">The maximum batch size of every export. It must be smaller or equal to maxQueueSize. The default value is 512.</param>
public BatchLogRecordExportProcessor(
BaseExporter<LogRecord> exporter,
int maxQueueSize = DefaultMaxQueueSize,
int scheduledDelayMilliseconds = DefaultScheduledDelayMilliseconds,
int exporterTimeoutMilliseconds = DefaultExporterTimeoutMilliseconds,
int maxExportBatchSize = DefaultMaxExportBatchSize)
: base(
exporter,
maxQueueSize,
scheduledDelayMilliseconds,
exporterTimeoutMilliseconds,
maxExportBatchSize)
}
/// <inheritdoc/>
public override void OnEnd(LogRecord data)
{
// Note: Intentionally doing a Debug.Assert here and not a
// Guard.ThrowIfNull to save prod cycles. Null should really never
// happen here.
Debug.Assert(data != null, "LogRecord was null.");
data!.Buffer();
data.AddReference();
if (!this.TryExport(data))
{
}
/// <inheritdoc/>
public override void OnEnd(LogRecord data)
{
// Note: Intentionally doing a Debug.Assert here and not a
// Guard.ThrowIfNull to save prod cycles. Null should really never
// happen here.
Debug.Assert(data != null, "LogRecord was null.");
data!.Buffer();
data.AddReference();
if (!this.TryExport(data))
{
LogRecordSharedPool.Current.Return(data);
}
LogRecordSharedPool.Current.Return(data);
}
}
}

View File

@ -20,108 +20,107 @@ using System.Diagnostics;
using OpenTelemetry.Internal;
using OpenTelemetry.Resources;
namespace OpenTelemetry.Logs
namespace OpenTelemetry.Logs;
/// <summary>
/// Contains OpenTelemetry logging options.
/// </summary>
public class OpenTelemetryLoggerOptions
{
internal readonly List<BaseProcessor<LogRecord>> Processors = new();
internal ResourceBuilder? ResourceBuilder;
/// <summary>
/// Contains OpenTelemetry logging options.
/// Gets or sets a value indicating whether or not formatted log message
/// should be included on generated <see cref="LogRecord"/>s. Default
/// value: <see langword="false"/>.
/// </summary>
public class OpenTelemetryLoggerOptions
/// <remarks>
/// Note: When set to <see langword="false"/> a formatted log message
/// will not be included if a message template can be found. If a
/// message template is not found, a formatted log message is always
/// included.
/// </remarks>
public bool IncludeFormattedMessage { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not log scopes should be
/// included on generated <see cref="LogRecord"/>s. Default value:
/// <see langword="false"/>.
/// </summary>
public bool IncludeScopes { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not log state should be
/// parsed into <see cref="LogRecord.Attributes"/> on generated <see
/// cref="LogRecord"/>s. Default value: <see langword="false"/>.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>Parsing is only executed when the state logged does NOT
/// implement <see cref="IReadOnlyList{T}"/> or <see
/// cref="IEnumerable{T}"/> where <c>T</c> is <c>KeyValuePair&lt;string,
/// object&gt;</c>.</item>
/// <item>When <see cref="ParseStateValues"/> is set to <see
/// langword="true"/> <see cref="LogRecord.State"/> will always be <see
/// langword="null"/>.</item>
/// </list>
/// </remarks>
public bool ParseStateValues { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not attributes specified
/// via log state should be included on generated <see
/// cref="LogRecord"/>s. Default value: <see langword="true"/>.
/// </summary>
internal bool IncludeAttributes { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not the <see
/// cref="Activity.TraceStateString"/> for the current <see
/// cref="Activity"/> should be included on generated <see
/// cref="LogRecord"/>s. Default value: <see langword="false"/>.
/// </summary>
internal bool IncludeTraceState { get; set; }
/// <summary>
/// Adds processor to the options.
/// </summary>
/// <param name="processor">Log processor to add.</param>
/// <returns>Returns <see cref="OpenTelemetryLoggerOptions"/> for chaining.</returns>
public OpenTelemetryLoggerOptions AddProcessor(BaseProcessor<LogRecord> processor)
{
internal readonly List<BaseProcessor<LogRecord>> Processors = new();
internal ResourceBuilder? ResourceBuilder;
Guard.ThrowIfNull(processor);
/// <summary>
/// Gets or sets a value indicating whether or not formatted log message
/// should be included on generated <see cref="LogRecord"/>s. Default
/// value: <see langword="false"/>.
/// </summary>
/// <remarks>
/// Note: When set to <see langword="false"/> a formatted log message
/// will not be included if a message template can be found. If a
/// message template is not found, a formatted log message is always
/// included.
/// </remarks>
public bool IncludeFormattedMessage { get; set; }
this.Processors.Add(processor);
/// <summary>
/// Gets or sets a value indicating whether or not log scopes should be
/// included on generated <see cref="LogRecord"/>s. Default value:
/// <see langword="false"/>.
/// </summary>
public bool IncludeScopes { get; set; }
return this;
}
/// <summary>
/// Gets or sets a value indicating whether or not log state should be
/// parsed into <see cref="LogRecord.Attributes"/> on generated <see
/// cref="LogRecord"/>s. Default value: <see langword="false"/>.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>Parsing is only executed when the state logged does NOT
/// implement <see cref="IReadOnlyList{T}"/> or <see
/// cref="IEnumerable{T}"/> where <c>T</c> is <c>KeyValuePair&lt;string,
/// object&gt;</c>.</item>
/// <item>When <see cref="ParseStateValues"/> is set to <see
/// langword="true"/> <see cref="LogRecord.State"/> will always be <see
/// langword="null"/>.</item>
/// </list>
/// </remarks>
public bool ParseStateValues { get; set; }
/// <summary>
/// Sets the <see cref="ResourceBuilder"/> from which the Resource associated with
/// this provider is built from. Overwrites currently set ResourceBuilder.
/// </summary>
/// <param name="resourceBuilder"><see cref="ResourceBuilder"/> from which Resource will be built.</param>
/// <returns>Returns <see cref="OpenTelemetryLoggerOptions"/> for chaining.</returns>
public OpenTelemetryLoggerOptions SetResourceBuilder(ResourceBuilder resourceBuilder)
{
Guard.ThrowIfNull(resourceBuilder);
/// <summary>
/// Gets or sets a value indicating whether or not attributes specified
/// via log state should be included on generated <see
/// cref="LogRecord"/>s. Default value: <see langword="true"/>.
/// </summary>
internal bool IncludeAttributes { get; set; } = true;
this.ResourceBuilder = resourceBuilder;
return this;
}
/// <summary>
/// Gets or sets a value indicating whether or not the <see
/// cref="Activity.TraceStateString"/> for the current <see
/// cref="Activity"/> should be included on generated <see
/// cref="LogRecord"/>s. Default value: <see langword="false"/>.
/// </summary>
internal bool IncludeTraceState { get; set; }
/// <summary>
/// Adds processor to the options.
/// </summary>
/// <param name="processor">Log processor to add.</param>
/// <returns>Returns <see cref="OpenTelemetryLoggerOptions"/> for chaining.</returns>
public OpenTelemetryLoggerOptions AddProcessor(BaseProcessor<LogRecord> processor)
internal OpenTelemetryLoggerOptions Copy()
{
return new()
{
Guard.ThrowIfNull(processor);
this.Processors.Add(processor);
return this;
}
/// <summary>
/// Sets the <see cref="ResourceBuilder"/> from which the Resource associated with
/// this provider is built from. Overwrites currently set ResourceBuilder.
/// </summary>
/// <param name="resourceBuilder"><see cref="ResourceBuilder"/> from which Resource will be built.</param>
/// <returns>Returns <see cref="OpenTelemetryLoggerOptions"/> for chaining.</returns>
public OpenTelemetryLoggerOptions SetResourceBuilder(ResourceBuilder resourceBuilder)
{
Guard.ThrowIfNull(resourceBuilder);
this.ResourceBuilder = resourceBuilder;
return this;
}
internal OpenTelemetryLoggerOptions Copy()
{
return new()
{
IncludeFormattedMessage = this.IncludeFormattedMessage,
IncludeScopes = this.IncludeScopes,
ParseStateValues = this.ParseStateValues,
IncludeAttributes = this.IncludeAttributes,
IncludeTraceState = this.IncludeTraceState,
};
}
IncludeFormattedMessage = this.IncludeFormattedMessage,
IncludeScopes = this.IncludeScopes,
ParseStateValues = this.ParseStateValues,
IncludeAttributes = this.IncludeAttributes,
IncludeTraceState = this.IncludeTraceState,
};
}
}

View File

@ -23,140 +23,139 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Logs
namespace OpenTelemetry.Logs;
/// <summary>
/// An <see cref="ILoggerProvider"/> implementation for exporting logs using OpenTelemetry.
/// </summary>
[ProviderAlias("OpenTelemetry")]
public class OpenTelemetryLoggerProvider : BaseProvider, ILoggerProvider, ISupportExternalScope
{
/// <summary>
/// An <see cref="ILoggerProvider"/> implementation for exporting logs using OpenTelemetry.
/// </summary>
[ProviderAlias("OpenTelemetry")]
public class OpenTelemetryLoggerProvider : BaseProvider, ILoggerProvider, ISupportExternalScope
internal readonly LoggerProvider Provider;
private readonly bool ownsProvider;
private readonly Hashtable loggers = new();
private bool disposed;
static OpenTelemetryLoggerProvider()
{
internal readonly LoggerProvider Provider;
private readonly bool ownsProvider;
private readonly Hashtable loggers = new();
private bool disposed;
// Accessing Sdk class is just to trigger its static ctor,
// which sets default Propagators and default Activity Id format
_ = Sdk.SuppressInstrumentation;
}
static OpenTelemetryLoggerProvider()
{
// Accessing Sdk class is just to trigger its static ctor,
// which sets default Propagators and default Activity Id format
_ = Sdk.SuppressInstrumentation;
}
/// <summary>
/// Initializes a new instance of the <see cref="OpenTelemetryLoggerProvider"/> class.
/// </summary>
/// <param name="options"><see cref="OpenTelemetryLoggerOptions"/>.</param>
// todo: [Obsolete("Use the Sdk.CreateLoggerProviderBuilder method instead this ctor will be removed in a future version.")]
public OpenTelemetryLoggerProvider(IOptionsMonitor<OpenTelemetryLoggerOptions> options)
{
Guard.ThrowIfNull(options);
/// <summary>
/// Initializes a new instance of the <see cref="OpenTelemetryLoggerProvider"/> class.
/// </summary>
/// <param name="options"><see cref="OpenTelemetryLoggerOptions"/>.</param>
// todo: [Obsolete("Use the Sdk.CreateLoggerProviderBuilder method instead this ctor will be removed in a future version.")]
public OpenTelemetryLoggerProvider(IOptionsMonitor<OpenTelemetryLoggerOptions> options)
{
Guard.ThrowIfNull(options);
var optionsInstance = options.CurrentValue;
var optionsInstance = options.CurrentValue;
this.Provider = Sdk
.CreateLoggerProviderBuilder()
.ConfigureBuilder((sp, builder) =>
{
if (optionsInstance.ResourceBuilder != null)
{
builder.SetResourceBuilder(optionsInstance.ResourceBuilder);
}
foreach (var processor in optionsInstance.Processors)
{
builder.AddProcessor(processor);
}
})
.Build();
this.Options = optionsInstance.Copy();
this.ownsProvider = true;
}
internal OpenTelemetryLoggerProvider(
LoggerProvider loggerProvider,
OpenTelemetryLoggerOptions options,
bool disposeProvider)
{
Debug.Assert(loggerProvider != null, "loggerProvider was null");
Debug.Assert(options != null, "options was null");
this.Provider = loggerProvider!;
this.Options = options!.Copy();
this.ownsProvider = disposeProvider;
}
internal OpenTelemetryLoggerOptions Options { get; }
internal IExternalScopeProvider? ScopeProvider { get; private set; }
/// <inheritdoc/>
void ISupportExternalScope.SetScopeProvider(IExternalScopeProvider scopeProvider)
{
this.ScopeProvider = scopeProvider;
lock (this.loggers)
this.Provider = Sdk
.CreateLoggerProviderBuilder()
.ConfigureBuilder((sp, builder) =>
{
foreach (DictionaryEntry entry in this.loggers)
if (optionsInstance.ResourceBuilder != null)
{
if (entry.Value is OpenTelemetryLogger logger)
{
logger.ScopeProvider = scopeProvider;
}
}
}
}
/// <inheritdoc/>
public ILogger CreateLogger(string categoryName)
{
if (this.loggers[categoryName] is not ILogger logger)
{
lock (this.loggers)
{
logger = (this.loggers[categoryName] as ILogger)!;
if (logger == null)
{
var loggerProviderSdk = this.Provider as LoggerProviderSdk;
if (loggerProviderSdk == null)
{
logger = NullLogger.Instance;
}
else
{
logger = new OpenTelemetryLogger(loggerProviderSdk, this.Options, categoryName)
{
ScopeProvider = this.ScopeProvider,
};
}
this.loggers[categoryName] = logger;
}
}
}
return logger;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (this.ownsProvider)
{
this.Provider.Dispose();
}
builder.SetResourceBuilder(optionsInstance.ResourceBuilder);
}
this.disposed = true;
OpenTelemetrySdkEventSource.Log.ProviderDisposed(nameof(OpenTelemetryLoggerProvider));
}
foreach (var processor in optionsInstance.Processors)
{
builder.AddProcessor(processor);
}
})
.Build();
base.Dispose(disposing);
this.Options = optionsInstance.Copy();
this.ownsProvider = true;
}
internal OpenTelemetryLoggerProvider(
LoggerProvider loggerProvider,
OpenTelemetryLoggerOptions options,
bool disposeProvider)
{
Debug.Assert(loggerProvider != null, "loggerProvider was null");
Debug.Assert(options != null, "options was null");
this.Provider = loggerProvider!;
this.Options = options!.Copy();
this.ownsProvider = disposeProvider;
}
internal OpenTelemetryLoggerOptions Options { get; }
internal IExternalScopeProvider? ScopeProvider { get; private set; }
/// <inheritdoc/>
void ISupportExternalScope.SetScopeProvider(IExternalScopeProvider scopeProvider)
{
this.ScopeProvider = scopeProvider;
lock (this.loggers)
{
foreach (DictionaryEntry entry in this.loggers)
{
if (entry.Value is OpenTelemetryLogger logger)
{
logger.ScopeProvider = scopeProvider;
}
}
}
}
/// <inheritdoc/>
public ILogger CreateLogger(string categoryName)
{
if (this.loggers[categoryName] is not ILogger logger)
{
lock (this.loggers)
{
logger = (this.loggers[categoryName] as ILogger)!;
if (logger == null)
{
var loggerProviderSdk = this.Provider as LoggerProviderSdk;
if (loggerProviderSdk == null)
{
logger = NullLogger.Instance;
}
else
{
logger = new OpenTelemetryLogger(loggerProviderSdk, this.Options, categoryName)
{
ScopeProvider = this.ScopeProvider,
};
}
this.loggers[categoryName] = logger;
}
}
}
return logger;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (this.ownsProvider)
{
this.Provider.Dispose();
}
}
this.disposed = true;
OpenTelemetrySdkEventSource.Log.ProviderDisposed(nameof(OpenTelemetryLoggerProvider));
}
base.Dispose(disposing);
}
}

View File

@ -24,80 +24,79 @@ using Microsoft.Extensions.Options;
using OpenTelemetry.Internal;
using OpenTelemetry.Logs;
namespace Microsoft.Extensions.Logging
namespace Microsoft.Extensions.Logging;
/// <summary>
/// Contains extension methods for registering <see cref="OpenTelemetryLoggerProvider"/> into a <see cref="ILoggingBuilder"/> instance.
/// </summary>
public static class OpenTelemetryLoggingExtensions
{
/// <summary>
/// Contains extension methods for registering <see cref="OpenTelemetryLoggerProvider"/> into a <see cref="ILoggingBuilder"/> instance.
/// Adds an OpenTelemetry logger named 'OpenTelemetry' to the <see cref="ILoggerFactory"/>.
/// </summary>
public static class OpenTelemetryLoggingExtensions
/// <remarks>
/// Note: This is safe to be called multiple times and by library
/// authors. Only a single <see cref="OpenTelemetryLoggerProvider"/>
/// will be created for a given <see cref="IServiceCollection"/>.
/// </remarks>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder)
{
/// <summary>
/// Adds an OpenTelemetry logger named 'OpenTelemetry' to the <see cref="ILoggerFactory"/>.
/// </summary>
/// <remarks>
/// Note: This is safe to be called multiple times and by library
/// authors. Only a single <see cref="OpenTelemetryLoggerProvider"/>
/// will be created for a given <see cref="IServiceCollection"/>.
/// </remarks>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder)
{
Guard.ThrowIfNull(builder);
Guard.ThrowIfNull(builder);
builder.AddConfiguration();
builder.AddConfiguration();
// Note: This will bind logger options element (eg "Logging:OpenTelemetry") to OpenTelemetryLoggerOptions
LoggerProviderOptions.RegisterProviderOptions<OpenTelemetryLoggerOptions, OpenTelemetryLoggerProvider>(builder.Services);
// Note: This will bind logger options element (eg "Logging:OpenTelemetry") to OpenTelemetryLoggerOptions
LoggerProviderOptions.RegisterProviderOptions<OpenTelemetryLoggerOptions, OpenTelemetryLoggerProvider>(builder.Services);
new LoggerProviderServiceCollectionBuilder(builder.Services).ConfigureBuilder(
(sp, logging) =>
{
var options = sp.GetRequiredService<IOptionsMonitor<OpenTelemetryLoggerOptions>>().CurrentValue;
if (options.ResourceBuilder != null)
{
logging.SetResourceBuilder(options.ResourceBuilder);
options.ResourceBuilder = null;
}
foreach (var processor in options.Processors)
{
logging.AddProcessor(processor);
}
options.Processors.Clear();
});
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<ILoggerProvider, OpenTelemetryLoggerProvider>(
sp => new OpenTelemetryLoggerProvider(
sp.GetRequiredService<LoggerProvider>(),
sp.GetRequiredService<IOptionsMonitor<OpenTelemetryLoggerOptions>>().CurrentValue,
disposeProvider: false)));
return builder;
}
/// <summary>
/// Adds an OpenTelemetry logger named 'OpenTelemetry' to the <see cref="ILoggerFactory"/>.
/// </summary>
/// <remarks><inheritdoc cref="AddOpenTelemetry(ILoggingBuilder)" path="/remarks"/></remarks>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="configure">Optional configuration action.</param>
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder,
Action<OpenTelemetryLoggerOptions>? configure)
{
if (configure != null)
new LoggerProviderServiceCollectionBuilder(builder.Services).ConfigureBuilder(
(sp, logging) =>
{
builder.Services.Configure(configure);
}
var options = sp.GetRequiredService<IOptionsMonitor<OpenTelemetryLoggerOptions>>().CurrentValue;
return AddOpenTelemetry(builder);
if (options.ResourceBuilder != null)
{
logging.SetResourceBuilder(options.ResourceBuilder);
options.ResourceBuilder = null;
}
foreach (var processor in options.Processors)
{
logging.AddProcessor(processor);
}
options.Processors.Clear();
});
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<ILoggerProvider, OpenTelemetryLoggerProvider>(
sp => new OpenTelemetryLoggerProvider(
sp.GetRequiredService<LoggerProvider>(),
sp.GetRequiredService<IOptionsMonitor<OpenTelemetryLoggerOptions>>().CurrentValue,
disposeProvider: false)));
return builder;
}
/// <summary>
/// Adds an OpenTelemetry logger named 'OpenTelemetry' to the <see cref="ILoggerFactory"/>.
/// </summary>
/// <remarks><inheritdoc cref="AddOpenTelemetry(ILoggingBuilder)" path="/remarks"/></remarks>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="configure">Optional configuration action.</param>
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder,
Action<OpenTelemetryLoggerOptions>? configure)
{
if (configure != null)
{
builder.Services.Configure(configure);
}
return AddOpenTelemetry(builder);
}
}

View File

@ -21,451 +21,450 @@ using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Logs
{
/// <summary>
/// Stores details about a log message.
/// </summary>
public sealed class LogRecord
{
internal LogRecordData Data;
internal LogRecordILoggerData ILoggerData;
internal List<KeyValuePair<string, object?>>? AttributeStorage;
internal List<object?>? ScopeStorage;
internal int PoolReferenceCount = int.MaxValue;
namespace OpenTelemetry.Logs;
private static readonly Action<object?, List<object?>> AddScopeToBufferedList = (object? scope, List<object?> state) =>
/// <summary>
/// Stores details about a log message.
/// </summary>
public sealed class LogRecord
{
internal LogRecordData Data;
internal LogRecordILoggerData ILoggerData;
internal List<KeyValuePair<string, object?>>? AttributeStorage;
internal List<object?>? ScopeStorage;
internal int PoolReferenceCount = int.MaxValue;
private static readonly Action<object?, List<object?>> AddScopeToBufferedList = (object? scope, List<object?> state) =>
{
state.Add(scope);
};
internal LogRecord()
{
}
// Note: Some users are calling this with reflection. Try not to change the signature to be nice.
[Obsolete("Call LogRecordPool.Rent instead.")]
internal LogRecord(
IExternalScopeProvider? scopeProvider,
DateTime timestamp,
string categoryName,
LogLevel logLevel,
EventId eventId,
string? formattedMessage,
object? state,
Exception? exception,
IReadOnlyList<KeyValuePair<string, object?>>? stateValues)
{
var activity = Activity.Current;
this.Data = new(activity)
{
state.Add(scope);
TimestampBacking = timestamp,
Body = formattedMessage,
};
internal LogRecord()
OpenTelemetryLogger.SetLogRecordSeverityFields(ref this.Data, logLevel);
this.ILoggerData = new()
{
TraceState = activity?.TraceStateString,
CategoryName = categoryName,
FormattedMessage = formattedMessage,
EventId = eventId,
Exception = exception,
State = state,
ScopeProvider = scopeProvider,
};
if (stateValues != null && stateValues.Count > 0)
{
var lastAttribute = stateValues[stateValues.Count - 1];
if (lastAttribute.Key == "{OriginalFormat}"
&& lastAttribute.Value is string template)
{
this.Data.Body = template;
}
this.Attributes = stateValues;
}
}
/// <summary>
/// Gets or sets the log timestamp.
/// </summary>
/// <remarks>
/// Note: If <see cref="Timestamp"/> is set to a value with <see
/// cref="DateTimeKind.Local"/> it will be automatically converted to
/// UTC using <see cref="DateTime.ToUniversalTime"/>.
/// </remarks>
public DateTime Timestamp
{
get => this.Data.Timestamp;
set => this.Data.Timestamp = value;
}
/// <summary>
/// Gets or sets the log <see cref="ActivityTraceId"/>.
/// </summary>
public ActivityTraceId TraceId
{
get => this.Data.TraceId;
set => this.Data.TraceId = value;
}
/// <summary>
/// Gets or sets the log <see cref="ActivitySpanId"/>.
/// </summary>
public ActivitySpanId SpanId
{
get => this.Data.SpanId;
set => this.Data.SpanId = value;
}
/// <summary>
/// Gets or sets the log <see cref="ActivityTraceFlags"/>.
/// </summary>
public ActivityTraceFlags TraceFlags
{
get => this.Data.TraceFlags;
set => this.Data.TraceFlags = value;
}
/// <summary>
/// Gets or sets the log trace state.
/// </summary>
/// <remarks>
/// Note: Only set if <see
/// cref="OpenTelemetryLoggerOptions.IncludeTraceState"/> is enabled.
/// </remarks>
public string? TraceState
{
get => this.ILoggerData.TraceState;
set => this.ILoggerData.TraceState = value;
}
/// <summary>
/// Gets or sets the log category name.
/// </summary>
public string? CategoryName
{
get => this.ILoggerData.CategoryName;
set => this.ILoggerData.CategoryName = value;
}
/// <summary>
/// Gets or sets the log <see cref="Microsoft.Extensions.Logging.LogLevel"/>.
/// </summary>
// todo: [Obsolete("Use Severity instead LogLevel will be removed in a future version.")]
public LogLevel LogLevel
{
get
{
if (this.Data.Severity.HasValue)
{
uint severity = (uint)this.Data.Severity.Value;
if (severity >= 1 && severity <= 24)
{
return (LogLevel)((severity - 1) / 4);
}
}
return LogLevel.Trace;
}
// Note: Some users are calling this with reflection. Try not to change the signature to be nice.
[Obsolete("Call LogRecordPool.Rent instead.")]
internal LogRecord(
IExternalScopeProvider? scopeProvider,
DateTime timestamp,
string categoryName,
LogLevel logLevel,
EventId eventId,
string? formattedMessage,
object? state,
Exception? exception,
IReadOnlyList<KeyValuePair<string, object?>>? stateValues)
set
{
var activity = Activity.Current;
OpenTelemetryLogger.SetLogRecordSeverityFields(ref this.Data, value);
}
}
this.Data = new(activity)
/// <summary>
/// Gets or sets the log <see cref="Microsoft.Extensions.Logging.EventId"/>.
/// </summary>
public EventId EventId
{
get => this.ILoggerData.EventId;
set => this.ILoggerData.EventId = value;
}
/// <summary>
/// Gets or sets the log formatted message.
/// </summary>
/// <remarks>
/// Note: Set if <see
/// cref="OpenTelemetryLoggerOptions.IncludeFormattedMessage"/> is
/// enabled or <c>{OriginalFormat}</c> attribute (message template) is
/// not found.
/// </remarks>
public string? FormattedMessage
{
get => this.ILoggerData.FormattedMessage;
set => this.ILoggerData.FormattedMessage = value;
}
/// <summary>
/// Gets or sets the log body.
/// </summary>
/// <remarks>
/// Note: Set to the <c>{OriginalFormat}</c> attribute (message
/// template) if found otherwise the formatted log message.
/// </remarks>
public string? Body
{
get => this.Data.Body;
set => this.Data.Body = value;
}
/// <summary>
/// Gets or sets the raw state attached to the log.
/// </summary>
/// <remarks>
/// Note: Set to <see langword="null"/> when <see
/// cref="OpenTelemetryLoggerOptions.ParseStateValues"/> is enabled.
/// </remarks>
[Obsolete("State cannot be accessed safely outside of an ILogger.Log call stack. Use Attributes instead to safely access the data attached to a LogRecord. State will be removed in a future version.")]
public object? State
{
get => this.ILoggerData.State;
set => this.ILoggerData.State = value;
}
/// <summary>
/// Gets or sets the state values attached to the log.
/// </summary>
/// <remarks><inheritdoc cref="Attributes" /></remarks>
[Obsolete("Use Attributes instead StateValues will be removed in a future version.")]
public IReadOnlyList<KeyValuePair<string, object?>>? StateValues
{
get => this.Attributes;
set => this.Attributes = value;
}
/// <summary>
/// Gets or sets the attributes attached to the log.
/// </summary>
/// <remarks>
/// Note: Set when <see
/// cref="OpenTelemetryLoggerOptions.IncludeAttributes"/> is enabled and
/// log record state implements <see cref="IReadOnlyList{T}"/> or <see
/// cref="IEnumerable{T}"/> of <see cref="KeyValuePair{TKey, TValue}"/>s
/// (where TKey is <c>string</c> and TValue is <c>object</c>) or <see
/// cref="OpenTelemetryLoggerOptions.ParseStateValues"/> is enabled
/// otherwise <see langword="null"/>.
/// </remarks>
public IReadOnlyList<KeyValuePair<string, object?>>? Attributes { get; set; }
/// <summary>
/// Gets or sets the log <see cref="System.Exception"/>.
/// </summary>
public Exception? Exception
{
get => this.ILoggerData.Exception;
set => this.ILoggerData.Exception = value;
}
/// <summary>
/// Gets or sets the original string representation of the severity as it is
/// known at the source.
/// </summary>
internal string? SeverityText
{
get => this.Data.SeverityText;
set => this.Data.SeverityText = value;
}
/// <summary>
/// Gets or sets the log <see cref="LogRecordSeverity"/>.
/// </summary>
internal LogRecordSeverity? Severity
{
get => this.Data.Severity;
set => this.Data.Severity = value;
}
/// <summary>
/// Gets or sets the <see cref="Logs.Logger"/> which emitted the <see cref="LogRecord"/>.
/// </summary>
internal Logger? Logger { get; /*todo: internal*/ set; }
/// <summary>
/// Executes callback for each currently active scope objects in order
/// of creation. All callbacks are guaranteed to be called inline from
/// this method.
/// </summary>
/// <typeparam name="TState">State.</typeparam>
/// <param name="callback">The callback to be executed for every scope object.</param>
/// <param name="state">The state object to be passed into the callback.</param>
public void ForEachScope<TState>(Action<LogRecordScope, TState> callback, TState state)
{
Guard.ThrowIfNull(callback);
var forEachScopeState = new ScopeForEachState<TState>(callback, state);
var bufferedScopes = this.ILoggerData.BufferedScopes;
if (bufferedScopes != null)
{
foreach (object? scope in bufferedScopes)
{
TimestampBacking = timestamp,
ScopeForEachState<TState>.ForEachScope(scope, forEachScopeState);
}
}
else
{
this.ILoggerData.ScopeProvider?.ForEachScope(ScopeForEachState<TState>.ForEachScope, forEachScopeState);
}
}
Body = formattedMessage,
/// <summary>
/// Gets a reference to the <see cref="LogRecordData"/> for the log message.
/// </summary>
/// <returns><see cref="LogRecordData"/>.</returns>
internal ref LogRecordData GetDataRef()
{
return ref this.Data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ResetReferenceCount()
{
this.PoolReferenceCount = 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void AddReference()
{
Interlocked.Increment(ref this.PoolReferenceCount);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int RemoveReference()
{
return Interlocked.Decrement(ref this.PoolReferenceCount);
}
// Note: Typically called when LogRecords are added into a batch so they
// can be safely processed outside of the log call chain.
internal void Buffer()
{
// Note: Attributes are buffered because some states are not safe to
// access outside of the log call chain. See:
// https://github.com/open-telemetry/opentelemetry-dotnet/issues/2905
this.BufferLogAttributes();
this.BufferLogScopes();
}
internal LogRecord Copy()
{
// Note: We only buffer scopes here because attributes are copied
// directly below.
this.BufferLogScopes();
return new()
{
Data = this.Data,
ILoggerData = this.ILoggerData.Copy(),
Attributes = this.Attributes == null ? null : new List<KeyValuePair<string, object?>>(this.Attributes),
};
}
/// <summary>
/// Buffers the attributes attached to the log into a list so that they
/// can be safely processed after the log message lifecycle has ended.
/// </summary>
private void BufferLogAttributes()
{
var attributes = this.Attributes;
if (attributes == null || attributes == this.AttributeStorage)
{
return;
}
var attributeStorage = this.AttributeStorage ??= new List<KeyValuePair<string, object?>>(attributes.Count);
// Note: AddRange here will copy all of the KeyValuePairs from
// attributes to AttributeStorage. This "captures" the state and
// fixes issues where the values are generated at enumeration time
// like
// https://github.com/open-telemetry/opentelemetry-dotnet/issues/2905.
attributeStorage.AddRange(attributes);
this.Attributes = attributeStorage;
}
/// <summary>
/// Buffers the scopes attached to the log into a list so that they can
/// be safely processed after the log message lifecycle has ended.
/// </summary>
private void BufferLogScopes()
{
var scopeProvider = this.ILoggerData.ScopeProvider;
if (scopeProvider == null)
{
return;
}
var scopeStorage = this.ScopeStorage ??= new List<object?>(LogRecordPoolHelper.DefaultMaxNumberOfScopes);
scopeProvider.ForEachScope(AddScopeToBufferedList, scopeStorage);
this.ILoggerData.ScopeProvider = null;
this.ILoggerData.BufferedScopes = scopeStorage;
}
internal struct LogRecordILoggerData
{
public string? TraceState;
public string? CategoryName;
public EventId EventId;
public string? FormattedMessage;
public Exception? Exception;
public object? State;
public IExternalScopeProvider? ScopeProvider;
public List<object?>? BufferedScopes;
public LogRecordILoggerData Copy()
{
var copy = new LogRecordILoggerData
{
TraceState = this.TraceState,
CategoryName = this.CategoryName,
EventId = this.EventId,
FormattedMessage = this.FormattedMessage,
Exception = this.Exception,
State = this.State,
};
OpenTelemetryLogger.SetLogRecordSeverityFields(ref this.Data, logLevel);
this.ILoggerData = new()
{
TraceState = activity?.TraceStateString,
CategoryName = categoryName,
FormattedMessage = formattedMessage,
EventId = eventId,
Exception = exception,
State = state,
ScopeProvider = scopeProvider,
};
if (stateValues != null && stateValues.Count > 0)
{
var lastAttribute = stateValues[stateValues.Count - 1];
if (lastAttribute.Key == "{OriginalFormat}"
&& lastAttribute.Value is string template)
{
this.Data.Body = template;
}
this.Attributes = stateValues;
}
}
/// <summary>
/// Gets or sets the log timestamp.
/// </summary>
/// <remarks>
/// Note: If <see cref="Timestamp"/> is set to a value with <see
/// cref="DateTimeKind.Local"/> it will be automatically converted to
/// UTC using <see cref="DateTime.ToUniversalTime"/>.
/// </remarks>
public DateTime Timestamp
{
get => this.Data.Timestamp;
set => this.Data.Timestamp = value;
}
/// <summary>
/// Gets or sets the log <see cref="ActivityTraceId"/>.
/// </summary>
public ActivityTraceId TraceId
{
get => this.Data.TraceId;
set => this.Data.TraceId = value;
}
/// <summary>
/// Gets or sets the log <see cref="ActivitySpanId"/>.
/// </summary>
public ActivitySpanId SpanId
{
get => this.Data.SpanId;
set => this.Data.SpanId = value;
}
/// <summary>
/// Gets or sets the log <see cref="ActivityTraceFlags"/>.
/// </summary>
public ActivityTraceFlags TraceFlags
{
get => this.Data.TraceFlags;
set => this.Data.TraceFlags = value;
}
/// <summary>
/// Gets or sets the log trace state.
/// </summary>
/// <remarks>
/// Note: Only set if <see
/// cref="OpenTelemetryLoggerOptions.IncludeTraceState"/> is enabled.
/// </remarks>
public string? TraceState
{
get => this.ILoggerData.TraceState;
set => this.ILoggerData.TraceState = value;
}
/// <summary>
/// Gets or sets the log category name.
/// </summary>
public string? CategoryName
{
get => this.ILoggerData.CategoryName;
set => this.ILoggerData.CategoryName = value;
}
/// <summary>
/// Gets or sets the log <see cref="Microsoft.Extensions.Logging.LogLevel"/>.
/// </summary>
// todo: [Obsolete("Use Severity instead LogLevel will be removed in a future version.")]
public LogLevel LogLevel
{
get
{
if (this.Data.Severity.HasValue)
{
uint severity = (uint)this.Data.Severity.Value;
if (severity >= 1 && severity <= 24)
{
return (LogLevel)((severity - 1) / 4);
}
}
return LogLevel.Trace;
}
set
{
OpenTelemetryLogger.SetLogRecordSeverityFields(ref this.Data, value);
}
}
/// <summary>
/// Gets or sets the log <see cref="Microsoft.Extensions.Logging.EventId"/>.
/// </summary>
public EventId EventId
{
get => this.ILoggerData.EventId;
set => this.ILoggerData.EventId = value;
}
/// <summary>
/// Gets or sets the log formatted message.
/// </summary>
/// <remarks>
/// Note: Set if <see
/// cref="OpenTelemetryLoggerOptions.IncludeFormattedMessage"/> is
/// enabled or <c>{OriginalFormat}</c> attribute (message template) is
/// not found.
/// </remarks>
public string? FormattedMessage
{
get => this.ILoggerData.FormattedMessage;
set => this.ILoggerData.FormattedMessage = value;
}
/// <summary>
/// Gets or sets the log body.
/// </summary>
/// <remarks>
/// Note: Set to the <c>{OriginalFormat}</c> attribute (message
/// template) if found otherwise the formatted log message.
/// </remarks>
public string? Body
{
get => this.Data.Body;
set => this.Data.Body = value;
}
/// <summary>
/// Gets or sets the raw state attached to the log.
/// </summary>
/// <remarks>
/// Note: Set to <see langword="null"/> when <see
/// cref="OpenTelemetryLoggerOptions.ParseStateValues"/> is enabled.
/// </remarks>
[Obsolete("State cannot be accessed safely outside of an ILogger.Log call stack. Use Attributes instead to safely access the data attached to a LogRecord. State will be removed in a future version.")]
public object? State
{
get => this.ILoggerData.State;
set => this.ILoggerData.State = value;
}
/// <summary>
/// Gets or sets the state values attached to the log.
/// </summary>
/// <remarks><inheritdoc cref="Attributes" /></remarks>
[Obsolete("Use Attributes instead StateValues will be removed in a future version.")]
public IReadOnlyList<KeyValuePair<string, object?>>? StateValues
{
get => this.Attributes;
set => this.Attributes = value;
}
/// <summary>
/// Gets or sets the attributes attached to the log.
/// </summary>
/// <remarks>
/// Note: Set when <see
/// cref="OpenTelemetryLoggerOptions.IncludeAttributes"/> is enabled and
/// log record state implements <see cref="IReadOnlyList{T}"/> or <see
/// cref="IEnumerable{T}"/> of <see cref="KeyValuePair{TKey, TValue}"/>s
/// (where TKey is <c>string</c> and TValue is <c>object</c>) or <see
/// cref="OpenTelemetryLoggerOptions.ParseStateValues"/> is enabled
/// otherwise <see langword="null"/>.
/// </remarks>
public IReadOnlyList<KeyValuePair<string, object?>>? Attributes { get; set; }
/// <summary>
/// Gets or sets the log <see cref="System.Exception"/>.
/// </summary>
public Exception? Exception
{
get => this.ILoggerData.Exception;
set => this.ILoggerData.Exception = value;
}
/// <summary>
/// Gets or sets the original string representation of the severity as it is
/// known at the source.
/// </summary>
internal string? SeverityText
{
get => this.Data.SeverityText;
set => this.Data.SeverityText = value;
}
/// <summary>
/// Gets or sets the log <see cref="LogRecordSeverity"/>.
/// </summary>
internal LogRecordSeverity? Severity
{
get => this.Data.Severity;
set => this.Data.Severity = value;
}
/// <summary>
/// Gets or sets the <see cref="Logs.Logger"/> which emitted the <see cref="LogRecord"/>.
/// </summary>
internal Logger? Logger { get; /*todo: internal*/ set; }
/// <summary>
/// Executes callback for each currently active scope objects in order
/// of creation. All callbacks are guaranteed to be called inline from
/// this method.
/// </summary>
/// <typeparam name="TState">State.</typeparam>
/// <param name="callback">The callback to be executed for every scope object.</param>
/// <param name="state">The state object to be passed into the callback.</param>
public void ForEachScope<TState>(Action<LogRecordScope, TState> callback, TState state)
{
Guard.ThrowIfNull(callback);
var forEachScopeState = new ScopeForEachState<TState>(callback, state);
var bufferedScopes = this.ILoggerData.BufferedScopes;
var bufferedScopes = this.BufferedScopes;
if (bufferedScopes != null)
{
foreach (object? scope in bufferedScopes)
{
ScopeForEachState<TState>.ForEachScope(scope, forEachScopeState);
}
}
else
{
this.ILoggerData.ScopeProvider?.ForEachScope(ScopeForEachState<TState>.ForEachScope, forEachScopeState);
}
}
/// <summary>
/// Gets a reference to the <see cref="LogRecordData"/> for the log message.
/// </summary>
/// <returns><see cref="LogRecordData"/>.</returns>
internal ref LogRecordData GetDataRef()
{
return ref this.Data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ResetReferenceCount()
{
this.PoolReferenceCount = 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void AddReference()
{
Interlocked.Increment(ref this.PoolReferenceCount);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int RemoveReference()
{
return Interlocked.Decrement(ref this.PoolReferenceCount);
}
// Note: Typically called when LogRecords are added into a batch so they
// can be safely processed outside of the log call chain.
internal void Buffer()
{
// Note: Attributes are buffered because some states are not safe to
// access outside of the log call chain. See:
// https://github.com/open-telemetry/opentelemetry-dotnet/issues/2905
this.BufferLogAttributes();
this.BufferLogScopes();
}
internal LogRecord Copy()
{
// Note: We only buffer scopes here because attributes are copied
// directly below.
this.BufferLogScopes();
return new()
{
Data = this.Data,
ILoggerData = this.ILoggerData.Copy(),
Attributes = this.Attributes == null ? null : new List<KeyValuePair<string, object?>>(this.Attributes),
};
}
/// <summary>
/// Buffers the attributes attached to the log into a list so that they
/// can be safely processed after the log message lifecycle has ended.
/// </summary>
private void BufferLogAttributes()
{
var attributes = this.Attributes;
if (attributes == null || attributes == this.AttributeStorage)
{
return;
copy.BufferedScopes = new List<object?>(bufferedScopes);
}
var attributeStorage = this.AttributeStorage ??= new List<KeyValuePair<string, object?>>(attributes.Count);
// Note: AddRange here will copy all of the KeyValuePairs from
// attributes to AttributeStorage. This "captures" the state and
// fixes issues where the values are generated at enumeration time
// like
// https://github.com/open-telemetry/opentelemetry-dotnet/issues/2905.
attributeStorage.AddRange(attributes);
this.Attributes = attributeStorage;
return copy;
}
}
/// <summary>
/// Buffers the scopes attached to the log into a list so that they can
/// be safely processed after the log message lifecycle has ended.
/// </summary>
private void BufferLogScopes()
private readonly struct ScopeForEachState<TState>
{
public static readonly Action<object?, ScopeForEachState<TState>> ForEachScope = (object? scope, ScopeForEachState<TState> state) =>
{
var scopeProvider = this.ILoggerData.ScopeProvider;
if (scopeProvider == null)
{
return;
}
LogRecordScope logRecordScope = new LogRecordScope(scope);
var scopeStorage = this.ScopeStorage ??= new List<object?>(LogRecordPoolHelper.DefaultMaxNumberOfScopes);
state.Callback(logRecordScope, state.UserState);
};
scopeProvider.ForEachScope(AddScopeToBufferedList, scopeStorage);
public readonly Action<LogRecordScope, TState> Callback;
this.ILoggerData.ScopeProvider = null;
public readonly TState UserState;
this.ILoggerData.BufferedScopes = scopeStorage;
}
internal struct LogRecordILoggerData
public ScopeForEachState(Action<LogRecordScope, TState> callback, TState state)
{
public string? TraceState;
public string? CategoryName;
public EventId EventId;
public string? FormattedMessage;
public Exception? Exception;
public object? State;
public IExternalScopeProvider? ScopeProvider;
public List<object?>? BufferedScopes;
public LogRecordILoggerData Copy()
{
var copy = new LogRecordILoggerData
{
TraceState = this.TraceState,
CategoryName = this.CategoryName,
EventId = this.EventId,
FormattedMessage = this.FormattedMessage,
Exception = this.Exception,
State = this.State,
};
var bufferedScopes = this.BufferedScopes;
if (bufferedScopes != null)
{
copy.BufferedScopes = new List<object?>(bufferedScopes);
}
return copy;
}
}
private readonly struct ScopeForEachState<TState>
{
public static readonly Action<object?, ScopeForEachState<TState>> ForEachScope = (object? scope, ScopeForEachState<TState> state) =>
{
LogRecordScope logRecordScope = new LogRecordScope(scope);
state.Callback(logRecordScope, state.UserState);
};
public readonly Action<LogRecordScope, TState> Callback;
public readonly TState UserState;
public ScopeForEachState(Action<LogRecordScope, TState> callback, TState state)
{
this.Callback = callback;
this.UserState = state;
}
this.Callback = callback;
this.UserState = state;
}
}
}

View File

@ -18,89 +18,88 @@
using System.Collections;
namespace OpenTelemetry.Logs
namespace OpenTelemetry.Logs;
/// <summary>
/// Stores details about a scope attached to a log message.
/// </summary>
public readonly struct LogRecordScope
{
/// <summary>
/// Stores details about a scope attached to a log message.
/// </summary>
public readonly struct LogRecordScope
internal LogRecordScope(object? scope)
{
internal LogRecordScope(object? scope)
this.Scope = scope;
}
/// <summary>
/// Gets the raw scope value.
/// </summary>
public object? Scope { get; }
/// <summary>
/// Gets an <see cref="IEnumerator"/> for looping over the inner values
/// of the scope.
/// </summary>
/// <returns><see cref="Enumerator"/>.</returns>
public Enumerator GetEnumerator() => new(this.Scope);
/// <summary>
/// LogRecordScope enumerator.
/// </summary>
public struct Enumerator : IEnumerator<KeyValuePair<string, object?>>
{
private readonly IReadOnlyList<KeyValuePair<string, object?>> scope;
private int position;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="scope">Scope.</param>
public Enumerator(object? scope)
{
this.Scope = scope;
if (scope is IReadOnlyList<KeyValuePair<string, object?>> scopeList)
{
this.scope = scopeList;
}
else if (scope is IEnumerable<KeyValuePair<string, object?>> scopeEnumerable)
{
this.scope = new List<KeyValuePair<string, object?>>(scopeEnumerable);
}
else
{
this.scope = new List<KeyValuePair<string, object?>>
{
new KeyValuePair<string, object?>(string.Empty, scope),
};
}
this.position = 0;
this.Current = default;
}
/// <summary>
/// Gets the raw scope value.
/// </summary>
public object? Scope { get; }
/// <inheritdoc/>
public KeyValuePair<string, object?> Current { get; private set; }
/// <summary>
/// Gets an <see cref="IEnumerator"/> for looping over the inner values
/// of the scope.
/// </summary>
/// <returns><see cref="Enumerator"/>.</returns>
public Enumerator GetEnumerator() => new(this.Scope);
object IEnumerator.Current => this.Current;
/// <summary>
/// LogRecordScope enumerator.
/// </summary>
public struct Enumerator : IEnumerator<KeyValuePair<string, object?>>
/// <inheritdoc/>
public bool MoveNext()
{
private readonly IReadOnlyList<KeyValuePair<string, object?>> scope;
private int position;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="scope">Scope.</param>
public Enumerator(object? scope)
if (this.position < this.scope.Count)
{
if (scope is IReadOnlyList<KeyValuePair<string, object?>> scopeList)
{
this.scope = scopeList;
}
else if (scope is IEnumerable<KeyValuePair<string, object?>> scopeEnumerable)
{
this.scope = new List<KeyValuePair<string, object?>>(scopeEnumerable);
}
else
{
this.scope = new List<KeyValuePair<string, object?>>
{
new KeyValuePair<string, object?>(string.Empty, scope),
};
}
this.position = 0;
this.Current = default;
this.Current = this.scope[this.position++];
return true;
}
/// <inheritdoc/>
public KeyValuePair<string, object?> Current { get; private set; }
object IEnumerator.Current => this.Current;
/// <inheritdoc/>
public bool MoveNext()
{
if (this.position < this.scope.Count)
{
this.Current = this.scope[this.position++];
return true;
}
return false;
}
/// <inheritdoc/>
public void Dispose()
{
}
/// <inheritdoc/>
public void Reset()
=> throw new NotSupportedException();
return false;
}
/// <inheritdoc/>
public void Dispose()
{
}
/// <inheritdoc/>
public void Reset()
=> throw new NotSupportedException();
}
}

View File

@ -16,12 +16,11 @@
#nullable enable
namespace OpenTelemetry.Logs
{
internal interface ILogRecordPool
{
LogRecord Rent();
namespace OpenTelemetry.Logs;
void Return(LogRecord logRecord);
}
internal interface ILogRecordPool
{
LogRecord Rent();
void Return(LogRecord logRecord);
}

View File

@ -16,45 +16,44 @@
#nullable enable
namespace OpenTelemetry.Logs
namespace OpenTelemetry.Logs;
internal static class LogRecordPoolHelper
{
internal static class LogRecordPoolHelper
public const int DefaultMaxNumberOfAttributes = 64;
public const int DefaultMaxNumberOfScopes = 16;
public static void Clear(LogRecord logRecord)
{
public const int DefaultMaxNumberOfAttributes = 64;
public const int DefaultMaxNumberOfScopes = 16;
public static void Clear(LogRecord logRecord)
var attributeStorage = logRecord.AttributeStorage;
if (attributeStorage != null)
{
var attributeStorage = logRecord.AttributeStorage;
if (attributeStorage != null)
if (attributeStorage.Count > DefaultMaxNumberOfAttributes)
{
if (attributeStorage.Count > DefaultMaxNumberOfAttributes)
{
// Don't allow the pool to grow unconstained.
logRecord.AttributeStorage = null;
}
else
{
/* List<T>.Clear sets the count/size to 0 but it maintains the
underlying array (capacity). */
attributeStorage.Clear();
}
// Don't allow the pool to grow unconstained.
logRecord.AttributeStorage = null;
}
var scopeStorage = logRecord.ScopeStorage;
if (scopeStorage != null)
else
{
if (scopeStorage.Count > DefaultMaxNumberOfScopes)
{
// Don't allow the pool to grow unconstained.
logRecord.ScopeStorage = null;
}
else
{
/* List<T>.Clear sets the count/size to 0 but it maintains the
underlying array (capacity). */
scopeStorage.Clear();
}
/* List<T>.Clear sets the count/size to 0 but it maintains the
underlying array (capacity). */
attributeStorage.Clear();
}
}
var scopeStorage = logRecord.ScopeStorage;
if (scopeStorage != null)
{
if (scopeStorage.Count > DefaultMaxNumberOfScopes)
{
// Don't allow the pool to grow unconstained.
logRecord.ScopeStorage = null;
}
else
{
/* List<T>.Clear sets the count/size to 0 but it maintains the
underlying array (capacity). */
scopeStorage.Clear();
}
}
}

View File

@ -19,130 +19,129 @@
using System.Diagnostics.CodeAnalysis;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Logs
namespace OpenTelemetry.Logs;
internal sealed class LogRecordSharedPool : ILogRecordPool
{
internal sealed class LogRecordSharedPool : ILogRecordPool
public const int DefaultMaxPoolSize = 2048;
public static LogRecordSharedPool Current = new(DefaultMaxPoolSize);
public readonly int Capacity;
private readonly LogRecord?[] pool;
private long rentIndex;
private long returnIndex;
public LogRecordSharedPool(int capacity)
{
public const int DefaultMaxPoolSize = 2048;
this.Capacity = capacity;
this.pool = new LogRecord?[capacity];
}
public static LogRecordSharedPool Current = new(DefaultMaxPoolSize);
public int Count => (int)(Volatile.Read(ref this.returnIndex) - Volatile.Read(ref this.rentIndex));
public readonly int Capacity;
private readonly LogRecord?[] pool;
private long rentIndex;
private long returnIndex;
// Note: It might make sense to expose this (somehow) in the future.
// Ideal config is shared pool capacity == max batch size.
public static void Resize(int capacity)
{
Guard.ThrowIfOutOfRange(capacity, min: 1);
public LogRecordSharedPool(int capacity)
Current = new(capacity);
}
public LogRecord Rent()
{
while (true)
{
this.Capacity = capacity;
this.pool = new LogRecord?[capacity];
}
var rentSnapshot = Volatile.Read(ref this.rentIndex);
var returnSnapshot = Volatile.Read(ref this.returnIndex);
public int Count => (int)(Volatile.Read(ref this.returnIndex) - Volatile.Read(ref this.rentIndex));
// Note: It might make sense to expose this (somehow) in the future.
// Ideal config is shared pool capacity == max batch size.
public static void Resize(int capacity)
{
Guard.ThrowIfOutOfRange(capacity, min: 1);
Current = new(capacity);
}
public LogRecord Rent()
{
while (true)
if (rentSnapshot >= returnSnapshot)
{
var rentSnapshot = Volatile.Read(ref this.rentIndex);
var returnSnapshot = Volatile.Read(ref this.returnIndex);
if (rentSnapshot >= returnSnapshot)
{
break; // buffer is empty
}
if (Interlocked.CompareExchange(ref this.rentIndex, rentSnapshot + 1, rentSnapshot) == rentSnapshot)
{
var logRecord = Interlocked.Exchange(ref this.pool[rentSnapshot % this.Capacity], null);
if (logRecord == null && !this.TryRentCoreRare(rentSnapshot, out logRecord))
{
continue;
}
logRecord.ResetReferenceCount();
return logRecord;
}
break; // buffer is empty
}
var newLogRecord = new LogRecord();
newLogRecord.ResetReferenceCount();
return newLogRecord;
if (Interlocked.CompareExchange(ref this.rentIndex, rentSnapshot + 1, rentSnapshot) == rentSnapshot)
{
var logRecord = Interlocked.Exchange(ref this.pool[rentSnapshot % this.Capacity], null);
if (logRecord == null && !this.TryRentCoreRare(rentSnapshot, out logRecord))
{
continue;
}
logRecord.ResetReferenceCount();
return logRecord;
}
}
public void Return(LogRecord logRecord)
var newLogRecord = new LogRecord();
newLogRecord.ResetReferenceCount();
return newLogRecord;
}
public void Return(LogRecord logRecord)
{
if (logRecord.RemoveReference() != 0)
{
if (logRecord.RemoveReference() != 0)
return;
}
LogRecordPoolHelper.Clear(logRecord);
while (true)
{
var rentSnapshot = Volatile.Read(ref this.rentIndex);
var returnSnapshot = Volatile.Read(ref this.returnIndex);
if (returnSnapshot - rentSnapshot >= this.Capacity)
{
return; // buffer is full
}
if (Interlocked.CompareExchange(ref this.returnIndex, returnSnapshot + 1, returnSnapshot) == returnSnapshot)
{
// If many threads are hammering rent/return it is possible
// for two threads to write to the same index. In that case
// only one of the logRecords will make it back into the
// pool. Anything lost in the race will collected by the GC
// and the pool will issue new instances as needed. This
// could be abated by an Interlocked.CompareExchange here
// but for the general use case of an exporter returning
// records one-by-one, better to keep this fast and not pay
// for Interlocked.CompareExchange. The race is more
// theoretical.
this.pool[returnSnapshot % this.Capacity] = logRecord;
return;
}
LogRecordPoolHelper.Clear(logRecord);
while (true)
{
var rentSnapshot = Volatile.Read(ref this.rentIndex);
var returnSnapshot = Volatile.Read(ref this.returnIndex);
if (returnSnapshot - rentSnapshot >= this.Capacity)
{
return; // buffer is full
}
if (Interlocked.CompareExchange(ref this.returnIndex, returnSnapshot + 1, returnSnapshot) == returnSnapshot)
{
// If many threads are hammering rent/return it is possible
// for two threads to write to the same index. In that case
// only one of the logRecords will make it back into the
// pool. Anything lost in the race will collected by the GC
// and the pool will issue new instances as needed. This
// could be abated by an Interlocked.CompareExchange here
// but for the general use case of an exporter returning
// records one-by-one, better to keep this fast and not pay
// for Interlocked.CompareExchange. The race is more
// theoretical.
this.pool[returnSnapshot % this.Capacity] = logRecord;
return;
}
}
}
}
private bool TryRentCoreRare(long rentSnapshot, [NotNullWhen(true)] out LogRecord? logRecord)
private bool TryRentCoreRare(long rentSnapshot, [NotNullWhen(true)] out LogRecord? logRecord)
{
SpinWait wait = default;
while (true)
{
SpinWait wait = default;
while (true)
if (wait.NextSpinWillYield)
{
if (wait.NextSpinWillYield)
{
// Super rare case. If many threads are hammering
// rent/return it is possible a read was issued an index and
// then yielded while other threads caused the pointers to
// wrap around. When the yielded thread wakes up its read
// index could have been stolen by another thread. To
// prevent deadlock, bail out of read after spinning. This
// will cause either a successful rent from another index,
// or a new record to be created
logRecord = null;
return false;
}
// Super rare case. If many threads are hammering
// rent/return it is possible a read was issued an index and
// then yielded while other threads caused the pointers to
// wrap around. When the yielded thread wakes up its read
// index could have been stolen by another thread. To
// prevent deadlock, bail out of read after spinning. This
// will cause either a successful rent from another index,
// or a new record to be created
logRecord = null;
return false;
}
wait.SpinOnce();
wait.SpinOnce();
logRecord = Interlocked.Exchange(ref this.pool[rentSnapshot % this.Capacity], null);
if (logRecord != null)
{
// Rare case where the write was still working when the read came in
return true;
}
logRecord = Interlocked.Exchange(ref this.pool[rentSnapshot % this.Capacity], null);
if (logRecord != null)
{
// Rare case where the write was still working when the read came in
return true;
}
}
}

View File

@ -16,38 +16,37 @@
#nullable enable
namespace OpenTelemetry.Logs
namespace OpenTelemetry.Logs;
internal sealed class LogRecordThreadStaticPool : ILogRecordPool
{
internal sealed class LogRecordThreadStaticPool : ILogRecordPool
[ThreadStatic]
public static LogRecord? Storage;
private LogRecordThreadStaticPool()
{
[ThreadStatic]
public static LogRecord? Storage;
}
private LogRecordThreadStaticPool()
public static LogRecordThreadStaticPool Instance { get; } = new();
public LogRecord Rent()
{
var logRecord = Storage;
if (logRecord != null)
{
Storage = null;
return logRecord;
}
public static LogRecordThreadStaticPool Instance { get; } = new();
return new();
}
public LogRecord Rent()
public void Return(LogRecord logRecord)
{
if (Storage == null)
{
var logRecord = Storage;
if (logRecord != null)
{
Storage = null;
return logRecord;
}
return new();
}
public void Return(LogRecord logRecord)
{
if (Storage == null)
{
LogRecordPoolHelper.Clear(logRecord);
Storage = logRecord;
}
LogRecordPoolHelper.Clear(logRecord);
Storage = logRecord;
}
}
}

View File

@ -18,20 +18,19 @@
using OpenTelemetry.Logs;
namespace OpenTelemetry
namespace OpenTelemetry;
/// <summary>
/// Implements a simple log record export processor.
/// </summary>
public class SimpleLogRecordExportProcessor : SimpleExportProcessor<LogRecord>
{
/// <summary>
/// Implements a simple log record export processor.
/// Initializes a new instance of the <see cref="SimpleLogRecordExportProcessor"/> class.
/// </summary>
public class SimpleLogRecordExportProcessor : SimpleExportProcessor<LogRecord>
/// <param name="exporter">Log record exporter.</param>
public SimpleLogRecordExportProcessor(BaseExporter<LogRecord> exporter)
: base(exporter)
{
/// <summary>
/// Initializes a new instance of the <see cref="SimpleLogRecordExportProcessor"/> class.
/// </summary>
/// <param name="exporter">Log record exporter.</param>
public SimpleLogRecordExportProcessor(BaseExporter<LogRecord> exporter)
: base(exporter)
{
}
}
}

View File

@ -21,82 +21,81 @@ using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
namespace OpenTelemetry
namespace OpenTelemetry;
/// <summary>
/// Contains provider extension methods.
/// </summary>
public static class ProviderExtensions
{
/// <summary>
/// Contains provider extension methods.
/// Gets the <see cref="Resource"/> associated with the <see cref="BaseProvider"/>.
/// </summary>
public static class ProviderExtensions
/// <param name="baseProvider"><see cref="BaseProvider"/>.</param>
/// <returns><see cref="Resource"/>if found otherwise <see cref="Resource.Empty"/>.</returns>
public static Resource GetResource(this BaseProvider baseProvider)
{
/// <summary>
/// Gets the <see cref="Resource"/> associated with the <see cref="BaseProvider"/>.
/// </summary>
/// <param name="baseProvider"><see cref="BaseProvider"/>.</param>
/// <returns><see cref="Resource"/>if found otherwise <see cref="Resource.Empty"/>.</returns>
public static Resource GetResource(this BaseProvider baseProvider)
if (baseProvider is TracerProviderSdk tracerProviderSdk)
{
if (baseProvider is TracerProviderSdk tracerProviderSdk)
{
return tracerProviderSdk.Resource;
}
else if (baseProvider is MeterProviderSdk meterProviderSdk)
{
return meterProviderSdk.Resource;
}
else if (baseProvider is LoggerProviderSdk loggerProviderSdk)
{
return loggerProviderSdk.Resource;
}
else if (baseProvider is OpenTelemetryLoggerProvider openTelemetryLoggerProvider)
{
return openTelemetryLoggerProvider.Provider.GetResource();
}
return Resource.Empty;
return tracerProviderSdk.Resource;
}
else if (baseProvider is MeterProviderSdk meterProviderSdk)
{
return meterProviderSdk.Resource;
}
else if (baseProvider is LoggerProviderSdk loggerProviderSdk)
{
return loggerProviderSdk.Resource;
}
else if (baseProvider is OpenTelemetryLoggerProvider openTelemetryLoggerProvider)
{
return openTelemetryLoggerProvider.Provider.GetResource();
}
/// <summary>
/// Gets the <see cref="Resource"/> associated with the <see cref="BaseProvider"/>.
/// </summary>
/// <param name="baseProvider"><see cref="BaseProvider"/>.</param>
/// <returns><see cref="Resource"/>if found otherwise <see cref="Resource.Empty"/>.</returns>
public static Resource GetDefaultResource(this BaseProvider baseProvider)
return Resource.Empty;
}
/// <summary>
/// Gets the <see cref="Resource"/> associated with the <see cref="BaseProvider"/>.
/// </summary>
/// <param name="baseProvider"><see cref="BaseProvider"/>.</param>
/// <returns><see cref="Resource"/>if found otherwise <see cref="Resource.Empty"/>.</returns>
public static Resource GetDefaultResource(this BaseProvider baseProvider)
{
var builder = ResourceBuilder.CreateDefault();
builder.ServiceProvider = GetServiceProvider(baseProvider);
return builder.Build();
}
internal static IServiceProvider? GetServiceProvider(this BaseProvider baseProvider)
{
if (baseProvider is TracerProviderSdk tracerProviderSdk)
{
var builder = ResourceBuilder.CreateDefault();
builder.ServiceProvider = GetServiceProvider(baseProvider);
return builder.Build();
return tracerProviderSdk.ServiceProvider;
}
else if (baseProvider is MeterProviderSdk meterProviderSdk)
{
return meterProviderSdk.ServiceProvider;
}
else if (baseProvider is LoggerProviderSdk loggerProviderSdk)
{
return loggerProviderSdk.ServiceProvider;
}
else if (baseProvider is OpenTelemetryLoggerProvider openTelemetryLoggerProvider)
{
return openTelemetryLoggerProvider.Provider.GetServiceProvider();
}
internal static IServiceProvider? GetServiceProvider(this BaseProvider baseProvider)
{
if (baseProvider is TracerProviderSdk tracerProviderSdk)
{
return tracerProviderSdk.ServiceProvider;
}
else if (baseProvider is MeterProviderSdk meterProviderSdk)
{
return meterProviderSdk.ServiceProvider;
}
else if (baseProvider is LoggerProviderSdk loggerProviderSdk)
{
return loggerProviderSdk.ServiceProvider;
}
else if (baseProvider is OpenTelemetryLoggerProvider openTelemetryLoggerProvider)
{
return openTelemetryLoggerProvider.Provider.GetServiceProvider();
}
return null;
}
return null;
internal static Action? GetObservableInstrumentCollectCallback(this BaseProvider baseProvider)
{
if (baseProvider is MeterProviderSdk meterProviderSdk)
{
return meterProviderSdk.CollectObservableInstruments;
}
internal static Action? GetObservableInstrumentCollectCallback(this BaseProvider baseProvider)
{
if (baseProvider is MeterProviderSdk meterProviderSdk)
{
return meterProviderSdk.CollectObservableInstruments;
}
return null;
}
return null;
}
}