当 运行 作为网络服务时,log4net 静默失败

log4net fails silently when running as Network Service

我有一个启动控制台应用程序的 Web 服务。控制台应用程序包含 Log4net。

来自控制台应用程序的代码:

log4net.Config.XmlConfigurator.Configure();
ILog log = log4net.LogManager.GetLogger(typeof(Program));

我遇到的问题是,当其 运行 为 network services 时,未创建 log4net 日志文件。应用程序不会失败它只是继续而不记录。

我需要找到一种方法来检测此问题并使应用程序失败,如果没有日志文件我们将无法继续。我需要通知用户他们需要修复权限,以便网络服务可以创建文件。

如何检测创建文件失败?我能够想出的唯一解决方案是检查该文件是否存在于应用程序的顶部,以及它是否不是为了停止而创建的。这对我来说就像一个黑客 Log4net 应该能够告诉我它无法创建文件。

更新:

我正在测试以下内容。我目前的测试方法是在第一次创建文件后将文件设置为只读,然后就没有权限写入它。此时它应该会失败,因为它无法写入日志文件。

string logFile = @"D:\TaskDev\Log4NetTesting\Log4NetTesting\bin\Debug\Test\Log4netTesting.Log";
log4net.GlobalContext.Properties["LogFileName"] = logFile;
log4net.Config.XmlConfigurator.Configure();
log4net.NDC.Push("Test Application");
ILog log = log4net.LogManager.GetLogger(typeof(Program));

var m =  LogManager.GetAllRepositories().SelectMany(repository => repository.GetAppenders()).OfType<FileAppender>();

try
  {
  log.Info("hello Log test");
  }
catch (Exception ex) {
   Console.WriteLine("Log write failed: " + ex.Message);            
  }
Console.WriteLine("Done");
Console.ReadLine();

app.config

 <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>
  <log4net>
    <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
      <errorHandler type="Log4NetTesting.util.CustomLogErrorHandler" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"/>
      </layout>
    </appender>
    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
      <file type="log4net.Util.PatternString" value="%property{LogFileName}" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="250KB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
      </layout>
    </appender>

您是否考虑过为您的 FileAppender 设置 ErrorHandler?

您可以使用以下代码获取配置文件中设置的 FileAppender:

var fileAppenders = LogManager.GetAllRepositories().SelectMany(repository => repository.GetAppenders()).OfType<FileAppender>();
foreach (var fileAppender in fileAppenders) {
    fileAppender.ErrorHandler = new MyErrorHandler();
}

其中 MyErrorHandler class 有点像这样:

class MyErrorHandler : IErrorHandler {
    public void Error(string messages) {
        // giver error message and abort
    }
    public void Error(string messages, Exception exception) {
        // giver error message and abort
    }
    public void Error(string messages, Exception exception, ErrorCode errorCode) {
        // giver error message and abort
    }
}

我不确定 "giver error message and abort" 有多简单,您在服务中没有 GUI 访问权限

此处列出了 FileAppender 成员:http://logging.apache.org/log4net/release/sdk/html/AllMembers_T_log4net_Appender_FileAppender.htm IErrorHandler 成员在这里: http://logging.apache.org/log4net/release/sdk/html/AllMembers_T_log4net_Core_IErrorHandler.htm

您可能还想在 repository.ConfigurationChanged

注册一个活动

通过 Jens 的一些提示,我得以完成这项工作。

app.config - 添加了这一行

 <errorHandler type="Log4NetTesting.util.CustomLogErrorHandler" />

CustomLogErrorHandler

  public class LogException : Exception
    {

        private readonly ErrorCode errorCode;

        /// <summary>Gets the service name which related to this exception.</summary>
        public ErrorCode ErrorCode
        {
            get { return errorCode; }
        }

        public LogException()
        {
        }

        public LogException(string message)
            : base(message)
        {
        }

        public LogException(string message, Exception inner)
            : base(message, inner)
        {
        }

        public LogException(string message, Exception inner, ErrorCode errorCode)
            : base(message, inner)
        {
            this.errorCode = errorCode;
        }
    }

    public class CustomLogErrorHandler : IErrorHandler
    {
        public string message { get;private set; }
        public Exception Exception { get; private set; }
        public ErrorCode ErrorCode { get; private set; }

        #region IErrorHandler Members
        public void Error(string message)
        {
            this.message = message;
        }
        public void Error(string message, Exception e)
        {
            this.message = message;
            this.Exception = e;
        }
        public void Error(string message, Exception e, ErrorCode errorCode)
        {
            this.message = message;
            this.Exception = e;
            this.ErrorCode = errorCode;
        }
        #endregion
    }

正在测试

 try
            {                

                string logFile = @"D:\TaskDev\Log4NetTesting\Log4NetTesting\bin\Debug\Test\Log4netTesting.Log";
                log4net.GlobalContext.Properties["LogFileName"] = logFile;
                log4net.Config.XmlConfigurator.Configure();
                log4net.NDC.Push("Test Application");
                ILog log = log4net.LogManager.GetLogger(typeof(Program));
                var fileAppenders = LogManager.GetAllRepositories().SelectMany(repository => repository.GetAppenders()).OfType<FileAppender>();

                var customLogErrorHandler = new CustomLogErrorHandler();  // custom error IErrorHandler. 

                // loop though fileappenders adding the error handler
                foreach (var fileAppender in fileAppenders)
                {
                    fileAppender.ErrorHandler = customLogErrorHandler;
                }
                log.Info("hello Log file");

                if (customLogErrorHandler.message != null)   // if there is an error throw exception
                {
                    throw new LogException(customLogErrorHandler.message, customLogErrorHandler.Exception, customLogErrorHandler.ErrorCode);
                }
            }
            catch (LogException lex) {
                // catch log exception
                Console.WriteLine(lex.ErrorCode);
                Console.WriteLine(lex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

虽然我明白默默地失败对某些人来说可能是件好事。对于我的应用程序,如果我没有日志文件并且如果 Log4Net 不能告诉我它有问题,我们的用户就无法知道有问题。

您可以这样做来验证配置是否有效:

if(!LogManager.GetRepository().Configured)
{
    XmlConfigurator.Configure();
}

if (!LogManager.GetRepository().Configured)
{
    var logMsg = new StringBuilder();
    logMsg.AppendLine("Failed to initialize log4net");
    foreach(var msg in LogManager.GetRepository().ConfigurationMessages)
    {
        logMsg.AppendLine(msg.ToString());
    }

    // ....do something with the message, raise
    // an evt to UI, log to OS Event logger, etc....
}