Essentially, there are two different ways I get ILogger instances. One works perfectly fine, the other doesn't.
I have an Azure Function like this:
class AzureFunctionClass {
private readonly ISomeClass _someclass;
public AzureFunctionClass(ISomeClass someClass){
_someclass = someClass;
}
public Task<IActionResult> AzureFunction(ILogger log){
log.LogInformation("This works, I see this message when run");
_someclass.ExecuteMethod();
}
}
Another class, not containing Azure functions, like this:
class SomeClass : ISomeClass {
private readonly ILogger<SomeClass> _log;
public SomeClass(ILogger log){
_log = log;
}
public void ExecuteMethod(){
_log.LogInformation("This doesn't crash so _log isn't null, but it
doesn't write anything");
}
}
Startup.cs:
class Startup : IWebJobsStartup {
public void Configure(IWebJobsBuilder builder){
builder.Services.AddScoped<ISomeClass, SomeClass>();
builder.Services.AddTransient(typeof(ILogger<>), typeof(Logger<>));
builder.Services.AddScoped<ILogger<SomeClass>, Logger<SomeClass>>();
}
}
And no, I'm afraid that AzureFunctionClass cannot just pass its ILogger instance to ISomeClass as a parameter.
I've also looked everywhere for log files, such as in Azure Storage Explorer, to see if it's possibly just not writing to the Azure Portal console. Every log file I found had logs for the working case described above, and none of them had logs for the other case.
Current syntax shown has some issues with this injected dependencies.
class SomeClass : ISomeClass {
private readonly ILogger _log;
public SomeClass(ILogger<SomeClass> log) {
_log = log;
}
public void ExecuteMethod() {
_log.LogInformation("This doesn't crash so _log isn't null, but it doesn't write anything");
}
}
Second issue is that logging is added by default and your manually added settings are overriding the default setup.
class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
builder.Services.AddScoped<AzureFunctionClass>();
builder.Services.AddScoped<ISomeClass, SomeClass>();
//...
}
}
Technically all you needed to add was your function class and its dependencies.
Reference Use dependency injection in .NET Azure Functions
At present, the function runtime has a bug due to which it filters out any log that is created with a category that doesn't start with string Function..
See these GitHub issues:
#4425 - ILogger is not injected when using new DI functionality
#4345 - Remove filters for ILoggers created by customer DI
The logger injected in the function method is done by the function runtime which creates the logger with category set to Function.<FunctionName>.User. So this gets logged properly. But the logger that is injected into the constructor is done by the asp.net core DI framework, which sets the category name for the logger as Type.FullName (type in your example case is SomeClass). Because it's fullname doesn't start with Function, the lines logged with this category are filtered out.
There are two ways to workaround this.
Option 1: Change host.json to not filter logs from your namespace
{
"version": "2.0",
"logging": {
"logLevel": {
"<YourNameSpace>": "Information"
}
}
}
Option 2: Inject ILoggerFactory in your ctor, and create a logger with a category that won't get filtered
class SomeClass : ISomeClass {
private readonly ILogger _log;
public SomeClass(ILoggerFactory loggerFactory){ // Note that we inject ILoggerFactory
this._log = loggerFactory.CreateLogger(
LogCategories.CreateFunctionUserCategory(this.GetType().FullName)); // Must use CreateFunctionUserCategory to create the log category name otherwise the log gets filtered out.
}
public void ExecuteMethod(){
_log.LogInformation("This should get logged correctly.");
}
}
Note that, ILogger is already registered into DI framework by the function runtime (as mentioned in NKosi's answer), so those lines can be removed.
Related
I have an older .NET 4.8 project that needs to use Airbrake. The project is using Unity for its IoC container, implementing the standard Repository Service pattern.
There's very little in the way of ASP.NET examples.
I am looking to do something like this:
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType(typeof(ILogger<>), typeof(ILogger<>));
container.RegisterType<IMyService, MyService();
}
public class MyController
{
private readonly ILogger<MyController> _logger;
private readonly IMyService _myService;
public MyController(ILogger<MyController> logger, IMyService _myService)
{
_logger = logger;
_myService = myService;
}
public MyMethod()
{
try
{
var x = _myService.DoThis();
}
catch (Exception ex)
{
_logger.LogError(e, e.Message);
}
}
}
I believe I need to either somehow register Airbrake with ILogger or perhaps create my own logging service.
public class Logging : ILogging
{
public void LogError(Exception e, string message)
{
var airbrake = new AirbrakeNotifier(new AirbrakeConfig
{
ProjectId = // pulled from web.config somehow
ProjectKey = // pulled from web.config somehow
});
var notice = airbrake.BuildNotice(ex);
airbrake.NotifyAsync(notice).Result;
}
}
I have tried using this as starting point: https://github.com/airbrake/sharpbrake/blob/master/docs/asp-net-http-module.md
This is excellent, but I need to extend it somehow to be able to use it within my services and not just the .Web project.
I know there's the ASP.NET module that will automatically capture the errors but I am wanting to manually log when I see fit, and avoid having to call the airbrake client every time I want to log an error.
Is there a way to do this or am I completely misunderstanding how this should be working?
You don't actually need to wire it up as part of the .NET ILogger. I am sure there is a way (probably via OWIN) but you nothing stops you from writing a basic logging service as you would any other service and using that via bog standard DI. The answer was pretty much in the question to begin with.
TLDR: Is it possible to modify the IServiceProvider after the Startup has ran?
I am running dll's (which implement a interface of me) during run-time. Therefore there's a file listener background job, which waits till the plugin-dll is dropped. Now I want to register classes of this dll to the dependency-injection system. Therefore I added IServiceCollection as a Singleton to the DI inside ConfigureServices to use inside another method.
In therefore I created a test-project and just tried to modify the ServiceCollection in the controller, because it was easier than stripping the background job down.
services.AddSingleton<IServiceCollection>(services);
So I added IServiceCollection to my controller to check if I can add a class to the DI after the Startup class has ran.
[Route("api/v1/test")]
public class TestController : Microsoft.AspNetCore.Mvc.Controller
{
private readonly IServiceCollection _services;
public TestController(IServiceCollection services)
{
_services = services;
var myInterface = HttpContext.RequestServices.GetService<IMyInterface>();
if (myInterface == null)
{
//check if dll exist and load it
//....
var implementation = new ForeignClassFromExternalDll();
_services.AddSingleton<IMyInterface>(implementation);
}
}
[HttpGet]
public IActionResult Test()
{
var myInterface = HttpContext.RequestServices.GetService<IMyInterface>();
return Json(myInterface.DoSomething());
}
}
public interface IMyInterface { /* ... */ }
public class ForeignClassFromExternalDll : IMyInterface { /* ... */ }
The Service was successfully added to the IServiceCollection, but the change is not persisted yet to HttpContext.RequestServices even after multiple calls the service count increases each time but I don't get the reference by the IServiceProvider.
Now my question is: Is that possible to achieve and yes how. Or should I rather not do that?
Is it possible to modify the IServiceProvider after the Startup has ran?
Short answer: No.
Once IServiceCollection.BuildServiceProvider() has been invoked, any changes to the collection has no effect on the built provider.
Use a factory delegate to defer the loading of the external implementation but this has to be done at start up like the rest of registration.
services.AddSingleton<IMyInterface>(_ => {
//check if dll exist and load it
//....
var implementation = new ForeignClassFromExternalDll();
return implementation;
});
You can now explicitly inject your interface into the controller constructor
private readonly IMyInterface myInterface;
public MyController(IMyInterface myInterface) {
this.myInterface = myInterface;
}
[HttpGet]
public IActionResult MyAction() {
return Json(myInterface.DoSomething());
}
and the load dll logic will be invoked when that interface is being resolved as the controller is resolved.
Given several types of class constructor injection, i.e.;
public class DataService :IDataService
{
public DataService(ILogger logger) { ... }
}
and,
public class Logger
{
public Logger(IDataService service) { ... }
}
or, should I do this instead;
public class DataService : IDataService, ILogger, IDisposable
{
public DataService() { ... }
}
However, I actually don't like to do this on every repository classes or other classes that needs data services and logging at the same time;
public class SomeRepository : IRepostiory
{
public SomeRepository (IDataService service, ILogger logger) { ... }
}
I'm fine with this model;
public interface IRepository : ILogger { ... }
or,
public interface IDataService : ILogger { ... }
Which one is preferred as best practice design? Also, how do we determine if we would like to log the data service process, and at the same time we also would like to log other components that injected ILogger services or using repository?
This is a tongue in cheek answer, but there is no "right" way to implement ILogger. You should probably use the existing framework to construct an ILogger, or hand it over to NLog, log4net, or Serilog. You shouldn't directly implement ILogger unless you plan to build your own logging framework. You should use the logging libary's extension methods to wireup the ILogger Here is an article about this.
Here is a minimal example of how to get console logging up and running:
var hostBuilder = Host.CreateDefaultBuilder().
ConfigureLogging((builderContext, loggingBuilder) =>
{
loggingBuilder.AddConsole((options) =>
{
//This displays arguments from the scope
options.IncludeScopes = true;
});
});
var host = hostBuilder.Build();
var logger = host.Services.GetRequiredService<ILogger<LogTest>>();
//This specifies that every time a log message is logged, the correlation id will be logged as part of it
using (logger.BeginScope("Correlation ID: {correlationID}", 123))
{
logger.LogInformation("Test");
logger.LogInformation("Test2");
}
I'll like to add that the BeginScope method could take any kind of object, not only string. You may pass a more complex object that could represent better the context/scope, like User Id, HostName, CorrelationId, Tennant Id ...
In my case where a created an external Api Logger service, and thus create my own ILogger where BeginScope with those values make more sense.
public IDisposable BeginScope<TState>(TState state)
{
_scopeContextManager = state as ScopeContextManager;
return default!;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
// use _scopeContextManager
}
I struggled for a long time before figuring this out.
I need to programatically create loggers for each controller (let's say: Documents, Customers and Warehouses, to log some operations inside them) and for a filter above controllers (to log parameters sent to each action). Each controller logic should be logged to another file ex. Documents.csv, Customers.csv and Warehouses.csv.
Currently I have a wrapper for nlog logger. The wrappers are injected to controllers instances via constructor and then nlog logger inside in initialized via LogManager. Initialization creates new target and loglevel and assigns it to LogManager.Configuration.
The problem is that after a few requests each loggers logs to each file, so ex. logic from Customers is logged to Customers.csv and vice versa, same thing with logger from Filters.
How should I then configure separate blog loggers with different target for each controller and separate for filter?
I prefer programmatic configuration than via xms etc.
Adapter as requested:
public class Logger : ILogger
{
private NLog.Logger _logger;
string _deployVersion;
public Logger(string deploymentVersion)
{
_deployVersion = deploymentVersion;
}
public void Init(string loggerName)
{
_logger = NLog.LogManager.GetLogger(loggerName);
}
public void Init(string loggerName, string header, string layout, Level level)
{
LoggingConfiguration config;
if (NLog.LogManager.Configuration == null)
{
config = new LoggingConfiguration();
}
else
{
config = LogManager.Configuration;
}
if (config.FindTargetByName(loggerName) == null)
{
var target = CreateTarget(loggerName, header, layout, level); //configures target: path, archives and layout
config.AddTarget(loggerName, target);
var logLevel = GetLogLevel(level); //translates Level enum to NLog level
var rule1 = new LoggingRule("*", logLevel, target);
config.LoggingRules.Add(rule1);
LogManager.Configuration = config;
}
_logger = LogManager.GetLogger(loggerName);
}
...
//Info, Debug etc. methods
Great, you've used an interface here already which makes a pattern for this easier to produce.
Your main problem here is responsibility, as your controller code will (I assume) call Init(string loggerName, string header, string layout, Level level). This is probably not best practice as you may have to repeat this code a lot and the controller probably shouldn't care about where the log goes or what it's formatted like...just the fact that a log is used.
Instead of injecting these directly, use a Factory to obtain the correct logger type. For example, CustomerLogger : ILogger:
public class LogFactory
{
public ILogger Get<T>() where T : ILogger
{
ILogger instance = null;
if (typeof(T) == typeof(CustomerLogger))
{
instance = (T)Activator.CreateInstance(typeof(T), "CustomerLogger", "Header", "Layout", Level.Verbose);
}
else if (...)
{
...etc
}
return instance;
}
}
This means you can leave the management and creation of ILogger concretes up to the Factory.
Notice I'm passing in string loggerName, string header, string layout, Level level so you can call Init in the CustomerLogger constructor. You then have different implementations of ILogger to suit your needs.
You can then either make the LogFactory use an interface and inject that into your controller, or simply new up the factory in the controller, either way you call LogFactory.Get<T>(). Option A would be better for unit testing purposes.
Hope this helps!
We have a self-hosted WebAPI application using OAuthBearerAuthenticationHandler and I see it logs exception using (ILogger)_logger when an exception is thrown. Since we are using our own logging framework, how can I supply my own ILogger to OAuthBearerAuthenticationHandler?
The solution for us was to set our won 'LoggerFactory'.
IAppBuilder appBuilder ............;
..........
appBuilder.SetLoggerFactory(new OwinLoggerFactory());
And for 'OwinLoggerFactory':
public class OwinLoggerFactory : ILoggerFactory
{
private readonly ILoggerFactory _baseLoggerFactory;
public OwinLoggerFactory()
{
_baseLoggerFactory=new DiagnosticsLoggerFactory();
}
public ILogger Create(string name)
{
//create your own OwinLogger class that implements 'ILogger'
// inside your own OwinLogger class, you may then hook up to any logging Fx you like.
}
}