From 6d2f153ba396b423253cf052e823e1f903b09bb6 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 24 May 2022 16:48:02 -0700 Subject: [PATCH 01/41] LogEmitter API. --- .../Controllers/WeatherForecastController.cs | 18 ++++- .../.publicApi/net462/PublicAPI.Unshipped.txt | 5 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 5 ++ src/OpenTelemetry/Logs/LogEmitter.cs | 67 +++++++++++++++++++ src/OpenTelemetry/Logs/LogRecord.cs | 23 +++++++ .../Logs/OpenTelemetryLoggerProvider.cs | 6 ++ .../Logs/OpenTelemetryLoggingExtensions.cs | 6 +- 7 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 src/OpenTelemetry/Logs/LogEmitter.cs diff --git a/examples/AspNetCore/Controllers/WeatherForecastController.cs b/examples/AspNetCore/Controllers/WeatherForecastController.cs index 54f19e63b4d..01edfa0aa17 100644 --- a/examples/AspNetCore/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore/Controllers/WeatherForecastController.cs @@ -17,6 +17,7 @@ namespace Examples.AspNetCore.Controllers; using Microsoft.AspNetCore.Mvc; +using OpenTelemetry.Logs; [ApiController] [Route("[controller]")] @@ -24,16 +25,20 @@ public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", }; private static readonly HttpClient HttpClient = new(); private readonly ILogger logger; + private readonly LogEmitter logEmitter; - public WeatherForecastController(ILogger logger) + public WeatherForecastController( + ILogger logger, + LogEmitter logEmitter) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.logEmitter = logEmitter ?? throw new ArgumentNullException(nameof(logEmitter)); } [HttpGet] @@ -54,11 +59,20 @@ public IEnumerable Get() }) .ToArray(); + // Log using ILogger API. this.logger.LogInformation( "WeatherForecasts generated {count}: {forecasts}", forecast.Length, forecast); + // Log using LogEmitter API. + this.logEmitter.Log(new( + categoryName: "WeatherForecasts", + timestamp: DateTime.UtcNow, + logLevel: LogLevel.Information, + message: "WeatherForecasts generated.", + stateValues: new List>() { new KeyValuePair("count", forecast.Length) })); + return forecast; } } diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index ff0d049a675..88aa0e45729 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -1,4 +1,9 @@ #nullable enable +OpenTelemetry.Logs.LogEmitter +OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void +OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void +OpenTelemetry.Logs.LogRecord.LogRecord(string! categoryName, System.DateTime timestamp, Microsoft.Extensions.Logging.LogLevel logLevel, string! message, Microsoft.Extensions.Logging.EventId eventId = default(Microsoft.Extensions.Logging.EventId), System.Exception? exception = null, System.Collections.Generic.IReadOnlyList>? stateValues = null) -> void OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index ff0d049a675..88aa0e45729 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,4 +1,9 @@ #nullable enable +OpenTelemetry.Logs.LogEmitter +OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void +OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void +OpenTelemetry.Logs.LogRecord.LogRecord(string! categoryName, System.DateTime timestamp, Microsoft.Extensions.Logging.LogLevel logLevel, string! message, Microsoft.Extensions.Logging.EventId eventId = default(Microsoft.Extensions.Logging.EventId), System.Exception? exception = null, System.Collections.Generic.IReadOnlyList>? stateValues = null) -> void OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! diff --git a/src/OpenTelemetry/Logs/LogEmitter.cs b/src/OpenTelemetry/Logs/LogEmitter.cs new file mode 100644 index 00000000000..1a301aadc07 --- /dev/null +++ b/src/OpenTelemetry/Logs/LogEmitter.cs @@ -0,0 +1,67 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Logs +{ + /// + /// LogEmitter implementation. + /// + /// + /// Spec reference: LogEmitter. + /// + public sealed class LogEmitter + { + private readonly OpenTelemetryLoggerProvider loggerProvider; + + /// + /// Initializes a new instance of the class. + /// + /// . + public LogEmitter(OpenTelemetryLoggerProvider loggerProvider) + { + Guard.ThrowIfNull(loggerProvider); + + this.loggerProvider = loggerProvider; + } + + /// + /// Emit a . + /// + /// . + public void Log(LogRecord logRecord) + { + Guard.ThrowIfNull(logRecord); + + var provider = this.loggerProvider; + var processor = provider.Processor; + if (processor != null) + { + if (provider.IncludeScopes) + { + logRecord.ScopeProvider = provider.ScopeProvider; + } + + processor.OnEnd(logRecord); + + logRecord.ScopeProvider = null; + } + } + } +} diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index 94722fd4cad..497c9f57e1f 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -36,6 +36,29 @@ public sealed class LogRecord private List? bufferedScopes; + public LogRecord( + string categoryName, + DateTime timestamp, + LogLevel logLevel, + string message, + EventId eventId = default, + Exception? exception = null, + IReadOnlyList>? stateValues = null) + : this( + scopeProvider: null, + timestamp.Kind == DateTimeKind.Local ? timestamp.ToUniversalTime() : timestamp, + categoryName, + logLevel, + eventId, + message, + state: null, + exception, + stateValues) + { + Guard.ThrowIfNullOrEmpty(categoryName); + Guard.ThrowIfNullOrEmpty(message); + } + internal LogRecord( IExternalScopeProvider? scopeProvider, DateTime timestamp, diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs index 0c33a7268a3..4a1927a314c 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs @@ -112,6 +112,12 @@ public ILogger CreateLogger(string categoryName) return logger; } + /// + /// Create a . + /// + /// . + public LogEmitter CreateEmitter() => new(this); + internal OpenTelemetryLoggerProvider AddProcessor(BaseProcessor processor) { Guard.ThrowIfNull(processor); diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs index 000542b2fbd..19f1fc04bc9 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs @@ -42,7 +42,11 @@ public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder, Act Guard.ThrowIfNull(builder); builder.AddConfiguration(); - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddEnumerable( + ServiceDescriptor.Singleton( + sp => sp.GetRequiredService())); if (configure != null) { From 101ce195dd2e062e56b93cd1aa0d3314c3b3e8b9 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Wed, 25 May 2022 15:01:10 -0700 Subject: [PATCH 02/41] Code review. --- .../ConsoleLogRecordExporter.cs | 6 +- .../.publicApi/net462/PublicAPI.Shipped.txt | 2 +- .../.publicApi/net462/PublicAPI.Unshipped.txt | 12 ++- .../netstandard2.0/PublicAPI.Shipped.txt | 2 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 12 ++- src/OpenTelemetry/Logs/LogRecord.cs | 100 +++++++++--------- 6 files changed, 80 insertions(+), 54 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs index b8e2cde014d..03cee10a9de 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs @@ -42,7 +42,11 @@ public override ExportResult Export(in Batch batch) this.WriteLine($"{"LogRecord.TraceFlags:",-RightPaddingLength}{logRecord.TraceFlags}"); } - this.WriteLine($"{"LogRecord.CategoryName:",-RightPaddingLength}{logRecord.CategoryName}"); + if (logRecord.CategoryName != null) + { + this.WriteLine($"{"LogRecord.CategoryName:",-RightPaddingLength}{logRecord.CategoryName}"); + } + this.WriteLine($"{"LogRecord.LogLevel:",-RightPaddingLength}{logRecord.LogLevel}"); if (logRecord.FormattedMessage != null) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt index 8b30b3a9d50..ea3aecdecc0 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt @@ -55,7 +55,7 @@ OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Failure = 1 -> OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Success = 0 -> OpenTelemetry.ExportResult OpenTelemetry.Logs.LogRecord -OpenTelemetry.Logs.LogRecord.CategoryName.get -> string! +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? OpenTelemetry.Logs.LogRecord.EventId.get -> Microsoft.Extensions.Logging.EventId OpenTelemetry.Logs.LogRecord.Exception.get -> System.Exception? OpenTelemetry.Logs.LogRecord.ForEachScope(System.Action! callback, TState state) -> void diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 88aa0e45729..9bfc533b271 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -2,8 +2,18 @@ OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void +OpenTelemetry.Logs.LogRecord.CategoryName.set -> void +OpenTelemetry.Logs.LogRecord.EventId.set -> void +OpenTelemetry.Logs.LogRecord.Exception.set -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void -OpenTelemetry.Logs.LogRecord.LogRecord(string! categoryName, System.DateTime timestamp, Microsoft.Extensions.Logging.LogLevel logLevel, string! message, Microsoft.Extensions.Logging.EventId eventId = default(Microsoft.Extensions.Logging.EventId), System.Exception? exception = null, System.Collections.Generic.IReadOnlyList>? stateValues = null) -> void +OpenTelemetry.Logs.LogRecord.LogLevel.set -> void +OpenTelemetry.Logs.LogRecord.LogRecord() -> void +OpenTelemetry.Logs.LogRecord.SetActivityContext(System.Diagnostics.Activity? activity) -> void +OpenTelemetry.Logs.LogRecord.SpanId.set -> void OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void +OpenTelemetry.Logs.LogRecord.Timestamp.set -> void +OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecord.TraceId.set -> void +OpenTelemetry.Logs.LogRecord.TraceState.set -> void OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt index 8b30b3a9d50..ea3aecdecc0 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -55,7 +55,7 @@ OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Failure = 1 -> OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Success = 0 -> OpenTelemetry.ExportResult OpenTelemetry.Logs.LogRecord -OpenTelemetry.Logs.LogRecord.CategoryName.get -> string! +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? OpenTelemetry.Logs.LogRecord.EventId.get -> Microsoft.Extensions.Logging.EventId OpenTelemetry.Logs.LogRecord.Exception.get -> System.Exception? OpenTelemetry.Logs.LogRecord.ForEachScope(System.Action! callback, TState state) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 88aa0e45729..9bfc533b271 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -2,8 +2,18 @@ OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void +OpenTelemetry.Logs.LogRecord.CategoryName.set -> void +OpenTelemetry.Logs.LogRecord.EventId.set -> void +OpenTelemetry.Logs.LogRecord.Exception.set -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void -OpenTelemetry.Logs.LogRecord.LogRecord(string! categoryName, System.DateTime timestamp, Microsoft.Extensions.Logging.LogLevel logLevel, string! message, Microsoft.Extensions.Logging.EventId eventId = default(Microsoft.Extensions.Logging.EventId), System.Exception? exception = null, System.Collections.Generic.IReadOnlyList>? stateValues = null) -> void +OpenTelemetry.Logs.LogRecord.LogLevel.set -> void +OpenTelemetry.Logs.LogRecord.LogRecord() -> void +OpenTelemetry.Logs.LogRecord.SetActivityContext(System.Diagnostics.Activity? activity) -> void +OpenTelemetry.Logs.LogRecord.SpanId.set -> void OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void +OpenTelemetry.Logs.LogRecord.Timestamp.set -> void +OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecord.TraceId.set -> void +OpenTelemetry.Logs.LogRecord.TraceState.set -> void OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index 497c9f57e1f..eb0c3f4ebd6 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -35,30 +35,21 @@ public sealed class LogRecord }; private List? bufferedScopes; + private DateTime timestamp; - public LogRecord( - string categoryName, - DateTime timestamp, - LogLevel logLevel, - string message, - EventId eventId = default, - Exception? exception = null, - IReadOnlyList>? stateValues = null) - : this( - scopeProvider: null, - timestamp.Kind == DateTimeKind.Local ? timestamp.ToUniversalTime() : timestamp, - categoryName, - logLevel, - eventId, - message, - state: null, - exception, - stateValues) + /// + /// Initializes a new instance of the class. + /// + /// + /// Note: The property is initialized to . + /// + public LogRecord() { - Guard.ThrowIfNullOrEmpty(categoryName); - Guard.ThrowIfNullOrEmpty(message); + this.timestamp = DateTime.UtcNow; } + // Note: Some users are calling this with reflection. Try not to change the signature to be nice. internal LogRecord( IExternalScopeProvider? scopeProvider, DateTime timestamp, @@ -71,17 +62,7 @@ internal LogRecord( IReadOnlyList>? stateValues) { this.ScopeProvider = scopeProvider; - - var activity = Activity.Current; - if (activity != null) - { - this.TraceId = activity.TraceId; - this.SpanId = activity.SpanId; - this.TraceState = activity.TraceStateString; - this.TraceFlags = activity.ActivityTraceFlags; - } - - this.Timestamp = timestamp; + this.timestamp = timestamp; this.CategoryName = categoryName; this.LogLevel = logLevel; this.EventId = eventId; @@ -89,47 +70,53 @@ internal LogRecord( this.State = state; this.StateValues = stateValues; this.Exception = exception; + + this.SetActivityContext(Activity.Current); } /// - /// Gets the log timestamp. + /// Gets or sets the log timestamp. /// - public DateTime Timestamp { get; } + public DateTime Timestamp + { + get => this.timestamp; + set { this.timestamp = value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : value; } + } /// - /// Gets the log . + /// Gets or sets the log . /// - public ActivityTraceId TraceId { get; } + public ActivityTraceId TraceId { get; set; } /// - /// Gets the log . + /// Gets or sets the log . /// - public ActivitySpanId SpanId { get; } + public ActivitySpanId SpanId { get; set; } /// - /// Gets the log . + /// Gets or sets the log . /// - public ActivityTraceFlags TraceFlags { get; } + public ActivityTraceFlags TraceFlags { get; set; } /// - /// Gets the log trace state. + /// Gets or sets the log trace state. /// - public string? TraceState { get; } + public string? TraceState { get; set; } /// - /// Gets the log category name. + /// Gets or sets the log category name. /// - public string CategoryName { get; } + public string? CategoryName { get; set; } /// - /// Gets the log . + /// Gets or sets the log . /// - public LogLevel LogLevel { get; } + public LogLevel LogLevel { get; set; } /// - /// Gets the log . + /// Gets or sets the log . /// - public EventId EventId { get; } + public EventId EventId { get; set; } /// /// Gets or sets the log formatted message. @@ -151,12 +138,27 @@ internal LogRecord( public IReadOnlyList>? StateValues { get; set; } /// - /// Gets the log . + /// Gets or sets the log . /// - public Exception? Exception { get; } + public Exception? Exception { get; set; } internal IExternalScopeProvider? ScopeProvider { get; set; } + /// + /// Set the log activity context fields from the supplied . + /// + /// . + public void SetActivityContext(Activity? activity) + { + if (activity != null) + { + this.TraceId = activity.TraceId; + this.SpanId = activity.SpanId; + this.TraceState = activity.TraceStateString; + this.TraceFlags = activity.ActivityTraceFlags; + } + } + /// /// Executes callback for each currently active scope objects in order /// of creation. All callbacks are guaranteed to be called inline from From 7eb000624d18a6b820e562f6bfa8bd8d5c140a81 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 26 May 2022 12:53:47 -0700 Subject: [PATCH 03/41] Added LogRecordPool. --- .../Controllers/WeatherForecastController.cs | 16 +- .../.publicApi/net462/PublicAPI.Unshipped.txt | 11 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 11 ++ src/OpenTelemetry/Batch.cs | 120 +++++++++++--- src/OpenTelemetry/BatchExportProcessor.cs | 14 +- .../Internal/Shims/IsExternalInit.cs | 25 +++ .../Internal/Shims/NullableAttributes.cs | 51 ++++++ .../Logs/BatchLogRecordExportProcessor.cs | 1 + src/OpenTelemetry/Logs/LogRecord.cs | 40 +++-- src/OpenTelemetry/Logs/LogRecordPool.cs | 156 ++++++++++++++++++ src/OpenTelemetry/Logs/OpenTelemetryLogger.cs | 24 +-- .../Logs/SimpleLogRecordExportProcessor.cs | 1 + src/OpenTelemetry/SimpleExportProcessor.cs | 14 +- 13 files changed, 429 insertions(+), 55 deletions(-) create mode 100644 src/OpenTelemetry/Internal/Shims/IsExternalInit.cs create mode 100644 src/OpenTelemetry/Internal/Shims/NullableAttributes.cs create mode 100644 src/OpenTelemetry/Logs/LogRecordPool.cs diff --git a/examples/AspNetCore/Controllers/WeatherForecastController.cs b/examples/AspNetCore/Controllers/WeatherForecastController.cs index 01edfa0aa17..f15f67d6ebf 100644 --- a/examples/AspNetCore/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore/Controllers/WeatherForecastController.cs @@ -16,6 +16,7 @@ namespace Examples.AspNetCore.Controllers; +using System.Diagnostics; using Microsoft.AspNetCore.Mvc; using OpenTelemetry.Logs; @@ -66,12 +67,15 @@ public IEnumerable Get() forecast); // Log using LogEmitter API. - this.logEmitter.Log(new( - categoryName: "WeatherForecasts", - timestamp: DateTime.UtcNow, - logLevel: LogLevel.Information, - message: "WeatherForecasts generated.", - stateValues: new List>() { new KeyValuePair("count", forecast.Length) })); + var logRecord = LogRecordPool.Rent(); + + logRecord.CategoryName = "WeatherForecasts"; + logRecord.LogLevel = LogLevel.Information; + logRecord.FormattedMessage = "WeatherForecasts generated."; + logRecord.StateValues = new List>() { new KeyValuePair("count", forecast.Length) }; + logRecord.SetActivityContext(Activity.Current); + + this.logEmitter.Log(logRecord); return forecast; } diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 9bfc533b271..2526cdf6149 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -1,4 +1,9 @@ #nullable enable +OpenTelemetry.Batch.Batch(T! item) -> void +OpenTelemetry.Batch.CleanupAction.get -> System.Action? +OpenTelemetry.Batch.CleanupAction.init -> void +OpenTelemetry.BatchExportProcessor.CleanupAction.get -> System.Action? +OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void @@ -16,4 +21,10 @@ OpenTelemetry.Logs.LogRecord.Timestamp.set -> void OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void OpenTelemetry.Logs.LogRecord.TraceId.set -> void OpenTelemetry.Logs.LogRecord.TraceState.set -> void +OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! +OpenTelemetry.SimpleExportProcessor.CleanupAction.get -> System.Action? +OpenTelemetry.SimpleExportProcessor.CleanupAction.init -> void +static OpenTelemetry.Logs.LogRecordPool.Rent() -> OpenTelemetry.Logs.LogRecord! +static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void +static OpenTelemetry.Logs.LogRecordPool.Return(OpenTelemetry.Logs.LogRecord! logRecord) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 9bfc533b271..2526cdf6149 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,4 +1,9 @@ #nullable enable +OpenTelemetry.Batch.Batch(T! item) -> void +OpenTelemetry.Batch.CleanupAction.get -> System.Action? +OpenTelemetry.Batch.CleanupAction.init -> void +OpenTelemetry.BatchExportProcessor.CleanupAction.get -> System.Action? +OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void @@ -16,4 +21,10 @@ OpenTelemetry.Logs.LogRecord.Timestamp.set -> void OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void OpenTelemetry.Logs.LogRecord.TraceId.set -> void OpenTelemetry.Logs.LogRecord.TraceState.set -> void +OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! +OpenTelemetry.SimpleExportProcessor.CleanupAction.get -> System.Action? +OpenTelemetry.SimpleExportProcessor.CleanupAction.init -> void +static OpenTelemetry.Logs.LogRecordPool.Rent() -> OpenTelemetry.Logs.LogRecord! +static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void +static OpenTelemetry.Logs.LogRecordPool.Return(OpenTelemetry.Logs.LogRecord! logRecord) -> void diff --git a/src/OpenTelemetry/Batch.cs b/src/OpenTelemetry/Batch.cs index df54bb81131..be27a1b1591 100644 --- a/src/OpenTelemetry/Batch.cs +++ b/src/OpenTelemetry/Batch.cs @@ -14,10 +14,13 @@ // limitations under the License. // +#nullable enable + using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; namespace OpenTelemetry @@ -29,9 +32,9 @@ namespace OpenTelemetry public readonly struct Batch : IDisposable where T : class { - private readonly T item; - private readonly CircularBuffer circularBuffer; - private readonly T[] items; + private readonly T? item; + private readonly CircularBuffer? circularBuffer; + private readonly T[]? items; private readonly long targetCount; /// @@ -50,9 +53,13 @@ public Batch(T[] items, int count) this.Count = this.targetCount = count; } - internal Batch(T item) + /// + /// Initializes a new instance of the struct. + /// + /// The item to store in the batch. + public Batch(T item) { - Debug.Assert(item != null, $"{nameof(item)} was null."); + Guard.ThrowIfNull(item); this.item = item; this.circularBuffer = null; @@ -68,7 +75,7 @@ internal Batch(CircularBuffer circularBuffer, int maxSize) this.item = null; this.items = null; this.circularBuffer = circularBuffer; - this.Count = Math.Min(maxSize, circularBuffer.Count); + this.Count = Math.Min(maxSize, circularBuffer!.Count); this.targetCount = circularBuffer.RemovedCount + this.Count; } @@ -79,6 +86,11 @@ internal Batch(CircularBuffer circularBuffer, int maxSize) /// public long Count { get; } + /// + /// Gets a cleanup action to be called after each item in the batch is processed. + /// + public Action? CleanupAction { get; init; } = null; + /// public void Dispose() { @@ -87,8 +99,33 @@ public void Dispose() // Drain anything left in the batch. while (this.circularBuffer.RemovedCount < this.targetCount) { - this.circularBuffer.Read(); + T item = this.circularBuffer.Read(); + this.CleanupAction?.Invoke(item); } + + return; + } + + var cleanupAction = this.CleanupAction; + if (cleanupAction == null) + { + return; + } + + if (this.items != null) + { + for (int i = 0; i < this.Count; i++) + { + var item = this.items[i]; + if (item != null) + { + cleanupAction(item); + } + } + } + else + { + cleanupAction(this.item!); } } @@ -99,7 +136,7 @@ public void Dispose() public Enumerator GetEnumerator() { return this.circularBuffer != null - ? new Enumerator(this.circularBuffer, this.targetCount) + ? new Enumerator(this.circularBuffer, this.targetCount, this.CleanupAction) : this.item != null ? new Enumerator(this.item) /* In the event someone uses default/new Batch() to create Batch we fallback to empty items mode. */ @@ -115,7 +152,7 @@ public struct Enumerator : IEnumerator { if (enumerator.targetCount >= 0) { - enumerator.Current = null; + enumerator.current = null; return false; } @@ -127,13 +164,33 @@ public struct Enumerator : IEnumerator { var circularBuffer = enumerator.circularBuffer; - if (circularBuffer.RemovedCount < enumerator.targetCount) + if (circularBuffer!.RemovedCount < enumerator.targetCount) + { + enumerator.current = circularBuffer.Read(); + return true; + } + + enumerator.current = null; + return false; + }; + + private static readonly BatchEnumeratorMoveNextFunc MoveNextCircularBufferWithCleanup = (ref Enumerator enumerator) => + { + var circularBuffer = enumerator.circularBuffer; + + var currentItem = enumerator.Current; + if (currentItem != null) + { + enumerator.cleanupAction!(currentItem); + } + + if (circularBuffer!.RemovedCount < enumerator.targetCount) { - enumerator.Current = circularBuffer.Read(); + enumerator.current = circularBuffer.Read(); return true; } - enumerator.Current = null; + enumerator.current = null; return false; }; @@ -143,59 +200,74 @@ public struct Enumerator : IEnumerator if (enumerator.itemIndex < enumerator.targetCount) { - enumerator.Current = items[enumerator.itemIndex++]; + enumerator.current = items![enumerator.itemIndex++]; return true; } - enumerator.Current = null; + enumerator.current = null; return false; }; - private readonly CircularBuffer circularBuffer; - private readonly T[] items; + private readonly CircularBuffer? circularBuffer; + private readonly T[]? items; private readonly BatchEnumeratorMoveNextFunc moveNextFunc; + private readonly Action? cleanupAction; private long targetCount; private int itemIndex; + [AllowNull] + private T current; internal Enumerator(T item) { - this.Current = item; + this.current = item; this.circularBuffer = null; this.items = null; this.targetCount = -1; this.itemIndex = 0; + this.cleanupAction = null; this.moveNextFunc = MoveNextSingleItem; } - internal Enumerator(CircularBuffer circularBuffer, long targetCount) + internal Enumerator(CircularBuffer circularBuffer, long targetCount, Action? cleanupAction) { - this.Current = null; + this.current = null; this.items = null; this.circularBuffer = circularBuffer; this.targetCount = targetCount; this.itemIndex = 0; - this.moveNextFunc = MoveNextCircularBuffer; + this.cleanupAction = cleanupAction; + this.moveNextFunc = cleanupAction != null ? MoveNextCircularBufferWithCleanup : MoveNextCircularBuffer; } internal Enumerator(T[] items, long targetCount) { - this.Current = null; + this.current = null; this.circularBuffer = null; this.items = items; this.targetCount = targetCount; this.itemIndex = 0; + this.cleanupAction = null; this.moveNextFunc = MoveNextArray; } /// - public T Current { get; private set; } + public readonly T Current => this.current; /// - object IEnumerator.Current => this.Current; + readonly object? IEnumerator.Current => this.current; /// public void Dispose() { + if (this.cleanupAction != null) + { + var currentItem = this.current; + if (currentItem != null) + { + this.cleanupAction(currentItem); + this.current = null; + } + } } /// @@ -205,7 +277,7 @@ public bool MoveNext() } /// - public void Reset() + public readonly void Reset() => throw new NotSupportedException(); } } diff --git a/src/OpenTelemetry/BatchExportProcessor.cs b/src/OpenTelemetry/BatchExportProcessor.cs index 943acf10a9b..46694ef465b 100644 --- a/src/OpenTelemetry/BatchExportProcessor.cs +++ b/src/OpenTelemetry/BatchExportProcessor.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System; using System.Diagnostics; using System.Threading; @@ -93,6 +95,11 @@ protected BatchExportProcessor( /// internal long ProcessedCount => this.circularBuffer.RemovedCount; + /// + /// Gets a cleanup action to be called after each item is exported. + /// + protected Action? CleanupAction { get; init; } + /// protected override void OnExport(T data) { @@ -248,6 +255,8 @@ protected override void Dispose(bool disposing) private void ExporterProc() { + var cleanupAction = this.CleanupAction; + var triggers = new WaitHandle[] { this.exportTrigger, this.shutdownTrigger }; while (true) @@ -268,7 +277,10 @@ private void ExporterProc() if (this.circularBuffer.Count > 0) { - using (var batch = new Batch(this.circularBuffer, this.maxExportBatchSize)) + using (var batch = new Batch(this.circularBuffer, this.maxExportBatchSize) + { + CleanupAction = cleanupAction, + }) { this.exporter.Export(batch); } diff --git a/src/OpenTelemetry/Internal/Shims/IsExternalInit.cs b/src/OpenTelemetry/Internal/Shims/IsExternalInit.cs new file mode 100644 index 00000000000..e2221592a6e --- /dev/null +++ b/src/OpenTelemetry/Internal/Shims/IsExternalInit.cs @@ -0,0 +1,25 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if NETFRAMEWORK || NETSTANDARD2_0 +namespace System.Runtime.CompilerServices +{ + // This enabled "init" keyword in net462 + netstandard2.0 targets. + internal sealed class IsExternalInit + { + } +} +#endif diff --git a/src/OpenTelemetry/Internal/Shims/NullableAttributes.cs b/src/OpenTelemetry/Internal/Shims/NullableAttributes.cs new file mode 100644 index 00000000000..78bcfeab9ae --- /dev/null +++ b/src/OpenTelemetry/Internal/Shims/NullableAttributes.cs @@ -0,0 +1,51 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Source: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs + +#pragma warning disable SA1649 // File name should match first type name +#pragma warning disable SA1402 // File may only contain a single type + +#if NETFRAMEWORK || NETSTANDARD2_0 +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class AllowNullAttribute : Attribute + { + } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class MaybeNullAttribute : Attribute + { + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } +} +#endif diff --git a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs index 8eb8cebe579..c026e65f426 100644 --- a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs +++ b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs @@ -47,6 +47,7 @@ public BatchLogRecordExportProcessor( exporterTimeoutMilliseconds, maxExportBatchSize) { + this.CleanupAction = LogRecordPool.Return; } /// diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index eb0c3f4ebd6..fdb104d87cd 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -37,19 +37,13 @@ public sealed class LogRecord private List? bufferedScopes; private DateTime timestamp; - /// - /// Initializes a new instance of the class. - /// - /// - /// Note: The property is initialized to . - /// - public LogRecord() + internal LogRecord() { this.timestamp = DateTime.UtcNow; } // 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, @@ -61,8 +55,9 @@ internal LogRecord( Exception? exception, IReadOnlyList>? stateValues) { - this.ScopeProvider = scopeProvider; this.timestamp = timestamp; + + this.ScopeProvider = scopeProvider; this.CategoryName = categoryName; this.LogLevel = logLevel; this.EventId = eventId; @@ -144,10 +139,29 @@ public DateTime Timestamp internal IExternalScopeProvider? ScopeProvider { get; set; } - /// - /// Set the log activity context fields from the supplied . - /// - /// . + internal void Clear(bool clearAllData) + { + this.timestamp = DateTime.UtcNow; + + if (!clearAllData) + { + return; + } + + this.CategoryName = null; + this.LogLevel = LogLevel.Trace; + this.EventId = default; + this.FormattedMessage = null; + this.State = null; + this.StateValues = null; + this.Exception = null; + + this.TraceId = default; + this.SpanId = default; + this.TraceState = null; + this.TraceFlags = ActivityTraceFlags.None; + } + public void SetActivityContext(Activity? activity) { if (activity != null) diff --git a/src/OpenTelemetry/Logs/LogRecordPool.cs b/src/OpenTelemetry/Logs/LogRecordPool.cs new file mode 100644 index 00000000000..ad9132711f9 --- /dev/null +++ b/src/OpenTelemetry/Logs/LogRecordPool.cs @@ -0,0 +1,156 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using System; +using System.Threading; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Logs +{ + /// + /// Manages a pool of instances. + /// + public sealed class LogRecordPool + { + private const int DefaultMaxPoolSize = 1024; + + private static LogRecordPool current = new(DefaultMaxPoolSize); + + [ThreadStatic] + private static LogRecord? threadStaticLogRecord; + + private readonly int sharedPoolSize; + private readonly LogRecord?[] sharedPool; + private int sharedPoolCurrentIndex = -1; + + private LogRecordPool(int size) + { + this.sharedPoolSize = size; + this.sharedPool = new LogRecord?[size]; + } + + /// + /// Resize the pool. + /// + /// The maximum number of s to store in the pool. + public static void Resize(int size) + { + Guard.ThrowIfOutOfRange(size, min: 1); + + current = new LogRecordPool(size); + } + + /// + /// Rent a from the pool. + /// + /// . + public static LogRecord Rent() => current.RentCore(clearIfReused: true); + + /// + /// Return a to the pool. + /// + /// + /// Note: If the rented is being processed by a + /// or a then + /// should NOT be called, the instance will automatically be returned to + /// the pool after being exported. + /// + /// . + public static void Return(LogRecord logRecord) => current.ReturnCore(logRecord); + + internal static LogRecord Rent(bool clearIfReused) => current.RentCore(clearIfReused); + + private LogRecord RentCore(bool clearIfReused) + { + LogRecord? logRecord = threadStaticLogRecord; + + if (logRecord != null) + { + threadStaticLogRecord = null; + + logRecord.Clear(clearIfReused); + + return logRecord; + } + + SpinWait wait = default; + while (true) + { + int sharedPoolIndex = this.sharedPoolCurrentIndex; + if (sharedPoolIndex < 0) + { + break; + } + + if (Interlocked.CompareExchange(ref this.sharedPoolCurrentIndex, sharedPoolIndex - 1, sharedPoolIndex) == sharedPoolIndex) + { + while (true) + { + logRecord = this.sharedPool[sharedPoolIndex]; + if (logRecord != null) + { + break; + } + + // If logRecord was null it means we raced with the return call, retry. + wait.SpinOnce(); + } + + this.sharedPool[sharedPoolIndex] = null; + + logRecord.Clear(clearIfReused); + + return logRecord; + } + + wait.SpinOnce(); + } + + return new LogRecord(); + } + + private void ReturnCore(LogRecord logRecord) + { + if (threadStaticLogRecord == null) + { + threadStaticLogRecord = logRecord; + } + else + { + SpinWait wait = default; + while (true) + { + int sharedPoolIndex = this.sharedPoolCurrentIndex; + if (sharedPoolIndex >= this.sharedPoolSize) + { + return; + } + + if (Interlocked.CompareExchange(ref this.sharedPoolCurrentIndex, sharedPoolIndex + 1, sharedPoolIndex) == sharedPoolIndex) + { + this.sharedPool[sharedPoolIndex + 1] = logRecord; + break; + } + + wait.SpinOnce(); + } + } + } + } +} diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index 8a128682d93..e47e498d673 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using OpenTelemetry.Internal; @@ -52,16 +53,19 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except var processor = provider.Processor; if (processor != null) { - var record = new LogRecord( - provider.IncludeScopes ? this.ScopeProvider : null, - DateTime.UtcNow, - this.categoryName, - logLevel, - eventId, - provider.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null, - provider.ParseStateValues ? null : state, - exception, - provider.ParseStateValues ? this.ParseState(state) : null); + var record = LogRecordPool.Rent(clearIfReused: false); + + record.ScopeProvider = provider.IncludeScopes ? this.ScopeProvider : null; + + record.CategoryName = this.categoryName; + record.LogLevel = logLevel; + record.EventId = eventId; + record.FormattedMessage = provider.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null; + record.State = provider.ParseStateValues ? null : state; + record.Exception = exception; + record.StateValues = provider.ParseStateValues ? this.ParseState(state) : null; + + record.SetActivityContext(Activity.Current); processor.OnEnd(record); diff --git a/src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs index 3ac9498601d..bcbfecf281c 100644 --- a/src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs +++ b/src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs @@ -32,6 +32,7 @@ public class SimpleLogRecordExportProcessor : SimpleExportProcessor public SimpleLogRecordExportProcessor(BaseExporter exporter) : base(exporter) { + this.CleanupAction = LogRecordPool.Return; } } } diff --git a/src/OpenTelemetry/SimpleExportProcessor.cs b/src/OpenTelemetry/SimpleExportProcessor.cs index e3c129fa0a5..b12314cb423 100644 --- a/src/OpenTelemetry/SimpleExportProcessor.cs +++ b/src/OpenTelemetry/SimpleExportProcessor.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System; using OpenTelemetry.Internal; @@ -37,6 +39,11 @@ protected SimpleExportProcessor(BaseExporter exporter) { } + /// + /// Gets a cleanup action to be called after each item is exported. + /// + protected Action? CleanupAction { get; init; } + /// protected override void OnExport(T data) { @@ -44,7 +51,12 @@ protected override void OnExport(T data) { try { - this.exporter.Export(new Batch(data)); + using var batch = new Batch(data) + { + CleanupAction = this.CleanupAction, + }; + + this.exporter.Export(batch); } catch (Exception ex) { From 20479f57ebb29d9346ca74e3075d4061a8dc2602 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 26 May 2022 12:57:21 -0700 Subject: [PATCH 04/41] Nullable annotation updates. --- .../.publicApi/net462/PublicAPI.Shipped.txt | 18 +++++++++--------- .../netstandard2.0/PublicAPI.Shipped.txt | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt index ea3aecdecc0..ecb3440acf4 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt @@ -17,13 +17,13 @@ OpenTelemetry.BaseProcessor.Dispose() -> void OpenTelemetry.BaseProcessor.ForceFlush(int timeoutMilliseconds = -1) -> bool ~OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider OpenTelemetry.BaseProcessor.Shutdown(int timeoutMilliseconds = -1) -> bool -~OpenTelemetry.Batch +OpenTelemetry.Batch OpenTelemetry.Batch.Batch() -> void -~OpenTelemetry.Batch.Batch(T[] items, int count) -> void +OpenTelemetry.Batch.Batch(T![]! items, int count) -> void OpenTelemetry.Batch.Count.get -> long OpenTelemetry.Batch.Dispose() -> void OpenTelemetry.Batch.Enumerator -~OpenTelemetry.Batch.Enumerator.Current.get -> T +OpenTelemetry.Batch.Enumerator.Current.get -> T! OpenTelemetry.Batch.Enumerator.Dispose() -> void OpenTelemetry.Batch.Enumerator.Enumerator() -> void OpenTelemetry.Batch.Enumerator.MoveNext() -> bool @@ -31,8 +31,8 @@ OpenTelemetry.Batch.Enumerator.Reset() -> void ~OpenTelemetry.Batch.GetEnumerator() -> OpenTelemetry.Batch.Enumerator OpenTelemetry.BatchActivityExportProcessor ~OpenTelemetry.BatchActivityExportProcessor.BatchActivityExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void -~OpenTelemetry.BatchExportProcessor -~OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.BatchExportProcessor +OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void ~OpenTelemetry.BatchExportProcessorOptions OpenTelemetry.BatchExportProcessorOptions.BatchExportProcessorOptions() -> void OpenTelemetry.BatchExportProcessorOptions.ExporterTimeoutMilliseconds.get -> int @@ -216,8 +216,8 @@ OpenTelemetry.Resources.ResourceBuilderExtensions OpenTelemetry.Sdk OpenTelemetry.SimpleActivityExportProcessor ~OpenTelemetry.SimpleActivityExportProcessor.SimpleActivityExportProcessor(OpenTelemetry.BaseExporter exporter) -> void -~OpenTelemetry.SimpleExportProcessor -~OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter exporter) -> void +OpenTelemetry.SimpleExportProcessor +OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void OpenTelemetry.SimpleLogRecordExportProcessor OpenTelemetry.SimpleLogRecordExportProcessor.SimpleLogRecordExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void OpenTelemetry.SuppressInstrumentationScope @@ -270,7 +270,7 @@ override OpenTelemetry.BaseExportProcessor.OnForceFlush(int timeoutMillisecon override OpenTelemetry.BaseExportProcessor.OnShutdown(int timeoutMilliseconds) -> bool ~override OpenTelemetry.BatchActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void override OpenTelemetry.BatchExportProcessor.Dispose(bool disposing) -> void -~override OpenTelemetry.BatchExportProcessor.OnExport(T data) -> void +override OpenTelemetry.BatchExportProcessor.OnExport(T! data) -> void override OpenTelemetry.BatchExportProcessor.OnForceFlush(int timeoutMilliseconds) -> bool override OpenTelemetry.BatchExportProcessor.OnShutdown(int timeoutMilliseconds) -> bool override OpenTelemetry.BatchLogRecordExportProcessor.OnEnd(OpenTelemetry.Logs.LogRecord! data) -> void @@ -288,7 +288,7 @@ override OpenTelemetry.Metrics.BaseExportingMetricReader.OnShutdown(int timeoutM override OpenTelemetry.Metrics.PeriodicExportingMetricReader.Dispose(bool disposing) -> void override OpenTelemetry.Metrics.PeriodicExportingMetricReader.OnShutdown(int timeoutMilliseconds) -> bool ~override OpenTelemetry.SimpleActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.SimpleExportProcessor.OnExport(T data) -> void +override OpenTelemetry.SimpleExportProcessor.OnExport(T! data) -> void override OpenTelemetry.Trace.AlwaysOffSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult override OpenTelemetry.Trace.AlwaysOnSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult override OpenTelemetry.Trace.ParentBasedSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt index ea3aecdecc0..ecb3440acf4 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -17,13 +17,13 @@ OpenTelemetry.BaseProcessor.Dispose() -> void OpenTelemetry.BaseProcessor.ForceFlush(int timeoutMilliseconds = -1) -> bool ~OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider OpenTelemetry.BaseProcessor.Shutdown(int timeoutMilliseconds = -1) -> bool -~OpenTelemetry.Batch +OpenTelemetry.Batch OpenTelemetry.Batch.Batch() -> void -~OpenTelemetry.Batch.Batch(T[] items, int count) -> void +OpenTelemetry.Batch.Batch(T![]! items, int count) -> void OpenTelemetry.Batch.Count.get -> long OpenTelemetry.Batch.Dispose() -> void OpenTelemetry.Batch.Enumerator -~OpenTelemetry.Batch.Enumerator.Current.get -> T +OpenTelemetry.Batch.Enumerator.Current.get -> T! OpenTelemetry.Batch.Enumerator.Dispose() -> void OpenTelemetry.Batch.Enumerator.Enumerator() -> void OpenTelemetry.Batch.Enumerator.MoveNext() -> bool @@ -31,8 +31,8 @@ OpenTelemetry.Batch.Enumerator.Reset() -> void ~OpenTelemetry.Batch.GetEnumerator() -> OpenTelemetry.Batch.Enumerator OpenTelemetry.BatchActivityExportProcessor ~OpenTelemetry.BatchActivityExportProcessor.BatchActivityExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void -~OpenTelemetry.BatchExportProcessor -~OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.BatchExportProcessor +OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void ~OpenTelemetry.BatchExportProcessorOptions OpenTelemetry.BatchExportProcessorOptions.BatchExportProcessorOptions() -> void OpenTelemetry.BatchExportProcessorOptions.ExporterTimeoutMilliseconds.get -> int @@ -216,8 +216,8 @@ OpenTelemetry.Resources.ResourceBuilderExtensions OpenTelemetry.Sdk OpenTelemetry.SimpleActivityExportProcessor ~OpenTelemetry.SimpleActivityExportProcessor.SimpleActivityExportProcessor(OpenTelemetry.BaseExporter exporter) -> void -~OpenTelemetry.SimpleExportProcessor -~OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter exporter) -> void +OpenTelemetry.SimpleExportProcessor +OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void OpenTelemetry.SimpleLogRecordExportProcessor OpenTelemetry.SimpleLogRecordExportProcessor.SimpleLogRecordExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void OpenTelemetry.SuppressInstrumentationScope @@ -270,7 +270,7 @@ override OpenTelemetry.BaseExportProcessor.OnForceFlush(int timeoutMillisecon override OpenTelemetry.BaseExportProcessor.OnShutdown(int timeoutMilliseconds) -> bool ~override OpenTelemetry.BatchActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void override OpenTelemetry.BatchExportProcessor.Dispose(bool disposing) -> void -~override OpenTelemetry.BatchExportProcessor.OnExport(T data) -> void +override OpenTelemetry.BatchExportProcessor.OnExport(T! data) -> void override OpenTelemetry.BatchExportProcessor.OnForceFlush(int timeoutMilliseconds) -> bool override OpenTelemetry.BatchExportProcessor.OnShutdown(int timeoutMilliseconds) -> bool override OpenTelemetry.BatchLogRecordExportProcessor.OnEnd(OpenTelemetry.Logs.LogRecord! data) -> void @@ -288,7 +288,7 @@ override OpenTelemetry.Metrics.BaseExportingMetricReader.OnShutdown(int timeoutM override OpenTelemetry.Metrics.PeriodicExportingMetricReader.Dispose(bool disposing) -> void override OpenTelemetry.Metrics.PeriodicExportingMetricReader.OnShutdown(int timeoutMilliseconds) -> bool ~override OpenTelemetry.SimpleActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.SimpleExportProcessor.OnExport(T data) -> void +override OpenTelemetry.SimpleExportProcessor.OnExport(T! data) -> void override OpenTelemetry.Trace.AlwaysOffSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult override OpenTelemetry.Trace.AlwaysOnSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult override OpenTelemetry.Trace.ParentBasedSampler.ShouldSample(in OpenTelemetry.Trace.SamplingParameters samplingParameters) -> OpenTelemetry.Trace.SamplingResult From 53a6854396aca19ae36da77c4714ef1af94588bf Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 26 May 2022 13:21:09 -0700 Subject: [PATCH 05/41] Cleanup. --- src/OpenTelemetry/Logs/LogRecord.cs | 52 ++++++++++++++++------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index fdb104d87cd..f5349318878 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -139,29 +139,12 @@ public DateTime Timestamp internal IExternalScopeProvider? ScopeProvider { get; set; } - internal void Clear(bool clearAllData) - { - this.timestamp = DateTime.UtcNow; - - if (!clearAllData) - { - return; - } - - this.CategoryName = null; - this.LogLevel = LogLevel.Trace; - this.EventId = default; - this.FormattedMessage = null; - this.State = null; - this.StateValues = null; - this.Exception = null; - - this.TraceId = default; - this.SpanId = default; - this.TraceState = null; - this.TraceFlags = ActivityTraceFlags.None; - } - + /// + /// Sets the log , , , and from the supplied + /// . + /// + /// . public void SetActivityContext(Activity? activity) { if (activity != null) @@ -207,6 +190,29 @@ public void ForEachScope(Action callback, TState } } + internal void Clear(bool clearAllData) + { + this.timestamp = DateTime.UtcNow; + + if (!clearAllData) + { + return; + } + + this.CategoryName = null; + this.LogLevel = LogLevel.Trace; + this.EventId = default; + this.FormattedMessage = null; + this.State = null; + this.StateValues = null; + this.Exception = null; + + this.TraceId = default; + this.SpanId = default; + this.TraceState = null; + this.TraceFlags = ActivityTraceFlags.None; + } + /// /// Buffers the scopes attached to the log into a list so that they can /// be safely processed after the log message lifecycle has ended. From fd5a87ec2495133a0f8efcdad589115f317c220e Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 26 May 2022 13:33:28 -0700 Subject: [PATCH 06/41] Cleanup. --- src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt | 1 - .../.publicApi/netstandard2.0/PublicAPI.Unshipped.txt | 1 - test/Benchmarks/Logs/LogScopeBenchmarks.cs | 2 ++ 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 2526cdf6149..56702510abf 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -12,7 +12,6 @@ OpenTelemetry.Logs.LogRecord.EventId.set -> void OpenTelemetry.Logs.LogRecord.Exception.set -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void OpenTelemetry.Logs.LogRecord.LogLevel.set -> void -OpenTelemetry.Logs.LogRecord.LogRecord() -> void OpenTelemetry.Logs.LogRecord.SetActivityContext(System.Diagnostics.Activity? activity) -> void OpenTelemetry.Logs.LogRecord.SpanId.set -> void OpenTelemetry.Logs.LogRecord.State.set -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 2526cdf6149..56702510abf 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -12,7 +12,6 @@ OpenTelemetry.Logs.LogRecord.EventId.set -> void OpenTelemetry.Logs.LogRecord.Exception.set -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void OpenTelemetry.Logs.LogRecord.LogLevel.set -> void -OpenTelemetry.Logs.LogRecord.LogRecord() -> void OpenTelemetry.Logs.LogRecord.SetActivityContext(System.Diagnostics.Activity? activity) -> void OpenTelemetry.Logs.LogRecord.SpanId.set -> void OpenTelemetry.Logs.LogRecord.State.set -> void diff --git a/test/Benchmarks/Logs/LogScopeBenchmarks.cs b/test/Benchmarks/Logs/LogScopeBenchmarks.cs index 7c21f970e9a..9cc2347d6e3 100644 --- a/test/Benchmarks/Logs/LogScopeBenchmarks.cs +++ b/test/Benchmarks/Logs/LogScopeBenchmarks.cs @@ -56,6 +56,7 @@ public LogScopeBenchmarks() new KeyValuePair("item5", "value5"), })); +#pragma warning disable CS0618 // Type or member is obsolete this.logRecord = new LogRecord( this.scopeProvider, DateTime.UtcNow, @@ -66,6 +67,7 @@ public LogScopeBenchmarks() null, null, null); +#pragma warning restore CS0618 // Type or member is obsolete } [Benchmark] From 035823bddcb1745edbb45b8e9d2b21a6ecd991be Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 26 May 2022 15:55:38 -0700 Subject: [PATCH 07/41] Added reference counting into the log record pool. --- .../.publicApi/net462/PublicAPI.Unshipped.txt | 5 ++-- .../netstandard2.0/PublicAPI.Unshipped.txt | 5 ++-- src/OpenTelemetry/BatchExportProcessor.cs | 10 ++++++++ .../Logs/BatchLogRecordExportProcessor.cs | 1 + src/OpenTelemetry/Logs/LogEmitter.cs | 4 ++++ src/OpenTelemetry/Logs/LogRecord.cs | 3 +++ src/OpenTelemetry/Logs/LogRecordPool.cs | 24 ++++++++++++++++++- src/OpenTelemetry/Logs/OpenTelemetryLogger.cs | 4 ++++ .../Logs/SimpleLogRecordExportProcessor.cs | 1 - src/OpenTelemetry/SimpleExportProcessor.cs | 12 +--------- 10 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 56702510abf..9c9f73deeeb 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -4,6 +4,8 @@ OpenTelemetry.Batch.CleanupAction.get -> System.Action? OpenTelemetry.Batch.CleanupAction.init -> void OpenTelemetry.BatchExportProcessor.CleanupAction.get -> System.Action? OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void +OpenTelemetry.BatchExportProcessor.InitializeAction.get -> System.Action? +OpenTelemetry.BatchExportProcessor.InitializeAction.init -> void OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void @@ -22,8 +24,7 @@ OpenTelemetry.Logs.LogRecord.TraceId.set -> void OpenTelemetry.Logs.LogRecord.TraceState.set -> void OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! -OpenTelemetry.SimpleExportProcessor.CleanupAction.get -> System.Action? -OpenTelemetry.SimpleExportProcessor.CleanupAction.init -> void static OpenTelemetry.Logs.LogRecordPool.Rent() -> OpenTelemetry.Logs.LogRecord! static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void static OpenTelemetry.Logs.LogRecordPool.Return(OpenTelemetry.Logs.LogRecord! logRecord) -> void +static OpenTelemetry.Logs.LogRecordPool.TrackReference(OpenTelemetry.Logs.LogRecord! logRecord) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 56702510abf..9c9f73deeeb 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -4,6 +4,8 @@ OpenTelemetry.Batch.CleanupAction.get -> System.Action? OpenTelemetry.Batch.CleanupAction.init -> void OpenTelemetry.BatchExportProcessor.CleanupAction.get -> System.Action? OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void +OpenTelemetry.BatchExportProcessor.InitializeAction.get -> System.Action? +OpenTelemetry.BatchExportProcessor.InitializeAction.init -> void OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void @@ -22,8 +24,7 @@ OpenTelemetry.Logs.LogRecord.TraceId.set -> void OpenTelemetry.Logs.LogRecord.TraceState.set -> void OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! -OpenTelemetry.SimpleExportProcessor.CleanupAction.get -> System.Action? -OpenTelemetry.SimpleExportProcessor.CleanupAction.init -> void static OpenTelemetry.Logs.LogRecordPool.Rent() -> OpenTelemetry.Logs.LogRecord! static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void static OpenTelemetry.Logs.LogRecordPool.Return(OpenTelemetry.Logs.LogRecord! logRecord) -> void +static OpenTelemetry.Logs.LogRecordPool.TrackReference(OpenTelemetry.Logs.LogRecord! logRecord) -> void diff --git a/src/OpenTelemetry/BatchExportProcessor.cs b/src/OpenTelemetry/BatchExportProcessor.cs index 46694ef465b..d8e21abe125 100644 --- a/src/OpenTelemetry/BatchExportProcessor.cs +++ b/src/OpenTelemetry/BatchExportProcessor.cs @@ -95,6 +95,11 @@ protected BatchExportProcessor( /// internal long ProcessedCount => this.circularBuffer.RemovedCount; + /// + /// Gets an initialization action to be called before each item is exported. + /// + protected Action? InitializeAction { get; init; } + /// /// Gets a cleanup action to be called after each item is exported. /// @@ -103,6 +108,8 @@ protected BatchExportProcessor( /// protected override void OnExport(T data) { + this.InitializeAction?.Invoke(data); + if (this.circularBuffer.TryAdd(data, maxSpinCount: 50000)) { if (this.circularBuffer.Count >= this.maxExportBatchSize) @@ -119,6 +126,9 @@ protected override void OnExport(T data) return; // enqueue succeeded } + // If item couldn't be added to batch we still call cleanup. + this.CleanupAction?.Invoke(data); + // either the queue is full or exceeded the spin limit, drop the item on the floor Interlocked.Increment(ref this.droppedCount); } diff --git a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs index c026e65f426..d260a2ee31c 100644 --- a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs +++ b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs @@ -47,6 +47,7 @@ public BatchLogRecordExportProcessor( exporterTimeoutMilliseconds, maxExportBatchSize) { + this.InitializeAction = LogRecordPool.TrackReference; this.CleanupAction = LogRecordPool.Return; } diff --git a/src/OpenTelemetry/Logs/LogEmitter.cs b/src/OpenTelemetry/Logs/LogEmitter.cs index 1a301aadc07..9fe6c5efa4f 100644 --- a/src/OpenTelemetry/Logs/LogEmitter.cs +++ b/src/OpenTelemetry/Logs/LogEmitter.cs @@ -61,6 +61,10 @@ public void Log(LogRecord logRecord) processor.OnEnd(logRecord); logRecord.ScopeProvider = null; + + // Attempt to return the LogRecord to the pool. This will no-op + // if a batch exporter has added a reference. + LogRecordPool.Return(logRecord); } } } diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index f5349318878..d8145143d81 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -29,6 +29,8 @@ namespace OpenTelemetry.Logs /// public sealed class LogRecord { + internal int PoolReferences = int.MaxValue; + private static readonly Action> AddScopeToBufferedList = (object? scope, List state) => { state.Add(scope); @@ -193,6 +195,7 @@ public void ForEachScope(Action callback, TState internal void Clear(bool clearAllData) { this.timestamp = DateTime.UtcNow; + this.PoolReferences = 1; if (!clearAllData) { diff --git a/src/OpenTelemetry/Logs/LogRecordPool.cs b/src/OpenTelemetry/Logs/LogRecordPool.cs index ad9132711f9..a6a94918adf 100644 --- a/src/OpenTelemetry/Logs/LogRecordPool.cs +++ b/src/OpenTelemetry/Logs/LogRecordPool.cs @@ -74,6 +74,19 @@ public static void Resize(int size) /// . public static void Return(LogRecord logRecord) => current.ReturnCore(logRecord); + /// + /// Tracks a reference to the supplied . + /// + /// + /// Note: A will not be returned to the pool + /// until it no longer has any references. + /// + /// . + public static void TrackReference(LogRecord logRecord) + { + Interlocked.Increment(ref logRecord.PoolReferences); + } + internal static LogRecord Rent(bool clearIfReused) => current.RentCore(clearIfReused); private LogRecord RentCore(bool clearIfReused) @@ -122,11 +135,20 @@ private LogRecord RentCore(bool clearIfReused) wait.SpinOnce(); } - return new LogRecord(); + return new LogRecord() + { + PoolReferences = 1, + }; } private void ReturnCore(LogRecord logRecord) { + int poolReferences = Interlocked.Decrement(ref logRecord.PoolReferences); + if (poolReferences > 0) + { + return; + } + if (threadStaticLogRecord == null) { threadStaticLogRecord = logRecord; diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index e47e498d673..6c89c230ac1 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -70,6 +70,10 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except processor.OnEnd(record); record.ScopeProvider = null; + + // Attempt to return the LogRecord to the pool. This will no-op + // if a batch exporter has added a reference. + LogRecordPool.Return(record); } } diff --git a/src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs index bcbfecf281c..3ac9498601d 100644 --- a/src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs +++ b/src/OpenTelemetry/Logs/SimpleLogRecordExportProcessor.cs @@ -32,7 +32,6 @@ public class SimpleLogRecordExportProcessor : SimpleExportProcessor public SimpleLogRecordExportProcessor(BaseExporter exporter) : base(exporter) { - this.CleanupAction = LogRecordPool.Return; } } } diff --git a/src/OpenTelemetry/SimpleExportProcessor.cs b/src/OpenTelemetry/SimpleExportProcessor.cs index b12314cb423..b7ada68a779 100644 --- a/src/OpenTelemetry/SimpleExportProcessor.cs +++ b/src/OpenTelemetry/SimpleExportProcessor.cs @@ -39,11 +39,6 @@ protected SimpleExportProcessor(BaseExporter exporter) { } - /// - /// Gets a cleanup action to be called after each item is exported. - /// - protected Action? CleanupAction { get; init; } - /// protected override void OnExport(T data) { @@ -51,12 +46,7 @@ protected override void OnExport(T data) { try { - using var batch = new Batch(data) - { - CleanupAction = this.CleanupAction, - }; - - this.exporter.Export(batch); + this.exporter.Export(new Batch(data)); } catch (Exception ex) { From 80cbdbbd7e4fcdc512a6b8f5a7b90ba1c9dbdafe Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sat, 28 May 2022 11:05:29 -0700 Subject: [PATCH 08/41] Tweaks. --- .../Controllers/WeatherForecastController.cs | 20 +- .../.publicApi/net462/PublicAPI.Unshipped.txt | 56 ++- .../netstandard2.0/PublicAPI.Unshipped.txt | 56 ++- src/OpenTelemetry/Batch.cs | 43 +-- src/OpenTelemetry/BatchExportProcessor.cs | 5 +- src/OpenTelemetry/Logs/LogEmitter.cs | 13 +- src/OpenTelemetry/Logs/LogRecord.cs | 111 ++---- src/OpenTelemetry/Logs/LogRecordAttributes.cs | 342 ++++++++++++++++++ src/OpenTelemetry/Logs/LogRecordData.cs | 130 +++++++ src/OpenTelemetry/Logs/LogRecordPool.cs | 57 ++- src/OpenTelemetry/Logs/OpenTelemetryLogger.cs | 52 ++- 11 files changed, 673 insertions(+), 212 deletions(-) create mode 100644 src/OpenTelemetry/Logs/LogRecordAttributes.cs create mode 100644 src/OpenTelemetry/Logs/LogRecordData.cs diff --git a/examples/AspNetCore/Controllers/WeatherForecastController.cs b/examples/AspNetCore/Controllers/WeatherForecastController.cs index f15f67d6ebf..18c4a0129a8 100644 --- a/examples/AspNetCore/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore/Controllers/WeatherForecastController.cs @@ -67,15 +67,17 @@ public IEnumerable Get() forecast); // Log using LogEmitter API. - var logRecord = LogRecordPool.Rent(); - - logRecord.CategoryName = "WeatherForecasts"; - logRecord.LogLevel = LogLevel.Information; - logRecord.FormattedMessage = "WeatherForecasts generated."; - logRecord.StateValues = new List>() { new KeyValuePair("count", forecast.Length) }; - logRecord.SetActivityContext(Activity.Current); - - this.logEmitter.Log(logRecord); + this.logEmitter.Log( + new LogRecordData(Activity.Current) + { + CategoryName = "WeatherForecasts", + LogLevel = LogLevel.Information, + Message = "WeatherForecasts generated.", + }, + new LogRecordAttributes() + { + ["count"] = forecast.Length, + }); return forecast; } diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 9c9f73deeeb..eb56268e3cb 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -1,30 +1,52 @@ #nullable enable -OpenTelemetry.Batch.Batch(T! item) -> void -OpenTelemetry.Batch.CleanupAction.get -> System.Action? -OpenTelemetry.Batch.CleanupAction.init -> void OpenTelemetry.BatchExportProcessor.CleanupAction.get -> System.Action? OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void OpenTelemetry.BatchExportProcessor.InitializeAction.get -> System.Action? OpenTelemetry.BatchExportProcessor.InitializeAction.init -> void OpenTelemetry.Logs.LogEmitter -OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void +OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributes attributes = default(OpenTelemetry.Logs.LogRecordAttributes)) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void -OpenTelemetry.Logs.LogRecord.CategoryName.set -> void -OpenTelemetry.Logs.LogRecord.EventId.set -> void -OpenTelemetry.Logs.LogRecord.Exception.set -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void -OpenTelemetry.Logs.LogRecord.LogLevel.set -> void -OpenTelemetry.Logs.LogRecord.SetActivityContext(System.Diagnostics.Activity? activity) -> void -OpenTelemetry.Logs.LogRecord.SpanId.set -> void OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void -OpenTelemetry.Logs.LogRecord.Timestamp.set -> void -OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void -OpenTelemetry.Logs.LogRecord.TraceId.set -> void -OpenTelemetry.Logs.LogRecord.TraceState.set -> void +OpenTelemetry.Logs.LogRecordAttributes +OpenTelemetry.Logs.LogRecordAttributes.Add(string! key, object? value) -> void +OpenTelemetry.Logs.LogRecordAttributes.Add(System.Collections.Generic.KeyValuePair attribute) -> void +OpenTelemetry.Logs.LogRecordAttributes.Count.get -> int +OpenTelemetry.Logs.LogRecordAttributes.Enumerator +OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Dispose() -> void +OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Enumerator() -> void +OpenTelemetry.Logs.LogRecordAttributes.Enumerator.MoveNext() -> bool +OpenTelemetry.Logs.LogRecordAttributes.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributes.Enumerator +OpenTelemetry.Logs.LogRecordAttributes.LogRecordAttributes() -> void +OpenTelemetry.Logs.LogRecordAttributes.this[int index].get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordAttributes.this[int index].set -> void +OpenTelemetry.Logs.LogRecordAttributes.this[string! key].set -> void +OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void +OpenTelemetry.Logs.LogRecordData.EventId.get -> Microsoft.Extensions.Logging.EventId +OpenTelemetry.Logs.LogRecordData.EventId.set -> void +OpenTelemetry.Logs.LogRecordData.Exception.get -> System.Exception? +OpenTelemetry.Logs.LogRecordData.Exception.set -> void +OpenTelemetry.Logs.LogRecordData.LogLevel.get -> Microsoft.Extensions.Logging.LogLevel +OpenTelemetry.Logs.LogRecordData.LogLevel.set -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData() -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData(System.Diagnostics.Activity? activity = null) -> void +OpenTelemetry.Logs.LogRecordData.Message.get -> string? +OpenTelemetry.Logs.LogRecordData.Message.set -> void +OpenTelemetry.Logs.LogRecordData.SpanId.get -> System.Diagnostics.ActivitySpanId +OpenTelemetry.Logs.LogRecordData.SpanId.set -> void +OpenTelemetry.Logs.LogRecordData.Timestamp.get -> System.DateTime +OpenTelemetry.Logs.LogRecordData.Timestamp.set -> void +OpenTelemetry.Logs.LogRecordData.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags +OpenTelemetry.Logs.LogRecordData.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecordData.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Logs.LogRecordData.TraceId.set -> void +OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? +OpenTelemetry.Logs.LogRecordData.TraceState.set -> void OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! -static OpenTelemetry.Logs.LogRecordPool.Rent() -> OpenTelemetry.Logs.LogRecord! +static OpenTelemetry.Logs.LogRecordAttributes.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributes static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void -static OpenTelemetry.Logs.LogRecordPool.Return(OpenTelemetry.Logs.LogRecord! logRecord) -> void -static OpenTelemetry.Logs.LogRecordPool.TrackReference(OpenTelemetry.Logs.LogRecord! logRecord) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 9c9f73deeeb..eb56268e3cb 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,30 +1,52 @@ #nullable enable -OpenTelemetry.Batch.Batch(T! item) -> void -OpenTelemetry.Batch.CleanupAction.get -> System.Action? -OpenTelemetry.Batch.CleanupAction.init -> void OpenTelemetry.BatchExportProcessor.CleanupAction.get -> System.Action? OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void OpenTelemetry.BatchExportProcessor.InitializeAction.get -> System.Action? OpenTelemetry.BatchExportProcessor.InitializeAction.init -> void OpenTelemetry.Logs.LogEmitter -OpenTelemetry.Logs.LogEmitter.Log(OpenTelemetry.Logs.LogRecord! logRecord) -> void +OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributes attributes = default(OpenTelemetry.Logs.LogRecordAttributes)) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void -OpenTelemetry.Logs.LogRecord.CategoryName.set -> void -OpenTelemetry.Logs.LogRecord.EventId.set -> void -OpenTelemetry.Logs.LogRecord.Exception.set -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void -OpenTelemetry.Logs.LogRecord.LogLevel.set -> void -OpenTelemetry.Logs.LogRecord.SetActivityContext(System.Diagnostics.Activity? activity) -> void -OpenTelemetry.Logs.LogRecord.SpanId.set -> void OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void -OpenTelemetry.Logs.LogRecord.Timestamp.set -> void -OpenTelemetry.Logs.LogRecord.TraceFlags.set -> void -OpenTelemetry.Logs.LogRecord.TraceId.set -> void -OpenTelemetry.Logs.LogRecord.TraceState.set -> void +OpenTelemetry.Logs.LogRecordAttributes +OpenTelemetry.Logs.LogRecordAttributes.Add(string! key, object? value) -> void +OpenTelemetry.Logs.LogRecordAttributes.Add(System.Collections.Generic.KeyValuePair attribute) -> void +OpenTelemetry.Logs.LogRecordAttributes.Count.get -> int +OpenTelemetry.Logs.LogRecordAttributes.Enumerator +OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Dispose() -> void +OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Enumerator() -> void +OpenTelemetry.Logs.LogRecordAttributes.Enumerator.MoveNext() -> bool +OpenTelemetry.Logs.LogRecordAttributes.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributes.Enumerator +OpenTelemetry.Logs.LogRecordAttributes.LogRecordAttributes() -> void +OpenTelemetry.Logs.LogRecordAttributes.this[int index].get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordAttributes.this[int index].set -> void +OpenTelemetry.Logs.LogRecordAttributes.this[string! key].set -> void +OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void +OpenTelemetry.Logs.LogRecordData.EventId.get -> Microsoft.Extensions.Logging.EventId +OpenTelemetry.Logs.LogRecordData.EventId.set -> void +OpenTelemetry.Logs.LogRecordData.Exception.get -> System.Exception? +OpenTelemetry.Logs.LogRecordData.Exception.set -> void +OpenTelemetry.Logs.LogRecordData.LogLevel.get -> Microsoft.Extensions.Logging.LogLevel +OpenTelemetry.Logs.LogRecordData.LogLevel.set -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData() -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData(System.Diagnostics.Activity? activity = null) -> void +OpenTelemetry.Logs.LogRecordData.Message.get -> string? +OpenTelemetry.Logs.LogRecordData.Message.set -> void +OpenTelemetry.Logs.LogRecordData.SpanId.get -> System.Diagnostics.ActivitySpanId +OpenTelemetry.Logs.LogRecordData.SpanId.set -> void +OpenTelemetry.Logs.LogRecordData.Timestamp.get -> System.DateTime +OpenTelemetry.Logs.LogRecordData.Timestamp.set -> void +OpenTelemetry.Logs.LogRecordData.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags +OpenTelemetry.Logs.LogRecordData.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecordData.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Logs.LogRecordData.TraceId.set -> void +OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? +OpenTelemetry.Logs.LogRecordData.TraceState.set -> void OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! -static OpenTelemetry.Logs.LogRecordPool.Rent() -> OpenTelemetry.Logs.LogRecord! +static OpenTelemetry.Logs.LogRecordAttributes.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributes static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void -static OpenTelemetry.Logs.LogRecordPool.Return(OpenTelemetry.Logs.LogRecord! logRecord) -> void -static OpenTelemetry.Logs.LogRecordPool.TrackReference(OpenTelemetry.Logs.LogRecord! logRecord) -> void diff --git a/src/OpenTelemetry/Batch.cs b/src/OpenTelemetry/Batch.cs index be27a1b1591..30b4c1f4dff 100644 --- a/src/OpenTelemetry/Batch.cs +++ b/src/OpenTelemetry/Batch.cs @@ -34,6 +34,7 @@ namespace OpenTelemetry { private readonly T? item; private readonly CircularBuffer? circularBuffer; + private readonly Action? cleanupAction; private readonly T[]? items; private readonly long targetCount; @@ -49,25 +50,23 @@ public Batch(T[] items, int count) this.item = null; this.circularBuffer = null; + this.cleanupAction = null; this.items = items; this.Count = this.targetCount = count; } - /// - /// Initializes a new instance of the struct. - /// - /// The item to store in the batch. - public Batch(T item) + internal Batch(T item) { Guard.ThrowIfNull(item); this.item = item; this.circularBuffer = null; + this.cleanupAction = null; this.items = null; this.Count = this.targetCount = 1; } - internal Batch(CircularBuffer circularBuffer, int maxSize) + internal Batch(CircularBuffer circularBuffer, int maxSize, Action? cleanupAction) { Debug.Assert(maxSize > 0, $"{nameof(maxSize)} should be a positive number."); Debug.Assert(circularBuffer != null, $"{nameof(circularBuffer)} was null."); @@ -75,6 +74,7 @@ internal Batch(CircularBuffer circularBuffer, int maxSize) this.item = null; this.items = null; this.circularBuffer = circularBuffer; + this.cleanupAction = cleanupAction; this.Count = Math.Min(maxSize, circularBuffer!.Count); this.targetCount = circularBuffer.RemovedCount + this.Count; } @@ -86,11 +86,6 @@ internal Batch(CircularBuffer circularBuffer, int maxSize) /// public long Count { get; } - /// - /// Gets a cleanup action to be called after each item in the batch is processed. - /// - public Action? CleanupAction { get; init; } = null; - /// public void Dispose() { @@ -100,33 +95,11 @@ public void Dispose() while (this.circularBuffer.RemovedCount < this.targetCount) { T item = this.circularBuffer.Read(); - this.CleanupAction?.Invoke(item); + this.cleanupAction?.Invoke(item); } return; } - - var cleanupAction = this.CleanupAction; - if (cleanupAction == null) - { - return; - } - - if (this.items != null) - { - for (int i = 0; i < this.Count; i++) - { - var item = this.items[i]; - if (item != null) - { - cleanupAction(item); - } - } - } - else - { - cleanupAction(this.item!); - } } /// @@ -136,7 +109,7 @@ public void Dispose() public Enumerator GetEnumerator() { return this.circularBuffer != null - ? new Enumerator(this.circularBuffer, this.targetCount, this.CleanupAction) + ? new Enumerator(this.circularBuffer, this.targetCount, this.cleanupAction) : this.item != null ? new Enumerator(this.item) /* In the event someone uses default/new Batch() to create Batch we fallback to empty items mode. */ diff --git a/src/OpenTelemetry/BatchExportProcessor.cs b/src/OpenTelemetry/BatchExportProcessor.cs index d8e21abe125..93ea11c3cf8 100644 --- a/src/OpenTelemetry/BatchExportProcessor.cs +++ b/src/OpenTelemetry/BatchExportProcessor.cs @@ -287,10 +287,7 @@ private void ExporterProc() if (this.circularBuffer.Count > 0) { - using (var batch = new Batch(this.circularBuffer, this.maxExportBatchSize) - { - CleanupAction = cleanupAction, - }) + using (var batch = new Batch(this.circularBuffer, this.maxExportBatchSize, cleanupAction)) { this.exporter.Export(batch); } diff --git a/src/OpenTelemetry/Logs/LogEmitter.cs b/src/OpenTelemetry/Logs/LogEmitter.cs index 9fe6c5efa4f..ef28799dc70 100644 --- a/src/OpenTelemetry/Logs/LogEmitter.cs +++ b/src/OpenTelemetry/Logs/LogEmitter.cs @@ -44,15 +44,20 @@ public LogEmitter(OpenTelemetryLoggerProvider loggerProvider) /// /// Emit a . /// - /// . - public void Log(LogRecord logRecord) + /// . + /// . + public void Log(in LogRecordData data, in LogRecordAttributes attributes = default) { - Guard.ThrowIfNull(logRecord); - var provider = this.loggerProvider; var processor = provider.Processor; if (processor != null) { + var logRecord = LogRecordPool.Rent(); + + logRecord.Data = data; + + attributes.ApplyToLogRecord(logRecord); + if (provider.IncludeScopes) { logRecord.ScopeProvider = provider.ScopeProvider; diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index d8145143d81..bcd4225afa8 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -30,6 +30,8 @@ namespace OpenTelemetry.Logs public sealed class LogRecord { internal int PoolReferences = int.MaxValue; + internal LogRecordData Data; + internal List>? AttributeStorage; private static readonly Action> AddScopeToBufferedList = (object? scope, List state) => { @@ -37,11 +39,9 @@ public sealed class LogRecord }; private List? bufferedScopes; - private DateTime timestamp; internal LogRecord() { - this.timestamp = DateTime.UtcNow; } // Note: Some users are calling this with reflection. Try not to change the signature to be nice. @@ -57,68 +57,70 @@ internal LogRecord( Exception? exception, IReadOnlyList>? stateValues) { - this.timestamp = timestamp; + this.Data = new(Activity.Current) + { + TimestampBacking = timestamp, + + CategoryName = categoryName, + LogLevel = logLevel, + EventId = eventId, + Message = formattedMessage, + Exception = exception, + }; this.ScopeProvider = scopeProvider; - this.CategoryName = categoryName; - this.LogLevel = logLevel; - this.EventId = eventId; - this.FormattedMessage = formattedMessage; - this.State = state; this.StateValues = stateValues; - this.Exception = exception; - - this.SetActivityContext(Activity.Current); + this.State = state; } /// - /// Gets or sets the log timestamp. + /// Gets the log timestamp. /// - public DateTime Timestamp - { - get => this.timestamp; - set { this.timestamp = value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : value; } - } + public DateTime Timestamp => this.Data.Timestamp; /// - /// Gets or sets the log . + /// Gets the log . /// - public ActivityTraceId TraceId { get; set; } + public ActivityTraceId TraceId => this.Data.TraceId; /// - /// Gets or sets the log . + /// Gets the log . /// - public ActivitySpanId SpanId { get; set; } + public ActivitySpanId SpanId => this.Data.SpanId; /// - /// Gets or sets the log . + /// Gets the log . /// - public ActivityTraceFlags TraceFlags { get; set; } + public ActivityTraceFlags TraceFlags => this.Data.TraceFlags; /// - /// Gets or sets the log trace state. + /// Gets the log trace state. /// - public string? TraceState { get; set; } + public string? TraceState => this.Data.TraceState; /// - /// Gets or sets the log category name. + /// Gets the log category name. /// - public string? CategoryName { get; set; } + public string? CategoryName => this.Data.CategoryName; /// - /// Gets or sets the log . + /// Gets the log . /// - public LogLevel LogLevel { get; set; } + public LogLevel LogLevel => this.Data.LogLevel; /// - /// Gets or sets the log . + /// Gets the log . /// - public EventId EventId { get; set; } + public EventId EventId => this.Data.EventId; /// /// Gets or sets the log formatted message. /// - public string? FormattedMessage { get; set; } + public string? FormattedMessage + { + get => this.Data.Message; + set => this.Data.Message = value; + } /// /// Gets or sets the raw state attached to the log. Set to >? StateValues { get; set; } /// - /// Gets or sets the log . + /// Gets the log . /// - public Exception? Exception { get; set; } + public Exception? Exception => this.Data.Exception; internal IExternalScopeProvider? ScopeProvider { get; set; } - /// - /// Sets the log , , , and from the supplied - /// . - /// - /// . - public void SetActivityContext(Activity? activity) - { - if (activity != null) - { - this.TraceId = activity.TraceId; - this.SpanId = activity.SpanId; - this.TraceState = activity.TraceStateString; - this.TraceFlags = activity.ActivityTraceFlags; - } - } - /// /// Executes callback for each currently active scope objects in order /// of creation. All callbacks are guaranteed to be called inline from @@ -192,30 +177,6 @@ public void ForEachScope(Action callback, TState } } - internal void Clear(bool clearAllData) - { - this.timestamp = DateTime.UtcNow; - this.PoolReferences = 1; - - if (!clearAllData) - { - return; - } - - this.CategoryName = null; - this.LogLevel = LogLevel.Trace; - this.EventId = default; - this.FormattedMessage = null; - this.State = null; - this.StateValues = null; - this.Exception = null; - - this.TraceId = default; - this.SpanId = default; - this.TraceState = null; - this.TraceFlags = ActivityTraceFlags.None; - } - /// /// Buffers the scopes attached to the log into a list so that they can /// be safely processed after the log message lifecycle has ended. diff --git a/src/OpenTelemetry/Logs/LogRecordAttributes.cs b/src/OpenTelemetry/Logs/LogRecordAttributes.cs new file mode 100644 index 00000000000..08a8f37c138 --- /dev/null +++ b/src/OpenTelemetry/Logs/LogRecordAttributes.cs @@ -0,0 +1,342 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Logs +{ + /// + /// Stores attributes to be added to a log message. + /// + public struct LogRecordAttributes : IReadOnlyList> + { + private const int OverflowAdditionalCapacity = 8; + private KeyValuePair attribute1; + private KeyValuePair attribute2; + private KeyValuePair attribute3; + private KeyValuePair attribute4; + private KeyValuePair attribute5; + private KeyValuePair attribute6; + private KeyValuePair attribute7; + private KeyValuePair attribute8; + private List>? overflowAttributes; + private int count; + + /// + public readonly int Count => this.count; + + /// + public KeyValuePair this[int index] + { + readonly get + { + if (this.overflowAttributes is not null) + { + Debug.Assert(index < this.overflowAttributes.Count, "Invalid index accessed."); + return this.overflowAttributes[index]; + } + + if ((uint)index >= (uint)this.count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return index switch + { + 0 => this.attribute1, + 1 => this.attribute2, + 2 => this.attribute3, + 3 => this.attribute4, + 4 => this.attribute5, + 5 => this.attribute6, + 6 => this.attribute7, + 7 => this.attribute8, + _ => default, // we shouldn't come here anyway. + }; + } + + set + { + if (this.overflowAttributes is not null) + { + Debug.Assert(index < this.overflowAttributes.Count, "Invalid index accessed."); + this.overflowAttributes[index] = value; + return; + } + + if ((uint)index >= (uint)this.count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + switch (index) + { + case 0: this.attribute1 = value; break; + case 1: this.attribute2 = value; break; + case 2: this.attribute3 = value; break; + case 3: this.attribute4 = value; break; + case 4: this.attribute5 = value; break; + case 5: this.attribute6 = value; break; + case 6: this.attribute7 = value; break; + case 7: this.attribute8 = value; break; + default: + Debug.Assert(false, "Unreachable code executed."); + break; + } + } + } + + /// + /// Add an attribute. + /// + /// Attribute name. + /// Attribute value. + [EditorBrowsable(EditorBrowsableState.Never)] + public object? this[string key] + { + // Note: This only exists to enable collection initializer syntax + // like { ["key"] = value }. + set => this.Add(new KeyValuePair(key, value)); + } + + /// + /// Create a collection from an enumerable. + /// + /// Source attributes. + /// . + public static LogRecordAttributes CreateFromEnumerable(IEnumerable> attributes) + { + Guard.ThrowIfNull(attributes); + + LogRecordAttributes logRecordAttributes = default; + logRecordAttributes.overflowAttributes = new(attributes); + logRecordAttributes.count = logRecordAttributes.overflowAttributes.Count; + return logRecordAttributes; + } + + /// + /// Add an attribute. + /// + /// Attribute name. + /// Attribute value. + public void Add(string key, object? value) + => this.Add(new KeyValuePair(key, value)); + + /// + /// Add an attribute. + /// + /// Attribute. + public void Add(KeyValuePair attribute) + { + if (this.overflowAttributes is not null) + { + this.overflowAttributes.Add(attribute); + this.count++; + return; + } + + Debug.Assert(this.count <= 8, "Item added beyond struct capacity."); + + switch (this.count) + { + case 0: this.attribute1 = attribute; break; + case 1: this.attribute2 = attribute; break; + case 2: this.attribute3 = attribute; break; + case 3: this.attribute4 = attribute; break; + case 4: this.attribute5 = attribute; break; + case 5: this.attribute6 = attribute; break; + case 6: this.attribute7 = attribute; break; + case 7: this.attribute8 = attribute; break; + case 8: + Debug.Assert(this.overflowAttributes is null, "Overflow attributes already created."); + this.MoveAttributesToTheOverflowList(); + Debug.Assert(this.overflowAttributes is not null, "Overflow attributes creation failure."); + this.overflowAttributes![8] = attribute; + break; + default: + // We shouldn't come here. + Debug.Assert(this.overflowAttributes is null, "Unreachable code executed."); + return; + } + + this.count++; + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// . + public readonly Enumerator GetEnumerator() + => new(in this); + + /// + readonly IEnumerator> IEnumerable>.GetEnumerator() => this.GetEnumerator(); + + /// + readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + internal readonly void ApplyToLogRecord(LogRecord logRecord) + { + int attributeCount = this.count; + if (attributeCount <= 0) + { + return; + } + + var overflowAttributes = this.overflowAttributes; + if (overflowAttributes != null) + { + // An allocation has already occurred, just use the buffer. + logRecord.StateValues = overflowAttributes; + return; + } + + int count = this.count; + + Debug.Assert(count <= 8, "Invalid size detected."); + + var attributeStorage = logRecord.AttributeStorage ??= new List>(OverflowAdditionalCapacity); + + try + { + /* Note: If we ever add NET6+ target, this can be done more efficiently: + attributeStorage.EnsureCapacity(count); + + switch (count) + { + case 0: break; + case 8: attributeStorage[7] = this.attribute8; goto case 7; + case 7: attributeStorage[6] = this.attribute7; goto case 6; + case 6: attributeStorage[5] = this.attribute6; goto case 5; + case 5: attributeStorage[4] = this.attribute5; goto case 4; + case 4: attributeStorage[3] = this.attribute4; goto case 3; + case 3: attributeStorage[2] = this.attribute3; goto case 2; + case 2: attributeStorage[1] = this.attribute2; goto case 1; + case 1: attributeStorage[0] = this.attribute1; break; + } + */ + + attributeStorage.Add(this.attribute1); + if (count == 1) + { + return; + } + + attributeStorage.Add(this.attribute2); + if (count == 2) + { + return; + } + + attributeStorage.Add(this.attribute3); + if (count == 3) + { + return; + } + + attributeStorage.Add(this.attribute4); + if (count == 4) + { + return; + } + + attributeStorage.Add(this.attribute5); + if (count == 5) + { + return; + } + + attributeStorage.Add(this.attribute6); + if (count == 6) + { + return; + } + + attributeStorage.Add(this.attribute7); + if (count == 7) + { + return; + } + + attributeStorage.Add(this.attribute8); + } + finally + { + logRecord.StateValues = attributeStorage; + } + } + + private void MoveAttributesToTheOverflowList() + { + this.overflowAttributes = new(16) + { + { this.attribute1 }, + { this.attribute2 }, + { this.attribute3 }, + { this.attribute4 }, + { this.attribute5 }, + { this.attribute6 }, + { this.attribute7 }, + { this.attribute8 }, + }; + } + + /// + /// Enumerates the elements of a . + /// + public struct Enumerator : IEnumerator>, IEnumerator + { + private LogRecordAttributes attributes; + private int index; + + internal Enumerator(in LogRecordAttributes attributes) + { + this.index = -1; + this.attributes = attributes; + } + + /// + public readonly KeyValuePair Current + => this.attributes[this.index]; + + /// + readonly object IEnumerator.Current => this.Current; + + /// + public bool MoveNext() + { + this.index++; + return this.index < this.attributes.Count; + } + + /// + public readonly void Dispose() + { + } + + /// + readonly void IEnumerator.Reset() + => throw new NotSupportedException(); + } + } +} diff --git a/src/OpenTelemetry/Logs/LogRecordData.cs b/src/OpenTelemetry/Logs/LogRecordData.cs new file mode 100644 index 00000000000..3a7da769cb2 --- /dev/null +++ b/src/OpenTelemetry/Logs/LogRecordData.cs @@ -0,0 +1,130 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using System; +using System.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace OpenTelemetry.Logs +{ + /// + /// Stores details about a log message. + /// + public struct LogRecordData + { + internal DateTime TimestampBacking = DateTime.UtcNow; + + /// + /// Initializes a new instance of the struct. + /// + /// + /// Note: The property is initialized to automatically. + /// + /// Optional used to populate context fields. + public LogRecordData(Activity? activity = null) + { + if (activity != null) + { + this.TraceId = activity.TraceId; + this.SpanId = activity.SpanId; + this.TraceState = activity.TraceStateString; + this.TraceFlags = activity.ActivityTraceFlags; + } + else + { + this.TraceId = default; + this.SpanId = default; + this.TraceState = null; + this.TraceFlags = ActivityTraceFlags.None; + } + } + + /// + /// Gets or sets the log timestamp. + /// + public DateTime Timestamp + { + readonly get => this.TimestampBacking; + set { this.TimestampBacking = value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : value; } + } + + /// + /// Gets or sets the log . + /// + public ActivityTraceId TraceId { get; set; } + + /// + /// Gets or sets the log . + /// + public ActivitySpanId SpanId { get; set; } + + /// + /// Gets or sets the log . + /// + public ActivityTraceFlags TraceFlags { get; set; } + + /// + /// Gets or sets the log trace state. + /// + public string? TraceState { get; set; } + + /// + /// Gets or sets the log category name. + /// + public string? CategoryName { get; set; } = null; + + /// + /// Gets or sets the log . + /// + public LogLevel LogLevel { get; set; } = LogLevel.Trace; + + /// + /// Gets or sets the log . + /// + public EventId EventId { get; set; } = default; + + /// + /// Gets or sets the log message. + /// + public string? Message { get; set; } = null; + + /// + /// Gets or sets the log . + /// + public Exception? Exception { get; set; } = null; + + internal static void SetActivityContext(ref LogRecordData data, Activity? activity = null) + { + if (activity != null) + { + data.TraceId = activity.TraceId; + data.SpanId = activity.SpanId; + data.TraceState = activity.TraceStateString; + data.TraceFlags = activity.ActivityTraceFlags; + } + else + { + data.TraceId = default; + data.SpanId = default; + data.TraceState = null; + data.TraceFlags = ActivityTraceFlags.None; + } + } + } +} diff --git a/src/OpenTelemetry/Logs/LogRecordPool.cs b/src/OpenTelemetry/Logs/LogRecordPool.cs index a6a94918adf..69f46f0375c 100644 --- a/src/OpenTelemetry/Logs/LogRecordPool.cs +++ b/src/OpenTelemetry/Logs/LogRecordPool.cs @@ -55,41 +55,14 @@ public static void Resize(int size) current = new LogRecordPool(size); } - /// - /// Rent a from the pool. - /// - /// . - public static LogRecord Rent() => current.RentCore(clearIfReused: true); - - /// - /// Return a to the pool. - /// - /// - /// Note: If the rented is being processed by a - /// or a then - /// should NOT be called, the instance will automatically be returned to - /// the pool after being exported. - /// - /// . - public static void Return(LogRecord logRecord) => current.ReturnCore(logRecord); + internal static LogRecord Rent() => current.RentCore(); - /// - /// Tracks a reference to the supplied . - /// - /// - /// Note: A will not be returned to the pool - /// until it no longer has any references. - /// - /// . - public static void TrackReference(LogRecord logRecord) - { - Interlocked.Increment(ref logRecord.PoolReferences); - } + internal static void Return(LogRecord logRecord) => current.ReturnCore(logRecord); - internal static LogRecord Rent(bool clearIfReused) => current.RentCore(clearIfReused); + internal static void TrackReference(LogRecord logRecord) + => Interlocked.Increment(ref logRecord.PoolReferences); - private LogRecord RentCore(bool clearIfReused) + private LogRecord RentCore() { LogRecord? logRecord = threadStaticLogRecord; @@ -97,7 +70,7 @@ private LogRecord RentCore(bool clearIfReused) { threadStaticLogRecord = null; - logRecord.Clear(clearIfReused); + logRecord.PoolReferences = 1; return logRecord; } @@ -127,7 +100,7 @@ private LogRecord RentCore(bool clearIfReused) this.sharedPool[sharedPoolIndex] = null; - logRecord.Clear(clearIfReused); + logRecord.PoolReferences = 1; return logRecord; } @@ -149,6 +122,22 @@ private void ReturnCore(LogRecord logRecord) return; } + var attributeStorage = logRecord.AttributeStorage; + if (attributeStorage != null) + { + if (attributeStorage.Count > 64) + { + // Don't allow the pool to grow unconstained. + logRecord.AttributeStorage = null; + } + else + { + /* List.Clear sets the size to 0 but it maintains the + underlying array. */ + attributeStorage.Clear(); + } + } + if (threadStaticLogRecord == null) { threadStaticLogRecord = logRecord; diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index 6c89c230ac1..67170084ea2 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -53,19 +53,22 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except var processor = provider.Processor; if (processor != null) { - var record = LogRecordPool.Rent(clearIfReused: false); + var record = LogRecordPool.Rent(); record.ScopeProvider = provider.IncludeScopes ? this.ScopeProvider : null; - - record.CategoryName = this.categoryName; - record.LogLevel = logLevel; - record.EventId = eventId; - record.FormattedMessage = provider.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null; record.State = provider.ParseStateValues ? null : state; - record.Exception = exception; - record.StateValues = provider.ParseStateValues ? this.ParseState(state) : null; + record.StateValues = provider.ParseStateValues ? this.ParseState(record, state) : null; + + ref LogRecordData data = ref record.Data; - record.SetActivityContext(Activity.Current); + data.TimestampBacking = DateTime.UtcNow; + data.CategoryName = this.categoryName; + data.LogLevel = logLevel; + data.EventId = eventId; + data.Message = provider.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null; + data.Exception = exception; + + LogRecordData.SetActivityContext(ref data, Activity.Current); processor.OnEnd(record); @@ -85,22 +88,37 @@ public bool IsEnabled(LogLevel logLevel) public IDisposable BeginScope(TState state) => this.ScopeProvider?.Push(state) ?? NullScope.Instance; - private IReadOnlyList> ParseState(TState state) + private IReadOnlyList> ParseState(LogRecord logRecord, TState state) { - if (state is IReadOnlyList> stateList) + if (state is LogRecordAttributes logRecordAttributes) + { + logRecordAttributes.ApplyToLogRecord(logRecord); + return logRecord.AttributeStorage!; + } + else if (state is IReadOnlyList> stateList) { - return stateList; + var attributeStorage = logRecord.AttributeStorage ??= new List>(stateList.Count); + attributeStorage.AddRange(stateList); + return attributeStorage; } else if (state is IEnumerable> stateValues) { - return new List>(stateValues); + var attributeStorage = logRecord.AttributeStorage; + if (attributeStorage == null) + { + return logRecord.AttributeStorage = new List>(stateValues); + } + else + { + attributeStorage.AddRange(stateValues); + return attributeStorage; + } } else { - return new List> - { - new KeyValuePair(string.Empty, state), - }; + var attributeStorage = logRecord.AttributeStorage ??= new List>(8); + attributeStorage.Add(new KeyValuePair(string.Empty, state)); + return attributeStorage; } } From 744c399dc076c4af05408ab0df2a678f300dc2b7 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 29 May 2022 13:11:56 -0700 Subject: [PATCH 09/41] Tweak. --- src/OpenTelemetry/Logs/LogRecordAttributes.cs | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/OpenTelemetry/Logs/LogRecordAttributes.cs b/src/OpenTelemetry/Logs/LogRecordAttributes.cs index 08a8f37c138..240bd919191 100644 --- a/src/OpenTelemetry/Logs/LogRecordAttributes.cs +++ b/src/OpenTelemetry/Logs/LogRecordAttributes.cs @@ -197,8 +197,8 @@ public readonly Enumerator GetEnumerator() internal readonly void ApplyToLogRecord(LogRecord logRecord) { - int attributeCount = this.count; - if (attributeCount <= 0) + int count = this.count; + if (count <= 0) { return; } @@ -211,30 +211,13 @@ internal readonly void ApplyToLogRecord(LogRecord logRecord) return; } - int count = this.count; - Debug.Assert(count <= 8, "Invalid size detected."); var attributeStorage = logRecord.AttributeStorage ??= new List>(OverflowAdditionalCapacity); try { - /* Note: If we ever add NET6+ target, this can be done more efficiently: - attributeStorage.EnsureCapacity(count); - - switch (count) - { - case 0: break; - case 8: attributeStorage[7] = this.attribute8; goto case 7; - case 7: attributeStorage[6] = this.attribute7; goto case 6; - case 6: attributeStorage[5] = this.attribute6; goto case 5; - case 5: attributeStorage[4] = this.attribute5; goto case 4; - case 4: attributeStorage[3] = this.attribute4; goto case 3; - case 3: attributeStorage[2] = this.attribute3; goto case 2; - case 2: attributeStorage[1] = this.attribute2; goto case 1; - case 1: attributeStorage[0] = this.attribute1; break; - } - */ + // TODO: Perf test this, adjust as needed. attributeStorage.Add(this.attribute1); if (count == 1) From df93cf58a5a12fd4bdce9906ce155e8b00badee7 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 29 May 2022 13:29:43 -0700 Subject: [PATCH 10/41] Test fix. --- src/OpenTelemetry/Batch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry/Batch.cs b/src/OpenTelemetry/Batch.cs index 30b4c1f4dff..146a505f043 100644 --- a/src/OpenTelemetry/Batch.cs +++ b/src/OpenTelemetry/Batch.cs @@ -66,7 +66,7 @@ internal Batch(T item) this.Count = this.targetCount = 1; } - internal Batch(CircularBuffer circularBuffer, int maxSize, Action? cleanupAction) + internal Batch(CircularBuffer circularBuffer, int maxSize, Action? cleanupAction = null) { Debug.Assert(maxSize > 0, $"{nameof(maxSize)} should be a positive number."); Debug.Assert(circularBuffer != null, $"{nameof(circularBuffer)} was null."); From 9a0ee320496d86c61d92d9434939b31faa0f6a64 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 29 May 2022 13:45:16 -0700 Subject: [PATCH 11/41] Test fix. --- src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs index 8dab7b51d96..2cd1c160e96 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using OpenTelemetry.Logs; namespace OpenTelemetry.Exporter { @@ -24,11 +25,13 @@ public class InMemoryExporter : BaseExporter { private readonly ICollection exportedItems; private readonly Func, ExportResult> onExport; + private readonly bool isLogExporter; public InMemoryExporter(ICollection exportedItems) { this.exportedItems = exportedItems; this.onExport = (Batch batch) => this.DefaultExport(batch); + this.isLogExporter = typeof(T) == typeof(LogRecord); } internal InMemoryExporter(Func, ExportResult> exportFunc) @@ -47,6 +50,11 @@ private ExportResult DefaultExport(in Batch batch) foreach (var data in batch) { + if (this.isLogExporter) + { + LogRecordPool.TrackReference((LogRecord)(object)data); + } + this.exportedItems.Add(data); } From 888eb54f89b4fdf6ccdc9e023b1a80e15a81e7f7 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Sun, 29 May 2022 21:32:17 -0700 Subject: [PATCH 12/41] Rename. --- .../Controllers/WeatherForecastController.cs | 2 +- .../.publicApi/net462/PublicAPI.Unshipped.txt | 32 +++++++++---------- .../netstandard2.0/PublicAPI.Unshipped.txt | 32 +++++++++---------- src/OpenTelemetry/Logs/LogEmitter.cs | 4 +-- ...ttributes.cs => LogRecordAttributeList.cs} | 20 ++++++------ src/OpenTelemetry/Logs/OpenTelemetryLogger.cs | 2 +- 6 files changed, 46 insertions(+), 46 deletions(-) rename src/OpenTelemetry/Logs/{LogRecordAttributes.cs => LogRecordAttributeList.cs} (93%) diff --git a/examples/AspNetCore/Controllers/WeatherForecastController.cs b/examples/AspNetCore/Controllers/WeatherForecastController.cs index 18c4a0129a8..76bbd0a1c4d 100644 --- a/examples/AspNetCore/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore/Controllers/WeatherForecastController.cs @@ -74,7 +74,7 @@ public IEnumerable Get() LogLevel = LogLevel.Information, Message = "WeatherForecasts generated.", }, - new LogRecordAttributes() + new LogRecordAttributeList() { ["count"] = forecast.Length, }); diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index eb56268e3cb..64129b597d5 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -4,25 +4,25 @@ OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void OpenTelemetry.BatchExportProcessor.InitializeAction.get -> System.Action? OpenTelemetry.BatchExportProcessor.InitializeAction.init -> void OpenTelemetry.Logs.LogEmitter -OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributes attributes = default(OpenTelemetry.Logs.LogRecordAttributes)) -> void +OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes = default(OpenTelemetry.Logs.LogRecordAttributeList)) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void -OpenTelemetry.Logs.LogRecordAttributes -OpenTelemetry.Logs.LogRecordAttributes.Add(string! key, object? value) -> void -OpenTelemetry.Logs.LogRecordAttributes.Add(System.Collections.Generic.KeyValuePair attribute) -> void -OpenTelemetry.Logs.LogRecordAttributes.Count.get -> int -OpenTelemetry.Logs.LogRecordAttributes.Enumerator -OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Dispose() -> void -OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Enumerator() -> void -OpenTelemetry.Logs.LogRecordAttributes.Enumerator.MoveNext() -> bool -OpenTelemetry.Logs.LogRecordAttributes.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributes.Enumerator -OpenTelemetry.Logs.LogRecordAttributes.LogRecordAttributes() -> void -OpenTelemetry.Logs.LogRecordAttributes.this[int index].get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributes.this[int index].set -> void -OpenTelemetry.Logs.LogRecordAttributes.this[string! key].set -> void +OpenTelemetry.Logs.LogRecordAttributeList +OpenTelemetry.Logs.LogRecordAttributeList.Add(string! key, object? value) -> void +OpenTelemetry.Logs.LogRecordAttributeList.Add(System.Collections.Generic.KeyValuePair attribute) -> void +OpenTelemetry.Logs.LogRecordAttributeList.Count.get -> int +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Dispose() -> void +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Enumerator() -> void +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.MoveNext() -> bool +OpenTelemetry.Logs.LogRecordAttributeList.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributeList.Enumerator +OpenTelemetry.Logs.LogRecordAttributeList.LogRecordAttributeList() -> void +OpenTelemetry.Logs.LogRecordAttributeList.this[int index].get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordAttributeList.this[int index].set -> void +OpenTelemetry.Logs.LogRecordAttributeList.this[string! key].set -> void OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void @@ -48,5 +48,5 @@ OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? OpenTelemetry.Logs.LogRecordData.TraceState.set -> void OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! -static OpenTelemetry.Logs.LogRecordAttributes.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributes +static OpenTelemetry.Logs.LogRecordAttributeList.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributeList static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index eb56268e3cb..64129b597d5 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -4,25 +4,25 @@ OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void OpenTelemetry.BatchExportProcessor.InitializeAction.get -> System.Action? OpenTelemetry.BatchExportProcessor.InitializeAction.init -> void OpenTelemetry.Logs.LogEmitter -OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributes attributes = default(OpenTelemetry.Logs.LogRecordAttributes)) -> void +OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes = default(OpenTelemetry.Logs.LogRecordAttributeList)) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void -OpenTelemetry.Logs.LogRecordAttributes -OpenTelemetry.Logs.LogRecordAttributes.Add(string! key, object? value) -> void -OpenTelemetry.Logs.LogRecordAttributes.Add(System.Collections.Generic.KeyValuePair attribute) -> void -OpenTelemetry.Logs.LogRecordAttributes.Count.get -> int -OpenTelemetry.Logs.LogRecordAttributes.Enumerator -OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Dispose() -> void -OpenTelemetry.Logs.LogRecordAttributes.Enumerator.Enumerator() -> void -OpenTelemetry.Logs.LogRecordAttributes.Enumerator.MoveNext() -> bool -OpenTelemetry.Logs.LogRecordAttributes.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributes.Enumerator -OpenTelemetry.Logs.LogRecordAttributes.LogRecordAttributes() -> void -OpenTelemetry.Logs.LogRecordAttributes.this[int index].get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributes.this[int index].set -> void -OpenTelemetry.Logs.LogRecordAttributes.this[string! key].set -> void +OpenTelemetry.Logs.LogRecordAttributeList +OpenTelemetry.Logs.LogRecordAttributeList.Add(string! key, object? value) -> void +OpenTelemetry.Logs.LogRecordAttributeList.Add(System.Collections.Generic.KeyValuePair attribute) -> void +OpenTelemetry.Logs.LogRecordAttributeList.Count.get -> int +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Dispose() -> void +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Enumerator() -> void +OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.MoveNext() -> bool +OpenTelemetry.Logs.LogRecordAttributeList.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributeList.Enumerator +OpenTelemetry.Logs.LogRecordAttributeList.LogRecordAttributeList() -> void +OpenTelemetry.Logs.LogRecordAttributeList.this[int index].get -> System.Collections.Generic.KeyValuePair +OpenTelemetry.Logs.LogRecordAttributeList.this[int index].set -> void +OpenTelemetry.Logs.LogRecordAttributeList.this[string! key].set -> void OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void @@ -48,5 +48,5 @@ OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? OpenTelemetry.Logs.LogRecordData.TraceState.set -> void OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! -static OpenTelemetry.Logs.LogRecordAttributes.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributes +static OpenTelemetry.Logs.LogRecordAttributeList.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributeList static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void diff --git a/src/OpenTelemetry/Logs/LogEmitter.cs b/src/OpenTelemetry/Logs/LogEmitter.cs index ef28799dc70..8fa353d9f3f 100644 --- a/src/OpenTelemetry/Logs/LogEmitter.cs +++ b/src/OpenTelemetry/Logs/LogEmitter.cs @@ -45,8 +45,8 @@ public LogEmitter(OpenTelemetryLoggerProvider loggerProvider) /// Emit a . /// /// . - /// . - public void Log(in LogRecordData data, in LogRecordAttributes attributes = default) + /// . + public void Log(in LogRecordData data, in LogRecordAttributeList attributes = default) { var provider = this.loggerProvider; var processor = provider.Processor; diff --git a/src/OpenTelemetry/Logs/LogRecordAttributes.cs b/src/OpenTelemetry/Logs/LogRecordAttributeList.cs similarity index 93% rename from src/OpenTelemetry/Logs/LogRecordAttributes.cs rename to src/OpenTelemetry/Logs/LogRecordAttributeList.cs index 240bd919191..f3f1020ce0c 100644 --- a/src/OpenTelemetry/Logs/LogRecordAttributes.cs +++ b/src/OpenTelemetry/Logs/LogRecordAttributeList.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ namespace OpenTelemetry.Logs /// /// Stores attributes to be added to a log message. /// - public struct LogRecordAttributes : IReadOnlyList> + public struct LogRecordAttributeList : IReadOnlyList> { private const int OverflowAdditionalCapacity = 8; private KeyValuePair attribute1; @@ -120,15 +120,15 @@ public object? this[string key] } /// - /// Create a collection from an enumerable. + /// Create a collection from an enumerable. /// /// Source attributes. - /// . - public static LogRecordAttributes CreateFromEnumerable(IEnumerable> attributes) + /// . + public static LogRecordAttributeList CreateFromEnumerable(IEnumerable> attributes) { Guard.ThrowIfNull(attributes); - LogRecordAttributes logRecordAttributes = default; + LogRecordAttributeList logRecordAttributes = default; logRecordAttributes.overflowAttributes = new(attributes); logRecordAttributes.count = logRecordAttributes.overflowAttributes.Count; return logRecordAttributes; @@ -183,7 +183,7 @@ public void Add(KeyValuePair attribute) } /// - /// Returns an enumerator that iterates through the . + /// Returns an enumerator that iterates through the . /// /// . public readonly Enumerator GetEnumerator() @@ -285,14 +285,14 @@ private void MoveAttributesToTheOverflowList() } /// - /// Enumerates the elements of a . + /// Enumerates the elements of a . /// public struct Enumerator : IEnumerator>, IEnumerator { - private LogRecordAttributes attributes; + private LogRecordAttributeList attributes; private int index; - internal Enumerator(in LogRecordAttributes attributes) + internal Enumerator(in LogRecordAttributeList attributes) { this.index = -1; this.attributes = attributes; diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index 67170084ea2..802fac8b003 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -90,7 +90,7 @@ public bool IsEnabled(LogLevel logLevel) private IReadOnlyList> ParseState(LogRecord logRecord, TState state) { - if (state is LogRecordAttributes logRecordAttributes) + if (state is LogRecordAttributeList logRecordAttributes) { logRecordAttributes.ApplyToLogRecord(logRecord); return logRecord.AttributeStorage!; From 558e8d2cf9dd414e81b4aab31b0e5c94d6f43623 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 31 May 2022 10:08:11 -0700 Subject: [PATCH 13/41] Trigger state buffering by processor inspection. --- .../.publicApi/net462/PublicAPI.Shipped.txt | 4 +-- .../netstandard2.0/PublicAPI.Shipped.txt | 4 +-- src/OpenTelemetry/CompositeProcessor.cs | 24 ++++++++-------- src/OpenTelemetry/Logs/OpenTelemetryLogger.cs | 10 +++++-- .../Logs/OpenTelemetryLoggerProvider.cs | 28 +++++++++++++++++++ 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt index ecb3440acf4..08a80922ae2 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt @@ -46,8 +46,8 @@ OpenTelemetry.BatchExportProcessorOptions.ScheduledDelayMilliseconds.set -> v OpenTelemetry.BatchLogRecordExportProcessor OpenTelemetry.BatchLogRecordExportProcessor.BatchLogRecordExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void OpenTelemetry.CompositeProcessor -~OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.CompositeProcessor -~OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable> processors) -> void +OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.CompositeProcessor! +OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable!>! processors) -> void OpenTelemetry.ExportProcessorType OpenTelemetry.ExportProcessorType.Batch = 1 -> OpenTelemetry.ExportProcessorType OpenTelemetry.ExportProcessorType.Simple = 0 -> OpenTelemetry.ExportProcessorType diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt index ecb3440acf4..08a80922ae2 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -46,8 +46,8 @@ OpenTelemetry.BatchExportProcessorOptions.ScheduledDelayMilliseconds.set -> v OpenTelemetry.BatchLogRecordExportProcessor OpenTelemetry.BatchLogRecordExportProcessor.BatchLogRecordExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void OpenTelemetry.CompositeProcessor -~OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.CompositeProcessor -~OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable> processors) -> void +OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.CompositeProcessor! +OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable!>! processors) -> void OpenTelemetry.ExportProcessorType OpenTelemetry.ExportProcessorType.Batch = 1 -> OpenTelemetry.ExportProcessorType OpenTelemetry.ExportProcessorType.Simple = 0 -> OpenTelemetry.ExportProcessorType diff --git a/src/OpenTelemetry/CompositeProcessor.cs b/src/OpenTelemetry/CompositeProcessor.cs index 49c2258bd49..223387c80a1 100644 --- a/src/OpenTelemetry/CompositeProcessor.cs +++ b/src/OpenTelemetry/CompositeProcessor.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System; using System.Collections.Generic; using System.Diagnostics; @@ -24,7 +26,7 @@ namespace OpenTelemetry { public class CompositeProcessor : BaseProcessor { - private readonly DoublyLinkedListNode head; + internal readonly DoublyLinkedListNode Head; private DoublyLinkedListNode tail; private bool disposed; @@ -38,8 +40,8 @@ public CompositeProcessor(IEnumerable> processors) throw new ArgumentException($"'{iter}' is null or empty", nameof(iter)); } - this.head = new DoublyLinkedListNode(iter.Current); - this.tail = this.head; + this.Head = new DoublyLinkedListNode(iter.Current); + this.tail = this.Head; while (iter.MoveNext()) { @@ -64,7 +66,7 @@ public CompositeProcessor AddProcessor(BaseProcessor processor) /// public override void OnEnd(T data) { - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { cur.Value.OnEnd(data); } @@ -73,7 +75,7 @@ public override void OnEnd(T data) /// public override void OnStart(T data) { - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { cur.Value.OnStart(data); } @@ -87,7 +89,7 @@ protected override bool OnForceFlush(int timeoutMilliseconds) ? null : Stopwatch.StartNew(); - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { if (sw == null) { @@ -113,7 +115,7 @@ protected override bool OnShutdown(int timeoutMilliseconds) ? null : Stopwatch.StartNew(); - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { if (sw == null) { @@ -138,7 +140,7 @@ protected override void Dispose(bool disposing) { if (disposing) { - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { try { @@ -157,7 +159,7 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - private class DoublyLinkedListNode + internal sealed class DoublyLinkedListNode { public readonly BaseProcessor Value; @@ -166,9 +168,9 @@ public DoublyLinkedListNode(BaseProcessor value) this.Value = value; } - public DoublyLinkedListNode Previous { get; set; } + public DoublyLinkedListNode? Previous { get; set; } - public DoublyLinkedListNode Next { get; set; } + public DoublyLinkedListNode? Next { get; set; } } } } diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index 802fac8b003..b6a148aeb9d 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -57,7 +57,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except record.ScopeProvider = provider.IncludeScopes ? this.ScopeProvider : null; record.State = provider.ParseStateValues ? null : state; - record.StateValues = provider.ParseStateValues ? this.ParseState(record, state) : null; + record.StateValues = provider.ParseStateValues ? this.ParseState(record, state, provider.HasBatchProcessor) : null; ref LogRecordData data = ref record.Data; @@ -88,7 +88,7 @@ public bool IsEnabled(LogLevel logLevel) public IDisposable BeginScope(TState state) => this.ScopeProvider?.Push(state) ?? NullScope.Instance; - private IReadOnlyList> ParseState(LogRecord logRecord, TState state) + private IReadOnlyList> ParseState(LogRecord logRecord, TState state, bool buffer) { if (state is LogRecordAttributeList logRecordAttributes) { @@ -97,6 +97,12 @@ public bool IsEnabled(LogLevel logLevel) } else if (state is IReadOnlyList> stateList) { + if (!buffer) + { + return stateList; + } + + // Note: Buffering is needed to fix: https://github.com/open-telemetry/opentelemetry-dotnet/issues/2905 var attributeStorage = logRecord.AttributeStorage ??= new List>(stateList.Count); attributeStorage.AddRange(stateList); return attributeStorage; diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs index 4a1927a314c..778397ad36b 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs @@ -34,6 +34,7 @@ public class OpenTelemetryLoggerProvider : BaseProvider, ILoggerProvider, ISuppo internal readonly bool IncludeFormattedMessage; internal readonly bool ParseStateValues; internal BaseProcessor? Processor; + internal bool HasBatchProcessor; internal Resource Resource; private readonly Hashtable loggers = new(); private bool disposed; @@ -124,6 +125,11 @@ internal OpenTelemetryLoggerProvider AddProcessor(BaseProcessor proce processor.SetParentProvider(this); + if (!this.HasBatchProcessor) + { + this.InspectProcessor(processor); + } + if (this.Processor == null) { this.Processor = processor; @@ -162,5 +168,27 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + + private void InspectProcessor(BaseProcessor processor) + { + if (processor is BatchExportProcessor) + { + this.HasBatchProcessor = true; + } + else if (processor is CompositeProcessor compositeProcessor) + { + var current = compositeProcessor.Head; + while (current != null) + { + this.InspectProcessor(current.Value); + if (this.HasBatchProcessor) + { + return; + } + + current = current.Next; + } + } + } } } From 4d76b87dd520389910138e7fcd8833c72b301a55 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 31 May 2022 12:28:43 -0700 Subject: [PATCH 14/41] Implement copy for in-memory log exporter. --- .../InMemoryExporter.cs | 17 ++++------- .../InMemoryExporterLoggingExtensions.cs | 30 ++++++++++++++++++- .../InMemoryExporterMetricsExtensions.cs | 2 +- src/OpenTelemetry/Logs/LogRecord.cs | 11 ++++--- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs index 2cd1c160e96..2f6d7a4c594 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporter.cs @@ -14,9 +14,7 @@ // limitations under the License. // -using System; using System.Collections.Generic; -using OpenTelemetry.Logs; namespace OpenTelemetry.Exporter { @@ -24,21 +22,21 @@ public class InMemoryExporter : BaseExporter where T : class { private readonly ICollection exportedItems; - private readonly Func, ExportResult> onExport; - private readonly bool isLogExporter; + private readonly ExportFunc onExport; public InMemoryExporter(ICollection exportedItems) { this.exportedItems = exportedItems; - this.onExport = (Batch batch) => this.DefaultExport(batch); - this.isLogExporter = typeof(T) == typeof(LogRecord); + this.onExport = (in Batch batch) => this.DefaultExport(in batch); } - internal InMemoryExporter(Func, ExportResult> exportFunc) + internal InMemoryExporter(ExportFunc exportFunc) { this.onExport = exportFunc; } + internal delegate ExportResult ExportFunc(in Batch batch); + public override ExportResult Export(in Batch batch) => this.onExport(batch); private ExportResult DefaultExport(in Batch batch) @@ -50,11 +48,6 @@ private ExportResult DefaultExport(in Batch batch) foreach (var data in batch) { - if (this.isLogExporter) - { - LogRecordPool.TrackReference((LogRecord)(object)data); - } - this.exportedItems.Add(data); } diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs index 9cbde894ced..b162fc0379c 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs @@ -27,7 +27,35 @@ public static OpenTelemetryLoggerOptions AddInMemoryExporter(this OpenTelemetryL Guard.ThrowIfNull(loggerOptions); Guard.ThrowIfNull(exportedItems); - return loggerOptions.AddProcessor(new SimpleLogRecordExportProcessor(new InMemoryExporter(exportedItems))); + var logExporter = new InMemoryExporter( + exportFunc: (in Batch batch) => ExportLogRecord(in batch, exportedItems)); + + return loggerOptions.AddProcessor(new SimpleLogRecordExportProcessor(logExporter)); + } + + private static ExportResult ExportLogRecord(in Batch batch, ICollection exportedItems) + { + if (exportedItems == null) + { + return ExportResult.Failure; + } + + foreach (var log in batch) + { + log.BufferLogScopes(); + + LogRecord copy = new() + { + Data = log.Data, + State = log.State, + StateValues = log.StateValues, + BufferedScopes = log.BufferedScopes, + }; + + exportedItems.Add(copy); + } + + return ExportResult.Success; } } } diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs index f817f5744fa..613067d5e8a 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterMetricsExtensions.cs @@ -145,7 +145,7 @@ private static MeterProviderBuilder AddInMemoryExporter( configureMetricReader?.Invoke(metricReaderOptions); var metricExporter = new InMemoryExporter( - exportFunc: metricBatch => ExportMetricSnapshot(metricBatch, exportedItems)); + exportFunc: (in Batch metricBatch) => ExportMetricSnapshot(in metricBatch, exportedItems)); var metricReader = PeriodicExportingMetricReaderHelper.CreatePeriodicExportingMetricReader( metricExporter, diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index bcd4225afa8..94eac8cb69c 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -32,14 +32,13 @@ public sealed class LogRecord internal int PoolReferences = int.MaxValue; internal LogRecordData Data; internal List>? AttributeStorage; + internal List? BufferedScopes; private static readonly Action> AddScopeToBufferedList = (object? scope, List state) => { state.Add(scope); }; - private List? bufferedScopes; - internal LogRecord() { } @@ -164,9 +163,9 @@ public void ForEachScope(Action callback, TState var forEachScopeState = new ScopeForEachState(callback, state); - if (this.bufferedScopes != null) + if (this.BufferedScopes != null) { - foreach (object? scope in this.bufferedScopes) + foreach (object? scope in this.BufferedScopes) { ScopeForEachState.ForEachScope(scope, forEachScopeState); } @@ -183,7 +182,7 @@ public void ForEachScope(Action callback, TState /// internal void BufferLogScopes() { - if (this.ScopeProvider == null || this.bufferedScopes != null) + if (this.ScopeProvider == null || this.BufferedScopes != null) { return; } @@ -192,7 +191,7 @@ internal void BufferLogScopes() this.ScopeProvider?.ForEachScope(AddScopeToBufferedList, scopes); - this.bufferedScopes = scopes; + this.BufferedScopes = scopes; } private readonly struct ScopeForEachState From 617d88d96691e9b9c47ad9496ae75a78e85c567f Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 31 May 2022 12:44:31 -0700 Subject: [PATCH 15/41] Added GetDataRef. --- .../Implementation/LogRecordExtensions.cs | 39 ++++++++++--------- .../.publicApi/net462/PublicAPI.Unshipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 1 + src/OpenTelemetry/Logs/LogRecord.cs | 9 +++++ 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs index 6139831d162..b27888cf9b4 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs @@ -65,30 +65,31 @@ internal static void AddBatch( internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord) { OtlpLogs.LogRecord otlpLogRecord = null; + ref LogRecordData data = ref logRecord.GetDataRef(); try { otlpLogRecord = new OtlpLogs.LogRecord { - TimeUnixNano = (ulong)logRecord.Timestamp.ToUnixTimeNanoseconds(), - SeverityNumber = GetSeverityNumber(logRecord.LogLevel), - SeverityText = LogLevels[(int)logRecord.LogLevel], + TimeUnixNano = (ulong)data.Timestamp.ToUnixTimeNanoseconds(), + SeverityNumber = GetSeverityNumber(data.LogLevel), + SeverityText = LogLevels[(int)data.LogLevel], }; - if (!string.IsNullOrEmpty(logRecord.CategoryName)) + if (!string.IsNullOrEmpty(data.CategoryName)) { // TODO: // 1. Track the following issue, and map CategoryName to Name // if it makes it to log data model. // https://github.com/open-telemetry/opentelemetry-specification/issues/2398 // 2. Confirm if this name for attribute is good. - otlpLogRecord.Attributes.AddStringAttribute("dotnet.ilogger.category", logRecord.CategoryName); + otlpLogRecord.Attributes.AddStringAttribute("dotnet.ilogger.category", data.CategoryName); } bool bodyPopulatedFromFormattedMessage = false; - if (logRecord.FormattedMessage != null) + if (data.Message != null) { - otlpLogRecord.Body = new OtlpCommon.AnyValue { StringValue = logRecord.FormattedMessage }; + otlpLogRecord.Body = new OtlpCommon.AnyValue { StringValue = data.Message }; bodyPopulatedFromFormattedMessage = true; } @@ -110,34 +111,34 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord) } } - if (logRecord.EventId.Id != default) + if (data.EventId.Id != default) { - otlpLogRecord.Attributes.AddIntAttribute(nameof(logRecord.EventId.Id), logRecord.EventId.Id); + otlpLogRecord.Attributes.AddIntAttribute(nameof(data.EventId.Id), data.EventId.Id); } - if (!string.IsNullOrEmpty(logRecord.EventId.Name)) + if (!string.IsNullOrEmpty(data.EventId.Name)) { - otlpLogRecord.Attributes.AddStringAttribute(nameof(logRecord.EventId.Name), logRecord.EventId.Name); + otlpLogRecord.Attributes.AddStringAttribute(nameof(data.EventId.Name), data.EventId.Name); } - if (logRecord.Exception != null) + if (data.Exception != null) { - otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionType, logRecord.Exception.GetType().Name); - otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionMessage, logRecord.Exception.Message); - otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionStacktrace, logRecord.Exception.ToInvariantString()); + otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionType, data.Exception.GetType().Name); + otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionMessage, data.Exception.Message); + otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionStacktrace, data.Exception.ToInvariantString()); } - if (logRecord.TraceId != default && logRecord.SpanId != default) + if (data.TraceId != default && data.SpanId != default) { byte[] traceIdBytes = new byte[16]; byte[] spanIdBytes = new byte[8]; - logRecord.TraceId.CopyTo(traceIdBytes); - logRecord.SpanId.CopyTo(spanIdBytes); + data.TraceId.CopyTo(traceIdBytes); + data.SpanId.CopyTo(spanIdBytes); otlpLogRecord.TraceId = UnsafeByteOperations.UnsafeWrap(traceIdBytes); otlpLogRecord.SpanId = UnsafeByteOperations.UnsafeWrap(spanIdBytes); - otlpLogRecord.Flags = (uint)logRecord.TraceFlags; + otlpLogRecord.Flags = (uint)data.TraceFlags; } int scopeDepth = -1; diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 64129b597d5..f59738ac7da 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -7,6 +7,7 @@ OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes = default(OpenTelemetry.Logs.LogRecordAttributeList)) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void +OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void OpenTelemetry.Logs.LogRecordAttributeList diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 64129b597d5..f59738ac7da 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -7,6 +7,7 @@ OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes = default(OpenTelemetry.Logs.LogRecordAttributeList)) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void +OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void OpenTelemetry.Logs.LogRecordAttributeList diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index 94eac8cb69c..0f781c2a852 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -176,6 +176,15 @@ public void ForEachScope(Action callback, TState } } + /// + /// Gets a reference to the for the log message. + /// + /// . + public ref LogRecordData GetDataRef() + { + return ref this.Data; + } + /// /// Buffers the scopes attached to the log into a list so that they can /// be safely processed after the log message lifecycle has ended. From ca7e319222a1f0cda86b9ef8efea8cbcd58b7057 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 31 May 2022 13:25:24 -0700 Subject: [PATCH 16/41] Tweaks. --- .../InMemoryExporterLoggingExtensions.cs | 6 +-- .../Logs/BatchLogRecordExportProcessor.cs | 2 +- src/OpenTelemetry/Logs/LogRecord.cs | 34 +++++++++++-- src/OpenTelemetry/Logs/LogRecordPool.cs | 51 +++++++++++++------ src/OpenTelemetry/Logs/OpenTelemetryLogger.cs | 14 ++--- .../Logs/OpenTelemetryLoggerProvider.cs | 28 ---------- .../OpenTelemetry.Tests/Logs/LogRecordTest.cs | 21 +------- 7 files changed, 74 insertions(+), 82 deletions(-) diff --git a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs index b162fc0379c..d20d9967a04 100644 --- a/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs +++ b/src/OpenTelemetry.Exporter.InMemory/InMemoryExporterLoggingExtensions.cs @@ -42,14 +42,14 @@ private static ExportResult ExportLogRecord(in Batch batch, ICollecti foreach (var log in batch) { - log.BufferLogScopes(); + log.Buffer(); LogRecord copy = new() { Data = log.Data, State = log.State, - StateValues = log.StateValues, - BufferedScopes = log.BufferedScopes, + StateValues = log.StateValues == null ? null : new List>(log.StateValues), + BufferedScopes = log.BufferedScopes == null ? null : new List(log.BufferedScopes), }; exportedItems.Add(copy); diff --git a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs index d260a2ee31c..d8a8bdbceec 100644 --- a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs +++ b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs @@ -59,7 +59,7 @@ public override void OnEnd(LogRecord data) // happen here. Debug.Assert(data != null, "LogRecord was null."); - data!.BufferLogScopes(); + data!.Buffer(); base.OnEnd(data); } diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index 0f781c2a852..a15a327ef0b 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -185,22 +185,46 @@ public ref LogRecordData GetDataRef() return ref this.Data; } + internal void Buffer() + { + this.BufferLogStateValues(); + this.BufferLogScopes(); + } + + /// + /// Buffers the state values attached to the log into a list so that + /// they can be safely processed after the log message lifecycle has + /// ended. + /// + private void BufferLogStateValues() + { + var stateValues = this.StateValues; + if (stateValues == null || stateValues == this.AttributeStorage) + { + return; + } + + var attributeStorage = this.AttributeStorage ??= new List>(stateValues.Count); + attributeStorage.AddRange(stateValues); + this.StateValues = attributeStorage; + } + /// /// Buffers the scopes attached to the log into a list so that they can /// be safely processed after the log message lifecycle has ended. /// - internal void BufferLogScopes() + private void BufferLogScopes() { - if (this.ScopeProvider == null || this.BufferedScopes != null) + if (this.ScopeProvider == null) { return; } - List scopes = new List(); + List scopes = this.BufferedScopes ??= new List(16); - this.ScopeProvider?.ForEachScope(AddScopeToBufferedList, scopes); + this.ScopeProvider.ForEachScope(AddScopeToBufferedList, scopes); - this.BufferedScopes = scopes; + this.ScopeProvider = null; } private readonly struct ScopeForEachState diff --git a/src/OpenTelemetry/Logs/LogRecordPool.cs b/src/OpenTelemetry/Logs/LogRecordPool.cs index 69f46f0375c..f68e96af84d 100644 --- a/src/OpenTelemetry/Logs/LogRecordPool.cs +++ b/src/OpenTelemetry/Logs/LogRecordPool.cs @@ -122,21 +122,7 @@ private void ReturnCore(LogRecord logRecord) return; } - var attributeStorage = logRecord.AttributeStorage; - if (attributeStorage != null) - { - if (attributeStorage.Count > 64) - { - // Don't allow the pool to grow unconstained. - logRecord.AttributeStorage = null; - } - else - { - /* List.Clear sets the size to 0 but it maintains the - underlying array. */ - attributeStorage.Clear(); - } - } + this.Clear(logRecord); if (threadStaticLogRecord == null) { @@ -163,5 +149,40 @@ underlying array. */ } } } + + private void Clear(LogRecord logRecord) + { + var attributeStorage = logRecord.AttributeStorage; + if (attributeStorage != null) + { + if (attributeStorage.Count > 64) + { + // Don't allow the pool to grow unconstained. + logRecord.AttributeStorage = null; + } + else + { + /* List.Clear sets the size to 0 but it maintains the + underlying array. */ + attributeStorage.Clear(); + } + } + + var bufferedScopes = logRecord.BufferedScopes; + if (bufferedScopes != null) + { + if (bufferedScopes.Count > 16) + { + // Don't allow the pool to grow unconstained. + logRecord.BufferedScopes = null; + } + else + { + /* List.Clear sets the size to 0 but it maintains the + underlying array. */ + bufferedScopes.Clear(); + } + } + } } } diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index b6a148aeb9d..28a73f662e8 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -57,7 +57,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except record.ScopeProvider = provider.IncludeScopes ? this.ScopeProvider : null; record.State = provider.ParseStateValues ? null : state; - record.StateValues = provider.ParseStateValues ? this.ParseState(record, state, provider.HasBatchProcessor) : null; + record.StateValues = provider.ParseStateValues ? this.ParseState(record, state) : null; ref LogRecordData data = ref record.Data; @@ -88,7 +88,7 @@ public bool IsEnabled(LogLevel logLevel) public IDisposable BeginScope(TState state) => this.ScopeProvider?.Push(state) ?? NullScope.Instance; - private IReadOnlyList> ParseState(LogRecord logRecord, TState state, bool buffer) + private IReadOnlyList> ParseState(LogRecord logRecord, TState state) { if (state is LogRecordAttributeList logRecordAttributes) { @@ -97,15 +97,7 @@ public bool IsEnabled(LogLevel logLevel) } else if (state is IReadOnlyList> stateList) { - if (!buffer) - { - return stateList; - } - - // Note: Buffering is needed to fix: https://github.com/open-telemetry/opentelemetry-dotnet/issues/2905 - var attributeStorage = logRecord.AttributeStorage ??= new List>(stateList.Count); - attributeStorage.AddRange(stateList); - return attributeStorage; + return stateList; } else if (state is IEnumerable> stateValues) { diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs index 778397ad36b..4a1927a314c 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs @@ -34,7 +34,6 @@ public class OpenTelemetryLoggerProvider : BaseProvider, ILoggerProvider, ISuppo internal readonly bool IncludeFormattedMessage; internal readonly bool ParseStateValues; internal BaseProcessor? Processor; - internal bool HasBatchProcessor; internal Resource Resource; private readonly Hashtable loggers = new(); private bool disposed; @@ -125,11 +124,6 @@ internal OpenTelemetryLoggerProvider AddProcessor(BaseProcessor proce processor.SetParentProvider(this); - if (!this.HasBatchProcessor) - { - this.InspectProcessor(processor); - } - if (this.Processor == null) { this.Processor = processor; @@ -168,27 +162,5 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - - private void InspectProcessor(BaseProcessor processor) - { - if (processor is BatchExportProcessor) - { - this.HasBatchProcessor = true; - } - else if (processor is CompositeProcessor compositeProcessor) - { - var current = compositeProcessor.Head; - while (current != null) - { - this.InspectProcessor(current.Value); - if (this.HasBatchProcessor) - { - return; - } - - current = current.Next; - } - } - } } } diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs index 37dd5615106..ad67a63c592 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs @@ -746,15 +746,13 @@ public void ParseStateValuesUsingCustomTest() private static ILoggerFactory InitializeLoggerFactory(out List exportedItems, Action configure = null) { - exportedItems = new List(); - var exporter = new InMemoryExporter(exportedItems); - var processor = new TestLogRecordProcessor(exporter); + var items = exportedItems = new List(); return LoggerFactory.Create(builder => { builder.AddOpenTelemetry(options => { configure?.Invoke(options); - options.AddProcessor(processor); + options.AddInMemoryExporter(items); }); builder.AddFilter(typeof(LogRecordTest).FullName, LogLevel.Trace); }); @@ -841,21 +839,6 @@ private class CustomState { public string Property { get; set; } } - - private class TestLogRecordProcessor : SimpleExportProcessor - { - public TestLogRecordProcessor(BaseExporter exporter) - : base(exporter) - { - } - - public override void OnEnd(LogRecord data) - { - data.BufferLogScopes(); - - base.OnEnd(data); - } - } } } #endif From a92e78e8c815de8d017db446e036f651d516cd41 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 31 May 2022 13:30:21 -0700 Subject: [PATCH 17/41] Revert CompositeProcessor change. --- src/OpenTelemetry/CompositeProcessor.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/OpenTelemetry/CompositeProcessor.cs b/src/OpenTelemetry/CompositeProcessor.cs index 223387c80a1..6adae3ade09 100644 --- a/src/OpenTelemetry/CompositeProcessor.cs +++ b/src/OpenTelemetry/CompositeProcessor.cs @@ -26,7 +26,7 @@ namespace OpenTelemetry { public class CompositeProcessor : BaseProcessor { - internal readonly DoublyLinkedListNode Head; + private readonly DoublyLinkedListNode head; private DoublyLinkedListNode tail; private bool disposed; @@ -40,8 +40,8 @@ public CompositeProcessor(IEnumerable> processors) throw new ArgumentException($"'{iter}' is null or empty", nameof(iter)); } - this.Head = new DoublyLinkedListNode(iter.Current); - this.tail = this.Head; + this.head = new DoublyLinkedListNode(iter.Current); + this.tail = this.head; while (iter.MoveNext()) { @@ -66,7 +66,7 @@ public CompositeProcessor AddProcessor(BaseProcessor processor) /// public override void OnEnd(T data) { - for (var cur = this.Head; cur != null; cur = cur.Next) + for (var cur = this.head; cur != null; cur = cur.Next) { cur.Value.OnEnd(data); } @@ -75,7 +75,7 @@ public override void OnEnd(T data) /// public override void OnStart(T data) { - for (var cur = this.Head; cur != null; cur = cur.Next) + for (var cur = this.head; cur != null; cur = cur.Next) { cur.Value.OnStart(data); } @@ -89,7 +89,7 @@ protected override bool OnForceFlush(int timeoutMilliseconds) ? null : Stopwatch.StartNew(); - for (var cur = this.Head; cur != null; cur = cur.Next) + for (var cur = this.head; cur != null; cur = cur.Next) { if (sw == null) { @@ -115,7 +115,7 @@ protected override bool OnShutdown(int timeoutMilliseconds) ? null : Stopwatch.StartNew(); - for (var cur = this.Head; cur != null; cur = cur.Next) + for (var cur = this.head; cur != null; cur = cur.Next) { if (sw == null) { @@ -140,7 +140,7 @@ protected override void Dispose(bool disposing) { if (disposing) { - for (var cur = this.Head; cur != null; cur = cur.Next) + for (var cur = this.head; cur != null; cur = cur.Next) { try { @@ -159,7 +159,7 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - internal sealed class DoublyLinkedListNode + private sealed class DoublyLinkedListNode { public readonly BaseProcessor Value; From 5e33474ec92ea5955cc08f90499199a738858470 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 31 May 2022 16:24:06 -0700 Subject: [PATCH 18/41] Add log stress tests to solution. --- OpenTelemetry.sln | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 8e14f86b9bc..3197a234d5e 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -233,7 +233,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Pr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Propagators.Tests", "test\OpenTelemetry.Extensions.Propagators.Tests\OpenTelemetry.Extensions.Propagators.Tests.csproj", "{476D804B-BFEC-4D34-814C-DFFD97109989}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "correlation", "docs\logs\correlation\correlation.csproj", "{9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "correlation", "docs\logs\correlation\correlation.csproj", "{9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Tests.Stress.Logs", "test\OpenTelemetry.Tests.Stress.Logs\OpenTelemetry.Tests.Stress.Logs.csproj", "{5FC0660F-3757-4594-806B-4375E06177A3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -493,6 +495,10 @@ Global {9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5}.Release|Any CPU.Build.0 = Release|Any CPU + {5FC0660F-3757-4594-806B-4375E06177A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FC0660F-3757-4594-806B-4375E06177A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FC0660F-3757-4594-806B-4375E06177A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FC0660F-3757-4594-806B-4375E06177A3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 6b15b3ba7b41b531c07500f2032d9871ea682102 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 31 May 2022 16:24:39 -0700 Subject: [PATCH 19/41] Tweaks. --- src/OpenTelemetry/Logs/LogRecordPool.cs | 45 ++++++++++++++++--------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/OpenTelemetry/Logs/LogRecordPool.cs b/src/OpenTelemetry/Logs/LogRecordPool.cs index f68e96af84d..b25c397bd1d 100644 --- a/src/OpenTelemetry/Logs/LogRecordPool.cs +++ b/src/OpenTelemetry/Logs/LogRecordPool.cs @@ -17,6 +17,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Threading; using OpenTelemetry.Internal; @@ -55,10 +56,13 @@ public static void Resize(int size) current = new LogRecordPool(size); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static LogRecord Rent() => current.RentCore(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void Return(LogRecord logRecord) => current.ReturnCore(logRecord); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void TrackReference(LogRecord logRecord) => Interlocked.Increment(ref logRecord.PoolReferences); @@ -75,6 +79,13 @@ private LogRecord RentCore() return logRecord; } + return this.RentFromSharedPool(); + } + + private LogRecord RentFromSharedPool() + { + LogRecord? logRecord; + SpinWait wait = default; while (true) { @@ -127,26 +138,30 @@ private void ReturnCore(LogRecord logRecord) if (threadStaticLogRecord == null) { threadStaticLogRecord = logRecord; + return; } - else + + this.ReturnToSharedPool(logRecord); + } + + private void ReturnToSharedPool(LogRecord logRecord) + { + SpinWait wait = default; + while (true) { - SpinWait wait = default; - while (true) + int sharedPoolIndex = this.sharedPoolCurrentIndex; + if (sharedPoolIndex >= this.sharedPoolSize) { - int sharedPoolIndex = this.sharedPoolCurrentIndex; - if (sharedPoolIndex >= this.sharedPoolSize) - { - return; - } - - if (Interlocked.CompareExchange(ref this.sharedPoolCurrentIndex, sharedPoolIndex + 1, sharedPoolIndex) == sharedPoolIndex) - { - this.sharedPool[sharedPoolIndex + 1] = logRecord; - break; - } + return; + } - wait.SpinOnce(); + if (Interlocked.CompareExchange(ref this.sharedPoolCurrentIndex, sharedPoolIndex + 1, sharedPoolIndex) == sharedPoolIndex) + { + this.sharedPool[sharedPoolIndex + 1] = logRecord; + break; } + + wait.SpinOnce(); } } From 2114dcaae6026b4927f5e38360808436329d1f83 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Wed, 1 Jun 2022 12:14:20 -0700 Subject: [PATCH 20/41] Code review. --- .../.publicApi/net462/PublicAPI.Unshipped.txt | 4 --- .../netstandard2.0/PublicAPI.Unshipped.txt | 4 --- src/OpenTelemetry/Batch.cs | 35 +++++++++---------- src/OpenTelemetry/BatchExportProcessor.cs | 34 +++++++----------- .../Logs/BatchLogRecordExportProcessor.cs | 9 +++-- 5 files changed, 35 insertions(+), 51 deletions(-) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index f59738ac7da..a59cccfea97 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -1,8 +1,4 @@ #nullable enable -OpenTelemetry.BatchExportProcessor.CleanupAction.get -> System.Action? -OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void -OpenTelemetry.BatchExportProcessor.InitializeAction.get -> System.Action? -OpenTelemetry.BatchExportProcessor.InitializeAction.init -> void OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes = default(OpenTelemetry.Logs.LogRecordAttributeList)) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index f59738ac7da..a59cccfea97 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,8 +1,4 @@ #nullable enable -OpenTelemetry.BatchExportProcessor.CleanupAction.get -> System.Action? -OpenTelemetry.BatchExportProcessor.CleanupAction.init -> void -OpenTelemetry.BatchExportProcessor.InitializeAction.get -> System.Action? -OpenTelemetry.BatchExportProcessor.InitializeAction.init -> void OpenTelemetry.Logs.LogEmitter OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes = default(OpenTelemetry.Logs.LogRecordAttributeList)) -> void OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void diff --git a/src/OpenTelemetry/Batch.cs b/src/OpenTelemetry/Batch.cs index 146a505f043..2abd128152d 100644 --- a/src/OpenTelemetry/Batch.cs +++ b/src/OpenTelemetry/Batch.cs @@ -22,6 +22,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using OpenTelemetry.Internal; +using OpenTelemetry.Logs; namespace OpenTelemetry { @@ -34,7 +35,6 @@ namespace OpenTelemetry { private readonly T? item; private readonly CircularBuffer? circularBuffer; - private readonly Action? cleanupAction; private readonly T[]? items; private readonly long targetCount; @@ -50,7 +50,6 @@ public Batch(T[] items, int count) this.item = null; this.circularBuffer = null; - this.cleanupAction = null; this.items = items; this.Count = this.targetCount = count; } @@ -61,12 +60,11 @@ internal Batch(T item) this.item = item; this.circularBuffer = null; - this.cleanupAction = null; this.items = null; this.Count = this.targetCount = 1; } - internal Batch(CircularBuffer circularBuffer, int maxSize, Action? cleanupAction = null) + internal Batch(CircularBuffer circularBuffer, int maxSize) { Debug.Assert(maxSize > 0, $"{nameof(maxSize)} should be a positive number."); Debug.Assert(circularBuffer != null, $"{nameof(circularBuffer)} was null."); @@ -74,7 +72,6 @@ internal Batch(CircularBuffer circularBuffer, int maxSize, Action? cleanup this.item = null; this.items = null; this.circularBuffer = circularBuffer; - this.cleanupAction = cleanupAction; this.Count = Math.Min(maxSize, circularBuffer!.Count); this.targetCount = circularBuffer.RemovedCount + this.Count; } @@ -95,10 +92,11 @@ public void Dispose() while (this.circularBuffer.RemovedCount < this.targetCount) { T item = this.circularBuffer.Read(); - this.cleanupAction?.Invoke(item); + if (typeof(T) == typeof(LogRecord)) + { + LogRecordPool.Return((LogRecord)(object)item); + } } - - return; } } @@ -109,7 +107,7 @@ public void Dispose() public Enumerator GetEnumerator() { return this.circularBuffer != null - ? new Enumerator(this.circularBuffer, this.targetCount, this.cleanupAction) + ? new Enumerator(this.circularBuffer, this.targetCount) : this.item != null ? new Enumerator(this.item) /* In the event someone uses default/new Batch() to create Batch we fallback to empty items mode. */ @@ -147,14 +145,17 @@ public struct Enumerator : IEnumerator return false; }; - private static readonly BatchEnumeratorMoveNextFunc MoveNextCircularBufferWithCleanup = (ref Enumerator enumerator) => + private static readonly BatchEnumeratorMoveNextFunc MoveNextCircularBufferLogRecord = (ref Enumerator enumerator) => { var circularBuffer = enumerator.circularBuffer; var currentItem = enumerator.Current; if (currentItem != null) { - enumerator.cleanupAction!(currentItem); + if (typeof(T) == typeof(LogRecord)) + { + LogRecordPool.Return((LogRecord)(object)currentItem); + } } if (circularBuffer!.RemovedCount < enumerator.targetCount) @@ -184,7 +185,6 @@ public struct Enumerator : IEnumerator private readonly CircularBuffer? circularBuffer; private readonly T[]? items; private readonly BatchEnumeratorMoveNextFunc moveNextFunc; - private readonly Action? cleanupAction; private long targetCount; private int itemIndex; [AllowNull] @@ -197,19 +197,17 @@ internal Enumerator(T item) this.items = null; this.targetCount = -1; this.itemIndex = 0; - this.cleanupAction = null; this.moveNextFunc = MoveNextSingleItem; } - internal Enumerator(CircularBuffer circularBuffer, long targetCount, Action? cleanupAction) + internal Enumerator(CircularBuffer circularBuffer, long targetCount) { this.current = null; this.items = null; this.circularBuffer = circularBuffer; this.targetCount = targetCount; this.itemIndex = 0; - this.cleanupAction = cleanupAction; - this.moveNextFunc = cleanupAction != null ? MoveNextCircularBufferWithCleanup : MoveNextCircularBuffer; + this.moveNextFunc = typeof(T) == typeof(LogRecord) ? MoveNextCircularBufferLogRecord : MoveNextCircularBuffer; } internal Enumerator(T[] items, long targetCount) @@ -219,7 +217,6 @@ internal Enumerator(T[] items, long targetCount) this.items = items; this.targetCount = targetCount; this.itemIndex = 0; - this.cleanupAction = null; this.moveNextFunc = MoveNextArray; } @@ -232,12 +229,12 @@ internal Enumerator(T[] items, long targetCount) /// public void Dispose() { - if (this.cleanupAction != null) + if (typeof(T) == typeof(LogRecord)) { var currentItem = this.current; if (currentItem != null) { - this.cleanupAction(currentItem); + LogRecordPool.Return((LogRecord)(object)currentItem); this.current = null; } } diff --git a/src/OpenTelemetry/BatchExportProcessor.cs b/src/OpenTelemetry/BatchExportProcessor.cs index 93ea11c3cf8..5fdc2c8893f 100644 --- a/src/OpenTelemetry/BatchExportProcessor.cs +++ b/src/OpenTelemetry/BatchExportProcessor.cs @@ -18,6 +18,7 @@ using System; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Threading; using OpenTelemetry.Internal; @@ -95,21 +96,9 @@ protected BatchExportProcessor( /// internal long ProcessedCount => this.circularBuffer.RemovedCount; - /// - /// Gets an initialization action to be called before each item is exported. - /// - protected Action? InitializeAction { get; init; } - - /// - /// Gets a cleanup action to be called after each item is exported. - /// - protected Action? CleanupAction { get; init; } - - /// - protected override void OnExport(T data) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryExport(T data) { - this.InitializeAction?.Invoke(data); - if (this.circularBuffer.TryAdd(data, maxSpinCount: 50000)) { if (this.circularBuffer.Count >= this.maxExportBatchSize) @@ -123,14 +112,19 @@ protected override void OnExport(T data) } } - return; // enqueue succeeded + return true; // enqueue succeeded } - // If item couldn't be added to batch we still call cleanup. - this.CleanupAction?.Invoke(data); - // either the queue is full or exceeded the spin limit, drop the item on the floor Interlocked.Increment(ref this.droppedCount); + + return false; + } + + /// + protected override void OnExport(T data) + { + this.TryExport(data); } /// @@ -265,8 +259,6 @@ protected override void Dispose(bool disposing) private void ExporterProc() { - var cleanupAction = this.CleanupAction; - var triggers = new WaitHandle[] { this.exportTrigger, this.shutdownTrigger }; while (true) @@ -287,7 +279,7 @@ private void ExporterProc() if (this.circularBuffer.Count > 0) { - using (var batch = new Batch(this.circularBuffer, this.maxExportBatchSize, cleanupAction)) + using (var batch = new Batch(this.circularBuffer, this.maxExportBatchSize)) { this.exporter.Export(batch); } diff --git a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs index d8a8bdbceec..c87b05ca740 100644 --- a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs +++ b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs @@ -47,8 +47,6 @@ public BatchLogRecordExportProcessor( exporterTimeoutMilliseconds, maxExportBatchSize) { - this.InitializeAction = LogRecordPool.TrackReference; - this.CleanupAction = LogRecordPool.Return; } /// @@ -61,7 +59,12 @@ public override void OnEnd(LogRecord data) data!.Buffer(); - base.OnEnd(data); + LogRecordPool.TrackReference(data); + + if (!this.TryExport(data)) + { + LogRecordPool.Return(data); + } } } } From 0d3453381a3b37952c7d62af6cc43061332a3e6b Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 2 Jun 2022 12:36:42 -0700 Subject: [PATCH 21/41] Code review, example app, serilog + eventsource extensions. --- OpenTelemetry.sln | 19 ++ .../Controllers/WeatherForecastController.cs | 22 +- .../LogEmitter/Examples.LogEmitter.csproj | 16 ++ examples/LogEmitter/Program.cs | 50 ++++ examples/LogEmitter/README.md | 11 + .../ConsoleLogRecordExporter.cs | 2 +- .../netstandard2.0/PublicAPI.Shipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 5 + .../netstandard2.1/PublicAPI.Shipped.txt | 1 + .../netstandard2.1/PublicAPI.Unshipped.txt | 5 + .../AssemblyInfo.cs | 35 +++ .../CHANGELOG.md | 5 + ...penTelemetry.Extensions.EventSource.csproj | 19 ++ .../OpenTelemetryEventSourceLogEmitter.cs | 218 ++++++++++++++++++ .../README.md | 30 +++ .../netstandard2.0/PublicAPI.Shipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 3 + .../AssemblyInfo.cs | 35 +++ .../CHANGELOG.md | 5 + .../OpenTelemetry.Extensions.Serilog.csproj | 23 ++ .../OpenTelemetrySerilogExtensions.cs | 45 ++++ .../OpenTelemetrySerilogSink.cs | 84 +++++++ .../README.md | 28 +++ .../.publicApi/net462/PublicAPI.Unshipped.txt | 22 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 22 +- src/OpenTelemetry/AssemblyInfo.cs | 2 + .../Internal/OpenTelemetrySdkEventSource.cs | 6 + src/OpenTelemetry/Logs/LogEmitter.cs | 8 +- .../Logs/LogRecordAttributeList.cs | 2 +- src/OpenTelemetry/Logs/OpenTelemetryLogger.cs | 4 +- .../Logs/OpenTelemetryLoggerProvider.cs | 55 ++++- .../Logs/OpenTelemetryLoggingExtensions.cs | 6 +- 32 files changed, 714 insertions(+), 76 deletions(-) create mode 100644 examples/LogEmitter/Examples.LogEmitter.csproj create mode 100644 examples/LogEmitter/Program.cs create mode 100644 examples/LogEmitter/README.md create mode 100644 src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Shipped.txt create mode 100644 src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt create mode 100644 src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Shipped.txt create mode 100644 src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt create mode 100644 src/OpenTelemetry.Extensions.EventSource/AssemblyInfo.cs create mode 100644 src/OpenTelemetry.Extensions.EventSource/CHANGELOG.md create mode 100644 src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj create mode 100644 src/OpenTelemetry.Extensions.EventSource/OpenTelemetryEventSourceLogEmitter.cs create mode 100644 src/OpenTelemetry.Extensions.EventSource/README.md create mode 100644 src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Shipped.txt create mode 100644 src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt create mode 100644 src/OpenTelemetry.Extensions.Serilog/AssemblyInfo.cs create mode 100644 src/OpenTelemetry.Extensions.Serilog/CHANGELOG.md create mode 100644 src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj create mode 100644 src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogExtensions.cs create mode 100644 src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogSink.cs create mode 100644 src/OpenTelemetry.Extensions.Serilog/README.md diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 3197a234d5e..f886632b310 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -237,6 +237,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "correlation", "docs\logs\co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Tests.Stress.Logs", "test\OpenTelemetry.Tests.Stress.Logs\OpenTelemetry.Tests.Stress.Logs.csproj", "{5FC0660F-3757-4594-806B-4375E06177A3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Serilog", "src\OpenTelemetry.Extensions.Serilog\OpenTelemetry.Extensions.Serilog.csproj", "{F5062ED1-6B59-45FC-8E08-2F5D41A19864}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.EventSource", "src\OpenTelemetry.Extensions.EventSource\OpenTelemetry.Extensions.EventSource.csproj", "{F1C65913-81EC-4297-A666-A66280E3E1B6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.LogEmitter", "examples\LogEmitter\Examples.LogEmitter.csproj", "{CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -499,6 +505,18 @@ Global {5FC0660F-3757-4594-806B-4375E06177A3}.Debug|Any CPU.Build.0 = Debug|Any CPU {5FC0660F-3757-4594-806B-4375E06177A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {5FC0660F-3757-4594-806B-4375E06177A3}.Release|Any CPU.Build.0 = Release|Any CPU + {F5062ED1-6B59-45FC-8E08-2F5D41A19864}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5062ED1-6B59-45FC-8E08-2F5D41A19864}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5062ED1-6B59-45FC-8E08-2F5D41A19864}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5062ED1-6B59-45FC-8E08-2F5D41A19864}.Release|Any CPU.Build.0 = Release|Any CPU + {F1C65913-81EC-4297-A666-A66280E3E1B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1C65913-81EC-4297-A666-A66280E3E1B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1C65913-81EC-4297-A666-A66280E3E1B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1C65913-81EC-4297-A666-A66280E3E1B6}.Release|Any CPU.Build.0 = Release|Any CPU + {CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -537,6 +555,7 @@ Global {41B784AA-3301-4126-AF9F-1D59BD04B0BF} = {3277B1C0-BDFE-4460-9B0D-D9A661FB48DB} {6C7A1595-36D6-4229-BBB5-5A6B5791791D} = {3862190B-E2C5-418E-AFDC-DB281FB5C705} {9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5} = {3862190B-E2C5-418E-AFDC-DB281FB5C705} + {CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521} diff --git a/examples/AspNetCore/Controllers/WeatherForecastController.cs b/examples/AspNetCore/Controllers/WeatherForecastController.cs index 76bbd0a1c4d..5f724a36412 100644 --- a/examples/AspNetCore/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore/Controllers/WeatherForecastController.cs @@ -16,9 +16,7 @@ namespace Examples.AspNetCore.Controllers; -using System.Diagnostics; using Microsoft.AspNetCore.Mvc; -using OpenTelemetry.Logs; [ApiController] [Route("[controller]")] @@ -32,14 +30,10 @@ public class WeatherForecastController : ControllerBase private static readonly HttpClient HttpClient = new(); private readonly ILogger logger; - private readonly LogEmitter logEmitter; - public WeatherForecastController( - ILogger logger, - LogEmitter logEmitter) + public WeatherForecastController(ILogger logger) { this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.logEmitter = logEmitter ?? throw new ArgumentNullException(nameof(logEmitter)); } [HttpGet] @@ -60,25 +54,11 @@ public IEnumerable Get() }) .ToArray(); - // Log using ILogger API. this.logger.LogInformation( "WeatherForecasts generated {count}: {forecasts}", forecast.Length, forecast); - // Log using LogEmitter API. - this.logEmitter.Log( - new LogRecordData(Activity.Current) - { - CategoryName = "WeatherForecasts", - LogLevel = LogLevel.Information, - Message = "WeatherForecasts generated.", - }, - new LogRecordAttributeList() - { - ["count"] = forecast.Length, - }); - return forecast; } } diff --git a/examples/LogEmitter/Examples.LogEmitter.csproj b/examples/LogEmitter/Examples.LogEmitter.csproj new file mode 100644 index 00000000000..a3746d731fb --- /dev/null +++ b/examples/LogEmitter/Examples.LogEmitter.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + diff --git a/examples/LogEmitter/Program.cs b/examples/LogEmitter/Program.cs new file mode 100644 index 00000000000..4c31938555b --- /dev/null +++ b/examples/LogEmitter/Program.cs @@ -0,0 +1,50 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics.Tracing; +using OpenTelemetry.Logs; +using OpenTelemetry.Resources; +using Serilog; + +var resourceBuilder = ResourceBuilder.CreateDefault().AddService("Examples.LogEmitter"); + +using var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => +{ + options.IncludeFormattedMessage = true; + options + .SetResourceBuilder(resourceBuilder) + .AddConsoleExporter(); +}); + +using var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name.StartsWith("OpenTelemetry") ? EventLevel.LogAlways : null); + +Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider) + .CreateLogger(); + +ILogger programLogger = Log.Logger.ForContext(); + +programLogger.Information("Application started {Greeting} {Location}", "Hello", "World"); + +programLogger.Information("Message {Array}", new string[] { "value1", "value2" }); + +openTelemetryLoggerProvider.ForceFlush(); + +Console.WriteLine("Press ENTER to exit..."); + +Console.ReadLine(); diff --git a/examples/LogEmitter/README.md b/examples/LogEmitter/README.md new file mode 100644 index 00000000000..da935d3df5f --- /dev/null +++ b/examples/LogEmitter/README.md @@ -0,0 +1,11 @@ +# OpenTelemetry LogEmitter Example + +This project contains examples of the `LogEmitter` API being used to extend +existing logging platforms to write into OpenTelemetry logs. + +* Serilog: Using OpenTelemetry.Extensions.Serilog +* EventSource: Using OpenTelemetry.Extensions.EventSource + +## References + +* [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs index 8181e031b02..8990ee4721a 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs @@ -80,7 +80,7 @@ public override ExportResult Export(in Batch batch) if (logRecord.EventId != default) { this.WriteLine($"{"LogRecord.EventId:",-RightPaddingLength}{logRecord.EventId.Id}"); - if (string.IsNullOrEmpty(logRecord.EventId.Name)) + if (!string.IsNullOrEmpty(logRecord.EventId.Name)) { this.WriteLine($"{"LogRecord.EventName:",-RightPaddingLength}{logRecord.EventId.Name}"); } diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..7dc5c58110b --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..16452ff842c --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,5 @@ +#nullable enable +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.OpenTelemetryEventSourceLogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, string! name, System.Diagnostics.Tracing.EventLevel logLevel = System.Diagnostics.Tracing.EventLevel.LogAlways) -> void +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.OpenTelemetryEventSourceLogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, System.Func! shouldListenToFunc) -> void +override OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.Dispose() -> void diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..7dc5c58110b --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..16452ff842c --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -0,0 +1,5 @@ +#nullable enable +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.OpenTelemetryEventSourceLogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, string! name, System.Diagnostics.Tracing.EventLevel logLevel = System.Diagnostics.Tracing.EventLevel.LogAlways) -> void +OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.OpenTelemetryEventSourceLogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, System.Func! shouldListenToFunc) -> void +override OpenTelemetry.Logs.OpenTelemetryEventSourceLogEmitter.Dispose() -> void diff --git a/src/OpenTelemetry.Extensions.EventSource/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.EventSource/AssemblyInfo.cs new file mode 100644 index 00000000000..a51a83d9d1d --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/AssemblyInfo.cs @@ -0,0 +1,35 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(false)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; + public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; + public const string MoqPublicKey = ""; +} +#endif diff --git a/src/OpenTelemetry.Extensions.EventSource/CHANGELOG.md b/src/OpenTelemetry.Extensions.EventSource/CHANGELOG.md new file mode 100644 index 00000000000..63bfc986bdc --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Unreleased + +Initial release. diff --git a/src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj b/src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj new file mode 100644 index 00000000000..8c31f6bc0b7 --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.1;netstandard2.0 + TODO + enable + AllEnabledByDefault + latest + + + + + + + + + + + diff --git a/src/OpenTelemetry.Extensions.EventSource/OpenTelemetryEventSourceLogEmitter.cs b/src/OpenTelemetry.Extensions.EventSource/OpenTelemetryEventSourceLogEmitter.cs new file mode 100644 index 00000000000..6259ae62d8e --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/OpenTelemetryEventSourceLogEmitter.cs @@ -0,0 +1,218 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Globalization; +using System.Linq; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Logs +{ + /// + /// Implements an which will convert events into OpenTelemetry logs. + /// + public sealed class OpenTelemetryEventSourceLogEmitter : EventListener + { + private readonly bool includeFormattedMessage; + private readonly LogEmitter logEmitter; + private readonly object lockObj = new(); + private readonly Func shouldListenToFunc; + private readonly List eventSources = new(); + private readonly List? eventSourcesBeforeConstructor = new(); + + /// + /// Initializes a new instance of the class. + /// + /// . + /// Name of the to listen to. + /// Event level to capture. + public OpenTelemetryEventSourceLogEmitter( + OpenTelemetryLoggerProvider openTelemetryLoggerProvider, + string name, + EventLevel logLevel = EventLevel.LogAlways) + : this(openTelemetryLoggerProvider, (n) => n == name ? logLevel : null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// . + /// Callback function used to decide if + /// events should be captured for a given . + /// Return if no events should be + /// captured. + public OpenTelemetryEventSourceLogEmitter( + OpenTelemetryLoggerProvider openTelemetryLoggerProvider, + Func shouldListenToFunc) + { + Guard.ThrowIfNull(openTelemetryLoggerProvider); + Guard.ThrowIfNull(shouldListenToFunc); + + this.includeFormattedMessage = openTelemetryLoggerProvider.IncludeFormattedMessage; + this.logEmitter = openTelemetryLoggerProvider.CreateEmitter(); + this.shouldListenToFunc = shouldListenToFunc; + + lock (this.lockObj) + { + foreach (EventSource eventSource in this.eventSourcesBeforeConstructor) + { + this.ProcessSource(eventSource); + } + + this.eventSourcesBeforeConstructor = null; + } + } + + /// + public override void Dispose() + { + foreach (EventSource eventSource in this.eventSources) + { + this.DisableEvents(eventSource); + } + + this.eventSources.Clear(); + + base.Dispose(); + } + +#pragma warning disable CA1062 // Validate arguments of public methods + /// + protected override void OnEventSourceCreated(EventSource eventSource) + { + Debug.Assert(eventSource != null, "EventSource was null."); + + try + { + if (this.eventSourcesBeforeConstructor != null) + { + lock (this.lockObj) + { + if (this.eventSourcesBeforeConstructor != null) + { + this.eventSourcesBeforeConstructor.Add(eventSource!); + return; + } + } + } + + this.ProcessSource(eventSource!); + } + finally + { + base.OnEventSourceCreated(eventSource); + } + } +#pragma warning restore CA1062 // Validate arguments of public methods + +#pragma warning disable CA1062 // Validate arguments of public methods + /// + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + Debug.Assert(eventData != null, "EventData was null."); + + string? rawMessage = eventData!.Message; + + LogRecordData data = new(Activity.Current) + { +#if NETSTANDARD2_1_OR_GREATER + Timestamp = eventData.TimeStamp, +#endif + EventId = new EventId(eventData.EventId, eventData.EventName), + LogLevel = ConvertEventLevelToLogLevel(eventData.Level), + }; + + LogRecordAttributeList attributes = default; + + attributes.Add("event_source.name", eventData.EventSource.Name); + + if (eventData.ActivityId != Guid.Empty) + { + attributes.Add("event_source.activity_id", eventData.ActivityId); + } + + if (eventData.RelatedActivityId != Guid.Empty) + { + attributes.Add("event_source.related_activity_id", eventData.RelatedActivityId); + } + + int payloadCount = eventData.Payload?.Count ?? 0; + + if (payloadCount > 0 && payloadCount == eventData.PayloadNames?.Count) + { + for (int i = 0; i < payloadCount; i++) + { + string name = eventData.PayloadNames[i]; + + if (!string.IsNullOrEmpty(rawMessage) && !this.includeFormattedMessage) + { + // TODO: This code converts the event message from + // string.Format syntax (eg: "Some message {0} {1}") + // into structured log format (eg: "Some message + // {propertyName1} {propertyName2}") but it is + // expensive. Probably needs a cahce. +#if NETSTANDARD2_0 + rawMessage = rawMessage.Replace($"{{{i}}}", $"{{{name}}}"); +#else + rawMessage = rawMessage.Replace($"{{{i}}}", $"{{{name}}}", StringComparison.Ordinal); +#endif + } + + attributes.Add(name, eventData.Payload![i]); + } + } + + if (!string.IsNullOrEmpty(rawMessage) && this.includeFormattedMessage && payloadCount > 0) + { + rawMessage = string.Format(CultureInfo.InvariantCulture, rawMessage, eventData.Payload!.ToArray()); + } + + data.Message = rawMessage; + + this.logEmitter.Log(in data, in attributes); + } +#pragma warning restore CA1062 // Validate arguments of public methods + + private static LogLevel ConvertEventLevelToLogLevel(EventLevel eventLevel) + { + return eventLevel switch + { + EventLevel.Informational => LogLevel.Information, + EventLevel.Warning => LogLevel.Warning, + EventLevel.Error => LogLevel.Error, + EventLevel.Critical => LogLevel.Critical, + _ => LogLevel.Trace, + }; + } + + private void ProcessSource(EventSource eventSource) + { + EventLevel? eventLevel = this.shouldListenToFunc(eventSource.Name); + + if (eventLevel.HasValue) + { + this.eventSources.Add(eventSource); + this.EnableEvents(eventSource, eventLevel.Value, EventKeywords.All); + } + } + } +} diff --git a/src/OpenTelemetry.Extensions.EventSource/README.md b/src/OpenTelemetry.Extensions.EventSource/README.md new file mode 100644 index 00000000000..7ceb64b668d --- /dev/null +++ b/src/OpenTelemetry.Extensions.EventSource/README.md @@ -0,0 +1,30 @@ +# OpenTelemetry.Extensions.EventSource + +This project contains an +[EventListener](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventlistener) +which can be used to translate events written to an +[EventSource](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventsource) +into to OpenTelemetry logs. + +## Usage Example + +```csharp +// Step 1: Configure OpenTelemetryLoggerProvider... +var resourceBuilder = ResourceBuilder.CreateDefault().AddService("MyService"); + +using var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => +{ + options + .SetResourceBuilder(resourceBuilder) + .AddConsoleExporter(); +}); + +// Step 2: Create OpenTelemetryEventSourceLogEmitter to listen to events... +using var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name.StartsWith("OpenTelemetry") ? EventLevel.LogAlways : null); +``` + +## References + +* [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..7dc5c58110b --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..9d643888bf5 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,3 @@ +#nullable enable +Serilog.OpenTelemetrySerilogExtensions +static Serilog.OpenTelemetrySerilogExtensions.OpenTelemetry(this Serilog.Configuration.LoggerSinkConfiguration! loggerConfiguration, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider) -> Serilog.LoggerConfiguration! diff --git a/src/OpenTelemetry.Extensions.Serilog/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.Serilog/AssemblyInfo.cs new file mode 100644 index 00000000000..a51a83d9d1d --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/AssemblyInfo.cs @@ -0,0 +1,35 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(false)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] + +#if SIGNED +internal static class AssemblyInfo +{ + public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; + public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; +} +#else +internal static class AssemblyInfo +{ + public const string PublicKey = ""; + public const string MoqPublicKey = ""; +} +#endif diff --git a/src/OpenTelemetry.Extensions.Serilog/CHANGELOG.md b/src/OpenTelemetry.Extensions.Serilog/CHANGELOG.md new file mode 100644 index 00000000000..63bfc986bdc --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Unreleased + +Initial release. diff --git a/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj new file mode 100644 index 00000000000..a94eda9a9f5 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0 + TODO + enable + AllEnabledByDefault + latest + + + + + + + + + + + + + + + diff --git a/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogExtensions.cs b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogExtensions.cs new file mode 100644 index 00000000000..70531ba71f5 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogExtensions.cs @@ -0,0 +1,45 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using OpenTelemetry.Internal; +using OpenTelemetry.Logs; +using Serilog.Configuration; + +namespace Serilog +{ + /// + /// Contains Serilog extension methods. + /// + public static class OpenTelemetrySerilogExtensions + { + /// + /// Adds a sink to Serilog which will + /// write to OpenTelemetry. + /// + /// . + /// . + /// Supplied for chaining calls. + public static LoggerConfiguration OpenTelemetry( + this LoggerSinkConfiguration loggerConfiguration, + OpenTelemetryLoggerProvider openTelemetryLoggerProvider) + { + Guard.ThrowIfNull(loggerConfiguration); + Guard.ThrowIfNull(openTelemetryLoggerProvider); + + return loggerConfiguration.Sink(new OpenTelemetrySerilogSink(openTelemetryLoggerProvider)); + } + } +} diff --git a/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogSink.cs b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogSink.cs new file mode 100644 index 00000000000..3b279ae39b9 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetrySerilogSink.cs @@ -0,0 +1,84 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Serilog.Core; +using Serilog.Events; + +namespace OpenTelemetry.Logs +{ + internal sealed class OpenTelemetrySerilogSink : ILogEventSink + { + private readonly bool includeFormattedMessage; + private readonly LogEmitter logEmitter; + + public OpenTelemetrySerilogSink(OpenTelemetryLoggerProvider openTelemetryLoggerProvider) + { + this.includeFormattedMessage = openTelemetryLoggerProvider.IncludeFormattedMessage; + this.logEmitter = openTelemetryLoggerProvider.CreateEmitter(); + } + + public void Emit(LogEvent logEvent) + { + Debug.Assert(logEvent != null, "LogEvent was null."); + + LogRecordData data = new(Activity.Current) + { + Timestamp = logEvent!.Timestamp.UtcDateTime, + LogLevel = (LogLevel)(int)logEvent.Level, + Message = this.includeFormattedMessage ? logEvent.RenderMessage() : logEvent.MessageTemplate.Text, + Exception = logEvent.Exception, + }; + + LogRecordAttributeList attributes = default; + foreach (KeyValuePair property in logEvent.Properties) + { + // TODO: Serilog supports complex type logging. This is not yet + // supported in OpenTelemetry. + if (property.Key == Constants.SourceContextPropertyName + && property.Value is ScalarValue sourceContextValue) + { + data.CategoryName = sourceContextValue.Value as string; + } + else if (property.Value is ScalarValue scalarValue) + { + attributes.Add(property.Key, scalarValue.Value); + } + else if (property.Value is SequenceValue sequenceValue) + { + IReadOnlyList elements = sequenceValue.Elements; + if (elements.Count > 0 && elements[0] is ScalarValue) + { + object[] values = new object[elements.Count]; + for (int i = 0; i < elements.Count; i++) + { + if (elements[i] is ScalarValue value) + { + values[i] = value.Value; + } + } + + attributes.Add(property.Key, values); + } + } + } + + this.logEmitter.Log(in data, in attributes); + } + } +} diff --git a/src/OpenTelemetry.Extensions.Serilog/README.md b/src/OpenTelemetry.Extensions.Serilog/README.md new file mode 100644 index 00000000000..7268fd0d3b0 --- /dev/null +++ b/src/OpenTelemetry.Extensions.Serilog/README.md @@ -0,0 +1,28 @@ +# OpenTelemetry.Extensions.Serilog + +This project contains a [Serilog](https://github.com/serilog/) +[sink](https://github.com/serilog/serilog/wiki/Configuration-Basics#sinks) for +writing log messages to OpenTelemetry. + +## Usage Example + +```csharp +// Step 1: Configure OpenTelemetryLoggerProvider... +var resourceBuilder = ResourceBuilder.CreateDefault().AddService("MyService"); + +using var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => +{ + options + .SetResourceBuilder(resourceBuilder) + .AddConsoleExporter(); +}); + +// Step 2: Register OpenTelemetry sink with Serilog... +Log.Logger = new LoggerConfiguration() + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider) + .CreateLogger(); +``` + +## References + +* [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index a59cccfea97..9abaa40c5b9 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -1,25 +1,8 @@ #nullable enable -OpenTelemetry.Logs.LogEmitter -OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes = default(OpenTelemetry.Logs.LogRecordAttributeList)) -> void -OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void -OpenTelemetry.Logs.LogRecordAttributeList -OpenTelemetry.Logs.LogRecordAttributeList.Add(string! key, object? value) -> void -OpenTelemetry.Logs.LogRecordAttributeList.Add(System.Collections.Generic.KeyValuePair attribute) -> void -OpenTelemetry.Logs.LogRecordAttributeList.Count.get -> int -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Dispose() -> void -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Enumerator() -> void -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.MoveNext() -> bool -OpenTelemetry.Logs.LogRecordAttributeList.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributeList.Enumerator -OpenTelemetry.Logs.LogRecordAttributeList.LogRecordAttributeList() -> void -OpenTelemetry.Logs.LogRecordAttributeList.this[int index].get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributeList.this[int index].set -> void -OpenTelemetry.Logs.LogRecordAttributeList.this[string! key].set -> void OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void @@ -44,6 +27,7 @@ OpenTelemetry.Logs.LogRecordData.TraceId.set -> void OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? OpenTelemetry.Logs.LogRecordData.TraceState.set -> void OpenTelemetry.Logs.LogRecordPool -OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! -static OpenTelemetry.Logs.LogRecordAttributeList.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributeList +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index a59cccfea97..9abaa40c5b9 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,25 +1,8 @@ #nullable enable -OpenTelemetry.Logs.LogEmitter -OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes = default(OpenTelemetry.Logs.LogRecordAttributeList)) -> void -OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecord.State.set -> void OpenTelemetry.Logs.LogRecord.StateValues.set -> void -OpenTelemetry.Logs.LogRecordAttributeList -OpenTelemetry.Logs.LogRecordAttributeList.Add(string! key, object? value) -> void -OpenTelemetry.Logs.LogRecordAttributeList.Add(System.Collections.Generic.KeyValuePair attribute) -> void -OpenTelemetry.Logs.LogRecordAttributeList.Count.get -> int -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Dispose() -> void -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.Enumerator() -> void -OpenTelemetry.Logs.LogRecordAttributeList.Enumerator.MoveNext() -> bool -OpenTelemetry.Logs.LogRecordAttributeList.GetEnumerator() -> OpenTelemetry.Logs.LogRecordAttributeList.Enumerator -OpenTelemetry.Logs.LogRecordAttributeList.LogRecordAttributeList() -> void -OpenTelemetry.Logs.LogRecordAttributeList.this[int index].get -> System.Collections.Generic.KeyValuePair -OpenTelemetry.Logs.LogRecordAttributeList.this[int index].set -> void -OpenTelemetry.Logs.LogRecordAttributeList.this[string! key].set -> void OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void @@ -44,6 +27,7 @@ OpenTelemetry.Logs.LogRecordData.TraceId.set -> void OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? OpenTelemetry.Logs.LogRecordData.TraceState.set -> void OpenTelemetry.Logs.LogRecordPool -OpenTelemetry.Logs.OpenTelemetryLoggerProvider.CreateEmitter() -> OpenTelemetry.Logs.LogEmitter! -static OpenTelemetry.Logs.LogRecordAttributeList.CreateFromEnumerable(System.Collections.Generic.IEnumerable>! attributes) -> OpenTelemetry.Logs.LogRecordAttributeList +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void diff --git a/src/OpenTelemetry/AssemblyInfo.cs b/src/OpenTelemetry/AssemblyInfo.cs index 545b35a13e8..71019d7c60e 100644 --- a/src/OpenTelemetry/AssemblyInfo.cs +++ b/src/OpenTelemetry/AssemblyInfo.cs @@ -21,5 +21,7 @@ [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.EventSource" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Serilog" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] [assembly: InternalsVisibleTo("Benchmarks" + AssemblyInfo.PublicKey)] diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index 0db72d4cba7..a25411cd082 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -390,6 +390,12 @@ public void UnsupportedAttributeType(string type, string key) this.WriteEvent(42, type.ToString(), key); } + [Event(43, Message = "ForceFlush invoked for provider '{0}' returned result '{1}'.", Level = EventLevel.Verbose)] + public void ForceFlushInvoked(string providerName, bool result) + { + this.WriteEvent(43, providerName, result); + } + #if DEBUG public class OpenTelemetryEventListener : EventListener { diff --git a/src/OpenTelemetry/Logs/LogEmitter.cs b/src/OpenTelemetry/Logs/LogEmitter.cs index 8fa353d9f3f..41f210ab5c0 100644 --- a/src/OpenTelemetry/Logs/LogEmitter.cs +++ b/src/OpenTelemetry/Logs/LogEmitter.cs @@ -26,15 +26,11 @@ namespace OpenTelemetry.Logs /// /// Spec reference: LogEmitter. /// - public sealed class LogEmitter + internal sealed class LogEmitter { private readonly OpenTelemetryLoggerProvider loggerProvider; - /// - /// Initializes a new instance of the class. - /// - /// . - public LogEmitter(OpenTelemetryLoggerProvider loggerProvider) + internal LogEmitter(OpenTelemetryLoggerProvider loggerProvider) { Guard.ThrowIfNull(loggerProvider); diff --git a/src/OpenTelemetry/Logs/LogRecordAttributeList.cs b/src/OpenTelemetry/Logs/LogRecordAttributeList.cs index f3f1020ce0c..23f896d92d7 100644 --- a/src/OpenTelemetry/Logs/LogRecordAttributeList.cs +++ b/src/OpenTelemetry/Logs/LogRecordAttributeList.cs @@ -28,7 +28,7 @@ namespace OpenTelemetry.Logs /// /// Stores attributes to be added to a log message. /// - public struct LogRecordAttributeList : IReadOnlyList> + internal struct LogRecordAttributeList : IReadOnlyList> { private const int OverflowAdditionalCapacity = 8; private KeyValuePair attribute1; diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index 28a73f662e8..b96ef721392 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -90,12 +90,14 @@ public bool IsEnabled(LogLevel logLevel) private IReadOnlyList> ParseState(LogRecord logRecord, TState state) { + /* TODO: Enable this if/when LogRecordAttributeList becomes public. if (state is LogRecordAttributeList logRecordAttributes) { logRecordAttributes.ApplyToLogRecord(logRecord); return logRecord.AttributeStorage!; } - else if (state is IReadOnlyList> stateList) + else*/ + if (state is IReadOnlyList> stateList) { return stateList; } diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs index 4a1927a314c..2c1c1287d4f 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs @@ -16,7 +16,9 @@ #nullable enable +using System; using System.Collections; +using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OpenTelemetry.Internal; @@ -31,7 +33,6 @@ namespace OpenTelemetry.Logs public class OpenTelemetryLoggerProvider : BaseProvider, ILoggerProvider, ISupportExternalScope { internal readonly bool IncludeScopes; - internal readonly bool IncludeFormattedMessage; internal readonly bool ParseStateValues; internal BaseProcessor? Processor; internal Resource Resource; @@ -50,7 +51,16 @@ static OpenTelemetryLoggerProvider() /// /// . public OpenTelemetryLoggerProvider(IOptionsMonitor options) - : this(options?.CurrentValue!) + : this(options?.CurrentValue ?? throw new ArgumentNullException(nameof(options))) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// configuration callback. + public OpenTelemetryLoggerProvider(Action configure) + : this(BuildOptions(configure)) { } @@ -70,6 +80,11 @@ internal OpenTelemetryLoggerProvider(OpenTelemetryLoggerOptions options) } } + /// + /// Gets a value indicating whether or not formatted messages should be included on log messages. + /// + public bool IncludeFormattedMessage { get; } + internal IExternalScopeProvider? ScopeProvider { get; private set; } /// @@ -112,11 +127,38 @@ public ILogger CreateLogger(string categoryName) return logger; } + /// + /// Flushes all the processors registered under , blocks the current thread + /// until flush completed, shutdown signaled or timed out. + /// + /// + /// The number (non-negative) of milliseconds to wait, or + /// Timeout.Infinite to wait indefinitely. + /// + /// + /// Returns true when force flush succeeded; otherwise, false. + /// + /// + /// Thrown when the timeoutMilliseconds is smaller than -1. + /// + /// + /// This function guarantees thread-safety. + /// + public bool ForceFlush(int timeoutMilliseconds = Timeout.Infinite) + { + bool result = this.Processor?.ForceFlush(timeoutMilliseconds) ?? true; + + OpenTelemetrySdkEventSource.Log.ForceFlushInvoked(nameof(OpenTelemetryLoggerProvider), result); + + return result; + } + /// /// Create a . /// /// . - public LogEmitter CreateEmitter() => new(this); + internal LogEmitter CreateEmitter() => new(this); internal OpenTelemetryLoggerProvider AddProcessor(BaseProcessor processor) { @@ -162,5 +204,12 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + + private static OpenTelemetryLoggerOptions BuildOptions(Action? configure = null) + { + OpenTelemetryLoggerOptions options = new(); + configure?.Invoke(options); + return options; + } } } diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs index 19f1fc04bc9..000542b2fbd 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs @@ -42,11 +42,7 @@ public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder, Act Guard.ThrowIfNull(builder); builder.AddConfiguration(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); - builder.Services.TryAddEnumerable( - ServiceDescriptor.Singleton( - sp => sp.GetRequiredService())); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); if (configure != null) { From bb4293cd245ab275a65227879ee393854ae9c20d Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 2 Jun 2022 12:52:48 -0700 Subject: [PATCH 22/41] Rename. --- OpenTelemetry.sln | 2 +- examples/LogEmitter/Examples.LogEmitter.csproj | 2 +- .../OpenTelemetry.Extensions.Serilog.csproj | 2 +- .../.publicApi/netstandard2.0/PublicAPI.Shipped.txt | 0 .../.publicApi/netstandard2.0/PublicAPI.Unshipped.txt | 0 .../.publicApi/netstandard2.1/PublicAPI.Shipped.txt | 0 .../.publicApi/netstandard2.1/PublicAPI.Unshipped.txt | 0 .../AssemblyInfo.cs | 0 .../CHANGELOG.md | 0 .../OpenTelemetry.Extensions.Tracing.csproj} | 2 +- .../OpenTelemetryEventSourceLogEmitter.cs | 0 .../README.md | 2 +- src/OpenTelemetry/AssemblyInfo.cs | 2 +- 13 files changed, 6 insertions(+), 6 deletions(-) rename src/{OpenTelemetry.Extensions.EventSource => OpenTelemetry.Extensions.Tracing}/.publicApi/netstandard2.0/PublicAPI.Shipped.txt (100%) rename src/{OpenTelemetry.Extensions.EventSource => OpenTelemetry.Extensions.Tracing}/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt (100%) rename src/{OpenTelemetry.Extensions.EventSource => OpenTelemetry.Extensions.Tracing}/.publicApi/netstandard2.1/PublicAPI.Shipped.txt (100%) rename src/{OpenTelemetry.Extensions.EventSource => OpenTelemetry.Extensions.Tracing}/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt (100%) rename src/{OpenTelemetry.Extensions.EventSource => OpenTelemetry.Extensions.Tracing}/AssemblyInfo.cs (100%) rename src/{OpenTelemetry.Extensions.EventSource => OpenTelemetry.Extensions.Tracing}/CHANGELOG.md (100%) rename src/{OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj => OpenTelemetry.Extensions.Tracing/OpenTelemetry.Extensions.Tracing.csproj} (86%) rename src/{OpenTelemetry.Extensions.EventSource => OpenTelemetry.Extensions.Tracing}/OpenTelemetryEventSourceLogEmitter.cs (100%) rename src/{OpenTelemetry.Extensions.EventSource => OpenTelemetry.Extensions.Tracing}/README.md (96%) diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index f886632b310..03d4384fb33 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -239,7 +239,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Tests.Stress. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Serilog", "src\OpenTelemetry.Extensions.Serilog\OpenTelemetry.Extensions.Serilog.csproj", "{F5062ED1-6B59-45FC-8E08-2F5D41A19864}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.EventSource", "src\OpenTelemetry.Extensions.EventSource\OpenTelemetry.Extensions.EventSource.csproj", "{F1C65913-81EC-4297-A666-A66280E3E1B6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Tracing", "src\OpenTelemetry.Extensions.Tracing\OpenTelemetry.Extensions.Tracing.csproj", "{F1C65913-81EC-4297-A666-A66280E3E1B6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.LogEmitter", "examples\LogEmitter\Examples.LogEmitter.csproj", "{CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}" EndProject diff --git a/examples/LogEmitter/Examples.LogEmitter.csproj b/examples/LogEmitter/Examples.LogEmitter.csproj index a3746d731fb..4042039e6b4 100644 --- a/examples/LogEmitter/Examples.LogEmitter.csproj +++ b/examples/LogEmitter/Examples.LogEmitter.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj index a94eda9a9f5..c25fe76e0db 100644 --- a/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj +++ b/src/OpenTelemetry.Extensions.Serilog/OpenTelemetry.Extensions.Serilog.csproj @@ -2,7 +2,7 @@ netstandard2.0 - TODO + Extensions to enable OpenTelemetry logging when using the Serilog library enable AllEnabledByDefault latest diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.Tracing/.publicApi/netstandard2.0/PublicAPI.Shipped.txt similarity index 100% rename from src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Shipped.txt rename to src/OpenTelemetry.Extensions.Tracing/.publicApi/netstandard2.0/PublicAPI.Shipped.txt diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.Tracing/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt rename to src/OpenTelemetry.Extensions.Tracing/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Shipped.txt b/src/OpenTelemetry.Extensions.Tracing/.publicApi/netstandard2.1/PublicAPI.Shipped.txt similarity index 100% rename from src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Shipped.txt rename to src/OpenTelemetry.Extensions.Tracing/.publicApi/netstandard2.1/PublicAPI.Shipped.txt diff --git a/src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Extensions.Tracing/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt similarity index 100% rename from src/OpenTelemetry.Extensions.EventSource/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt rename to src/OpenTelemetry.Extensions.Tracing/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt diff --git a/src/OpenTelemetry.Extensions.EventSource/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.Tracing/AssemblyInfo.cs similarity index 100% rename from src/OpenTelemetry.Extensions.EventSource/AssemblyInfo.cs rename to src/OpenTelemetry.Extensions.Tracing/AssemblyInfo.cs diff --git a/src/OpenTelemetry.Extensions.EventSource/CHANGELOG.md b/src/OpenTelemetry.Extensions.Tracing/CHANGELOG.md similarity index 100% rename from src/OpenTelemetry.Extensions.EventSource/CHANGELOG.md rename to src/OpenTelemetry.Extensions.Tracing/CHANGELOG.md diff --git a/src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj b/src/OpenTelemetry.Extensions.Tracing/OpenTelemetry.Extensions.Tracing.csproj similarity index 86% rename from src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj rename to src/OpenTelemetry.Extensions.Tracing/OpenTelemetry.Extensions.Tracing.csproj index 8c31f6bc0b7..eaa13a04b69 100644 --- a/src/OpenTelemetry.Extensions.EventSource/OpenTelemetry.Extensions.EventSource.csproj +++ b/src/OpenTelemetry.Extensions.Tracing/OpenTelemetry.Extensions.Tracing.csproj @@ -2,7 +2,7 @@ netstandard2.1;netstandard2.0 - TODO + Extensions for using OpenTelemetry with System.Diagnostics.Tracing enable AllEnabledByDefault latest diff --git a/src/OpenTelemetry.Extensions.EventSource/OpenTelemetryEventSourceLogEmitter.cs b/src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs similarity index 100% rename from src/OpenTelemetry.Extensions.EventSource/OpenTelemetryEventSourceLogEmitter.cs rename to src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs diff --git a/src/OpenTelemetry.Extensions.EventSource/README.md b/src/OpenTelemetry.Extensions.Tracing/README.md similarity index 96% rename from src/OpenTelemetry.Extensions.EventSource/README.md rename to src/OpenTelemetry.Extensions.Tracing/README.md index 7ceb64b668d..87bb38b70d6 100644 --- a/src/OpenTelemetry.Extensions.EventSource/README.md +++ b/src/OpenTelemetry.Extensions.Tracing/README.md @@ -1,4 +1,4 @@ -# OpenTelemetry.Extensions.EventSource +# OpenTelemetry.Extensions.Tracing This project contains an [EventListener](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventlistener) diff --git a/src/OpenTelemetry/AssemblyInfo.cs b/src/OpenTelemetry/AssemblyInfo.cs index 71019d7c60e..99d1bfa8ad4 100644 --- a/src/OpenTelemetry/AssemblyInfo.cs +++ b/src/OpenTelemetry/AssemblyInfo.cs @@ -21,7 +21,7 @@ [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.EventSource" + AssemblyInfo.PublicKey)] +[assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Tracing" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Serilog" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] [assembly: InternalsVisibleTo("Benchmarks" + AssemblyInfo.PublicKey)] From 380f139952b4d680ba66cc352ad8a655f56f4a04 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 2 Jun 2022 13:08:32 -0700 Subject: [PATCH 23/41] Typo. --- .../OpenTelemetryEventSourceLogEmitter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs b/src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs index 6259ae62d8e..319aa959b31 100644 --- a/src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs +++ b/src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs @@ -169,7 +169,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) // string.Format syntax (eg: "Some message {0} {1}") // into structured log format (eg: "Some message // {propertyName1} {propertyName2}") but it is - // expensive. Probably needs a cahce. + // expensive. Probably needs a cache. #if NETSTANDARD2_0 rawMessage = rawMessage.Replace($"{{{i}}}", $"{{{name}}}"); #else From 87597f8e24b869bcd6253a5f30aef57e374ae1dc Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 6 Jun 2022 12:07:49 -0700 Subject: [PATCH 24/41] New pool design + tests. --- .../.publicApi/net462/PublicAPI.Unshipped.txt | 2 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 2 +- src/OpenTelemetry/Logs/LogRecord.cs | 2 +- src/OpenTelemetry/Logs/LogRecordPool.cs | 201 +++++++------ .../Logs/LogRecordPoolTests.cs | 269 ++++++++++++++++++ 5 files changed, 369 insertions(+), 107 deletions(-) create mode 100644 test/OpenTelemetry.Tests/Logs/LogRecordPoolTests.cs diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 9abaa40c5b9..1db77d551ec 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -30,4 +30,4 @@ OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void -static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void +static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 9abaa40c5b9..1db77d551ec 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -30,4 +30,4 @@ OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void -static OpenTelemetry.Logs.LogRecordPool.Resize(int size) -> void +static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs index a15a327ef0b..f57ecc5d5e9 100644 --- a/src/OpenTelemetry/Logs/LogRecord.cs +++ b/src/OpenTelemetry/Logs/LogRecord.cs @@ -29,10 +29,10 @@ namespace OpenTelemetry.Logs /// public sealed class LogRecord { - internal int PoolReferences = int.MaxValue; internal LogRecordData Data; internal List>? AttributeStorage; internal List? BufferedScopes; + internal int PoolReferenceCount = int.MaxValue; private static readonly Action> AddScopeToBufferedList = (object? scope, List state) => { diff --git a/src/OpenTelemetry/Logs/LogRecordPool.cs b/src/OpenTelemetry/Logs/LogRecordPool.cs index b25c397bd1d..b96652706ec 100644 --- a/src/OpenTelemetry/Logs/LogRecordPool.cs +++ b/src/OpenTelemetry/Logs/LogRecordPool.cs @@ -16,7 +16,7 @@ #nullable enable -using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Threading; using OpenTelemetry.Internal; @@ -28,174 +28,167 @@ namespace OpenTelemetry.Logs /// public sealed class LogRecordPool { - private const int DefaultMaxPoolSize = 1024; + internal const int DefaultMaxPoolSize = 2048; + internal const int DefaultMaxNumberOfAttributes = 64; + internal const int DefaultMaxNumberOfScopes = 16; - private static LogRecordPool current = new(DefaultMaxPoolSize); + internal static LogRecordPool Current = new(DefaultMaxPoolSize); - [ThreadStatic] - private static LogRecord? threadStaticLogRecord; + internal readonly int Capacity; + private readonly LogRecord?[] pool; + private long rentIndex; + private long returnIndex; - private readonly int sharedPoolSize; - private readonly LogRecord?[] sharedPool; - private int sharedPoolCurrentIndex = -1; - - private LogRecordPool(int size) + internal LogRecordPool(int capacity) { - this.sharedPoolSize = size; - this.sharedPool = new LogRecord?[size]; + this.Capacity = capacity; + this.pool = new LogRecord?[capacity]; } + internal int Count => (int)(Interlocked.Read(ref this.returnIndex) - Interlocked.Read(ref this.rentIndex)); + /// /// Resize the pool. /// - /// The maximum number of s to store in the pool. - public static void Resize(int size) + /// The maximum number of s to store in the pool. + public static void Resize(int capacity) { - Guard.ThrowIfOutOfRange(size, min: 1); + Guard.ThrowIfOutOfRange(capacity, min: 1); - current = new LogRecordPool(size); + Current = new LogRecordPool(capacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static LogRecord Rent() => current.RentCore(); + internal static LogRecord Rent() => Current.RentCore(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void Return(LogRecord logRecord) => current.ReturnCore(logRecord); + internal static void Return(LogRecord logRecord) => Current.ReturnCore(logRecord); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void TrackReference(LogRecord logRecord) - => Interlocked.Increment(ref logRecord.PoolReferences); - - private LogRecord RentCore() { - LogRecord? logRecord = threadStaticLogRecord; + Interlocked.Increment(ref logRecord.PoolReferenceCount); + } - if (logRecord != null) + private void Clear(LogRecord logRecord) + { + var attributeStorage = logRecord.AttributeStorage; + if (attributeStorage != null) { - threadStaticLogRecord = null; - - logRecord.PoolReferences = 1; - - return logRecord; + if (attributeStorage.Count > DefaultMaxNumberOfAttributes) + { + // Don't allow the pool to grow unconstained. + logRecord.AttributeStorage = null; + } + else + { + /* List.Clear sets the size to 0 but it maintains the + underlying array. */ + attributeStorage.Clear(); + } } - return this.RentFromSharedPool(); + var bufferedScopes = logRecord.BufferedScopes; + if (bufferedScopes != null) + { + if (bufferedScopes.Count > DefaultMaxNumberOfScopes) + { + // Don't allow the pool to grow unconstained. + logRecord.BufferedScopes = null; + } + else + { + /* List.Clear sets the size to 0 but it maintains the + underlying array. */ + bufferedScopes.Clear(); + } + } } - private LogRecord RentFromSharedPool() + private LogRecord RentCore() { - LogRecord? logRecord; - - SpinWait wait = default; while (true) { - int sharedPoolIndex = this.sharedPoolCurrentIndex; - if (sharedPoolIndex < 0) + var rentSnapshot = Interlocked.Read(ref this.rentIndex); + var returnSnapshot = Interlocked.Read(ref this.returnIndex); + + if (rentSnapshot >= returnSnapshot) { - break; + break; // buffer is empty } - if (Interlocked.CompareExchange(ref this.sharedPoolCurrentIndex, sharedPoolIndex - 1, sharedPoolIndex) == sharedPoolIndex) + if (Interlocked.CompareExchange(ref this.rentIndex, rentSnapshot + 1, rentSnapshot) == rentSnapshot) { - while (true) + var logRecord = Interlocked.Exchange(ref this.pool[rentSnapshot % this.Capacity], null); + if (logRecord == null && !this.TryRentCoreRare(rentSnapshot, out logRecord)) { - logRecord = this.sharedPool[sharedPoolIndex]; - if (logRecord != null) - { - break; - } - - // If logRecord was null it means we raced with the return call, retry. - wait.SpinOnce(); + continue; } - this.sharedPool[sharedPoolIndex] = null; - - logRecord.PoolReferences = 1; - + logRecord.PoolReferenceCount = 1; return logRecord; } - - wait.SpinOnce(); } return new LogRecord() { - PoolReferences = 1, + PoolReferenceCount = 1, }; } - private void ReturnCore(LogRecord logRecord) - { - int poolReferences = Interlocked.Decrement(ref logRecord.PoolReferences); - if (poolReferences > 0) - { - return; - } - - this.Clear(logRecord); - - if (threadStaticLogRecord == null) - { - threadStaticLogRecord = logRecord; - return; - } - - this.ReturnToSharedPool(logRecord); - } - - private void ReturnToSharedPool(LogRecord logRecord) + private bool TryRentCoreRare(long rentSnapshot, [NotNullWhen(true)] out LogRecord? logRecord) { SpinWait wait = default; while (true) { - int sharedPoolIndex = this.sharedPoolCurrentIndex; - if (sharedPoolIndex >= this.sharedPoolSize) + if (wait.NextSpinWillYield) { - return; + // 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; } - if (Interlocked.CompareExchange(ref this.sharedPoolCurrentIndex, sharedPoolIndex + 1, sharedPoolIndex) == sharedPoolIndex) + wait.SpinOnce(); + + logRecord = Interlocked.Exchange(ref this.pool[rentSnapshot % this.Capacity], null); + if (logRecord != null) { - this.sharedPool[sharedPoolIndex + 1] = logRecord; - break; + // Rare case where the write was still working when the read came in + return true; } - - wait.SpinOnce(); } } - private void Clear(LogRecord logRecord) + private void ReturnCore(LogRecord logRecord) { - var attributeStorage = logRecord.AttributeStorage; - if (attributeStorage != null) + if (Interlocked.Decrement(ref logRecord.PoolReferenceCount) != 0) { - if (attributeStorage.Count > 64) - { - // Don't allow the pool to grow unconstained. - logRecord.AttributeStorage = null; - } - else - { - /* List.Clear sets the size to 0 but it maintains the - underlying array. */ - attributeStorage.Clear(); - } + return; } - var bufferedScopes = logRecord.BufferedScopes; - if (bufferedScopes != null) + this.Clear(logRecord); + + while (true) { - if (bufferedScopes.Count > 16) + var rentSnapshot = Interlocked.Read(ref this.rentIndex); + var returnSnapshot = Interlocked.Read(ref this.returnIndex); + + if (returnSnapshot - rentSnapshot >= this.Capacity) { - // Don't allow the pool to grow unconstained. - logRecord.BufferedScopes = null; + return; // buffer is full } - else + + if (Interlocked.CompareExchange(ref this.returnIndex, returnSnapshot + 1, returnSnapshot) == returnSnapshot) { - /* List.Clear sets the size to 0 but it maintains the - underlying array. */ - bufferedScopes.Clear(); + this.pool[returnSnapshot % this.Capacity] = logRecord; + return; } } } diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordPoolTests.cs new file mode 100644 index 00000000000..80480ac1b29 --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/LogRecordPoolTests.cs @@ -0,0 +1,269 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using OpenTelemetry.Logs; +using Xunit; + +namespace OpenTelemetry.Tests.Logs +{ + public sealed class LogRecordPoolTests + { + [Fact] + public void ResizeTests() + { + LogRecordPool.Resize(LogRecordPool.DefaultMaxPoolSize); + Assert.NotNull(LogRecordPool.Current); + Assert.Equal(LogRecordPool.DefaultMaxPoolSize, LogRecordPool.Current.Capacity); + + Assert.Throws(() => LogRecordPool.Resize(0)); + + var beforePool = LogRecordPool.Current; + + LogRecordPool.Resize(1); + + Assert.NotNull(LogRecordPool.Current); + Assert.Equal(1, LogRecordPool.Current.Capacity); + Assert.NotEqual(beforePool, LogRecordPool.Current); + } + + [Fact] + public void RentReturnTests() + { + LogRecordPool.Resize(2); + + var logRecord1 = LogRecordPool.Rent(); + Assert.NotNull(logRecord1); + + var logRecord2 = LogRecordPool.Rent(); + Assert.NotNull(logRecord1); + + LogRecordPool.Return(logRecord1); + + Assert.Equal(1, LogRecordPool.Current.Count); + + // Note: This is ignored because logRecord manually created has PoolReferenceCount = int.MaxValue. + LogRecord manualRecord = new(); + Assert.Equal(int.MaxValue, manualRecord.PoolReferenceCount); + LogRecordPool.Return(manualRecord); + + Assert.Equal(1, LogRecordPool.Current.Count); + + LogRecordPool.Return(logRecord2); + + Assert.Equal(2, LogRecordPool.Current.Count); + + logRecord1 = LogRecordPool.Rent(); + Assert.NotNull(logRecord1); + Assert.Equal(1, LogRecordPool.Current.Count); + + logRecord2 = LogRecordPool.Rent(); + Assert.NotNull(logRecord2); + Assert.Equal(0, LogRecordPool.Current.Count); + + var logRecord3 = LogRecordPool.Rent(); + var logRecord4 = LogRecordPool.Rent(); + Assert.NotNull(logRecord3); + Assert.NotNull(logRecord4); + + LogRecordPool.Return(logRecord1); + LogRecordPool.Return(logRecord2); + LogRecordPool.Return(logRecord3); + LogRecordPool.Return(logRecord4); // <- Discarded due to pool size of 2 + + Assert.Equal(2, LogRecordPool.Current.Count); + } + + [Fact] + public void TrackReferenceTests() + { + LogRecordPool.Resize(2); + + var logRecord1 = LogRecordPool.Rent(); + Assert.NotNull(logRecord1); + + Assert.Equal(1, logRecord1.PoolReferenceCount); + + LogRecordPool.TrackReference(logRecord1); + + Assert.Equal(2, logRecord1.PoolReferenceCount); + + LogRecordPool.Return(logRecord1); + + Assert.Equal(1, logRecord1.PoolReferenceCount); + + LogRecordPool.Return(logRecord1); + + Assert.Equal(1, LogRecordPool.Current.Count); + Assert.Equal(0, logRecord1.PoolReferenceCount); + + LogRecordPool.Return(logRecord1); + + Assert.Equal(-1, logRecord1.PoolReferenceCount); + Assert.Equal(1, LogRecordPool.Current.Count); // Record was not returned because PoolReferences was negative. + } + + [Fact] + public void ClearTests() + { + LogRecordPool.Resize(LogRecordPool.DefaultMaxPoolSize); + + var logRecord1 = LogRecordPool.Rent(); + logRecord1.AttributeStorage = new List>(16) + { + new KeyValuePair("key1", "value1"), + new KeyValuePair("key2", "value2"), + }; + logRecord1.BufferedScopes = new List(8) { null, null }; + + LogRecordPool.Return(logRecord1); + + Assert.Empty(logRecord1.AttributeStorage); + Assert.Equal(16, logRecord1.AttributeStorage.Capacity); + Assert.Empty(logRecord1.BufferedScopes); + Assert.Equal(8, logRecord1.BufferedScopes.Capacity); + + logRecord1 = LogRecordPool.Rent(); + + Assert.NotNull(logRecord1.AttributeStorage); + Assert.NotNull(logRecord1.BufferedScopes); + + for (int i = 0; i <= LogRecordPool.DefaultMaxNumberOfAttributes; i++) + { + logRecord1.AttributeStorage!.Add(new KeyValuePair("key", "value")); + } + + for (int i = 0; i <= LogRecordPool.DefaultMaxNumberOfScopes; i++) + { + logRecord1.BufferedScopes!.Add(null); + } + + LogRecordPool.Return(logRecord1); + + Assert.Null(logRecord1.AttributeStorage); + Assert.Null(logRecord1.BufferedScopes); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ExportTest(bool warmup) + { + LogRecordPool.Resize(LogRecordPool.DefaultMaxPoolSize); + + if (warmup) + { + for (int i = 0; i < LogRecordPool.DefaultMaxPoolSize; i++) + { + LogRecordPool.Return(new LogRecord { PoolReferenceCount = 1 }); + } + } + + using BatchLogRecordExportProcessor processor = new(new NoopExporter()); + + List tasks = new(); + + for (int i = 0; i < Environment.ProcessorCount; i++) + { + tasks.Add(Task.Run(async () => + { + Random random = new Random(); + + await Task.Delay(random.Next(100, 150)).ConfigureAwait(false); + + for (int i = 0; i < 1000; i++) + { + var logRecord = LogRecordPool.Rent(); + + processor.OnEnd(logRecord); + + // This should no-op mostly. + LogRecordPool.Return(logRecord); + + await Task.Delay(random.Next(0, 20)).ConfigureAwait(false); + } + })); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + processor.ForceFlush(); + + if (warmup) + { + Assert.Equal(LogRecordPool.DefaultMaxPoolSize, LogRecordPool.Current.Count); + } + + Assert.True(LogRecordPool.Current.Count <= LogRecordPool.DefaultMaxPoolSize); + } + + [Fact] + public async Task DeadlockTest() + { + /* + * The way the LogRecordPool works is it maintains two counters one + * for readers and one for writers. The counters always increment + * and point to an index in the pool array by way of a modulus on + * the size of the array (index = counter % capacity). Under very + * heavy load it is possible for a reader to receive an index and + * then be yielded. When waking up that index may no longer be valid + * if other threads caused the counters to loop around. There is + * protection for this case in the pool, this test verifies it is + * working. + * + * This is considered a corner case. Many threads have to be renting + * & returning logs in a tight loop for this to happen. Real + * applications should be logging based on logic firing which should + * have more natural back-off time. + */ + + LogRecordPool.Resize(LogRecordPool.DefaultMaxPoolSize); + + List tasks = new(); + + for (int i = 0; i < Environment.ProcessorCount; i++) + { + tasks.Add(Task.Run(async () => + { + await Task.Delay(2000).ConfigureAwait(false); + + for (int i = 0; i < 100_000; i++) + { + var logRecord = LogRecordPool.Rent(); + + LogRecordPool.Return(logRecord); + } + })); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + Assert.True(LogRecordPool.Current.Count <= LogRecordPool.DefaultMaxPoolSize); + } + + private sealed class NoopExporter : BaseExporter + { + public override ExportResult Export(in Batch batch) + { + return ExportResult.Success; + } + } + } +} From 345e1a2c97e71abbf4ccadb604b15ac9a7b6d50a Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 6 Jun 2022 14:41:47 -0700 Subject: [PATCH 25/41] Pool selection based on processor. --- src/OpenTelemetry/Batch.cs | 6 +- src/OpenTelemetry/CompositeProcessor.cs | 18 +-- .../Logs/BatchLogRecordExportProcessor.cs | 4 +- src/OpenTelemetry/Logs/LogEmitter.cs | 6 +- src/OpenTelemetry/Logs/OpenTelemetryLogger.cs | 6 +- .../Logs/OpenTelemetryLoggerProvider.cs | 31 ++++ src/OpenTelemetry/Logs/Pool/ILogRecordPool.cs | 27 ++++ src/OpenTelemetry/Logs/Pool/LogRecordPool.cs | 39 +++++ .../LogRecordSharedPool.cs} | 137 ++++++++---------- .../Logs/Pool/LogRecordThreadStaticPool.cs | 55 +++++++ ...olTests.cs => LogRecordSharedPoolTests.cs} | 119 ++++++++------- .../Logs/LogRecordThreadStaticPoolTests.cs | 90 ++++++++++++ .../Logs/OpenTelemetryLoggerProviderTests.cs | 85 +++++++++++ 13 files changed, 471 insertions(+), 152 deletions(-) create mode 100644 src/OpenTelemetry/Logs/Pool/ILogRecordPool.cs create mode 100644 src/OpenTelemetry/Logs/Pool/LogRecordPool.cs rename src/OpenTelemetry/Logs/{LogRecordPool.cs => Pool/LogRecordSharedPool.cs} (82%) create mode 100644 src/OpenTelemetry/Logs/Pool/LogRecordThreadStaticPool.cs rename test/OpenTelemetry.Tests/Logs/{LogRecordPoolTests.cs => LogRecordSharedPoolTests.cs} (64%) create mode 100644 test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs create mode 100644 test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs diff --git a/src/OpenTelemetry/Batch.cs b/src/OpenTelemetry/Batch.cs index 2abd128152d..ea8576f9287 100644 --- a/src/OpenTelemetry/Batch.cs +++ b/src/OpenTelemetry/Batch.cs @@ -94,7 +94,7 @@ public void Dispose() T item = this.circularBuffer.Read(); if (typeof(T) == typeof(LogRecord)) { - LogRecordPool.Return((LogRecord)(object)item); + LogRecordSharedPool.Current.Return((LogRecord)(object)item); } } } @@ -154,7 +154,7 @@ public struct Enumerator : IEnumerator { if (typeof(T) == typeof(LogRecord)) { - LogRecordPool.Return((LogRecord)(object)currentItem); + LogRecordSharedPool.Current.Return((LogRecord)(object)currentItem); } } @@ -234,7 +234,7 @@ public void Dispose() var currentItem = this.current; if (currentItem != null) { - LogRecordPool.Return((LogRecord)(object)currentItem); + LogRecordSharedPool.Current.Return((LogRecord)(object)currentItem); this.current = null; } } diff --git a/src/OpenTelemetry/CompositeProcessor.cs b/src/OpenTelemetry/CompositeProcessor.cs index 6adae3ade09..223387c80a1 100644 --- a/src/OpenTelemetry/CompositeProcessor.cs +++ b/src/OpenTelemetry/CompositeProcessor.cs @@ -26,7 +26,7 @@ namespace OpenTelemetry { public class CompositeProcessor : BaseProcessor { - private readonly DoublyLinkedListNode head; + internal readonly DoublyLinkedListNode Head; private DoublyLinkedListNode tail; private bool disposed; @@ -40,8 +40,8 @@ public CompositeProcessor(IEnumerable> processors) throw new ArgumentException($"'{iter}' is null or empty", nameof(iter)); } - this.head = new DoublyLinkedListNode(iter.Current); - this.tail = this.head; + this.Head = new DoublyLinkedListNode(iter.Current); + this.tail = this.Head; while (iter.MoveNext()) { @@ -66,7 +66,7 @@ public CompositeProcessor AddProcessor(BaseProcessor processor) /// public override void OnEnd(T data) { - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { cur.Value.OnEnd(data); } @@ -75,7 +75,7 @@ public override void OnEnd(T data) /// public override void OnStart(T data) { - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { cur.Value.OnStart(data); } @@ -89,7 +89,7 @@ protected override bool OnForceFlush(int timeoutMilliseconds) ? null : Stopwatch.StartNew(); - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { if (sw == null) { @@ -115,7 +115,7 @@ protected override bool OnShutdown(int timeoutMilliseconds) ? null : Stopwatch.StartNew(); - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { if (sw == null) { @@ -140,7 +140,7 @@ protected override void Dispose(bool disposing) { if (disposing) { - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { try { @@ -159,7 +159,7 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - private sealed class DoublyLinkedListNode + internal sealed class DoublyLinkedListNode { public readonly BaseProcessor Value; diff --git a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs index c87b05ca740..4b77537dd2c 100644 --- a/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs +++ b/src/OpenTelemetry/Logs/BatchLogRecordExportProcessor.cs @@ -59,11 +59,11 @@ public override void OnEnd(LogRecord data) data!.Buffer(); - LogRecordPool.TrackReference(data); + LogRecordSharedPool.Current.TrackReference(data); if (!this.TryExport(data)) { - LogRecordPool.Return(data); + LogRecordSharedPool.Current.Return(data); } } } diff --git a/src/OpenTelemetry/Logs/LogEmitter.cs b/src/OpenTelemetry/Logs/LogEmitter.cs index 41f210ab5c0..0df8fa14652 100644 --- a/src/OpenTelemetry/Logs/LogEmitter.cs +++ b/src/OpenTelemetry/Logs/LogEmitter.cs @@ -48,7 +48,9 @@ public void Log(in LogRecordData data, in LogRecordAttributeList attributes = de var processor = provider.Processor; if (processor != null) { - var logRecord = LogRecordPool.Rent(); + var pool = provider.LogRecordPool; + + var logRecord = pool.Rent(); logRecord.Data = data; @@ -65,7 +67,7 @@ public void Log(in LogRecordData data, in LogRecordAttributeList attributes = de // Attempt to return the LogRecord to the pool. This will no-op // if a batch exporter has added a reference. - LogRecordPool.Return(logRecord); + pool.Return(logRecord); } } } diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs index b96ef721392..21fd08062b3 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLogger.cs @@ -53,7 +53,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except var processor = provider.Processor; if (processor != null) { - var record = LogRecordPool.Rent(); + var pool = provider.LogRecordPool; + + var record = pool.Rent(); record.ScopeProvider = provider.IncludeScopes ? this.ScopeProvider : null; record.State = provider.ParseStateValues ? null : state; @@ -76,7 +78,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except // Attempt to return the LogRecord to the pool. This will no-op // if a batch exporter has added a reference. - LogRecordPool.Return(record); + pool.Return(record); } } diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs index 2c1c1287d4f..031ca317883 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggerProvider.cs @@ -37,6 +37,7 @@ public class OpenTelemetryLoggerProvider : BaseProvider, ILoggerProvider, ISuppo internal BaseProcessor? Processor; internal Resource Resource; private readonly Hashtable loggers = new(); + private ILogRecordPool? threadStaticPool = LogRecordThreadStaticPool.Instance; private bool disposed; static OpenTelemetryLoggerProvider() @@ -87,6 +88,8 @@ internal OpenTelemetryLoggerProvider(OpenTelemetryLoggerOptions options) internal IExternalScopeProvider? ScopeProvider { get; private set; } + internal ILogRecordPool LogRecordPool => this.threadStaticPool ?? LogRecordSharedPool.Current; + /// void ISupportExternalScope.SetScopeProvider(IExternalScopeProvider scopeProvider) { @@ -166,6 +169,11 @@ internal OpenTelemetryLoggerProvider AddProcessor(BaseProcessor proce processor.SetParentProvider(this); + if (this.threadStaticPool != null && this.ContainsBatchProcessor(processor)) + { + this.threadStaticPool = null; + } + if (this.Processor == null) { this.Processor = processor; @@ -186,6 +194,29 @@ internal OpenTelemetryLoggerProvider AddProcessor(BaseProcessor proce return this; } + internal bool ContainsBatchProcessor(BaseProcessor processor) + { + if (processor is BatchExportProcessor) + { + return true; + } + else if (processor is CompositeProcessor compositeProcessor) + { + var current = compositeProcessor.Head; + while (current != null) + { + if (this.ContainsBatchProcessor(current.Value)) + { + return true; + } + + current = current.Next; + } + } + + return false; + } + /// protected override void Dispose(bool disposing) { diff --git a/src/OpenTelemetry/Logs/Pool/ILogRecordPool.cs b/src/OpenTelemetry/Logs/Pool/ILogRecordPool.cs new file mode 100644 index 00000000000..14d12cc6e36 --- /dev/null +++ b/src/OpenTelemetry/Logs/Pool/ILogRecordPool.cs @@ -0,0 +1,27 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +namespace OpenTelemetry.Logs +{ + internal interface ILogRecordPool + { + LogRecord Rent(); + + void Return(LogRecord logRecord); + } +} diff --git a/src/OpenTelemetry/Logs/Pool/LogRecordPool.cs b/src/OpenTelemetry/Logs/Pool/LogRecordPool.cs new file mode 100644 index 00000000000..c6a337b3b0a --- /dev/null +++ b/src/OpenTelemetry/Logs/Pool/LogRecordPool.cs @@ -0,0 +1,39 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Logs +{ + /// + /// Manages a pool of instances. + /// + public static class LogRecordPool + { + /// + /// Resize the pool. + /// + /// The maximum number of s to store in the pool. + public static void Resize(int capacity) + { + Guard.ThrowIfOutOfRange(capacity, min: 1); + + LogRecordSharedPool.Current = new(capacity); + } + } +} diff --git a/src/OpenTelemetry/Logs/LogRecordPool.cs b/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs similarity index 82% rename from src/OpenTelemetry/Logs/LogRecordPool.cs rename to src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs index b96652706ec..fe8f566e405 100644 --- a/src/OpenTelemetry/Logs/LogRecordPool.cs +++ b/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,27 +19,23 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Threading; -using OpenTelemetry.Internal; namespace OpenTelemetry.Logs { - /// - /// Manages a pool of instances. - /// - public sealed class LogRecordPool + internal sealed class LogRecordSharedPool : ILogRecordPool { internal const int DefaultMaxPoolSize = 2048; internal const int DefaultMaxNumberOfAttributes = 64; internal const int DefaultMaxNumberOfScopes = 16; - internal static LogRecordPool Current = new(DefaultMaxPoolSize); + internal static LogRecordSharedPool Current = new(DefaultMaxPoolSize); internal readonly int Capacity; private readonly LogRecord?[] pool; private long rentIndex; private long returnIndex; - internal LogRecordPool(int capacity) + internal LogRecordSharedPool(int capacity) { this.Capacity = capacity; this.pool = new LogRecord?[capacity]; @@ -47,30 +43,71 @@ internal LogRecordPool(int capacity) internal int Count => (int)(Interlocked.Read(ref this.returnIndex) - Interlocked.Read(ref this.rentIndex)); - /// - /// Resize the pool. - /// - /// The maximum number of s to store in the pool. - public static void Resize(int capacity) + public LogRecord Rent() { - Guard.ThrowIfOutOfRange(capacity, min: 1); + while (true) + { + var rentSnapshot = Interlocked.Read(ref this.rentIndex); + var returnSnapshot = Interlocked.Read(ref this.returnIndex); + + if (rentSnapshot >= returnSnapshot) + { + break; // buffer is empty + } - Current = new LogRecordPool(capacity); + 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.PoolReferenceCount = 1; + return logRecord; + } + } + + return new LogRecord() + { + PoolReferenceCount = 1, + }; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static LogRecord Rent() => Current.RentCore(); + public void Return(LogRecord logRecord) + { + if (Interlocked.Decrement(ref logRecord.PoolReferenceCount) != 0) + { + return; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void Return(LogRecord logRecord) => Current.ReturnCore(logRecord); + Clear(logRecord); + + while (true) + { + var rentSnapshot = Interlocked.Read(ref this.rentIndex); + var returnSnapshot = Interlocked.Read(ref this.returnIndex); + + if (returnSnapshot - rentSnapshot >= this.Capacity) + { + return; // buffer is full + } + + if (Interlocked.CompareExchange(ref this.returnIndex, returnSnapshot + 1, returnSnapshot) == returnSnapshot) + { + this.pool[returnSnapshot % this.Capacity] = logRecord; + return; + } + } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void TrackReference(LogRecord logRecord) + public void TrackReference(LogRecord logRecord) { Interlocked.Increment(ref logRecord.PoolReferenceCount); } - private void Clear(LogRecord logRecord) + internal static void Clear(LogRecord logRecord) { var attributeStorage = logRecord.AttributeStorage; if (attributeStorage != null) @@ -105,37 +142,6 @@ underlying array. */ } } - private LogRecord RentCore() - { - while (true) - { - var rentSnapshot = Interlocked.Read(ref this.rentIndex); - var returnSnapshot = Interlocked.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.PoolReferenceCount = 1; - return logRecord; - } - } - - return new LogRecord() - { - PoolReferenceCount = 1, - }; - } - private bool TryRentCoreRare(long rentSnapshot, [NotNullWhen(true)] out LogRecord? logRecord) { SpinWait wait = default; @@ -165,32 +171,5 @@ private bool TryRentCoreRare(long rentSnapshot, [NotNullWhen(true)] out LogRecor } } } - - private void ReturnCore(LogRecord logRecord) - { - if (Interlocked.Decrement(ref logRecord.PoolReferenceCount) != 0) - { - return; - } - - this.Clear(logRecord); - - while (true) - { - var rentSnapshot = Interlocked.Read(ref this.rentIndex); - var returnSnapshot = Interlocked.Read(ref this.returnIndex); - - if (returnSnapshot - rentSnapshot >= this.Capacity) - { - return; // buffer is full - } - - if (Interlocked.CompareExchange(ref this.returnIndex, returnSnapshot + 1, returnSnapshot) == returnSnapshot) - { - this.pool[returnSnapshot % this.Capacity] = logRecord; - return; - } - } - } } } diff --git a/src/OpenTelemetry/Logs/Pool/LogRecordThreadStaticPool.cs b/src/OpenTelemetry/Logs/Pool/LogRecordThreadStaticPool.cs new file mode 100644 index 00000000000..90a2c3759ae --- /dev/null +++ b/src/OpenTelemetry/Logs/Pool/LogRecordThreadStaticPool.cs @@ -0,0 +1,55 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using System; + +namespace OpenTelemetry.Logs +{ + internal sealed class LogRecordThreadStaticPool : ILogRecordPool + { + [ThreadStatic] + internal static LogRecord? Storage; + + private LogRecordThreadStaticPool() + { + } + + public static LogRecordThreadStaticPool Instance { get; } = new(); + + public LogRecord Rent() + { + var logRecord = Storage; + if (logRecord != null) + { + Storage = null; + return logRecord; + } + + return new(); + } + + public void Return(LogRecord logRecord) + { + if (Storage == null) + { + LogRecordSharedPool.Clear(logRecord); + Storage = logRecord; + } + } + } +} diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs similarity index 64% rename from test/OpenTelemetry.Tests/Logs/LogRecordPoolTests.cs rename to test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs index 80480ac1b29..8e686870587 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordPoolTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,29 +19,28 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using OpenTelemetry.Logs; using Xunit; -namespace OpenTelemetry.Tests.Logs +namespace OpenTelemetry.Logs.Tests { - public sealed class LogRecordPoolTests + public sealed class LogRecordSharedPoolTests { [Fact] public void ResizeTests() { - LogRecordPool.Resize(LogRecordPool.DefaultMaxPoolSize); - Assert.NotNull(LogRecordPool.Current); - Assert.Equal(LogRecordPool.DefaultMaxPoolSize, LogRecordPool.Current.Capacity); + LogRecordPool.Resize(LogRecordSharedPool.DefaultMaxPoolSize); + Assert.NotNull(LogRecordSharedPool.Current); + Assert.Equal(LogRecordSharedPool.DefaultMaxPoolSize, LogRecordSharedPool.Current.Capacity); Assert.Throws(() => LogRecordPool.Resize(0)); - var beforePool = LogRecordPool.Current; + var beforePool = LogRecordSharedPool.Current; LogRecordPool.Resize(1); - Assert.NotNull(LogRecordPool.Current); - Assert.Equal(1, LogRecordPool.Current.Capacity); - Assert.NotEqual(beforePool, LogRecordPool.Current); + Assert.NotNull(LogRecordSharedPool.Current); + Assert.Equal(1, LogRecordSharedPool.Current.Capacity); + Assert.NotEqual(beforePool, LogRecordSharedPool.Current); } [Fact] @@ -49,46 +48,48 @@ public void RentReturnTests() { LogRecordPool.Resize(2); - var logRecord1 = LogRecordPool.Rent(); + var pool = LogRecordSharedPool.Current; + + var logRecord1 = pool.Rent(); Assert.NotNull(logRecord1); - var logRecord2 = LogRecordPool.Rent(); + var logRecord2 = pool.Rent(); Assert.NotNull(logRecord1); - LogRecordPool.Return(logRecord1); + pool.Return(logRecord1); - Assert.Equal(1, LogRecordPool.Current.Count); + Assert.Equal(1, pool.Count); // Note: This is ignored because logRecord manually created has PoolReferenceCount = int.MaxValue. LogRecord manualRecord = new(); Assert.Equal(int.MaxValue, manualRecord.PoolReferenceCount); - LogRecordPool.Return(manualRecord); + pool.Return(manualRecord); - Assert.Equal(1, LogRecordPool.Current.Count); + Assert.Equal(1, pool.Count); - LogRecordPool.Return(logRecord2); + pool.Return(logRecord2); - Assert.Equal(2, LogRecordPool.Current.Count); + Assert.Equal(2, pool.Count); - logRecord1 = LogRecordPool.Rent(); + logRecord1 = pool.Rent(); Assert.NotNull(logRecord1); - Assert.Equal(1, LogRecordPool.Current.Count); + Assert.Equal(1, pool.Count); - logRecord2 = LogRecordPool.Rent(); + logRecord2 = pool.Rent(); Assert.NotNull(logRecord2); - Assert.Equal(0, LogRecordPool.Current.Count); + Assert.Equal(0, pool.Count); - var logRecord3 = LogRecordPool.Rent(); - var logRecord4 = LogRecordPool.Rent(); + var logRecord3 = pool.Rent(); + var logRecord4 = pool.Rent(); Assert.NotNull(logRecord3); Assert.NotNull(logRecord4); - LogRecordPool.Return(logRecord1); - LogRecordPool.Return(logRecord2); - LogRecordPool.Return(logRecord3); - LogRecordPool.Return(logRecord4); // <- Discarded due to pool size of 2 + pool.Return(logRecord1); + pool.Return(logRecord2); + pool.Return(logRecord3); + pool.Return(logRecord4); // <- Discarded due to pool size of 2 - Assert.Equal(2, LogRecordPool.Current.Count); + Assert.Equal(2, pool.Count); } [Fact] @@ -96,36 +97,40 @@ public void TrackReferenceTests() { LogRecordPool.Resize(2); - var logRecord1 = LogRecordPool.Rent(); + var pool = LogRecordSharedPool.Current; + + var logRecord1 = pool.Rent(); Assert.NotNull(logRecord1); Assert.Equal(1, logRecord1.PoolReferenceCount); - LogRecordPool.TrackReference(logRecord1); + pool.TrackReference(logRecord1); Assert.Equal(2, logRecord1.PoolReferenceCount); - LogRecordPool.Return(logRecord1); + pool.Return(logRecord1); Assert.Equal(1, logRecord1.PoolReferenceCount); - LogRecordPool.Return(logRecord1); + pool.Return(logRecord1); - Assert.Equal(1, LogRecordPool.Current.Count); + Assert.Equal(1, pool.Count); Assert.Equal(0, logRecord1.PoolReferenceCount); - LogRecordPool.Return(logRecord1); + pool.Return(logRecord1); Assert.Equal(-1, logRecord1.PoolReferenceCount); - Assert.Equal(1, LogRecordPool.Current.Count); // Record was not returned because PoolReferences was negative. + Assert.Equal(1, pool.Count); // Record was not returned because PoolReferences was negative. } [Fact] public void ClearTests() { - LogRecordPool.Resize(LogRecordPool.DefaultMaxPoolSize); + LogRecordPool.Resize(LogRecordSharedPool.DefaultMaxPoolSize); + + var pool = LogRecordSharedPool.Current; - var logRecord1 = LogRecordPool.Rent(); + var logRecord1 = pool.Rent(); logRecord1.AttributeStorage = new List>(16) { new KeyValuePair("key1", "value1"), @@ -133,29 +138,29 @@ public void ClearTests() }; logRecord1.BufferedScopes = new List(8) { null, null }; - LogRecordPool.Return(logRecord1); + pool.Return(logRecord1); Assert.Empty(logRecord1.AttributeStorage); Assert.Equal(16, logRecord1.AttributeStorage.Capacity); Assert.Empty(logRecord1.BufferedScopes); Assert.Equal(8, logRecord1.BufferedScopes.Capacity); - logRecord1 = LogRecordPool.Rent(); + logRecord1 = pool.Rent(); Assert.NotNull(logRecord1.AttributeStorage); Assert.NotNull(logRecord1.BufferedScopes); - for (int i = 0; i <= LogRecordPool.DefaultMaxNumberOfAttributes; i++) + for (int i = 0; i <= LogRecordSharedPool.DefaultMaxNumberOfAttributes; i++) { logRecord1.AttributeStorage!.Add(new KeyValuePair("key", "value")); } - for (int i = 0; i <= LogRecordPool.DefaultMaxNumberOfScopes; i++) + for (int i = 0; i <= LogRecordSharedPool.DefaultMaxNumberOfScopes; i++) { logRecord1.BufferedScopes!.Add(null); } - LogRecordPool.Return(logRecord1); + pool.Return(logRecord1); Assert.Null(logRecord1.AttributeStorage); Assert.Null(logRecord1.BufferedScopes); @@ -166,13 +171,15 @@ public void ClearTests() [InlineData(true)] public async Task ExportTest(bool warmup) { - LogRecordPool.Resize(LogRecordPool.DefaultMaxPoolSize); + LogRecordPool.Resize(LogRecordSharedPool.DefaultMaxPoolSize); + + var pool = LogRecordSharedPool.Current; if (warmup) { - for (int i = 0; i < LogRecordPool.DefaultMaxPoolSize; i++) + for (int i = 0; i < LogRecordSharedPool.DefaultMaxPoolSize; i++) { - LogRecordPool.Return(new LogRecord { PoolReferenceCount = 1 }); + pool.Return(new LogRecord { PoolReferenceCount = 1 }); } } @@ -190,12 +197,12 @@ public async Task ExportTest(bool warmup) for (int i = 0; i < 1000; i++) { - var logRecord = LogRecordPool.Rent(); + var logRecord = pool.Rent(); processor.OnEnd(logRecord); // This should no-op mostly. - LogRecordPool.Return(logRecord); + pool.Return(logRecord); await Task.Delay(random.Next(0, 20)).ConfigureAwait(false); } @@ -208,10 +215,10 @@ public async Task ExportTest(bool warmup) if (warmup) { - Assert.Equal(LogRecordPool.DefaultMaxPoolSize, LogRecordPool.Current.Count); + Assert.Equal(LogRecordSharedPool.DefaultMaxPoolSize, pool.Count); } - Assert.True(LogRecordPool.Current.Count <= LogRecordPool.DefaultMaxPoolSize); + Assert.True(pool.Count <= LogRecordSharedPool.DefaultMaxPoolSize); } [Fact] @@ -234,7 +241,9 @@ public async Task DeadlockTest() * have more natural back-off time. */ - LogRecordPool.Resize(LogRecordPool.DefaultMaxPoolSize); + LogRecordPool.Resize(LogRecordSharedPool.DefaultMaxPoolSize); + + var pool = LogRecordSharedPool.Current; List tasks = new(); @@ -246,16 +255,16 @@ public async Task DeadlockTest() for (int i = 0; i < 100_000; i++) { - var logRecord = LogRecordPool.Rent(); + var logRecord = pool.Rent(); - LogRecordPool.Return(logRecord); + pool.Return(logRecord); } })); } await Task.WhenAll(tasks).ConfigureAwait(false); - Assert.True(LogRecordPool.Current.Count <= LogRecordPool.DefaultMaxPoolSize); + Assert.True(pool.Count <= LogRecordSharedPool.DefaultMaxPoolSize); } private sealed class NoopExporter : BaseExporter diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs new file mode 100644 index 00000000000..36b4d6c1371 --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/LogRecordThreadStaticPoolTests.cs @@ -0,0 +1,90 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using System.Collections.Generic; +using Xunit; + +namespace OpenTelemetry.Logs.Tests +{ + public sealed class LogRecordThreadStaticPoolTests + { + [Fact] + public void RentReturnTests() + { + LogRecordThreadStaticPool.Storage = null; + + var logRecord = LogRecordThreadStaticPool.Instance.Rent(); + Assert.NotNull(logRecord); + Assert.Null(LogRecordThreadStaticPool.Storage); + + LogRecordThreadStaticPool.Instance.Return(logRecord); + Assert.NotNull(LogRecordThreadStaticPool.Storage); + Assert.Equal(logRecord, LogRecordThreadStaticPool.Storage); + + LogRecordThreadStaticPool.Instance.Return(new()); + Assert.NotNull(LogRecordThreadStaticPool.Storage); + Assert.Equal(logRecord, LogRecordThreadStaticPool.Storage); + + LogRecordThreadStaticPool.Storage = null; + + var manual = new LogRecord(); + LogRecordThreadStaticPool.Instance.Return(manual); + Assert.NotNull(LogRecordThreadStaticPool.Storage); + Assert.Equal(manual, LogRecordThreadStaticPool.Storage); + } + + [Fact] + public void ClearTests() + { + var logRecord1 = LogRecordThreadStaticPool.Instance.Rent(); + logRecord1.AttributeStorage = new List>(16) + { + new KeyValuePair("key1", "value1"), + new KeyValuePair("key2", "value2"), + }; + logRecord1.BufferedScopes = new List(8) { null, null }; + + LogRecordThreadStaticPool.Instance.Return(logRecord1); + + Assert.Empty(logRecord1.AttributeStorage); + Assert.Equal(16, logRecord1.AttributeStorage.Capacity); + Assert.Empty(logRecord1.BufferedScopes); + Assert.Equal(8, logRecord1.BufferedScopes.Capacity); + + logRecord1 = LogRecordThreadStaticPool.Instance.Rent(); + + Assert.NotNull(logRecord1.AttributeStorage); + Assert.NotNull(logRecord1.BufferedScopes); + + for (int i = 0; i <= LogRecordSharedPool.DefaultMaxNumberOfAttributes; i++) + { + logRecord1.AttributeStorage!.Add(new KeyValuePair("key", "value")); + } + + for (int i = 0; i <= LogRecordSharedPool.DefaultMaxNumberOfScopes; i++) + { + logRecord1.BufferedScopes!.Add(null); + } + + LogRecordThreadStaticPool.Instance.Return(logRecord1); + + Assert.Null(logRecord1.AttributeStorage); + Assert.Null(logRecord1.BufferedScopes); + } + } +} diff --git a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs new file mode 100644 index 00000000000..51c754fcbc2 --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggerProviderTests.cs @@ -0,0 +1,85 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +using Xunit; + +namespace OpenTelemetry.Logs.Tests +{ + public sealed class OpenTelemetryLoggerProviderTests + { + [Fact] + public void ThreadStaticPoolUsedByProviderTests() + { + using var provider1 = new OpenTelemetryLoggerProvider(new OpenTelemetryLoggerOptions()); + + Assert.Equal(LogRecordThreadStaticPool.Instance, provider1.LogRecordPool); + + var options = new OpenTelemetryLoggerOptions(); + options.AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())); + + using var provider2 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordThreadStaticPool.Instance, provider2.LogRecordPool); + + options.AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())); + + using var provider3 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordThreadStaticPool.Instance, provider3.LogRecordPool); + } + + [Fact] + public void SharedPoolUsedByProviderTests() + { + var options = new OpenTelemetryLoggerOptions(); + options.AddProcessor(new BatchLogRecordExportProcessor(new NoopExporter())); + + using var provider1 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordSharedPool.Current, provider1.LogRecordPool); + + options = new OpenTelemetryLoggerOptions(); + options.AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())); + options.AddProcessor(new BatchLogRecordExportProcessor(new NoopExporter())); + + using var provider2 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordSharedPool.Current, provider2.LogRecordPool); + + options = new OpenTelemetryLoggerOptions(); + options.AddProcessor(new SimpleLogRecordExportProcessor(new NoopExporter())); + options.AddProcessor(new CompositeProcessor(new BaseProcessor[] + { + new SimpleLogRecordExportProcessor(new NoopExporter()), + new BatchLogRecordExportProcessor(new NoopExporter()), + })); + + using var provider3 = new OpenTelemetryLoggerProvider(options); + + Assert.Equal(LogRecordSharedPool.Current, provider3.LogRecordPool); + } + + private sealed class NoopExporter : BaseExporter + { + public override ExportResult Export(in Batch batch) + { + return ExportResult.Success; + } + } + } +} From a12d432b44f343ab0adc13a92a6fe2bc9f486b62 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 09:47:13 -0700 Subject: [PATCH 26/41] Update public api files. --- .../.publicApi/net462/PublicAPI.Shipped.txt | 22 +++++++------- .../.publicApi/net462/PublicAPI.Unshipped.txt | 30 +++++++++++++++++++ .../netstandard2.0/PublicAPI.Shipped.txt | 22 +++++++------- .../netstandard2.0/PublicAPI.Unshipped.txt | 30 +++++++++++++++++++ 4 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt index 56774a7fc4d..2d5c8ae9c91 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt @@ -6,16 +6,16 @@ ~OpenTelemetry.BaseExportProcessor ~OpenTelemetry.BaseExportProcessor.BaseExportProcessor(OpenTelemetry.BaseExporter exporter) -> void ~OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider -~OpenTelemetry.Batch -~OpenTelemetry.Batch.Batch(T[] items, int count) -> void -~OpenTelemetry.Batch.Enumerator.Current.get -> T +OpenTelemetry.Batch +OpenTelemetry.Batch.Batch(T![]! items, int count) -> void +OpenTelemetry.Batch.Enumerator.Current.get -> T! ~OpenTelemetry.Batch.GetEnumerator() -> OpenTelemetry.Batch.Enumerator ~OpenTelemetry.BatchActivityExportProcessor.BatchActivityExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void -~OpenTelemetry.BatchExportProcessor -~OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.BatchExportProcessor +OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void ~OpenTelemetry.BatchExportProcessorOptions -~OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.CompositeProcessor -~OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable> processors) -> void +OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.CompositeProcessor! +OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable!>! processors) -> void ~OpenTelemetry.Metrics.BaseExportingMetricReader.BaseExportingMetricReader(OpenTelemetry.BaseExporter exporter) -> void ~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.get -> double[] ~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.set -> void @@ -46,8 +46,8 @@ ~OpenTelemetry.Resources.ResourceBuilder.Build() -> OpenTelemetry.Resources.Resource ~OpenTelemetry.Resources.ResourceBuilder.Clear() -> OpenTelemetry.Resources.ResourceBuilder ~OpenTelemetry.SimpleActivityExportProcessor.SimpleActivityExportProcessor(OpenTelemetry.BaseExporter exporter) -> void -~OpenTelemetry.SimpleExportProcessor -~OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter exporter) -> void +OpenTelemetry.SimpleExportProcessor +OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void ~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler) -> void ~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void ~OpenTelemetry.Trace.Sampler.Description.get -> string @@ -62,11 +62,11 @@ ~OpenTelemetry.Trace.TracerProviderBuilderBase.Build() -> OpenTelemetry.Trace.TracerProvider ~override OpenTelemetry.BaseExportProcessor.OnEnd(T data) -> void ~override OpenTelemetry.BatchActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.BatchExportProcessor.OnExport(T data) -> void +override OpenTelemetry.BatchExportProcessor.OnExport(T! data) -> void ~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder ~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddMeter(params string[] names) -> OpenTelemetry.Metrics.MeterProviderBuilder ~override OpenTelemetry.SimpleActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.SimpleExportProcessor.OnExport(T data) -> void +override OpenTelemetry.SimpleExportProcessor.OnExport(T! data) -> void ~override OpenTelemetry.Trace.SamplingResult.Equals(object obj) -> bool ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddLegacySource(string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index e69de29bb2d..2bebdae610d 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -0,0 +1,30 @@ +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void +OpenTelemetry.Logs.LogRecordData.EventId.get -> Microsoft.Extensions.Logging.EventId +OpenTelemetry.Logs.LogRecordData.EventId.set -> void +OpenTelemetry.Logs.LogRecordData.Exception.get -> System.Exception? +OpenTelemetry.Logs.LogRecordData.Exception.set -> void +OpenTelemetry.Logs.LogRecordData.LogLevel.get -> Microsoft.Extensions.Logging.LogLevel +OpenTelemetry.Logs.LogRecordData.LogLevel.set -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData() -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData(System.Diagnostics.Activity? activity = null) -> void +OpenTelemetry.Logs.LogRecordData.Message.get -> string? +OpenTelemetry.Logs.LogRecordData.Message.set -> void +OpenTelemetry.Logs.LogRecordData.SpanId.get -> System.Diagnostics.ActivitySpanId +OpenTelemetry.Logs.LogRecordData.SpanId.set -> void +OpenTelemetry.Logs.LogRecordData.Timestamp.get -> System.DateTime +OpenTelemetry.Logs.LogRecordData.Timestamp.set -> void +OpenTelemetry.Logs.LogRecordData.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags +OpenTelemetry.Logs.LogRecordData.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecordData.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Logs.LogRecordData.TraceId.set -> void +OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? +OpenTelemetry.Logs.LogRecordData.TraceState.set -> void +OpenTelemetry.Logs.LogRecordPool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void \ No newline at end of file diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt index 56774a7fc4d..2d5c8ae9c91 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -6,16 +6,16 @@ ~OpenTelemetry.BaseExportProcessor ~OpenTelemetry.BaseExportProcessor.BaseExportProcessor(OpenTelemetry.BaseExporter exporter) -> void ~OpenTelemetry.BaseProcessor.ParentProvider.get -> OpenTelemetry.BaseProvider -~OpenTelemetry.Batch -~OpenTelemetry.Batch.Batch(T[] items, int count) -> void -~OpenTelemetry.Batch.Enumerator.Current.get -> T +OpenTelemetry.Batch +OpenTelemetry.Batch.Batch(T![]! items, int count) -> void +OpenTelemetry.Batch.Enumerator.Current.get -> T! ~OpenTelemetry.Batch.GetEnumerator() -> OpenTelemetry.Batch.Enumerator ~OpenTelemetry.BatchActivityExportProcessor.BatchActivityExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void -~OpenTelemetry.BatchExportProcessor -~OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void +OpenTelemetry.BatchExportProcessor +OpenTelemetry.BatchExportProcessor.BatchExportProcessor(OpenTelemetry.BaseExporter! exporter, int maxQueueSize = 2048, int scheduledDelayMilliseconds = 5000, int exporterTimeoutMilliseconds = 30000, int maxExportBatchSize = 512) -> void ~OpenTelemetry.BatchExportProcessorOptions -~OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.CompositeProcessor -~OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable> processors) -> void +OpenTelemetry.CompositeProcessor.AddProcessor(OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.CompositeProcessor! +OpenTelemetry.CompositeProcessor.CompositeProcessor(System.Collections.Generic.IEnumerable!>! processors) -> void ~OpenTelemetry.Metrics.BaseExportingMetricReader.BaseExportingMetricReader(OpenTelemetry.BaseExporter exporter) -> void ~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.get -> double[] ~OpenTelemetry.Metrics.ExplicitBucketHistogramConfiguration.Boundaries.set -> void @@ -46,8 +46,8 @@ ~OpenTelemetry.Resources.ResourceBuilder.Build() -> OpenTelemetry.Resources.Resource ~OpenTelemetry.Resources.ResourceBuilder.Clear() -> OpenTelemetry.Resources.ResourceBuilder ~OpenTelemetry.SimpleActivityExportProcessor.SimpleActivityExportProcessor(OpenTelemetry.BaseExporter exporter) -> void -~OpenTelemetry.SimpleExportProcessor -~OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter exporter) -> void +OpenTelemetry.SimpleExportProcessor +OpenTelemetry.SimpleExportProcessor.SimpleExportProcessor(OpenTelemetry.BaseExporter! exporter) -> void ~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler) -> void ~OpenTelemetry.Trace.ParentBasedSampler.ParentBasedSampler(OpenTelemetry.Trace.Sampler rootSampler, OpenTelemetry.Trace.Sampler remoteParentSampled = null, OpenTelemetry.Trace.Sampler remoteParentNotSampled = null, OpenTelemetry.Trace.Sampler localParentSampled = null, OpenTelemetry.Trace.Sampler localParentNotSampled = null) -> void ~OpenTelemetry.Trace.Sampler.Description.get -> string @@ -62,11 +62,11 @@ ~OpenTelemetry.Trace.TracerProviderBuilderBase.Build() -> OpenTelemetry.Trace.TracerProvider ~override OpenTelemetry.BaseExportProcessor.OnEnd(T data) -> void ~override OpenTelemetry.BatchActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.BatchExportProcessor.OnExport(T data) -> void +override OpenTelemetry.BatchExportProcessor.OnExport(T! data) -> void ~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Metrics.MeterProviderBuilder ~override OpenTelemetry.Metrics.MeterProviderBuilderBase.AddMeter(params string[] names) -> OpenTelemetry.Metrics.MeterProviderBuilder ~override OpenTelemetry.SimpleActivityExportProcessor.OnEnd(System.Diagnostics.Activity data) -> void -~override OpenTelemetry.SimpleExportProcessor.OnExport(T data) -> void +override OpenTelemetry.SimpleExportProcessor.OnExport(T! data) -> void ~override OpenTelemetry.Trace.SamplingResult.Equals(object obj) -> bool ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(System.Func instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder ~override OpenTelemetry.Trace.TracerProviderBuilderBase.AddLegacySource(string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb2d..2bebdae610d 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,30 @@ +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void +OpenTelemetry.Logs.LogRecordData.EventId.get -> Microsoft.Extensions.Logging.EventId +OpenTelemetry.Logs.LogRecordData.EventId.set -> void +OpenTelemetry.Logs.LogRecordData.Exception.get -> System.Exception? +OpenTelemetry.Logs.LogRecordData.Exception.set -> void +OpenTelemetry.Logs.LogRecordData.LogLevel.get -> Microsoft.Extensions.Logging.LogLevel +OpenTelemetry.Logs.LogRecordData.LogLevel.set -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData() -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData(System.Diagnostics.Activity? activity = null) -> void +OpenTelemetry.Logs.LogRecordData.Message.get -> string? +OpenTelemetry.Logs.LogRecordData.Message.set -> void +OpenTelemetry.Logs.LogRecordData.SpanId.get -> System.Diagnostics.ActivitySpanId +OpenTelemetry.Logs.LogRecordData.SpanId.set -> void +OpenTelemetry.Logs.LogRecordData.Timestamp.get -> System.DateTime +OpenTelemetry.Logs.LogRecordData.Timestamp.set -> void +OpenTelemetry.Logs.LogRecordData.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags +OpenTelemetry.Logs.LogRecordData.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecordData.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Logs.LogRecordData.TraceId.set -> void +OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? +OpenTelemetry.Logs.LogRecordData.TraceState.set -> void +OpenTelemetry.Logs.LogRecordPool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void \ No newline at end of file From 3b35268f7668aede29ad24714b0da32fb881287c Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 09:49:38 -0700 Subject: [PATCH 27/41] Public api fix. --- src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt | 2 +- src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt | 3 +-- .../.publicApi/netstandard2.0/PublicAPI.Shipped.txt | 2 +- .../.publicApi/netstandard2.0/PublicAPI.Unshipped.txt | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt index 2d5c8ae9c91..f6a55fad59e 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt @@ -145,7 +145,7 @@ OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Failure = 1 -> OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Success = 0 -> OpenTelemetry.ExportResult OpenTelemetry.Logs.LogRecord -OpenTelemetry.Logs.LogRecord.CategoryName.get -> string! +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? OpenTelemetry.Logs.LogRecord.EventId.get -> Microsoft.Extensions.Logging.EventId OpenTelemetry.Logs.LogRecord.Exception.get -> System.Exception? OpenTelemetry.Logs.LogRecord.ForEachScope(System.Action! callback, TState state) -> void diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 2bebdae610d..161c2b56087 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -1,4 +1,3 @@ -OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? @@ -27,4 +26,4 @@ OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void -static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void \ No newline at end of file +static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt index 2d5c8ae9c91..f6a55fad59e 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -145,7 +145,7 @@ OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Failure = 1 -> OpenTelemetry.ExportResult OpenTelemetry.ExportResult.Success = 0 -> OpenTelemetry.ExportResult OpenTelemetry.Logs.LogRecord -OpenTelemetry.Logs.LogRecord.CategoryName.get -> string! +OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? OpenTelemetry.Logs.LogRecord.EventId.get -> Microsoft.Extensions.Logging.EventId OpenTelemetry.Logs.LogRecord.Exception.get -> System.Exception? OpenTelemetry.Logs.LogRecord.ForEachScope(System.Action! callback, TState state) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 2bebdae610d..161c2b56087 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,4 +1,3 @@ -OpenTelemetry.Logs.LogRecord.CategoryName.get -> string? OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecordData OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? @@ -27,4 +26,4 @@ OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void -static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void \ No newline at end of file +static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void From 57775a5d1bb269be734c4311cf9c23f71f35311a Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 10:01:55 -0700 Subject: [PATCH 28/41] Lint and race comment. --- src/OpenTelemetry.Extensions.Serilog/README.md | 2 +- src/OpenTelemetry.Extensions.Tracing/README.md | 2 +- src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Extensions.Serilog/README.md b/src/OpenTelemetry.Extensions.Serilog/README.md index 7268fd0d3b0..2d1b1e8dc42 100644 --- a/src/OpenTelemetry.Extensions.Serilog/README.md +++ b/src/OpenTelemetry.Extensions.Serilog/README.md @@ -7,7 +7,7 @@ writing log messages to OpenTelemetry. ## Usage Example ```csharp -// Step 1: Configure OpenTelemetryLoggerProvider... +// Step 1: Configure OpenTelemetryLoggerProvider... var resourceBuilder = ResourceBuilder.CreateDefault().AddService("MyService"); using var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => diff --git a/src/OpenTelemetry.Extensions.Tracing/README.md b/src/OpenTelemetry.Extensions.Tracing/README.md index 87bb38b70d6..d14f85bd468 100644 --- a/src/OpenTelemetry.Extensions.Tracing/README.md +++ b/src/OpenTelemetry.Extensions.Tracing/README.md @@ -9,7 +9,7 @@ into to OpenTelemetry logs. ## Usage Example ```csharp -// Step 1: Configure OpenTelemetryLoggerProvider... +// Step 1: Configure OpenTelemetryLoggerProvider... var resourceBuilder = ResourceBuilder.CreateDefault().AddService("MyService"); using var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => diff --git a/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs b/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs index fe8f566e405..9cb249172bc 100644 --- a/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs +++ b/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs @@ -95,6 +95,11 @@ public void Return(LogRecord logRecord) 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.pool[returnSnapshot % this.Capacity] = logRecord; return; } From 9ee18df6fa62abb1064f02e2544aadf7794faa08 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 10:08:14 -0700 Subject: [PATCH 29/41] Comments in log emitter example app. --- examples/LogEmitter/Program.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/LogEmitter/Program.cs b/examples/LogEmitter/Program.cs index 4c31938555b..ec72976ee7e 100644 --- a/examples/LogEmitter/Program.cs +++ b/examples/LogEmitter/Program.cs @@ -29,20 +29,26 @@ .AddConsoleExporter(); }); +// Creates an OpenTelemetryEventSourceLogEmitter for routing EventSources with +// names matching OpenTelemetry* into logs using var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( openTelemetryLoggerProvider, (name) => name.StartsWith("OpenTelemetry") ? EventLevel.LogAlways : null); +// Configue Serilog global logger Log.Logger = new LoggerConfiguration() - .WriteTo.OpenTelemetry(openTelemetryLoggerProvider) + .WriteTo.OpenTelemetry(openTelemetryLoggerProvider) // <- Register OpenTelemetry Serilog sink .CreateLogger(); +// Note: Serilog ForContext API is used to set "CategoryName" on log messages ILogger programLogger = Log.Logger.ForContext(); programLogger.Information("Application started {Greeting} {Location}", "Hello", "World"); programLogger.Information("Message {Array}", new string[] { "value1", "value2" }); +// Note: ForceFlush is only called here to demo +// OpenTelemetryEventSourceLogEmitter converting SDK events into log messages openTelemetryLoggerProvider.ForceFlush(); Console.WriteLine("Press ENTER to exit..."); From f347e51b966bbca4be663a4d486d6463520ab990 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 11:34:47 -0700 Subject: [PATCH 30/41] Switch to Volatile.Read. --- src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs b/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs index 9cb249172bc..14285b8aab4 100644 --- a/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs +++ b/src/OpenTelemetry/Logs/Pool/LogRecordSharedPool.cs @@ -41,14 +41,14 @@ internal LogRecordSharedPool(int capacity) this.pool = new LogRecord?[capacity]; } - internal int Count => (int)(Interlocked.Read(ref this.returnIndex) - Interlocked.Read(ref this.rentIndex)); + internal int Count => (int)(Volatile.Read(ref this.returnIndex) - Volatile.Read(ref this.rentIndex)); public LogRecord Rent() { while (true) { - var rentSnapshot = Interlocked.Read(ref this.rentIndex); - var returnSnapshot = Interlocked.Read(ref this.returnIndex); + var rentSnapshot = Volatile.Read(ref this.rentIndex); + var returnSnapshot = Volatile.Read(ref this.returnIndex); if (rentSnapshot >= returnSnapshot) { @@ -85,8 +85,8 @@ public void Return(LogRecord logRecord) while (true) { - var rentSnapshot = Interlocked.Read(ref this.rentIndex); - var returnSnapshot = Interlocked.Read(ref this.returnIndex); + var rentSnapshot = Volatile.Read(ref this.rentIndex); + var returnSnapshot = Volatile.Read(ref this.returnIndex); if (returnSnapshot - rentSnapshot >= this.Capacity) { From 5537c738620cd72c80627d44a7ffdb78912ab4fa Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 11:35:03 -0700 Subject: [PATCH 31/41] Bump Microsoft.DotNet.ApiCompat. --- build/Common.prod.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Common.prod.props b/build/Common.prod.props index c97f40a0ed9..03513295cf8 100644 --- a/build/Common.prod.props +++ b/build/Common.prod.props @@ -12,7 +12,7 @@ - + From e0c2347d2f7a58136fb33ac3bab331e6c6325d78 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 11:38:01 -0700 Subject: [PATCH 32/41] Typo fix. --- examples/LogEmitter/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/LogEmitter/Program.cs b/examples/LogEmitter/Program.cs index ec72976ee7e..6f5b5a8e0ef 100644 --- a/examples/LogEmitter/Program.cs +++ b/examples/LogEmitter/Program.cs @@ -35,7 +35,7 @@ openTelemetryLoggerProvider, (name) => name.StartsWith("OpenTelemetry") ? EventLevel.LogAlways : null); -// Configue Serilog global logger +// Configure Serilog global logger Log.Logger = new LoggerConfiguration() .WriteTo.OpenTelemetry(openTelemetryLoggerProvider) // <- Register OpenTelemetry Serilog sink .CreateLogger(); From dd80cc986094ea2521ec957d414db9d90ec3ccd9 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 11:44:16 -0700 Subject: [PATCH 33/41] Bump Microsoft.DotNet.ApiCompat. --- build/Common.prod.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Common.prod.props b/build/Common.prod.props index 03513295cf8..c88cf685e3e 100644 --- a/build/Common.prod.props +++ b/build/Common.prod.props @@ -12,7 +12,7 @@ - + From 4f6b9e0bad01b68a6064d5abf18ce6c5868f8b6d Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 13:35:56 -0700 Subject: [PATCH 34/41] Attempting to fix ApiCompat failure. --- build/Common.prod.props | 3 ++- build/GlobalAttrExclusions.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 build/GlobalAttrExclusions.txt diff --git a/build/Common.prod.props b/build/Common.prod.props index c88cf685e3e..2bf35969d32 100644 --- a/build/Common.prod.props +++ b/build/Common.prod.props @@ -12,7 +12,7 @@ - + @@ -35,6 +35,7 @@ $(Build_ArtifactStagingDirectory) true + $(RepoRoot)build\GlobalAttrExclusions.txt diff --git a/build/GlobalAttrExclusions.txt b/build/GlobalAttrExclusions.txt new file mode 100644 index 00000000000..c159ba8233f --- /dev/null +++ b/build/GlobalAttrExclusions.txt @@ -0,0 +1 @@ +T:System.Runtime.CompilerServices.CompilerGeneratedAttribute \ No newline at end of file From 1dcd19341535e539fa309090f92f869d94453f24 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 14:08:26 -0700 Subject: [PATCH 35/41] Tweak ApiCompatExcludeAttributeList path. --- build/Common.prod.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Common.prod.props b/build/Common.prod.props index 2bf35969d32..915644b7755 100644 --- a/build/Common.prod.props +++ b/build/Common.prod.props @@ -35,7 +35,7 @@ $(Build_ArtifactStagingDirectory) true - $(RepoRoot)build\GlobalAttrExclusions.txt + $(RepoRoot)\build\GlobalAttrExclusions.txt From 226facf4052b0a74baecf3df0052c87af193ef3b Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 7 Jun 2022 14:25:07 -0700 Subject: [PATCH 36/41] Exclude NullableContextAttribute from ApiCompat. --- OpenTelemetry.sln | 1 + build/GlobalAttrExclusions.txt | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index eccd7011bad..93b4709673a 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -29,6 +29,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E build\docker-compose.net6.0.yml = build\docker-compose.net6.0.yml build\docker-compose.netcoreapp3.1.yml = build\docker-compose.netcoreapp3.1.yml build\finalize-publicapi.ps1 = build\finalize-publicapi.ps1 + build\GlobalAttrExclusions.txt = build\GlobalAttrExclusions.txt build\opentelemetry-icon-color.png = build\opentelemetry-icon-color.png build\OpenTelemetry.prod.loose.ruleset = build\OpenTelemetry.prod.loose.ruleset build\OpenTelemetry.prod.ruleset = build\OpenTelemetry.prod.ruleset diff --git a/build/GlobalAttrExclusions.txt b/build/GlobalAttrExclusions.txt index c159ba8233f..dc571573014 100644 --- a/build/GlobalAttrExclusions.txt +++ b/build/GlobalAttrExclusions.txt @@ -1 +1,4 @@ -T:System.Runtime.CompilerServices.CompilerGeneratedAttribute \ No newline at end of file +// These attributes should be excluded from ApiCompat checks. + +T:System.Runtime.CompilerServices.CompilerGeneratedAttribute +T:System.Runtime.CompilerServices.NullableContextAttribute From cb69e49876d8f9e37f3e27820349399e62e1f319 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 16 Jun 2022 16:06:46 -0700 Subject: [PATCH 37/41] Fix merge. --- src/OpenTelemetry/CompositeProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry/CompositeProcessor.cs b/src/OpenTelemetry/CompositeProcessor.cs index e0f37537137..38a2babf9c9 100644 --- a/src/OpenTelemetry/CompositeProcessor.cs +++ b/src/OpenTelemetry/CompositeProcessor.cs @@ -85,7 +85,7 @@ internal override void SetParentProvider(BaseProvider parentProvider) { base.SetParentProvider(parentProvider); - for (var cur = this.head; cur != null; cur = cur.Next) + for (var cur = this.Head; cur != null; cur = cur.Next) { cur.Value.SetParentProvider(parentProvider); } From b71006f713015c4dd9a840083c3e32309095fe5a Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 17 Jun 2022 10:57:41 -0700 Subject: [PATCH 38/41] Updates. --- .../.publicApi/net6.0/PublicAPI.Unshipped.txt | 29 ++++++++++++++++++- .../netstandard2.1/PublicAPI.Unshipped.txt | 29 ++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt index 7ead6b2aa97..78759d1f7f7 100644 --- a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -1,3 +1,30 @@ +OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void +OpenTelemetry.Logs.LogRecordData.EventId.get -> Microsoft.Extensions.Logging.EventId +OpenTelemetry.Logs.LogRecordData.EventId.set -> void +OpenTelemetry.Logs.LogRecordData.Exception.get -> System.Exception? +OpenTelemetry.Logs.LogRecordData.Exception.set -> void +OpenTelemetry.Logs.LogRecordData.LogLevel.get -> Microsoft.Extensions.Logging.LogLevel +OpenTelemetry.Logs.LogRecordData.LogLevel.set -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData() -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData(System.Diagnostics.Activity? activity = null) -> void +OpenTelemetry.Logs.LogRecordData.Message.get -> string? +OpenTelemetry.Logs.LogRecordData.Message.set -> void +OpenTelemetry.Logs.LogRecordData.SpanId.get -> System.Diagnostics.ActivitySpanId +OpenTelemetry.Logs.LogRecordData.SpanId.set -> void +OpenTelemetry.Logs.LogRecordData.Timestamp.get -> System.DateTime +OpenTelemetry.Logs.LogRecordData.Timestamp.set -> void +OpenTelemetry.Logs.LogRecordData.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags +OpenTelemetry.Logs.LogRecordData.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecordData.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Logs.LogRecordData.TraceId.set -> void +OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? +OpenTelemetry.Logs.LogRecordData.TraceState.set -> void +OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void -OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void \ No newline at end of file +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void \ No newline at end of file diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt index 7ead6b2aa97..78759d1f7f7 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -1,3 +1,30 @@ +OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData +OpenTelemetry.Logs.LogRecordData.CategoryName.get -> string? +OpenTelemetry.Logs.LogRecordData.CategoryName.set -> void +OpenTelemetry.Logs.LogRecordData.EventId.get -> Microsoft.Extensions.Logging.EventId +OpenTelemetry.Logs.LogRecordData.EventId.set -> void +OpenTelemetry.Logs.LogRecordData.Exception.get -> System.Exception? +OpenTelemetry.Logs.LogRecordData.Exception.set -> void +OpenTelemetry.Logs.LogRecordData.LogLevel.get -> Microsoft.Extensions.Logging.LogLevel +OpenTelemetry.Logs.LogRecordData.LogLevel.set -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData() -> void +OpenTelemetry.Logs.LogRecordData.LogRecordData(System.Diagnostics.Activity? activity = null) -> void +OpenTelemetry.Logs.LogRecordData.Message.get -> string? +OpenTelemetry.Logs.LogRecordData.Message.set -> void +OpenTelemetry.Logs.LogRecordData.SpanId.get -> System.Diagnostics.ActivitySpanId +OpenTelemetry.Logs.LogRecordData.SpanId.set -> void +OpenTelemetry.Logs.LogRecordData.Timestamp.get -> System.DateTime +OpenTelemetry.Logs.LogRecordData.Timestamp.set -> void +OpenTelemetry.Logs.LogRecordData.TraceFlags.get -> System.Diagnostics.ActivityTraceFlags +OpenTelemetry.Logs.LogRecordData.TraceFlags.set -> void +OpenTelemetry.Logs.LogRecordData.TraceId.get -> System.Diagnostics.ActivityTraceId +OpenTelemetry.Logs.LogRecordData.TraceId.set -> void +OpenTelemetry.Logs.LogRecordData.TraceState.get -> string? +OpenTelemetry.Logs.LogRecordData.TraceState.set -> void +OpenTelemetry.Logs.LogRecordPool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMilliseconds = -1) -> bool +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.IncludeFormattedMessage.get -> bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void -OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void \ No newline at end of file +OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +static OpenTelemetry.Logs.LogRecordPool.Resize(int capacity) -> void \ No newline at end of file From 764714505e9b9fdfd6c06e3c65d0da28a467a529 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 17 Jun 2022 21:48:28 -0700 Subject: [PATCH 39/41] Revert OtlpLogExporter use of logRecord.GetDataRef because it is now internal. --- .../Implementation/LogRecordExtensions.cs | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs index b27888cf9b4..6139831d162 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs @@ -65,31 +65,30 @@ internal static void AddBatch( internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord) { OtlpLogs.LogRecord otlpLogRecord = null; - ref LogRecordData data = ref logRecord.GetDataRef(); try { otlpLogRecord = new OtlpLogs.LogRecord { - TimeUnixNano = (ulong)data.Timestamp.ToUnixTimeNanoseconds(), - SeverityNumber = GetSeverityNumber(data.LogLevel), - SeverityText = LogLevels[(int)data.LogLevel], + TimeUnixNano = (ulong)logRecord.Timestamp.ToUnixTimeNanoseconds(), + SeverityNumber = GetSeverityNumber(logRecord.LogLevel), + SeverityText = LogLevels[(int)logRecord.LogLevel], }; - if (!string.IsNullOrEmpty(data.CategoryName)) + if (!string.IsNullOrEmpty(logRecord.CategoryName)) { // TODO: // 1. Track the following issue, and map CategoryName to Name // if it makes it to log data model. // https://github.com/open-telemetry/opentelemetry-specification/issues/2398 // 2. Confirm if this name for attribute is good. - otlpLogRecord.Attributes.AddStringAttribute("dotnet.ilogger.category", data.CategoryName); + otlpLogRecord.Attributes.AddStringAttribute("dotnet.ilogger.category", logRecord.CategoryName); } bool bodyPopulatedFromFormattedMessage = false; - if (data.Message != null) + if (logRecord.FormattedMessage != null) { - otlpLogRecord.Body = new OtlpCommon.AnyValue { StringValue = data.Message }; + otlpLogRecord.Body = new OtlpCommon.AnyValue { StringValue = logRecord.FormattedMessage }; bodyPopulatedFromFormattedMessage = true; } @@ -111,34 +110,34 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord) } } - if (data.EventId.Id != default) + if (logRecord.EventId.Id != default) { - otlpLogRecord.Attributes.AddIntAttribute(nameof(data.EventId.Id), data.EventId.Id); + otlpLogRecord.Attributes.AddIntAttribute(nameof(logRecord.EventId.Id), logRecord.EventId.Id); } - if (!string.IsNullOrEmpty(data.EventId.Name)) + if (!string.IsNullOrEmpty(logRecord.EventId.Name)) { - otlpLogRecord.Attributes.AddStringAttribute(nameof(data.EventId.Name), data.EventId.Name); + otlpLogRecord.Attributes.AddStringAttribute(nameof(logRecord.EventId.Name), logRecord.EventId.Name); } - if (data.Exception != null) + if (logRecord.Exception != null) { - otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionType, data.Exception.GetType().Name); - otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionMessage, data.Exception.Message); - otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionStacktrace, data.Exception.ToInvariantString()); + otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionType, logRecord.Exception.GetType().Name); + otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionMessage, logRecord.Exception.Message); + otlpLogRecord.Attributes.AddStringAttribute(SemanticConventions.AttributeExceptionStacktrace, logRecord.Exception.ToInvariantString()); } - if (data.TraceId != default && data.SpanId != default) + if (logRecord.TraceId != default && logRecord.SpanId != default) { byte[] traceIdBytes = new byte[16]; byte[] spanIdBytes = new byte[8]; - data.TraceId.CopyTo(traceIdBytes); - data.SpanId.CopyTo(spanIdBytes); + logRecord.TraceId.CopyTo(traceIdBytes); + logRecord.SpanId.CopyTo(spanIdBytes); otlpLogRecord.TraceId = UnsafeByteOperations.UnsafeWrap(traceIdBytes); otlpLogRecord.SpanId = UnsafeByteOperations.UnsafeWrap(spanIdBytes); - otlpLogRecord.Flags = (uint)data.TraceFlags; + otlpLogRecord.Flags = (uint)logRecord.TraceFlags; } int scopeDepth = -1; From d1a4b3bb5f04abb435d617a6b20ba86b322bfbab Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 30 Jun 2022 09:45:46 -0700 Subject: [PATCH 40/41] Merge fix. --- src/OpenTelemetry/Logs/Pool/LogRecordPool.cs | 39 -------------------- 1 file changed, 39 deletions(-) delete mode 100644 src/OpenTelemetry/Logs/Pool/LogRecordPool.cs diff --git a/src/OpenTelemetry/Logs/Pool/LogRecordPool.cs b/src/OpenTelemetry/Logs/Pool/LogRecordPool.cs deleted file mode 100644 index c6a337b3b0a..00000000000 --- a/src/OpenTelemetry/Logs/Pool/LogRecordPool.cs +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#nullable enable - -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Logs -{ - /// - /// Manages a pool of instances. - /// - public static class LogRecordPool - { - /// - /// Resize the pool. - /// - /// The maximum number of s to store in the pool. - public static void Resize(int capacity) - { - Guard.ThrowIfOutOfRange(capacity, min: 1); - - LogRecordSharedPool.Current = new(capacity); - } - } -} From cf69b8c6c72c33effe4255fbc7a2e1d470e90806 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 18 Jul 2022 10:00:13 -0700 Subject: [PATCH 41/41] Updates. --- OpenTelemetry.sln | 6 ++ .../LogEmitter/Examples.LogEmitter.csproj | 16 ------ examples/LogEmitter/Program.cs | 56 ------------------- examples/LogEmitter/README.md | 11 ---- .../Examples.LoggingExtensions.csproj | 1 + examples/LoggingExtensions/Program.cs | 9 ++- .../OpenTelemetryEventSourceLogEmitter.cs | 2 +- 7 files changed, 16 insertions(+), 85 deletions(-) delete mode 100644 examples/LogEmitter/Examples.LogEmitter.csproj delete mode 100644 examples/LogEmitter/Program.cs delete mode 100644 examples/LogEmitter/README.md diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index fb0f5a2f842..917ae4b448e 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -235,6 +235,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Tr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.LoggingExtensions", "examples\LoggingExtensions\Examples.LoggingExtensions.csproj", "{CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Extensions.Serilog.Tests", "test\OpenTelemetry.Extensions.Serilog.Tests\OpenTelemetry.Extensions.Serilog.Tests.csproj", "{C470DC46-8DE6-4668-BD25-ADF851F6E1E2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -481,6 +483,10 @@ Global {CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}.Debug|Any CPU.Build.0 = Debug|Any CPU {CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}.Release|Any CPU.ActiveCfg = Release|Any CPU {CCBBAD69-4A7D-41F8-8697-6E3F043F7BD5}.Release|Any CPU.Build.0 = Release|Any CPU + {C470DC46-8DE6-4668-BD25-ADF851F6E1E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C470DC46-8DE6-4668-BD25-ADF851F6E1E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C470DC46-8DE6-4668-BD25-ADF851F6E1E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C470DC46-8DE6-4668-BD25-ADF851F6E1E2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/examples/LogEmitter/Examples.LogEmitter.csproj b/examples/LogEmitter/Examples.LogEmitter.csproj deleted file mode 100644 index 4042039e6b4..00000000000 --- a/examples/LogEmitter/Examples.LogEmitter.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - net6.0 - enable - enable - - - - - - - - - diff --git a/examples/LogEmitter/Program.cs b/examples/LogEmitter/Program.cs deleted file mode 100644 index 6f5b5a8e0ef..00000000000 --- a/examples/LogEmitter/Program.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Diagnostics.Tracing; -using OpenTelemetry.Logs; -using OpenTelemetry.Resources; -using Serilog; - -var resourceBuilder = ResourceBuilder.CreateDefault().AddService("Examples.LogEmitter"); - -using var openTelemetryLoggerProvider = new OpenTelemetryLoggerProvider(options => -{ - options.IncludeFormattedMessage = true; - options - .SetResourceBuilder(resourceBuilder) - .AddConsoleExporter(); -}); - -// Creates an OpenTelemetryEventSourceLogEmitter for routing EventSources with -// names matching OpenTelemetry* into logs -using var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( - openTelemetryLoggerProvider, - (name) => name.StartsWith("OpenTelemetry") ? EventLevel.LogAlways : null); - -// Configure Serilog global logger -Log.Logger = new LoggerConfiguration() - .WriteTo.OpenTelemetry(openTelemetryLoggerProvider) // <- Register OpenTelemetry Serilog sink - .CreateLogger(); - -// Note: Serilog ForContext API is used to set "CategoryName" on log messages -ILogger programLogger = Log.Logger.ForContext(); - -programLogger.Information("Application started {Greeting} {Location}", "Hello", "World"); - -programLogger.Information("Message {Array}", new string[] { "value1", "value2" }); - -// Note: ForceFlush is only called here to demo -// OpenTelemetryEventSourceLogEmitter converting SDK events into log messages -openTelemetryLoggerProvider.ForceFlush(); - -Console.WriteLine("Press ENTER to exit..."); - -Console.ReadLine(); diff --git a/examples/LogEmitter/README.md b/examples/LogEmitter/README.md deleted file mode 100644 index da935d3df5f..00000000000 --- a/examples/LogEmitter/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# OpenTelemetry LogEmitter Example - -This project contains examples of the `LogEmitter` API being used to extend -existing logging platforms to write into OpenTelemetry logs. - -* Serilog: Using OpenTelemetry.Extensions.Serilog -* EventSource: Using OpenTelemetry.Extensions.EventSource - -## References - -* [OpenTelemetry Project](https://opentelemetry.io/) diff --git a/examples/LoggingExtensions/Examples.LoggingExtensions.csproj b/examples/LoggingExtensions/Examples.LoggingExtensions.csproj index 8e502ea56d8..c365a45c62c 100644 --- a/examples/LoggingExtensions/Examples.LoggingExtensions.csproj +++ b/examples/LoggingExtensions/Examples.LoggingExtensions.csproj @@ -9,6 +9,7 @@ + diff --git a/examples/LoggingExtensions/Program.cs b/examples/LoggingExtensions/Program.cs index 310e225337f..22b89381304 100644 --- a/examples/LoggingExtensions/Program.cs +++ b/examples/LoggingExtensions/Program.cs @@ -14,11 +14,12 @@ // limitations under the License. // +using System.Diagnostics.Tracing; using OpenTelemetry.Logs; using OpenTelemetry.Resources; using Serilog; -var resourceBuilder = ResourceBuilder.CreateDefault().AddService("Examples.LogEmitter"); +var resourceBuilder = ResourceBuilder.CreateDefault().AddService("Examples.LoggingExtensions"); // Note: It is important that OpenTelemetryLoggerProvider is disposed when the // app is shutdown. In this example we allow Serilog to do that by calling CloseAndFlush. @@ -30,6 +31,12 @@ .AddConsoleExporter(); }); +// Creates an OpenTelemetryEventSourceLogEmitter for routing EventSources with +// names matching OpenTelemetry* into logs +using var openTelemetryEventSourceLogEmitter = new OpenTelemetryEventSourceLogEmitter( + openTelemetryLoggerProvider, + (name) => name.StartsWith("OpenTelemetry") ? EventLevel.LogAlways : null); + // Configure Serilog global logger Log.Logger = new LoggerConfiguration() .WriteTo.OpenTelemetry(openTelemetryLoggerProvider, disposeProvider: true) // <- Register OpenTelemetry Serilog sink diff --git a/src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs b/src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs index 319aa959b31..da1541b2709 100644 --- a/src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs +++ b/src/OpenTelemetry.Extensions.Tracing/OpenTelemetryEventSourceLogEmitter.cs @@ -188,7 +188,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) data.Message = rawMessage; - this.logEmitter.Log(in data, in attributes); + this.logEmitter.Emit(in data, in attributes); } #pragma warning restore CA1062 // Validate arguments of public methods