Inevitably, if you’re building an API, you’re going to want to monitor requests made to that API. A big step in doing that is logging each API request so that you can capture and determine important information about your endpoints, such as data being sent in, data being returned, how many times endpoints were invoked, and how long it took endpoints to serve their requests.
ASP.NET Web API makes logging API requests straightforward with a DelegatingHandler, which is part of the message handlers responsible for processing various parts of the Web API request pipeline. Implementing your own DelegatingHandler allows you to intercept API calls so that you can take additional action, such as validating API keys, performing authentication, or in this case, logging API requests.
The ApiLogEntry
For this demonstration, we’re going to log the data that came in as part of the request and the data being returned as part of the response. To capture this set of data we have a class named ApiLogEntry, and this is what we’ll persist to our database.
The ApiLogHandler
With that in place we can take a look at the ApiLogHandler class, our custom DelegatingHandler used to intercept all API calls:
Some things to note about this class:
- It inherits from DelegatingHandler; this is important.
- It uses ContinueWith statements to help avoid potential deadlock issues.
- It uses the Newtonsoft.Json library to perform serialization.
- You have to get the calling app from somewhere, such as the Authorization header, query parameter, etc.
- You have to write the code to save the API log entry to your database.
Registering ApiLogHandler
Now that we have our custom DelegatingHandler, we need to let the Web API infrastructure know it exists, which we do by registering it in the Application_Start method of global.asax:
Done and Done
And that’s it. This pattern can be used as-is or tweaked to satisfy other needs, but as you can see, being able to add your own custom message handler (in the form of as DelegatingHandler) takes minimal effort for something that could have a big benefit to your application.
Featured Image: Some rights reserved by Rick Payette
Thanks for the write up
Would you explain further why ContinueWith is needed instead of just using await?
Thank You
Assessing task.Result before the task is completed can cause deadlocks in ASP.NET, so accessing task.Result inside a ContinueWith statement helps avoid the potential deadlock issue. It’s really just used as a safety mechanism.
But doesn’t the await keyword promt the compiler to rewrite the code to use a continueWith?
Or is there something in the rewrite that’s different to doing it by hand?
Hmmm, good question, I don’t know off hand, but I’ll have to dig in and find out.
Are you Implement in Visual Studio 2010 right
In 4.0 .Net Framework Below Method is Available
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
In 4.5 .Net Framework Below Method is Available
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Great post. Your code works well! I was going to use an Action Filter but this is better.
Hi thank you fro sharing nice way handle log for api i just need to save in to txt where can i add write file
Hi,
Can you please clarify whether this approach is better than logging in Action filter or not?
Thanks
It’s not that this is approach is “better”, but more about your specific needs. See these two Stack Overflow threads for further details:
http://stackoverflow.com/questions/11123015/when-to-use-httpmessagehandler-vs-actionfilter
http://stackoverflow.com/questions/23825505/actionattributefilter-vs-delegatinghandler-advantages-disadvantages
With ASPNET 5, DelegateHandler is not available. Can we have sample with ASPNET 5, MVC 6
Good article!
How would you sanitize the logged response content? For privacy/security reasons you should not log email address, credit card data and such.
Absolutely Great Article!!!
I would like to know how I can store this into a database.
I look forward to hear from you soon.
Use Entity Framework to save to DB. It will make the work very easy.
create a DataContext class as below:
public class LoggingContext : DbContext
{
public DbSet ReqRespLogEntry { get; set; }
public LoggingContext() : base(“DefaultConnection”)
{
}
}
Here “DefaultConnection” is entry in your config file for DB connection.
Then create object of ‘apiLogEntry’ and save it to DB.
new ApiLogEntry
{
ApiLogEntryId = 1,
Application = “My Service”,
…
…
}
Use entity framework to save to DB.
using (var db = new WebAPIRequestLogging.DataContext.LoggingContext())
{
db.ReqRespLogEntry.Add(apiLogEntry);
db.SaveChanges();
}
Hope this helps.
Application Property in ApiLogentry //The application that made the request.
The line
var routeData = request.GetRouteData();
is always returning null. Is there something I need to do to get this to work? If I do this
routeData = request.GetConfiguration().Routes.GetRouteData(request);
I at least get a non null value but when I go to serialize it I get an error
“Self referencing loop detected for property ‘Configuration’ with type ‘System.Web.Http.HttpConfiguration’
I was able to log simple route data but I mostly use attribute routing. below is my sample code. and I’m getting — Self referencing loop detected for property ‘aggregateRoute’ with type ‘System.Web.Http.Routing.RouteCollectionRoute’. Path ‘Route[0].DataTokens.actions[0].Configuration.Initializer.target0’.”
sample code:
[RoutePrefix(“api/v1/users/me/activities”)]
public class UserActivityController : ApiController
{
[Route(“recent”)]
[HttpGet]
public virtual IHttpActionResult recentActivities(int count)
{
return Ok(“2 activities found”);
}
}
I tried adding JsonSerializerSettings but no luck. any one resolved this self referencing loop issue??
private string SerializeRouteData(IHttpRouteData routeData)
{
return JsonConvert.SerializeObject(routeData, Formatting.Indented,
new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
}
Brian,
Did you find the solution of your error, I’m also facing same problem.
Can you help me ?
Jitendra
I changed the code in SerializeRouteData to the code below and it worked
return JsonConvert.SerializeObject(routeData, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
Your context.User.Identity.Name is always null.
any solution for the username problem context.User.Identity.Name is always null.
I’m using this:
request.GetRequestContext().Principal as ClaimsPrincipal;
in an action filter specifically for logging. It’s working.
I’m overriding this:
public async override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
try
{
var thePrincial = actionExecutedContext.Request.GetRequestContext().Principal.Identity
…more code
}
}
let me know if you need more code.
See: http://stackoverflow.com/questions/18459916/httpcontext-current-items-after-an-async-operation
Add to your appSettings in Web.config:
Hi,
@Brian: Try this;
return JsonConvert.SerializeObject(routeData, Formatting.Indented, new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
@Nadav & Abdallah: You can set identity.Name property with this;
identity.AddClaim(new Claim(ClaimTypes.Name, “UserName”));
hi great post :) how logging items save database
Excellent article, thank you
Good stuff.
My Request contains Credit card information which i dont want to Log Database, for obvious security reasons.
How can we ‘remove’ that piece of info to be logged?
Thanks in advance.
how do you handle an error scenario i.e. lets say I have a global error handler which gets called during an exception and I want to log the request received date time. How do you handle the request received time in the Global error handler and log it to the database.
That work very well. Thanks for this complete and good solution
task.result line #18 will throw an exception when we have unhanded exception.
task state is becoming Faulte
Lovely article. Thanks, Dave!