xunit-logging icon indicating copy to clipboard operation
xunit-logging copied to clipboard

Add custom prefix or customize message format

Open berhir opened this issue 2 years ago • 4 comments

Is your feature request related to a problem? Please describe.

We have many integration tests which start a web server (using WebApplicationFactory) and a client app that has its own host and service collection. Now both services, the server and the client, are writing log messages to the same output and it's hard to find out if a log message comes from the server or the client.

Describe the solution you'd like

I would like to add a custom prefix to all messages so I can easily distinguish if a message is from the server or the client. A simple solution would be to add a new property to the XUnitLoggerOptions to define a custom prefix. Or a bit more generic solution would be to add a property that allows me to define a function where I can manipulate the message in any way before it gets logged.

A more complex solution would be to add support for custom formatters. The default console logger has support for it. Here is a sample how to add a custom prefix to all console logs using a custom formatter: https://learn.microsoft.com/en-us/dotnet/core/extensions/console-log-formatter#implement-a-custom-formatter

Describe alternatives you've considered

I didn't find one

Additional context

berhir avatar Jul 05 '23 09:07 berhir

Thanks for the suggestion. This sounds like a reasonable request, I'll take a look into it sometime in the next few days.

I think just allowing the formatting to be completely overridden, rather than adding something specific to your use case, would be the best approach.

martincostello avatar Jul 05 '23 15:07 martincostello

I've had a quick look at this just now.

I can fairly easily add an option to completely replace the message formatting implementation, but this then means you need to re-implement everything just to tweak it (say by adding a prefix).

A better way to do it would be to refactor to make it composable enough so you can just tweak it, but that will need further design thought as it's likely going to involve changing the API surface in a breaking way.

To do what you want in the currently available version, I would suggest wrapping the output helper in another like this so you can intercept the call to write and add your prefix:

outputHelper = new PrefixedOutputHelper("Client", outputHelper);

public class PrefixedOutputHelper : ITestOutputHelper
{
    public PrefixedOutputHelper(string prefix, ITestOutputHelper inner)
    {
        Prefix = prefix;
        Inner = inner;
    }

    private string Prefix { get; }

    private ITestOutputHelper Inner { get; }

    public void WriteLine(string message)
        => Inner.WriteLine($"{Prefix} - {message}");

    public void WriteLine(string format, params object[] args)
        => Inner.WriteLine($"{Prefix} - {format}", args);
}

I'll think about adding the functionality to customise properly as a first-class feature at some point in the future.

martincostello avatar Jul 07 '23 10:07 martincostello

Thank you for taking a look and the suggestion, for my use case this is a perfect solution!

I can fairly easily add an option to completely replace the message formatting implementation, but this then means you need to re-implement everything just to tweak it (say by adding a prefix).

Maybe you can provide a default formatter implementation that can be derived from or wrapped (like wrapping the ITestOutputHelper in your suggested solution), then it wouldn't be necessary to re-implement everything.

Anyway, I am totally happy with your provided solution, but for more advanced use cases it still may be a nice feature to completely replace and customize the formatter.

berhir avatar Jul 11 '23 06:07 berhir

If I'm going to go to the effort of publishing a new release, I think I'd just go the whole hog and introduce the ability to provide a custom formatter like for the console.

Most of the implementation is actually based on the console, it was just written before that capability was added - otherwise I'd have probably implemented something similar at the time 😄

martincostello avatar Jul 11 '23 08:07 martincostello