I have an Asp.net MVC project which is using Elmah to log the exceptions.
The project is using Entity Framework which can throw a DbEntityValidationException exception where is specifies which properties couldn't be saved into Database.
I am trying to capture that exception (as is not logged in details by Elmah), and append the info to the current exception.
The problem is that, if i modify the current exception, the changes are not captured by the Elmah. Instead, i have to raise another exception with the details about DbEntityValidationException, but this will create two consecutive errors logs.
Is possible to append some extra text to the exception before elmah logs it?
public class ErrorHandleAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled == true)
return;
Exception exception = filterContext.Exception;
Exception dbEntityException = exception;
while (dbEntityException != null && dbEntityException is System.Data.Entity.Validation.DbEntityValidationException == false)
{
dbEntityException = dbEntityException.InnerException;
}
if (dbEntityException != null && dbEntityException is System.Data.Entity.Validation.DbEntityValidationException)
{
DbEntityValidationException dbEntityValidationException = dbEntityException as DbEntityValidationException;
StringBuilder sb = new StringBuilder();
sb.AppendLine("Db Entity Validation Exception");
foreach (var item in dbEntityValidationException.EntityValidationErrors)
{
sb.AppendFormat("Entity: {0} {1}", item.Entry.Entity.ToString(), Environment.NewLine);
foreach (var error in item.ValidationErrors)
{
sb.AppendFormat("{0}: {1} {2}", error.PropertyName, error.ErrorMessage, Environment.NewLine);
}
}
exception = new Exception(sb.ToString(), exception);
ErrorSignal.FromCurrentContext().Raise(exception);
}
}
}
Related
Occasionally, a bug will trigger one of those unrecoverable exceptions (e. g. a StackOverflowException) that causes our entire ASP.NET MVC application to crash. When this happens, the Windows event log typically contains some information about the error, although it is minimal. Currently, for example, we have what we believe is a StackOverflowException being thrown inside a reflection invocation. In such cases, the event log contains some basic information about the outer TargetInvocationException and nothing that will let us pinpoint the problem. My question is, is it possible to configure something in .NET/IIS/Windows to log more information when this happens? If we could get, for example, the first N characters of the full exception (including stack trace and inner exceptions), we could easily debug and fix the issue.
You can't control what another class writes to the log, but you can capture exceptions in the global.asax file and do your own logging. I tend to use a combination of database logging and email notifications.
--global.asax
void Application_Error(object sender, EventArgs e)
{
// Code that runs when an unhandled error occurs
Exception exSvr = Server.GetLastError();
if (exSvr == null)
return;
if (exSvr.Message == "The client disconnected.")
{
// I don't want to be notified everytime someone closes
// the browser mid-request.
return;
}
if (exSvr is System.Threading.ThreadAbortException || exSvr.InnerException is System.Threading.ThreadAbortException || exSvr.Message.StartsWith("Thread was being aborted") || (exSvr.InnerException != null && exSvr.InnerException.Message.StartsWith("Thread was being aborted")))
{
// So we don't get notified everytime .NET kills a thread.
// NOTE: This error is how .NET kills a thread, and will happen every time we call Response.Redirect("", true)
return;
}
ErrorHandler.SendServerErrMsg("Application Error", exSvr, HttpContext.Current);
}
--ErrorHandler
public static void SendServerErrMsg(string codeSection, Exception exSvr, HttpContext context, SeverityLevel lvl = SeverityLevel.Warning)
{
if (_config == null || !_config.EnableEmailAlerts)
return;
if (context != null && context.Request != null && context.Request.Url != null && context.Request.Url.IsLoopback && !_config.SendAlertsInDebugMode)
return;
if (context != null && context.IsDebuggingEnabled && !_config.SendAlertsInDebugMode)
return;
// Attempt to get the currently logged in user.
System.Web.Security.MembershipUser curUsr = null;
try
{ curUsr = System.Web.Security.Membership.GetUser(); }
catch { } // If it fails, don't worry about it.
// Set the message's subject line.
string subject = _config.ApplicationName + " Exception - " + HttpUtility.HtmlEncode(codeSection);
// Build the page body in a StringBuilder object.
System.Text.StringBuilder sb = new System.Text.StringBuilder();
if (context != null && context.Request != null)
sb.AppendLine(string.Format("<h2>{0}</h2>", HttpUtility.HtmlEncode(context.Request.ApplicationPath)));
sb.AppendLine("<p>The following exception has occurred:</p>");
sb.AppendLine("<ul>");
Exception innerEx = exSvr;
while (innerEx != null)
{
sb.AppendLine(string.Format("<li>{0}</li>", HttpUtility.HtmlEncode(innerEx.Message)));
innerEx = innerEx.InnerException;
}
sb.AppendLine("</ul>");
sb.AppendLine("<p/>");
if (exSvr.TargetSite != null)
sb.AppendLine(string.Format("<p>Target Site: {0} {1} (in {2})</p>", exSvr.TargetSite.MemberType, exSvr.TargetSite.Name, exSvr.TargetSite.DeclaringType));
else
sb.AppendLine("<p>Target Site: N/A</p>");
sb.AppendLine("<p/>");
sb.AppendLine("<hr />");
if (context != null && context.Request != null)
{
HttpRequest req = context.Request;
sb.AppendLine("<p>");
sb.AppendLine(string.Format("<div>Request URL: {0}</div>", HttpUtility.HtmlEncode(req.Url.ToString())));
sb.AppendLine(string.Format("<div>Request Path: {0}</div>", HttpUtility.HtmlEncode(req.FilePath)));
sb.AppendLine(string.Format("<div>User: {0}</div>", (curUsr != null) ? HttpUtility.HtmlEncode(curUsr.UserName) : "Unknown"));
sb.AppendLine(string.Format("<div>User Host Address: {0}</div>", HttpUtility.HtmlEncode(req.UserHostAddress)));
// We're going to try a reverse DNS search on the IP address. Failover,
// just uses the UserHostName value from the HttpContext object.
string hostName = null;
try { hostName = System.Net.Dns.GetHostEntry(req.UserHostAddress).HostName; }
catch { hostName = null; }
if (hostName == null) hostName = req.UserHostName;
sb.AppendLine(string.Format("<div>User Host Name: {0}</div>", HttpUtility.HtmlEncode(hostName)));
sb.AppendLine(string.Format("<div>User Agent: {0}</div>", HttpUtility.HtmlEncode(req.UserAgent)));
sb.AppendLine(string.Format("<div>Server Name: {0}</div>", (context.Server != null ? HttpUtility.HtmlEncode(context.Server.MachineName) : "Unknown")));
sb.AppendLine(string.Format("<div>Request Identity: {0}</div>", (req.LogonUserIdentity != null ? HttpUtility.HtmlEncode(req.LogonUserIdentity.Name) : "Unknown")));
sb.AppendLine("</p>");
sb.AppendLine("<hr />");
}
sb.AppendLine("<p>Stack trace follows:</p>");
Exception ex = exSvr;
while (ex != null)
{
sb.AppendLine("<p>");
sb.AppendLine(string.Format("<b>>> {0}:</b> {1} in {2}<br/>", ex.GetType().Name, HttpUtility.HtmlEncode(ex.Message), HttpUtility.HtmlEncode(ex.Source)));
sb.AppendLine(HttpUtility.HtmlEncode(ex.StackTrace));
sb.AppendLine("</p>");
ex = ex.InnerException;
}
if (context!=null && context.Trace.IsEnabled)
{
sb.AppendLine("<hr />");
sb.AppendLine("<p>Web Trace:</p>");
sb.AppendLine(string.Format("<p>{0}</p>", HttpUtility.HtmlEncode(context.Trace.ToString())));
}
EmailHelper.SendEmail(GetAlertContacts(), subject.Trim(), sb.ToString().Trim(), true);
}
One of my tables have a unique key and when I try to insert a duplicate record it throws an exception as expected. But I need to distinguish unique key exceptions from others, so that I can customize the error message for unique key constraint violations.
All the solutions I've found online suggests to cast ex.InnerException to System.Data.SqlClient.SqlException and check the if Number property is equal to 2601 or 2627 as follows:
try
{
_context.SaveChanges();
}
catch (Exception ex)
{
var sqlException = ex.InnerException as System.Data.SqlClient.SqlException;
if (sqlException.Number == 2601 || sqlException.Number == 2627)
{
ErrorMessage = "Cannot insert duplicate values.";
}
else
{
ErrorMessage = "Error while saving data.";
}
}
But the problem is, casting ex.InnerException to System.Data.SqlClient.SqlException causes invalid cast error since ex.InnerException is actually type of System.Data.Entity.Core.UpdateException, not System.Data.SqlClient.SqlException.
What is the problem with the code above? How can I catch Unique Key Constraint violations?
With EF6 and the DbContext API (for SQL Server), I'm currently using this piece of code:
try
{
// Some DB access
}
catch (Exception ex)
{
HandleException(ex);
}
public virtual void HandleException(Exception exception)
{
if (exception is DbUpdateConcurrencyException concurrencyEx)
{
// A custom exception of yours for concurrency issues
throw new ConcurrencyException();
}
else if (exception is DbUpdateException dbUpdateEx)
{
if (dbUpdateEx.InnerException != null
&& dbUpdateEx.InnerException.InnerException != null)
{
if (dbUpdateEx.InnerException.InnerException is SqlException sqlException)
{
switch (sqlException.Number)
{
case 2627: // Unique constraint error
case 547: // Constraint check violation
case 2601: // Duplicated key row error
// Constraint violation exception
// A custom exception of yours for concurrency issues
throw new ConcurrencyException();
default:
// A custom exception of yours for other DB issues
throw new DatabaseAccessException(
dbUpdateEx.Message, dbUpdateEx.InnerException);
}
}
throw new DatabaseAccessException(dbUpdateEx.Message, dbUpdateEx.InnerException);
}
}
// If we're here then no exception has been thrown
// So add another piece of code below for other exceptions not yet handled...
}
As you mentioned UpdateException, I'm assuming you're using the ObjectContext API, but it should be similar.
In my case, I'm using EF 6 and decorated one of the properties in my model with:
[Index(IsUnique = true)]
To catch the violation I do the following, using C# 7, this becomes much easier:
protected async Task<IActionResult> PostItem(Item item)
{
_DbContext.Items.Add(item);
try
{
await _DbContext.SaveChangesAsync();
}
catch (DbUpdateException e)
when (e.InnerException?.InnerException is SqlException sqlEx &&
(sqlEx.Number == 2601 || sqlEx.Number == 2627))
{
return StatusCode(StatusCodes.Status409Conflict);
}
return Ok();
}
Note, that this will only catch unique index constraint violation.
try
{
// do your insert
}
catch(Exception ex)
{
if (ex.GetBaseException().GetType() == typeof(SqlException))
{
Int32 ErrorCode = ((SqlException)ex.InnerException).Number;
switch(ErrorCode)
{
case 2627: // Unique constraint error
break;
case 547: // Constraint check violation
break;
case 2601: // Duplicated key row error
break;
default:
break;
}
}
else
{
// handle normal exception
}
}
// put this block in your loop
try
{
// do your insert
}
catch(SqlException ex)
{
// the exception alone won't tell you why it failed...
if(ex.Number == 2627) // <-- but this will
{
//Violation of primary key. Handle Exception
}
}
EDIT:
You could also just inspect the message component of the exception. Something like this:
if (ex.Message.Contains("UniqueConstraint")) // do stuff
I thought it might be useful to show some code not only handling the duplicate row exception but also extracting some useful information that could be used for programmatic purposes. E.g. composing a custom message.
This Exception subclass uses regex to extract the db table name, index name, and key values.
public class DuplicateKeyRowException : Exception
{
public string TableName { get; }
public string IndexName { get; }
public string KeyValues { get; }
public DuplicateKeyRowException(SqlException e) : base(e.Message, e)
{
if (e.Number != 2601)
throw new ArgumentException("SqlException is not a duplicate key row exception", e);
var regex = #"\ACannot insert duplicate key row in object \'(?<TableName>.+?)\' with unique index \'(?<IndexName>.+?)\'\. The duplicate key value is \((?<KeyValues>.+?)\)";
var match = new System.Text.RegularExpressions.Regex(regex, System.Text.RegularExpressions.RegexOptions.Compiled).Match(e.Message);
Data["TableName"] = TableName = match?.Groups["TableName"].Value;
Data["IndexName"] = IndexName = match?.Groups["IndexName"].Value;
Data["KeyValues"] = KeyValues = match?.Groups["KeyValues"].Value;
}
}
The DuplicateKeyRowException class is easy enough to use... just create some error handling code like in previous answers...
public void SomeDbWork() {
// ... code to create/edit/update/delete entities goes here ...
try { Context.SaveChanges(); }
catch (DbUpdateException e) { throw HandleDbUpdateException(e); }
}
public Exception HandleDbUpdateException(DbUpdateException e)
{
// handle specific inner exceptions...
if (e.InnerException is System.Data.SqlClient.SqlException ie)
return HandleSqlException(ie);
return e; // or, return the generic error
}
public Exception HandleSqlException(System.Data.SqlClient.SqlException e)
{
// handle specific error codes...
if (e.Number == 2601) return new DuplicateKeyRowException(e);
return e; // or, return the generic error
}
If you want to catch unique constraint
try {
// code here
}
catch(Exception ex) {
//check for Exception type as sql Exception
if(ex.GetBaseException().GetType() == typeof(SqlException)) {
//Violation of primary key/Unique constraint can be handled here. Also you may //check if Exception Message contains the constraint Name
}
}
You have to be very specific while writing the code.
try
{
// do your stuff here.
{
catch (Exception ex)
{
if (ex.Message.Contains("UNIQUE KEY"))
{
Master.ShowMessage("Cannot insert duplicate Name.", MasterSite.MessageType.Error);
}
else { Master.ShowMessage(ex.Message, MasterSite.MessageType.Error); }
}
I have just updated the above code a bit and its working for me.
I'm developing with C#, ASP.NET MVC Web Api, Entity Framework and .NET Framework 4.0.
I have this code to log an exception:
public void LogCompleteException(
string controllerName,
string actionName,
Exception exception)
{
string exceptionMessage = string.Empty;
Exception e = exception;
if (e.InnerException == null)
e = null;
else
while (e.InnerException != null) e = e.InnerException;
if (e == null)
exceptionMessage = exception.Message;
else
exceptionMessage = string.Format("{0}\n\rInnerException: {1}", exception.Message, e.Message);
_logger.ErrorFormat(
LogTextFormatString,
ExceptionText,
controllerName,
actionName,
exceptionMessage);
}
But on my log file I have found this:
Validation failed for one or more entities. See
'EntityValidationErrors' property for more details.
When I wrote 'See 'EntityValidationErrors' property for more details.', I'm only showing an example where I haven't log an important property.
There are a lot of kind of exceptions; but with my method I'm not logging all the relevant information because there could be properties like 'EntityValidationErrors' that I don't log.
When I pass the exception to log it I don't know what properties it has, and I don't know how to log each property it has.
Do you know a method to log an exception completely? My code doesn't long exceptions with an EntityValidationErrors property or any other important property.
I'm doing the logging with log4net.
Since the inner exception is an exception itself, perhaps you can just recurse and reuse the method you already have:
if (e.InnerException != null)
{
LogCompleteException(controllerName, actionName, e.InnerException);
}
If this does work, however, you will still be missing the EntityValidationErrors.
Another method, which I used recently is to just explicitly trap and log the exception where it occurs:
try
{
db.Add(something);
db.SaveChanges();
}
catch (DbEntityValidationException ex)
{
StringBuilder sb = new StringBuilder();
// collect some extra info about the exception before logging
foreach (var eve in ex.EntityValidationErrors)
{
sb.AppendLine(String.Format("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:", eve.Entry.Entity.GetType().Name, eve.Entry.State));
foreach (var ve in eve.ValidationErrors)
{
sb.AppendLine(String.Format("Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage));
}
}
logger.Error("There was an error while trying to parse and save something!\n" + sb.ToString(), ex);
}
If you want the Exception.Data dictionary entries, you can add those as well:
foreach (DictionaryEntry entry in ex.Data)
{
// you will want to use a StringBuilder instead of concatenating strings if you do this
exceptionMessage = exceptionMessage + string.Format("Exception.Data[{0}]: {1}", entry.Key, entry.Value);
}
As for properties for Custom exception classes just as the EntityValidationErrors, I would just trap and parse those where they occur. Otherwise you would have to override ToString() on every exception type or use some reflection hackery to enumerate all the properties which would significantly clutter the logs with properties you dont care about.
you could use elmah and the log4net appender. Elmah logs catches all exceptions and can log them to a log4net instance.
I have this function which I use for my validation
public void Validate()
{
Action<List<Field>> validateFields = (field) =>
{
if (field != null && field.Any())
{
field.ForEach(x => x.Validate());
}
};
new List<List<Field>> { this.PersonElements, this.ContactElements, this.MiscElements }.ForEach(x =>
{
try
{
validateFields(x);
}
catch (Exception e)
{
Log.ErrorFormat("An exception has occurred while validation of {1} : {0}", e, x.ToString()); // print something as x.Name.ToString());
throw;
}
});
}
The problem is with this line:
new List<List<Field>> { this.PersonElements, this.ContactElements, this.MiscElements }.ForEach(x =>
I need to (in case an exception has occurred) log something as
An exception has occurred while trying to process "PersonElements / ContactElements / MiscElements" depending upon during which validation an exception has occurred.
How to get the list variable name in case of an exception occurring during its processing ?
Thanks in advance.
Variable names are only meaningful in their context. The same object could be accessed with different variables. So it is reasonable to provide the name of the collection in a type like a Tuple<String, List<Field>>.
new List<Tuple<List<Field>>> { Tuple.Create("PersonElements", this.PersonElements), ... }.ForEach (
...
validateFields(x.Item2);
...
Log.ErrorFormat("An exception has occurred while validation of {1} : {0}", e, x.Item1);
I am writing a class that does operations to multiple streams. Here is a example of what I am doing now
Dictionary<int, int> dict = new Dictionary<int, int>(_Streams.Count);
for (int i = 0; i < _Streams.Count; i++)
{
try
{
dict.Add(i, _Streams[i].Read(buffer, offset, count));
}
catch (System.IO.IOException e)
{
throw new System.IO.IOException(String.Format("I/O exception occurred in stream {0}", i), e);
}
catch (System.NotSupportedException e)
{
throw new System.NotSupportedException(String.Format("The reading of the stream {0} is not supported", i), e);
}
catch (System.ObjectDisposedException e)
{
throw new System.ObjectDisposedException(String.Format("Stream {0} is Disposed", i), e);
}
}
int? last = null;
foreach (var i in dict)
{
if (last == null)
last = i.Value;
if (last != i.Value)
throw new ReadStreamsDiffrentExecption(dict);
last = i.Value;
}
return (int)last;
I would like to simplify my code down to
Dictionary<int, int> dict = new Dictionary<int, int>(_Streams.Count);
for (int i = 0; i < _Streams.Count; i++)
{
try
{
dict.Add(i, _Streams[i].Read(buffer, offset, count));
}
catch (Exception e)
{
throw new Exception(String.Format("Exception occurred in stream {0}", i), e);
}
}
int? last = null;
foreach (var i in dict)
{
if (last == null)
last = i.Value;
if (last != i.Value)
throw new ReadStreamsDiffrentExecption(dict);
last = i.Value;
}
return (int)last;
However if anyone is trying to catch specific exceptions my wrapper will hide the exception that Read threw. How can I preserve the type of exception, add my extra info, but not need to write a handler for every possible contingency in the try block.
I would suggest not catching those exceptions at all...
The information you add could (mostly) be gleaned from the stackdump.
You could use catch-and-wrap to translate to a library-specific exception:
catch (Exception e)
{
throw new ReadStreamsErrorExecption(
String.Format("Exception occurred in stream {0}", i), e);
}
I think you have a bit of an issue here in the way you are working with your exception.
You should not be throwing the base Exception class, but something more specific so they can handle it.
Is the id value something that is really "valuable" from a diagnostic function?
I would review what you are doing, and see if you really need to be wrapping the exception.
I find the first version is better for readability and is the more expressive to my eye. This is how exception handling should be written.
Generally the rule that I've picked up from Eric Lipperts blogs, is that you should only capture an exception if you're going to do something about it.
Here you are just re-throwing the exception with a new message. Just let the client handle the exceptions themselves unless you're going to try and recover from errors. In which case add a
throw;
If you need to bubble the exception backup because you can't handle it.
One little known .NET trick is that you CAN add information to an Exception without wrapping it. Every exception has a .Data dictionary on it that you can stuff with additional information, e.g.
try
{
...
}
catch (FileNotFoundException ex)
{
ex.Data.Add("filename", filename);
throw;
}
Now in your top-level exception handling code you can dump the exception and its associated dictionary out to your log file or into your Exceptions database and thus get far more information than you had before.
In an ASP.NET application you might want to add the URL, the username, the referrer, the contents of the cookies, ... to the .Data dictionary before letting your application error handler take it.