繁体   English   中英

在 asp.net 核心 webapi 中实现 log4net

[英]Implement log4net in asp.net core webapi

我有一个 asp.net 核心 web api。 截至目前,我正在使用 ILogger 记录消息。 但是 ILogger 中没有致命的日志级别。 有关键级别,但我们的团队需要致命词而不是关键词。有什么办法可以调整打印到日志的工作吗?

如果没有,我想用其中具有致命级别的 log4Net 替换 ILogger。所以这就是我所做的,但不知何故它不起作用。 我有多层架构: WebApplication1WebApplication1.Helper 所有这些都是解决方案中的不同项目。

WebApplication1中:我添加了 Microsoft.Extensions.Logging.Log4Net.AspNetCore 参考。
启动.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();
    }

家庭控制器.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 项目
WebApplication1.Helper项目中,我添加了一个接口ILog4NetHelper和 class ,它实现了这个接口Log4NetHelper 我还添加了 log4Net 配置文件。

  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;
            }
        }
    }

当我托管这个应用程序并运行它时,它给了我一个 500 内部服务器错误。 错误信息是这样的:

InvalidOperationException:尝试激活“WebApplication1.Helper.Log4NetHelper”时无法解析“WebApplication1.Helper.Log4NetHelper”类型的服务。 Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(类型 serviceType,类型 implementationType,CallSiteChain callSiteChain,ParameterInfo[] 参数,bool throwIfCallSiteNotFound)

我该如何解决这个问题?

ASP.Net Core 内置日志记录是 Microsoft 在以注入依赖项的方式进行日志记录方面的尝试。 它遵循 Log4Net 方法的基本原则和原则(已在.Net、Java 和 Javascript 等标准化)。 因此,这两种方法并不完全相互矛盾。

但是,在这种特殊情况下,实现似乎实际上与两种日志记录方法的意图相冲突。

Log4Net 将记录写入日志 output两个行为分开。 第一个是通过ILog接口完成的。 第二个是通过其中一个Appenders完成的。

同样,ASP.net Core API 使用ILogger和一个或多个Provider来发出日志消息。

由于我对 log4net 更满意,而且在每个 CLASS 中通过依赖注入添加记录器也没有多大意义,我使用 log4net 的方法LogManager.GetLogger(typeof(MyClass))而不是通过 Microsoft DI . 我的附加程序也通过 log4net 运行。 因此,我的实施侧重于将 Microsoft 日志输出转换为 log4net 格式,这似乎是您的团队想要的,但与您在这里所做的相反。 我的方法是基于这篇文章 我使用的代码如下。

实施说明:

我通过 log4net 设置了一个自定义 appender,它将我的日志写入日志数据库(常用的数据库是 loki 和/或 elasticsearch)。

startup.cs上的Configure()方法中,您需要具有以下行(请注意,我在ConfigureServices中实例化了 customAppender,然后将其添加到 DI,但您不必这样做) :

loggerFactory.AddLog4Net(_serverConfig.LoggingSettings, customAppender);

ConfigureServices()中还必须有以下内容(不知道为什么,但它似乎确保了常规的 .net 核心日志记录启动)。

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;
    }
}

我没有完全理解你正在混合的东西。

尝试使用 Log4Helper class 的以下代码。 我不确定,但检查它是否有效。

public class Log4NetHelper : ILog4NetHelper
{
    readonly ILog _log;

    public Log4NetHelper()
    {
        _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;
        }
    }
}

几年前我在使用 Log4Net 时遇到了多个问题——我不记得具体是什么,从那以后我切换到我自己的 log 实现,编写自己的实现并不复杂。 我的实现复制如下。

当我不得不更改日志 class 时,这是一场噩梦,因为我不得不用新的实现替换它的所有用法。

定制 class 有 2 个好处。

A. 它将符合您的应用程序的要求。 B. 它充当您可能使用的任何底层日志框架的包装器。 所以现在即使我将来必须更改日志记录,我只需要更改此自定义 class 的内部实现。

自从编写了这个包装器,我已经从 log4net 更改为 Microsoft 的日志扩展,现在 100% 是我自己的实现。

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();
        }
    }
}

Microsoft.Extensions.Logging 系统不包含致命级别作为接受的 LogLevel

但是,如果您有兴趣将严重消息视为 log4net 的致命级别,从 v.2.2.5 开始, Microsoft.Extensions.Logging.Log4Net.AspNetCore nuget package 上有一个属性允许您决定是否为严重级别管理为致命与否。

请查看原始问题以检查为什么按原样完成实施。

我在您的代码中看到一个问题。 您的 class Log4NetHelper在构造函数中需要Log4NetHelper的实例。 所以它不能创建 Log4NetHelper。 为什么你在构造函数中传递Log4NetHelper ,它闻起来很臭。 您应该提供默认构造函数,或带有在 DI 服务中注册的参数的构造函数。

请尝试添加无参数构造函数并检查它是否工作,如果没有检查异常和/或错误消息。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM