This post is an introduction to the actual problem of error reporting, how to manage it in different stages of the development and production and a proposition of code to manage it.
Introduction
Report errors appear to be a trivial task, but if you do not carefully analyse your system, you can go with security problems or by not giving the user and the technical service the right information to report and solve it. In this article, we introduce the actual problem, how to manage it in different stages of the development and production and a proposition of code to manage it.
Background
In the early WEB API based in server code, the problem to send errors to the web was not so complicated. The server managed the request to the API and when an error happened, you could send the technical error information to the WEB Server with all the information to resolve the bug, and the Web Server only sent to the user in the browser a friendly information about the error. In this type of environment, you do not need to limit the quantity of information that your API sends to the WEB server, because the communication is only between the servers. Assuming the case that your services are in the intranet or you are using an encrypted protocol.
With the popularity of the client-based web as Angular and other frameworks, the call to the API services is done from the browser directly. That means that if you continue sending the error information in the same way that is used in a Server base WEB type ASP.NET or NET MVC, the technical detail of the error is open to be discovery for a potential hacker in clear text in the browser, no matter what protocol you are used to protect the information. This situation is a important hole of security in our system.
Proposed Solution: Link the Log of the Error in the Logger With the Message Sent to the Application
One possible solution is creating a field that is sent with the friendly message to the browser and storing the same value in the logger system as part of the logged information, In this way, you can find the technical information about the error in the logger. Link the error with a record in the logger can be resolved without security risk the problem to supply the technical service with the necessary information to correct a bug situation in production.
You can use a time stamp as field to link the error to use and the log in the logger system. Let's see this in an example (see image below).
Suppose that an exception is raised in the micro service “Users
”, the system creates a time stamp and sends the technical information to the logger system and also sends to the client browser, the same timestamp and a friendly informational message about the error that also includes the name of the failed service. The user calls the support center, and gives it the error with the time stamp that identified it, the support center sends an email to the developers in charge of the production errors. The developer goes to the “user“ micro service logger and looks for the entry in the given date time with all the necessary information to identify the problem.
Recoverable and Catastrophic Errors
Other problem to be resolved is what type of HTTP error we will send to the API client. When the error is catastrophic, the logical selection is the codes 500 meaning that something that was not recuperable happened in the call to the API. In this case, it is easy to select send to the browser a friendly error linked with a technical error logged.
If the error is recoverable by some action that the user can do, it is using error type 400 that can cover error for data validation, absence of data or other similar that. In general, error 400 should be an error that does not stop the user from continuing or correcting the response of the service. The error message sent to the user in this case should be able to explain to the end user what happens and what action can by done to avoid it. Let's see both errors in production and developer conditions.
Managing Catastrophic Errors in Production
An unrecovered error in production should only report that a catastrophic error happened, for two reasons: one a security concern and second the user cannot do anything to resolve the error. That we can do is send a Friendly error to the user with a time stamp, and the technical problem to the logger system with the same timestamp. The Friendly error sent to the user should be related to the logger entry through the time stamp, making easy the localization on the logger, the related technical entry of the error.
Managing Catastrophic Errors in Development
In the case of development, we can simply send the technical error to the browser because only the development and possibly the QA team will be able to see the information. Also, we need to send also the error to the logger system and also maintain the relation between the error sent to the browser with the entry in the log.
Managing Recoverable Errors in Production
In the case of recoverable errors, we can send the information to the user that allows him/her to recovery from the error. For example, in the case of validation error: change a bad name or fill a missing field.
The decision to log this error in production can be optional, you can opt for logging that type the error, in this case, the recommendation is to move the level of this type the error to WARN
or INFO
to allow filter by ERROR
only error of catastrophic information.
Managing Recoverable Errors in Development
In the case of development environment and validation errors, we proceed similar to production to send to the client all the possible information to the client. But we recommended to log this information in the logger system. This is important in the development process to resolve problems, because bad request to the API.
Using the Code
In this article, we propose an object that allows us to manage all the different types of error discussed here. To do that, we using the following classes:
public class ErrorManager : IErrorManager
{
public DateTime DateCreated { get; set; }
public List<ErrorResponse> Errors { get; set; }
public string Description { get; set; }
public Exception DebugException { get; set; }
public static IErrorManager Factory()
{
IErrorManager manager = new ErrorManager();
manager.Errors = new List<ErrorResponse>();
return manager;
}
}
And to hold the list of errors response, the following class:
public class ErrorResponse
{
public string Code { get; set; }
public string Description { get; set; }
}
As you see in the comment of each property in this object, you can put all the information to use in error type 400 or 500 in your web. In production or in development stage. The only thing that you need to take into account is use the properties according to the error experimented.
This class should work with a logger system to allow the connection of the logged error with the information sent to the user.
Let's see now how to use this class to manage general errors in a .NET Core 3.1 application. The next code snippet explains how to configure the class to be used in the general error handle of the application in the startup.cs file.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
DateTime errorReportedtime = DateTime.UtcNow;
ErrorManager errorManager = new ErrorManager()
{
DateCreated = errorReportedtime,
};
if (!env.IsProduction())
{
errorManager.DebugException = errorFeature.Error;
errorManager.Description = errorFeature.Error.Message;
}
else
{
errorManager.Description = $" {errorManager.DateCreated}:
Please call our service that a error happen in NetCoreDemo";
}
var content = JsonConvert.SerializeObject(errorManager);
context.Response.StatusCode = 500;
await context.Response.WriteAsync(content);
});
});
app.UseHttpsRedirection();
app.UseRouting();
}
Observe that the code manages the production different to other environment, in the case of production, only a generic error with the TimeStamp
is sent to the browser. In the case of other environment, an extensive error report is sent to the user.
In both cases, the extensive technological information should be stored in the Logger
, and the logger should be linked using the time stamp with the message sent to the browser client.
The rest of the code here is to take the object and return it using the context response object.
In the case of error 400, you can use the validation in the request pipeline, and send the same object, different configured to the client browser. The code to do this is below:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
IErrorManager errorManager = ErrorManager.Factory();
StringBuilder messageb = new StringBuilder();
foreach (var item in context.ModelState)
{
messageb.Append($"Validation Failure in
{context.ActionDescriptor.DisplayName}
Parameter: {item.Key} Error: ");
foreach (var i in item.Value.Errors)
{
messageb.Append($" {i.ErrorMessage} - ");
}
errorManager.Errors.Add(new ErrorResponse()
{
Code = "400",
Description = messageb.ToString()
});
}
DateTime errorTime = DateTime.UtcNow;
errorManager.DateCreated = errorTime;
errorManager.Description =
$"{errorTime}: Validation Error in NetCoreDemo";
var error = new BadRequestObjectResult(errorManager);
return error;
};
});
}
In this code, we use the array of Errors
for the property Errors
to list of validation errors present in the validation model State. Also, we give here a description of the error and a link to the same error to be sent to the Logger System. Observe that here, we send the same information for production and development environment.
The complete code for this example can be download from here.
Points of Interest
- You should not send in production, technical error to the browser based web style Angular, instead of that, you should store the technical data of the error in the logger system and only send a friendly error to the user.
- You should send with the friendly message the possibility to link the friendly message with the stored information in the logger system.
- You should use always that is possible the same object to report all type of errors. You should customize the object for the case of production and development environment.
- You can see also this information in Video format at: https://youtu.be/Sq39bscnNtU
History
- 13th June, 2021: First version
- Adding Video companion