Tracing in ASP.NET MVC Razor Views

Tracing in ASP.NET MVC Razor Views

Rate This
  • Comments 10

System.Diagnostics.Trace can be a useful tool for logging and debugging, and sometimes it would be handy to be able to call one of the tracing methods from a Razor view.

For example, when an MVC application runs in production, by default MVC catches most application exceptions for you and routes them to Views/Shared/Error.cshtml. That view doesn’t show error details for security reasons, so you end up with the not-very-helpful message “An error occurred while processing your request.”

image

Running locally you get detailed error info, or you can catch exceptions by running in debug mode, but what to do about errors that happen in production? 

If you can re-create the error in production, you can temporarily tell the site to go ahead and display detailed error information by adding a customErrors element to your Web.config file in the system.web element:

Code Snippet
  1. <system.web>
  2.   <customErrors mode="Off" defaultRedirect="Error" />

Now instead of “An error occurred” you get an actual error message and a stack trace.

image

But another quick-and-dirty alternative that doesn’t require temporarily exposing error details to the public, is to add tracing statements to the view, for example:

Code Snippet
  1. @using System.Diagnostics
  2.  
  3. @model System.Web.Mvc.HandleErrorInfo
  4.  
  5. @{
  6.     ViewBag.Title = "Error";
  7.     var message = string.Format("Error in Controller {0}, Action method {1}. Exception: {2}",
  8.         Model.ControllerName, Model.ActionName, Model.Exception.Message);
  9.     if (Model.Exception.InnerException != null)
  10.     {
  11.         message += "; Inner exception: " + Model.Exception.InnerException.Message;
  12.     }
  13.     Trace.TraceError(message);    
  14. }
  15.  
  16. <hgroup class="title">
  17.     <h1 class="error">Error.</h1>
  18.     <h2 class="error">An error occurred while processing your request.</h2>
  19. </hgroup>

Unfortunately, when you do this you don’t get any trace output, and if you step through the code in the debugger you’ll see it step right over the TraceError method call without executing it.

Trace statements require the TRACE compiler constant, but it’s on by default and you can verify that in the Build tab of the project properties window:

image

The problem is that this setting in the .csproj file only applies to .cs files.  ASP.NET uses a different compile process for .cshtml files (or .aspx files in Web Forms), and the settings for that compile process are in the Web.config file. If you don’t explicitly specify the TRACE constant there, tracing method calls in .cshtml views are ignored.

You have at least two options for dealing with this situation if you want to add tracing in a view: define a TRACE constant for the .cshtml compiler, or use a static helper method.

Below is an example of what you have to add to the application Web.config file for a Visual Studio project that targets .NET 4.5, in order to define a TRACE constant for the .cshtml compiler:

Code Snippet
  1. <system.codedom>
  2.   <compilers>
  3.     <compiler
  4.       language="c#;cs;csharp"
  5.       extension=".cs"
  6.       type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
  7.       compilerOptions="/define:TRACE"
  8.       warningLevel="1" />
  9.   </compilers>
  10. </system.codedom>

With this in place the TraceError method call gets executed.

If you’re using a different Visual Studio or ASP.NET version, you can get the Version number you need by looking at your root Web.config file, copying out the codedom element from there, and adding the compilerOptions setting. The root Web.config file for .NET 4 or 4.5 is located in C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config:

Code Snippet
  1. <system.codedom>
  2.     <compilers>
  3.         <compiler language="c#;cs;csharp" extension=".cs" warningLevel="4" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  4.             <providerOption name="CompilerVersion" value="v4.0"/>
  5.             <providerOption name="WarnAsError" value="false"/>
  6.         </compiler>
  7.         <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" warningLevel="4" type="Microsoft.VisualBasic.VBCodeProvider, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  8.             <providerOption name="CompilerVersion" value="v4.0"/>
  9.             <providerOption name="OptionInfer" value="true"/>
  10.             <providerOption name="WarnAsError" value="false"/>
  11.         </compiler>
  12.     </compilers>
  13. </system.codedom>
  

If you’d rather use a static trace helper, create a class like the following example:

Code Snippet
  1. using System.Diagnostics;
  2.  
  3. namespace MyExample
  4. {
  5.     public static class TraceHelper
  6.     {
  7.         public static void MyTrace(TraceLevel level, string message)
  8.         {
  9.             switch (level)
  10.             {
  11.                 case TraceLevel.Error:
  12.                     Trace.TraceError(message);
  13.                     break;
  14.                 case TraceLevel.Warning:
  15.                     Trace.TraceWarning(message);
  16.                     break;
  17.                 case TraceLevel.Info:
  18.                     Trace.TraceInformation(message);
  19.                     break;
  20.                 case TraceLevel.Verbose:
  21.                     Trace.WriteLine(message);
  22.                     break;
  23.             }
  24.         }
  25.     }
  26. }

Now you can use virtually the same code in your view without having to add a codeDom element to your Web.config:

Code Snippet
  1. @using MyExample
  2. @using System.Diagnostics
  3.  
  4. @model System.Web.Mvc.HandleErrorInfo
  5.  
  6. @{
  7.     ViewBag.Title = "Error";
  8.     var message = string.Format("Error in Controller {0}, Action method {1}. Exception: {2}",
  9.         Model.ControllerName, Model.ActionName, Model.Exception.Message);
  10.     if (Model.Exception.InnerException != null)
  11.     {
  12.         message += "; Inner exception: " + Model.Exception.InnerException.Message;
  13.     }
  14.     TraceHelper.MyTrace(TraceLevel.Error, message);
  15. }
  16.  
  17. <hgroup class="title">
  18.     <h1 class="error">Error.</h1>
  19.     <h2 class="error">An error occurred while processing your request.</h2>
  20. </hgroup>

There are many ways to get the trace output, and I’ve added some links for info about that at the end of this post.

Adding tracing to views might be an effective way to debug a problem in production, and I’ve used Error.cshtml to show an example of how to do that. If your goal is a more permanent method of logging unhandled errors, a better method is to use an exception filter. To do that you create a class that implements IExceptionFilter, such as the following example:

Code Snippet
  1. using System.Web.Mvc;
  2.  
  3. namespace MyExample
  4. {
  5.     public class ErrorLoggerFilter : IExceptionFilter
  6.    {
  7.         public void OnException (ExceptionContext context)
  8.         {
  9.             var message = string.Format("Error processing URL: {0}. Exception: {1}",
  10.                 context.HttpContext.Request.Url,  context.Exception.Message);
  11.             if (context.Exception.InnerException != null)
  12.             {
  13.                 message += "; Inner exception: " + context.Exception.InnerException.Message;
  14.             }
  15.             context.HttpContext.Trace.Write(message);
  16.             System.Diagnostics.Trace.TraceError(message);    
  17.         }
  18.     }
  19. }

And then register the filter in App_Start/FilterConfig.cs:

Code Snippet
  1. using System.Web;
  2. using System.Web.Mvc;
  3.  
  4. namespace MyExample
  5. {
  6.     public class FilterConfig
  7.     {
  8.         public static void RegisterGlobalFilters(GlobalFilterCollection filters)
  9.         {
  10.             filters.Add(new HandleErrorAttribute());
  11.             filters.Add(new ErrorLoggerFilter());
  12.         }
  13.     }
  14. }

For another look at tracing best practices for web applications, see 47:00-55:36 in this video: Scott Guthrie: Building Real World Cloud Apps with Windows Azure - Part 2.

If you deploy your application to a Windows Azure Web Site, the latest SDK makes it exceptionally easy to get trace output immediately in the Visual Studio Output window while the site is running.  In two weeks the tutorial I’ve written about this will be published on WindowsAzure.com; until then, see the intro in the ScottGu blog post introducing Windows Azure SDK 2.0.

Some other links:

Thanks to Rick Anderson and Yishai Galatzer for help with the code for this blog post.

Leave a Comment
  • Please add 8 and 3 and type the answer here:
  • Post
  • Pretty useful stuff.

    I also like the new code snippet widgets in this blog post. Huge improvement over what used to be,

  • Tracing isn't an effective way of capturing errors in Prod, as having <Deployment Retail="True"> is recommended on Prod servers (msdn.microsoft.com/.../system.web.configuration.deploymentsection.retail.aspx), which disables Trace output.  We use other logging frameworks, such as Elmah or Log4Net.

  • @Tracing not recommended for Prod - Thanks for the comment. I agree ELMAH is a great tool, but it doesn't apply to all scenarios in which tracing is useful; I haven't used Log4Net. <Deployment Retail="True"> is described but isn't actually recommended for all production servers in the MSDN article you link to.  Hosting providers I have experience with haven't use it, and Windows Azure Web Sites don't use it. In fact, System.Diagnostics tracing is integrated into Windows Azure Web Sites and Visual Studio in a way that makes it very convenient to turn tracing output on and off and get streaming logs or log file downloads.

  • MSDN blogs doesn't seem to be that deep , can we get some blogs more advanced ?

  • Hi Tom, that's useful, but How I can Trace in ASPX Views.

  • @garfieldzf - The only difference for an .aspx view is in how you wrap the code in the view, using angle bracket percent. I just tried both methods in an .aspx view, and both work.

  • Usefull , thanks (y)

  • @Tom

    In production , i have to delete my extra code  or i can keep all of this without warring about performance?

    thanks

  • @Ben - Performance is mainly affected by how many logs you're actually writing, and trace listeners give you an option to log only Error-level, or Error and Warning, or Error, Warning, and Information, etc. logs. If you set the logging level to Error in production you probably aren't going to be writing a high volume of logs normally, then when you need to debug a problem, you can temporarily set the logging level to a more inclusive setting. The Trace statements themselves are not likely to have a noticeable impact on performance in most scenarios, but you can test that without changing your code by doing performance testing with and without the TRACE compiler constant defined. Without the compiler constant, the Trace statements aren't executed.

  • @Ben - A followup on tracing in production -- In the post I link to Scott Guthrie's recent NDC 2013 presentation on best practices for cloud applications. In that presentation he recommends keeping logging on in production at Warning level or Information level so you have historical data you can use for monitoring and analyzing issues, and switching to Verbose when you need more info for troubleshooting. His example takes advantage of cheap Windows Azure storage for storing large volumes of data, but the point applies to any web app -- the trivial performance hit of logging is more than offset by the benefits of having information about what's going on with the app in production.  Scott's demo app uses System.Diagnostics.Trace.

Page 1 of 1 (10 items)