diff --git a/ExceptionNotification.Core.Tests/Email/EmailBuilderTests.cs b/ExceptionNotification.Core.Tests/Email/EmailBuilderTests.cs index 3dd6496..5e5933e 100644 --- a/ExceptionNotification.Core.Tests/Email/EmailBuilderTests.cs +++ b/ExceptionNotification.Core.Tests/Email/EmailBuilderTests.cs @@ -8,13 +8,13 @@ using Microsoft.AspNetCore.Http.Internal; using Xunit; -namespace ExceptionNotification.Core.Tests +namespace ExceptionNotification.Core.Tests.Email { public class EmailBuilderTests { private readonly Exception _exception; - private readonly IEmailConfiguration _emailConfiguration; + private readonly EmailConfiguration _emailConfiguration; private readonly NotifierOptions _notifierOptions; @@ -46,7 +46,7 @@ public void ComposeEmailThrowsExceptionWhenSenderIsNull() { _emailConfiguration.Sender = null; - var exception = Assert.Throws(() => EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions)); + var exception = Assert.Throws(() => EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions, null)); Assert.Equal("ComposeEmail failure: Sender is null.", exception.Message); } @@ -55,12 +55,12 @@ public void ComposeEmailThrowsExceptionWhenRecipientsCollectionIsEmpty() { _emailConfiguration.Recipients = null; - var exception = Assert.Throws(() => EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions)); + var exception = Assert.Throws(() => EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions, null)); Assert.Equal("ComposeEmail failure: Recipients collection is empty.", exception.Message); _emailConfiguration.Recipients = new List(); - exception = Assert.Throws(() => EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions)); + exception = Assert.Throws(() => EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions, null)); Assert.Equal("ComposeEmail failure: Recipients collection is empty.", exception.Message); } @@ -68,7 +68,7 @@ public void ComposeEmailThrowsExceptionWhenRecipientsCollectionIsEmpty() [Fact] public void ComposeEmailReturnsMailMessage() { - var email = EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions); + var email = EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions, null); Assert.IsType(email); Assert.Equal("[Fried Chicken - Test] EXCEPTION!", email.Subject); @@ -84,7 +84,7 @@ public void ComposeEmailReturnsMailMessage() [Fact] public void ComposeEmailBuildsMessageBody() { - var email = EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions); + var email = EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions, null); Assert.IsType(email); Assert.Contains("------------------\nException Message:\n------------------\n\nThis is an exception!", email.Body); @@ -104,8 +104,8 @@ public void ComposeEmailBuildsMessageBodyWithRequest() Method = HttpMethod.Get.ToString() } }; - _notifierOptions.Request = new DefaultHttpRequest(httpContext); - var email = EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions); + var request = new DefaultHttpRequest(httpContext); + var email = EmailBuilder.ComposeEmail(_exception, _emailConfiguration, _notifierOptions, request); Assert.IsType(email); Assert.Contains("------------------\nException Message:\n------------------\n\nThis is an exception!", email.Body); diff --git a/ExceptionNotification.Core.Tests/Email/EmailExceptionNotifierTests.cs b/ExceptionNotification.Core.Tests/Email/EmailExceptionNotifierTests.cs deleted file mode 100644 index 5e3005c..0000000 --- a/ExceptionNotification.Core.Tests/Email/EmailExceptionNotifierTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using ExceptionNotification.Core.Email; -using ExceptionNotification.Core.Exceptions.Email; -using Xunit; - -namespace ExceptionNotification.Core.Tests.Email -{ - public class EmailExceptionNotifierTests - { - [Fact] - public void NotifyExceptionThrowsExceptionWhenConfigurationIsNull() - { - EmailExceptionNotifier.Setup(null); - - var exception = Assert.Throws(() => EmailExceptionNotifier.NotifyException(new Exception(), new NotifierOptions())); - Assert.Equal("NotifyException failure: configuration is null.", exception.Message); - } - - [Fact] - public void NotifyExceptionThrowsExceptionWhenExceptionIsNull() - { - EmailExceptionNotifier.Setup(new EmailConfiguration()); - - var exception = Assert.Throws(() => EmailExceptionNotifier.NotifyException(null, new NotifierOptions())); - Assert.Equal("NotifyException failure: exception is null.", exception.Message); - } - } -} diff --git a/ExceptionNotification.Core.Tests/Email/EmailNotifierTests.cs b/ExceptionNotification.Core.Tests/Email/EmailNotifierTests.cs new file mode 100644 index 0000000..5ef70e5 --- /dev/null +++ b/ExceptionNotification.Core.Tests/Email/EmailNotifierTests.cs @@ -0,0 +1,28 @@ +using System; +using ExceptionNotification.Core.Email; +using ExceptionNotification.Core.Exceptions.Email; +using Xunit; + +namespace ExceptionNotification.Core.Tests.Email +{ + public class EmailNotifierTests + { + [Fact] + public void FireExceptionThrowsExceptionWhenConfigurationIsNull() + { + var notifier = new EmailNotifier(null); + + var exception = Assert.Throws(() => notifier.FireNotification(new Exception(), null)); + Assert.Equal("FireNotification failure: configuration is null.", exception.Message); + } + + [Fact] + public void FireExceptionThrowsExceptionWhenExceptionIsNull() + { + var notifier = new EmailNotifier(new EmailConfiguration()); + + var exception = Assert.Throws(() => notifier.FireNotification(null, null)); + Assert.Equal("FireNotification failure: exception is null.", exception.Message); + } + } +} diff --git a/ExceptionNotification.Core.Tests/Hipchat/HipchatNotifierTests.cs b/ExceptionNotification.Core.Tests/Hipchat/HipchatNotifierTests.cs new file mode 100644 index 0000000..d1463a7 --- /dev/null +++ b/ExceptionNotification.Core.Tests/Hipchat/HipchatNotifierTests.cs @@ -0,0 +1,28 @@ +using System; +using ExceptionNotification.Core.Exceptions.Email; +using ExceptionNotification.Core.Hipchat; +using Xunit; + +namespace ExceptionNotification.Core.Tests.Hipchat +{ + public class HipchatNotifierTests + { + [Fact] + public void FireExceptionThrowsExceptionWhenConfigurationIsNull() + { + var notifier = new HipchatNotifier(null); + + var exception = Assert.Throws(() => notifier.FireNotification(new Exception(), null)); + Assert.Equal("FireNotification failure: configuration is null.", exception.Message); + } + + [Fact] + public void FireExceptionThrowsExceptionWhenExceptionIsNull() + { + var notifier = new HipchatNotifier(new HipchatConfiguration()); + + var exception = Assert.Throws(() => notifier.FireNotification(null, null)); + Assert.Equal("FireNotification failure: exception is null.", exception.Message); + } + } +} diff --git a/ExceptionNotification.Core/BaseNotifier.cs b/ExceptionNotification.Core/BaseNotifier.cs new file mode 100644 index 0000000..f82d652 --- /dev/null +++ b/ExceptionNotification.Core/BaseNotifier.cs @@ -0,0 +1,26 @@ +using System; +using System.Reflection; +using Microsoft.AspNetCore.Http; + +namespace ExceptionNotification.Core +{ + public class BaseNotifier + { + protected NotifierOptions NotifierOptions; + + public BaseNotifier() + { + NotifierOptions = new NotifierOptions + { + ProjectName = Assembly.GetEntryAssembly().GetName().Name, + Environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") + }; + } + + public virtual void FireNotification(Exception exception) + {} + + public virtual void FireNotification(Exception exception, HttpRequest request) + {} + } +} diff --git a/ExceptionNotification.Core/Email/EmailBuilder.cs b/ExceptionNotification.Core/Email/EmailBuilder.cs index 056ef77..d747226 100644 --- a/ExceptionNotification.Core/Email/EmailBuilder.cs +++ b/ExceptionNotification.Core/Email/EmailBuilder.cs @@ -8,7 +8,7 @@ namespace ExceptionNotification.Core.Email { public static class EmailBuilder { - public static MailMessage ComposeEmail(Exception exception, IEmailConfiguration emailConfiguration, NotifierOptions notifierOptions) + public static MailMessage ComposeEmail(Exception exception, EmailConfiguration emailConfiguration, NotifierOptions notifierOptions, HttpRequest request) { if (IsSenderNull(emailConfiguration.Sender)) { @@ -24,7 +24,7 @@ public static MailMessage ComposeEmail(Exception exception, IEmailConfiguration { Subject = ComposeSubject(notifierOptions), From = new MailAddress(emailConfiguration.Sender.Address, emailConfiguration.Sender.DisplayName), - Body = ComposeContent(exception, notifierOptions.Request) + Body = ComposeContent(exception, request) }; emailConfiguration.Recipients.ForEach(r => diff --git a/ExceptionNotification.Core/Email/EmailConfiguration.cs b/ExceptionNotification.Core/Email/EmailConfiguration.cs index 401fade..b0e26ae 100644 --- a/ExceptionNotification.Core/Email/EmailConfiguration.cs +++ b/ExceptionNotification.Core/Email/EmailConfiguration.cs @@ -2,7 +2,7 @@ namespace ExceptionNotification.Core.Email { - public class EmailConfiguration : IEmailConfiguration + public class EmailConfiguration { public string SmtpServer { get; set; } diff --git a/ExceptionNotification.Core/Email/EmailExceptionNotifier.cs b/ExceptionNotification.Core/Email/EmailExceptionNotifier.cs deleted file mode 100644 index ff0c4db..0000000 --- a/ExceptionNotification.Core/Email/EmailExceptionNotifier.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Net; -using System.Net.Mail; -using System.Reflection; -using ExceptionNotification.Core.Exceptions.Email; - -namespace ExceptionNotification.Core.Email -{ - public static class EmailExceptionNotifier - { - private static IEmailConfiguration _configuration; - - public static void Setup(IEmailConfiguration configuration) - { - _configuration = configuration; - } - - public static void NotifyException(Exception exception) - { - var notifierOptions = new NotifierOptions - { - ProjectName = Assembly.GetEntryAssembly().GetName().Name, - Environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") - }; - - NotifyException(exception, notifierOptions); - } - - public static void NotifyException(Exception exception, NotifierOptions options) - { - if (_configuration == null) - { - throw new ConfigurationMissingException("NotifyException failure: configuration is null."); - } - - if (exception == null) - { - throw new ExceptionMissingException("NotifyException failure: exception is null."); - } - - var message = EmailBuilder.ComposeEmail(exception, _configuration, options); - - using (var client = new SmtpClient(_configuration.SmtpServer, _configuration.SmtpPort)) - { - client.UseDefaultCredentials = false; - - if (_configuration.UseCredentials) - { - client.Credentials = new NetworkCredential(_configuration.SmtpUser, _configuration.SmtpPassword); - } - - client.EnableSsl = _configuration.EnableSsl; - client.Send(message); - } - } - } -} diff --git a/ExceptionNotification.Core/Email/EmailNotifier.cs b/ExceptionNotification.Core/Email/EmailNotifier.cs new file mode 100644 index 0000000..6882130 --- /dev/null +++ b/ExceptionNotification.Core/Email/EmailNotifier.cs @@ -0,0 +1,59 @@ +using System; +using System.Net; +using System.Net.Mail; +using ExceptionNotification.Core.Exceptions.Email; +using Microsoft.AspNetCore.Http; + +namespace ExceptionNotification.Core.Email +{ + public class EmailNotifier : BaseNotifier + { + private readonly EmailConfiguration _configuration; + + public EmailNotifier(EmailConfiguration configuration) + { + _configuration = configuration; + } + + public override void FireNotification(Exception exception) + { + FireNotification(exception, null); + } + + public override void FireNotification(Exception exception, HttpRequest request) + { + if (_configuration == null) + { + throw new ConfigurationMissingException("FireNotification failure: configuration is null."); + } + + if (exception == null) + { + throw new ExceptionMissingException("FireNotification failure: exception is null."); + } + + var message = EmailBuilder.ComposeEmail(exception, _configuration, NotifierOptions, request); + + try + { + using (var client = new SmtpClient(_configuration.SmtpServer, _configuration.SmtpPort)) + { + client.UseDefaultCredentials = false; + + if (_configuration.UseCredentials) + { + client.Credentials = + new NetworkCredential(_configuration.SmtpUser, _configuration.SmtpPassword); + } + + client.EnableSsl = _configuration.EnableSsl; + client.Send(message); + } + } + catch (Exception) + { + // + } + } + } +} diff --git a/ExceptionNotification.Core/Email/IEmailConfiguration.cs b/ExceptionNotification.Core/Email/IEmailConfiguration.cs deleted file mode 100644 index 8da8f97..0000000 --- a/ExceptionNotification.Core/Email/IEmailConfiguration.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; - -namespace ExceptionNotification.Core.Email -{ - public interface IEmailConfiguration - { - string SmtpServer { get; set; } - - int SmtpPort { get; set; } - - string SmtpUser { get; set; } - - string SmtpPassword { get; set; } - - bool UseCredentials { get; set; } - - bool EnableSsl { get; set; } - - EmailAddress Sender { get; set; } - - List Recipients { get; set; } - } -} diff --git a/ExceptionNotification.Core/ExceptionNotification.Core.csproj b/ExceptionNotification.Core/ExceptionNotification.Core.csproj index 6b8d3f9..0fbc620 100644 --- a/ExceptionNotification.Core/ExceptionNotification.Core.csproj +++ b/ExceptionNotification.Core/ExceptionNotification.Core.csproj @@ -9,10 +9,11 @@ https://github.com/merodriguezblanco/ExceptionNotification.Core dotnet dotnetcore-2 exception-handling true - 1.1.0 + 1.2.0 + diff --git a/ExceptionNotification.Core/ExceptionNotifier.cs b/ExceptionNotification.Core/ExceptionNotifier.cs new file mode 100644 index 0000000..797ac4a --- /dev/null +++ b/ExceptionNotification.Core/ExceptionNotifier.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using ExceptionNotification.Core.Email; +using ExceptionNotification.Core.Hipchat; +using Microsoft.AspNetCore.Http; + +namespace ExceptionNotification.Core +{ + public static class ExceptionNotifier + { + private static readonly List _notifiers = new List(); + + private static IExceptionNotifierConfiguration _configuration; + + public static void Setup(IExceptionNotifierConfiguration configuration) + { + _configuration = configuration; + + if (_configuration.Email != null) + { + _notifiers.Add(new EmailNotifier(_configuration.Email)); + } + + if (_configuration.Hipchat != null) + { + _notifiers.Add(new HipchatNotifier(_configuration.Hipchat)); + } + } + + public static void NotifyException(Exception exception) + { + _notifiers.ForEach(notifier => + { + notifier.FireNotification(exception); + }); + } + + public static void NotifyException(Exception exception, HttpRequest request) + { + _notifiers.ForEach(notifier => + { + notifier.FireNotification(exception, request); + }); + } + } +} diff --git a/ExceptionNotification.Core/ExceptionNotifierConfiguration.cs b/ExceptionNotification.Core/ExceptionNotifierConfiguration.cs new file mode 100644 index 0000000..a882fae --- /dev/null +++ b/ExceptionNotification.Core/ExceptionNotifierConfiguration.cs @@ -0,0 +1,12 @@ +using ExceptionNotification.Core.Email; +using ExceptionNotification.Core.Hipchat; + +namespace ExceptionNotification.Core +{ + public class ExceptionNotifierConfiguration : IExceptionNotifierConfiguration + { + public EmailConfiguration Email { get; set; } + + public HipchatConfiguration Hipchat { get; set; } + } +} diff --git a/ExceptionNotification.Core/Hipchat/HipchatConfiguration.cs b/ExceptionNotification.Core/Hipchat/HipchatConfiguration.cs new file mode 100644 index 0000000..9a8b0f5 --- /dev/null +++ b/ExceptionNotification.Core/Hipchat/HipchatConfiguration.cs @@ -0,0 +1,9 @@ +namespace ExceptionNotification.Core.Hipchat +{ + public class HipchatConfiguration + { + public string ApiToken { get; set; } + + public string RoomName { get; set; } + } +} diff --git a/ExceptionNotification.Core/Hipchat/HipchatNotifier.cs b/ExceptionNotification.Core/Hipchat/HipchatNotifier.cs new file mode 100644 index 0000000..33983d5 --- /dev/null +++ b/ExceptionNotification.Core/Hipchat/HipchatNotifier.cs @@ -0,0 +1,53 @@ +using System; +using ExceptionNotification.Core.Exceptions.Email; +using HipChat.Net; +using HipChat.Net.Http; +using HipChat.Net.Models.Request; +using Microsoft.AspNetCore.Http; + +namespace ExceptionNotification.Core.Hipchat +{ + public class HipchatNotifier : BaseNotifier + { + private readonly HipchatConfiguration _configuration; + + public HipchatNotifier(HipchatConfiguration configuration) + { + _configuration = configuration; + } + + public override void FireNotification(Exception exception) + { + FireNotification(exception, null); + } + + public override void FireNotification(Exception exception, HttpRequest request) + { + if (_configuration == null) + { + throw new ConfigurationMissingException("FireNotification failure: configuration is null."); + } + + if (exception == null) + { + throw new ExceptionMissingException("FireNotification failure: exception is null."); + } + + try + { + var client = new HipChatClient(new ApiConnection(new Credentials(_configuration.ApiToken))); + client.Rooms.SendNotificationAsync(_configuration.RoomName, NotificationMessage(), true, MessageFormat.Html, + MessageColor.Red); + } + catch (Exception) + { + // + } + } + + private string NotificationMessage() + { + return $"[{NotifierOptions.ProjectName} - {NotifierOptions.Environment}] EXCEPTION!"; + } + } +} diff --git a/ExceptionNotification.Core/IExceptionNotifierConfiguration.cs b/ExceptionNotification.Core/IExceptionNotifierConfiguration.cs new file mode 100644 index 0000000..338f4ab --- /dev/null +++ b/ExceptionNotification.Core/IExceptionNotifierConfiguration.cs @@ -0,0 +1,12 @@ +using ExceptionNotification.Core.Email; +using ExceptionNotification.Core.Hipchat; + +namespace ExceptionNotification.Core +{ + public interface IExceptionNotifierConfiguration + { + EmailConfiguration Email { get; set; } + + HipchatConfiguration Hipchat { get; set; } + } +} diff --git a/ExceptionNotification.Core/Middlewares/EmailExceptionMiddleware.cs b/ExceptionNotification.Core/Middlewares/ExceptionMiddleware.cs similarity index 62% rename from ExceptionNotification.Core/Middlewares/EmailExceptionMiddleware.cs rename to ExceptionNotification.Core/Middlewares/ExceptionMiddleware.cs index 9566955..c7cd9b6 100644 --- a/ExceptionNotification.Core/Middlewares/EmailExceptionMiddleware.cs +++ b/ExceptionNotification.Core/Middlewares/ExceptionMiddleware.cs @@ -3,15 +3,14 @@ using System.Net; using System.Reflection; using System.Threading.Tasks; -using ExceptionNotification.Core.Email; namespace ExceptionNotification.Core.Middlewares { - public class EmailExceptionMiddleware + public class ExceptionMiddleware { private readonly RequestDelegate _next; - public EmailExceptionMiddleware(RequestDelegate next) + public ExceptionMiddleware(RequestDelegate next) { _next = next; } @@ -30,13 +29,7 @@ public async Task InvokeAsync(HttpContext context) private Task HandleExceptionAsync(HttpContext context, Exception exception) { - var notifierOptions = new NotifierOptions - { - ProjectName = Assembly.GetEntryAssembly().GetName().Name, - Environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), - Request = context.Request - }; - EmailExceptionNotifier.NotifyException(exception, notifierOptions); + ExceptionNotifier.NotifyException(exception, context.Request); context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; diff --git a/ExceptionNotification.Core/NotifierOptions.cs b/ExceptionNotification.Core/NotifierOptions.cs index d5861bd..0faedd9 100644 --- a/ExceptionNotification.Core/NotifierOptions.cs +++ b/ExceptionNotification.Core/NotifierOptions.cs @@ -7,7 +7,5 @@ public class NotifierOptions public string ProjectName { get; set; } public string Environment { get; set; } - - public HttpRequest Request { get; set; } } } diff --git a/README.md b/README.md index ed572fb..9b0d094 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Overview -`ExceptionNotification.Core` is a NET core package that provides a set of notifiers for sending exception notifications when errors occur in your NET Core API. So far, the notifiers can deliver notifications only via e-mail. Its idea is based on the great [ExceptionNotification gem](https://github.com/smartinez87/exception_notification) that provides notifiers for Ruby applications. +`ExceptionNotification.Core` is a NET core package that provides a set of notifiers for sending exception notifications when errors occur in your NET Core API. So far, the notifiers can deliver notifications only via e-mail and hipchat. Its idea is based on the great [ExceptionNotification gem](https://github.com/smartinez87/exception_notification) that provides notifiers for Ruby applications. ## **WARNING: This plugin is in early development stage.** @@ -19,7 +19,7 @@ Install this package using the NuGet command line: ```bash -PM> Install-Package ExceptionNotification.Core -Version 1.1.0 +PM> Install-Package ExceptionNotification.Core -Version 1.2.0 ``` ## Usage @@ -30,20 +30,26 @@ To setup the package you must add some credentials to your `appsettings.(emailConfiguration); + services.AddSingleton(configuration); } public void Configure(IApplicationBuilder app) { // ... - app.UseMiddleware(); + app.UseMiddleware(); } } ``` @@ -93,7 +99,7 @@ try } catch(Exception exception) { - EmailExceptionNotification.NotifyException(exception); + ExceptionNotifier.NotifyException(exception); } ``` @@ -102,7 +108,8 @@ catch(Exception exception) This package currently provides an e-mail notifier. It would be ideal to implement the following notifiers as well: * Slack -* Hipchat + +More testing is needed as well. ## Contributing