├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── Rebus.Diagnostics.Tests ├── AssemblyInfo.cs ├── Incoming │ ├── HandlerInvokerWrapperTests.cs │ ├── IncomingDiagnosticsHandlerInvokerWrapperTests.cs │ ├── IncomingDiagnosticsStepTests.cs │ └── TestInvoker.cs ├── IntegrationTests.cs ├── Outgoing │ ├── MeterObserver.cs │ └── OutgoingDiagnosticsStepTests.cs ├── Rebus.Diagnostics.Tests.csproj └── TestHelpers.cs ├── Rebus.Diagnostics ├── Config │ └── DiagnosticSourcesConfigurationExtensions.cs ├── Diagnostics │ ├── Helpers │ │ ├── ActivityExtensions.cs │ │ ├── MessageWrapper.cs │ │ └── TagHelper.cs │ ├── Incoming │ │ ├── AfterProcessMessage.cs │ │ ├── BeforeProcessMessage.cs │ │ ├── HandlerInvokerWrapper.cs │ │ ├── IncomingDiagnosticsHandlerInvokerWrapper.cs │ │ └── IncomingDiagnosticsStep.cs │ ├── Outgoing │ │ ├── AfterSendMessage.cs │ │ ├── BeforeSendMessage.cs │ │ └── OutgoingDiagnosticsStep.cs │ └── RebusDiagnosticConstants.cs └── Rebus.Diagnostics.csproj ├── Rebus.OpenTelemetry.sln ├── Rebus.OpenTelemetry ├── Configuration │ ├── MeterBuilderExtensions.cs │ └── TraceBuilderExtensions.cs └── Rebus.OpenTelemetry.csproj ├── appveyor.yml ├── artwork └── little_rebusbus2_copy-500x500.png ├── scripts ├── build.cmd ├── push.cmd └── release.cmd └── tools └── NuGet └── nuget.exe /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ master ] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: windows-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - name: Setup .NET 7 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: '7.0.x' 20 | - name: Setup .NET 6 21 | uses: actions/setup-dotnet@v1 22 | with: 23 | dotnet-version: '6.0.x' 24 | - name: Restore 25 | run: dotnet restore 26 | - name: Build 27 | run: dotnet build --no-restore 28 | - name: Test 29 | run: dotnet test --no-build --verbosity normal 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | bin 3 | deploy 4 | deploy/* 5 | _ReSharper.* 6 | *.csproj.user 7 | *.resharper.user 8 | *.ReSharper.user 9 | *.teamcity.user 10 | *.TeamCity.user 11 | *.resharper 12 | *.DotSettings.user 13 | *.dotsettings.user 14 | *.ncrunchproject 15 | *.ncrunchsolution 16 | *.suo 17 | *.cache 18 | ~$* 19 | .vs 20 | .vs/* 21 | _NCrunch_* 22 | *.user 23 | *.backup 24 | 25 | # MS Guideline 26 | **/packages/* 27 | !**/packages/build/ 28 | AssemblyInfo_Patch.cs 29 | .idea/ 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.0.1 4 | * Initial version made by [zlepper] based on work from [dariogriffo] 5 | 6 | 7 | ## 0.0.2 8 | * Fix baggage not being propagated across the wire - thanks [zlepper] 9 | 10 | ## 0.0.3 11 | * Fix activity context not being available in logger - thanks [zlepper] 12 | 13 | ## 0.0.4 14 | * Make activity kind be "internal" for the process step - thanks [zlepper] 15 | 16 | ## 0.0.5 17 | * Update packages and framework targets - thanks [arildboifot] 18 | 19 | ## 1.0.0 20 | * Update to Rebus 8 21 | * Add system.diagnostics.metrics.meter support - thanks [droosma] 22 | * Rebus hard dependency on Rebus' intent header - thanks [riezebosch] 23 | 24 | ## 1.0.1 25 | * Prevent tag duplicates - thanks [pfab-io] 26 | 27 | ## 1.1.0 28 | * Extend System.Diagnostics.DiagnosticSource version range to work with .NET 9 - thanks [rasmusjp] 29 | 30 | ## 1.1.1 31 | * Fix inverted message size and message delay meters - thanks [arielmoraes] 32 | 33 | ## 1.2.0 34 | * Forward exceptions and add a bit more data to traces - thanks [zlepper] 35 | 36 | [arielmoraes]: https://github.com/arielmoraes 37 | [arildboifot]: https://github.com/arildboifot 38 | [dariogriffo]: https://github.com/dariogriffo 39 | [droosma]: https://github.com/droosma 40 | [rasmusjp]: https://github.com/rasmusjp 41 | [riezebosch]: https://github.com/riezebosch 42 | [pfab-io]: https://github.com/pfab-io 43 | [zlepper]: https://github.com/zlepper 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributions are most welcome! :) 2 | 3 | I do prefer it if we communicate a little bit before you send PRs, though. 4 | This is because _I value your time_, and it would be a shame if you spent time 5 | working on something that could be made better in another way, or wasn't 6 | actually needed because what you wanted to achieve could be done better in 7 | another way, etc. 8 | 9 | ## "Beginners" 10 | 11 | Contributions are ALSO very welcome if you consider yourself a beginner 12 | at open source. Everyone has to start somewhere, right? 13 | 14 | Here's how you would ideally do it if you were to contribute to Rebus: 15 | 16 | * Pick an [issue](https://github.com/rebus-org/Rebus/issues) you're interested in doing, 17 | or dream up something yourself that you feel is missing. 18 | * If you talk to me first (either via comments on the issue or by email), I 19 | will guarantee that your contribution is accepted. 20 | * Send me a "pull request" (which is how you make contributions on GitHub) 21 | 22 | ### Here's how you create a pull request/PR 23 | 24 | * Fork Rebus ([Fork A Repo @ GitHub docs](https://help.github.com/articles/fork-a-repo/)) 25 | * Clone your fork ([Cloning A Repository @ GitHub docs](https://help.github.com/articles/cloning-a-repository/)) 26 | * Make changes to your local copy (e.g. `git commit -am"bam!!!11"` - check [this](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository) out for more info) 27 | * Push your local changes to your fork ([Pushing To A Remote @ GitHub docs](https://help.github.com/articles/pushing-to-a-remote/)) 28 | * Send me a pull request ([Using Pull Requests @ GitHub docs](https://help.github.com/articles/using-pull-requests/)) 29 | 30 | When you do this, your changes become visible to me. I can then review it, and we can discuss 31 | each line of code if necessary. 32 | 33 | If you push additional changes to your fork during this process, 34 | the changes become immediately available in the pull request. 35 | 36 | When all is good, I accept your PR by merging it, and then you're (even more) awesome! 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Rebus is licensed under [The MIT License (MIT)](http://opensource.org/licenses/MIT) 2 | 3 | # The MIT License (MIT) 4 | 5 | Copyright (c) 2012 Mogens Heller Grabe 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | ------- 26 | 27 | This license was chosen with the intention of making it easy for everyone to use Rebus. If the license has the opposite effect for your specific usage/organization/whatever, please contact me and we'll see if we can work something out. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rebus.OpenTelemetry 2 | 3 | Makes Rebus emit Diagnostic traces, which OpenTelemetry can be used to generate tracing. 4 | 5 | # Usage 6 | 7 | Add the packages: 8 | ```c# 9 | Rebus.Diagnostics 10 | Rebus.OpenTelemetry 11 | ``` 12 | 13 | When configuring Rebus call `EnableDiagnosticSources` like this: 14 | ```c# 15 | using var publisherActivator = new BuiltinHandlerActivator(); 16 | 17 | var bus = Configure.With(publisherActivator) 18 | .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "Messages")) 19 | .Options(o => o.EnableDiagnosticSources()) // This is the important line 20 | .Start(); 21 | ``` 22 | 23 | Then add Rebus tracing to your OpenTelemetry calls, by doing `AddRebusInstrumentation`: 24 | 25 | ```c# 26 | var tracerProvider = Sdk.CreateTracerProviderBuilder() 27 | .AddRebusInstrumentation() 28 | .Build() 29 | ``` 30 | 31 | And then everything should just work. 🙂 -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | [assembly: Parallelizable(ParallelScope.Children)] -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/Incoming/HandlerInvokerWrapperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Diagnostics.Incoming; 7 | 8 | namespace Rebus.Diagnostics.Tests.Incoming 9 | { 10 | [TestFixture] 11 | public class HandlerInvokerWrapperTests 12 | { 13 | [OneTimeSetUp] 14 | public static void ListenForRebus() 15 | { 16 | TestHelpers.ListenForRebus(); 17 | } 18 | 19 | [Test] 20 | public async Task CreatesNewSubActivityIfThereIsAnActiveActivity() 21 | { 22 | using var activity = new Activity("MyActivity"); 23 | var innerInvokerWasInvoked = false; 24 | var hadActivity = false; 25 | 26 | var innerInvoker = new TestInvoker(() => 27 | { 28 | innerInvokerWasInvoked = true; 29 | hadActivity = Activity.Current != null; 30 | // ReSharper disable once AccessToDisposedClosure 31 | Assert.That(Activity.Current!.ParentId, Is.EqualTo(activity.Id)); 32 | }); 33 | 34 | var wrapper = new HandlerInvokerWrapper(innerInvoker, "MyMessage"); 35 | 36 | activity.Start(); 37 | 38 | Assume.That(activity, Is.SameAs(Activity.Current)); 39 | 40 | await wrapper.Invoke(); 41 | 42 | Assert.That(innerInvokerWasInvoked); 43 | Assert.That(hadActivity); 44 | } 45 | 46 | [Test] 47 | public void MarksActivityAsFailedIfHandlerThrows() 48 | { 49 | using var activity = new Activity("MyActivity"); 50 | 51 | Activity? innerActivity = null; 52 | var innerInvoker = new TestInvoker(() => 53 | { 54 | innerActivity = Activity.Current; 55 | throw new Exception("Look im failing"); 56 | }); 57 | 58 | var wrapper = new HandlerInvokerWrapper(innerInvoker, "MyMessage"); 59 | 60 | activity.Start(); 61 | 62 | Assume.That(activity, Is.SameAs(Activity.Current)); 63 | 64 | Assert.That(async () => 65 | { 66 | await wrapper.Invoke(); 67 | }, Throws.Exception); 68 | 69 | 70 | Assert.That(innerActivity, Is.Not.Null); 71 | 72 | Assert.That(innerActivity!.Status, Is.EqualTo(ActivityStatusCode.Error)); 73 | Assert.That(innerActivity!.StatusDescription, Is.EqualTo("Look im failing")); 74 | Assert.That(innerActivity.Events, Has.Exactly(1).Items); 75 | var ev = innerActivity.Events.Single(); 76 | Assert.That(ev.Tags.FirstOrDefault(t => t.Key == "exception.type").Value, Is.EqualTo("System.Exception")); 77 | Assert.That(ev.Tags.FirstOrDefault(t => t.Key == "exception.message").Value, Is.EqualTo("Look im failing")); 78 | } 79 | 80 | [Test] 81 | public async Task CreatesNoNewActivityIfThereIsntAlreadyAnActiveActivity() 82 | { 83 | var innerInvokerWasInvoked = false; 84 | var hadActivity = false; 85 | 86 | var innerInvoker = new TestInvoker(() => 87 | { 88 | innerInvokerWasInvoked = true; 89 | hadActivity = Activity.Current != null; 90 | }); 91 | 92 | var wrapper = new HandlerInvokerWrapper(innerInvoker, "MyMessage"); 93 | 94 | await wrapper.Invoke(); 95 | 96 | Assert.That(innerInvokerWasInvoked); 97 | Assert.That(hadActivity, Is.False); 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/Incoming/IncomingDiagnosticsHandlerInvokerWrapperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Threading.Tasks; 6 | using System.Transactions; 7 | using NUnit.Framework; 8 | using Rebus.Diagnostics.Incoming; 9 | using Rebus.Messages; 10 | using Rebus.Pipeline; 11 | using Rebus.Pipeline.Receive; 12 | using Rebus.Transport; 13 | 14 | namespace Rebus.Diagnostics.Tests.Incoming 15 | { 16 | [TestFixture] 17 | public class IncomingDiagnosticsHandlerInvokerWrapperTests 18 | { 19 | 20 | [OneTimeSetUp] 21 | public static void ListenForRebus() 22 | { 23 | TestHelpers.ListenForRebus(); 24 | } 25 | 26 | [Test] 27 | public async Task WrapsInvokersSoTheyCanRunInsideAnActivity() 28 | { 29 | var step = new IncomingDiagnosticsHandlerInvokerWrapper(); 30 | 31 | var headers = new Dictionary 32 | { 33 | {Headers.Type, "MyType"} 34 | }; 35 | var transportMessage = new TransportMessage(headers, Array.Empty()); 36 | var message = new Message(headers, Array.Empty()); 37 | 38 | var innerInvoker = new TestInvoker(() => { }); 39 | 40 | var handlerInvokers = new HandlerInvokers(message, new[] {innerInvoker}); 41 | 42 | using var scope = new RebusTransactionScope(); 43 | var context = new IncomingStepContext(transportMessage, scope.TransactionContext); 44 | context.Save(handlerInvokers); 45 | 46 | var callbackWasInvoked = false; 47 | await step.Process(context, () => 48 | { 49 | callbackWasInvoked = true; 50 | return Task.CompletedTask; 51 | }); 52 | 53 | Assert.That(callbackWasInvoked); 54 | 55 | var updatedInvokers = context.Load(); 56 | Assert.That(updatedInvokers, Is.Not.SameAs(handlerInvokers)); 57 | Assert.That(updatedInvokers, Has.Exactly(1).Items.And.All.TypeOf()); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/Incoming/IncomingDiagnosticsStepTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Text.RegularExpressions; 8 | using System.Threading.Tasks; 9 | using NUnit.Framework; 10 | using OpenTelemetry; 11 | using OpenTelemetry.Trace; 12 | using Rebus.Activation; 13 | using Rebus.Config; 14 | using Rebus.Diagnostics.Incoming; 15 | using Rebus.Diagnostics.Tests.Outgoing; 16 | using Rebus.Logging; 17 | using Rebus.Messages; 18 | using Rebus.OpenTelemetry.Configuration; 19 | using Rebus.Pipeline; 20 | using Rebus.Retry.Simple; 21 | using Rebus.Tests.Contracts.Extensions; 22 | using Rebus.Transport; 23 | using Rebus.Transport.InMem; 24 | 25 | namespace Rebus.Diagnostics.Tests.Incoming 26 | { 27 | [TestFixture] 28 | public class IncomingDiagnosticsStepTests 29 | { 30 | 31 | [OneTimeSetUp] 32 | public static void ListenForRebus() 33 | { 34 | TestHelpers.ListenForRebus(); 35 | } 36 | 37 | 38 | [Test] 39 | public async Task StartsActivityWhenTraceStateHeaderIsSet() 40 | { 41 | var activity = new Activity("MyOperation"); 42 | activity.SetIdFormat(ActivityIdFormat.W3C); // Only the default on .net 5. Below that Hierarchical is the default 43 | activity.Start(); 44 | 45 | Assume.That(activity.Id, Is.Not.Null); 46 | var headers = new Dictionary 47 | { 48 | {Headers.Type, "MyType"}, 49 | {Headers.Intent, Headers.IntentOptions.PublishSubscribe}, 50 | {Headers.MessageId, "MyMessage"}, 51 | {RebusDiagnosticConstants.TraceStateHeaderName, activity.Id!} 52 | }; 53 | 54 | activity.Stop(); 55 | 56 | var transportMessage = new TransportMessage(headers, Array.Empty()); 57 | 58 | using var scope = new RebusTransactionScope(); 59 | var context = new IncomingStepContext(transportMessage, scope.TransactionContext); 60 | 61 | var step = new IncomingDiagnosticsStep(); 62 | var callbackInvoked = false; 63 | await step.Process(context, () => 64 | { 65 | callbackInvoked = true; 66 | Assert.That(Activity.Current!.ParentId, Is.EqualTo(activity.Id)); 67 | return Task.CompletedTask; 68 | }); 69 | 70 | Assert.That(callbackInvoked); 71 | } 72 | 73 | [Test] 74 | public async Task StartsAnEntireNewActivityIfNoActivityIsCurrentlyActive() 75 | { 76 | var headers = new Dictionary 77 | { 78 | {Headers.Type, "MyType"}, 79 | {Headers.Intent, Headers.IntentOptions.PublishSubscribe}, 80 | {Headers.MessageId, "MyMessage"}, 81 | }; 82 | 83 | 84 | var transportMessage = new TransportMessage(headers, Array.Empty()); 85 | 86 | Activity.DefaultIdFormat = ActivityIdFormat.W3C; 87 | using var scope = new RebusTransactionScope(); 88 | var context = new IncomingStepContext(transportMessage, scope.TransactionContext); 89 | 90 | var step = new IncomingDiagnosticsStep(); 91 | var callbackInvoked = false; 92 | await step.Process(context, () => 93 | { 94 | callbackInvoked = true; 95 | Assert.That(Activity.Current, Is.Not.Null); 96 | Assert.That(Activity.Current!.RootId, Is.EqualTo(Activity.Current.TraceId.ToString())); 97 | return Task.CompletedTask; 98 | }); 99 | 100 | Assert.That(callbackInvoked); 101 | } 102 | 103 | [Test] 104 | public async Task ActivityIsAvailableInRetryLogging() 105 | { 106 | using var tracerProvider = Sdk.CreateTracerProviderBuilder() 107 | .AddRebusInstrumentation() 108 | .Build(); 109 | 110 | using var receiverActivator = new BuiltinHandlerActivator(); 111 | 112 | string handlerReceivedTraceId = ""; 113 | 114 | receiverActivator.Handle((string _) => 115 | { 116 | handlerReceivedTraceId = Activity.Current!.TraceId.ToString(); 117 | 118 | throw new Exception("Ohh nooo"); 119 | }); 120 | 121 | var network = new InMemNetwork(); 122 | 123 | var logger = new TestLogger(); 124 | 125 | 126 | var receiver = Configure.With(receiverActivator) 127 | .Logging(l => 128 | { 129 | l.Register(_ => new TestLoggerFactory(logger)); 130 | }) 131 | .Transport(t => t.UseInMemoryTransport(network, "Receiver")) 132 | .Options(o => 133 | { 134 | o.RetryStrategy(maxDeliveryAttempts: 1); 135 | o.EnableDiagnosticSources(); 136 | }) 137 | .Start(); 138 | 139 | 140 | string operationTraceId = ""; 141 | await Task.Run(async () => 142 | { 143 | var activity = new Activity("MyOperation"); 144 | activity.SetIdFormat(ActivityIdFormat 145 | .W3C); // Only the default on .net 5. Below that Hierarchical is the default 146 | activity.Start(); 147 | 148 | operationTraceId = activity.TraceId.ToString(); 149 | 150 | await receiver.SendLocal("hej med dig!"); 151 | }); 152 | 153 | await network.WaitForNextMessageFrom("error"); 154 | 155 | Assert.That(handlerReceivedTraceId, Is.EqualTo(operationTraceId)); 156 | 157 | // Unhandled exception is the only message logged at warning level 158 | var warning = logger.TraceIds.Single(t => t.Level == LogLevel.Warn); 159 | 160 | Assert.That(warning.TraceId, Is.EqualTo(operationTraceId)); 161 | } 162 | 163 | [Test] 164 | public async Task DefaultsWhenIntentIsNotSet() 165 | { 166 | var headers = new Dictionary 167 | { 168 | {Headers.Type, "MyType"}, 169 | {Headers.MessageId, "MyMessage"}, 170 | }; 171 | 172 | var transportMessage = new TransportMessage(headers, Array.Empty()); 173 | 174 | using var scope = new RebusTransactionScope(); 175 | var context = new IncomingStepContext(transportMessage, scope.TransactionContext); 176 | 177 | var step = new IncomingDiagnosticsStep(); 178 | var callbackInvoked = false; 179 | await step.Process(context, () => 180 | { 181 | callbackInvoked = true; 182 | Assert.That(Activity.Current!.Kind, Is.EqualTo(ActivityKind.Consumer)); 183 | return Task.CompletedTask; 184 | }); 185 | 186 | Assert.That(callbackInvoked); 187 | } 188 | 189 | [Test] 190 | public async Task StartsAnEntireNewActivsssityIfNoActivityIsCurrentlyActive() 191 | { 192 | var meterObserver = new MeterObserver(); 193 | 194 | var headers = new Dictionary 195 | { 196 | {Headers.Type, "MyType"}, 197 | {Headers.Intent, Headers.IntentOptions.PublishSubscribe}, 198 | {Headers.MessageId, "MyMessage"}, 199 | }; 200 | 201 | var transportMessage = new TransportMessage(headers, Array.Empty()); 202 | 203 | using var scope = new RebusTransactionScope(); 204 | var context = new IncomingStepContext(transportMessage, scope.TransactionContext); 205 | 206 | var step = new IncomingDiagnosticsStep(); 207 | await step.Process(context, () => Task.CompletedTask); 208 | 209 | Assert.That(meterObserver.InstrumentCalled("incoming", RebusDiagnosticConstants.MessageCountMeterNameTemplate), Is.True); 210 | Assert.That(meterObserver.InstrumentCalled("incoming", RebusDiagnosticConstants.MessageDelayMeterNameTemplate), Is.True); 211 | Assert.That(meterObserver.InstrumentCalled("incoming", RebusDiagnosticConstants.MessageSizeMeterNameTemplate), Is.True); 212 | } 213 | 214 | private class TestLoggerFactory : IRebusLoggerFactory 215 | { 216 | private TestLogger _logger; 217 | 218 | public TestLoggerFactory(TestLogger logger) 219 | { 220 | _logger = logger; 221 | } 222 | 223 | public ILog GetLogger() 224 | { 225 | return _logger; 226 | } 227 | } 228 | 229 | private enum LogLevel 230 | { 231 | Debug, 232 | Info, 233 | Warn, 234 | Error 235 | } 236 | 237 | private class TestLogger : ILog 238 | { 239 | public List<(LogLevel Level, string Message, string TraceId)> TraceIds = new(); 240 | 241 | private void StoreTraceId(LogLevel level, string message) 242 | { 243 | if (Activity.Current == null) 244 | { 245 | TraceIds.Add((level, message, "NO-TRACE")); 246 | } 247 | else 248 | { 249 | var traceId = Activity.Current!.TraceId.ToString(); 250 | TraceIds.Add((level, message, traceId)); 251 | } 252 | } 253 | 254 | public void Debug(string message, params object[] objs) 255 | { 256 | StoreTraceId(LogLevel.Debug, RenderString(message, objs)); 257 | } 258 | 259 | public void Info(string message, params object[] objs) 260 | { 261 | StoreTraceId(LogLevel.Info, RenderString(message, objs)); 262 | } 263 | 264 | public void Warn(string message, params object[] objs) 265 | { 266 | StoreTraceId(LogLevel.Warn, RenderString(message, objs)); 267 | } 268 | 269 | public void Warn(Exception exception, string message, params object[] objs) 270 | { 271 | StoreTraceId(LogLevel.Warn, RenderString(message, objs)); 272 | } 273 | 274 | public void Error(string message, params object[] objs) 275 | { 276 | StoreTraceId(LogLevel.Error, RenderString(message, objs)); 277 | } 278 | 279 | public void Error(Exception exception, string message, params object[] objs) 280 | { 281 | StoreTraceId(LogLevel.Error, RenderString(message, objs)); 282 | } 283 | 284 | public override string ToString() 285 | { 286 | return string.Join("\n", TraceIds); 287 | } 288 | 289 | #region "Borrowed" from the Rebus source code, please ignore 290 | internal static readonly Regex PlaceholderRegex = new Regex(@"{\w*[\:(\w|\.|\d|\-)*]+}", RegexOptions.Compiled); 291 | /// 292 | /// Renders the string by replacing placeholders on the form {whatever} with 293 | /// the 294 | /// string representation of each object from . Note that the actual content of the 295 | /// placeholders 296 | /// is ignored - i.e. it doesn't matter whether it says {0}, {name}, or 297 | /// {whatvgejigoejigoejigoe} 298 | /// - values are interpolated based on their order regardless of the name of the placeholder. 299 | /// 300 | private string RenderString(string message, object[] objs) 301 | { 302 | try 303 | { 304 | var index = 0; 305 | return PlaceholderRegex.Replace(message, match => 306 | { 307 | try 308 | { 309 | var value = objs[index]; 310 | index++; 311 | 312 | var format = match.Value.Substring(1, match.Value.Length - 2) 313 | .Split(':') 314 | .Skip(1) 315 | .FirstOrDefault(); 316 | 317 | return FormatObject(value, format); 318 | } 319 | catch (IndexOutOfRangeException) 320 | { 321 | return "???"; 322 | } 323 | }); 324 | } 325 | catch 326 | { 327 | return message; 328 | } 329 | } 330 | 331 | /// 332 | /// Formatter function that is invoked for each object value to be rendered into a string while interpolating log lines 333 | /// 334 | private string FormatObject(object obj, string? format) 335 | { 336 | if (obj is string) return $@"""{obj}"""; 337 | if (obj is IEnumerable) 338 | { 339 | var valueStrings = ((IEnumerable) obj).Cast().Select(o => FormatObject(o, format)); 340 | 341 | return $"[{string.Join(", ", valueStrings)}]"; 342 | } 343 | 344 | if (obj is DateTime) return ((DateTime) obj).ToString(format ?? "O"); 345 | if (obj is DateTimeOffset) return ((DateTimeOffset) obj).ToString(format ?? "O"); 346 | if (obj is IFormattable) return ((IFormattable) obj).ToString(format, CultureInfo.InvariantCulture); 347 | if (obj is IConvertible) return ((IConvertible) obj).ToString(CultureInfo.InvariantCulture); 348 | return obj.ToString()!; 349 | } 350 | 351 | #endregion 352 | } 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/Incoming/TestInvoker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Rebus.Pipeline.Receive; 4 | using Rebus.Sagas; 5 | 6 | namespace Rebus.Diagnostics.Tests.Incoming 7 | { 8 | public class TestInvoker : HandlerInvoker 9 | { 10 | private readonly Action _invoke; 11 | 12 | public TestInvoker(Action invoke) 13 | { 14 | _invoke = invoke; 15 | } 16 | 17 | public override Task Invoke() 18 | { 19 | _invoke(); 20 | return Task.CompletedTask; 21 | } 22 | 23 | public override void SetSagaData(ISagaData sagaData) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | public override ISagaData GetSagaData() 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | 33 | public override void SkipInvocation() 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | 38 | public override bool HasSaga => false; 39 | public override Saga Saga => null!; 40 | public override object Handler => null!; 41 | public override bool WillBeInvoked => true; 42 | } 43 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/IntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Config; 8 | using Rebus.Persistence.InMem; 9 | using Rebus.Transport.InMem; 10 | 11 | namespace Rebus.Diagnostics.Tests 12 | { 13 | [TestFixture] 14 | public class IntegrationTests 15 | { 16 | [OneTimeSetUp] 17 | public static void ListenForRebus() 18 | { 19 | TestHelpers.ListenForRebus(); 20 | } 21 | 22 | [Test] 23 | public async Task ActuallyPassesActivityToHandlerOnOtherSide() 24 | { 25 | var network = new InMemNetwork(); 26 | var subscriberStore = new InMemorySubscriberStore(); 27 | 28 | using var publisherActivator = new BuiltinHandlerActivator(); 29 | using var subscriberActivator = new BuiltinHandlerActivator(); 30 | using var eventWasReceived = new ManualResetEvent(initialState: false); 31 | 32 | var publisher = Configure.With(publisherActivator) 33 | .Transport(t => t.UseInMemoryTransport(network, "publisher")) 34 | .Options(o => o.EnableDiagnosticSources()) 35 | .Start(); 36 | 37 | var rootActivity = new Activity("root"); 38 | rootActivity.SetIdFormat(ActivityIdFormat.W3C); 39 | 40 | subscriberActivator.Handle(_ => 41 | { 42 | var act = Activity.Current!; 43 | Assert.That(act, Is.Not.Null); 44 | Assert.That(act.RootId, Is.EqualTo(rootActivity.RootId)); 45 | Assert.That(act.Id, Is.Not.EqualTo(rootActivity.RootId)); 46 | 47 | // ReSharper disable once AccessToDisposedClosure 48 | eventWasReceived.Set(); 49 | 50 | Assert.That(act.GetBaggageItem("MyBaggage"), Is.EqualTo("Hej Verden!")); 51 | 52 | return Task.CompletedTask; 53 | }); 54 | 55 | var subscriber = Configure.With(subscriberActivator) 56 | .Transport(t => t.UseInMemoryTransport(network, "subscriber")) 57 | .Options(o => o.EnableDiagnosticSources()) 58 | .Start(); 59 | 60 | await subscriber.Subscribe(); 61 | 62 | rootActivity.AddBaggage("MyBaggage", "Hej Verden!"); 63 | rootActivity.Start(); 64 | await publisher.Publish("Super Duper fed besked"); 65 | 66 | Assert.That(eventWasReceived.WaitOne(TimeSpan.FromSeconds(5)), Is.True, "Did not receive the published event within 5 seconds"); 67 | 68 | 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/Outgoing/MeterObserver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics.Metrics; 3 | using System.Linq; 4 | 5 | namespace Rebus.Diagnostics.Tests.Outgoing 6 | { 7 | internal class MeterObserver 8 | { 9 | private readonly List _publishedInstruments; 10 | public MeterObserver() 11 | { 12 | _publishedInstruments = new List(); 13 | 14 | var meterListener = new MeterListener(); 15 | meterListener.InstrumentPublished += (instrument, _) => _publishedInstruments.Add(instrument); 16 | meterListener.Start(); 17 | } 18 | 19 | public bool InstrumentCalled(string direction, string template) 20 | => _publishedInstruments.Any(p => p.Name == string.Format(template, direction)); 21 | 22 | public bool InstrumentCalled(string name) 23 | => _publishedInstruments.Any(p => p.Name == name); 24 | } 25 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/Outgoing/OutgoingDiagnosticsStepTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Diagnostics.Outgoing; 7 | using Rebus.Messages; 8 | using Rebus.Pipeline; 9 | using Rebus.Pipeline.Send; 10 | using Rebus.Tests.Contracts; 11 | using Rebus.Transport; 12 | 13 | namespace Rebus.Diagnostics.Tests.Outgoing 14 | { 15 | [TestFixture] 16 | public class OutgoingDiagnosticsStepTests : FixtureBase 17 | { 18 | RebusTransactionScope _scope = null!; 19 | 20 | [OneTimeSetUp] 21 | public static void ListenForRebus() 22 | { 23 | TestHelpers.ListenForRebus(); 24 | } 25 | 26 | protected override void SetUp() 27 | { 28 | _scope = Using(new RebusTransactionScope()); 29 | } 30 | 31 | [Test] 32 | public async Task StartsNoActivityIfThereIsNoCurrentActivity() 33 | { 34 | var step = new OutgoingDiagnosticsStep(); 35 | 36 | var headers = GetMessageHeaders("id", Headers.IntentOptions.PublishSubscribe); 37 | var message = new Message(headers, new object()); 38 | var transportMessage = new TransportMessage(headers, Array.Empty()); 39 | 40 | var destinations = new DestinationAddresses(new List {"MyQueue"}); 41 | 42 | var context = new OutgoingStepContext(message, _scope.TransactionContext, destinations); 43 | context.Save(transportMessage); 44 | 45 | var hadActivity = false; 46 | var callbackWasInvoked = false; 47 | 48 | await step.Process(context, () => 49 | { 50 | hadActivity = Activity.Current != null; 51 | callbackWasInvoked = true; 52 | return Task.CompletedTask; 53 | }); 54 | 55 | Assert.That(hadActivity, Is.False); 56 | Assert.That(headers, Has.No.ContainKey(RebusDiagnosticConstants.TraceStateHeaderName)); 57 | Assert.That(callbackWasInvoked, Is.True); 58 | } 59 | 60 | [Test] 61 | public async Task StartsNewActivityIfThereIsAlreadyAParentActivity() 62 | { 63 | Assume.That(RebusDiagnosticConstants.ActivitySource.HasListeners(), Is.True); 64 | 65 | var step = new OutgoingDiagnosticsStep(); 66 | 67 | var headers = GetMessageHeaders("id", Headers.IntentOptions.PublishSubscribe); 68 | 69 | var message = new Message(headers, new object()); 70 | var transportMessage = new TransportMessage(headers, Array.Empty()); 71 | 72 | var destinations = new DestinationAddresses(new List {"MyQueue"}); 73 | 74 | var context = new OutgoingStepContext(message, _scope.TransactionContext, destinations); 75 | context.Save(transportMessage); 76 | 77 | using var activity = new Activity("MyActivity"); 78 | activity.SetIdFormat(ActivityIdFormat.W3C); 79 | activity.Start(); 80 | 81 | Assume.That(activity, Is.SameAs(Activity.Current)); 82 | var hadActivity = false; 83 | var hadExpectedParent = false; 84 | 85 | await step.Process(context, () => 86 | { 87 | 88 | hadActivity = Activity.Current != null; 89 | 90 | hadExpectedParent = Activity.Current?.ParentSpanId == activity.SpanId; 91 | return Task.CompletedTask; 92 | }); 93 | 94 | Assert.That(hadActivity, Is.True); 95 | Assert.That(hadExpectedParent, Is.True); 96 | Assert.That(transportMessage.Headers, Contains.Key(RebusDiagnosticConstants.TraceStateHeaderName)); 97 | Assert.That(transportMessage.Headers[RebusDiagnosticConstants.TraceStateHeaderName], Is.Not.Null.And.Not.Empty); 98 | } 99 | 100 | [Test] 101 | public async Task RecordsMessageStatistics() 102 | { 103 | var meterObserver = new MeterObserver(); 104 | 105 | var step = new OutgoingDiagnosticsStep(); 106 | 107 | var headers = GetMessageHeaders("id", Headers.IntentOptions.PublishSubscribe); 108 | var message = new Message(headers, new object()); 109 | var transportMessage = new TransportMessage(headers, Array.Empty()); 110 | 111 | var destinations = new DestinationAddresses(new List { "MyQueue" }); 112 | 113 | var context = new OutgoingStepContext(message, _scope.TransactionContext, destinations); 114 | context.Save(transportMessage); 115 | 116 | await step.Process(context, () => Task.CompletedTask); 117 | 118 | Assert.That(meterObserver.InstrumentCalled("outgoing", RebusDiagnosticConstants.MessageCountMeterNameTemplate), Is.True); 119 | Assert.That(meterObserver.InstrumentCalled("outgoing", RebusDiagnosticConstants.MessageDelayMeterNameTemplate), Is.True); 120 | Assert.That(meterObserver.InstrumentCalled("outgoing", RebusDiagnosticConstants.MessageSizeMeterNameTemplate), Is.True); 121 | } 122 | 123 | private Dictionary GetMessageHeaders(string messageId, string intent) 124 | { 125 | return new Dictionary 126 | { 127 | {Headers.Type, typeof(OutgoingDiagnosticsStepTests).AssemblyQualifiedName!}, 128 | {Headers.MessageId, messageId}, 129 | {Headers.Intent, intent} 130 | }; 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/Rebus.Diagnostics.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0;net6.0;net48 5 | 6 | false 7 | 9 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Rebus.Diagnostics.Tests/TestHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace Rebus.Diagnostics.Tests 6 | { 7 | public static class TestHelpers 8 | { 9 | public static void ListenForRebus() 10 | { 11 | ActivitySource.AddActivityListener(new ActivityListener 12 | { 13 | ShouldListenTo = source => source.Name == RebusDiagnosticConstants.ActivitySourceName, 14 | Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData 15 | }); 16 | } 17 | 18 | private class AnonymousObserver : IObserver 19 | { 20 | private Action _handle; 21 | 22 | public AnonymousObserver(Action handle) 23 | { 24 | _handle = handle; 25 | } 26 | 27 | public void OnCompleted() 28 | { 29 | } 30 | 31 | public void OnError(Exception error) 32 | { 33 | } 34 | 35 | public void OnNext(T value) 36 | { 37 | _handle(value); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Config/DiagnosticSourcesConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Diagnostics.Incoming; 3 | using Rebus.Diagnostics.Outgoing; 4 | using Rebus.Pipeline; 5 | using Rebus.Pipeline.Receive; 6 | using Rebus.Pipeline.Send; 7 | 8 | namespace Rebus.Config; 9 | 10 | public static class DiagnosticSourcesConfigurationExtensions 11 | { 12 | public static OptionsConfigurer EnableDiagnosticSources(this OptionsConfigurer configurer) 13 | { 14 | if (configurer == null) throw new ArgumentNullException(nameof(configurer)); 15 | 16 | configurer.Decorate(c => 17 | { 18 | var pipeline = c.Get(); 19 | var injector = new PipelineStepInjector(pipeline); 20 | 21 | var outgoingStep = new OutgoingDiagnosticsStep(); 22 | injector.OnSend(outgoingStep, PipelineRelativePosition.Before, 23 | typeof(SendOutgoingMessageStep)); 24 | 25 | var incomingStep = new IncomingDiagnosticsStep(); 26 | 27 | var invokerWrapper = new IncomingDiagnosticsHandlerInvokerWrapper(); 28 | injector.OnReceive(invokerWrapper, PipelineRelativePosition.After, typeof(ActivateHandlersStep)); 29 | 30 | var concatenator = new PipelineStepConcatenator(injector); 31 | concatenator.OnReceive(incomingStep, PipelineAbsolutePosition.Front); 32 | 33 | return concatenator; 34 | }); 35 | return configurer; 36 | } 37 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Helpers/ActivityExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace Rebus.Diagnostics.Helpers; 6 | 7 | internal static class ActivityExtensions 8 | { 9 | 10 | const string ExceptionEventName = "exception"; 11 | const string ExceptionMessageTag = "exception.message"; 12 | const string ExceptionStackTraceTag = "exception.stacktrace"; 13 | const string ExceptionTypeTag = "exception.type"; 14 | 15 | 16 | // "Borrowed" from https://github.com/tarekgh/runtime/blob/d4464d7ae6d99d75ff89309fb56fd2a5a8f0c845/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs#L535 17 | // to avoid requiring version 9 of System.Diagnostics.DiagnosticSource 18 | public static Activity AddException(this Activity activity, Exception exception, 19 | ActivityTagsCollection? tags = null, DateTimeOffset timestamp = default) 20 | { 21 | if (exception == null) 22 | { 23 | throw new ArgumentNullException(nameof(exception)); 24 | } 25 | 26 | var exceptionTags = tags ?? new(); 27 | 28 | var hasMessage = false; 29 | var hasStackTrace = false; 30 | var hasType = false; 31 | 32 | foreach (var pair in exceptionTags) 33 | { 34 | if (pair.Key == ExceptionMessageTag) 35 | { 36 | hasMessage = true; 37 | } 38 | else if (pair.Key == ExceptionStackTraceTag) 39 | { 40 | hasStackTrace = true; 41 | } 42 | else if (pair.Key == ExceptionTypeTag) 43 | { 44 | hasType = true; 45 | } 46 | } 47 | 48 | if (!hasMessage) 49 | { 50 | exceptionTags.Add(new KeyValuePair(ExceptionMessageTag, exception.Message)); 51 | } 52 | 53 | if (!hasStackTrace) 54 | { 55 | exceptionTags.Add(new KeyValuePair(ExceptionStackTraceTag, exception.ToString())); 56 | } 57 | 58 | if (!hasType) 59 | { 60 | exceptionTags.Add(new KeyValuePair(ExceptionTypeTag, exception.GetType().ToString())); 61 | } 62 | 63 | return activity.AddEvent(new ActivityEvent(ExceptionEventName, timestamp, exceptionTags)); 64 | } 65 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Helpers/MessageWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Rebus.Bus; 3 | using Rebus.Extensions; 4 | using Rebus.Messages; 5 | 6 | namespace Rebus.Diagnostics.Helpers; 7 | 8 | /// 9 | /// Wraps Message and TransportMessage so they can be used the same way 10 | /// 11 | internal abstract class MessageWrapper 12 | { 13 | protected abstract Dictionary Headers { get; } 14 | 15 | internal abstract string GetMessageId(); 16 | 17 | public string GetIntentOption() 18 | { 19 | return Headers.TryGetValue(Rebus.Messages.Headers.Intent, out var header) 20 | ? header 21 | : Rebus.Messages.Headers.IntentOptions.PublishSubscribe; 22 | } 23 | 24 | internal string GetCorrectionId() 25 | { 26 | return Headers.GetValueOrNull(Rebus.Messages.Headers.Intent) ?? GetMessageId(); 27 | } 28 | 29 | internal string GetMessageType() 30 | { 31 | return Headers.GetValueOrNull(Rebus.Messages.Headers.Type) ?? ""; 32 | } 33 | } 34 | 35 | internal class TransportMessageWrapper : MessageWrapper 36 | { 37 | private readonly TransportMessage _message; 38 | 39 | internal TransportMessageWrapper(TransportMessage message) => _message = message; 40 | 41 | protected override Dictionary Headers => _message.Headers; 42 | internal override string GetMessageId() => _message.GetMessageId(); 43 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Helpers/TagHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Rebus.Messages; 3 | 4 | namespace Rebus.Diagnostics.Helpers; 5 | 6 | internal static class TagHelper 7 | { 8 | internal static ActivityTagsCollection ExtractInitialTags(MessageWrapper message) 9 | { 10 | var initialTags = new ActivityTagsCollection(); 11 | 12 | var kind = message.GetIntentOption() switch 13 | { 14 | Headers.IntentOptions.PublishSubscribe => "topic", 15 | Headers.IntentOptions.PointToPoint => "queue", 16 | _ => "" 17 | }; 18 | initialTags.Add("messaging.destination_kind", kind); 19 | initialTags.Add("messaging.message_id", message.GetMessageId()); 20 | initialTags.Add("messaging.conversation_id", message.GetCorrectionId()); 21 | initialTags.Add("rebus.message.type", message.GetMessageType()); 22 | 23 | return initialTags; 24 | } 25 | 26 | internal static void CopyBaggage(Activity parentActivity, Activity? activity) 27 | { 28 | if (activity == null) return; 29 | 30 | foreach (var keyValuePair in parentActivity.Baggage) 31 | { 32 | activity?.AddBaggage(keyValuePair.Key, keyValuePair.Value); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Incoming/AfterProcessMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Rebus.Pipeline; 4 | 5 | namespace Rebus.Diagnostics.Incoming; 6 | 7 | public class AfterProcessMessage 8 | { 9 | public const string EventName = RebusDiagnosticConstants.ConsumerActivityName + "." + nameof(AfterProcessMessage); 10 | 11 | public AfterProcessMessage(IncomingStepContext context, Activity? activity) 12 | { 13 | Context = context; 14 | StartTimeUtc = activity?.StartTimeUtc ?? default; 15 | Duration = activity?.Duration ?? default; 16 | } 17 | 18 | 19 | public IncomingStepContext Context { get; } 20 | 21 | public DateTime StartTimeUtc { get; } 22 | 23 | public TimeSpan Duration { get; } 24 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Incoming/BeforeProcessMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Rebus.Pipeline; 4 | 5 | namespace Rebus.Diagnostics.Incoming; 6 | 7 | public class BeforeProcessMessage 8 | { 9 | public const string EventName = RebusDiagnosticConstants.ConsumerActivityName + "." + nameof(BeforeProcessMessage); 10 | 11 | public BeforeProcessMessage(IncomingStepContext context, Activity? activity) 12 | { 13 | Context = context; 14 | StartTimeUtc = activity?.StartTimeUtc ?? default; 15 | } 16 | 17 | public IncomingStepContext Context { get; } 18 | 19 | public DateTime StartTimeUtc { get; } 20 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Incoming/HandlerInvokerWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using Rebus.Diagnostics.Helpers; 5 | using Rebus.Pipeline.Receive; 6 | using Rebus.Sagas; 7 | 8 | namespace Rebus.Diagnostics.Incoming; 9 | 10 | internal class HandlerInvokerWrapper : HandlerInvoker 11 | { 12 | private readonly HandlerInvoker _handlerInvokerImplementation; 13 | 14 | private readonly string _messageType; 15 | 16 | internal HandlerInvokerWrapper(HandlerInvoker handlerInvokerImplementation, string messageType) 17 | { 18 | _handlerInvokerImplementation = handlerInvokerImplementation; 19 | _messageType = messageType; 20 | } 21 | 22 | public override async Task Invoke() 23 | { 24 | var parentActivity = Activity.Current; 25 | if (parentActivity == null) 26 | { 27 | await _handlerInvokerImplementation.Invoke(); 28 | return; 29 | } 30 | 31 | var initialTags = new ActivityTagsCollection(); 32 | foreach (var tag in parentActivity.Tags) 33 | { 34 | if (!initialTags.ContainsKey(tag.Key)) 35 | initialTags.Add(tag.Key, tag.Value); 36 | } 37 | initialTags["messaging.operation"] = "process"; 38 | initialTags["rebus.handler.type"] = _handlerInvokerImplementation.Handler?.GetType().FullName ?? "Unknown handler type"; 39 | 40 | using var activity = RebusDiagnosticConstants.ActivitySource.StartActivity($"{_messageType} process", ActivityKind.Internal, parentActivity.Context, initialTags); 41 | 42 | TagHelper.CopyBaggage(parentActivity, activity); 43 | 44 | try 45 | { 46 | await _handlerInvokerImplementation.Invoke(); 47 | } 48 | catch (Exception e) 49 | { 50 | activity?.AddException(e); 51 | activity?.SetStatus(ActivityStatusCode.Error, e.Message); 52 | throw; 53 | } 54 | } 55 | 56 | public override void SetSagaData(ISagaData sagaData) 57 | { 58 | _handlerInvokerImplementation.SetSagaData(sagaData); 59 | } 60 | 61 | public override ISagaData GetSagaData() 62 | { 63 | return _handlerInvokerImplementation.GetSagaData(); 64 | } 65 | 66 | public override void SkipInvocation() 67 | { 68 | _handlerInvokerImplementation.SkipInvocation(); 69 | } 70 | 71 | public override bool HasSaga => _handlerInvokerImplementation.HasSaga; 72 | 73 | public override Saga Saga => _handlerInvokerImplementation.Saga; 74 | 75 | public override object Handler => _handlerInvokerImplementation.Handler; 76 | 77 | public override bool WillBeInvoked => _handlerInvokerImplementation.WillBeInvoked; 78 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Incoming/IncomingDiagnosticsHandlerInvokerWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Rebus.Bus; 5 | using Rebus.Messages; 6 | using Rebus.Pipeline; 7 | using Rebus.Pipeline.Receive; 8 | 9 | namespace Rebus.Diagnostics.Incoming; 10 | 11 | [StepDocumentation("Wraps all `HandlerInvoker`s so they can be traced individually")] 12 | public class IncomingDiagnosticsHandlerInvokerWrapper : IIncomingStep 13 | { 14 | public Task Process(IncomingStepContext context, Func next) 15 | { 16 | var currentHandlerInvokers = context.Load(); 17 | 18 | var message = currentHandlerInvokers.Message; 19 | var messageType = message.GetMessageType(); 20 | 21 | var wrappedHandlerInvokers = currentHandlerInvokers 22 | .Select(invoker => new HandlerInvokerWrapper(invoker, messageType)); 23 | 24 | var updatedHandlerInvokers = new HandlerInvokers(message, wrappedHandlerInvokers); 25 | context.Save(updatedHandlerInvokers); 26 | 27 | return next(); 28 | } 29 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Incoming/IncomingDiagnosticsStep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Threading.Tasks; 5 | using Newtonsoft.Json; 6 | using Rebus.Bus; 7 | using Rebus.Diagnostics.Helpers; 8 | using Rebus.Diagnostics.Outgoing; 9 | using Rebus.Messages; 10 | using Rebus.Pipeline; 11 | 12 | namespace Rebus.Diagnostics.Incoming 13 | { 14 | [StepDocumentation("Extracts trace from the incoming message and starts an activity for it")] 15 | public class IncomingDiagnosticsStep : IIncomingStep 16 | { 17 | private static readonly DiagnosticSource DiagnosticListener = 18 | new DiagnosticListener(RebusDiagnosticConstants.ConsumerActivityName); 19 | private readonly StepMeter _stepMeter; 20 | 21 | public IncomingDiagnosticsStep() 22 | { 23 | _stepMeter = new StepMeter("incoming"); 24 | } 25 | 26 | public async Task Process(IncomingStepContext context, Func next) 27 | { 28 | var message = context.Load(); 29 | 30 | using var activity = StartActivity(context, message); 31 | 32 | _stepMeter.Observe(message); 33 | 34 | try 35 | { 36 | await next(); 37 | } 38 | finally 39 | { 40 | SendAfterProcessEvent(activity, context); 41 | } 42 | } 43 | 44 | private static Activity? StartActivity(IncomingStepContext context, TransportMessage message) 45 | { 46 | Activity? activity = null; 47 | if (RebusDiagnosticConstants.ActivitySource.HasListeners()) 48 | { 49 | var headers = message.Headers; 50 | 51 | var messageType = message.GetMessageType(); 52 | 53 | var messageWrapper = new TransportMessageWrapper(message); 54 | 55 | var initialTags = TagHelper.ExtractInitialTags(messageWrapper); 56 | initialTags.Add("messaging.operation", "receive"); 57 | 58 | var activityKind = messageWrapper.GetIntentOption() == Headers.IntentOptions.PublishSubscribe 59 | ? ActivityKind.Consumer 60 | : ActivityKind.Server; 61 | 62 | var activityName = $"{messageType} receive"; 63 | if (!headers.TryGetValue(RebusDiagnosticConstants.TraceStateHeaderName, out var traceState)) 64 | { 65 | activity = RebusDiagnosticConstants.ActivitySource.StartActivity(activityName, activityKind, default(ActivityContext), initialTags); 66 | } 67 | else 68 | { 69 | activity = RebusDiagnosticConstants.ActivitySource.StartActivity(activityName, activityKind, 70 | traceState, initialTags); 71 | } 72 | 73 | if (activity != null) 74 | { 75 | CopyBaggage(headers, activity); 76 | } 77 | 78 | // TODO: Not sure if this is still needed 79 | // DiagnosticListener.OnActivityImport(activity, context); 80 | } 81 | 82 | SendBeforeProcessEvent(context, activity); 83 | 84 | return activity; 85 | } 86 | 87 | private static void CopyBaggage(Dictionary headers, Activity activity) 88 | { 89 | if (headers.TryGetValue(RebusDiagnosticConstants.BaggageHeaderName, out var baggageContent)) 90 | { 91 | var baggage = 92 | JsonConvert.DeserializeObject>>(baggageContent); 93 | 94 | foreach (var keyValuePair in baggage) 95 | { 96 | activity.AddBaggage(keyValuePair.Key, keyValuePair.Value); 97 | } 98 | } 99 | } 100 | 101 | private static void SendBeforeProcessEvent(IncomingStepContext context, Activity? activity) 102 | { 103 | if (DiagnosticListener.IsEnabled(BeforeProcessMessage.EventName, context)) 104 | { 105 | DiagnosticListener.Write(BeforeProcessMessage.EventName, new BeforeProcessMessage(context, activity)); 106 | } 107 | } 108 | 109 | private static void SendAfterProcessEvent(Activity? activity, IncomingStepContext context) 110 | { 111 | if (DiagnosticListener.IsEnabled(AfterProcessMessage.EventName)) 112 | { 113 | DiagnosticListener.Write(AfterProcessMessage.EventName, new AfterProcessMessage(context, activity)); 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Outgoing/AfterSendMessage.cs: -------------------------------------------------------------------------------- 1 | using Rebus.Pipeline; 2 | 3 | namespace Rebus.Diagnostics.Outgoing 4 | { 5 | public class AfterSendMessage 6 | { 7 | public const string EventName = RebusDiagnosticConstants.ProducerActivityName + "." + nameof(AfterSendMessage); 8 | 9 | public AfterSendMessage(OutgoingStepContext context) => Context = context; 10 | 11 | public OutgoingStepContext Context { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Outgoing/BeforeSendMessage.cs: -------------------------------------------------------------------------------- 1 | using Rebus.Pipeline; 2 | 3 | namespace Rebus.Diagnostics.Outgoing 4 | { 5 | public class BeforeSendMessage 6 | { 7 | public const string EventName = RebusDiagnosticConstants.ProducerActivityName + "." + nameof(BeforeSendMessage); 8 | 9 | public BeforeSendMessage(OutgoingStepContext context) => Context = context; 10 | 11 | public OutgoingStepContext Context { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/Outgoing/OutgoingDiagnosticsStep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Diagnostics.Metrics; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | using Rebus.Bus; 9 | using Rebus.Diagnostics.Helpers; 10 | using Rebus.Messages; 11 | using Rebus.Pipeline; 12 | using Rebus.Pipeline.Send; 13 | 14 | namespace Rebus.Diagnostics.Outgoing 15 | { 16 | internal class StepMeter 17 | { 18 | private readonly Histogram _messageSize; 19 | private readonly Histogram _messageDelay; 20 | private readonly Counter _messagesCount; 21 | 22 | public StepMeter(string direction) 23 | { 24 | var meter = RebusDiagnosticConstants.Meter; 25 | _messageSize = meter.CreateHistogram(string.Format(RebusDiagnosticConstants.MessageSizeMeterNameTemplate, direction), "bytes", "message body size"); 26 | _messageDelay = meter.CreateHistogram(string.Format(RebusDiagnosticConstants.MessageDelayMeterNameTemplate, direction), "milliseconds", "milliseconds since creation"); 27 | _messagesCount = meter.CreateCounter(string.Format(RebusDiagnosticConstants.MessageCountMeterNameTemplate, direction), "messages", $"number of messages {direction}"); 28 | } 29 | 30 | public void Observe(TransportMessage message) 31 | { 32 | var messageType = message.GetMessageType(); 33 | 34 | var tags = new KeyValuePair("type", messageType); 35 | 36 | _messageSize.Record(message.Body.Length, tags); 37 | _messagesCount.Add(1, tags); 38 | 39 | var delay = SentDelay(message, DateTime.UtcNow); 40 | _messageDelay.Record((int)delay.TotalMilliseconds, tags); 41 | } 42 | 43 | private static TimeSpan SentDelay(TransportMessage message, DateTime date) 44 | { 45 | if (!message.Headers.TryGetValue(Headers.SentTime, out var sentTime) 46 | || !DateTime.TryParse(sentTime, out var sentDateTime)) 47 | { 48 | return TimeSpan.Zero; 49 | } 50 | 51 | return date.Subtract(sentDateTime); 52 | } 53 | } 54 | 55 | [StepDocumentation("Creates a new activity for sending the provided message and passes it along on the message")] 56 | public class OutgoingDiagnosticsStep : IOutgoingStep 57 | { 58 | private static readonly DiagnosticSource DiagnosticListener = new DiagnosticListener(RebusDiagnosticConstants.ProducerActivityName); 59 | private readonly StepMeter _stepMeter; 60 | 61 | public OutgoingDiagnosticsStep() 62 | { 63 | _stepMeter = new StepMeter("outgoing"); 64 | } 65 | 66 | public async Task Process(OutgoingStepContext context, Func next) 67 | { 68 | var message = context.Load(); 69 | 70 | using var activity = StartActivity(context, message); 71 | 72 | _stepMeter.Observe(message); 73 | InjectHeaders(activity, message); 74 | 75 | try 76 | { 77 | await next(); 78 | } 79 | finally 80 | { 81 | SendAfterSendEvent(context); 82 | } 83 | } 84 | 85 | private static void InjectHeaders(Activity? activity, TransportMessage message) 86 | { 87 | if (activity == null) return; 88 | 89 | var headers = message.Headers; 90 | 91 | if (!headers.ContainsKey(RebusDiagnosticConstants.TraceStateHeaderName)) 92 | { 93 | headers[RebusDiagnosticConstants.TraceStateHeaderName] = activity.Id; 94 | } 95 | 96 | if (!headers.ContainsKey(RebusDiagnosticConstants.BaggageHeaderName)) 97 | { 98 | headers[RebusDiagnosticConstants.BaggageHeaderName] = JsonConvert.SerializeObject(activity.Baggage); 99 | } 100 | } 101 | 102 | private static Activity? StartActivity(OutgoingStepContext context, TransportMessage message) 103 | { 104 | var parentActivity = Activity.Current; 105 | 106 | if (parentActivity == null) 107 | { 108 | return null; 109 | } 110 | 111 | Activity? activity = null; 112 | if (RebusDiagnosticConstants.ActivitySource.HasListeners()) 113 | { 114 | var messageType = message.GetMessageType(); 115 | 116 | var messageWrapper = new TransportMessageWrapper(message); 117 | 118 | 119 | var activityKind = messageWrapper.GetIntentOption() == Headers.IntentOptions.PublishSubscribe 120 | ? ActivityKind.Producer 121 | : ActivityKind.Client; 122 | 123 | var activityName = $"{messageType} send"; 124 | 125 | var initialTags = TagHelper.ExtractInitialTags(messageWrapper); 126 | 127 | // Per the spec on how to handle array types: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/common.md#attributes 128 | var destinationAddresses = context.Load(); 129 | initialTags.Add("messaging.destination", JsonConvert.SerializeObject(destinationAddresses.ToList())); 130 | 131 | // TODO: Transport specific tags, like rabbitmq routing key 132 | 133 | activity = RebusDiagnosticConstants.ActivitySource.StartActivity(activityName, activityKind, 134 | parentActivity.Context, initialTags); 135 | 136 | TagHelper.CopyBaggage(parentActivity, activity); 137 | 138 | // TODO: Figure out if this is actually needed now 139 | // DiagnosticListener.OnActivityImport(activity, context); 140 | } 141 | 142 | SendBeforeSendEvent(context); 143 | 144 | return activity; 145 | } 146 | 147 | private static void SendBeforeSendEvent(OutgoingStepContext context) 148 | { 149 | if (DiagnosticListener.IsEnabled(BeforeSendMessage.EventName, context)) 150 | { 151 | DiagnosticListener.Write(BeforeSendMessage.EventName, new BeforeSendMessage(context)); 152 | } 153 | } 154 | 155 | private static void SendAfterSendEvent(OutgoingStepContext context) 156 | { 157 | if (DiagnosticListener.IsEnabled(AfterSendMessage.EventName)) 158 | { 159 | DiagnosticListener.Write(AfterSendMessage.EventName, new AfterSendMessage(context)); 160 | } 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /Rebus.Diagnostics/Diagnostics/RebusDiagnosticConstants.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Diagnostics.Metrics; 3 | 4 | namespace Rebus.Diagnostics 5 | { 6 | public static class RebusDiagnosticConstants 7 | { 8 | public const string TraceStateHeaderName = "rbs-ot-tracestate"; 9 | public const string BaggageHeaderName = "rbs-ot-correlation-context"; 10 | 11 | 12 | public const string ConsumerActivityName = ActivitySourceName + ".Receive"; 13 | public const string ProducerActivityName = ActivitySourceName + ".Send"; 14 | 15 | public const string MessageDelayMeterNameTemplate = MeterName + ".message.{0}.delay"; 16 | public const string MessageSizeMeterNameTemplate = MeterName + ".message.{0}.size"; 17 | public const string MessageCountMeterNameTemplate = MeterName + ".message.{0}"; 18 | 19 | public const string ActivitySourceName = "Rebus.Diagnostics"; 20 | public const string MeterName = "Rebus.Diagnostics"; 21 | 22 | public static readonly ActivitySource ActivitySource = new ActivitySource(ActivitySourceName, 23 | typeof(RebusDiagnosticConstants).Assembly.GetName().Version.ToString()); 24 | public static readonly Meter Meter = new Meter(MeterName, 25 | typeof(RebusDiagnosticConstants).Assembly.GetName().Version.ToString()); 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Rebus.Diagnostics/Rebus.Diagnostics.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 11 5 | enable 6 | Rebus 7 | MIT 8 | Diagnostics implementation for Rebus 9 | Copyright Rebus FM ApS 2012 10 | https://github.com/rebus-org/Rebus.OpenTelemetry 11 | mookid8000 12 | rebus open-telemetry diagnostics metrics 13 | little_rebusbus2_copy-500x500.png 14 | README.md 15 | 16 | 17 | 18 | 19 | True 20 | \ 21 | 22 | 23 | True 24 | \ 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Rebus.OpenTelemetry.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29911.84 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rebus.OpenTelemetry", "Rebus.OpenTelemetry\Rebus.OpenTelemetry.csproj", "{3EBC745B-8651-4E1A-A851-E7D94B8E4EF7}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rebus.Diagnostics.Tests", "Rebus.Diagnostics.Tests\Rebus.Diagnostics.Tests.csproj", "{D3787E01-2508-4533-83F4-C5AEBD4030BE}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rebus.Diagnostics", "Rebus.Diagnostics\Rebus.Diagnostics.csproj", "{D2BE7EE9-FF01-43C4-8BB6-31B6545ADC74}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{21C83471-FC10-4D2E-96B6-4D3605661BC6}" 13 | ProjectSection(SolutionItems) = preProject 14 | .gitignore = .gitignore 15 | appveyor.yml = appveyor.yml 16 | scripts\build.cmd = scripts\build.cmd 17 | CHANGELOG.md = CHANGELOG.md 18 | CONTRIBUTING.md = CONTRIBUTING.md 19 | LICENSE.md = LICENSE.md 20 | scripts\push.cmd = scripts\push.cmd 21 | README.md = README.md 22 | scripts\release.cmd = scripts\release.cmd 23 | EndProjectSection 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {3EBC745B-8651-4E1A-A851-E7D94B8E4EF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {3EBC745B-8651-4E1A-A851-E7D94B8E4EF7}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {3EBC745B-8651-4E1A-A851-E7D94B8E4EF7}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {3EBC745B-8651-4E1A-A851-E7D94B8E4EF7}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {D3787E01-2508-4533-83F4-C5AEBD4030BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {D3787E01-2508-4533-83F4-C5AEBD4030BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {D3787E01-2508-4533-83F4-C5AEBD4030BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {D3787E01-2508-4533-83F4-C5AEBD4030BE}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {D2BE7EE9-FF01-43C4-8BB6-31B6545ADC74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {D2BE7EE9-FF01-43C4-8BB6-31B6545ADC74}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {D2BE7EE9-FF01-43C4-8BB6-31B6545ADC74}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {D2BE7EE9-FF01-43C4-8BB6-31B6545ADC74}.Release|Any CPU.Build.0 = Release|Any CPU 43 | EndGlobalSection 44 | GlobalSection(SolutionProperties) = preSolution 45 | HideSolutionNode = FALSE 46 | EndGlobalSection 47 | GlobalSection(ExtensibilityGlobals) = postSolution 48 | SolutionGuid = {BBB4D569-C2CF-41F9-8125-334F94398744} 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /Rebus.OpenTelemetry/Configuration/MeterBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using OpenTelemetry.Metrics; 4 | 5 | using Rebus.Diagnostics; 6 | 7 | namespace Rebus.OpenTelemetry.Configuration 8 | { 9 | public static class MeterBuilderExtensions 10 | { 11 | public static MeterProviderBuilder AddRebusInstrumentation(this MeterProviderBuilder builder) 12 | { 13 | if (builder == null) throw new ArgumentNullException(nameof(builder)); 14 | 15 | return builder.AddMeter(RebusDiagnosticConstants.MeterName); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Rebus.OpenTelemetry/Configuration/TraceBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using OpenTelemetry.Trace; 4 | using Rebus.Diagnostics; 5 | 6 | namespace Rebus.OpenTelemetry.Configuration; 7 | 8 | public static class TraceBuilderExtensions 9 | { 10 | public static TracerProviderBuilder AddRebusInstrumentation(this TracerProviderBuilder builder) 11 | { 12 | if (builder == null) throw new ArgumentNullException(nameof(builder)); 13 | 14 | return builder.AddSource(RebusDiagnosticConstants.ActivitySourceName); 15 | } 16 | } -------------------------------------------------------------------------------- /Rebus.OpenTelemetry/Rebus.OpenTelemetry.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 11 5 | MIT 6 | OpenTelemetry integration for Rebus 7 | Copyright Rebus FM ApS 2012 8 | https://github.com/rebus-org/Rebus.OpenTelemetry 9 | mookid8000 10 | rebus open-telemetry diagnostics metrics 11 | little_rebusbus2_copy-500x500.png 12 | README.md 13 | 14 | 15 | 16 | 17 | True 18 | \ 19 | 20 | 21 | True 22 | \ 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | 3 | shallow_clone: true 4 | 5 | cache: 6 | - packages -> **\packages.config 7 | - '%LocalAppData%\NuGet\Cache' 8 | 9 | before_build: 10 | - appveyor-retry dotnet restore -v Minimal 11 | 12 | build_script: 13 | - dotnet build -c Release --no-restore 14 | 15 | test_script: 16 | - dotnet test -c Release --no-restore 17 | 18 | -------------------------------------------------------------------------------- /artwork/little_rebusbus2_copy-500x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebus-org/Rebus.OpenTelemetry/d2500487524104ed52e39fcfccd04fbdda69ec63/artwork/little_rebusbus2_copy-500x500.png -------------------------------------------------------------------------------- /scripts/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set scriptsdir=%~dp0 4 | set root=%scriptsdir%\.. 5 | set project=%1 6 | set version=%2 7 | 8 | if "%project%"=="" ( 9 | echo Please invoke the build script with a project name as its first argument. 10 | echo. 11 | goto exit_fail 12 | ) 13 | 14 | if "%version%"=="" ( 15 | echo Please invoke the build script with a version as its second argument. 16 | echo. 17 | goto exit_fail 18 | ) 19 | 20 | set Version=%version% 21 | 22 | pushd %root% 23 | 24 | dotnet restore 25 | if %ERRORLEVEL% neq 0 ( 26 | popd 27 | goto exit_fail 28 | ) 29 | 30 | dotnet build "%root%\Rebus.Diagnostics" -c Release --no-restore 31 | if %ERRORLEVEL% neq 0 ( 32 | popd 33 | goto exit_fail 34 | ) 35 | 36 | dotnet build "%root%\Rebus.OpenTelemetry" -c Release --no-restore 37 | if %ERRORLEVEL% neq 0 ( 38 | popd 39 | goto exit_fail 40 | ) 41 | 42 | popd 43 | 44 | 45 | 46 | 47 | 48 | 49 | goto exit_success 50 | :exit_fail 51 | exit /b 1 52 | :exit_success -------------------------------------------------------------------------------- /scripts/push.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set version=%1 4 | 5 | if "%version%"=="" ( 6 | echo Please remember to specify which version to push as an argument. 7 | goto exit_fail 8 | ) 9 | 10 | set reporoot=%~dp0\.. 11 | set destination=%reporoot%\deploy 12 | 13 | if not exist "%destination%" ( 14 | echo Could not find %destination% 15 | echo. 16 | echo Did you remember to build the packages before running this script? 17 | ) 18 | 19 | set nuget=%reporoot%\tools\NuGet\NuGet.exe 20 | 21 | if not exist "%nuget%" ( 22 | echo Could not find NuGet here: 23 | echo. 24 | echo "%nuget%" 25 | echo. 26 | goto exit_fail 27 | ) 28 | 29 | 30 | "%nuget%" push "%destination%\*.%version%.nupkg" -Source https://nuget.org 31 | if %ERRORLEVEL% neq 0 ( 32 | echo NuGet push failed. 33 | goto exit_fail 34 | ) 35 | 36 | 37 | 38 | 39 | 40 | 41 | goto exit_success 42 | :exit_fail 43 | exit /b 1 44 | :exit_success 45 | -------------------------------------------------------------------------------- /scripts/release.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set scriptsdir=%~dp0 4 | set root=%scriptsdir%\.. 5 | set deploydir=%root%\deploy 6 | set project=%1 7 | set version=%2 8 | 9 | if "%project%"=="" ( 10 | echo Please invoke the build script with a project name as its first argument. 11 | echo. 12 | goto exit_fail 13 | ) 14 | 15 | if "%version%"=="" ( 16 | echo Please invoke the build script with a version as its second argument. 17 | echo. 18 | goto exit_fail 19 | ) 20 | 21 | set Version=%version% 22 | 23 | if exist "%deploydir%" ( 24 | rd "%deploydir%" /s/q 25 | ) 26 | 27 | pushd %root% 28 | 29 | dotnet restore 30 | if %ERRORLEVEL% neq 0 ( 31 | popd 32 | goto exit_fail 33 | ) 34 | 35 | dotnet pack "%root%/Rebus.Diagnostics" -c Release -o "%deploydir%" -p:PackageVersion=%version% --no-restore 36 | if %ERRORLEVEL% neq 0 ( 37 | popd 38 | goto exit_fail 39 | ) 40 | 41 | dotnet pack "%root%/Rebus.OpenTelemetry" -c Release -o "%deploydir%" -p:PackageVersion=%version% --no-restore 42 | if %ERRORLEVEL% neq 0 ( 43 | popd 44 | goto exit_fail 45 | ) 46 | 47 | call scripts\push.cmd "%version%" 48 | 49 | popd 50 | 51 | 52 | 53 | 54 | 55 | 56 | goto exit_success 57 | :exit_fail 58 | exit /b 1 59 | :exit_success -------------------------------------------------------------------------------- /tools/NuGet/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebus-org/Rebus.OpenTelemetry/d2500487524104ed52e39fcfccd04fbdda69ec63/tools/NuGet/nuget.exe --------------------------------------------------------------------------------