Skip to content
Browse files

Enhance RequestLogger, include optional Session, Resposne and Error T…

…racking
  • Loading branch information...
1 parent a8a47c6 commit 4044e8e92784d6e35e61234cec9b2eb9395819ce @mythz mythz committed Apr 3, 2012
View
2 src/ServiceStack.Common/Web/HttpHeaders.cs
@@ -8,6 +8,8 @@ public static class HttpHeaders
public const string XUserAuthId = "X-UAId";
+ public const string XForwardedFor = "X-Forwarded-For";
+
public const string CacheControl = "Cache-Control";
public const string IfModifiedSince = "If-Modified-Since";
View
13 src/ServiceStack.Common/Web/HttpResultExtensions.cs
@@ -1,4 +1,5 @@
-using ServiceStack.Common.Utils;
+using System;
+using ServiceStack.Common.Utils;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface.ServiceModel;
@@ -37,5 +38,15 @@ public static ResponseStatus ToResponseStatus(this object response)
return ReflectionUtils.GetProperty(response, propertyInfo) as ResponseStatus;
}
+
+ /// <summary>
+ /// Whether the response is an IHttpError or Exception
+ /// </summary>
+ /// <param name="response"></param>
+ /// <returns></returns>
+ public static bool IsErrorResponse(this object response)
+ {
+ return response != null && (response is IHttpError || response is Exception);
+ }
}
}
View
41 src/ServiceStack.Interfaces/ServiceHost/IRequestLogger.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using ServiceStack.ServiceInterface.ServiceModel;
namespace ServiceStack.ServiceHost
@@ -9,11 +10,43 @@ namespace ServiceStack.ServiceHost
public interface IRequestLogger
{
/// <summary>
+ /// Turn On/Off Session Tracking
+ /// </summary>
+ bool EnableSessionTracking { get; set; }
+
+ /// <summary>
+ /// Turn On/Off Tracking of Responses
+ /// </summary>
+ bool EnableResponseTracking { get; set; }
+
+ /// <summary>
+ /// Turn On/Off Tracking of Exceptions
+ /// </summary>
+ bool EnableErrorTracking { get; set; }
+
+ /// <summary>
+ /// Limit access to /requestlogs service to role
+ /// </summary>
+ string RequiresRole { get; set; }
+
+ /// <summary>
+ /// Don't log requests of these types.
+ /// </summary>
+ Type[] ExcludeRequestDtoTypes { get; set; }
+
+ /// <summary>
+ /// Don't log request bodys for services with sensitive information.
+ /// By default Auth and Registration requests are hidden.
+ /// </summary>
+ Type[] HideRequestBodyForRequestDtoTypes { get; set; }
+
+ /// <summary>
/// Log a request
/// </summary>
- /// <param name="requestContext"></param>
- /// <param name="requestDto"></param>
- void Log(IRequestContext requestContext, object requestDto);
+ /// <param name="requestContext">The RequestContext</param>
+ /// <param name="requestDto">Request DTO</param>
+ /// <param name="response">Response DTO or Exception</param>
+ void Log(IRequestContext requestContext, object requestDto, object response);
/// <summary>
/// View the most recent logs
View
4 src/ServiceStack.Interfaces/ServiceInterface.ServiceModel/RequestLogEntry.cs
@@ -17,9 +17,13 @@ public class RequestLogEntry
public string UserAuthId { get; set; }
public string SessionId { get; set; }
public string IpAddress { get; set; }
+ public string ForwardedFor { get; set; }
public string Referer { get; set; }
public Dictionary<string, string> Headers { get; set; }
public Dictionary<string, string> FormData { get; set; }
public Dictionary<string, object> Items { get; set; }
+ public object Session { get; set; }
+ public object ResponseDto { get; set; }
+ public object ErrorResponse { get; set; }
}
}
View
56 src/ServiceStack.ServiceInterface/Admin/RequestLogsFeature.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface.Auth;
using ServiceStack.ServiceInterface.Providers;
@@ -8,18 +9,59 @@ namespace ServiceStack.ServiceInterface.Admin
{
public class RequestLogsFeature : IPlugin
{
+ /// <summary>
+ /// RequestLogs service Route, default is /requestlogs
+ /// </summary>
public string AtRestPath { get; set; }
+ /// <summary>
+ /// Turn On/Off Session Tracking
+ /// </summary>
+ public bool EnableSessionTracking { get; set; }
+
+ /// <summary>
+ /// Turn On/Off Tracking of Responses
+ /// </summary>
+ public bool EnableResponseTracking { get; set; }
+
+ /// <summary>
+ /// Turn On/Off Tracking of Exceptions
+ /// </summary>
+ public bool EnableErrorTracking { get; set; }
+
+ /// <summary>
+ /// Size of InMemoryRollingRequestLogger circular buffer
+ /// </summary>
public int? Capacity { get; set; }
+ /// <summary>
+ /// Limit access to /requestlogs service to these roles
+ /// </summary>
+ public string RequiresRole { get; set; }
+
+ /// <summary>
+ /// Change the RequestLogger provider. Default is InMemoryRollingRequestLogger
+ /// </summary>
public IRequestLogger RequestLogger { get; set; }
+ /// <summary>
+ /// Don't log requests of these types. By default RequestLog's are excluded
+ /// </summary>
+ public Type[] ExcludeRequestDtoTypes { get; set; }
+
+ /// <summary>
+ /// Don't log request bodys for services with sensitive information.
+ /// By default Auth and Registration requests are hidden.
+ /// </summary>
public Type[] HideRequestBodyForRequestDtoTypes { get; set; }
public RequestLogsFeature(int? capacity = null)
{
this.AtRestPath = "/requestlogs";
this.Capacity = capacity;
+ this.RequiresRole = RoleNames.Admin;
+ this.EnableErrorTracking = true;
+ this.ExcludeRequestDtoTypes = new[] { typeof(RequestLogs) };
this.HideRequestBodyForRequestDtoTypes = new[] {
typeof(Auth.Auth), typeof(Registration)
};
@@ -28,10 +70,16 @@ public RequestLogsFeature(int? capacity = null)
public void Register(IAppHost appHost)
{
appHost.RegisterService<RequestLogsService>(AtRestPath);
- appHost.Register(RequestLogger
- ?? new InMemoryRollingRequestLogger(Capacity) {
- HideRequestBodyForRequestDtoTypes = HideRequestBodyForRequestDtoTypes
- });
+
+ var requestLogger = RequestLogger ?? new InMemoryRollingRequestLogger(Capacity);
+ requestLogger.EnableSessionTracking = EnableSessionTracking;
+ requestLogger.EnableResponseTracking = EnableResponseTracking;
+ requestLogger.EnableErrorTracking = EnableErrorTracking;
+ requestLogger.RequiresRole = RequiresRole;
+ requestLogger.ExcludeRequestDtoTypes = ExcludeRequestDtoTypes;
+ requestLogger.HideRequestBodyForRequestDtoTypes = HideRequestBodyForRequestDtoTypes;
+
+ appHost.Register(requestLogger);
}
}
}
View
46 src/ServiceStack.ServiceInterface/Admin/RequestLogsService.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using ServiceStack.Common;
-using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface.ServiceModel;
namespace ServiceStack.ServiceInterface.Admin
@@ -12,15 +11,21 @@ public class RequestLogs
public int? BeforeSecs { get; set; }
public int? AfterSecs { get; set; }
public string IpAddress { get; set; }
+ public string ForwardedFor { get; set; }
public string UserAuthId { get; set; }
public string SessionId { get; set; }
public string Referer { get; set; }
public string PathInfo { get; set; }
public long[] Ids { get; set; }
public int? BeforeId { get; set; }
public int? AfterId { get; set; }
+ public bool? HasResponse { get; set; }
+ public bool? WithErrors { get; set; }
public int Skip { get; set; }
public int? Take { get; set; }
+ public bool? EnableSessionTracking { get; set; }
+ public bool? EnableResponseTracking { get; set; }
+ public bool? EnableErrorTracking { get; set; }
}
public class RequestLogsResponse
@@ -35,30 +40,37 @@ public RequestLogsResponse()
public ResponseStatus ResponseStatus { get; set; }
}
- [RequiredRole(RoleNames.Admin)]
public class RequestLogsService : ServiceBase<RequestLogs>
{
- public IRequestLogger RequestLogger { get; set; }
-
private static readonly Dictionary<string,string> Usage = new Dictionary<string,string> {
- {"BeforeSecs", "int - Requests before elapsed time"},
- {"AfterSecs", "int - Requests after elapsed time"},
- {"IpAddress", "string - Requests matcing Ip Address"},
- {"UserAuthId", "string - Requests matcing UserAuthId"},
- {"SessionId", "string - Requests matcing SessionId"},
- {"Referer", "string - Requests matcing Http Referer"},
- {"PathInfo", "string - Requests matcing PathInfo"},
- {"BeforeId", "int - Requests before RequestLog Id"},
- {"AfterId", "int - Requests after RequestLog Id"},
- {"Skip", "int - Skip past N results"},
- {"Take", "int - Only look at last N results"},
+ {"int BeforeSecs", "Requests before elapsed time"},
+ {"int AfterSecs", "Requests after elapsed time"},
+ {"string IpAddress", "Requests matching Ip Address"},
+ {"string ForwardedFor", "Requests matching Forwarded Ip Address"},
+ {"string UserAuthId", "Requests matching UserAuthId"},
+ {"string SessionId", "Requests matching SessionId"},
+ {"string Referer", "Requests matching Http Referer"},
+ {"string PathInfo", "Requests matching PathInfo"},
+ {"int BeforeId", "Requests before RequestLog Id"},
+ {"int AfterId", "Requests after RequestLog Id"},
+ {"bool WithErrors", "Requests with errors"},
+ {"int Skip", "Skip past N results"},
+ {"int Take", "Only look at last N results"},
+ {"bool EnableSessionTracking", "Turn On/Off Session Tracking"},
+ {"bool EnableResponseTracking", "Turn On/Off Tracking of Responses"},
+ {"bool EnableErrorTracking", "Turn On/Off Tracking of Errors"},
};
protected override object Run(RequestLogs request)
{
if (RequestLogger == null)
throw new Exception("No IRequestLogger is registered");
+ RequiredRoleAttribute.AssertRequiredRoles(RequestContext, RequestLogger.RequiresRole);
+
+ if (request.EnableSessionTracking.HasValue)
+ RequestLogger.EnableSessionTracking = request.EnableSessionTracking.Value;
+
var now = DateTime.UtcNow;
var logs = RequestLogger.GetLatestLogs(request.Take).AsQueryable();
@@ -82,6 +94,10 @@ protected override object Run(RequestLogs request)
logs = logs.Where(x => x.Id <= request.BeforeId);
if (request.AfterId.HasValue)
logs = logs.Where(x => x.Id > request.AfterId);
+ if (request.WithErrors.HasValue)
+ logs = request.WithErrors.Value
+ ? logs.Where(x => x.ErrorResponse != null)
+ : logs.Where(x => x.ErrorResponse == null);
var results = logs.Skip(request.Skip).OrderByDescending(x => x.Id).ToList();
View
49 src/ServiceStack.ServiceInterface/Providers/InMemoryRollingRequestLogger.cs
@@ -7,7 +7,6 @@
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface.ServiceModel;
using ServiceStack.ServiceModel;
-using ServiceStack.Text;
namespace ServiceStack.ServiceInterface.Providers
{
@@ -19,37 +18,64 @@ public class InMemoryRollingRequestLogger : IRequestLogger
private readonly ConcurrentQueue<RequestLogEntry> logEntries = new ConcurrentQueue<RequestLogEntry>();
readonly int capacity;
+ public bool EnableSessionTracking { get; set; }
+
+ public bool EnableResponseTracking { get; set; }
+
+ public bool EnableErrorTracking { get; set; }
+
+ public string RequiresRole { get; set; }
+
+ public Type[] ExcludeRequestDtoTypes { get; set; }
+
public Type[] HideRequestBodyForRequestDtoTypes { get; set; }
public InMemoryRollingRequestLogger(int? capacity = DefaultCapacity)
{
this.capacity = capacity.GetValueOrDefault(DefaultCapacity);
}
- public void Log(IRequestContext requestContext, object requestDto)
+ public void Log(IRequestContext requestContext, object requestDto, object response)
{
+ var requestType = requestDto != null ? requestDto.GetType() : null;
+
+ if (ExcludeRequestDtoTypes != null
+ && requestType != null
+ && ExcludeRequestDtoTypes.Contains(requestType))
+ return;
+
var httpReq = requestContext.Get<IHttpRequest>();
var entry = new RequestLogEntry {
Id = Interlocked.Increment(ref requestId),
DateTime = DateTime.UtcNow,
HttpMethod = httpReq.HttpMethod,
AbsoluteUri = httpReq.AbsoluteUri,
- IpAddress = requestContext.IpAddress,
PathInfo = httpReq.PathInfo,
+ IpAddress = requestContext.IpAddress,
+ ForwardedFor = httpReq.Headers[HttpHeaders.XForwardedFor],
Referer = httpReq.Headers[HttpHeaders.Referer],
Headers = httpReq.Headers.ToDictionary(),
UserAuthId = httpReq.GetItemOrCookie(HttpHeaders.XUserAuthId),
SessionId = httpReq.GetSessionId(),
Items = httpReq.Items,
+ Session = EnableSessionTracking ? httpReq.GetSession() : null,
};
if (HideRequestBodyForRequestDtoTypes != null
- && requestDto != null
- && !HideRequestBodyForRequestDtoTypes.Contains(requestDto.GetType()))
+ && requestType != null
+ && !HideRequestBodyForRequestDtoTypes.Contains(requestType))
{
entry.RequestDto = requestDto;
entry.FormData = httpReq.FormData.ToDictionary();
}
+ if (response.IsErrorResponse()) {
+ if (EnableResponseTracking)
+ entry.ResponseDto = response;
+ }
+ else {
+ if (EnableErrorTracking)
+ entry.ErrorResponse = ToSerializableErrorResponse(response);
+ }
logEntries.Enqueue(entry);
@@ -65,5 +91,18 @@ public List<RequestLogEntry> GetLatestLogs(int? take)
? new List<RequestLogEntry>(requestLogEntries.Take(take.Value))
: new List<RequestLogEntry>(requestLogEntries);
}
+
+ public static object ToSerializableErrorResponse(object response)
+ {
+ var errorResult = response as IHttpResult;
+ if (errorResult != null)
+ return errorResult.Response;
+
+ var ex = response as Exception;
+ if (ex != null)
+ ResponseStatusTranslator.Instance.Parse(ex);
+
+ return null;
+ }
}
}
View
23 src/ServiceStack.ServiceInterface/RequiredRoleAttribute.cs
@@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Net;
-using System.Text;
-using System.Web;
using ServiceStack.Common;
+using ServiceStack.Common.Web;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface.Auth;
-using ServiceStack.Text;
namespace ServiceStack.ServiceInterface
{
@@ -69,6 +66,24 @@ public bool HasAllRoles(IAuthSession session)
.All(requiredRole => session != null
&& session.HasRole(requiredRole));
}
+
+ /// <summary>
+ /// Check all session is in all supplied roles otherwise a 401 HttpError is thrown
+ /// </summary>
+ /// <param name="requestContext"></param>
+ /// <param name="requiredRoles"></param>
+ public static void AssertRequiredRoles(IRequestContext requestContext, params string[] requiredRoles)
+ {
+ if (requiredRoles.IsEmpty()) return;
+
+ var req = requestContext.Get<IHttpRequest>();
+ var session = req.GetSession();
+
+ if (session != null && requiredRoles.All(session.HasRole))
+ return;
+
+ throw new HttpError(HttpStatusCode.Unauthorized, "Invalid Role");
+ }
}
}
View
25 src/ServiceStack.ServiceInterface/RestServiceBase.cs
@@ -50,9 +50,8 @@ public object Get(TRequest request)
{
try
{
- OnEachRequest(request);
- OnBeforeExecute(request);
- return OnAfterExecute(OnGet(request));
+ BeforeEachRequest(request);
+ return AfterEachRequest(request, OnGet(request));
}
catch (Exception ex)
{
@@ -87,9 +86,8 @@ public object Put(TRequest request)
{
try
{
- OnEachRequest(request);
- OnBeforeExecute(request);
- return OnAfterExecute(OnPut(request));
+ BeforeEachRequest(request);
+ return AfterEachRequest(request, OnPut(request));
}
catch (Exception ex)
{
@@ -124,9 +122,8 @@ public object Post(TRequest request)
{
try
{
- OnEachRequest(request);
- OnBeforeExecute(request);
- return OnAfterExecute(OnPost(request));
+ BeforeEachRequest(request);
+ return AfterEachRequest(request, OnPost(request));
}
catch (Exception ex)
{
@@ -161,9 +158,8 @@ public object Delete(TRequest request)
{
try
{
- OnEachRequest(request);
- OnBeforeExecute(request);
- return OnAfterExecute(OnDelete(request));
+ BeforeEachRequest(request);
+ return AfterEachRequest(request, OnDelete(request));
}
catch (Exception ex)
{
@@ -198,9 +194,8 @@ public object Patch(TRequest request)
{
try
{
- OnEachRequest(request);
- OnBeforeExecute(request);
- return OnAfterExecute(OnPatch(request));
+ BeforeEachRequest(request);
+ return AfterEachRequest(request, OnPatch(request));
}
catch (Exception ex)
{
View
38 src/ServiceStack.ServiceInterface/ServiceBase.cs
@@ -73,17 +73,26 @@ public IAppHost GetAppHost()
/// Easy way to log all requests
/// </summary>
/// <param name="requestDto"></param>
- protected void OnEachRequest(object requestDto)
+ protected void BeforeEachRequest(TRequest requestDto)
{
- if (this.RequestLogger == null) return;
- try
- {
- RequestLogger.Log(this.RequestContext, requestDto);
- }
- catch (Exception ex)
+ OnBeforeExecute(requestDto);
+ }
+
+ protected object AfterEachRequest(TRequest requestDto, object response)
+ {
+ if (this.RequestLogger != null)
{
- Log.Error("Error while logging request: " + requestDto.Dump(), ex);
+ try
+ {
+ RequestLogger.Log(this.RequestContext, requestDto, response);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Error while logging request: " + requestDto.Dump(), ex);
+ }
}
+
+ return response.IsErrorResponse() ? null : OnAfterExecute(response);
}
private ISession session;
@@ -183,9 +192,8 @@ public object Execute(TRequest request)
{
try
{
- OnEachRequest(request);
- OnBeforeExecute(request);
- return OnAfterExecute(Run(request));
+ BeforeEachRequest(request);
+ return AfterEachRequest(request, Run(request));
}
catch (Exception ex)
{
@@ -242,7 +250,11 @@ protected virtual object HandleException(TRequest request, Exception ex)
}
}
- return ServiceUtils.CreateErrorResponse(request, ex, responseStatus);
+ var errorResponse = ServiceUtils.CreateErrorResponse(request, ex, responseStatus);
+
+ AfterEachRequest(request, errorResponse ?? ex);
+
+ return errorResponse;
}
protected HttpResult View(string viewName, object response)
@@ -283,7 +295,7 @@ public virtual object ExecuteAsync(TRequest request)
return Execute(request);
}
- OnEachRequest(request);
+ BeforeEachRequest(request);
//Capture and persist this async request on this Services 'In Queue'
//for execution after this request has been completed

0 comments on commit 4044e8e

Please sign in to comment.
Something went wrong with that request. Please try again.