Skip to content

Commit 5a5576e

Browse files
authored
Merge pull request #7588 from Particular/nsb10-logging-newrelic
Add New Relic Logging sample for NServiceBus 10
2 parents f8b0258 + 4dfdd53 commit 5a5576e

File tree

9 files changed

+262
-0
lines changed

9 files changed

+262
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net10.0</TargetFramework>
4+
<OutputType>Exe</OutputType>
5+
<LangVersion>preview</LangVersion>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="NewRelic.Agent.Api" Version="10.*" />
9+
<PackageReference Include="NServiceBus" Version="10.0.0-alpha.1" />
10+
<PackageReference Include="NServiceBus.Metrics" Version="6.0.0-alpha.1" />
11+
<PackageReference Include="NServiceBus.Extensions.Hosting" Version="4.0.0-alpha.1" />
12+
</ItemGroup>
13+
</Project>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using NServiceBus;
5+
6+
// Simulates busy (almost no delay) / quiet time in a sine wave
7+
class LoadSimulator(IMessageSession messageSession, TimeSpan minimumDelay, TimeSpan idleDuration)
8+
{
9+
CancellationTokenSource tokenSource = new();
10+
TimeSpan idleDuration = TimeSpan.FromTicks(idleDuration.Ticks / 2);
11+
Task fork;
12+
13+
public void Start()
14+
{
15+
fork = Task.Run(Loop, CancellationToken.None);
16+
}
17+
18+
async Task Loop()
19+
{
20+
try
21+
{
22+
while (!tokenSource.IsCancellationRequested)
23+
{
24+
await Work();
25+
var delay = NextDelay();
26+
await Task.Delay(delay, tokenSource.Token);
27+
}
28+
}
29+
catch (OperationCanceledException)
30+
{
31+
}
32+
}
33+
34+
int index;
35+
36+
TimeSpan NextDelay()
37+
{
38+
var angleInRadians = Math.PI / 180.0 * ++index;
39+
var delay = TimeSpan.FromMilliseconds(idleDuration.TotalMilliseconds * Math.Sin(angleInRadians));
40+
delay += idleDuration;
41+
delay += minimumDelay;
42+
return delay;
43+
}
44+
45+
Task Work() => messageSession.SendLocal(new SomeCommand());
46+
47+
public Task Stop()
48+
{
49+
tokenSource.Cancel();
50+
return fork;
51+
}
52+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
namespace Endpoint
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using NewRelic.Api.Agent;
6+
using NServiceBus;
7+
using NServiceBus.Configuration.AdvancedExtensibility;
8+
9+
public class NewRelicMetrics
10+
{
11+
public static void Setup(EndpointConfiguration endpointConfiguration)
12+
{
13+
#region newrelic-name-mapping
14+
15+
var endpointName = endpointConfiguration.GetSettings().EndpointName();
16+
17+
var nameMapping = new Dictionary<string, string>
18+
{
19+
// https://docs.newrelic.com/docs/agents/manage-apm-agents/agent-data/collect-custom-metrics
20+
{"# of msgs successfully processed / sec", FormatMetric("Success_Total", endpointName)},
21+
{"# of msgs pulled from the input queue /sec", FormatMetric("Fetched_Total", endpointName)},
22+
{"# of msgs failures / sec", FormatMetric("Failure_Total", endpointName)},
23+
{"Critical Time", FormatMetric("CriticalTime_Seconds", endpointName)},
24+
{"Processing Time", FormatMetric("ProcessingTime_Seconds", endpointName)},
25+
};
26+
#endregion
27+
28+
#region newrelic-enable-nsb-metrics
29+
30+
var metricsOptions = endpointConfiguration.EnableMetrics();
31+
32+
#endregion
33+
34+
#region newrelic-register-probe
35+
36+
metricsOptions.RegisterObservers(
37+
register: probeContext =>
38+
{
39+
RegisterProbes(probeContext, endpointName, nameMapping);
40+
});
41+
42+
#endregion
43+
}
44+
45+
static void RegisterProbes(ProbeContext context, string endpointName, Dictionary<string, string> nameMapping)
46+
{
47+
#region newrelic-observers-registration
48+
49+
foreach (var duration in context.Durations)
50+
{
51+
duration.Register((ref DurationEvent @event) =>
52+
{
53+
nameMapping.TryGetValue(duration.Name, out var mappedName);
54+
var newRelicName = string.Format(mappedName ?? FormatMetric(duration.Name, endpointName), Normalize(@event.MessageType));
55+
NewRelic.RecordResponseTimeMetric(newRelicName, Convert.ToInt64(@event.Duration.TotalMilliseconds));
56+
});
57+
}
58+
59+
foreach (var signal in context.Signals)
60+
{
61+
signal.Register((ref SignalEvent @event) =>
62+
{
63+
nameMapping.TryGetValue(signal.Name, out var mappedName);
64+
var newRelicName = string.Format(mappedName ?? FormatMetric(signal.Name, endpointName), Normalize(@event.MessageType));
65+
NewRelic.RecordMetric(newRelicName, 1);
66+
});
67+
}
68+
69+
#endregion
70+
}
71+
72+
static string FormatMetric(string name, string prefix) => Normalize($"Custom/NServiceBus/{prefix}/{{0}}/{name}");
73+
74+
static string Normalize(string name)
75+
{
76+
var result = name.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
77+
if (result.Length > 0)
78+
{
79+
name = result[0];
80+
}
81+
return name.Replace(" ", "_").Replace(".", "_").Replace("-", "_");
82+
}
83+
84+
static string[] SplitComma = new[] { "," };
85+
}
86+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using Microsoft.Extensions.Hosting;
3+
using Endpoint;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using NServiceBus;
6+
7+
Console.Title = "TracingEndpoint";
8+
9+
var host = Host.CreateDefaultBuilder(args)
10+
.UseConsoleLifetime()
11+
.UseNServiceBus(_ =>
12+
{
13+
var endpointConfiguration = new EndpointConfiguration("TracingEndpoint");
14+
15+
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
16+
endpointConfiguration.UseTransport<LearningTransport>();
17+
18+
NewRelicMetrics.Setup(endpointConfiguration);
19+
20+
return endpointConfiguration;
21+
})
22+
.Build();
23+
24+
await host.StartAsync();
25+
26+
var endpointInstance = host.Services.GetRequiredService<IMessageSession>();
27+
28+
#region newrelic-load-simulator
29+
30+
var simulator = new LoadSimulator(endpointInstance, TimeSpan.Zero, TimeSpan.FromSeconds(10));
31+
simulator.Start();
32+
33+
#endregion
34+
35+
try
36+
{
37+
Console.WriteLine("Endpoint started.");
38+
Console.WriteLine("Press [ENTER] to send additional messages.");
39+
Console.WriteLine("Press [Q] to quit.");
40+
41+
while (true)
42+
{
43+
switch (Console.ReadKey(true).Key)
44+
{
45+
case ConsoleKey.Q:
46+
return;
47+
case ConsoleKey.Enter:
48+
await endpointInstance.SendLocal(new SomeCommand());
49+
break;
50+
}
51+
}
52+
}
53+
finally
54+
{
55+
await simulator.Stop();
56+
await host.StopAsync();
57+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using NServiceBus;
2+
3+
class SomeCommand : ICommand;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using NServiceBus;
4+
using NServiceBus.Logging;
5+
6+
class SomeCommandHandler :
7+
IHandleMessages<SomeCommand>
8+
{
9+
static ILog log = LogManager.GetLogger<SomeCommandHandler>();
10+
static Random random = new Random();
11+
12+
public async Task Handle(SomeCommand message, IMessageHandlerContext context)
13+
{
14+
await Task.Delay(random.Next(50, 250), context.CancellationToken);
15+
16+
if (random.Next(10) <= 1)
17+
{
18+
throw new Exception("Random 10% chaos!");
19+
}
20+
21+
log.Info("Hello from SomeCommandHandler");
22+
}
23+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<configuration>
3+
<!-- startcode newrelic-appname -->
4+
<appSettings>
5+
<add key="NewRelic.AgentEnabled" value="true" />
6+
<add key="NewRelic.AppName" value="NewRelic_Metrics4" />
7+
</appSettings>
8+
<!-- endcode -->
9+
</configuration>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.29728.190
5+
MinimumVisualStudioVersion = 15.0.26730.12
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Endpoint", "Endpoint\Endpoint.csproj", "{FF5BEF96-88BA-470D-975A-4EF710EE2B7A}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
EndGlobalSection
12+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
13+
{FF5BEF96-88BA-470D-975A-4EF710EE2B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14+
{FF5BEF96-88BA-470D-975A-4EF710EE2B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
15+
EndGlobalSection
16+
GlobalSection(SolutionProperties) = preSolution
17+
HideSolutionNode = FALSE
18+
EndGlobalSection
19+
EndGlobal

samples/logging/new-relic/Metrics_6/prerelease.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)