Reputation: 2378
I have a asp.net core web api. As of now I'm using ILogger to log the messages. But ILogger doesn't have Fatal loglevel in it. There is Critical level, but our team requires Fatal word instead of Critical word.Is there any way I can tweak the work which gets printed to logs?
If not, I want to replace ILogger with log4Net which has Fatal level in it.So this is what I have done , but somehow it is not working. I have multi layer architecture : WebApplication1, WebApplication1.Helper . All these are different projects with in a solution.
In WebApplication1:
I have added Microsoft.Extensions.Logging.Log4Net.AspNetCore reference.
In startup.cs
public void ConfigureServices(IServiceCollection apiServices)
{
var provider = apiServices.BuildServiceProvider();
var factory = new LoggerFactory()
.AddConsole().AddLog4Net().AddApplicationInsights(provider, LogLevel.Information);
apiServices.AddSingleton(factory);
apiServices.AddLogging();
apiServices.AddMvc();
apiServices.AddOptions();
}
HomeController.cs
[Route("api/[controller]")]
[ApiController]
public class HomeController : Controller
{
private readonly ILog4NetHelper _logHelper = new Log4NetHelper();
[HttpGet]
public virtual IActionResult GetData()
{
try
{
_logHelper.Log4NetMessage("Info", "Start GetData");
return new OkObjectResult("Your in Home Controller");
}
catch (Exception ex)
{
_logHelper.Log4NetMessage("Error", "Exception in GetData" + ex.Message);
throw;
}
}
}
WebApplication1.Helper project
And in WebApplication1.Helper
project , I have added a interface ILog4NetHelper
and class which implements this interface Log4NetHelper
. Also I have added log4Net config file.
public class Log4NetHelper : ILog4NetHelper
{
readonly ILog _log =log4net.LogManager.GetLogger(typeof(Log4NetHelper));
public void Log4NetMessage(string type,string message)
{
string logMessage = message;
switch (type)
{
case "Info":
_log.Info(logMessage);
break;
case "Error":
_log.Error(logMessage);
break;
case "Fatal":
_log.Fatal(logMessage);
break;
default:
_log.Info(logMessage);
break;
}
}
}
When I host this application and run this, it is giving me a 500 internal server error. The error message is this :
InvalidOperationException: Unable to resolve service for type 'WebApplication1.Helper.Log4NetHelper' while attempting to activate 'WebApplication1.Helper.Log4NetHelper'. Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, bool throwIfCallSiteNotFound)
How can I resolve this?
Upvotes: 2
Views: 10789
Reputation: 2245
The Microsoft.Extensions.Logging system doesn't contain Fatal level as an accepted LogLevel.
But, if you are interested in treat the Critical messages as log4net's Fatal level, starting at v.2.2.5, there is a property on Microsoft.Extensions.Logging.Log4Net.AspNetCore nuget package that allow you to decide if the Critical Level is managed as Fatal or not.
Please, review the original issue to check why the implementation was done as it was.
Upvotes: 0
Reputation: 1526
I ran into multiple issues with Log4Net few years ago - i don't remember exactly what, since then i switched to my own implementation for log, it is not complicated to write your own. my implementation is copied below.
When i had to change the log class, it was nightmare since i had to replace all its usage with the new implementation.
There are 2 benefits of custom class.
A. It will match your application's requirement. B. It acts as a wrapper for any underlying logging framework that you may use. So now even if i have to change logging in future, i just have to change the internal implementation of this custom class.
Since writing this wrapper, i have changed from log4net to Microsofts log extensions and now 100 % my own implementation.
using System;
using System.Text;
using System.IO;
namespace BF
{
//This is a custom publisher class use to publish the exception details
//into log file
public class LogPublisher
{
private static object _lock;
static LogPublisher()
{
_lock = new object();
}
//Constructor
public LogPublisher()
{
}
//Method to publish the exception details into log file
public static void Debug(string message)
{
if (ClientConfigHandler.Config.IsDebugMode())
{
Exception eMsg = new Exception(message);
Publish(eMsg, "#DEBUG");
}
}
public static void DebugBackgroundAction(string message)
{
if (ClientConfigHandler.Config.IsDebugMode())
{
Exception eMsg = new Exception(message);
Publish(eMsg, "#DEBUG #BG");
}
}
public static void BackgroundAction(string message)
{
Exception eMsg = new Exception(message);
Publish(eMsg, "#BG");
}
public static void Publish(string message)
{
Exception eMsg = new Exception(message);
Publish(eMsg, "");
}
public static void Publish(Exception fException)
{
Publish(fException, "");
}
public static void Publish(Exception fException, string prefix)
{
if (fException == null) return;
// Load Config values if they are provided.
string m_LogName = ResourceConfig.LogFileName;
// Create StringBuilder to maintain publishing information.
StringBuilder strInfo = new StringBuilder();
// Record required content of the AdditionalInfo collection.
strInfo.AppendFormat("{0}**T {1} {2} ", Environment.NewLine, CommonConversions.CurrentTime.ToString(CommonConversions.DATE_TIME_FORMAT_LOG), prefix);
// Append the exception message and stack trace
strInfo.Append(BuildExceptionLog(fException, false));
try
{
lock (_lock)
{
FileStream fs = File.Open(m_LogName, FileMode.Append, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
sw.Write(strInfo.ToString());
sw.Close();
fs.Close();
}
}
catch
{
//ignore log error
}
}
private static string BuildExceptionLog(Exception fException, bool isInnerExp)
{
StringBuilder strInfo = new StringBuilder();
if (fException != null)
{
string msgType;
if (isInnerExp)
{
msgType = "#IN-ERR";
}
else if (fException.StackTrace == null)
{
msgType = "#INF";
}
else
{
msgType = "#ERR";
}
strInfo.AppendFormat("{0}: {1}", msgType, fException.Message.ToString());
if (fException.StackTrace != null)
{
strInfo.AppendFormat("{0}#TRACE: {1}", Environment.NewLine, fException.StackTrace);
}
if (fException.InnerException != null)
{
strInfo.AppendFormat("{0}{1}", Environment.NewLine, BuildExceptionLog(fException.InnerException, true));
}
}
return strInfo.ToString();
}
}
}
Upvotes: 0
Reputation: 16167
ASP.Net Core built-in logging was Microsoft's stab at doing logging the Microsoft, dependency-injected way. It follows the basic principles and tenets of the Log4Net approach (which has been standardized across .Net, Java, and Javascript, among others). So, the two approaches are not entirely at odds with one another.
However, in this particular case, the implementation appears to actually conflict with the intent of both approaches to logging.
Log4Net separates out the two acts of recording and writing log output. The first is done via the ILog interface. The second is done via one of the Appenders.
Similarly, the ASP.net Core API uses an ILogger and one or more Providers to emit log messages.
As I am more comfortable with log4net, and also don't see much of a point in having loggers added via dependency injection in EVERY CLASS, I used log4net's approach of LogManager.GetLogger(typeof(MyClass))
rather than doing it via Microsoft DI. My appenders also run through log4net. Thus, my implementation focused on translating the Microsoft logging outputs into the log4net format, which appears to be the what your team would like but the opposite of what you are doing here. My approach was based on this article. The code I used is below.
Implementation Notes:
I set up a custom appender via log4net which writes my logs out to a logging database (commonly-used databases for this are loki and/or elasticsearch).
In the Configure()
method on startup.cs
, you'll need to have the following line (note that I instantiate the customAppender in the ConfigureServices
and then add it to the DI, but you wouldn't have to do it this way):
loggerFactory.AddLog4Net(_serverConfig.LoggingSettings, customAppender);
It is also necessary to have the following in ConfigureServices()
(not sure why, but it seems to ensure that the regular .net core logging kicks in).
services.AddLogging(config => {
config.AddDebug();
config.AddConsole();
});
Log4NetLogger.cs
/// <summary>
/// Writes ASP.net core logs out to the log4net system.
/// </summary>
public class Log4NetLogger : ILogger
{
private readonly ILog _logger;
public Log4NetLogger(string name)
{
_logger = LogManager.GetLogger(typeof(Log4NetProvider).Assembly, name);
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
switch (logLevel) {
case LogLevel.Critical:
return _logger.IsFatalEnabled;
case LogLevel.Debug:
case LogLevel.Trace:
return _logger.IsDebugEnabled;
case LogLevel.Error:
return _logger.IsErrorEnabled;
case LogLevel.Information:
return _logger.IsInfoEnabled;
case LogLevel.Warning:
return _logger.IsWarnEnabled;
default:
throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
if (!this.IsEnabled(logLevel)) {
return;
}
if (formatter == null) {
throw new ArgumentNullException(nameof(formatter));
}
string message = null;
if (null != formatter) {
message = formatter(state, exception);
}
if (!string.IsNullOrEmpty(message) || exception != null) {
switch (logLevel) {
case LogLevel.Critical:
_logger.Fatal(message);
break;
case LogLevel.Debug:
case LogLevel.Trace:
_logger.Debug(message);
break;
case LogLevel.Error:
_logger.Error(message);
break;
case LogLevel.Information:
_logger.Info(message);
break;
case LogLevel.Warning:
_logger.Warn(message);
break;
default:
_logger.Warn($"Encountered unknown log level {logLevel}, writing out as Info.");
_logger.Info(message, exception);
break;
}
}
}
Log4NetProvider.cs
/// <summary>
/// Returns new log4net loggers when called by the ASP.net core logging framework
/// </summary>
public class Log4NetProvider : ILoggerProvider
{
private readonly LoggingConfig _config;
private readonly ConcurrentDictionary<string, Log4NetLogger> _loggers =
new ConcurrentDictionary<string, Log4NetLogger>();
private readonly ILoggerRepository _repository =
log4net.LogManager.CreateRepository(typeof(Log4NetProvider).Assembly, typeof(log4net.Repository.Hierarchy.Hierarchy));
public Log4NetProvider(LoggingConfig config, MyCustomAppender otherAppender)
{
_config = config;
BasicConfigurator.Configure(_repository, new ConsoleAppender(), otherAppender);
LogManager.GetLogger(this.GetType()).Info("Logging initialized.");
}
public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(categoryName, this.CreateLoggerImplementation(categoryName));
}
public void Dispose()
{
_loggers.Clear();
}
private Log4NetLogger CreateLoggerImplementation(string name)
{
return new Log4NetLogger(name);
}
}
Log4NetExtensions.cs
/// <summary>
/// A helper class for initializing Log4Net in the .NET core project.
/// </summary>
public static class Log4netExtensions
{
public static ILoggerFactory AddLog4Net(this ILoggerFactory factory, LoggingConfig config, MyCustomAppender appender)
{
factory.AddProvider(new Log4NetProvider(config, appender));
return factory;
}
}
Upvotes: 3
Reputation: 3110
I see one issue in your code. Your class Log4NetHelper requires in constructor an instance of Log4NetHelper. So it can't create Log4NetHelper. Why you passing Log4NetHelper in constructor, it smells. You should provide default constructor, or constructor with parameters which are registered in DI service.
Please try add parameterless constructor and check if it works, if not check exception and/or error message.
Upvotes: -1