3
\$\begingroup\$

Being a newbie into AngularJS, I have overlooked the importance of client side logging until some non-reproducible client-side errors occurred and I realized that they were not logged.

My web application is developed using MVC 5 and AngularJS and my intention is to have a good client side logging that sends errors and debug information to a database table.

AngularJS part is based on the great code provided by Nadeem Shabir here. Basically, all Angular's unhandled exceptions are caught and logged, but a logging service also allows to arbitrary send debug information:

/**
    factory that provides a wrapper for stacktrace's print method
*/
coreModule.factory(
    "traceService",
    function () {
        return ({
            print: printStackTrace
        });
    }
);

/**
 * Override Angular's built in exception handler, and tell it to 
 * use our new exceptionLoggingService which is defined below
 */
coreModule.provider(
    "$exceptionHandler", {
        $get: function (exceptionLoggingService) {
            return (exceptionLoggingService);
        }
    }
);

/**
 * Exception Logging Service, currently only used by the $exceptionHandler
 * it preserves the default behaviour ( logging to the console) but 
 * also posts the error server side after generating a stacktrace.
 */
coreModule.factory(
    "exceptionLoggingService", function ($log, $window, $rootUrlService, traceService) {
        function error(exception, cause) {

            // preserve the default behaviour which will log the error
            // to the console, and allow the application to continue running.
            $log.error.apply($log, arguments);

            // now try to log the error to the server side.
            try {
                var errorMessage = exception.toString();

                // use our traceService to generate a stack trace
                var stackTrace = traceService.print({ e: exception });

                // use AJAX (in this example jQuery) and NOT 
                // an angular service such as $http 
                $.ajax({
                    type: "POST",
                    url: $rootUrlService.rootUrl + "Logging/LogException",
                    contentType: "application/json",
                    data: angular.toJson({
                        url: $window.location.href,
                        message: errorMessage,
                        type: "exception",
                        stackTrace: stackTrace,
                        cause: (cause || "")
                    })
                });
            } catch (loggingError) {
                $log.warn("Error server-side logging failed");
                $log.log(loggingError);
            }
        }
        return (error);
    }
);

/**
 * Application Logging Service to give us a way of logging 
 * error / debug statements from the client to the server.
 */
coreModule.factory(
    "applicationLoggingService", function ($log, $window, $rootUrlService) {
        return ({
            error: function (message) {
                // preserve default behaviour
                $log.error.apply($log, arguments);
                // send server side
                $.ajax({
                    type: "POST",
                    url: $rootUrlService.rootUrl + "Logging/Log",
                    contentType: "application/json",
                    data: angular.toJson({
                        url: $window.location.href,
                        message: message,
                        type: "error"
                    })
                });
            },
            debug: function (message) {
                $log.log.apply($log, arguments);
                $.ajax({
                    type: "POST",
                    url: $rootUrlService.rootUrl + "Logging/Log",
                    contentType: "application/json",
                    data: angular.toJson({
                        url: $window.location.href,
                        message: message,
                        type: "debug"
                    })
                });
            }
        });
    }
);

MVC controller code (server-side)

public class LoggingController : BaseController
{
    [HttpPost]
    public void LogException(String url, String message, String type, List<String> stackTrace, String cause)
    {
        String stackTraceStr = String.Join(Environment.NewLine, stackTrace);
        Logger.Log(LogLevel.Error, "Angular/Client error {0} at URL = {1}, client stack trace = {2}, cause = {3}", message, url, stackTraceStr, cause);
    }

    [HttpPost]
    public void Log(String url, String message, String type)
    {
        var level = type.Contains("error") ? LogLevel.Error : LogLevel.Info;
        Logger.Log(level, "Angular/Client custom info {0} at URL = {1}, type = {2}", message, url, type);
    }
}


public class BaseController : Controller
{
    [Inject]
    public ILoggingService Logger { get; set; }

    public BaseController()
    {
        // trigger the dependency injection on properties 
        NinjectWebCommon.PerformInjectionOn(this);
    }
}

Logging service

public class LoggingService : ILoggingService
{
    private static Logger logger = LogManager.GetCurrentClassLogger();

    public void Log(LogLevel level, String message)
    {
        logger.Log(level, message);
    }

    public void Log(LogLevel level, String format, params object[] parameters)
    {
        logger.Log(level, format, parameters);
    }

    public void Log(LogLevel level, IList<String> list)
    {
        String output = String.Join("; ", list);
        Log(level, output);
    }

    public void Log(LogLevel level, String message, Exception exc)
    {
        try
        {
            GlobalDiagnosticsContext.Set("FullExceptionInfo", exc.ToString());
            logger.Log(level, message, exc);
        }
        finally
        {
            GlobalDiagnosticsContext.Remove("FullExceptionInfo");
        }
    }

    public void Log(LogLevel level, String format, Exception exc, params object[] parameters)
    {
        try
        {
            GlobalDiagnosticsContext.Set("FullExceptionInfo", exc.ToString());
            logger.Log(level, format, parameters);
        }
        finally
        {
            GlobalDiagnosticsContext.Remove("FullExceptionInfo");
        }
    }
}

In order to reduce logging overhead, async logging is enabled for all database writes.

NLog configuration

  <targets async="true">
    <target name="database" type="Database">
      <connectionString>
        <!-- connection string comes here -->
      </connectionString>
      <commandText>
        insert into dbo.nlog
        (log_date, log_level_id, log_level, logger, log_message, machine_name, log_user_name, call_site, thread, exception, stack_trace, full_exception_info)
        values(@timestamp, dbo.func_get_nlog_level_id(@level), @level, NULL /*@logger*/, @message, @machinename, @username, NULL /*@call_site */, @threadid, @log_exception, @stacktrace, @FullExceptionInfo);
      </commandText>
      <parameter name="@timestamp" layout="${longdate}"/>
      <parameter name="@level" layout="${level}"/>
      <parameter name="@logger" layout="${logger}"/>
      <parameter name="@message" layout="${message}"/>
      <parameter name="@machinename" layout="${machinename}"/>
      <parameter name="@username" layout="${windows-identity:domain=true}"/>
      <parameter name="@call_site" layout="${callsite:filename=true}"/>
      <parameter name="@threadid" layout="${threadid}"/>
      <parameter name="@log_exception" layout="${exception}"/>
      <parameter name="@stacktrace" layout="${stacktrace}"/>
      <parameter name="@FullExceptionInfo" layout="${gdc:FullExceptionInfo}"/>
    </target>
  </targets>

Can this logging mechanism be improved? some pitfalls I might fall into?

\$\endgroup\$
0

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.