Add Microsoft.Extensions.Logging to .NETStandard project without assuming Dependency Injection available - c#

I want to support logging in my .NETStandard project that will be consumed by a .NET Core Console or Web Client. However I don't want to presume the client uses a constructor that requires a ILogger dependency in the constructor of the classes I wish to log from.
If the logger does not exist, I basically don't want to fail because of this.
So my question is how can I reference ILogger in my code without passing it to the constructor?
using Microsoft.Extensions.Logging;
namespace MyApp
{
public class MyClass
{
//slf4net logger implementation
private static readonly slf4net.ILogger _slf4netLogger = slf4net.LoggerFactory.GetLogger(typeof(MyClass));
//Microsoft.Extensions.Logging???
private static readonly ILogger<MyClass> _logger = ???
public MyClass()
{
//Constructor empty
}
public void MyMethod()
{
//slf4net logger works like this
_slf4netLogger.Trace("This got logged");
//this won't work because the logger was never passed from the constructor
_logger.LogInformation("A message for the log if one is listening");
}
}
}
references:
https://github.com/ef-labs/slf4net
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging?tabs=aspnetcore2x
It seems like I'm not alone with my frustration here
Accessing the Logging API Outside of a MVC Controller
OK, so this is where the new logging API quickly becomes a nightmare.
- https://stackify.com/net-core-loggerfactory-use-correctly/

Related

Azure Durable Functions - where's the HostBuilder equivalent?

I'm using an Azure Durable Function to orchestrate other functions, currently contained in the same project. I want to configure services and logging for those orchestrated functions. How can I do that?
Here is some more detail:
In a "normal" Azure Function I have a Program.cs and a Main method with the following code that sets up the environment for the function execution:
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureLogging(loggingBuilder => { loggingBuilder.SetMinimumLevel(LogLevel.Trace); })... etc. pp.
Using the HostBuilder I can add additional logging providers, add caching services etc. Those services are then injected via the constructor of the Azure Function.
Now in comparison when creating a Durable Function project via the VS Code "Durable Function Orchestration" template there is no Program.cs, no HostBuilder and no constructor. There are just some static methods representing the orchestrator and an orchestrated function.
As there is no out-of-the-box HostBuilder in the "Durable Function Orchestration" template - how does the HostBuilder equivalent look like for Durable Functions? Whats the pattern or convention here? Do I write it myself? Or is there some instance floating around or initialization I can hook into? Or should orchestrated functions be put into separate Azure Function projects where I can make use of the HostBuilder?
Any hints are appreciated.
By default the ILogger instance is injected in your functions, unless you are using DI.All you need to do is Use the ILogger.
[FunctionName("funcname")]
public async static Task RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context,
ILogger log)
{
log.LogInformation("Starting Orchestration");
}
Check
Incase if you using Dependency injection you should just do the below in your startup builder.Services.AddLogging();
Also check
So the solution is to use a FunctionsStartup class as outlined here. This should make dependency injection work, also for Durable Functions.
For me it did not work immediately though and it took a while to figure out why. What I tried is adding an additional parameter (myService) to the static methods like so:
[FunctionName("DurableFunctionsOrchestrationCSharp1_Hello")]
public static string SayHello([ActivityTrigger] string name, ILogger log, IMyService myService)
{
log.LogInformation($"Saying hello to {name}.");
return $"Hello {name}!";
}
I also added the startup class according to the documentation that is supposed to provide the implementation for IMyService.
This did never work. The error I got is this:
Microsoft.Azure.WebJobs.Host: Error indexing method
'DurableFunctionsOrchestrationCSharp1_Hello'.
Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'myService' to
type IMyService. Make sure the parameter Type is supported by the
binding. If you're using binding extensions (e.g. Azure Storage,
ServiceBus, Timers, etc.) make sure you've called the registration
method for the extension(s) in your startup code (e.g.
builder.AddAzureStorage(), builder.AddServiceBus(),
builder.AddTimers(), etc.).
This error message suggests that it should work while in reality it never does.
The solution was to get rid of the static methods and use classes with constructors. Constructor injection WORKS.
The working class looks like this:
public class Activities
{
IMyService _service;
public Activities(IMyService service)
{
_service = service;
}
[FunctionName("DurableFunctionsOrchestrationCSharp1_Hello")]
public string SayHello([ActivityTrigger] string name, ILogger log)
{
log.LogInformation($"Saying hello to {name} {_service.GetType()}.");
return $"Hello {name}!";
}
}
Note that I moved the function here and made it non-static.
The constructor is properly invoked, given a IMyService instance created by the Startup class and then the function is executed.
The minimal startup class I used for testing looks like this:
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
public interface IMyService
{
}
public class MyService : IMyService
{
}
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<IMyService>((s) => {
return new MyService();
});
}
}
}
So dependency injection works for Durable Functions, if you are injecting into constructors.

How to properly implement ILogger.IsEnabled() in custom logger in ASP.NET Core MVC

I am (as something of a novice) implementing my own custom logger for use in ASP.NET Core MVC apps. I have this logger working functionally in every regard. But I cheated a little so far, namely I implemented the ILogger.IsEnabled method as follows:
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
Functionally, this works fine, since the framework ensures that the Log() method is only invoked if the log level is at or higher than the one specified. So the correct "things" are being logged and the lower-level "things" are not being logged as expected.
However, I also want to support the following kind of situation in my code, where _logger is typed as ILogger and is properly injected in my controller:
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("This is an expensive message to generate: " +
JsonConvert.SerializeObject(request));
}
To make this effective, my IsEnabled() method should be able to know what the log level IS for the instance of the logger that was created with my LoggerProvider, but I don't know how to get that information directly, or how to pass it properly to the injected instance of the the logger I am working with.
Complex examples and tutorials I have been able to find seem to be constructed in every case for console app types, not network app types, and so far I have been unsuccessful at figuring out how to do this through the templated Startup class in ASP.NET MVC.
What is the simplest and most effective way to stop cheating at my custom IsEnabled() method in order to avoid the unnecessary serialization (in my example) if none of the registered loggers in the injected instance are handling the Debug log level? Or do you have a favorite example or tutorial in the ASP.NET core setting you can point me to?
You can take a look at built-in loggers source code and see how they implement it.
In short, they only check that logLevel != LogLevel.None, but depending on the logger logic, you might also want to check some other configuration. For example, DebugLogger logger also checks the Debugger.IsAttached property and EventLogLogger checks the EventLogSettings.Filter (supplied via constructor).
Update
To make this effective, my IsEnabled() method should be able to know what the log level IS for the instance of the logger that was created with my LoggerProvider, but I don't know how to get that information directly, or how to pass it properly to the injected instance of the the logger I am working with.
You can create an implementation of ILoggerProvider which in turn can make use of dependency injection to get the configuration you want. If you want to use the options pattern to configure it, you must do something along the lines of:
public class MyLoggerProvider : ILoggerProvider
{
private readonly IOptions<MyLoggerOptions> _options;
public MyLoggerProvider(IOptions<MyLoggerOptions> options)
{
_options = options;
}
public ILogger CreateLogger(string name)
{
return new MyLogger(name, _options.Value);
}
}
And optionally add an extension method to make registration easier:
public static class MyLoggerExtensions
{
public static ILoggingBuilder AddMyLogger(this ILoggingBuilder builder, Action<MyLoggerOptions> configure)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, MyLoggerProvider>());
LoggerProviderOptions.RegisterProviderOptions<MyLoggerOptions, MyLoggerProvider>(builder.Services);
builder.Services.Configure(configure);
}
}

Benefit of AddSingleton(S, T) instead of AddSingleton(T)

What is the benefit of using services.AddSingleton<SomeService, SomeServiceImplementation>() instead of services.AddSingleton<SomeServiceImplementation>() ?
For example i've got an sample Interface
interface ISampleInterface
{
void DoSomething();
}
And a Sample-Class:
class SampleClass : ISampleInterface
{
public void DoSomething()
{
console.write("hi");
}
}
No i do services.AddSingleton<SampleClass>()
Why or when to use services.AddSingleton<ISampleInterface, SampleClass>() ?
Thanks for your help! :-)
services.AddSingleton<SampleInterface, SampleClass>() allows you to register different implementations for the same interface without modifying the rest of your code.
Change implementations with minimal effort
Suppose you have an ILogger interface and implementation that log eg to the browser's console or send the log entry to different services eg ConsoleLogger, MyServiceLogger or PrometheusLogger. If you registered only the implementation, with eg services.AddSingleton<ConsoleLogger>() you'd have to change all of your classes each time you changed a logger implementation.
You'd have to go to each page and change
#inject ConsoleLogger logger;
to
#inject MyServiceLogger logger;
Forget about specifying the logger at runtime too. You'd have to deploy the application each time you wanted to use a new logging service.
By registering the interface and a specific implementation, all of your classes can keep using ILogger<T> and never know that the implementation has changed.
Implementation selection at runtime
You could even change the implementation at runtime, based on environment variables, configuration, or any other logic you want, eg :
if (app.IsDevelopment)
{
services.AddSingleton<ILogger,ConsoleLogger>();
}
else
{
services.AddSingleton<ILogger,MyServiceLogger>();
}
Unit Testing
In unit tests you could use a null logger - in fact the Logging middleware has a NullLogger class just for this reason, in the core Abstractions package.
Or you could wrap your test framework's output methods into an ILogger implementation and use that, without modifying the code. xUnit for example uses the ITestOutputHelper interface for this. You could create an XUnitlogger that forwards calls to this interface:
public class XUnitLogger:ILogger
{
private readonly ITestOutputHelper _output;
public XUnitLogger(ITestOutputHelper output)
{
_output=output;
}
...
void Log(...)
{
_output.WriteLine(...);
}
}

How to log NLog calls from a class library in my ASP.NET Core MVC app?

Let's pretend I have two projects.
The first one is an ASP.NET Core MVC project that relies on NLog.Extensions.Logging for its logging. This is great; I can use dependency injection on my controllers to get an ILogger instance, and the nlog.config file contains, well, my NLog config.
The second one is a class library, which the API depends on, that relies directly on NLog for its logging. It contains calls like this:
public class SampleClass
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public void DoStuff()
{
if (_failed) Logger.Error("oh no");
}
}
These classes are instantiated with some reflexive wizardry, and I can't use dependency injection to replace their logger. You can also think of them as some sort of model, which can't get instantiated at startup.
How do I get my library's logs to show up in the API's logging output? I would expect them to get caught by the nlog.config automatically, but they don't seem to be.
You don't need separate configuration file.
If your ASP.net MVC core project has nlog.config and it is successfully copied during build process then same configuration will get load when
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
Make sure you have copied file correctly. Also MinLevel properly set in configuration NLog.config.
Make sure you have .NET Core ClassLibrary ( Just to make sure it is loading successfully)
In your case too you can use Dependency Injection but it is different Story.
Here is the complete example with NLog
You need to get NLog and NLog.Web.AspnetCore Package
In Program.cs
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
}).UseNLog()
.UseStartup<Startup>();
}
Now In ClassLibrary Project Just Add Referece for NLog.
Note : Here Make sure that ILogger is from Microsoft.Extensions.Logging not from NLog.
public class Class1
{
//private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private ILogger<Class1> _logger = null;
public Class1(ILogger<Class1> logger)
{
this._logger = logger;
}
public void DoStuff()
{
var _failed = true;
if (_failed) _logger.LogError("oh no");
}
}
Now it will work without any issue as well.
Class libraries should never depend on a particular logging implementation. Instead, you should use an abstraction, referred to as a facade. The Microsoft.Extensions.Logging library is one such facade you can utilize, but there's others like Common.Logging. Regardless, the classes that need to utilize logging should be injected with this abstract logging facade. For example:
public class SampleClass
{
private readonly ILogger _logger;
public SampleClass(ILogger<SampleClass> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public void DoStuff()
{
if (_failed) _logger.LogError("oh no");
}
}
Then, in your web application or whatever other concrete application that is utilizing your class library, that is where you actually set up your logging implementation, and register that via your DI container to injected in into your logging facade.
Long and short, your class library depends only on your logging facade, which allows it to generically call stuff like LogError. The application that uses your library sets up its actual concrete logging implementation, which will then be utilized under the hood by the facade for the logging the library does.

DI/IoC with a Singleton Logger class

We are using an in house simple Logger class for our application's logging tasks (.NET 3.5).
The logger code is pretty old, and is designed similarly to this:
public class Logger : ILogger
{
private ILogger instance;
private static ILogger Instance
{
// Initialized on first use.
get { return instance; }
}
public static void Debug(string msg)
{
instance.Debug(msg);
}
public static void Error(string msg)
{
....
}
}
The instance itself is being initialized on first usage (lazily).
This is not a Singleton according to its strict "by the book" implementation, but nonetheless, the access to this class from all calling code is a static access.
I would like, for testing purposes and for other architectural reasons, to be able to replace the internal instance with something else (inject it).
How can i achieve this easily? we are not using any IoC container at the moment, but i would not want to expose a setter to the Instance property since that would defeat the whole Singleton like design.
Any suggestions on how to come up with a solution for this?
Consider using Fakes Framework for testing purposes. You could stub the call to static method with something like this
ShimLogger.Instance = () => new LoggerMock();
In case of .net 3.5 you can use Moles Framework to stub static method call. Configuration code will look something like:
MLogger.Instance = () => new LoggerMock();
It would require to make static method Instance public, but after this configuration every call to static method will return your mocked instance.
Indeed, a setter does not sound like a good choice.
Instead, I would consider two possible approaches. First, an explcit configuration method:
public class Logger : ILogger {
public void ConfigureLogger( ILogger logger ) {
this.instance = logger;
}
}
An advantage of such approach is that the intention is clear plus you have to call this method in an explicit way.
Another option would be to allow one to pass a type of your logger in your configuration:
<appSettings>
<add key="loggerType" value="The.Type.From, Some.Assembly" />
</appSettings>
Then, in your Logger class you rewrite the initialization routine so that if the configuration parameter is present, you prefer the type provided in the configuration OVER the default type.
An advantage of such approach is that you can reconfigure the client with the configuration change with no changes to the code.
Anyway, IoC containers don't bite. Introduce one as it pays off in a long term.
I wouldn't roll your own. I use the Enterprise Library for almost all my logging needs. It works on desktop and asp.net projects. Asp.net can be a bit more problematic since you have to deal with security on the server but I've done it.
http://entlib.codeplex.com/
People also like Log4Net but I've never used it so I can't comment on it.
I would modify the code using the Logger. Instead of accessing the logger through Logger.Instance, pass in the desired instance of the logger into the object. Then in your factories and/or composition root you pass Logger.Instance as the source of the logger in your production code, and in your unit tests it is easy to use a mock logger.
public class Foo
{
private readonly ILogger logger;
public Foo(ILogger logger)
{
if (logger == null)
throw new ArgumentNullException("logger");
this.logger = logger;
}
public void Func()
{
try
{
// do something
}
catch (Exception ex)
{
// call the provided logger dependency
this.logger.WriteError(ex);
// not the static singleton property
Logger.Instance.WriteError(ex);
}
}
}
Another idea would be to make an internal setter for your Instance property and use the InternalsVisibleTo attribute to make the internal setter visible to your test assembly. Note that if the assembly that contains your logger is strong named, then you must specify the PublicKey in the InternalsVisibleTo attribute. Obviously this is most helpful (in the sense of not letting other developers accidentally - or on purpose - setting Instance to something else) if your logger lives in is own assembly or in some kind of infrastructure assembly where most development/logging is NOT taking place.

Categories

Resources