使用 nlog.config 在 Azure 函数日志流中进行 NLog

时间:2021-02-24 19:17:27

标签: azure azure-functions nlog

我的问题与这个问题非常相似:How can I get NLog output to appear in the streaming logs for an Azure Function?。在那个问题中,接受的答案显示了如何在函数调用开始时配置 nlog。我想通过 nlog.config 文件进行配置,或者至少在设置时配置一次,而不是在每次调用函数时配置。

通过下面的代码,我在 Application Insight 日志中看到 NLog 消息,但在日志流中没有看到。我希望从 NLog 记录的消息也显示在 Log Streams 中。

功能代码

    private static readonly Logger _logger = NLog.LogManager.GetCurrentClassLogger();

    [FunctionName("TestLog")]
    public static async Task<IActionResult> TestLog([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
                                                HttpRequest req, ILogger log)
    {
        //shows in both Application Insights and Log Stream.
        log.LogInformation("Log From ILogger");

        //shows in Application Insights but not in Log Stream.
        _logger.Info("Log From NLog");

        return new OkObjectResult("OK");
    }

函数启动

    [assembly: FunctionsStartup(typeof(MyNamespace.TestFunctionApp.Startup))]
    
    namespace MyNamespace.TestFunctionApp
    {            
        public class Startup : FunctionsStartup
        {            
            public override void Configure(IFunctionsHostBuilder builder)
            {
                //nLog file ends 1 directory up from bins when deployed
                var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                var rootDirectory = Path.GetFullPath(Path.Combine(binDirectory, ".."));
                var nLogConfigPath = Path.Combine(rootDirectory, "NLog.config");
        
                //create MicrosoftILoggerTarget (I think this has to be done from code since it needs a ref to an ILogger instance).  But this does not seem to work.
                var loggerFactory = new Microsoft.Extensions.Logging.LoggerFactory();
                var azureILogger = loggerFactory.CreateLogger("NLog");
                var loggerTarget = new NLog.Extensions.Logging.MicrosoftILoggerTarget(azureILogger);
        
                //setup NLog
                LogManager.Setup()
                          .SetupExtensions(e => e.AutoLoadAssemblies(false))
                          .LoadConfigurationFromFile(nLogConfigPath, optional: false)
                          .LoadConfiguration(builder => builder.Configuration.AddRuleForAllLevels(loggerTarget));            
             }
        }
   }

NLog.config

<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <extensions>
    <add assembly="Microsoft.ApplicationInsights.NLogTarget" />
  </extensions>
  <targets>
    <target name="a" xsi:type="ApplicationInsightsTarget"/>
  </targets>
  <rules>
    <logger name="*" minlevel="Info" writeTo="a" />
  </rules>
</nlog>

4 个答案:

答案 0 :(得分:1)

通常的目标是让 Microsoft ILogger 的输出到达 NLog 目标。这是通过调用 UseNLog()AddNLog() 来完成的,这会将 NLog 添加为 LoggingProvider。

替代方案有一个已经使用 NLog 的现有库,但希望将输出重定向到 Microsoft ILoggerFactory。但是如果因为循环/递归的噩梦而还想将 NLog 添加为 LoggingProvider,则必须小心。

但我想您可以执行以下操作将所有输出从 NLog 重定向到 Microsoft ILoggerFactory:

    public class Startup : FunctionsStartup
    {            
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var serviceProvider = builder.Services.BuildServiceProvider();
            var executionContextOptions = serviceProvider.GetService<IOptions<ExecutionContextOptions>>().Value;
            var appDirectory = executionContextOptions.AppDirectory;

            var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
                          
            // Setup NLog redirect all logevents to Microsoft ILoggerFactory
            var nlogLoggerName = "NLog";
            var nlogLogger = loggerFactory.CreateLogger(nlogLoggerName);
            var nlogTarget = new NLog.Extensions.Logging.MicrosoftILoggerTarget(nlogLogger);
            var nLogConfigPath = Path.Combine(appDirectory, "NLog.config");
    
            //setup NLog
            LogManager.Setup()
                      .SetupExtensions(e => e.AutoLoadAssemblies(false))
                      .LoadConfigurationFromFile(nLogConfigPath, optional: false)
                      .LoadConfiguration(builder => {
                          // Ignore output from logger named "NLog" to avoid recursion
                          builder.Configuration.Rules.Add(new LoggingRule() { LoggerNamePattern = nlogLoggerName, MaxLevel = LogLevel.Off, Final = true });
                          builder.Configuration.AddRuleForAllLevels(loggerTarget));
                      });
         }
    }

答案 1 :(得分:1)

我设法从 Rolf 的答案中得到了一些有用的东西。但它是 hacky,只有当你的函数应用中只有 1 个函数时才有意义。

函数启动代码:

    public class Startup : FunctionsStartup
    {

        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton<NLog.Logger>(SetupNLog);
        }
        
        private NLog.Logger SetupNLog(IServiceProvider serviceProvider)
        {
            //find NLog.config
            var executionContextOptions = serviceProvider.GetService<IOptions<ExecutionContextOptions>>().Value;
            var appDirectory = executionContextOptions.AppDirectory;
            var nLogConfigPath = Path.Combine(appDirectory, "NLog.config");

            //setup target to forward NLog logs to ILogger
            //NOTE: string passed to CreateLogger must match "function.<function name>" or logs will not show in Log Stream
            var msLoggerFactory = serviceProvider.GetService<ILoggerFactory>();
            var msLogger = msLoggerFactory.CreateLogger("Function.TestLog");
            var nLogToMsLogTarget = new MicrosoftILoggerTarget(msLogger);
            
            //setup NLog
            LogManager.Setup()
                      .SetupExtensions(e => e.AutoLoadAssemblies(false))
                      .LoadConfigurationFromFile(nLogConfigPath, optional: false)
                      .LoadConfiguration(c => c.Configuration.AddRuleForAllLevels(nLogToMsLogTarget));

            //return an NLog logger
            return LogManager.GetLogger("NLog");
        }
   }

函数代码:

    public class Function1
    {

        private readonly NLog.Logger _logger;

        public Function1(NLog.Logger logger)
        {
            _logger = logger;
        }

        [FunctionName("TestLog")]
        public async Task<IActionResult> TestLog([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
                                                    HttpRequest req, Microsoft.Extensions.Logging.ILogger log)
        {
            log.LogInformation("Log From ILogger");
    
            _logger.Info("Log From NLog");

            var other = new SomeOtherClassWithNLog();
            other.SomeMethod();
    
            return new OkObjectResult("OK");
        }
    }


        public class SomeOtherClassWithNLog
        {
           private static readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

           public void SomeMethod()
           {
               _logger.Info("Logs From Another Class"); 
           }

        }

我还发现 Azure 似乎将日志流中显示的日志过滤为仅包含类别名称中带有“Host.*”或“Function..*”的日志。在查看单个函数的监视器页面时,它会被进一步过滤,在这种情况下,它会过滤到“Function..*”。

因此,您在查看函数应用级别的日志流时遇到了困难,其中还写入了许多“不是我的应用”日志。或者仅查看一个函数级别的日志流,即使那不是您调试的函数。

我想最后我将只使用默认的 ILogger,并且可以接受直接记录到 NLog 记录器的代码在日志流查看器中不可见的事实。

答案 2 :(得分:0)

相信日志流正在监视 HOME 文件夹中的文件,因此可以尝试添加文件目标:

<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <extensions>
    <add assembly="Microsoft.ApplicationInsights.NLogTarget" />
  </extensions>
  <targets>
    <target name="a" xsi:type="ApplicationInsightsTarget"/>
    <target name="logfile" xsi:type="File" filename="${environment:HOME:cached=true}/logfiles/application/app-${shortdate}-${processid}.txt" />
  </targets>
  <rules>
    <logger name="*" minlevel="Info" writeTo="logfile, a" />
  </rules>
</nlog>

另见:https://github.com/NLog/NLog.Extensions.Logging/wiki/NLog-cloud-logging-with-Azure-function-or-AWS-lambda#writing-to-azure-diagnostics-log-stream

答案 3 :(得分:0)

是时候回答我的第三个问题了。与 NLog.Extensions.Logging 版。 1.7.1 那么 MicrosoftILoggerTarget 可以将 ILoggerFactory 作为输入参数,并且可以覆盖 LoggerName。

所以你可以这样设置:

    var msLoggerFactory = serviceProvider.GetService<ILoggerFactory>();
    var nLogToMsLogTarget = new MicrosoftILoggerTarget(msLoggerFactory);
    nLogToMsLogTarget.LoggerName = "${mdlc:FunctionName}";

像这样使用它:

    [FunctionName("TestLog")]
    public async Task<IActionResult> TestLog([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
                                                HttpRequest req, Microsoft.Extensions.Logging.ILogger log)
    {
        using (NLog.MappedDiagnosticsLogicalContext.SetScoped("FunctionName", log.ToString())
        {
             log.LogInformation("Log From ILogger");

             LogManager.GetCurrentClassLogger().Info("Log From NLog");

             var other = new SomeOtherClassWithNLog();
             other.SomeMethod();

             return new OkObjectResult("OK");
        }
    }

不再受 MicrosoftILoggerTarget 的单个静态 Logger-Name 的限制

相关问题