Scroll Top

StarCellar E03: Configuring logging with Apizr

SCE03_gif

Episode 03 of an Apizr walkthrough building a MAUI app requesting a backend API.

Apizr is a Refit based web api client manager, but resilient (retry, connectivity, cache, auth, log, priority, etc…).

This episode is about configuring logging.

APIZR

If you’re new to Apizr, you shoud know that it’s based on Refit and aims to provide many more features on top of it, like retry handling, connectivity check, cache management, authentication handling, priority management, and so on… It’s here to help me to don’t repeat myself into each and every projects while dealing with api request calls and resilience needs.

Get the big picture by reading the documentation:

Read - Documentation

Feel free to browse code, tests and samples and maybe to submit PRs or ideas:

Browse - Source

Don’t forget the YouTube Channel Playlist about Apizr:

Watch - Tutorials

The starcellar series

The StarCellar walkthrough series is built thanks to multiple episodes, each with a dedicated source code branch, blog post and playlist video.

Feel free to fork the project and start from the main branch, so that you could follow this tutorial coding by yourself. If you do so, don’t forget to run the API through a Dev Tunnel and to update the MAUI app’s base address.

the starcellar episode 03

The current episode is covered by this video:

Configuring logging Without Apizr

First, the Without.Apizr project as usual!

Start by referencing NuGet packages:

  • HttpTracer

Now open the MauiProgram class and scroll to the Refit registration part.

There, add the following RefitSettings parameter to both RefitClient registrations:

new RefitSettings
{
    HttpMessageHandlerFactory = () => new HttpTracerHandler
    {
        Verbosity = HttpMessageParts.All
    }
}

Here we provide the HttpTracerHandler to Refit by setting its HttpMessageHandler.

HttpTracerHandler will trace http traces with a verbosity set to All.

You can run the app and see that there’s now some http traces right into the VS output.

You should see something like that:

Configuring logging With Apizr

Now the With.Apizr project!

Nothing more to install, just decorate APIs by the Log attribute provided by Apizr like:

[BaseAddress("/wines"), Log]
public interface ICellarApi
{
    [Get("")]
    Task<IEnumerable<Wine>> GetWinesAsync();

    [Get("/{id}")]
    Task<Wine> GetWineDetailsAsync(Guid id);

    [Post("")]
    Task<Wine> CreateWineAsync(Wine item);
    
    [Put("/{id}")]
    Task UpdateWineAsync(Guid id, Wine item);
    
    [Delete("/{id}")]
    Task DeleteWineAsync(Guid id);
}

Here we tell Apizr to log all traces by default thanks to attribute design.

You can run the app and see that there’s now some http traces right into the VS output.

Adjusting logging options With Apizr

When you don’t say anything but just Log like in previous API design, Apizr will use following default logging options:

  • HttpTracerMode: Everything (will log everything anytime)
  • TrafficVerbosity: All (will log all http traces)
  • LogLevels: [Low] Trace, [Medium] Information and [High] Critical (log levels to use while writing logs)

You definitly can ajust logging options instead of using default ones.

Here is the same API with more custom options:

[assembly:Log]
namespace StarCellar.With.Apizr.Services.Apis.Cellar
{
    [BaseAddress("/wines"), 
    Log(HttpMessageParts.RequestAll, HttpTracerMode.ErrorsAndExceptionsOnly, LogLevel.Information)]
    public interface ICellarApi
    {
        [Get(""), 
        Log(HttpMessageParts.RequestBody, HttpTracerMode.ExceptionsOnly, LogLevel.Warning)]
        Task<IEnumerable<Wine>> GetWinesAsync();

        [Get("/{id}")]
        Task<Wine> GetWineDetailsAsync(Guid id);

        [Post("")]
        Task<Wine> CreateWineAsync(Wine item);
        
        [Put("/{id}")]
        Task UpdateWineAsync(Guid id, Wine item);
        
        [Delete("/{id}")]
        Task DeleteWineAsync(Guid id);
    }
}

There, you can see that we decided to apply the default logging configuration to all assembly APIs, but a custom one to ICellarApi specific API and all its requests, but a custom one to GetWinesAsync specific request.

You can run the app and see that everything’s logging as asked to.

Understanding Apizr logging configuration pipeline

Previously, we configured logging options by design, decorating attribute at different levels:

  • APIs Assembly: will be shared by all APIs in the assembly
  • API interface: will be set to decorated API only, overriding parent logging options
  • Request method: will be set to decorated request only, overriding parent logging options

But we can do the same fluently at registration and request time (with ApizrRequestOptions parameter) like:

// Registering
builder.Services.AddApizr(
    registry => registry
        .AddManagerFor<ICellarApi>(options => options
            .WithLogging(HttpMessageParts.RequestAll, HttpTracerMode.ErrorsAndExceptionsOnly, LogLevel.Information))
        .AddManagerFor<IFileApi>(),

    options => options
        .WithLogging());

...

// Requesting
var wines = await _cellarApiManager.ExecuteAsync((api, opt) => api.GetWinesAsync(opt), 
    options => options.WithLogging(HttpMessageParts.RequestBody, HttpTracerMode.ExceptionsOnly, LogLevel.Warning));

Actually, we even can mix it all together, I mean Design, Register and Request time configurations.

Just keep in mind that fluent configuration always wins over attribute one, and the closest configuration to the request always wins over all others.

Redacting logged headers With Apizr

While logging http traces, you may have to respect some privacy or security policies, like hiding sensitive headers.

Apizr lets you do that by design using a star * symbol:

[BaseAddress("/wines"), Log]
public interface ICellarApi
{
    [Get(""), Headers("MyHeaderKey: *MyHeaderValue*")]
    Task<IEnumerable<Wine>> GetWinesAsync();

    ...
}

Or fluently thanks to the following options:

// direct header configuration
options => options.WithHeaders(["MyHeaderKey: *MyHeaderValue*"])

// OR direct redacting configuration
options => options.WithLoggedHeadersRedactionNames(new[]{ "MyHeaderKey" })

// OR factory redacting configuration
options => options.WithLoggedHeadersRedactionRule(header => header == "MyHeaderKey")

From there, you should see your logs with headers redacted like so:

 ==================== HTTP REQUEST: [GET] ==================== 
GET https://mycustomurl.com/path
Request Headers:
MyHeaderKey: *
Running the app

Nothing’s changed on the user experience point of view. The same app, but with some options adjusted and shared thanks to Apizr:

get more of it

Feel free to ask me anything on twitter, or opening a discussion, issue or PR on GitHub.

Again, it’s basically all built for my own use and motivated by my needs. But it’s live on Nuget and opened to all so feel free to contribute.

Apizr brings many more features so you should head to the next episode to continue your walkthrough path.

Specialized since 2013 in cross-platform applications development for iOS, Android and Windows, using technologies such as Microsoft Xamarin and Microsoft Azure. Initially focused, since 2005, on development, then administration of Customer Relationship Management systems, mainly around solutions such as Microsoft SharePoint and Microsoft Dynamics CRM.

Related Posts

Leave a comment