diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index f43534a60aa..ae90dbc9e28 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -35,6 +35,8 @@ OpenTelemetry.Metrics.ExponentialHistogramData.ZeroCount.get -> long OpenTelemetry.Metrics.MetricType.ExponentialHistogram = 80 -> OpenTelemetry.Metrics.MetricType OpenTelemetry.Metrics.TraceBasedExemplarFilter OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void +override OpenTelemetry.BaseExportProcessor.ToString() -> string! +override OpenTelemetry.BaseProcessor.ToString() -> string! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilter! exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt index f43534a60aa..ae90dbc9e28 100644 --- a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -35,6 +35,8 @@ OpenTelemetry.Metrics.ExponentialHistogramData.ZeroCount.get -> long OpenTelemetry.Metrics.MetricType.ExponentialHistogram = 80 -> OpenTelemetry.Metrics.MetricType OpenTelemetry.Metrics.TraceBasedExemplarFilter OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void +override OpenTelemetry.BaseExportProcessor.ToString() -> string! +override OpenTelemetry.BaseProcessor.ToString() -> string! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilter! exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index f43534a60aa..ae90dbc9e28 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -35,6 +35,8 @@ OpenTelemetry.Metrics.ExponentialHistogramData.ZeroCount.get -> long OpenTelemetry.Metrics.MetricType.ExponentialHistogram = 80 -> OpenTelemetry.Metrics.MetricType OpenTelemetry.Metrics.TraceBasedExemplarFilter OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void +override OpenTelemetry.BaseExportProcessor.ToString() -> string! +override OpenTelemetry.BaseProcessor.ToString() -> string! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilter! exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt index f43534a60aa..ae90dbc9e28 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -35,6 +35,8 @@ OpenTelemetry.Metrics.ExponentialHistogramData.ZeroCount.get -> long OpenTelemetry.Metrics.MetricType.ExponentialHistogram = 80 -> OpenTelemetry.Metrics.MetricType OpenTelemetry.Metrics.TraceBasedExemplarFilter OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void +override OpenTelemetry.BaseExportProcessor.ToString() -> string! +override OpenTelemetry.BaseProcessor.ToString() -> string! static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilter! exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool diff --git a/src/OpenTelemetry/BaseExportProcessor.cs b/src/OpenTelemetry/BaseExportProcessor.cs index 9684a764ae2..ebe282c33c2 100644 --- a/src/OpenTelemetry/BaseExportProcessor.cs +++ b/src/OpenTelemetry/BaseExportProcessor.cs @@ -48,6 +48,7 @@ public abstract class BaseExportProcessor : BaseProcessor where T : class { protected readonly BaseExporter exporter; + private readonly string friendlyTypeName; private bool disposed; /// @@ -58,11 +59,16 @@ protected BaseExportProcessor(BaseExporter exporter) { Guard.ThrowIfNull(exporter); + this.friendlyTypeName = $"{this.GetType().Name}{{{exporter.GetType().Name}}}"; this.exporter = exporter; } internal BaseExporter Exporter => this.exporter; + /// + public override string ToString() + => this.friendlyTypeName; + /// public sealed override void OnStart(T data) { diff --git a/src/OpenTelemetry/BaseProcessor.cs b/src/OpenTelemetry/BaseProcessor.cs index 78e6c2daf88..8020a141b50 100644 --- a/src/OpenTelemetry/BaseProcessor.cs +++ b/src/OpenTelemetry/BaseProcessor.cs @@ -153,6 +153,10 @@ public void Dispose() GC.SuppressFinalize(this); } + /// + public override string ToString() + => this.typeName; + internal virtual void SetParentProvider(BaseProvider parentProvider) { this.ParentProvider = parentProvider; diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index 872657af4d0..bac167db332 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -149,6 +149,15 @@ public void LoggerParseStateException(Exception exception) } } + [NonEvent] + public void LoggerProviderException(string methodName, Exception ex) + { + if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) + { + this.LoggerProviderException(methodName, ex.ToInvariantString()); + } + } + [Event(4, Message = "Unknown error in SpanProcessor event '{0}': '{1}'.", Level = EventLevel.Error)] public void SpanProcessorException(string evnt, string ex) { @@ -299,6 +308,18 @@ public void LoggerParseStateException(string type, string error) this.WriteEvent(48, type, error); } + [Event(49, Message = "LoggerProviderSdk event: '{0}'", Level = EventLevel.Verbose)] + public void LoggerProviderSdkEvent(string message) + { + this.WriteEvent(49, message); + } + + [Event(50, Message = "Unknown error in LoggerProvider '{0}': '{1}'.", Level = EventLevel.Error)] + public void LoggerProviderException(string methodName, string ex) + { + this.WriteEvent(50, methodName, ex); + } + #if DEBUG public class OpenTelemetryEventListener : EventListener { diff --git a/src/OpenTelemetry/Logs/LoggerProviderSdk.cs b/src/OpenTelemetry/Logs/LoggerProviderSdk.cs index 5b108966aaa..9baf8d47188 100644 --- a/src/OpenTelemetry/Logs/LoggerProviderSdk.cs +++ b/src/OpenTelemetry/Logs/LoggerProviderSdk.cs @@ -16,14 +16,224 @@ #nullable enable +using System.Diagnostics; +using System.Text; +using Microsoft.Extensions.DependencyInjection; +using OpenTelemetry.Internal; +using OpenTelemetry.Resources; + namespace OpenTelemetry.Logs; +/// +/// SDK implementation. +/// internal sealed class LoggerProviderSdk : LoggerProvider { + internal readonly IServiceProvider ServiceProvider; + private readonly IDisposable? ownedServiceProvider; + private readonly List instrumentations = new(); + private ILogRecordPool? threadStaticPool = LogRecordThreadStaticPool.Instance; + private int shutdownCount; + private bool disposed; + public LoggerProviderSdk( IServiceProvider serviceProvider, bool ownsServiceProvider) { + Debug.Assert(serviceProvider != null, "serviceProvider was null"); + + var state = serviceProvider!.GetRequiredService(); + state.RegisterProvider(this); + + this.ServiceProvider = serviceProvider!; + + if (ownsServiceProvider) + { + this.ownedServiceProvider = serviceProvider as IDisposable; + Debug.Assert(this.ownedServiceProvider != null, "ownedServiceProvider was null"); + } + + OpenTelemetrySdkEventSource.Log.LoggerProviderSdkEvent("Building TracerProvider."); + + var configureProviderBuilders = serviceProvider!.GetServices(); + foreach (var configureProviderBuilder in configureProviderBuilders) + { + configureProviderBuilder.ConfigureBuilder(serviceProvider!, state); + } + + var resourceBuilder = state.ResourceBuilder ?? ResourceBuilder.CreateDefault(); + resourceBuilder.ServiceProvider = serviceProvider; + this.Resource = resourceBuilder.Build(); + + foreach (var processor in state.Processors) + { + this.AddProcessor(processor); + } + + StringBuilder instrumentationFactoriesAdded = new StringBuilder(); + + foreach (var instrumentation in state.Instrumentation) + { + this.instrumentations.Add(instrumentation.Instance); + instrumentationFactoriesAdded.Append(instrumentation.Name); + instrumentationFactoriesAdded.Append(';'); + } + + if (instrumentationFactoriesAdded.Length != 0) + { + instrumentationFactoriesAdded.Remove(instrumentationFactoriesAdded.Length - 1, 1); + OpenTelemetrySdkEventSource.Log.LoggerProviderSdkEvent($"Instrumentations added = \"{instrumentationFactoriesAdded}\"."); + } + + OpenTelemetrySdkEventSource.Log.LoggerProviderSdkEvent("LoggerProviderSdk built successfully."); + } + + public Resource Resource { get; } + + public List Instrumentations => this.instrumentations; + + public BaseProcessor? Processor { get; private set; } + + public ILogRecordPool LogRecordPool => this.threadStaticPool ?? LogRecordSharedPool.Current; + + public void AddProcessor(BaseProcessor processor) + { + Guard.ThrowIfNull(processor); + + processor.SetParentProvider(this); + + if (this.threadStaticPool != null && this.ContainsBatchProcessor(processor)) + { + OpenTelemetrySdkEventSource.Log.LoggerProviderSdkEvent("Using shared thread pool."); + + this.threadStaticPool = null; + } + + StringBuilder processorAdded = new StringBuilder(); + + if (this.Processor == null) + { + processorAdded.Append("Setting processor to '"); + processorAdded.Append(processor); + processorAdded.Append('\''); + + this.Processor = processor; + } + else if (this.Processor is CompositeProcessor compositeProcessor) + { + processorAdded.Append("Adding processor '"); + processorAdded.Append(processor); + processorAdded.Append("' to composite processor"); + + compositeProcessor.AddProcessor(processor); + } + else + { + processorAdded.Append("Creating new composite processor and adding new processor '"); + processorAdded.Append(processor); + processorAdded.Append('\''); + + var newCompositeProcessor = new CompositeProcessor(new[] + { + this.Processor, + }); + newCompositeProcessor.SetParentProvider(this); + newCompositeProcessor.AddProcessor(processor); + this.Processor = newCompositeProcessor; + } + + OpenTelemetrySdkEventSource.Log.LoggerProviderSdkEvent($"Completed adding processor = \"{processorAdded}\"."); + } + + public bool ForceFlush(int timeoutMilliseconds = Timeout.Infinite) + { + try + { + return this.Processor?.ForceFlush(timeoutMilliseconds) ?? true; + } + catch (Exception ex) + { + OpenTelemetrySdkEventSource.Log.LoggerProviderException(nameof(this.ForceFlush), ex); + return false; + } + } + + public bool Shutdown(int timeoutMilliseconds) + { + if (Interlocked.Increment(ref this.shutdownCount) > 1) + { + return false; // shutdown already called + } + + try + { + return this.Processor?.Shutdown(timeoutMilliseconds) ?? true; + } + catch (Exception ex) + { + OpenTelemetrySdkEventSource.Log.LoggerProviderException(nameof(this.Shutdown), ex); + return false; + } + } + + public 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 bool TryCreateLogger(string? name, out Logger? logger) + { + // TODO: return new LoggerSdk(this, name); throw new NotImplementedException(); } + + /// + protected override void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + if (this.instrumentations != null) + { + foreach (var item in this.instrumentations) + { + (item as IDisposable)?.Dispose(); + } + + this.instrumentations.Clear(); + } + + // Wait for up to 5 seconds grace period + this.Processor?.Shutdown(5000); + this.Processor?.Dispose(); + + this.ownedServiceProvider?.Dispose(); + } + + this.disposed = true; + OpenTelemetrySdkEventSource.Log.ProviderDisposed(nameof(LoggerProviderSdk)); + } + + base.Dispose(disposing); + } }