[sdk-logs] [nits] Update source files to use file scoped namespaces (#4476)
This commit is contained in:
parent
660d5b55cf
commit
f6fc73e02f
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string,
|
||||
/// object></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<string,
|
||||
/// object></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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue