Greg Burghardt
Greg Burghardt

Reputation: 18783

How to configure ASP.NET Core to use ElmahLoggerProvider in ElmahCore for ILogger<T> implementations

I'm trying to use ElmahCore with an ASP.NET Razor pages application. I am purposefully throwing an exception in one of the pages to test out the error handling.

Desired Behavior

Actual Behavior: I get the message at the top of the screen, but navigating to /elmah in my web app shows that no errors have occurred.

Screenshot of the Elmah error dashboard showing no errors have been logged.

Some basics:

Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddElmah(options =>
{
    var settings = builder.Configuration.GetSection("Elmah")
                                        .Get<ElmahSettings>();

    settings.ApplyTo(options);
//           ^^^^^^^^^^^^^^^^
//       see ElmahSettings below
});

builder.Services.AddRazorPages(...);
builder.Services.AddAuthentication(...);
builder.Services.AddCookie(...);

// more services

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.UseElmah();
//  ^^^^^^^^^^^

app.MapRazorPages();

app.Run();

ElmahSettings classes

public class ElmahSettings
{
    public string ApplicationName { get; set; }
    public ElmahEmailSettings Email { get; set; }

    /// <summary>
    /// Apply these settings to the given <see cref="ElmahOptions"/> object
    /// </summary>
    /// <param name="options"></param>
    public void ApplyTo(ElmahOptions options)
    {
        options.ApplicationName = ApplicationName;
        options.OnPermissionCheck = IsUserAuthorized;

        if (Email != null && Email.Recipients != null)
        {
            foreach (var recipient in Email.Recipients)
            {
                options.Notifiers.Add(new ErrorMailNotifier(recipient.Key, new EmailOptions()
                {
                    MailSender = Email.Sender,
                    MailRecipient = recipient.Value
                }));
            }
        }
    }

    /// <summary>
    /// Returns whether or not a request is authorized to view the Elmah error page.
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public bool IsUserAuthorized(HttpContext context)
    {
        if (!context.User.Identity.IsAuthenticated || Email == null || Email.Recipients == null)
            return false;

        var emailClaim = context.User.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Email);

        if (emailClaim == null)
            return false;

        return Email.Recipients.Any(recipient => recipient.Value.Equals(emailClaim.Value, StringComparison.OrdinalIgnoreCase));
    }
}

public class ElmahEmailSettings
{
    public string Sender { get; set; }
    public Dictionary<string, string> Recipients { get; set; }
}

Relevant contents of appsettings.json

{
  "Logging": {
    "EventLog": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft.AspNetCore": "Warning"
      }
    }
  },
  "AllowedHosts": "*",

  // maps to ElmahSettings class
  "Elmah": {
    "ApplicationName": "My Application Name",
    "Email": {
      "Sender": "[email protected]",
      "Recipients": {
        "John Doe": "[email protected]",
        "Jane Doe": "[email protected]"
      }
    }
  }
}

Error handling in one of the pages

public class TestModel : PageModel
{
    private readonly ILogger<TestModel> logger;


    public TestModel(ILogger<TestModel> logger)
    {
        this.logger = logger;
    }

    public void OnGet()
    {
        try
        {
            throw new Exception("This is just a test!");
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "An error occurred");
            //     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            //  I want this to show up in the Elmah logs

            // custom extension method to show a red error at the top of the page
            // which works as intended.
            this.ShowMessage("An error occurred, please try again.");

            return Page();
        }
    }
}

I see that ElmahCore provides an implementation of ILoggerProvider in ElmahCore.Mvc.Logging.ElmahLoggingProvider, but its constructor requires an HttpContextAccessor object. I don't see where I can access this object in Program.cs, unless I have a fully initialized IServicesCollection object, but at that point it feels too late in the lifecycle of the application to configure a logging provider.

I really feel like this should be possible if I can just get the ElmahLoggingProvider registered properly. When I debug the application, I can see the logger field in my page model is a Microsoft.Extensions.Logging.Logger object, which mentions nothing about Elmah or ElmahCore.

Question: how to properly register the ElmahLoggerProvider so errors are logged to the Elmah error dashboard?

Upvotes: 2

Views: 883

Answers (1)

Guru Stron
Guru Stron

Reputation: 141565

Based on examples in the github repo (one, two) if you catch exception explicitly you can/need to explicitly raise error:

catch (Exception ex)
{
    logger.LogError(ex, "An error occurred");
    HttpContext.RaiseError(ex);
    // or 
    // ElmahExtensions.RaiseError(ex);
    
    // ...
}

For non-handled exceptions it should work out of the box.

Upvotes: 0

Related Questions