无法在.NET Windows服务中使用log4net

39

我有一个带有 app.configlog4net.config 的Windows服务。

app.config

  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>
  <log4net configSource="log4net.config" />

log4net.config:

<log4net>
  <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
    <param name="File" value="D:\Projects\Integration\Interface Module\bin\Logs\MyFirstLogger.log"/>
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    <appendToFile value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="2" />
    <maximumFileSize value="1MB" />
    <staticLogFileName value="true" />
    <layout type="log4net.Layout.PatternLayout">
      <param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
    </layout>
  </appender>

  <root>
    <level value="ALL" />
    <appender-ref ref="LogFileAppender" />
  </root>
</log4net>

我也在 AssemblyInfo.cs 中添加了这个:

[assembly: log4net.Config.XmlConfigurator(Watch = true)]

在我的其中一门课程中,我学习到:

private readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

并且。
_log.Info(content);

我已将Logs文件夹的完全权限授予所有用户。我的bin文件夹(服务运行的文件夹)中包含了app.configlog4net.config两个文件。但是没有生成任何日志文件。我错过了哪些设置?更新于2014年3月4日:如果你像我一样使用单独的配置文件(log4net.config),请记得在解决方案资源管理器中将Copy to output directory设置为Copy always

如果这个程序是在IIS应用程序中运行的,那么您需要给予IIS_IUSRS对日志目录的完全权限。 - PhilChuang
它作为Windows服务托管。 - Null Reference
你提到给用户授予日志文件夹的权限,但是你的服务实际上是否作为用户运行(而不是像LocalService这样的东西)? - sgmoore
属性[assembly: log4net.Config.XmlConfigurator(Watch = true)]和对LogManager.GetLogger的调用是否在同一个程序集中? - YK1
14个回答

37
请注意,当该进程作为Windows服务运行时,Environment.CurrentDirectory将为"C:\Windows\system32"。
因此,如果您将log4net配置文件(log4net.config)放在*.exe旁边,您可以使用以下代码配置log4net。
var assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
XmlConfigurator.Configure(new FileInfo(Path.Combine(assemblyFolder, "log4net.config")));

这对我来说就是这样了。还要注意,由于相同的原因,您可能不想在日志文件名中使用相对路径,因为它们最终会被放置在windows\system32下,如果权限允许写入的话。 - N8allan
好的答案。对我也起作用了。 - andrew pate

24

按设计,Log4Net是故障停止的。这意味着在运行时,log4net不会抛出意外的异常,从而潜在地导致应用程序崩溃。

因此,很难找出问题的原因。

我如何启用log4net内部调试?

来自FAQ - http://logging.apache.org/log4net/release/faq.html

  • 可以通过在应用程序的配置文件中设置一个值来启用内部调试(不是log4net配置文件,除非log4net配置数据嵌入在应用程序的配置文件中)。必须将log4net.Internal.Debug应用程序设置设置为true。例如:
<?xml version="1.0" encoding="utf-8" ?> 
<configuration>
            <appSettings>
                <add key="log4net.Internal.Debug" value="true"/>
            </appSettings> 
</configuration>
这个设置将在启动时立即读取,导致所有内部调试消息被发出。
  • 要通过编程方式启用log4net的内部调试,您需要将log4net.Util.LogLog.InternalDebugging属性设置为true。显然,设置得越早,产生的调试信息就越多。

所以我创建了一个自定义类来使用log4Net - 因为配置文件非常令人困惑,我创建了这个帮助类。

  • 您可以在应用程序中启动尽可能多的appender,因此如果一个dll调用其他dll,则两者都可以启动appenders并且两个appender都可以工作。
  • 还可以关闭appender(如文件appender),然后将其作为电子邮件发送。
Log4NetFileHelper log = new Log4NetFileHelper();
        log.Init(); //Initialize
        log.AddConsoleLogging(); //Add Console Logging
        log.AddFileLogging(Path.Combine(AssemblyDirectory, "BatchConsole.log")); 
        log.AddFileLogging(Path.Combine(AssemblyDirectory,"BatchConsole_error.log"),log4net.Core.Level.Error); 

Do set this Property to True log4net.Util.LogLog.InternalDebugging=true;

public class Log4NetFileHelper
{
    private string  DEFAULT_LOG_FILENAME=string.Format("application_log_{0}.log",DateTime.Now.ToString("yyyyMMMdd_hhmm"));
    Logger root;
    public Log4NetFileHelper()
    {

    }

    public virtual void Init()
    {
        root = ((Hierarchy)LogManager.GetRepository()).Root;
        //root.AddAppender(GetConsoleAppender());
        //root.AddAppender(GetFileAppender(sFileName));
        root.Repository.Configured = true;
    }

    #region Public Helper Methods
    #region Console Logging
    public virtual void AddConsoleLogging()
    {
        ConsoleAppender C = GetConsoleAppender();
        AddConsoleLogging(C);
    }

    public virtual void AddConsoleLogging(ConsoleAppender C)
    {
        root.AddAppender(C);
    }
    #endregion

    #region File Logging
    public virtual FileAppender AddFileLogging()
    {
        return AddFileLogging(DEFAULT_LOG_FILENAME);
    }

    public virtual FileAppender AddFileLogging(string sFileFullPath)
    {
        return AddFileLogging(sFileFullPath, log4net.Core.Level.All);
    }

    public virtual FileAppender AddFileLogging(string sFileFullPath, log4net.Core.Level threshold)
    {
        return AddFileLogging(sFileFullPath, threshold,true);  
    }

    public virtual FileAppender AddFileLogging(string sFileFullPath, log4net.Core.Level threshold, bool bAppendfile)
    {
        FileAppender appender = GetFileAppender(sFileFullPath, threshold , bAppendfile);
        root.AddAppender(appender);
        return appender;
    }

    public virtual SmtpAppender AddSMTPLogging(string smtpHost, string From, string To, string CC, string subject, log4net.Core.Level threshhold)
    {
        SmtpAppender appender = GetSMTPAppender(smtpHost, From, To, CC, subject, threshhold);
         root.AddAppender(appender);
         return appender;
    }

    #endregion


    public log4net.Appender.IAppender GetLogAppender(string AppenderName)
    {
        AppenderCollection ac = ((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root.Appenders;

        foreach(log4net.Appender.IAppender appender in ac){
            if (appender.Name == AppenderName)
            {
                return appender;
            }
        }

        return null;
    }

    public void CloseAppender(string AppenderName)
    {
        log4net.Appender.IAppender appender = GetLogAppender(AppenderName);
        CloseAppender(appender);
    }

    private void CloseAppender(log4net.Appender.IAppender appender)
    {
        appender.Close();
    }

    #endregion

    #region Private Methods

    private SmtpAppender GetSMTPAppender(string smtpHost, string From, string To, string CC, string subject, log4net.Core.Level threshhold)
    {
        SmtpAppender lAppender = new SmtpAppender();
        lAppender.Cc = CC;
        lAppender.To = To;
        lAppender.From = From;
        lAppender.SmtpHost = smtpHost;
        lAppender.Subject = subject;
        lAppender.BufferSize = 512;
        lAppender.Lossy = false;
        lAppender.Layout = new
        log4net.Layout.PatternLayout("%date{dd-MM-yyyy HH:mm:ss,fff} %5level [%2thread] %message (%logger{1}:%line)%n");
        lAppender.Threshold = threshhold;
        lAppender.ActivateOptions();
        return lAppender;
    }

    private ConsoleAppender GetConsoleAppender()
    {
        ConsoleAppender lAppender = new ConsoleAppender();
        lAppender.Name = "Console";
        lAppender.Layout = new 
        log4net.Layout.PatternLayout(" %message %n");
        lAppender.Threshold = log4net.Core.Level.All;
        lAppender.ActivateOptions();
        return lAppender;
    } 
    /// <summary>
    /// DETAILED Logging 
    /// log4net.Layout.PatternLayout("%date{dd-MM-yyyy HH:mm:ss,fff} %5level [%2thread] %message (%logger{1}:%line)%n");
    ///  
    /// </summary>
    /// <param name="sFileName"></param>
    /// <param name="threshhold"></param>
    /// <returns></returns>
    private FileAppender GetFileAppender(string sFileName , log4net.Core.Level threshhold ,bool bFileAppend)
    {
        FileAppender lAppender = new FileAppender();
        lAppender.Name = sFileName;
        lAppender.AppendToFile = bFileAppend;
        lAppender.File = sFileName;
        lAppender.Layout = new 
        log4net.Layout.PatternLayout("%date{dd-MM-yyyy HH:mm:ss,fff} %5level [%2thread] %message (%logger{1}:%line)%n");
        lAppender.Threshold = threshhold;
        lAppender.ActivateOptions();
        return lAppender;
    }

    //private FileAppender GetFileAppender(string sFileName)
    //{
    //    return GetFileAppender(sFileName, log4net.Core.Level.All,true);
    //}

    #endregion

    private void  ConfigureLog(string sFileName)
    {


    }
}

11
以下是我使用的配置: AssemblyInfo.cs
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "Log4net.config", Watch = true)]

Log4net.Config

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
    <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender,log4net">
            <param name="File" value="C:\TEMP\Logs.txt"/>
            <lockingModel type="log4net.Appender.FileAppender+MinimalLock,log4net" />
            <appendToFile value="true" />
            <rollingStyle value="Size" />
            <maxSizeRollBackups value="2" />
            <maximumFileSize value="1MB" />
            <staticLogFileName value="true" />
        <layout type="log4net.Layout.PatternLayout,log4net">
            <param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
        </layout>
    </appender>
    <root>
         <level value="ALL" />
         <appender-ref ref="LogFileAppender" />
    </root>
</log4net>

C# 代码

private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(class_name));

我在C#类库项目中设置了这个,并且所有其他项目都使用此项目引用来记录异常。

在我的情况下,问题似乎出现在FileAppender上。我将其替换为RollingFileAppender,然后它就正常工作了。之前没有任何帮助。 - realnero
只需更改 y,即可使用 GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 并且完美运作,谢谢! - Hernaldo Gonzalez

9

检查和重新检查后... :-)

你所需要做的就是在创建日志记录器之前调用XmlConfigurator.Configure();(仅一次)。

很高兴能帮助你,

Ofir


我在我的AssemblyInfo.cs文件中完成了这个操作 [assembly: log4net.Config.XmlConfigurator(Watch = true)]。 - Null Reference
我认为你需要调用XmlConfigurator.Configure(),或者更改你的assemblyInfo.cs文件,使用 [assembly: log4net.Config.XmlConfigurator(Watch = true, ConfigFile="log4net.config")]。 - sgmoore
在 AssemblyInfo.cs 文件中对我不起作用。 请在“GetLogger”之前调用“XmlConfigurator.Configure();”。 如果还是不行,请告诉我。 - Ofir

7
如果您将创建一个不同的配置文件并将log4net相关内容放在其中,那么您需要使用[assembly: log4net.Config.XmlConfigurator(ConfigFile = @"...\log4net.config", Watch = true)]而不是只使用[assembly: log4net.Config.XmlConfigurator(Watch = true)]。这个修改需要放在AssemblyInfo.cs中。
否则,您需要将配置文件中的<log4net>...</log4net>部分放在App.config中。请注意,html标签应保留且不做修改。

ConfigFile = @"...\log4net.config" <- 这里必须填写完整的目录吗?还是只需要填写 "...\log4net.config" 即可? - Null Reference
它必须是log4net.config的完整路径。例如,如果它位于您的C:\temp中,则应输入C:\temp\log4net.config。我不确定它是否接受相对路径,但值得一试。 - Alpay
这篇文章清晰地解释了使用方法。希望能对您有所帮助。 - Alpay

4

很抱歉如果有些内容看起来很显然,但这是我会检查的内容:

  • 确保你的log4net.config文件属性“Copy to Output”设置为“Always”,通过检查bin目录中是否有该文件进行验证

  • 还要注意log4net文档中与AssemblyInfo.cs属性相关的内容:

使用属性可以更清晰地定义应用程序配置将从哪里加载的方法。但值得注意的是,属性仅是被动的。它们只提供信息。 因此,如果您使用配置属性,则必须调用log4net以允许其读取属性。对LogManager.GetLogger的简单调用将导致调用程序集上的属性被读取和处理。因此,在应用程序启动尽早进行日志记录调用至关重要,尤其是在加载和调用任何外部程序集之前。

  • 为了排除故障,您可以尝试从程序集级别属性切换到显式配置调用

    XmlConfigurator.Configure();

    应该足够了。

  • 我总是将log4net.config作为完整的配置文件,以以下方式开始

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <configSections>
        <section name="log4net" 
           type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
      </configSections>
      <log4net>
    ...
      </log4net>
    </configuration>
    

只要您的配置文件是log4net.config,就不需要在app.config中添加与log4net相关的内容。


在应用程序启动早期尽快进行日志调用是必不可少的。这让我浪费了整整一天的时间,在两个完全相同配置的项目中寻找错误,其中一个正常工作而另一个却没有。 - sox with Monica

3

当你说“所有用户”拥有日志目录的完全权限时,这是否包括服务账户?

请检查LocalService、NetworkService、LocalSystem等是否具有权限(取决于服务运行的上下文)。

此外,如果您有一个测试工具将服务作为应用程序运行,当以您的用户身份运行时,日志记录是否正常工作?

如果作为应用程序无法正常运行,则可能存在log4net配置问题(其他答案已经试图解决此问题)。


1

log4net在活动用户的权限下运行。请确保活动用户具有创建/修改/删除指定文本文件的权限。


1

请上传您的应用程序,以便我可以自己调试它?

这里有几个建议需要检查:

  1. 将文件路径中的所有“\”替换为“\”

  2. 将所有log4net配置嵌入到应用程序的配置文件中。

  3. 启用log4net调试(请参见此处

  4. 尝试不同的配置。只需从互联网上获取一个示例配置。

  5. 为了确保,我会为所有用户授予最大权限访问您的日志目录

  6. 尝试卸载并重新安装服务。


1
在上面的评论中,延庭陈提到: 使用下面的代码,您可以找到在Windows计划程序下运行应用程序时log4net记录了哪些配置消息。这可能有助于某些人了解log4net,特别是在无法看到命令屏幕的服务或计划程序下运行时。
  private static void InstanceLogger()
    {
        if (logger == null)
            logger = LogManager.GetLogger(typeof(Utility));

        // Code to troubleshoot Log4Net issues through Event log viewer
        StringBuilder sb = new StringBuilder();

        foreach (log4net.Util.LogLog m in logger.Logger.Repository.ConfigurationMessages)
        {
            sb.AppendLine(m.Message);
        }

        throw new Exception("String messages: " + sb.ToString());

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接