I am trying to add logging to my app using Web Api 2 and Owin, so I started using Microsoft Owin Logging, which requires an ILogger and ILoggerFactory, that has been implemented and it works great when I need to log anything inside the STARTUP method or any of the Owin Middleware components.
For example, when I am in the Startup method I can create the logger using:
public void Configuration(IAppBuilder app)
{
// Creates configuration
var configuration = new HttpConfiguration();
// Configure WebApi Settings
WebApiConfig.Register(configuration);
app.SetLoggerFactory(new OwinLog4NetLoggerFactory("Default"));
var logger = app.CreateLogger<Startup>();
logger.WriteInformation("test log");
// Enabled WebApi in OWIN
app.UseWebApi(configuration);
}
Where "OwinLog4NetLoggerFactory" is my custom ILoggerFactory implementation.
So far, so good... but... How can I create the logger when I am in the actual web api action method?... I tried accessing the Request.GetOwinEnvironment() and the logger factory is not in the dictionary.
For example:
public class AccountController : ApiController
{
public int Get(int id)
{
// Create logger here
return id + 1;
}
}
I know I can create a static class with a reference to the Logger Factory or even Injection to add the logger to the api controller, but that seems too complicated for something that should be already there.
Any ideas would be appreciated.
I'd recommend writing your middleware so that you can handle the logging outside of the controller:
public class LoggingMiddleware : OwinMiddleware
{
public LoggingMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
//handle request logging
await Next.Invoke(context);
//handle response logging
}
}
Then in Startup class:
public class Startup
{
// ReSharper disable once UnusedMember.Global
public void Configuration(IAppBuilder appBuilder)
{
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
appBuilder.Use<LoggingMiddleware>();
appBuilder.UseWebApi(config);
}
}
The request would then come in, hit the request logging code in the LoggingMiddleware, hit the controller code and then response would be logged on the LoggingMiddleware on the way back.
However, if all you are wanting to do is send an object through from middleware to the controller you can use context.Set("loggingObject", loggingObject); in the middleware and then
var loggingObject = Request.GetOwinContext().Get<LoggerClass>("loggingObject"); in the controller.
instead of adding logging code in every method, I create a MessageLoggingHandler that can be registered in Global.asax.cs once, and it then logs every Request and Response.
Here is the code that I use, you can modify as per your requirements:
First Create a MessageHandler class that inherits from DelegationHandler:
public abstract class MessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var corrId = Guid.NewGuid();
var requestMethod = request.Method.Method.ToString();
var requestUri = request.RequestUri.ToString();
var ipAddress = request.GetOwinContext().Request.RemoteIpAddress;
var requestMessage = await request.Content.ReadAsByteArrayAsync();
await LogMessageAsync(corrId, requestUri, ipAddress, "Request", requestMethod, request.Headers.ToString(), requestMessage, string.Empty);
var response = await base.SendAsync(request, cancellationToken);
var responseMessage = await response.Content.ReadAsByteArrayAsync();
await LogMessageAsync(corrId, requestUri, ipAddress, "Response", requestMethod, response.Headers.ToString(), responseMessage, ((int)response.StatusCode).ToString() + "-" + response.ReasonPhrase);
return response;
}
protected abstract Task LogMessageAsync(Guid CorrelationId, string APIUrl, string ClientIPAddress, string RequestResponse, string HttpMethod, string HttpHeaders, byte[] HttpMessage, string HttpStatusCode);
}
public class MessageLoggingHandler : MessageHandler
{
protected override async Task LogMessageAsync(Guid CorrelationId, string APIUrl, string ClientIPAddress, string RequestResponse, string HttpMethod, string HttpHeaders, byte[] HttpMessage, string HttpStatusCode)
{
// Create logger here
//Do your logging here
}
}
Then in your Global.asax.cs, you need to register the above created MessageLoggingHandler:
GlobalConfiguration.Configuration.MessageHandlers.Add(new MessageLoggingHandler());
Just be aware, this will log every request and response, will full message body. This can take a lot of space very quickly (depending on your API's usage). So you may need to tweak this (for example - keep records for a month or so, or ignore 200-OK responses etc)
I would recommend using the Common.Logging library in your applications, available on NuGet. Common.Logging gives you a common interface for using your preferred logging solution. It solves a lot of issues like yours. Here is an example using Common.Logging with NLog:
In your controller, you would access it like so:
public class MyController : ApiController
{
private static readonly ILog Log = LogManager.GetLogger<MyController>();
public async Task<IHttpActionResult> Get([FromUri] int id)
{
Log.Debug("Called Get with id " + id.ToString());
return Ok();
}
}
Pick up the latest Common.Logging.NLog package on NuGet (as of this writing, it should be Common.Logging.NLog41). Then in your web.config, you would configure Common.Logging to use your NLog configuration:
<common>
<logging>
<factoryAdapter type="Common.Logging.NLog.NLogLoggerFactoryAdapter, Common.Logging.NLog41">
<arg key="configType" value="FILE" />
<arg key="configFile" value="~/NLog.config" />
</factoryAdapter>
</logging>
</common>
Here are some additional links:
https://github.com/net-commons/common-logging
https://cmatskas.com/an-introduction-to-common-logging-api-2/
Related
I've got an authorization handler that works when succeeding, but fails when... failing.
Here it is:
public class HeaderHandler : AuthorizationHandler<HeaderRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected async override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
HeaderRequirement requirement)
{
var Request = _httpContextAccessor.HttpContext.Request;
try
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
context.Succeed(requirement);
}
catch (Exception ex)
{
context.Fail();
}
}
}
And it's configured this way:
services.AddAuthorization(options =>
{
options.AddPolicy(nameof(Policy.AuthorizationHeader), //an enum of mine...
policy => policy.Requirements.Add(new HeaderRequirement()));
});
And here's how I use it in a controller:
[Authorize(Policy = nameof(Policy.AuthorizationHeader))]
public async Task<IActionResult> CreateSpace([FromQuery] CreateSpaceViewModel viewModel)
{
//....
}
This works fine when succeeding, I'm reaching the code above. But when failing in the handler I get:
System.InvalidOperationException: No authenticationScheme was
specified, and there was no DefaultChallengeScheme found. The default
schemes can be set using either AddAuthentication(string
defaultScheme) or AddAuthentication(Action
configureOptions).
It's as if when failing, I hadn't setup the handler in Startup.cs. It mentions authentication even though I'm not using that...
From what I can read, Authentication is to let user in the application, and Authorization is restricting access to certain resources for users that have been let in.
But I'm coding an API, I don't care about the "application" part. I just want to decorate certain action with attributes... Like in .net 4.7.
Any idea ?
I will use an ActionFilter instead. Setting the response's content from an authorization handler isn't even supported yet so no point in getting it to work properly.
I am using Refit and would like to set OPTIONAL dynamic headers for some methods. For example if the user is logged in, I want to have header "UserId" and "AuthenticationToken", otherwise do NOT set the headers
[Post("/search")]
Task<SearchResponse> Search([Body]SearchRequest request, [Header("UserId")] string userId,[Header("AuthorizationToken")] string token);
Not sure if I pass null value to userId and token, the two headers will have null value or just be skipped (not included in the header)?
Thanks.
Using refit you can implement DelegatingHandler and then register that to do whatever you need to the http request before it is sent on. Here is adding an origin header to each request. Refit interface does not need to worry about it.
public class AddOriginHeaderToRequest : DelegatingHandler
{
private const string ServiceNameSettingLocation = "AppConfig:Logging:ServiceName";
private readonly IHttpContextAccessor httpContextAccessor;
private readonly IConfiguration configuration;
public AddOriginHeaderToRequest(IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
{
this.httpContextAccessor = httpContextAccessor;
this.configuration = configuration;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var origin = this.configuration[AddOriginHeaderToRequest.SomethingThatShouldBeDefined];
if (!(request.Headers.Contains("origin") || request.Headers.Contains("Origin")) && origin != null)
{
request.Headers.Add("origin", origin);
}
return await base.SendAsync(request, cancellationToken);
}
}
Then register it like this:
services.AddTransient<AddOriginHeaderToRequest>();
Then the refit client can be registered like this (this is a redacted version of one of our nuget packages so will hopefully give an idea of how it works):
public static IHttpClientBuilder AddHttpClientWithDefaultHandlers(
this IServiceCollection services,
string name,
Action<HttpClient> configureClient)
{
return services.AddHttpClient(name, configureClient)
.AddHttpMessageHandler<AddOriginHeaderToRequest>();
}
Then in our service we register our refit handler like this:
services.AddHttpClientWithRefitAndDefaultHandlers<ImyHttpClient>(
"myHttpClient",
c =>
{
c.BaseAddress = new Uri(appSettings.Value.AppConfig.SomeUrl);
});
This can be simplified but we have a number of different handlers that massage our http requests in a standard way.
I hope that gives you a pointer to how it could work.
I would like to increase / improve my logging.
So far I had in each controller action code like
public asyc Task<IActionResult> Action1(int id, [FromBody] RequestModel request) {
string log = $"{nameof(Action1)}(id: {id}, request: {request?.ToJson()})";
_logger.LogInformation(log);
The main purpose was to see whats actually reaching the controller action.
I removed it since it cluttered the code heavily (e.g. for methods with a lot of paramters). But now I am unhappy with the result that the logs do not show the information any more (and I needed them to investigate some unexplainable bugs).
Is there a way to hook up into the model binder result (e.g. via a service filter) to log the model binder result?
Works like charm: thanks to Shahzad Hassan
public class MethodCallParameterLogger : IAsyncActionFilter
{
public ILoggerFactory LoggerFactory { get; set; }
public MethodCallParameterLogger(ILoggerFactory loggerFactory)
{
LoggerFactory = loggerFactory;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
LoggerFactory
// the display name contains the controller name space, controller name, method name and the postfix " (ActionLogger)"
.CreateLogger(context.ActionDescriptor.DisplayName.Split(" ")[0])
// MIND THAT THIS LOGS EVEN SENSITIVE DATA (e.g. credentials) !!!
.LogInformation(JsonConvert.SerializeObject(context.ActionArguments));
var resultContext = await next();
}
}
I think you can use an ActionFilter instead. ActionFilters are executed after the model binding, so you can retrieve the parameters from the ActionExecutingContext. You can override the OnActionExecuting method and log whatever is required:
public class LogParamsFilter : ActionFilterAttribute
{
private readonly ILogger<LogsParamsFilter> _logger;
public LogParamsFilter (ILogger<LogsParamsFilter> logger)
{
_logger = logger;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var id = (int)context.ActionArguments["id"];
var request = context.ActionArguments["request"] as RequestModel;
var action = context.ActionDescriptor.DisplayName;
string log = $"{action)}(id: {id}, request: {request?.ToJson()})";
_logger.LogInformation(log);
base.OnActionExecuting(context);
}
}
You would need to use it as TypeFilter on the controller action so that its dependencies i.e. ILogger is resolved via DI.
[TypeFilter(typeof(LogParamsFilter))]
public asyc Task<IActionResult> Action1(int id, [FromBody] RequestModel request)
{
...
}
Or you can register it globally in the startup for all controllers:
services.AddMvc(options => options
.Filters.Add(new TypeFilterAttribute(typeof(LogParamsFilter))));
In order to use it as a generic filter for all controller actions, iterate through the context.ActionArguments.Keys property and log the value for each key. You would need to do some type checkings and call .ToJson() if the type of the ActionArgument is RequestModel.
I hope that helps.
I am running some load tests on my Web API application, and am trying to find a way to identify each request as it comes in to the methods OnActionExecuted and OnActionExecuting
My question is, within the objects HttpActionExecutedContext and HttpActionContext is there some kind of unique identifier I can obtain to identify an individual request.
I have tried adding a unix timestamp to my query string, but the requests are often coming in at the same time so this does not help.
I am hoping these objects have some kind of property?
You could add a scoped class with a generated identifier using Dependency Injection.
A scoped class is created once per request.
public class IdentifiedScope
{
public Guid Id { get; } = Guid.NewGuid();
}
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IdentifiedScope>();
}
// Controller
public MyController(IdentifiedScope identifiedScope)
{
this.identifiedScope = identifiedScope;
}
// Usage in an ActionFilter
public override async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
var identifiedScope =
context.HttpContext.RequestServices.GetService<IdentifiedScope>();
}
If you want to check requests execution time you could use serilog
Setup this code on your statup.cs
Log.Logger = new LoggerConfiguration()
.Enrich.With<HttpRequestIdEnricher>()
.Enrich.With<HttpRequestNumberEnricher>()
.Enrich.With<HttpSessionIdEnricher>()
.Enrich.With<HttpRequestTraceIdEnricher>()
.WriteTo.Debug()
.CreateLogger();
HttpRequestIdEnricher
HttpRequestNumberEnricher
HttpSessionIdEnricher
HttpRequestTraceIdEnricher
These might be separate nuget packages just seraching for htem if they
are don't come with Serilog
Install Serilog
and Serilog.Sinks.Debug
The for each request you should see the result in the console
[05:06:14 INF] HTTP GET /api/v1/blablabla/blablablaresponded 401 in
7530ms
Hope it helps
I've created a basic webAPI project (blank web project with webAPI checked) and added the owin nuget packages to the project.
Microsoft.AspNet.WebApi.Owin
Microsoft.Owin.Host.SystemWeb
Owin
I've then created a Logging class, and hooked it up via startup
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
Debug.WriteLine("Startup Called");
var config = new HttpConfiguration();
WebApiConfig.Register(config);
appBuilder.UseWebApi(config);
appBuilder.Use(typeof(LoggingMiddleware));
}
}
public class LoggingMiddleware
{
private AppFunc Next { get; set; }
public LoggingMiddleware(AppFunc next)
{
Next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
Debug.WriteLine("Begin Request");
await Next.Invoke(environment);
Debug.WriteLine("End Request");
}
}
When I run the project, and the default page opens, I see the Begin/End requests called (twice, as it happens, not sure why that is).
However, if I try to call an /api route (such as `/api/ping/'), the request completes successfully, but I do not see the Begin/End request states in the log.
What am I missing with this?
Owin executes the middleware items in the order that they are registered, ending at the call to the controller (appBuilder.UseWebApi(config)) which does not appear to call next.Invoke(). Given that the code in the question has the Logging Middleware class registered after the UseWebApi call, this causes it to never be called for API requests.
Changing the code to:
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
//.....
//This must be registered first
appBuilder.Use(typeof(LoggingMiddleware));
//Register this last
appBuilder.UseWebApi(config);
}
}
resolves the issue.