Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Marking private classes as public would provide an easier time creating custom Probe types #162

Open
bh3605 opened this issue Jan 4, 2021 · 5 comments

Comments

@bh3605
Copy link

bh3605 commented Jan 4, 2021

Issue

In the current version of NServiceBus.Metrics the Metrics feature, Probe class, and ProbePropertiesAttribute are closed off from public consumption. In a previous PR the Metrics feature was opened up allowing for public consumption and allowing other features to depend on its availability. However, the Probe and ProbePropertiesAttribute classes are still closed off.

Solution

What would be great is having the Probe class and ProbePropertiesAttribute class marked public as well. This would enable custom probe types to be registered in a feature which depends on the MetricsFeature.

I've had to essentially rebuild this package in my own project because I wanted to be able to register a probe that would contain the processing time and the LogicalMessage object. Was able to reuse the IProbe interface and inherit from ProbeContext to manage a small amount of compatibility. Had to rewrite everything else. Opening up the Probe class would allow me to inherit from it and same idea for the ProbePropertiesAttribute class. The implementation of OpenProbeProperties in the example below is exactly the same as ProbeProperties along with a custom Probe class I named OpenProbe.

Example of some code I have using the (once published) Metrics Feature:

public class ExtraMetricsFeature : Feature
{
	public ExtraMetricsFeature()
	{
		EnableByDefault();
		DependsOn<MetricsFeature();
		Defaults(settings => {
			settings.SetDefault<ExtraMetricsOptions>();
		});
	}
	
	protected  override  void  Setup(FeatureConfigurationContext  context)
	{
		var options = context.Settings.GetOrDefault<ExtraMetricsOptions>();
		var probeContext = BuildProbes(context);
		context.RegisterStartupTask(new SetupExtraRegisteredObservers(options, probeContext);
	}

	private MessageProbeContext BuildProbes(FeatureConfigurationContext context)
	{
		var behavior = new MessageDiagnosticsBehavior();
		context.Pipeline.Register("MessageDiagnosticsBehavior", behavior, "Provides performance counter and message content for message analysis");
		var myProbeBuilders = new MyProbeBuilder[]
		{
			new MessageHandledProbeBuilder(behavior)
		};
			
		return new MessageProbeContext(myProbeBuilders.Select(b => b.Build()).ToArray());
	}
}

class SetupExtraRegisteredObservers : FeatureStartupTask
{
	readonly MessageMetricsOptions options;
	readonly MessageProbeContext probeContext;

	public SetupExtraRegisteredObservers(ExtraMetricsOptions options, MessageProbeContext probeContext)
	{
	    this.options = options;
	    this.probeContext = probeContext;
        }

	protected override Task OnStart(IMessageSession session)
	{
		options.SetUpObservers(probeContext);
		return Task.FromResult(0);
	}

	protected override Task OnStop(IMessageSession session) => Task.FromResult(0);
}

public class MessageProbeContext : ProbeContext
{
	/// <summary>
	/// Creates <see cref="MessageProbeContext"/>.
	/// </summary>
	public MessageProbeContext(IReadOnlyCollection<IMyProbe> probes) : base(new IDurationProbe[0], new ISignalProbe[0])
	{
		Probes = probes;
	}

	/// <summary>
	/// My custom type probes.
	/// </summary>
	public IReadOnlyCollection<IMyProbe> Probes { get; }
}
	
[OpenProbeProperties("Handled", "The message content and handling duration.")]
public class MessageHandledProbeBuilder : MyProbeBuilder
{
	private readonly MessageDiagnosticsBehavior behavior;
	public MessageHandledProbeBuilder(MessageDiagnosticsBehavior behavior)
	{
		this.behavior = behavior;
	}

	protected override void WireUp(MyProbe probe)
	{
		behavior.ProcessingSuccess = probe;
	}
}
	
public class MessageDiagnosticsBehavior : IBehavior<IIncomingLogicalMessageContext, IIncomingLogicalMessageContext>
{
	public MyProbe ProcessingSuccess;
	public async Task Invoke(IIncomingLogicalMessageContext context, Func<IIncomingLogicalMessageContext, Task> next)
	{
		var sw = new Stopwatch();
		sw.Start();
		try
		{
			await next(context).ConfigureAwait(false);
			sw.Stop();
		}
		catch(Exception)
		{
			sw.Stop();
			throw;
		}

		var @event = new ContentEvent(context.Message, sw.Elapsed);
		ProcessingSuccess?.Record(@event);
	}
}
@timbussmann
Copy link
Contributor

what kind of custom probe do you want to write?

I think just opening up the types wouldn't be enough to ensure proper integration they would also need to be added the the ProbeContext managed by the feature?

In a previous PR the Metrics feature was opened up allowing for public consumption and allowing other features to depend on its availability.

btw. the primary intention was for metrics reporting implementations to be able to depend properly on the feature to ensure the correct startup order.

@bh3605
Copy link
Author

bh3605 commented Feb 11, 2021

You're correct. I did have to create a new ProbeContext object to accommodate my custom one.

I want a custom probe containing the duration and message body.

@timbussmann
Copy link
Contributor

I want a custom probe containing the duration and message body.

have you tried to implement this using a custom behavior instead? This might be a much simpler way of implementing something like this, as access to the message body is easily possible too.

@bh3605
Copy link
Author

bh3605 commented Feb 12, 2021

I did try a custom behavior, but it wasn't what I wanted. I wanted the ability to fire and forget the event to the probe listener without incurring any further message processing time.

@timbussmann
Copy link
Contributor

Thanks for sharing some more details on your usage scenario. I'm not sure the package was designed to be extensible in such ways but I'll forward this as a feature request to our internal backlog for further prioritization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants