From ea789aa489176dcbbd511a8461e9aabe6226431f Mon Sep 17 00:00:00 2001 From: "Tristan (HouseCat) Hyams" Date: Sat, 30 Mar 2024 12:06:23 -0500 Subject: [PATCH] Switching DataFlows to OpenTelemetry vs. IMetricsProvider. --- RabbitMQ.Dataflows.sln | 7 + src/HouseofCat.Dataflows/BaseDataflow.cs | 65 ++++----- .../Extensions/WorkStateExtensions.cs | 137 ++++++++++++++++++ src/HouseofCat.Dataflows/IWorkState.cs | 7 +- .../HouseofCat.Metrics.csproj | 1 + src/HouseofCat.Metrics/IMetricsProvider.cs | 57 +++++++- src/HouseofCat.Metrics/NullMetricsProvider.cs | 31 +++- .../OpenTelemetryMetricsProvider.cs | 90 +++++++++++- .../Prometheus/PrometheusMetricsProvider.cs | 29 ++++ .../Dataflows/ConsumerDataflow.cs | 120 ++++++++------- .../Dataflows/RabbitWorkState.cs | 3 + .../Extensions/MessageExtensions.cs | 2 +- src/HouseofCat.RabbitMQ/Messages/Letter.cs | 28 ++-- .../OpenTelemetry.Console.Tests.csproj | 19 +++ tests/OpenTelemetry.Console.Tests/Program.cs | 26 ++++ .../Tests/SpanTests.cs | 94 ++++++++++++ version.props | 6 +- 17 files changed, 596 insertions(+), 126 deletions(-) create mode 100644 src/HouseofCat.Dataflows/Extensions/WorkStateExtensions.cs create mode 100644 tests/OpenTelemetry.Console.Tests/OpenTelemetry.Console.Tests.csproj create mode 100644 tests/OpenTelemetry.Console.Tests/Program.cs create mode 100644 tests/OpenTelemetry.Console.Tests/Tests/SpanTests.cs diff --git a/RabbitMQ.Dataflows.sln b/RabbitMQ.Dataflows.sln index 6a292ccf..8f19ad99 100644 --- a/RabbitMQ.Dataflows.sln +++ b/RabbitMQ.Dataflows.sln @@ -86,6 +86,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rabbitmq", "rabbitmq", "{5C guides\rabbitmq\Serialization.md = guides\rabbitmq\Serialization.md EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Console.Tests", "tests\OpenTelemetry.Console.Tests\OpenTelemetry.Console.Tests.csproj", "{077E07C3-9A35-42A0-8228-E9778F02DFCE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -136,6 +138,10 @@ Global {528C015E-58FC-44B4-BAC5-05BCFCD506C9}.Debug|Any CPU.Build.0 = Debug|Any CPU {528C015E-58FC-44B4-BAC5-05BCFCD506C9}.Release|Any CPU.ActiveCfg = Release|Any CPU {528C015E-58FC-44B4-BAC5-05BCFCD506C9}.Release|Any CPU.Build.0 = Release|Any CPU + {077E07C3-9A35-42A0-8228-E9778F02DFCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {077E07C3-9A35-42A0-8228-E9778F02DFCE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {077E07C3-9A35-42A0-8228-E9778F02DFCE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {077E07C3-9A35-42A0-8228-E9778F02DFCE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -160,6 +166,7 @@ Global {0930ACF5-372E-49AF-B53F-A06C3A5FA997} = {D01D59E9-75CC-4C74-A887-22B0A55C994B} {528C015E-58FC-44B4-BAC5-05BCFCD506C9} = {D01D59E9-75CC-4C74-A887-22B0A55C994B} {5C94F432-8229-4FFA-8DBF-AC1DEDE66265} = {1AB5E832-AD36-40AF-A7E9-A105A778116C} + {077E07C3-9A35-42A0-8228-E9778F02DFCE} = {D01D59E9-75CC-4C74-A887-22B0A55C994B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D7099845-267C-4232-8764-5DF25D6B2C79} diff --git a/src/HouseofCat.Dataflows/BaseDataflow.cs b/src/HouseofCat.Dataflows/BaseDataflow.cs index e34e1bbb..c4d62c55 100644 --- a/src/HouseofCat.Dataflows/BaseDataflow.cs +++ b/src/HouseofCat.Dataflows/BaseDataflow.cs @@ -1,7 +1,8 @@ using HouseofCat.Compression; +using HouseofCat.Dataflows.Extensions; using HouseofCat.Encryption; -using HouseofCat.Metrics; using HouseofCat.Serialization; +using OpenTelemetry.Trace; using System; using System.Runtime.ExceptionServices; using System.Threading.Tasks; @@ -17,7 +18,6 @@ namespace HouseofCat.Dataflows; protected ISerializationProvider _serializationProvider; protected IEncryptionProvider _encryptionProvider; protected ICompressionProvider _compressProvider; - protected IMetricsProvider _metricsProvider; protected ISourceBlock _currentBlock; public Task Completion { get; protected set; } @@ -60,20 +60,20 @@ public TransformBlock GetTransformBlock( public TransformBlock GetWrappedTransformBlock( Func action, ExecutionDataflowBlockOptions options, - string metricIdentifier, - bool metricMicroScale = false, - string metricUnit = null, - string metricDescription = null) + string metricIdentifier) { TState WrapAction(TState state) { + using var childSpan = state.CreateActiveSpan(metricIdentifier, SpanKind.Consumer); try { - using var multiDispose = _metricsProvider.TrackAndDuration(metricIdentifier, metricMicroScale, metricUnit, metricDescription, state.MetricTags); + return action(state); } catch (Exception ex) { + childSpan?.SetStatus(Status.Error.WithDescription(ex.Message)); + childSpan?.RecordException(ex); state.IsFaulted = true; state.EDI = ExceptionDispatchInfo.Capture(ex); return state; @@ -86,20 +86,19 @@ TState WrapAction(TState state) public TransformBlock GetWrappedTransformBlock( Func> action, ExecutionDataflowBlockOptions options, - string metricIdentifier, - bool metricMicroScale = false, - string metricUnit = null, - string metricDescription = null) + string metricIdentifier) { async Task WrapActionAsync(TState state) { + using var childSpan = state.CreateActiveSpan(metricIdentifier, SpanKind.Consumer); try { - using var multiDispose = _metricsProvider.TrackAndDuration(metricIdentifier, metricMicroScale, metricUnit, metricDescription, state.MetricTags); return await action(state).ConfigureAwait(false); } catch (Exception ex) { + childSpan?.SetStatus(Status.Error.WithDescription(ex.Message)); + childSpan?.RecordException(ex); state.IsFaulted = true; state.EDI = ExceptionDispatchInfo.Capture(ex); return state; @@ -112,20 +111,20 @@ async Task WrapActionAsync(TState state) public ActionBlock GetWrappedActionBlock( Action action, ExecutionDataflowBlockOptions options, - string metricIdentifier, - bool metricMicroScale = false, - string metricUnit = null, - string metricDescription = null) + string spanName) { void WrapAction(TState state) { + using var childSpan = state.CreateActiveSpan(spanName, SpanKind.Consumer); try { - using var multiDispose = _metricsProvider.TrackAndDuration(metricIdentifier, metricMicroScale, metricUnit, metricDescription, state.MetricTags); action(state); } - catch - { /* Actions are terminating block, so swallow (maybe log) */ } + catch (Exception ex) + { + childSpan?.SetStatus(Status.Error.WithDescription(ex.Message)); + childSpan?.RecordException(ex); + } } return new ActionBlock(WrapAction, options); @@ -134,20 +133,20 @@ void WrapAction(TState state) public ActionBlock GetWrappedActionBlock( Func action, ExecutionDataflowBlockOptions options, - string metricIdentifier, - bool metricMicroScale = false, - string metricUnit = null, - string metricDescription = null) + string spanName) { void WrapAction(TState state) { + using var childSpan = state.CreateActiveSpan(spanName, SpanKind.Consumer); try { - using var multiDispose = _metricsProvider.TrackAndDuration(metricIdentifier, metricMicroScale, metricUnit, metricDescription, state.MetricTags); action(state); } - catch - { /* Actions are terminating block, so swallow (maybe log) */ } + catch (Exception ex) + { + childSpan?.SetStatus(Status.Error.WithDescription(ex.Message)); + childSpan?.RecordException(ex); + } } return new ActionBlock(WrapAction, options); @@ -156,20 +155,20 @@ void WrapAction(TState state) public ActionBlock GetWrappedActionBlock( Func action, ExecutionDataflowBlockOptions options, - string metricIdentifier, - bool metricMicroScale = false, - string metricUnit = null, - string metricDescription = null) + string spanName) { async Task WrapActionAsync(TState state) { + using var childSpan = state.CreateActiveSpan(spanName, SpanKind.Consumer); try { - using var multiDispose = _metricsProvider.TrackAndDuration(metricIdentifier, metricMicroScale, metricUnit, metricDescription, state.MetricTags); await action(state).ConfigureAwait(false); } - catch - { /* Actions are terminating block, so swallow (maybe log) */ } + catch (Exception ex) + { + childSpan?.SetStatus(Status.Error.WithDescription(ex.Message)); + childSpan?.RecordException(ex); + } } return new ActionBlock(WrapActionAsync, options); diff --git a/src/HouseofCat.Dataflows/Extensions/WorkStateExtensions.cs b/src/HouseofCat.Dataflows/Extensions/WorkStateExtensions.cs new file mode 100644 index 00000000..47ee4e8a --- /dev/null +++ b/src/HouseofCat.Dataflows/Extensions/WorkStateExtensions.cs @@ -0,0 +1,137 @@ +using OpenTelemetry.Trace; +using System.Diagnostics; + +namespace HouseofCat.Dataflows.Extensions; + +public static class WorkStateExtensions +{ + public static void SetOpenTelemetryError(this IWorkState state, string message = null) + { + if (state is null) return; + state.SetCurrentActivityAsError(message); + + if (state.RootSpan is null) return; + state.SetCurrentSpanAsError(message); + } + + public static void SetCurrentActivityAsError(this IWorkState state, string message = null) + { + var activity = Activity.Current; + if (activity is null) return; + + activity.SetStatus(ActivityStatusCode.Error, message ?? state.EDI.SourceException?.Message); + if (state?.EDI is not null) + { + if (state.EDI.SourceException is not null) + { + activity.RecordException(state.EDI.SourceException); + } + } + else + { + activity.SetStatus(ActivityStatusCode.Error, message); + } + } + + public static void SetCurrentSpanAsError(this IWorkState state, string message = null) + { + var span = Tracer.CurrentSpan; + if (span is null) return; + + span.SetStatus(Status.Error); + span.SetAttribute("Error", message); + if (state?.EDI is not null) + { + if (state.EDI.SourceException is not null) + { + span.RecordException(state.EDI.SourceException); + } + } + } + + public static string OpenTelemetryDefaultProviderTraceSourceNameKey { get; set; } = "OpenTelemetryTraceSourceName"; + public static string OpenTelemetryDefaultProviderTracerServiceVersionKey { get; set; } = "OpenTelemetryTraceSourceVersion"; + + private static readonly string _defaultProviderTracerSourceName = "HouseofCat.Dataflows"; + + public static void SetWorkflowNameAsOpenTelemetrySourceName( + this IWorkState state, + string workflowName, + string version = null) + { + state.Data[OpenTelemetryDefaultProviderTraceSourceNameKey] = workflowName ?? _defaultProviderTracerSourceName; + state.Data[OpenTelemetryDefaultProviderTracerServiceVersionKey] = version; + } + + public static void StartRootSpan( + this IWorkState state, + string spanName, + SpanKind spanKind = SpanKind.Internal, + SpanAttributes spanAttributes = null) + { + if (state.Data.TryGetValue(OpenTelemetryDefaultProviderTraceSourceNameKey, out var workflowName)) + { + state.Data.TryGetValue(OpenTelemetryDefaultProviderTracerServiceVersionKey, out var version); + + state.RootSpan = TracerProvider + .Default + ?.GetTracer( + workflowName?.ToString() ?? _defaultProviderTracerSourceName, + version?.ToString()) + ?.StartRootSpan(spanName, spanKind, initialAttributes: spanAttributes); + } + else + { + state.RootSpan = TracerProvider + .Default + ?.GetTracer(_defaultProviderTracerSourceName, null) + ?.StartRootSpan(spanName, spanKind, initialAttributes: spanAttributes); + } + } + + public static TelemetrySpan CreateActiveSpan( + this IWorkState state, + string spanName, + SpanKind spanKind = SpanKind.Internal, + SpanAttributes spanAttributes = null) + { + if (state.Data.TryGetValue(OpenTelemetryDefaultProviderTraceSourceNameKey, out var workflowName)) + { + state.Data.TryGetValue(OpenTelemetryDefaultProviderTracerServiceVersionKey, out var version); + + return TracerProvider + .Default + ?.GetTracer( + workflowName?.ToString() ?? _defaultProviderTracerSourceName, + version?.ToString()) + ?.StartActiveSpan( + spanName, + spanKind, + parentContext: state.RootSpan?.Context ?? default, + initialAttributes: spanAttributes); + } + else + { + return TracerProvider + .Default + ?.GetTracer(_defaultProviderTracerSourceName, null) + ?.StartActiveSpan( + spanName, + spanKind, + parentContext: state.RootSpan?.Context ?? default, + initialAttributes: spanAttributes); + } + } + + public static void EndRootSpan( + this IWorkState state, + bool includeErrorWhenFaulted = false) + { + if (includeErrorWhenFaulted && state.IsFaulted) + { + state.SetOpenTelemetryError(); + } + state?.RootSpan?.End(); + state?.RootSpan?.Dispose(); + } +} diff --git a/src/HouseofCat.Dataflows/IWorkState.cs b/src/HouseofCat.Dataflows/IWorkState.cs index 23e7450d..adfdba54 100644 --- a/src/HouseofCat.Dataflows/IWorkState.cs +++ b/src/HouseofCat.Dataflows/IWorkState.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using OpenTelemetry.Trace; +using System.Collections.Generic; using System.Runtime.ExceptionServices; namespace HouseofCat.Dataflows; @@ -18,6 +19,6 @@ public interface IWorkState // Outbound byte[] SendData { get; set; } - // Metrics - IDictionary MetricTags { get; set; } + // RootSpan + TelemetrySpan RootSpan { get; set; } } diff --git a/src/HouseofCat.Metrics/HouseofCat.Metrics.csproj b/src/HouseofCat.Metrics/HouseofCat.Metrics.csproj index 2482b37d..b3a00626 100644 --- a/src/HouseofCat.Metrics/HouseofCat.Metrics.csproj +++ b/src/HouseofCat.Metrics/HouseofCat.Metrics.csproj @@ -1,6 +1,7 @@  + diff --git a/src/HouseofCat.Metrics/IMetricsProvider.cs b/src/HouseofCat.Metrics/IMetricsProvider.cs index 3f9dd436..1bfa668e 100644 --- a/src/HouseofCat.Metrics/IMetricsProvider.cs +++ b/src/HouseofCat.Metrics/IMetricsProvider.cs @@ -1,5 +1,7 @@ -using System; +using OpenTelemetry.Trace; +using System; using System.Collections.Generic; +using System.Diagnostics; namespace HouseofCat.Metrics; @@ -11,8 +13,57 @@ public interface IMetricsProvider void IncrementGauge(string name, string unit = null, string description = null); void ObserveValue(string name, double value, string unit = null, string description = null); void ObserveValueFluctuation(string name, double value, string unit = null, string description = null); + IDisposable Duration(string name, bool microScale = false, string unit = null, string description = null); + IDisposable Track(string name, string unit = null, string description = null); - IDisposable TrackAndDuration(string name, bool microScale = false, string unit = null, string description = null, IDictionary metricTags = null); - IDisposable Trace(string name, IDictionary metricTags = null); + + IDisposable TrackAndDuration( + string name, + bool microScale = false, + string unit = null, + string description = null, + ActivityKind activityKind = ActivityKind.Internal, + IDictionary metricTags = null); + + IDisposable Trace( + string name, + ActivityKind activityKind = ActivityKind.Internal, + IDictionary metricTags = null); + + /// + /// Creates a new active span. + /// + /// + /// + /// + /// + IDisposable GetSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + IDictionary metricTags = null); + + /// + /// Attempts to create a new active child span from CURRENT active span or just a new active span if no current span. + /// + /// + /// + /// + /// + IDisposable GetChildSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + IDictionary metricTags = null); + + /// + /// Creates a new active child span from provided span context. + /// + /// + /// + /// + /// + IDisposable GetChildSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + SpanContext parentSpanContext = default); } diff --git a/src/HouseofCat.Metrics/NullMetricsProvider.cs b/src/HouseofCat.Metrics/NullMetricsProvider.cs index 69764826..30addafb 100644 --- a/src/HouseofCat.Metrics/NullMetricsProvider.cs +++ b/src/HouseofCat.Metrics/NullMetricsProvider.cs @@ -1,5 +1,7 @@ -using System; +using OpenTelemetry.Trace; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace HouseofCat.Metrics; @@ -48,6 +50,7 @@ public IDisposable TrackAndDuration( bool microScale = false, string unit = null, string description = null, + ActivityKind activityKind = ActivityKind.Internal, IDictionary metricTags = null) { return null; @@ -56,8 +59,34 @@ public IDisposable TrackAndDuration( [MethodImpl(MethodImplOptions.AggressiveInlining)] public IDisposable Trace( string name, + ActivityKind activityKind = ActivityKind.Internal, IDictionary metricTags = null) { return null; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IDisposable GetSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + IDictionary metricTags = null) + { + return null; + } + + public IDisposable GetChildSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + IDictionary metricTags = null) + { + return null; + } + + public IDisposable GetChildSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + SpanContext parentSpanContext = default) + { + return null; + } } diff --git a/src/HouseofCat.Metrics/OpenTelemetry/OpenTelemetryMetricsProvider.cs b/src/HouseofCat.Metrics/OpenTelemetry/OpenTelemetryMetricsProvider.cs index 72670dfd..bdccb679 100644 --- a/src/HouseofCat.Metrics/OpenTelemetry/OpenTelemetryMetricsProvider.cs +++ b/src/HouseofCat.Metrics/OpenTelemetry/OpenTelemetryMetricsProvider.cs @@ -1,4 +1,5 @@ using HouseofCat.Utilities.Errors; +using OpenTelemetry.Trace; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -14,6 +15,7 @@ public class OpenTelemetryMetricsProvider : IMetricsProvider, IDisposable private readonly Meter _meter; private readonly string _meterName; private readonly ActivitySource _activitySource; + private readonly Tracer _tracer; public ConcurrentDictionary Counters { get; } = new ConcurrentDictionary(); public ConcurrentDictionary Gauges { get; } = new ConcurrentDictionary(); @@ -30,6 +32,7 @@ public OpenTelemetryMetricsProvider(IMeterFactory meterFactory, string meterName _meterName = meterName; _meter = _factory.Create(_meterName); + _tracer = TracerProvider.Default.GetTracer(activitySourceName); _activitySource = new ActivitySource(activitySourceName ?? "HouseofCat.Metrics", activityVersion); } @@ -143,25 +146,37 @@ public IDisposable TrackAndDuration( bool microScale = false, string unit = null, string description = null, + ActivityKind activityKind = ActivityKind.Internal, IDictionary tags = null) { - return GetActivity(name, tags); + return GetActivity(name, activityKind, tags); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public IDisposable Trace( string name, + ActivityKind activityKind = ActivityKind.Internal, IDictionary metricTags = null) { - return GetActivity(name, metricTags); + return GetActivity(name, activityKind, metricTags); } - private Activity GetActivity(string name, IDictionary metricTags) + private Activity GetActivity( + string name, + ActivityKind activityKind = ActivityKind.Internal, + IDictionary metricTags = null) { - var activity = _activitySource.StartActivity(name); - if (activity is not null - && metricTags is not null - && activity.IsAllDataRequested) + var parentActivity = Activity.Current; + + Activity activity; + if (parentActivity is not null) + { activity = _activitySource?.StartActivity(name, activityKind, parentActivity.Context); } + else + { activity = _activitySource?.StartActivity(name, activityKind); } + + if (activity is null) return null; + + if (metricTags is not null && activity.IsAllDataRequested) { foreach (var tag in metricTags) { @@ -172,5 +187,66 @@ private Activity GetActivity(string name, IDictionary metricTags return activity; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IDisposable GetSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + IDictionary metricTags = null) + { + return GetDefaultTelemetrySpan(name, false, spanKind, metricTags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IDisposable GetChildSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + IDictionary metricTags = null) + { + return GetDefaultTelemetrySpan(name, true, spanKind, metricTags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IDisposable GetChildSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + SpanContext parentSpanContext = default) + { + return GetChildTelemetrySpan(name, spanKind, parentSpanContext); + } + + private TelemetrySpan GetDefaultTelemetrySpan( + string name, + bool childSpanOfCurrent = false, + SpanKind spanKind = SpanKind.Internal, + IDictionary metricTags = null) + { + if (childSpanOfCurrent) + { + var parentSpan = Tracer.CurrentSpan; + if (parentSpan is not null) return _tracer?.StartActiveSpan(name, spanKind, parentSpan.Context); + } + + var span = _tracer?.StartActiveSpan(name); + if (span is null) return null; + + if (metricTags is not null) + { + foreach (var tag in metricTags) + { + span.SetAttribute(tag.Key, tag.Value); + } + } + + return span; + } + + private TelemetrySpan GetChildTelemetrySpan( + string name, + SpanKind spanKind = SpanKind.Internal, + SpanContext parentSpanContext = default) + { + return _tracer?.StartActiveSpan(name, spanKind, parentSpanContext); + } + #endregion } diff --git a/src/HouseofCat.Metrics/Prometheus/PrometheusMetricsProvider.cs b/src/HouseofCat.Metrics/Prometheus/PrometheusMetricsProvider.cs index 3aad3c95..75465dbd 100644 --- a/src/HouseofCat.Metrics/Prometheus/PrometheusMetricsProvider.cs +++ b/src/HouseofCat.Metrics/Prometheus/PrometheusMetricsProvider.cs @@ -1,11 +1,13 @@ using HouseofCat.Utilities; using HouseofCat.Utilities.Errors; +using OpenTelemetry.Trace; using Prometheus; using Prometheus.DotNetRuntime; using Prometheus.DotNetRuntime.Metrics.Producers; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace HouseofCat.Metrics; @@ -191,6 +193,7 @@ public IDisposable TrackAndDuration( bool microScale = false, string unit = null, string description = null, + ActivityKind activityKind = ActivityKind.Internal, IDictionary metricTags = null) { var duration = Duration(name, microScale, description: description); @@ -201,10 +204,36 @@ public IDisposable TrackAndDuration( [MethodImpl(MethodImplOptions.AggressiveInlining)] public IDisposable Trace( string name, + ActivityKind activityKind = ActivityKind.Internal, IDictionary metricTags = null) { return null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IDisposable GetSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + IDictionary metricTags = null) + { + return null; + } + + public IDisposable GetChildSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + IDictionary metricTags = null) + { + return null; + } + + public IDisposable GetChildSpan( + string name, + SpanKind spanKind = SpanKind.Internal, + SpanContext parentSpanContext = default) + { + return null; + } + #endregion } diff --git a/src/HouseofCat.RabbitMQ/Dataflows/ConsumerDataflow.cs b/src/HouseofCat.RabbitMQ/Dataflows/ConsumerDataflow.cs index 0e43ecf0..fc961d7d 100644 --- a/src/HouseofCat.RabbitMQ/Dataflows/ConsumerDataflow.cs +++ b/src/HouseofCat.RabbitMQ/Dataflows/ConsumerDataflow.cs @@ -1,16 +1,18 @@ using HouseofCat.Compression; using HouseofCat.Dataflows; +using HouseofCat.Dataflows.Extensions; using HouseofCat.Encryption; -using HouseofCat.Metrics; using HouseofCat.RabbitMQ.Services; using HouseofCat.Serialization; using HouseofCat.Utilities.Errors; +using OpenTelemetry.Trace; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; +using static HouseofCat.Dataflows.Extensions.WorkStateExtensions; namespace HouseofCat.RabbitMQ.Dataflows; @@ -208,12 +210,6 @@ public ConsumerDataflow SetEncryptionProvider(IEncryptionProvider provid return this; } - public ConsumerDataflow SetMetricsProvider(IMetricsProvider provider) - { - _metricsProvider = provider ?? new NullMetricsProvider(); - return this; - } - #region Step Adders protected virtual ITargetBlock CreateTargetBlock( @@ -237,7 +233,7 @@ public ConsumerDataflow WithErrorHandling( { _errorBuffer = CreateTargetBlock(boundedCapacity, taskScheduler); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); - _errorAction = GetWrappedActionBlock(action, executionOptions, $"{WorkflowName}_ErrorHandler", false); + _errorAction = GetWrappedActionBlock(action, executionOptions, $"{WorkflowName}_ErrorHandler"); } return this; } @@ -254,7 +250,7 @@ public ConsumerDataflow WithErrorHandling( { _errorBuffer = CreateTargetBlock(boundedCapacity, taskScheduler); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); - _errorAction = GetWrappedActionBlock(action, executionOptions, $"{WorkflowName}_ErrorHandler", false); + _errorAction = GetWrappedActionBlock(action, executionOptions, $"{WorkflowName}_ErrorHandler"); } return this; } @@ -267,35 +263,31 @@ public ConsumerDataflow WithReadyToProcessBuffer(int boundedCapacity, Ta public ConsumerDataflow AddStep( Func suppliedStep, - string metricIdentifier, - bool metricMicroScale = false, - string metricDescription = null, + string spanName, int? maxDoP = null, bool? ensureOrdered = null, int? boundedCapacity = null, TaskScheduler taskScheduler = null) { Guard.AgainstNull(suppliedStep, nameof(suppliedStep)); - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps", metricDescription); + var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); - _suppliedTransforms.Add(GetWrappedTransformBlock(suppliedStep, executionOptions, metricIdentifier, metricMicroScale)); + _suppliedTransforms.Add(GetWrappedTransformBlock(suppliedStep, executionOptions, spanName)); return this; } public ConsumerDataflow AddStep( Func> suppliedStep, - string metricIdentifier, - bool metricMicroScale = false, - string metricDescription = null, + string spanName, int? maxDoP = null, bool? ensureOrdered = null, int? boundedCapacity = null, TaskScheduler taskScheduler = null) { Guard.AgainstNull(suppliedStep, nameof(suppliedStep)); - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps", metricDescription); + var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); - _suppliedTransforms.Add(GetWrappedTransformBlock(suppliedStep, executionOptions, metricIdentifier, metricMicroScale)); + _suppliedTransforms.Add(GetWrappedTransformBlock(suppliedStep, executionOptions, spanName)); return this; } @@ -316,9 +308,8 @@ public ConsumerDataflow WithFinalization( Guard.AgainstNull(action, nameof(action)); if (_finalization == null) { - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps"); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); - _finalization = GetWrappedActionBlock(action, executionOptions, $"{WorkflowName}_Finalization", true); + _finalization = GetWrappedActionBlock(action, executionOptions, $"{WorkflowName}_Finalization"); } return this; } @@ -333,9 +324,8 @@ public ConsumerDataflow WithFinalization( Guard.AgainstNull(action, nameof(action)); if (_finalization == null) { - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps"); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); - _finalization = GetWrappedActionBlock(action, executionOptions, $"{WorkflowName}_Finalization", true); + _finalization = GetWrappedActionBlock(action, executionOptions, $"{WorkflowName}_Finalization"); } return this; } @@ -347,11 +337,12 @@ public ConsumerDataflow WithBuildState( int? boundedCapacity = null, TaskScheduler taskScheduler = null) { + Guard.AgainstNullOrEmpty(stateKey, nameof(stateKey)); + if (_buildStateBlock == null) { - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps"); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); - _buildStateBlock = GetBuildStateBlock(_serializationProvider, stateKey, executionOptions); + _buildStateBlock = GetBuildStateBlock(stateKey, executionOptions); } return this; } @@ -365,7 +356,6 @@ public ConsumerDataflow WithDecryptionStep( Guard.AgainstNull(_encryptionProvider, nameof(_encryptionProvider)); if (_decryptBlock == null) { - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps"); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); _decryptBlock = GetByteManipulationTransformBlock( @@ -373,8 +363,7 @@ public ConsumerDataflow WithDecryptionStep( executionOptions, false, x => x.ReceivedData.Encrypted, - $"{WorkflowName}_Decrypt", - true); + $"{WorkflowName}_Decrypt"); } return this; } @@ -388,7 +377,6 @@ public ConsumerDataflow WithDecompressionStep( Guard.AgainstNull(_compressProvider, nameof(_compressProvider)); if (_decompressBlock == null) { - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps"); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); _decompressBlock = GetByteManipulationTransformBlock( @@ -396,8 +384,7 @@ public ConsumerDataflow WithDecompressionStep( executionOptions, false, x => x.ReceivedData.Compressed, - $"{WorkflowName}_Decompress", - true); + $"{WorkflowName}_Decompress"); } return this; @@ -412,7 +399,6 @@ public ConsumerDataflow WithCreateSendLetter( { if (_createSendLetter == null) { - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps"); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); _createSendLetter = GetWrappedTransformBlock(createLetter, executionOptions, $"{WorkflowName}_CreateSendLetter"); } @@ -428,7 +414,6 @@ public ConsumerDataflow WithCompression( Guard.AgainstNull(_compressProvider, nameof(_compressProvider)); if (_compressBlock == null) { - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps"); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); _compressBlock = GetByteManipulationTransformBlock( @@ -436,8 +421,7 @@ public ConsumerDataflow WithCompression( executionOptions, true, x => !x.ReceivedData.Compressed, - $"{WorkflowName}_Compress", - true); + $"{WorkflowName}_Compress"); } return this; } @@ -451,7 +435,6 @@ public ConsumerDataflow WithEncryption( Guard.AgainstNull(_encryptionProvider, nameof(_encryptionProvider)); if (_encryptBlock == null) { - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps"); var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); _encryptBlock = GetByteManipulationTransformBlock( @@ -459,8 +442,7 @@ public ConsumerDataflow WithEncryption( executionOptions, true, x => !x.ReceivedData.Encrypted, - $"{WorkflowName}_Encrypt", - true); + $"{WorkflowName}_Encrypt"); } return this; } @@ -471,7 +453,6 @@ public ConsumerDataflow WithSendStep( int? boundedCapacity = null, TaskScheduler taskScheduler = null) { - _metricsProvider.IncrementCounter($"{WorkflowName}_Steps"); if (_sendLetterBlock == null) { var executionOptions = GetExecuteStepOptions(maxDoP, ensureOrdered, boundedCapacity, taskScheduler ?? _taskScheduler); @@ -601,8 +582,9 @@ private void LinkWithFaultRoute(ISourceBlock source, IPropagatorBlock $"{WorkflowName}_StateBuild"; - public virtual TState BuildState(ISerializationProvider provider, string key, ReceivedData data) + private static string RootSpanIdentifier => "{0}_RootSpan_{1}"; + + public virtual TState BuildState(string key, ReceivedData data) { var state = new TState { @@ -611,26 +593,44 @@ public virtual TState BuildState(ISerializationProvider provider, string k }; // If the SerializationProvider was assigned, use it, else it's raw bytes. - if (provider != null) - { state.Data[key] = provider.Deserialize(data.Data); } + if (_serializationProvider != null) + { state.Data[key] = _serializationProvider.Deserialize(data.Data); } else { state.Data[key] = data.Data; } + state.SetWorkflowNameAsOpenTelemetrySourceName(WorkflowName); + + var spanAttributes = new SpanAttributes(); + spanAttributes.Add(nameof(WorkflowName), WorkflowName); + spanAttributes.Add(nameof(_consumerOptions.ConsumerName), _consumerOptions.ConsumerName); + if (state.ReceivedData?.Letter?.MessageId is not null) + { + spanAttributes.Add(nameof(state.ReceivedData.Letter.MessageId), state.ReceivedData.Letter.MessageId); + } + if (state.ReceivedData?.Letter?.Metadata?.Id is not null) + { + spanAttributes.Add(nameof(state.ReceivedData.Letter.Metadata.Id), state.ReceivedData.Letter.Metadata.Id); + } + + state.StartRootSpan( + string.Format( + RootSpanIdentifier, + WorkflowName, + state.ReceivedData?.Letter?.MessageId ?? Guid.NewGuid().ToString()), + SpanKind.Consumer, + spanAttributes); + return state; } public TransformBlock GetBuildStateBlock( - ISerializationProvider provider, string key, ExecutionDataflowBlockOptions options) { TState BuildStateWrap(ReceivedData data) { try - { - using var multiDispose = _metricsProvider.TrackAndDuration(StateIdentifier, true); - return BuildState(provider, key, data); - } + { return BuildState(key, data); } catch { return null; } } @@ -643,17 +643,13 @@ public TransformBlock GetByteManipulationTransformBlock( ExecutionDataflowBlockOptions options, bool outbound, Predicate predicate, - string metricIdentifier, - bool metricMicroScale = false, - string metricUnit = null, - string metricDescription = null) + string metricIdentifier) { TState WrapAction(TState state) { + using var childSpan = state.CreateActiveSpan(metricIdentifier, SpanKind.Consumer); try { - using var multiDispose = _metricsProvider.TrackAndDuration(metricIdentifier, metricMicroScale, metricUnit, metricDescription, state.MetricTags); - if (outbound) { if (state.SendData?.Length > 0) @@ -678,6 +674,8 @@ TState WrapAction(TState state) } catch (Exception ex) { + childSpan?.SetStatus(Status.Error); + childSpan?.RecordException(ex); state.IsFaulted = true; state.EDI = ExceptionDispatchInfo.Capture(ex); return state; @@ -692,16 +690,13 @@ public TransformBlock GetByteManipulationTransformBlock( ExecutionDataflowBlockOptions options, bool outbound, Predicate predicate, - string metricIdentifier, - bool metricMicroScale = false, - string metricUnit = null, - string metricDescription = null) + string metricIdentifier) { async Task WrapActionAsync(TState state) { + using var childSpan = state.CreateActiveSpan(metricIdentifier, SpanKind.Consumer); try { - using var multiDispose = _metricsProvider.TrackAndDuration(metricIdentifier, metricMicroScale, metricUnit, metricDescription, state.MetricTags); if (outbound) { @@ -726,6 +721,8 @@ async Task WrapActionAsync(TState state) } catch (Exception ex) { + childSpan?.SetStatus(Status.Error); + childSpan?.RecordException(ex); state.IsFaulted = true; state.EDI = ExceptionDispatchInfo.Capture(ex); return state; @@ -742,10 +739,9 @@ public TransformBlock GetWrappedPublishTransformBlock( { async Task WrapPublishAsync(TState state) { + using var childSpan = state.CreateActiveSpan(PublishStepIdentifier, SpanKind.Producer); try { - using var multiDispose = _metricsProvider.TrackAndDuration(PublishStepIdentifier, true, metricTags: state.MetricTags); - await service.Publisher.PublishAsync(state.SendMessage, true, true).ConfigureAwait(false); state.SendMessageSent = true; @@ -753,6 +749,8 @@ async Task WrapPublishAsync(TState state) } catch (Exception ex) { + childSpan?.SetStatus(Status.Error); + childSpan?.RecordException(ex); state.IsFaulted = true; state.EDI = ExceptionDispatchInfo.Capture(ex); return state; diff --git a/src/HouseofCat.RabbitMQ/Dataflows/RabbitWorkState.cs b/src/HouseofCat.RabbitMQ/Dataflows/RabbitWorkState.cs index 1c256409..52299935 100644 --- a/src/HouseofCat.RabbitMQ/Dataflows/RabbitWorkState.cs +++ b/src/HouseofCat.RabbitMQ/Dataflows/RabbitWorkState.cs @@ -1,4 +1,5 @@ using HouseofCat.Dataflows; +using OpenTelemetry.Trace; using System.Collections.Generic; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; @@ -30,4 +31,6 @@ public abstract class RabbitWorkState : IRabbitWorkState public ExceptionDispatchInfo EDI { get; set; } public IDictionary MetricTags { get; set; } + + public TelemetrySpan RootSpan { get; set; } } diff --git a/src/HouseofCat.RabbitMQ/Extensions/MessageExtensions.cs b/src/HouseofCat.RabbitMQ/Extensions/MessageExtensions.cs index 9fcb04f9..eb83783f 100644 --- a/src/HouseofCat.RabbitMQ/Extensions/MessageExtensions.cs +++ b/src/HouseofCat.RabbitMQ/Extensions/MessageExtensions.cs @@ -83,7 +83,7 @@ public static IMessage CreateSimpleRandomLetter(string queueName, int bodySize = return new Letter { MessageId = Guid.NewGuid().ToString(), - LetterMetadata = new LetterMetadata(), + Metadata = new LetterMetadata(), Envelope = new Envelope { Exchange = string.Empty, diff --git a/src/HouseofCat.RabbitMQ/Messages/Letter.cs b/src/HouseofCat.RabbitMQ/Messages/Letter.cs index 16d25931..fde51ca1 100644 --- a/src/HouseofCat.RabbitMQ/Messages/Letter.cs +++ b/src/HouseofCat.RabbitMQ/Messages/Letter.cs @@ -32,14 +32,14 @@ public class Letter : IMessage public Envelope Envelope { get; set; } public string MessageId { get; set; } - public LetterMetadata LetterMetadata { get; set; } + public IMetadata Metadata { get; set; } public ReadOnlyMemory Body { get; set; } public IBasicProperties BuildProperties(IChannelHost channelHost, bool withOptionalHeaders) { MessageId ??= Guid.NewGuid().ToString(); - var props = this.CreateBasicProperties(channelHost, withOptionalHeaders, LetterMetadata); + var props = this.CreateBasicProperties(channelHost, withOptionalHeaders, Metadata); props.MessageId = MessageId; // Non-optional Header. @@ -59,7 +59,7 @@ public Letter(string exchange, string routingKey, ReadOnlyMemory data, Let RoutingOptions = routingOptions ?? RoutingOptions.CreateDefaultRoutingOptions() }; Body = data; - LetterMetadata = metadata ?? new LetterMetadata(); + Metadata = metadata ?? new LetterMetadata(); } public Letter(string exchange, string routingKey, ReadOnlyMemory data, string id, RoutingOptions routingOptions = null) @@ -72,9 +72,9 @@ public Letter(string exchange, string routingKey, ReadOnlyMemory data, str }; Body = data; if (!string.IsNullOrWhiteSpace(id)) - { LetterMetadata = new LetterMetadata { Id = id }; } + { Metadata = new LetterMetadata { Id = id }; } else - { LetterMetadata = new LetterMetadata(); } + { Metadata = new LetterMetadata(); } } public Letter(string exchange, string routingKey, byte[] data, string id, byte priority) @@ -87,29 +87,29 @@ public Letter(string exchange, string routingKey, byte[] data, string id, byte p }; Body = data; if (!string.IsNullOrWhiteSpace(id)) - { LetterMetadata = new LetterMetadata { Id = id }; } + { Metadata = new LetterMetadata { Id = id }; } else - { LetterMetadata = new LetterMetadata(); } + { Metadata = new LetterMetadata(); } } public Letter Clone() { var clone = this.Clone(); - clone.LetterMetadata = LetterMetadata.Clone(); + clone.Metadata = Metadata.Clone(); return clone; } - public IMetadata GetMetadata() => LetterMetadata; + public IMetadata GetMetadata() => Metadata; public IMetadata CreateMetadataIfMissing() { - LetterMetadata ??= new LetterMetadata(); - return LetterMetadata; + Metadata ??= new LetterMetadata(); + return Metadata; } - public T GetHeader(string key) => LetterMetadata.GetHeader(key); - public bool RemoveHeader(string key) => LetterMetadata.RemoveHeader(key); - public IDictionary GetHeadersOutOfMetadata() => LetterMetadata.GetHeadersOutOfMetadata(); + public T GetHeader(string key) => Metadata.GetHeader(key); + public bool RemoveHeader(string key) => Metadata.RemoveHeader(key); + public IDictionary GetHeadersOutOfMetadata() => Metadata.GetHeadersOutOfMetadata(); public ReadOnlyMemory GetBodyToPublish(ISerializationProvider serializationProvider) => serializationProvider.Serialize(this).ToArray(); diff --git a/tests/OpenTelemetry.Console.Tests/OpenTelemetry.Console.Tests.csproj b/tests/OpenTelemetry.Console.Tests/OpenTelemetry.Console.Tests.csproj new file mode 100644 index 00000000..f9523c37 --- /dev/null +++ b/tests/OpenTelemetry.Console.Tests/OpenTelemetry.Console.Tests.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + + + + + + + + + + + + + diff --git a/tests/OpenTelemetry.Console.Tests/Program.cs b/tests/OpenTelemetry.Console.Tests/Program.cs new file mode 100644 index 00000000..e31acda3 --- /dev/null +++ b/tests/OpenTelemetry.Console.Tests/Program.cs @@ -0,0 +1,26 @@ +using HouseofCat.Utilities; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using OpenTelmetry.Tests; + +var loggerFactory = LogHelper.CreateConsoleLoggerFactory(LogLevel.Information); +LogHelper.LoggerFactory = loggerFactory; +var logger = loggerFactory.CreateLogger(); + +var applicationName = "OpenTelemetry.ConsoleTests"; +var workflowName = "MyWorkflowName"; + +using var traceProvider = Sdk + .CreateTracerProviderBuilder() + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(applicationName)) + .AddSource(workflowName) + .AddConsoleExporter() + .Build(); + +SpanTests.RunRootSpanWithChildSpanErrorTest(logger, workflowName); + +logger.LogInformation("Tests complete! Press return to exit...."); + +Console.ReadLine(); diff --git a/tests/OpenTelemetry.Console.Tests/Tests/SpanTests.cs b/tests/OpenTelemetry.Console.Tests/Tests/SpanTests.cs new file mode 100644 index 00000000..8e9aee6b --- /dev/null +++ b/tests/OpenTelemetry.Console.Tests/Tests/SpanTests.cs @@ -0,0 +1,94 @@ +using HouseofCat.RabbitMQ.Dataflows; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Trace; +using static HouseofCat.Dataflows.Extensions.WorkStateExtensions; + +namespace OpenTelmetry.Tests; + +public static class SpanTests +{ + public class CustomWorkState : RabbitWorkState + { + public CustomWorkState() + { + Data = new Dictionary(); + } + } + + public static void RunRootSpanTest(ILogger logger, string sourceName) + { + logger.LogInformation($"Starting {nameof(RunRootSpanTest)}..."); + + var workstate = new CustomWorkState(); + + workstate.SetWorkflowNameAsOpenTelemetrySourceName(sourceName, "v1.0.0"); + workstate.StartRootSpan("RunBasicSpanTest.Root", SpanKind.Internal); + workstate.EndRootSpan(); + + logger.LogInformation($"Finished {nameof(RunRootSpanTest)}."); + } + + public static void RunRootSpanWithChildSpanTest(ILogger logger, string workflowName) + { + logger.LogInformation($"Starting {RunRootSpanWithChildSpanTest}..."); + + var workstate = new CustomWorkState(); + + workstate.SetWorkflowNameAsOpenTelemetrySourceName(workflowName, "v1.0.0"); + workstate.StartRootSpan("RunBasicSpanTest.Root", SpanKind.Internal); + using (var span = workstate.CreateActiveSpan("RunBasicSpanTest.Child", SpanKind.Internal)) + { + span.SetStatus(Status.Ok); + } + workstate.EndRootSpan(); + + logger.LogInformation($"Finished {nameof(RunRootSpanWithChildSpanTest)}."); + } + + public static void RunRootSpanWithChildSpanErrorTest(ILogger logger, string workflowName) + { + logger.LogInformation($"Starting {RunRootSpanWithChildSpanTest}..."); + + var workstate = new CustomWorkState(); + + workstate.SetWorkflowNameAsOpenTelemetrySourceName(workflowName, "v1.0.0"); + workstate.StartRootSpan("RunBasicSpanTest.Root", SpanKind.Internal); + + using (var span = workstate.CreateActiveSpan("RunBasicSpanTest.Child", SpanKind.Internal)) + { + workstate.SetCurrentSpanAsError("Span had an error!"); + } + + workstate.EndRootSpan(); + + logger.LogInformation($"Finished {nameof(RunRootSpanWithChildSpanTest)}."); + } + + public static void RunRootSpanWithManyChildSpanFlatLevelTest(ILogger logger, string workflowName) + { + logger.LogInformation($"Starting {RunRootSpanWithChildSpanTest}..."); + + var workstate = new CustomWorkState(); + + workstate.SetWorkflowNameAsOpenTelemetrySourceName(workflowName, "v1.0.0"); + workstate.StartRootSpan("RunBasicSpanTest.Root", SpanKind.Internal); + + for (var i = 0; i < 10; i++) + { + using var span = workstate.CreateActiveSpan($"RunBasicSpanTest.Child.{i}", SpanKind.Internal); + + if (i == 9) + { + workstate.SetCurrentSpanAsError("Span 9 had an error!"); + } + else + { + span.SetStatus(Status.Ok); + } + } + + workstate.EndRootSpan(); + + logger.LogInformation($"Finished {nameof(RunRootSpanWithChildSpanTest)}."); + } +} diff --git a/version.props b/version.props index 9cb3e1fe..42b37052 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@ - 3.2.2 - 3.2.2 - 3.2.2 + 3.3.0 + 3.3.0 + 3.3.0