In ASP.NET Core 1.0, I have a custom implementation of the ILoggerProvider and ILogger interfaces. I would like to be able to access the HttpContext from the Log method.
It seems I need to inject an IHttpContextAccessor into the custom ILogger, but can't find how to do that. The ILoggerProvider object is created at startup, and the CreateLogger method doesn't allow for dependency injection.
Is there a simple way to use dependency injection with ILogger?
Here is an example
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
{
loggerFactory.AddCustomLogger(serviceProvider.GetService<IHttpContextAccessor>());
//
}
Custom Logger:
public class CustomLogProvider : ILoggerProvider
{
private readonly Func<string, LogLevel, bool> _filter;
private readonly IHttpContextAccessor _accessor;
public CustomLogProvider(Func<string, LogLevel, bool> filter, IHttpContextAccessor accessor)
{
_filter = filter;
_accessor = accessor;
}
public ILogger CreateLogger(string categoryName)
{
return new CustomLogger(categoryName, _filter, _accessor);
}
public void Dispose()
{
}
}
public class CustomLogger : ILogger
{
private string _categoryName;
private Func<string, LogLevel, bool> _filter;
private readonly IHttpContextAccessor _accessor;
public CustomLogger(string categoryName, Func<string, LogLevel, bool> filter, IHttpContextAccessor accessor)
{
_categoryName = categoryName;
_filter = filter;
_accessor = accessor;
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return (_filter == null || _filter(_categoryName, logLevel));
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
if (formatter == null)
{
throw new ArgumentNullException(nameof(formatter));
}
var message = formatter(state, exception);
if (string.IsNullOrEmpty(message))
{
return;
}
message = $"{ logLevel }: {message}";
if (exception != null)
{
message += Environment.NewLine + Environment.NewLine + exception.ToString();
}
if(_accessor.HttpContext != null) // you should check HttpContext
{
message += Environment.NewLine + Environment.NewLine + _accessor.HttpContext.Request.Path;
}
// your implementation
}
}
public static class CustomLoggerExtensions
{
public static ILoggerFactory AddCustomLogger(this ILoggerFactory factory, IHttpContextAccessor accessor,
Func<string, LogLevel, bool> filter = null)
{
factory.AddProvider(new CustomLogProvider(filter, accessor));
return factory;
}
}
Although above way works, i would prefer to implement custom IRequestLogger instead of injecting HttpContextAccessor. Implementation is like below(it is not tested):
public interface IRequestLogger<T>
{
void Log(LogLevel logLevel, EventId eventId, string message); // you can change this
}
public class RequestLogger<T> : IRequestLogger<T>
{
private readonly IHttpContextAccessor _accessor;
private readonly ILogger _logger;
public RequestLogger(ILogger<T> logger, IHttpContextAccessor accessor)
{
_accessor = accessor;
_logger = logger;
}
public void Log(LogLevel logLevel, EventId eventId, string message)
{
Func<object, Exception, string> _messageFormatter = (object state, Exception error) =>
{
return state.ToString();
};
_logger.Log(LogLevel.Critical, 0, new FormattedLogValues(message), null, _messageFormatter);
}
}
And simple usage:
public class LogType
{
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton(typeof(IRequestLogger<>), typeof(RequestLogger<>));
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(true);
app.Run(async (context) =>
{
var requestLogger = context.RequestServices.GetService<IRequestLogger<LogType>>();
requestLogger.Log(LogLevel.Critical, 11, "<message>");
//
});
}
I haven't tested this code, but I believe the approach will be something like the following.
In your Startup.cs class, register a HttpContextAccessor by adding the following line to the ConfigureServices method:
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Then, add an additional parameter for the IHttpContextAccessor httpContextAccessor to the Configure method (still inside Startup.cs), something like:
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IHttpContextAccessor httpContextAccessor)
Inside this Configure method, you can add now invoke an extension method on the logger factory to create your custom log provider, something like:
loggerFactory.AddMyCustomProvider(httpContextAccessor);
where the extension method (that you need to create) will be something like:
public static class MyCustomLoggerExtensions
{
public static ILoggerFactory AddMyCustomProvider(
this ILoggerFactory factory,
IHttpContextAccessor httpContextAccessor)
{
factory.AddProvider(new MyCustomLoggerProvider(httpContextAccessor));
return factory;
}
}
Related
public class MyApplication extends Application<MyConfiguration> {
final static Logger LOG = Logger.getLogger(MyApplication.class);
public static void main(final String[] args) throws Exception {
new MyApplication().run(args);
}
#Override
public String getName() {
return "PFed";
}
#Override
public void initialize(final Bootstrap<MyConfiguration> bootstrap) {
// TODO: application initialization
bootstrap.addBundle(new DBIExceptionsBundle());
}
#Override
public void run(final MyConfiguration configuration,
final Environment environment) {
// TODO: implement application
final DBIFactory factory = new DBIFactory();
final DBI jdbi = factory.build(environment, configuration.getDataSourceFactory(), "postgresql");
UserDAO userDAO = jdbi.onDemand(UserDAO.class);
userDAO.findNameById(1);
UserResource userResource = new UserResource(new UserService(userDAO));
environment.jersey().register(userResource);
}
I get the the following error at findNameById.
java.lang.NoSuchMethodError: java.lang.Object.findNameById(I)Ljava/lang/String;
at org.skife.jdbi.v2.sqlobject.CloseInternalDoNotUseThisClass$$EnhancerByCGLIB$$a0e63670.CGLIB$findNameById$5()
}
public interface UserDAO {
#SqlQuery("select userId from user where id = :email")
User isEmailAndUsernameUnique(#Bind("email") String email);
#SqlQuery("select name from something where id = :id")
String findNameById(#Bind("id") int id);
}
I have a logger in my Startup configure method:
public void Configure(
IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory ) {
loggerFactory.AddConsole( Configuration.GetSection( "Logging" ) );
loggerFactory.AddDebug();
loggerFactory.AddFile(#"C:\Some\Path\portal-{Date}.txt");
I have created a BenchmarkFilter Class that implements IActionFilter. It will run some performance benchmarks on Action Methods when enabled. I can pass this a logger via DI but i need this to log to a new file location, in a csv format. How would i go about this? My Class so far:
public class BenchmarkFilter : IActionFilter
{
private readonly ILogger _logger;
private readonly bool _isBenchmarkOn;
private string _benchmarkFilePath;
private Stopwatch _stopWatch = new Stopwatch();
public BenchmarkFilter(
ILoggerFactory loggerFactory, IOptions<AppSettings> appSettings)
{
_isBenchmarkOn = appSettings.Value.EnableBenchmarkLogging;
_benchmarkFilePath = appSettings.Value.BenchmarkFilePath;
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (_isBenchmarkOn)
{
_stopWatch = Stopwatch.StartNew();
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (_stopWatch.IsRunning)
{
_stopWatch.Stop();
var seconds = _stopWatch.ElapsedMilliseconds;
//logging to do
}
}
}
I have an ASP.NET Core app with various controllers that inherit from BaseController. I need to implement some basic benchmarking tests, using Stopwatch, it will just start at the beginning of an action method and finish at the end. I can turn this on and off via appsettings.json. There is an ILogger factory in Startup.cs:
public void Configure ( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory ) {
loggerFactory.AddConsole( Configuration.GetSection( "Logging" ) );
loggerFactory.AddDebug();
loggerFactory.AddFile(#"C:\Logs\Portal\portal-{Date}.txt");
I have added ILogger to my BaseController (below), I am hoping this will be supplied via DI. Given the above, can I use this to log my benchmark results to file in a different location to the startup file? I would like a .csv file with certain columns which i can populate with results. Is this possible?
public class BaseController : Controller {
protected AppSettings AppSettings;
protected IMapper Mapper;
protected IPortalApiService PortalApiService;
protected ILogger Logger;
protected UserManager<ApplicationUser> UserManager;
private static Stopwatch _stopWatch = new Stopwatch();
private static long _seconds;
public BaseController ( IMapper mapper,
IOptions<AppSettings> appSettings,
UserManager<ApplicationUser> userManager,
IPortalApiService PortalApiService,
ILogger logger) {
Mapper = mapper;
AppSettings = appSettings.Value;
UserManager = userManager;
PortalApiService = PortalApiService;
Logger = logger;
}
public BaseController ( IMapper mapper,
IOptions<AppSettings> appSettings,
UserManager<ApplicationUser> userManager,
ILogger logger) {
Mapper = mapper;
AppSettings = appSettings.Value;
UserManager = userManager;
Logger = logger;
}
protected Task<ApplicationUser> GetCurrentUserAsync () {
return UserManager.GetUserAsync( HttpContext.User );
}
public void StartBenchmark()
{
if (AppSettings.EnableBenchmarkLogging)
{
_stopWatch = Stopwatch.StartNew();
}
}
public void EndBenchmark()
{
if (_stopWatch.IsRunning)
{
_stopWatch.Stop();
_seconds = _stopWatch.ElapsedMilliseconds;
//logging to do
}
}
}
It is not a good idea to use a BaseController in MVC. There are better ways to implement crosscutting concerns. In this particular case, you could use a global filter.
public class BenchmarkFilter : IActionFilter
{
private readonly ILogger Logger;
// DON'T DECLARE STATIC!!
private Stopwatch _stopWatch = new Stopwatch();
public BenchmarkFilter(ILogger logger)
{
_logger = logger ??
throw new ArgumentNullException(nameof(logger));
}
public void OnActionExecuting(ActionExecutingContext context)
{
_stopWatch = Stopwatch.StartNew();
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (_stopWatch.IsRunning)
{
_stopWatch.Stop();
var seconds = _stopWatch.ElapsedMilliseconds;
//logging to do
}
}
}
This allows you to inject services via DI through the constructor without having to add those parameters to every controller that subclasses a common BaseController, separating the concern of benchmarking from the controller entirely.
Usage
In Startup.cs, add the filter in the ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(typeof(BenchmarkFilter)); // runs on every action method call
});
services.AddScoped<BenchmarkFilter>();
// ....
}
What's wrong with my binding?
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IBaseRepository<User>>().To<UserRepository>();
}
My IBaseRepository
public interface IBaseRepository<TEntity> where TEntity : class
{
void Commit();
void Delete(TEntity entity);
void Delete(object id);
void Dispose();
IQueryable<TEntity> GetAll();
IQueryable<TEntity> GetAll(object filter);
TEntity GetById(object id);
TEntity GetFullObject(object id);
IQueryable<TEntity> GetPaged(int top = 20, int skip = 0, object orderBy = null, object filter = null);
void Insert(TEntity entity);
void Update(TEntity entity);
}
And my UserRepository
public class UserRepository : BaseRepository<User>
{
public UserRepository(DataContext context) : base(context)
{
if(context == null)
throw new ArgumentNullException();
}
}
This is the error I recive
Error activating IBaseRepository{Role}
No matching bindings are available, and the type is not self-bindable.
Activation path:
2) Injection of dependency IBaseRepository{Role} into parameter roles of constructor of type UsersController
1) Request for UsersController
Well your exception message clearly states that the binding for IBaseRepository<Role> is missing. Role not User.
So adding
kernel.Bind<IBaseRepository<Role>>().To<RoleRepository>();
should help! ;-)
I have implemented a Windsor for my controller like described here
http://sitecore-estate.nl/wp/2014/12/sitecore-mvc-dependency-injection-using-castle-windsor/
and set up my WebApi like here https://kb.sitecore.net/en/Articles/2015/07/15/11/30/700677.aspx
for regular controller it is works good. But I wonder how to use it for ApiController. Next way is not working
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<IHttpController>().LifestyleTransient());
}
Yes answer for this question would be like use IHttpControllerActivator:
public class WindsorHttpControllerFactory : IHttpControllerActivator
{
private readonly IWindsorContainer _container;
public WindsorHttpControllerFactory(IWindsorContainer container)
{
_container = container;
}
public IHttpController Create(HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
var controller =
(IHttpController)_container.Resolve(controllerType);
request.RegisterForDispose(
new Release(
() => _container.Release(controller)));
return controller;
}
class Release : IDisposable
{
readonly Action _release;
public Release(Action release)
{
_release = release;
}
public void Dispose()
{
_release();
}
}
}
public class WebApiInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Types.FromThisAssembly().BasedOn<IHttpController>().LifestyleTransient());
}
}
public class InitializeWindsorControllerFactory
{
public virtual void Process(PipelineArgs args)
{
SetupControllerFactory(args);
}
public virtual void SetupControllerFactory(PipelineArgs args)
{
IWindsorContainer container = new WindsorContainer().Install(FromAssembly.This());
IControllerFactory controllerFactory = new WindsorControllerFactory(container.Kernel);
SitecoreControllerFactory sitecoreControllerFactory = new SitecoreControllerFactory(controllerFactory);
System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory(sitecoreControllerFactory);
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator),new WindsorHttpControllerFactory(container));
}
}
and config settings for
<pipelines>
<initialize>
<processor type="My.IoC.InitializeWindsorControllerFactory, My.IoC" patch:instead="*[type='Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory, Sitecore.Mvc']"/>
</initialize>
</pipelines>