Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added API usage logging feature.

  • Loading branch information...
commit edcef0555de1ba41116f5fbef5f66ba31cb0b553 1 parent a1c30b6
authored March 23, 2012
10  .gitignore
... ...
@@ -0,0 +1,10 @@
  1
+NuGetPublish/
  2
+_ReSharper.*/
  3
+bin/
  4
+obj/
  5
+build/
  6
+packages/
  7
+TestResults/
  8
+
  9
+*.suo
  10
+*.user
6  Samples/ContactManager.Web/Global.asax.cs
@@ -7,6 +7,7 @@
7 7
 using Newtonsoft.Json;
8 8
 using Newtonsoft.Json.Converters;
9 9
 using Ninject;
  10
+using Thinktecture.Web.Http.Data;
10 11
 using Thinktecture.Web.Http.Filters;
11 12
 using Thinktecture.Web.Http.Formatters;
12 13
 using Thinktecture.Web.Http.Handlers;
@@ -29,7 +30,10 @@ public static void RegisterApis(HttpConfiguration config)
29 30
             config.Formatters.Add(new ContactCalendarFormatter());
30 31
             
31 32
             config.MessageHandlers.Add(new UriFormatExtensionHandler(new UriExtensionMappings()));
32  
-            config.MessageHandlers.Add(new LoggingHandler());
  33
+            
  34
+            var loggingRepo = config.ServiceResolver.GetService(typeof(ILoggingRepository)) as ILoggingRepository;
  35
+            config.MessageHandlers.Add(new LoggingHandler(loggingRepo));
  36
+
33 37
             config.MessageHandlers.Add(new NotAcceptableHandler());
34 38
 
35 39
             ConfigureResolver(config);
11  TestProject/EncodingTests.cs
@@ -46,17 +46,6 @@ public void Post_Lots_Of_Contacts_Using_EncodingHandler_Test()
46 46
 
47 47
             Assert.IsNotNull(response);
48 48
             Assert.IsTrue(response.StatusCode == HttpStatusCode.Created);
49  
-            //Assert.AreEqual("Hello-back", response.Content.ReadAsStringAsync().Result);
50  
-        }
51  
-    }
52  
-
53  
-
54  
-    public class GreetingsController : ApiController
55  
-    {
56  
-        [HttpPost]
57  
-        public string Post([FromBody]string greeting)
58  
-        {
59  
-            return greeting + "-back";
60 49
         }
61 50
     }
62 51
 
62  TestProject/LoggingTests.cs
... ...
@@ -0,0 +1,62 @@
  1
+using System;
  2
+using System.Collections.Generic;
  3
+using System.Net.Http;
  4
+using System.Net.Http.Formatting;
  5
+using System.Net.Http.Headers;
  6
+using System.Threading;
  7
+using System.Web.Http;
  8
+using System.Web.Http.ModelBinding;
  9
+using ContactManager.Models;
  10
+using Microsoft.VisualStudio.TestTools.UnitTesting;
  11
+using Thinktecture.Web.Http;
  12
+using Thinktecture.Web.Http.Formatters;
  13
+using Thinktecture.Web.Http.Handlers;
  14
+using Thinktecture.Web.Http.Testing;
  15
+
  16
+namespace TestProject
  17
+{
  18
+    [TestClass]
  19
+    public class LoggingTests
  20
+    {
  21
+        [TestMethod]
  22
+        public void Log_Simple_Request_Test_Should_Log_Request_And_Response()
  23
+        {
  24
+            var config = new HttpConfiguration();
  25
+            config.Routes.MapHttpRoute("default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
  26
+            config.ServiceResolver.SetService(typeof(IRequestContentReadPolicy), new ReadAsSingleObjectPolicy());
  27
+
  28
+            var dummyRepository = new DummyLoggingRepository();
  29
+            config.MessageHandlers.Add(new LoggingHandler(dummyRepository));
  30
+
  31
+            config.MessageHandlers.Add(new EncodingHandler());
  32
+            config.Formatters.Add(new ProtoBufFormatter());
  33
+
  34
+            var formatters = new List<MediaTypeFormatter>() { new JsonMediaTypeFormatter(), new ProtoBufFormatter() };
  35
+
  36
+            var server = new HttpServer(config);
  37
+            var client = new HttpClient(new EncodingHandler(server));
  38
+            client.DefaultRequestHeaders.Clear();
  39
+            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  40
+
  41
+            var content = new List<Contact>();
  42
+            var c = new Contact { Id = 1, Birthday = DateTime.Now.AddYears(-20) };
  43
+            content.Add(c);
  44
+
  45
+            var request = new HttpRequestMessage<List<Contact>>(content, ProtoBufFormatter.DefaultMediaType, formatters);
  46
+            var response = client.PostAsync("http://anything/api/contacts", request.Content).Result;
  47
+
  48
+            Assert.IsNotNull(response);
  49
+
  50
+            // Note: Because content within the logging handler can be extracted as an async
  51
+            // operation, there is no guarantee that that handler is called directly after we
  52
+            // get the response. So we have a sleep to give it time to execute.
  53
+            //TODO: Find better way of ensuring all logging requests are done.
  54
+
  55
+            Thread.Sleep(1000);
  56
+
  57
+            Assert.AreEqual<int>(2, dummyRepository.LogMessageCount);
  58
+            Assert.IsTrue(dummyRepository.HasRequestMessageTypeBeenReceived, "No request message has been logged");
  59
+            Assert.IsTrue(dummyRepository.HasResponseMessageTypeBeenReceived, "No Response message has been received");
  60
+        }
  61
+    }
  62
+}
1  TestProject/TestProject.csproj
@@ -71,6 +71,7 @@
71 71
   </ItemGroup>
72 72
   <ItemGroup>
73 73
     <Compile Include="EncodingTests.cs" />
  74
+    <Compile Include="LoggingTests.cs" />
74 75
     <Compile Include="Properties\AssemblyInfo.cs" />
75 76
     <Compile Include="DependencyInjectionTests.cs" />
76 77
     <Compile Include="ReadWriteFormUrlEncodedFormatterTests.cs" />
10  Thinktecture.Web.Http/Data/ILoggingRepository.cs
... ...
@@ -0,0 +1,10 @@
  1
+using Thinktecture.Web.Http.Messages;
  2
+
  3
+namespace Thinktecture.Web.Http.Data
  4
+{
  5
+    // Code based on: http://weblogs.asp.net/pglavich/archive/2012/02/26/asp-net-web-api-request-response-usage-logging.aspx
  6
+	public interface ILoggingRepository
  7
+	{
  8
+		void Log(ApiLoggingInfo loggingInfo);
  9
+	}
  10
+}
127  Thinktecture.Web.Http/Handlers/LoggingHandler.cs
... ...
@@ -1,17 +1,120 @@
1  
-using System.Diagnostics;
  1
+using System.Collections.Generic;
  2
+using System.Linq;
2 3
 using System.Net.Http;
3  
-using System.Threading;
4  
-using System.Threading.Tasks;
  4
+using System.Text;
  5
+using System.Web;
  6
+using Thinktecture.Web.Http.Data;
  7
+using Thinktecture.Web.Http.Messages;
5 8
 
6 9
 namespace Thinktecture.Web.Http.Handlers
7 10
 {
8  
-    public class LoggingHandler : DelegatingHandler
9  
-    {
10  
-        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
11  
-        {
12  
-            Trace.TraceInformation("Begin Request: {0} {1}", request.Method, request.RequestUri);
13  
-
14  
-            return base.SendAsync(request, cancellationToken);
15  
-        }
16  
-    }
  11
+    // Code based on: http://weblogs.asp.net/pglavich/archive/2012/02/26/asp-net-web-api-request-response-usage-logging.aspx
  12
+	public class LoggingHandler : DelegatingHandler
  13
+	{
  14
+		private ILoggingRepository _repository;
  15
+
  16
+		public LoggingHandler(ILoggingRepository repository)
  17
+		{
  18
+			_repository = repository;
  19
+		}
  20
+
  21
+		public LoggingHandler(HttpMessageHandler innerHandler, ILoggingRepository repository)
  22
+			: base(innerHandler)
  23
+		{
  24
+			_repository = repository;
  25
+		}
  26
+
  27
+		protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
  28
+		{
  29
+			// Log the request information
  30
+			LogRequestLoggingInfo(request);
  31
+
  32
+			// Execute the request
  33
+			var response = base.SendAsync(request, cancellationToken);
  34
+
  35
+			response.ContinueWith((responseMsg) =>
  36
+			{
  37
+				// Extract the response logging info then persist the information
  38
+				LogResponseLoggingInfo(responseMsg.Result);
  39
+			});
  40
+
  41
+			return response;
  42
+		}
  43
+
  44
+		private void LogRequestLoggingInfo(HttpRequestMessage request)
  45
+		{
  46
+			var info = new ApiLoggingInfo();
  47
+			info.HttpMethod = request.Method.Method;
  48
+			info.UriAccessed = request.RequestUri.AbsoluteUri;
  49
+			info.IpAddress = HttpContext.Current != null ? HttpContext.Current.Request.UserHostAddress : "0.0.0.0";
  50
+			info.MessageType = HttpMessageType.Request;
  51
+			
  52
+			ExtractMessageHeadersIntoLoggingInfo(info, request.Headers.ToList());
  53
+			
  54
+			if (request.Content != null)
  55
+			{
  56
+				request.Content.ReadAsByteArrayAsync()
  57
+					.ContinueWith((task) =>
  58
+					{
  59
+						info.BodyContent = System.Text.UTF8Encoding.UTF8.GetString(task.Result);
  60
+						_repository.Log(info);
  61
+
  62
+					});
  63
+
  64
+				return;
  65
+			}
  66
+
  67
+			_repository.Log(info);
  68
+		}
  69
+
  70
+		private void LogResponseLoggingInfo(HttpResponseMessage response)
  71
+		{
  72
+			var info = new ApiLoggingInfo();
  73
+			info.MessageType = HttpMessageType.Response;
  74
+			info.HttpMethod = response.RequestMessage.Method.ToString();
  75
+			info.ResponseStatusCode = response.StatusCode;
  76
+			info.ResponseStatusMessage = response.ReasonPhrase;
  77
+			info.UriAccessed = response.RequestMessage.RequestUri.AbsoluteUri;
  78
+			info.IpAddress = HttpContext.Current != null ? HttpContext.Current.Request.UserHostAddress : "0.0.0.0";
  79
+
  80
+			ExtractMessageHeadersIntoLoggingInfo(info, response.Headers.ToList());
  81
+			
  82
+			if (response.Content != null)
  83
+			{
  84
+				response.Content.ReadAsByteArrayAsync()
  85
+					.ContinueWith(t =>
  86
+					{
  87
+						var responseMsg = System.Text.UTF8Encoding.UTF8.GetString(t.Result);
  88
+						info.BodyContent = responseMsg;
  89
+						_repository.Log(info);
  90
+					});
  91
+
  92
+				return;
  93
+			}
  94
+
  95
+			_repository.Log(info);
  96
+		}
  97
+
  98
+		private void ExtractMessageHeadersIntoLoggingInfo(ApiLoggingInfo info, List<KeyValuePair<string, IEnumerable<string>>> headers)
  99
+		{
  100
+			headers.ForEach(h =>
  101
+			{
  102
+				// convert the header values into one long string from a series of IEnumerable<string> values so it looks for like a HTTP header
  103
+				var headerValues = new StringBuilder();
  104
+
  105
+				if (h.Value != null)
  106
+				{
  107
+					foreach (var hv in h.Value)
  108
+					{
  109
+						if (headerValues.Length > 0)
  110
+						{
  111
+							headerValues.Append(", ");
  112
+						}
  113
+						headerValues.Append(hv);
  114
+					}
  115
+				}
  116
+				info.Headers.Add(string.Format("{0}: {1}", h.Key, headerValues.ToString()));
  117
+			});
  118
+		}
  119
+	}
17 120
 }
30  Thinktecture.Web.Http/Messages/ApiLoggingInfo.cs
... ...
@@ -0,0 +1,30 @@
  1
+using System.Collections.Generic;
  2
+using System.Net;
  3
+
  4
+namespace Thinktecture.Web.Http.Messages
  5
+{
  6
+    // Code based on: http://weblogs.asp.net/pglavich/archive/2012/02/26/asp-net-web-api-request-response-usage-logging.aspx
  7
+	public class ApiLoggingInfo
  8
+	{
  9
+		private List<string> _headers = new List<string>();
  10
+
  11
+		public string HttpMethod { get; set; }
  12
+		public string UriAccessed { get; set; }
  13
+		public string BodyContent { get; set; }
  14
+		public HttpStatusCode ResponseStatusCode { get; set; }
  15
+		public string ResponseStatusMessage { get; set; }
  16
+		public string IpAddress { get; set; }
  17
+		public HttpMessageType MessageType { get; set; }
  18
+
  19
+		public List<string> Headers
  20
+		{
  21
+			get { return _headers; }
  22
+		}
  23
+	}
  24
+
  25
+	public enum HttpMessageType
  26
+	{
  27
+		Request,
  28
+		Response
  29
+	}
  30
+}
30  Thinktecture.Web.Http/Testing/DummyLoggingRepository.cs
... ...
@@ -0,0 +1,30 @@
  1
+using System.Diagnostics;
  2
+using Thinktecture.Web.Http.Data;
  3
+using Thinktecture.Web.Http.Messages;
  4
+
  5
+namespace Thinktecture.Web.Http.Testing
  6
+{
  7
+	public class DummyLoggingRepository : ILoggingRepository
  8
+	{
  9
+		public int LogMessageCount { get; set; }
  10
+
  11
+		public bool HasRequestMessageTypeBeenReceived { get; set; }
  12
+		public bool HasResponseMessageTypeBeenReceived { get; set; }
  13
+
  14
+		public void Log(Messages.ApiLoggingInfo loggingInfo)
  15
+		{
  16
+			LogMessageCount++;
  17
+
  18
+			if (loggingInfo.MessageType == HttpMessageType.Response)
  19
+			{
  20
+				HasResponseMessageTypeBeenReceived = true;
  21
+			}
  22
+			else
  23
+			{
  24
+				HasRequestMessageTypeBeenReceived = true;
  25
+			}
  26
+
  27
+			Trace.WriteLine(string.Format("Message: Type:{0}, Uri:{1}, Method:{2}", loggingInfo.MessageType, loggingInfo.UriAccessed, loggingInfo.HttpMethod));
  28
+		}
  29
+	}
  30
+}
3  Thinktecture.Web.Http/Thinktecture.Web.Http.csproj
@@ -81,6 +81,7 @@
81 81
   </ItemGroup>
82 82
   <ItemGroup>
83 83
     <Compile Include="Content\CompressedContent.cs" />
  84
+    <Compile Include="Data\ILoggingRepository.cs" />
84 85
     <Compile Include="Filters\EnableCorsAttribute.cs" />
85 86
     <Compile Include="Filters\ValidationAttribute.cs" />
86 87
     <Compile Include="Formatters\ProtoBufFormatter.cs" />
@@ -91,6 +92,7 @@
91 92
     <Compile Include="Handlers\EncodingHandler.cs" />
92 93
     <Compile Include="Handlers\NotAcceptableHandler.cs" />
93 94
     <Compile Include="Handlers\MethodOverrideHandler.cs" />
  95
+    <Compile Include="Messages\ApiLoggingInfo.cs" />
94 96
     <Compile Include="ReadAsSingleObjectPolicy.cs" />
95 97
     <Compile Include="Selectors\CorsActionSelector.cs" />
96 98
     <Compile Include="Handlers\SimpleCorsHandler.cs" />
@@ -100,6 +102,7 @@
100 102
     <Compile Include="Properties\AssemblyInfo.cs" />
101 103
     <Compile Include="Content\SimpleObjectContent.cs" />
102 104
     <Compile Include="SelfHost\SslHttpSelfHostConfiguration.cs" />
  105
+    <Compile Include="Testing\DummyLoggingRepository.cs" />
103 106
     <Compile Include="Testing\FakeController.cs" />
104 107
     <Compile Include="Testing\FakeHandler.cs" />
105 108
     <Compile Include="Testing\TestFactory.cs" />

0 notes on commit edcef05

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