I'm currently working with Azure Functions and I just found this interface definition.
Assembly Microsoft.Extensions.Logging.Abstractions, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
using System;
namespace Microsoft.Extensions.Logging
{
public interface ILogger
{
IDisposable BeginScope<TState>(TState state);
bool IsEnabled(LogLevel logLevel);
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
}
}
I am particular interested in void Log<TState>. This function has what looks like a generic, but seems to magically expand into 6 functions.
log.LogCritical("...");
log.LogDebug("...");
log.LogError("...");
log.LogInformation("...");
log.LogTrace("...");
log.LogWarning("...");
I receive the reference to log via the Azure Function defintion.
[FunctionName("WhoAmI")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequestMessage req, ILogger log)
{ ... }
The Microsoft samples and documentation reflect this.
I'm guessing this is a C# or Visual Studio feature and not witchcraft, but which feature is it?
This is C# feature called Extension Methods. These methods are defined in LoggerExtensions static class. Here is a sample signature:
public static void LogDebug (
this ILogger logger, EventId eventId, string message, object[] args);
It's the keyword this which makes the call look like it's the member of interface.
LoggerExtensions class.
C# Extension methods.
Credits to #SirRufo for giving the right hint in the comments. I just don't want to leave this question without the full answer :)
Related
I have an issue in async WCF service using SoapCore in .Net 6 using a cancellation token and XmlSerializer serializer.
The detailed WCF application is as follows:
WCF service in C# .Net Core 6 using SoapCore NuGet package using SoapSerializer.XmlSerializer serializer
I created an async method that has the [OperationContract] attribute with a CancellationToken parameter
I try to get the WSDL using the URL https://localhost:7026/Services.svc?WSDL and it fails because of the CancellationToken with the exception ArgumentException: .NET type CancellationToken cannot be resolved into XML schema type (CancellationToken has namespace starting with System (System.Threading.CancellationToken), is a structure (value type), and is categorized by SoapCore code as very similar to bool, int, long, ... and tries to generate an XML for it and it fails)
I tried adding the [XmlIgnore] attribute to the parameter CancellationToken of the method having the [OperationContract] attribute and it doesn't work
[MessageContract(IsWrapped = false)] cannot be added to parameters of methods
Note: This works with SoapCore with SoapSerializer.DataContractSerializer serializer, but the generated WSDL is bigger enumerating many basic types that I don't use and I want to use SoapSerializer.XmlSerializer if possible.
Program.cs code:
using Microsoft.Extensions.DependencyInjection.Extensions;
using SoapCore;
namespace TestSoapCore;
public static class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSoapCore();
builder.Services.TryAddSingleton<MyService>();
builder.Services.AddMvc();
var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.UseSoapEndpoint<MyService>(
"/Services.svc",
new SoapEncoderOptions(),
SoapSerializer.XmlSerializer
// This works with SoapSerializer.DataContractSerializer but I prefer SoapSerializer.XmlSerializer if possible
);
});
app.Run();
}
}
Contract.cs code:
using System.Runtime.Serialization;
namespace TestSoapCore;
[DataContract]
public class Contract {
[DataMember]
public string? TestProperty { get; set; }
}
MyService.cs code:
using System.ServiceModel;
using System.Xml.Serialization;
namespace TestSoapCore;
[ServiceContract]
public class MyService
{
[OperationContract]
public async Task<string> Test(
Contract contract,
// [MessageContract(IsWrapped = false)] cannot be added to parameters
[XmlIgnore] // This doesn't work
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return contract?.TestProperty + "2";
}
}
Full exception while getting the WSDL at https://localhost:7026/Services.svc?WSDL when SoapSerializer.XmlSerializer serializer is used:
How the WSDL works with SoapSerializer.XmlSerializer serializer without any CancellationToken (but I want the CancellationToken for async methods, it is better to have it):
How the WSDL is bloated and has many basic types I don't use when SoapSerializer.DataContractSerializer serializer is used (that's why I still prefer SoapSerializer.XmlSerializer if possible):
Part 1:
Part 2:
Part 3:
Part 4:
Like Aleksander Ch response, but mixed with the example of official page
Interface
[ServiceContract]
public interface IService
{
public void SetCancellationToken(CancellationToken cancellationToken);
[OperationContract]
public Task<int> CheckStatus();
}
Service (Very important ThreadLocal variable)
public sealed class Service : IService
{
private readonly ThreadLocal<CancellationToken> _threadCancellationToken = new();
Task<int> ILectoService.CheckStatus()
{
if (_threadCancellationToken.IsValueCreated)
_threadCancellationToken.Value.ThrowIfCancellationRequested();
........
}
void ILectoService.SetCancellationToken(CancellationToken cancellationToken)
{
_threadCancellationToken.Value = cancellationToken;
}
}
ServiceOperationTuner
internal sealed class ServiceOperationTuner : IServiceOperationTuner
{
public void Tune(HttpContext httpContext, object serviceInstance, SoapCore.ServiceModel.OperationDescription operation)
{
if (serviceInstance is IService service)
{
service.SetCancellationToken(httpContext.RequestAborted);
}
}
}
Add service operation tuner in Program
builder.Services.AddSoapServiceOperationTuner(new ServiceOperationTuner());
Because the CancellationToken is not working very well with WCF using SoapCore (SoapSerializer.XmlSerializer serializer doesn't generate the WSDL because of CancellationToken and SoapSerializer.DataContractSerializer serializer puts the CancellationToken as an object that needs to be sent with many properties and their types) I ended up removing the CancellationToken entirely and I used the SoapSerializer.XmlSerializer to have less data (WSDL not bloated with all the unused types, many pages of useless data).
PS: This is how the CancellationToken looks with SoapCore and SoapSerializer.DataContractSerializer serializer (not very nice..):
If You want to get SoapCore working with cancellation token there is another approach. SoapCore allows us to define ServiceOperationTuner which can read the cancellationToken from HttpContext and than assign it to service. The example bellow explains the idea of sollution
Define service operation tuner
public class ServiceOperationTuner : IServiceOperationTuner
{
public void Tune(HttpContext httpContext, object serviceInstance, OperationDescription operation)
{
if (serviceInstance != null && serviceInstance is MyService)
{
MyService service = serviceInstance as MyService;
service.SetCancellationToken(httpContext.RequestAborted);
}
}
}
Add service operation tuner in Program
builder.Services.AddSoapServiceOperationTuner(new ServiceOperationTuner());
Modify MyService in order to get cancellationToken
public class MyService
{
CancellationToken _cancellationToken;
public void SetCancellationToken(CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
}
I have to migrate a part of a monolith to be able to run the migrated part independently, but i'm new in azure functions.
Multiple HttpTriggers contain an a unsupported parameter type. (IMultiplierService)
public static async Task<IActionResult> GetMultiplier(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "multipliers/{id:guid}")] HttpRequest req,
string id,
IMultiplierService multiplierService){ ... }
I read online and understand that the string id is a reference to the {id:guid} in the route, but i could not find online what the purpose is of such an interface given as a parameter.
(IMultiplierService is a CRUD like interface. Contains method like 'GetById' or 'GetAll'.)
Can anyone explain how to support such a custom class as parameter input for the HttpTrigger Azure Function.
If you have questions or need more information. Go ahead.
The proper way to insert the crud like interface into the azure functions is to use dependency injection. You dont need to create static functions anymore. All you need to do is register the interface and its implementation in the startup class so that the azure functions runtime inject an instance of correct implementation of your interface. Consider following example of a azure function which uses a ISqlHelper interface. I write my non static function class as follows
public class NotifyMember
{
private readonly ISqlHelper _sqlHelper;
public NotifyMember(ISqlHelper sqlHelper)
{
_sqlHelper = sqlHelper ?? throw new ArgumentNullException(nameof(sqlHelper));
}
[FunctionName(nameof(NotifyMember))]
public async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "multipliers/{id:guid}")] HttpRequest req,
string id, ILogger logger)
{//Perform function work here}
}
And I register my instance of class which implements ISqlHelper in my startup class as
[assembly: FunctionsStartup(typeof(MyFunction.Startup))]
namepace MyFunction{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddTransient<ISqlHelper, SqlHelper>();
}
}
}
For more information on how to do it refer Dependency Injection in Azure Functions
Assume the following typical Queue Trigger function:
public void Run([QueueTrigger("queue1")]object data, ILogger log)
{
// Do something with data
}
My problem is that "queue1" has to be a constant field, so it has to be defined at compile time.
Also, I'd want to have a base class for Queue Triggers, that could work like this:
public abstract class QueueBase<TModel>
{
public void Run([QueueTrigger("queueName")]TModel data, ILogger log)
{
// Do something with data, log something etc.
OnRunExecuted(data);
// Do something with data, log something etc.
}
public abstract void OnRunExecuted(TModel data);
}
with this, I could write own classes which inherit from QueueBase but can even live inside a library which doesn't have Microsoft.Azure.WebJobs dependency:
public class MyQueueHandler : QueueBase<MyModel>
{
public void OnRunExecuted(MyModel data) => ...;
}
But it's impossible to pass in a Queue name... is it?
See binding expressions:
In short, you can pass in a variable queue name as "%queue-name-variable%"
[FunctionName("QueueTrigger")]
public static void Run(
[QueueTrigger("%queue-name-variable%")]string myQueueItem,
ILogger log)
{
log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
}
Where input-queue-name is defined in your configuration like
{"queue-name-variable": "queue-name-in-current-env"}
As i remember attribute QueueTrigger accept only const string, so you can try make some tricks using environment variables like in post how to pass dynamic queue name
Given a class with a constructor signature of
public Foo(ILogger<Foo> logger) {
// ...
}
that I want to test, I need some way to provide an ILogger<Foo> in the test. It's been asked before, but the only answer then was to set up a full-blown service registry, configure logging and resolve the logger from there. This seems very overkill to me.
Is there a simple way to provide an implementation of ILogger<T> for testing purposes?
Note: it doesn't have to actually log anything - just not blow up when the subject under test tries to log something.
Starting from dotnet core 2.0 there's a generic NullLogger<T> class available:
var foo = new Foo(NullLogger<Foo>.Instance);
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nulllogger-1?view=aspnetcore-2.1 (docs)
https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Abstractions/NullLoggerOfT.cs (source)
Or if you need it as part of your services:
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nullloggerfactory?view=aspnetcore-2.1 (docs)
You can create an instance of ILogger<Foo> using NullLoggerFactory as the factory.
Consider the following controller:
public abstract class Foo: Controller
{
public Foo(ILogger<Foo> logger) {
Logger = logger;
}
public ILogger Logger { get; private set; }
}
A sample unit test could be:
[TestMethod]
public void FooConstructorUnitTest()
{
// Arrange
ILogger<FooController> logger = new Logger<FooController>(new NullLoggerFactory());
// Act
FooController target = new FooController(logger);
// Assert
Assert.AreSame(logger, target.Logger);
}
If you use generic logger (ILogger<>) in your classes those instances are generated from IServiceProvider you should register generic NullLogger<> on service provider as below. Not important what you use generic type T in ILogger<>
services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
You have two options:
Create empty implementation of ILogger<Foo> by hand and pass an instance of it to ctor.
Create same empty implementation on the fly using some mocking framework like Moq, NSubstitute, etc.
You could inject ILoggerFactory instead and then create the logger
public Foo(ILoggerFactory loggerFactory) {
logger = loggerFactory.CreateLogger<Foo>();
// ...
}
At startup you need to add the NullLoggerFactory service of course:
services.AddSingleton<ILoggerFactory, NullLoggerFactory>()
From the docs for ILogger<T> (emphasis mine):
A generic interface for logging where the category name is derived from the specified TCategoryName type name. Generally used to enable activation of a named ILogger from dependency injection.
So one option would be to change the implementation of the Foo method to take a plain ILogger and use the NullLogger implementation.
You should use the Null Object Pattern. This has two advantages for you: 1) you can get your tests up and running quickly and they won't "blow up", and 2) anyone will be able to use your class without supplying a logger. Just use NullLogger.Instance, or NullLoggerFactory.Instance.
However, you should use a mocking framework to verify that log calls get made. Here is some sample code with Moq.
[TestMethod]
public void TestLogError()
{
var recordId = new Guid("0b88ae00-7889-414a-aa26-18f206470001");
_logTest.ProcessWithException(recordId);
_loggerMock.Verify
(
l => l.Log
(
//Check the severity level
LogLevel.Error,
//This may or may not be relevant to your scenario
It.IsAny<EventId>(),
//This is the magical Moq code that exposes internal log processing from the extension methods
It.Is<It.IsAnyType>((state, t) =>
//This confirms that the correct log message was sent to the logger. {OriginalFormat} should match the value passed to the logger
//Note: messages should be retrieved from a service that will probably store the strings in a resource file
CheckValue(state, LogTest.ErrorMessage, "{OriginalFormat}") &&
//This confirms that an argument with a key of "recordId" was sent with the correct value
//In Application Insights, this will turn up in Custom Dimensions
CheckValue(state, recordId, nameof(recordId))
),
//Confirm the exception type
It.IsAny<NotImplementedException>(),
//Accept any valid Func here. The Func is specified by the extension methods
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
//Make sure the message was logged the correct number of times
Times.Exactly(1)
);
}
private static bool CheckValue(object state, object expectedValue, string key)
{
var keyValuePairList = (IReadOnlyList<KeyValuePair<string, object>>)state;
var actualValue = keyValuePairList.First(kvp => string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0).Value;
return expectedValue.Equals(actualValue);
}
For more context, see this article.
If you need to verify the calls in addition to just provide the instance, it gets somewhat complicated. The reason is that most calls does not actually belong to the ILogger interface itself.
I have written a more detailed answer here.
Here is a small overview.
Example of a method that I have made to work with NSubstitute:
public static class LoggerTestingExtensions
{
public static void LogError(this ILogger logger, string message)
{
logger.Log(
LogLevel.Error,
0,
Arg.Is<FormattedLogValues>(v => v.ToString() == message),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>());
}
}
And this is how it can be used:
_logger.Received(1).LogError("Something bad happened");
You should try this for mocking ILogger:
mock.Setup(m => m.Log<object>(It.IsAny<LogLevel>(),It.IsAny<EventId>(),It.IsAny<object>(),It.IsAny<Exception>(),It.IsAny<Func<object, Exception,string>>()))
.Callback<LogLevel, EventId, object, Exception, Func<object, Exception, string>>((logLevel, eventId, obj, exception, func) =>
{
string msg = func.Invoke(obj, exception);
Console.WriteLine(msg);
});
This worked for me:
private FooController _fooController;
private Mock<ILogger<FooController>> _logger;
[TestInitialize]
public void Setup()
{
_logger = new Mock<ILogger<FooController>>();
_fooController = new FooController(_logger.Object);
}
I use Ninject as a DI Container in my application. In order to loosely couple to my logging library, I use an interface like this:
public interface ILogger
{
void Debug(string message);
void Debug(string message, Exception exception);
void Debug(Exception exception);
void Info(string message);
...you get the idea
And my implementation looks like this
public class Log4NetLogger : ILogger
{
private ILog _log;
public Log4NetLogger(ILog log)
{
_log = log;
}
public void Debug(string message)
{
_log.Debug(message);
}
... etc etc
A sample class with a logging dependency
public partial class HomeController
{
private ILogger _logger;
public HomeController(ILogger logger)
{
_logger = logger;
}
When instantiating an instance of Log4Net, you should give it the name of the class for which it will be logging. This is proving to be a challenge with Ninject.
The goal is that when instantiating HomeController, Ninject should instantiate ILog with a "name" of "HomeController"
Here is what I have for config
public class LoggingModule : NinjectModule
{
public override void Load()
{
Bind<ILog>().ToMethod(x => LogManager.GetLogger(GetParentTypeName(x)))
.InSingletonScope();
Bind<ILogger>().To<Log4NetLogger>()
.InSingletonScope();
}
private string GetParentTypeName(IContext context)
{
return context.Request.ParentContext.Request.ParentContext.Request.Service.FullName;
}
}
However the "Name" that is being passed to ILog is not what I'm expecting. I can't figure out any rhyme or reason either, sometimes it's right, most of the time it's not. The Names that I'm seeing are names of OTHER classes which also have dependencies on the ILogger.
I personally have no interest in abstracting away my logger, so my implementation modules reference log4net.dll directly and my constructors request an ILog as desired.
To achieve this, a one line registration using Ninject v3 looks like this at the end of my static void RegisterServices( IKernel kernel ):
kernel.Bind<ILog>().ToMethod( context=>
LogManager.GetLogger( context.Request.Target.Member.ReflectedType ) );
kernel.Get<LogCanary>();
}
class LogCanary
{
public LogCanary(ILog log)
{
log.Debug( "Debug Logging Canary message" );
log.Info( "Logging Canary message" );
}
}
For ease of diagnosing logging issues, I stick the following at the start to get a non-DI driven message too:
public static class NinjectWebCommon
{
public static void Start()
{
LogManager.GetLogger( typeof( NinjectWebCommon ) ).Info( "Start" );
Which yields the following on starting of the app:
<datetime> INFO MeApp.App_Start.NinjectWebCommon - Start
<datetime> DEBUG MeApp.App_Start.NinjectWebCommon+LogCanary - Debug Logging Canary message
<datetime> INFO MeApp.App_Start.NinjectWebCommon+LogCanary - Logging Canary message
The Ninject.Extension.Logging extension already provides all you are implementing yourself. Including support for log4net, NLog and NLog2.
https://github.com/ninject/ninject.extensions.logging
Also you want to use the following as logger type:
context.Request.ParentRequest.ParentRequest.Target.Member.DeclaringType
Otherwise you will get the logger for the service type instead of the implementation type.
The Scope of ILog and ILogger needs to be Transient, otherwise it will just reuse the first logger that it creates. Thanks to #Meryln Morgan-Graham for helping me find that.
Bind<ILog>().ToMethod(x => LogManager.GetLogger(GetParentTypeName(x)))
.InSingletonScope();
You are currently binding in Singleton scope, so only one logger is created which will use the name of the first one created. Instead use InTransientScope()
maybe my answer is late but I'm using this format:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ILog>()
.ToMethod(c => LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType))
.InSingletonScope();
}
For all of you that are still looking for the correct answer, the correct implementation is :
public class LoggingModule : NinjectModule
{
public override void Load()
{
Bind<ILog>().ToMethod(x => LogManager.GetLogger(x.Request.Target.Member.DeclaringType));
Bind<ILogger>().To<Log4NetLogger>()
.InSingletonScope();
}
}
Emphasis on:
x.Request.Target.Member.DeclaringType
I do like the idea of wrapping the Log4Net in my own interfaces. I don't want to be dependent on Ninjects implementation, because to me that just means I take a dependency on Ninject throughout my application and I thought that was the exact opposite of what dependency injection is for. Decouple from third party services. So I took the original posters code but I changed the following code to make it work.
private string GetParentTypeName(IContext context)
{
var res = context.Request.ParentRequest.ParentRequest.Service.FullName;
return res.ToString();
}
I have to call ParentRequest.ParentRequest so that when I print the layout %logger it will print the class that calls the Log4Net log method instead of the Log4Net class of the method that called the Log method.