diff --git a/src/OpenTelemetry/.publicApi/net452/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net452/PublicAPI.Unshipped.txt index 33ee0c8ec19..9c0d218fcb2 100644 --- a/src/OpenTelemetry/.publicApi/net452/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net452/PublicAPI.Unshipped.txt @@ -244,6 +244,7 @@ static OpenTelemetry.SuppressInstrumentationScope.Enter() -> int static OpenTelemetry.Trace.SamplingResult.operator !=(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool static OpenTelemetry.Trace.SamplingResult.operator ==(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool static OpenTelemetry.Trace.TracerProviderExtensions.AddProcessor(this OpenTelemetry.Trace.TracerProvider provider, OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.Trace.TracerProvider +static OpenTelemetry.Trace.TracerProviderExtensions.Shutdown(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool virtual OpenTelemetry.BaseExporter.Dispose(bool disposing) -> void virtual OpenTelemetry.BaseExporter.OnShutdown(int timeoutMilliseconds) -> bool virtual OpenTelemetry.BaseProcessor.Dispose(bool disposing) -> void diff --git a/src/OpenTelemetry/.publicApi/net46/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net46/PublicAPI.Unshipped.txt index 33ee0c8ec19..9c0d218fcb2 100644 --- a/src/OpenTelemetry/.publicApi/net46/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net46/PublicAPI.Unshipped.txt @@ -244,6 +244,7 @@ static OpenTelemetry.SuppressInstrumentationScope.Enter() -> int static OpenTelemetry.Trace.SamplingResult.operator !=(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool static OpenTelemetry.Trace.SamplingResult.operator ==(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool static OpenTelemetry.Trace.TracerProviderExtensions.AddProcessor(this OpenTelemetry.Trace.TracerProvider provider, OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.Trace.TracerProvider +static OpenTelemetry.Trace.TracerProviderExtensions.Shutdown(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool virtual OpenTelemetry.BaseExporter.Dispose(bool disposing) -> void virtual OpenTelemetry.BaseExporter.OnShutdown(int timeoutMilliseconds) -> bool virtual OpenTelemetry.BaseProcessor.Dispose(bool disposing) -> void diff --git a/src/OpenTelemetry/.publicApi/net461/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net461/PublicAPI.Unshipped.txt index 8e5f12664f1..180dae6a6eb 100644 --- a/src/OpenTelemetry/.publicApi/net461/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net461/PublicAPI.Unshipped.txt @@ -264,6 +264,7 @@ static OpenTelemetry.SuppressInstrumentationScope.Enter() -> int static OpenTelemetry.Trace.SamplingResult.operator !=(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool static OpenTelemetry.Trace.SamplingResult.operator ==(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool static OpenTelemetry.Trace.TracerProviderExtensions.AddProcessor(this OpenTelemetry.Trace.TracerProvider provider, OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.Trace.TracerProvider +static OpenTelemetry.Trace.TracerProviderExtensions.Shutdown(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool virtual OpenTelemetry.BaseExporter.Dispose(bool disposing) -> void virtual OpenTelemetry.BaseExporter.OnShutdown(int timeoutMilliseconds) -> bool virtual OpenTelemetry.BaseProcessor.Dispose(bool disposing) -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 8e5f12664f1..180dae6a6eb 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -264,6 +264,7 @@ static OpenTelemetry.SuppressInstrumentationScope.Enter() -> int static OpenTelemetry.Trace.SamplingResult.operator !=(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool static OpenTelemetry.Trace.SamplingResult.operator ==(OpenTelemetry.Trace.SamplingResult decision1, OpenTelemetry.Trace.SamplingResult decision2) -> bool static OpenTelemetry.Trace.TracerProviderExtensions.AddProcessor(this OpenTelemetry.Trace.TracerProvider provider, OpenTelemetry.BaseProcessor processor) -> OpenTelemetry.Trace.TracerProvider +static OpenTelemetry.Trace.TracerProviderExtensions.Shutdown(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool virtual OpenTelemetry.BaseExporter.Dispose(bool disposing) -> void virtual OpenTelemetry.BaseExporter.OnShutdown(int timeoutMilliseconds) -> bool virtual OpenTelemetry.BaseProcessor.Dispose(bool disposing) -> void diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 6de4e181edd..efa00f629cc 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -14,6 +14,8 @@ * `ActivitySourceAdapter` supports setting `ActivitySource` for Activities created without `ActivitySource`. ([#1515](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1515/)) + * Implemented Shutdown for TracerProvider + ([#1489](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1489)) ## 0.8.0-beta.1 diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index 01fc3484a59..bca59bb3ccd 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -116,6 +116,15 @@ public void SelfDiagnosticsFileCreateException(string logDirectory, Exception ex } } + [NonEvent] + public void TracerProviderException(string evnt, Exception ex) + { + if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + { + this.TracerProviderException(evnt, ex.ToInvariantString()); + } + } + [Event(1, Message = "Span processor queue size reached maximum. Throttling spans.", Level = EventLevel.Warning)] public void SpanProcessorQueueIsExhausted() { @@ -272,6 +281,12 @@ public void ResourceDetectorFailed(string resourceDetector, string issue) this.WriteEvent(27, resourceDetector, issue); } + [Event(28, Message = "Unknown error in TracerProvider '{0}': '{1}'.", Level = EventLevel.Warning)] + public void TracerProviderException(string evnt, string ex) + { + this.WriteEvent(28, evnt, ex); + } + #if DEBUG public class OpenTelemetryEventListener : EventListener { diff --git a/src/OpenTelemetry/Trace/TracerProviderExtensions.cs b/src/OpenTelemetry/Trace/TracerProviderExtensions.cs index f936943e00a..302f85d5173 100644 --- a/src/OpenTelemetry/Trace/TracerProviderExtensions.cs +++ b/src/OpenTelemetry/Trace/TracerProviderExtensions.cs @@ -16,6 +16,8 @@ using System; using System.Diagnostics; +using System.Threading; +using OpenTelemetry.Internal; namespace OpenTelemetry.Trace { @@ -40,5 +42,57 @@ public static TracerProvider AddProcessor(this TracerProvider provider, BaseProc return provider; } + + /// + /// Attempts to shutdown the TracerProviderSdk, blocks the current thread until + /// shutdown completed or timed out. + /// + /// TracerProviderSdk instance on which Shutdown will be called. + /// + /// The number of milliseconds to wait, or Timeout.Infinite to + /// wait indefinitely. + /// + /// + /// Returns true when shutdown succeeded; otherwise, false. + /// + /// + /// Thrown when the timeoutMilliseconds is smaller than -1. + /// + /// + /// This function guarantees thread-safety. Only the first call will + /// win, subsequent calls will be no-op. + /// + public static bool Shutdown(this TracerProvider provider, int timeoutMilliseconds = Timeout.Infinite) + { + if (provider == null) + { + throw new ArgumentNullException(nameof(provider)); + } + + if (provider is TracerProviderSdk tracerProviderSdk) + { + if (timeoutMilliseconds < 0 && timeoutMilliseconds != Timeout.Infinite) + { + throw new ArgumentOutOfRangeException(nameof(timeoutMilliseconds), timeoutMilliseconds, "timeoutMilliseconds should be non-negative."); + } + + if (Interlocked.Increment(ref tracerProviderSdk.ShutdownCount) > 1) + { + return false; // shutdown already called + } + + try + { + return tracerProviderSdk.OnShutdown(timeoutMilliseconds); + } + catch (Exception ex) + { + OpenTelemetrySdkEventSource.Log.TracerProviderException(nameof(tracerProviderSdk.OnShutdown), ex); + return false; + } + } + + return true; + } } } diff --git a/src/OpenTelemetry/Trace/TracerProviderSdk.cs b/src/OpenTelemetry/Trace/TracerProviderSdk.cs index 044058a4276..61c75fbfcaf 100644 --- a/src/OpenTelemetry/Trace/TracerProviderSdk.cs +++ b/src/OpenTelemetry/Trace/TracerProviderSdk.cs @@ -20,6 +20,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using System.Threading; using OpenTelemetry.Internal; using OpenTelemetry.Resources; @@ -27,6 +28,8 @@ namespace OpenTelemetry.Trace { internal class TracerProviderSdk : TracerProvider { + internal int ShutdownCount; + private readonly List instrumentations = new List(); private readonly ActivityListener listener; private readonly Sampler sampler; @@ -196,6 +199,41 @@ internal TracerProviderSdk AddProcessor(BaseProcessor processor) return this; } + /// + /// Called by Shutdown. This function should block the current + /// thread until shutdown completed or timed out. + /// + /// + /// The number of milliseconds to wait, or Timeout.Infinite to + /// wait indefinitely. + /// + /// + /// Returns true when shutdown succeeded; otherwise, false. + /// + /// + /// This function is called synchronously on the thread which made the + /// first call to Shutdown. This function should not throw + /// exceptions. + /// + internal bool OnShutdown(int timeoutMilliseconds) + { + // TO DO Put OnShutdown logic in a task to run within the user provider timeOutMilliseconds + bool? result; + if (this.instrumentations != null) + { + foreach (var item in this.instrumentations) + { + (item as IDisposable)?.Dispose(); + } + + this.instrumentations.Clear(); + } + + result = this.processor?.Shutdown(timeoutMilliseconds); + this.listener?.Dispose(); + return result ?? true; + } + protected override void Dispose(bool disposing) { if (this.instrumentations != null)