Event and error logging in Asp.net MVC 5 project

11,436

Solution 1

There isn't an industry standard. I've used filters or I've overridden the "onActionExecuting" method on the base controller class to record controller / action events.

EDIT ::

Trying to be more helpful but this is really vague. If you're worried about errors and things like that use elmah. For other logging use Nlog or Log4Net.

If you're trying to do extra logging like auditing or something like that you can use any combination of those, or something custom. In my site I created a table that stores every click on the site by creating an object sort of like this :

    public class audit
{
    public int ID { get; set; }
    public DateTime AuditDate { get; set; }
    public string ControllerName { get; set; }
    public string ActionName { get; set; }
    public Dictionary<string, object> values
}

In my base constructor, I overrode the OnActionExecuting event :

    protected override void OnActionExecuting(ActionExecutingContext ctx)
    {
        checkForLogging(ctx);

        //do not omit this line
        base.OnActionExecuting(ctx);
    }

Lets say I want to log all Get Requests using my new audit object

  private void checkForLogging(ActionExecutingContext ctx)
    {
        //we leave logging of posts up to the actual methods because they're more complex...
        if (ctx.HttpContext.Request.RequestType == "GET")
        {
                logging(ctx.ActionDescriptor.ActionName, ctx.ActionDescriptor.ControllerDescriptor.ControllerName, ctx.ActionParameters);                
        }
    }

Thats all the info I need to fill my logging object with the action name, the controller name and all the params passed into the method. You can either save this to a db, or a logfile or whatever you want really.

The point is just its a pretty big thing. This is just one way to do it and it may or may not help you. Maybe define a bit more what exactly you want to log and when you want to do it?

You can create a custom attribute and decorate methods with it and then check if that attribute is present when the OnActionExecuting method fires. You can then get that filter if present and read from it and use that to drive your logging if you want...

Solution 2

Maybe this example will help. My focus on logging is in the CREATE, EDIT, DELETE actions.

I am using MVC 5 Code-first EF 6.1 (VS 2013) , and for this example I are referring to the Create action for an entity called "WorkFlow"

I actually view these logs from SSRS, but you could add a controller and Views for WriteUsageLog and view them from the MVC application

  1. MODEL: Create a MODEL Entity called "WriteUsageLog" which will be where the log records are kept
  2. CONTROLLER: Extract, or refactor, the HttpPost overload of the "Create" action from the WorkFlowController into a Partial Class called "WorkFlowController" (These partials are to avoid being deleted and rebuilt when I use the wizard to create Controllers)
  3. Other Classes in the CONTROLLER folder: Then there are some helper functions that are required in a class called "General_Object_Extensions" and "General_ActiveDirectory_Extensions" (NOTE: these are not really 'extensions')
  4. Add the following line to the DBContext:

    public DbSet WriteUsageLogs { get; set; }

    The advantage of this example is:

  5. I am recording the following for the record:

    • User Name from Active Directory
    • The DateTime that the log record is being created
    • The computer name
    • And a Note that consists of the values for all the entity properties
  6. I am recording the log in a table from which I can access it either using an MVC controller, or preferably from SQL Server Report Server. Where I can monitor all my MVC applications

/Models/WriteUsageLog.cs

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MileageReimbursement.Models
{
    public class WriteUsageLog
    {
        public WriteUsageLog()
        {
            this.DateTimeCreated = DateTime.Now; // auto-populates DateTimeCreated field
        }

        [Key]
        public int WriteUsageLogID { get; set; }

        [Column(TypeName = "nvarchar(max)")]
        public string Note { get; set; }

        public string UserLogIn { get; set; }
        public string ComputerName { get; set; }

        public DateTime DateTimeCreated { get; private set; }  //private set to for auto-populates DateTimeCreated field

    }
}

/Controllers/ControllerPartials.cs

using System.Linq;
using System.Web.Mvc;
using MileageReimbursement.Models;

//These partials are to avoid be deleted and rebuilt when I use the wizard to create Controllers

namespace MileageReimbursement.Controllers
{
    public partial class WorkFlowController : Controller
    {

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "whatever")] WorkFlow workFlow)
        {
            ...

            if (ModelState.IsValid) 
            {
                db.WorkFlows.Add(workFlow);
                db.SaveChanges();
                //===================================================================================================================
                string sX = workFlow.GetStringWith_RecordProperties();
                //===================================================================================================================
                var logRecord = new WriteUsageLog();
                logRecord.Note = "New WorkFlow Record Added - " + sX; 

                logRecord.UserLogIn = General_ActiveDirectory_Extensions.fn_sUser();
                string IP = Request.UserHostName;
                logRecord.ComputerName = General_functions.fn_ComputerName(IP);


                db.WriteUsageLogs.Add(logRecord);
                db.SaveChanges();
                //===================================================================================================================

                return RedirectToAction("Index");
            }
            else   // OR the user is directed back to the validation error messages and given an opportunity to correct them
            {
               ...
                return View(workFlow);  //This sends the user back to the CREATE view to deal with their errors
            }
        }
    }
}

/Controllers/ControllerExtensions.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Web;


namespace MileageReimbursement.Controllers
    {

    public static class General_ActiveDirectory_Extensions
        {

        public static string fn_sUser()
            {
            char cX = '\\';
            string sUser = General_functions.fn_ReturnPortionOfStringAfterLastOccuranceOfACharacter(HttpContext.Current.User.Identity.Name, cX);
            return sUser;   //returns just the short logon name Example for 'accessiicarewnc\ggarson', it returns 'ggarson'   
            }


        }   //General_ActiveDirectory_Extensions

    public static class General_Object_Extensions
        {

        public static string GetStringWith_RecordProperties(this object Record)
            {

            string sX = null;
            Dictionary<string, object> _record = GetDictionary_WithPropertiesForOneRecord(Record);
            int iPropertyCounter = 0;

            foreach (var KeyValuePair in _record)
                {

                iPropertyCounter += 1;
                object thePropertyValue = _record[KeyValuePair.Key];
                if (thePropertyValue != null)
                    {
                    sX = sX + iPropertyCounter + ") Property: {" + KeyValuePair.Key + "} = [" + thePropertyValue + "] \r\n";
                    }
                else
                    {
                    sX = sX + iPropertyCounter + ") Property: {" + KeyValuePair.Key + "} = [{NULL}] \r\n";
                    }

                }

            return sX;
            }

        public static Dictionary<string, object> GetDictionary_WithPropertiesForOneRecord(object atype)
            {
            if (atype == null) return new Dictionary<string, object>();
            Type t = atype.GetType();
            PropertyInfo[] props = t.GetProperties();
            Dictionary<string, object> dict = new Dictionary<string, object>();
            foreach (PropertyInfo prp in props)
                {
                object value = prp.GetValue(atype, new object[] { });
                dict.Add(prp.Name, value);
                }
            return dict;
            }

        }   //General_Object_Extensions

    public static class General_functions
        {
        public static string fn_ComputerName(string IP)
            {
            //USAGE
            //From: http://stackoverflow.com/questions/1444592/determine-clients-computer-name
            //string IP = Request.UserHostName;
            //string compName = CompNameHelper.DetermineCompName(IP);


            IPAddress myIP = IPAddress.Parse(IP);
            IPHostEntry GetIPHost = Dns.GetHostEntry(myIP);
            List<string> compName = GetIPHost.HostName.ToString().Split('.').ToList();
            return compName.First();
            }

        static public string fn_ReturnPortionOfStringAfterLastOccuranceOfACharacter(string strInput, char cBreakCharacter)
            {
            // NOTE: for path backslash "/", set cBreakCharacter = '\\'
            string strX = null;

            //1] how long is the string
            int iStrLenth = strInput.Length;

            //2] How far from the end does the last occurance of the character occur

            int iLenthFromTheLeftOfTheLastOccurance = strInput.LastIndexOf(cBreakCharacter);

            int iLenthFromTheRightToUse = 0;
            iLenthFromTheRightToUse = iStrLenth - iLenthFromTheLeftOfTheLastOccurance;

            //3] Get the Portion of the string, that occurs after the last occurance of the character
            strX = fn_ReturnLastXLettersOfString(iLenthFromTheRightToUse, strInput);

            return strX;

            }


        static private string fn_ReturnLastXLettersOfString(int iNoOfCharToReturn, string strIn)
            {
            int iLenth = 0;
            string strX = null;
            int iNoOfCharacters = iNoOfCharToReturn;

            iLenth = strIn.Length;
            if (iLenth >= iNoOfCharacters)
                {
                strX = strIn.Substring(iLenth - iNoOfCharacters + 1);

                }
            else
                {
                strX = strIn;
                }


            return strX;
            }


        }   //General_functions
    }

Solution 3

I would agree that Log4Net and NLog seem to be the two most commonly used products on the different projects I have been a member.

If you are looking for a great tool that you can use for logging, error handling and anything else where AOP would be beneficial I would highly recommend PostSharp (http://www.postsharp.net/). You set your logging/error handling up centrally and then just decorate methods. It is a well documented and supported product. They have a community license, which is free - and it is free for individuals. They also have professional and ultimate versions of the products, which would make more sense if you're using it as a team.

I don't work at PostSharp :-) I've just used it in the past and really like it.

Share:
11,436
Zapnologica
Author by

Zapnologica

I am an enthusiastic developer indulging in the world of programming. in love with C# .net

Updated on June 04, 2022

Comments

  • Zapnologica
    Zapnologica almost 2 years

    I am looking at implementing a logging mechanism in a site of mine, I wish to do basic user action logging. I don't want to log every single button they click on, but I do want to log actions they do which makes changes.

    Are there any libraries or articles / tutorials out there which can help me implement a good and efficient logging mechanism for my asp.net site. Im not sure if there are any changes in MVC5 that might come in use for logging as I know user Authentication and Authorization has changed a fair amount from 4 to 5.

    I'm sure that there is a dynamic library out there that will work in many different situations.

    Nice to haves:

    • Async capability
    • Scalable
    • Simple to use

    I'm thinking along the lines of creating a custom filter or attribute that then logs the suers action, but that's just my Idea, Im here to ask what the standard / industry way to do it is.

  • Zapnologica
    Zapnologica about 10 years
    Im gonna need a bit more info than that to select as my answer,
  • spaceman
    spaceman about 10 years
    I tried to expand a bit, but you need to be a bit clearer with what you want to do...
  • SAR
    SAR over 7 years
    logging(ctx.ActionDescriptor.Ac,,,, name logging doest not exist, EROR
  • SAR
    SAR over 7 years
    in create it's working fine, is it working in update too, and is it secure to give the full query in database?! thank you was very use full
  • SAR
    SAR over 7 years
    and why do i need to include paritial in each controller
  • spaceman
    spaceman over 7 years
    logging doesn't exist, its a hypothetical method that would take the inputs and save them to the db or to a console or to a file or whatever you want. its not a hard method to implement.