建立Log
Log的用途:
- Debug,trace,analytics
- 記錄exception
- 集中管理訊息
此節將會透過「NLog」及「Exception Filters」記錄每一個Request的錯誤
實作
先切換到主專案
nuget
- 加入 NLog
web.config
- 加入Nlog區段
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
...省略
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
</configSections>
...省略
<nlog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="logfile" xsi:type="File" fileName="${basedir}/APILog/${date:format=yyyy-MM-dd}-api.log" />
<target name="eventlog" xsi:type="EventLog" layout="${message}" log="Application" source="Api Services" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="logfile" />
<logger name="*" minlevel="Trace" writeTo="eventlog" />
</rules>
</nlog>
</configuration>
建立Nlog的Helper
自行建立Helpers資料夾來放置Nlog的Helper
- 繼承ITraceWriter取得web api的所有資訊
- 主要的進入點在ITraceWriter方法
- 記錄error /info 的訊息
JSONHelper.cs
透過JavaScriptSerializer
反序列化成JSON格式
using System;
using System.Web.Script.Serialization;
namespace WebApi.Helpers
{
public static class JSONHelper
{
#region Public extension methods.
/// <summary>
/// Extened method of object class, Converts an object to a json string.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static string ToJSON(this object obj)
{
var serializer = new JavaScriptSerializer();
try
{
return serializer.Serialize(obj);
}
catch (Exception ex)
{
return "";
}
}
#endregion
}
}
NLogger.cs
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Web.Http.Tracing;
namespace WebApi.Helpers
{
/// <summary>
/// Public class to log Error/info messages to the access log file
/// </summary>
public sealed class NLogger : ITraceWriter
{
#region Private member variables.
private static readonly Logger ClassLogger = LogManager.GetCurrentClassLogger();
private static readonly Lazy<Dictionary<TraceLevel, Action<string>>> LoggingMap = new Lazy<Dictionary<TraceLevel, Action<string>>>(() => new Dictionary<TraceLevel, Action<string>> {
{ TraceLevel.Info, ClassLogger.Info },
{ TraceLevel.Debug, ClassLogger.Debug },
{ TraceLevel.Error, ClassLogger.Error },
{ TraceLevel.Fatal, ClassLogger.Fatal },
{ TraceLevel.Warn, ClassLogger.Warn } });
#endregion
#region Private properties.
/// <summary>
/// Get property for Logger
/// </summary>
private Dictionary<TraceLevel, Action<string>> Logger
{
get { return LoggingMap.Value; }
}
#endregion
#region Public member methods.
/// <summary>
/// Implementation of TraceWriter to trace the logs.
/// </summary>
/// <param name="request"></param>
/// <param name="category"></param>
/// <param name="level"></param>
/// <param name="traceAction"></param>
public void Trace(HttpRequestMessage request, string category, TraceLevel level, Action<TraceRecord> traceAction)
{
if (level != TraceLevel.Off)
{
if (traceAction != null && traceAction.Target != null)
{
category = category + Environment.NewLine + "Action Parameters : " + traceAction.Target.ToJSON();
}
var record = new TraceRecord(request, category, level);
if (traceAction != null) traceAction(record);
Log(record);
}
}
#endregion
#region Private member methods.
/// <summary>
/// Logs info/Error to Log file
/// </summary>
/// <param name="record"></param>
private void Log(TraceRecord record)
{
var message = new StringBuilder();
if (!string.IsNullOrWhiteSpace(record.Message))
message.Append("").Append(record.Message + Environment.NewLine);
if (record.Request != null)
{
if (record.Request.Method != null)
message.Append("Method: " + record.Request.Method + Environment.NewLine);
if (record.Request.RequestUri != null)
message.Append("").Append("URL: " + record.Request.RequestUri + Environment.NewLine);
if (record.Request.Headers != null && record.Request.Headers.Contains("Token") && record.Request.Headers.GetValues("Token") != null && record.Request.Headers.GetValues("Token").FirstOrDefault() != null)
message.Append("").Append("Token: " + record.Request.Headers.GetValues("Token").FirstOrDefault() + Environment.NewLine);
}
if (!string.IsNullOrWhiteSpace(record.Category))
message.Append("").Append(record.Category);
if (!string.IsNullOrWhiteSpace(record.Operator))
message.Append(" ").Append(record.Operator).Append(" ").Append(record.Operation);
Logger[record.Level](Convert.ToString(message) + Environment.NewLine);
}
#endregion
}
}
建立ActionFilter
我們將會透過OnActionExecuting
方法來記錄Reuest的資料至NLog
請先移至ActionFilters資料夾
LoggingFilterAttribute.cs
using System;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.Tracing;
using WebApi.Helpers;
namespace WebApi.ActionFilters
{
public class LoggingFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
//將ITraceWriter替換成NLogger類別(己實作ITraceWriter)
GlobalConfiguration.Configuration.Services.Replace(typeof(ITraceWriter), new NLogger());
//此時呼叫ITraceWriter實際上是透過NLogger裡的實作內容
var trace = GlobalConfiguration.Configuration.Services.GetTraceWriter();
//記錄資訊
trace.Info(filterContext.Request, "Controller : " + filterContext.ControllerContext.ControllerDescriptor.ControllerType.FullName + Environment.NewLine + "Action : " + filterContext.ActionDescriptor.ActionName, "JSON", filterContext.ActionArguments);
}
}
}
註冊LoggingFilterAttribute
WebApiConfig.cs
打開主專案下的App_Start資料夾,並將之前要將每一次的Request資訊記錄到Log的ActionFilter註冊進全域設定
using System.Web.Http;
using WebApi.ActionFilters;
namespace WebApi
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 設定和服務
//Log
config.Filters.Add(new LoggingFilterAttribute());
..以下省略
}
}
}
記錄的Log格式
一般文字檔
2016-07-14 22:48:33.5750|INFO|WebApi.Helpers.NLogger|JSON
Method: POST
URL: http://localhost:8126/login
Controller : WebApi.Controllers.AuthenticateController
Action : Authenticate
Action Parameters : {"messageFormat":"JSON","messageArguments":[{}]}
2016-07-14 22:48:47.1250|INFO|WebApi.Helpers.NLogger|JSON
Method: GET
URL: http://localhost:8126/api/product
Token: 3e944339-bf5f-42f4-a0ea-c450e00dcdb3
Controller : WebApi.Controllers.ProductController
Action : Get
Action Parameters : {"messageFormat":"JSON","messageArguments":[{}]}
Log-發生例外的原因
之前是記錄每一筆Reqeust資訊,而現在是要改成發生例外才記錄。
移至ActionFilter資料夾
GlobalExceptionAttribute.cs
- 繼承ExceptionFilterAttribute
- 覆寫OnException方法
- 將ITraceWriter替換成Nloger的服務
- 呼叫GetTraceWriter並記錄例外訊息
using System;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Filters;
using System.Web.Http.Tracing;
using WebApi.Helpers;
namespace WebApi.ActionFilters
{
/// <summary>
/// Action filter to handle for Global application errors.
/// </summary>
public class GlobalExceptionAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
GlobalConfiguration.Configuration.Services.Replace(typeof(ITraceWriter), new NLogger());
var trace = GlobalConfiguration.Configuration.Services.GetTraceWriter();
trace.Error(context.Request, "Controller : " + context.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName + Environment.NewLine + "Action : " + context.ActionContext.ActionDescriptor.ActionName, context.Exception);
var exceptionType = context.Exception.GetType();
if (exceptionType == typeof(ValidationException))
{
var resp = new HttpResponseMessage(HttpStatusCode.BadRequest) {
Content = new StringContent(context.Exception.Message), ReasonPhrase = "ValidationException",
};
throw new HttpResponseException(resp);
}
else if (exceptionType == typeof(UnauthorizedAccessException))
{
throw new HttpResponseException(context.Request.CreateResponse(HttpStatusCode.Unauthorized));
}
else
{
throw new HttpResponseException(context.Request.CreateResponse(HttpStatusCode.InternalServerError));
}
}
}
}
NLogger.cs
針對Log方法加入Exception的判斷
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Web.Http.Tracing;
namespace WebApi.Helpers
{
/// <summary>
/// Public class to log Error/info messages to the access log file
/// </summary>
public sealed class NLogger : ITraceWriter
{
...省略
#region Private member methods.
/// <summary>
/// Logs info/Error to Log file
/// </summary>
/// <param name="record"></param>
/// <summary>
/// Logs info/Error to Log file
/// </summary>
/// <param name="record"></param>
private void Log(TraceRecord record)
{
/****** 保持原本的方法-開始 *********/
var message = new StringBuilder();
if (!string.IsNullOrWhiteSpace(record.Message))
message.Append("").Append(record.Message + Environment.NewLine);
if (record.Request != null)
{
if (record.Request.Method != null)
message.Append("Method: " + record.Request.Method + Environment.NewLine);
if (record.Request.RequestUri != null)
message.Append("").Append("URL: " + record.Request.RequestUri + Environment.NewLine);
if (record.Request.Headers != null && record.Request.Headers.Contains("Token") && record.Request.Headers.GetValues("Token") != null && record.Request.Headers.GetValues("Token").FirstOrDefault() != null)
message.Append("").Append("Token: " + record.Request.Headers.GetValues("Token").FirstOrDefault() + Environment.NewLine);
}
if (!string.IsNullOrWhiteSpace(record.Category))
message.Append("").Append(record.Category);
if (!string.IsNullOrWhiteSpace(record.Operator))
message.Append(" ").Append(record.Operator).Append(" ").Append(record.Operation);
/********* 維持原本的方法-結束 *************/
//加入Exception的判斷
if (record.Exception != null && !string.IsNullOrWhiteSpace(record.Exception.GetBaseException().Message))
{
var exceptionType = record.Exception.GetType();
message.Append(Environment.NewLine);
message.Append("").Append("Error: " + record.Exception.GetBaseException().Message + Environment.NewLine);
}
//維持原本的方法
Logger[record.Level](Convert.ToString(message) + Environment.NewLine);
}
#endregion
}
}
註冊GlobalExceptionAttribute
WebApiConfig.cs
打開主專案下的App_Start資料夾,並將之前要將每一次的Request資訊記錄到Log的ActionFilter註冊進全域設定
using System.Web.Http;
using WebApi.ActionFilters;
namespace WebApi
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 設定和服務
//exception
config.Filters.Add(new GlobalExceptionAttribute());
..以下省略
}
}
}
測試方式
新增資料時不傳送任何資料,會記錄的Log格式如下:
HttpPost -> http://localhost/api/product/
若有啟用RoutePrefix,記得在Action上加入[Route("")]才能正常運作
一般文字檔
2016-07-14 23:36:49.1554|ERROR|WebApi.Helpers.NLogger|Method: POST
URL: http://localhost:8126/api/product/
Token: 3e944339-bf5f-42f4-a0ea-c450e00dcdb3
Controller : WebApi.Controllers.ProductController
Action : Post
Action Parameters :
Error: 並未將物件參考設定為物件的執行個體。
自訂例外訊息
移至Helpers資料夾,並建立以下檔案
ServiceStatus.cs
using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace WebApi.Helpers
{
#region Status Object Class
/// <summary>
/// Public class to return input status
/// </summary>
[Serializable]
[DataContract]
public class ServiceStatus
{
#region Public properties.
/// <summary>
/// Get/Set property for accessing Status Code
/// </summary>
[JsonProperty("StatusCode")]
[DataMember]
public int StatusCode { get; set; }
/// <summary>
/// Get/Set property for accessing Status Message
/// </summary>
[JsonProperty("StatusMessage")]
[DataMember]
public string StatusMessage { get; set; }
/// <summary>
/// Get/Set property for accessing Status Message
/// </summary>
[JsonProperty("ReasonPhrase")]
[DataMember]
public string ReasonPhrase { get; set; }
#endregion
}
#endregion
}
建立ErrorHelper資料夾,並建立以下檔案
IApiExceptions.cs
- 錯誤訊息
- 錯誤代碼
- HttpStatusCode
using System.Net;
namespace WebApi.ErrorHelper
{
/// <summary>
/// IApiExceptions Interface
/// </summary>
public interface IApiExceptions
{
/// <summary>
/// ErrorCode
/// </summary>
int ErrorCode { get; set; }
/// <summary>
/// ErrorDescription
/// </summary>
string ErrorDescription { get; set; }
/// <summary>
/// HttpStatus
/// </summary>
HttpStatusCode HttpStatus { get; set; }
/// <summary>
/// ReasonPhrase
/// </summary>
string ReasonPhrase { get; set; }
}
}
ApiDataException.cs
記錄資料層級的Exception
using System;
using System.Net;
using System.Runtime.Serialization;
namespace WebApi.ErrorHelper
{
/// <summary>
/// Api Data Exception
/// </summary>
[Serializable]
[DataContract]
public class ApiDataException : Exception, IApiExceptions
{
#region Public Serializable properties.
[DataMember]
public int ErrorCode { get; set; }
[DataMember]
public string ErrorDescription { get; set; }
[DataMember]
public HttpStatusCode HttpStatus { get; set; }
string reasonPhrase = "ApiDataException";
[DataMember]
public string ReasonPhrase
{
get { return this.reasonPhrase; }
set { this.reasonPhrase = value; }
}
#endregion
#region Public Constructor.
/// <summary>
/// Public constructor for Api Data Exception
/// </summary>
/// <param name="errorCode"></param>
/// <param name="errorDescription"></param>
/// <param name="httpStatus"></param>
public ApiDataException(int errorCode, string errorDescription, HttpStatusCode httpStatus)
{
ErrorCode = errorCode;
ErrorDescription = errorDescription;
HttpStatus = httpStatus;
}
#endregion
}
}
ApiBusinessException.cs
記錄商業邏輯層的Exception
using System;
using System.Net;
using System.Runtime.Serialization;
namespace WebApi.ErrorHelper
{
/// <summary>
/// Api Business Exception
/// </summary>
[Serializable]
[DataContract]
public class ApiBusinessException : Exception, IApiExceptions
{
#region Public Serializable properties.
[DataMember]
public int ErrorCode { get; set; }
[DataMember]
public string ErrorDescription { get; set; }
[DataMember]
public HttpStatusCode HttpStatus { get; set; }
string reasonPhrase = "ApiBusinessException";
[DataMember]
public string ReasonPhrase
{
get { return this.reasonPhrase; }
set { this.reasonPhrase = value; }
}
#endregion
#region Public Constructor.
/// <summary>
/// Public constructor for Api Business Exception
/// </summary>
/// <param name="errorCode"></param>
/// <param name="errorDescription"></param>
/// <param name="httpStatus"></param>
public ApiBusinessException(int errorCode, string errorDescription, HttpStatusCode httpStatus)
{
ErrorCode = errorCode;
ErrorDescription = errorDescription;
HttpStatus = httpStatus;
}
#endregion
}
}
ApiException.cs
記錄API的Exception
using System;
using System.Net;
using System.Runtime.Serialization;
namespace WebApi.ErrorHelper
{
/// <summary>
/// Api Exception
/// </summary>
[Serializable]
[DataContract]
public class ApiException : Exception, IApiExceptions
{
#region Public Serializable properties.
[DataMember]
public int ErrorCode { get; set; }
[DataMember]
public string ErrorDescription { get; set; }
[DataMember]
public HttpStatusCode HttpStatus { get; set; }
string reasonPhrase = "ApiException";
[DataMember]
public string ReasonPhrase
{
get { return this.reasonPhrase; }
set { this.reasonPhrase = value; }
}
#endregion
}
}
NLogger.cs
針對Log方法加入自訂Exception的判斷
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Web.Http.Tracing;
namespace WebApi.Helpers
{
/// <summary>
/// Public class to log Error/info messages to the access log file
/// </summary>
public sealed class NLogger : ITraceWriter
{
...省略
#region Private member methods.
/// <summary>
/// Logs info/Error to Log file
/// </summary>
/// <param name="record"></param>
/// <summary>
/// Logs info/Error to Log file
/// </summary>
/// <param name="record"></param>
private void Log(TraceRecord record)
{
/****** 保持原本的方法-開始 *********/
var message = new StringBuilder();
if (!string.IsNullOrWhiteSpace(record.Message))
message.Append("").Append(record.Message + Environment.NewLine);
if (record.Request != null)
{
if (record.Request.Method != null)
message.Append("Method: " + record.Request.Method + Environment.NewLine);
if (record.Request.RequestUri != null)
message.Append("").Append("URL: " + record.Request.RequestUri + Environment.NewLine);
if (record.Request.Headers != null && record.Request.Headers.Contains("Token") && record.Request.Headers.GetValues("Token") != null && record.Request.Headers.GetValues("Token").FirstOrDefault() != null)
message.Append("").Append("Token: " + record.Request.Headers.GetValues("Token").FirstOrDefault() + Environment.NewLine);
}
if (!string.IsNullOrWhiteSpace(record.Category))
message.Append("").Append(record.Category);
if (!string.IsNullOrWhiteSpace(record.Operator))
message.Append(" ").Append(record.Operator).Append(" ").Append(record.Operation);
/********* 維持原本的方法-結束 *************/
if (record.Exception != null && !string.IsNullOrWhiteSpace(record.Exception.GetBaseException().Message))
{
//加入自訂Exception的判斷邏輯
var exceptionType = record.Exception.GetType();
message.Append(Environment.NewLine);
if (exceptionType == typeof(ApiException))
{
var exception = record.Exception as ApiException;
if (exception != null)
{
message.Append("").Append("Error: " + exception.ErrorDescription + Environment.NewLine);
message.Append("").Append("Error Code: " + exception.ErrorCode + Environment.NewLine);
}
}
else if (exceptionType == typeof(ApiBusinessException))
{
var exception = record.Exception as ApiBusinessException;
if (exception != null)
{
message.Append("").Append("Error: " + exception.ErrorDescription + Environment.NewLine);
message.Append("").Append("Error Code: " + exception.ErrorCode + Environment.NewLine);
}
}
else if (exceptionType == typeof(ApiDataException))
{
var exception = record.Exception as ApiDataException;
if (exception != null)
{
message.Append("").Append("Error: " + exception.ErrorDescription + Environment.NewLine);
message.Append("").Append("Error Code: " + exception.ErrorCode + Environment.NewLine);
}
}
else
message.Append("").Append("Error: " + record.Exception.GetBaseException().Message + Environment.NewLine);
}
//維持原本的方法
Logger[record.Level](Convert.ToString(message) + Environment.NewLine);
}
#endregion
}
}
GlobalExceptionAttribute.cs
加入自訂Exception的判斷
using System;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Filters;
using System.Web.Http.Tracing;
using WebApi.ErrorHelper;
using WebApi.Helpers;
namespace WebApi.ActionFilters
{
/// <summary>
/// Action filter to handle for Global application errors.
/// </summary>
public class GlobalExceptionAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
GlobalConfiguration.Configuration.Services.Replace(typeof(ITraceWriter), new NLogger());
var trace = GlobalConfiguration.Configuration.Services.GetTraceWriter();
trace.Error(context.Request, "Controller : " + context.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName + Environment.NewLine + "Action : " + context.ActionContext.ActionDescriptor.ActionName, context.Exception);
var exceptionType = context.Exception.GetType();
if (exceptionType == typeof(ValidationException))
{
var resp = new HttpResponseMessage(HttpStatusCode.BadRequest) {
Content = new StringContent(context.Exception.Message),
ReasonPhrase = "ValidationException", };
throw new HttpResponseException(resp);
}
else if (exceptionType == typeof(UnauthorizedAccessException))
{
throw new HttpResponseException(context.Request.CreateResponse(HttpStatusCode.Unauthorized, new ServiceStatus() { StatusCode = (int)HttpStatusCode.Unauthorized, StatusMessage = "UnAuthorized", ReasonPhrase = "UnAuthorized Access" }));
}
else if (exceptionType == typeof(ApiException))
{
var webapiException = context.Exception as ApiException;
if (webapiException != null)
throw new HttpResponseException(context.Request.CreateResponse(webapiException.HttpStatus, new ServiceStatus() { StatusCode = webapiException.ErrorCode, StatusMessage = webapiException.ErrorDescription, ReasonPhrase = webapiException.ReasonPhrase }));
}
//加入自訂的Exception
else if (exceptionType == typeof(ApiBusinessException))
{
var businessException = context.Exception as ApiBusinessException;
if (businessException != null)
throw new HttpResponseException(context.Request.CreateResponse(businessException.HttpStatus, new ServiceStatus() { StatusCode = businessException.ErrorCode, StatusMessage = businessException.ErrorDescription, ReasonPhrase = businessException.ReasonPhrase }));
}
else if (exceptionType == typeof(ApiDataException))
{
var dataException = context.Exception as ApiDataException;
if (dataException != null)
throw new HttpResponseException(context.Request.CreateResponse(dataException.HttpStatus, new ServiceStatus() { StatusCode = dataException.ErrorCode, StatusMessage = dataException.ErrorDescription, ReasonPhrase = dataException.ReasonPhrase }));
}
else
{
throw new HttpResponseException(context.Request.CreateResponse(HttpStatusCode.InternalServerError));
}
}
}
}
ProductController.cs
修改Controller拋出例外的方式
[Route("v2/productid/{id:int}")]
[Route("v2/particularproduct/{id:int}")]
[Route("v2/myproduct/{id:range(1, 3)}")]
[Route("{id}")]
// GET api/product/5
public HttpResponseMessage Get(int id)
{
if (id > 0)
{
var product = _productServices.GetProductById(id);
if (product != null){
return Request.CreateResponse(HttpStatusCode.OK, product);
}
//原本回傳錯誤訊息的方式
//return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No product found for this id");
//改為拋出例外
throw new ApiDataException(1001, "No product found for this id.", HttpStatusCode.NotFound);
}
throw new ApiException() { ErrorCode = (int)HttpStatusCode.BadRequest, ErrorDescription = "Bad Request..." };
}
// DELETE api/product/5
public bool Delete(int id)
{
if (id > 0)
{
var isSuccess = _productServices.DeleteProduct(id);
if (isSuccess)
{
return true;
}
throw new ApiDataException(1002, "Product is already deleted or not exist in system.", HttpStatusCode.NoContent);
}
throw new ApiException() { ErrorCode = (int)HttpStatusCode.BadRequest, ErrorDescription = "Bad Request..." };
}
測試方式
HttpGet -> http://localhost:8126/api/product/1231
記錄的錯誤訊息
記錄的檔案格式如下
一般文字檔
若是透過自訂的拋出例外方式,會額外記錄以下欄位:
- Error(錯誤訊息)
- Error Code(錯誤代碼)
//回傳的方法:
//return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No product found for this id");
2016-07-15 17:51:02.5458|INFO|WebApi.Helpers.NLogger|JSON
Method: GET
URL: http://localhost:8126/api/product/1231
Token: 0e9808df-28e8-44d9-b32e-27c036147500
Controller : WebApi.Controllers.ProductController
Action : Get
Action Parameters : {"messageFormat":"JSON","messageArguments":[{"id":1231}]}
//回傳的方法:
//throw new ApiDataException(1001, "No product found for this id.", HttpStatusCode.NotFound);
2016-07-15 17:51:04.6592|ERROR|WebApi.Helpers.NLogger|Method: GET
URL: http://localhost:8126/api/product/1231
Token: 0e9808df-28e8-44d9-b32e-27c036147500
Controller : WebApi.Controllers.ProductController
Action : Get
Action Parameters :
Error: No product found for this id.
Error Code: 1001