Wie kann ich den Dateispeicherort programmgesteuert ändern?

75

Ich bin völlig neu in Log4net. Ich habe es geschafft, etwas in Gang zu bringen, indem ich eine Konfigurationsdatei hinzugefügt und einfach protokolliert habe. Ich habe den Wert fest codiert, "C:\temp\log.txt"aber das ist nicht gut genug.

Die Protokolle müssen in die speziellen Ordner verschoben werden

path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);

und dieser Pfad ändert sich je nachdem, ob Sie Windows Server 2008 oder Windows XP oder Vista usw. verwenden.

Wie kann ich den Speicherort der Datei in log4net programmgesteuert ändern?

Folgendes habe ich getan:

<configSections>
<section name="log4net"
         type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net"/>
</configSections>
<log4net>         
    <root>
        <level value="DEBUG" />
        <appender-ref ref="LogFileAppender" />
    </root>
    <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
        <param name="File" value="C:\temp\log.txt" />
        <param name="AppendToFile" value="true" />
        <rollingStyle value="Size" />
        <maxSizeRollBackups value="10" />
        <maximumFileSize value="10MB" />
        <staticLogFileName value="true" />
        <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%-5p%d{yyyy-MM-dd hh:mm:ss} – %m%n" />
        </layout>
    </appender>
</log4net>

class Program
{
    protected static readonly ILog log = LogManager.GetLogger(typeof(Program));

    static void Main(string[] args)
    {
        log4net.Config.XmlConfigurator.Configure();
        log.Warn("Log something");

        path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);


        // How can I change where I log stuff?
    }
}

Ich muss nur herausfinden, wie ich Änderungen vornehmen kann, um Inhalte dort zu protokollieren, wo ich möchte.

Irgendwelche Vorschläge? Danke vielmals

iliketocode
quelle
Sie müssen die IAppender Ihres Loggers durchsuchen und die Datei FileAppender.File auf den gewünschten Ausgabepfad einstellen. Hier ist ein wirklich gutes Beispiel .
Cam Soper

Antworten:

89

log4net kann dies für Sie erledigen. Jede Appender-Eigenschaft vom Typ string kann in diesem Fall mit dem Optionshandler log4net.Util.PatternString formatiert werden . PatternString unterstützt auch die Special Enumeration , die die folgende elegante Konfiguration ermöglicht:

<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender" >
    <file type="log4net.Util.PatternString" 
        value="%envFolderPath{CommonApplicationData}\\test.txt" />
    ...
</appender>

Hier ist ein Unit-Test, der den Pudding beweist:

[Test]
public void Load()
{
    XmlConfigurator.Configure();
    var fileAppender = LogManager.GetRepository()
        .GetAppenders().First(appender => appender is RollingFileAppender);

    var expectedFile = 
        Path.Combine(
            Environment.GetFolderPath(
                Environment.SpecialFolder.CommonApplicationData),
                "test.txt");

    Assert.That(fileAppender, 
        Is.Not.Null & Has.Property("File").EqualTo(expectedFile));
}

Der folgende Test überprüft, ob log4net tatsächlich auf die Festplatte schreibt (was dies im Grunde genommen zu einem "Integrationstest" macht, nicht zu einem Komponententest, aber wir lassen es vorerst dabei):

[Test]
public void Log4net_WritesToDisk()
{
    var expectedFile = 
        Path.Combine(
            Environment.GetFolderPath(
                Environment.SpecialFolder.CommonApplicationData),
                "test.txt");

    if (File.Exists(expectedFile))
        File.Delete(expectedFile);

    XmlConfigurator.Configure();

    var log = LogManager.GetLogger(typeof (ConfigTest));
    log.Info("Message from test");

    LogManager.Shutdown();

    Assert.That(File.ReadAllText(expectedFile), 
        Text.Contains("Message from test"));
}

NB: Ich empfehle dringend, die im obigen Beispiel gezeigte kompakte Eigenschaftssyntax zu verwenden. Wenn Sie alle "<property name =" entfernen, ist Ihre Konfiguration viel besser lesbar.

Peter Lillevold
quelle
1
Hallo DANKE, ich habe es versucht, melde mich aber immer noch nicht in der Anwendung an. Daten Ich habe im Internet nachgesehen. Ich habe etwas namens PatternString gefunden, aber ich habe keine Ahnung, wie ich es verwenden soll. Hat jemand ein Beispiel?
@brix - Meine erste Antwort hat nicht funktioniert, also musste ich mir selbst beweisen, dass log4net dies kann. PatternString ist die Lösung :)
Peter Lillevold
2
% envFolderPath {} scheint nicht Teil der aktuellen Version (1.2.10) von log4net zu sein. Ich musste r606477 aus ihrem Subversion-Repository ziehen, um diese sehr nützliche Funktion zu aktivieren. Ich würde erwarten, dass dies in der nächsten Version (1.2.11) enthalten sein wird.
Rodney Schuler
1
@rschuler: Ich habe eine Antwort hinzugefügt, die mit 1.2.10 funktioniert, siehe unten
Codeulike
3
Rufen Sie in der folgenden Antwort von @ JackAce nach dem Zurücksetzen des Dateipfads unbedingt ".ActivateOptions ()" auf, damit er aktiviert wird. Durch Aufrufen von .file wird es festgelegt, aber es wird erst verwendet, wenn ActivateOptions aufgerufen wird
Jeffrey Knight
45

Ich habe eine Mutation dieses Codes in den Interwebs gefunden:

XmlConfigurator.Configure();
log4net.Repository.Hierarchy.Hierarchy h =
(log4net.Repository.Hierarchy.Hierarchy) LogManager.GetRepository();
foreach (IAppender a in h.Root.Appenders)
{
    if (a is FileAppender)
    {
        FileAppender fa = (FileAppender)a;
        // Programmatically set this to the desired location here
        string logFileLocation = @"C:\MySpecialFolder\MyFile.log";

        // Uncomment the lines below if you want to retain the base file name
        // and change the folder name...
        //FileInfo fileInfo = new FileInfo(fa.File);
        //logFileLocation = string.Format(@"C:\MySpecialFolder\{0}", fileInfo.Name);

        fa.File = logFileLocation;
        fa.ActivateOptions();
        break;
    }
}

Das funktioniert bei mir. Unsere Anwendung muss die Protokolldatei in einem Ordner ablegen, der die Versionsnummer der App basierend auf der Datei AssemblyInfo.cs enthält.

Sie sollten in der Lage sein, die logFileLocation programmgesteuert festzulegen (z. B. können Sie Server.MapPath () verwenden, wenn dies eine Webanwendung ist), um Ihren Anforderungen zu entsprechen.

JackAce
quelle
4
Ich habe das Gefühl, dass dies viel mehr Kontrolle über den Speicherort der Datei gibt.
Fregas
16

Es sieht so aus, als würde Peters Antwort für Log4net v1.2.10.0 nicht funktionieren. Eine alternative Methode wird hier beschrieben .

Grundsätzlich besteht die Methode darin, einen benutzerdefinierten Musterkonverter für die log4net-Konfigurationsdatei zu implementieren.

Fügen Sie diese Klasse zuerst Ihrem Projekt hinzu:

public class SpecialFolderPatternConverter : log4net.Util.PatternConverter
{
    override protected void Convert(System.IO.TextWriter writer, object state)
    {
        Environment.SpecialFolder specialFolder = (Environment.SpecialFolder)Enum.Parse(typeof(Environment.SpecialFolder), base.Option, true);
        writer.Write(Environment.GetFolderPath(specialFolder));
    }
}

Richten Sie dann den File-Parameter Ihres FileAppender wie folgt ein:

<file type="log4net.Util.PatternString">
    <converter>
      <name value="folder" />
      <type value="MyAppName.SpecialFolderPatternConverter,MyAppName" />
    </converter>
    <conversionPattern value="%folder{CommonApplicationData}\\SomeOtherFolder\\log.txt" />
  </file>

Grundsätzlich %folderweist es den aufgerufenen Konverter an, folderder auf die SpecialFolderPatternConverter-Klasse verweist. Anschließend wird Convertdiese Klasse aufgerufen und der Aufzählungswert CommonApplicationData (oder was auch immer) übergeben.

codeulike
quelle
Dank dafür. Übrigens bezieht sich App in MyAppName meiner Meinung nach eher auf Project als auf App.
Jamie Kitson
Dies ist die einzige Option, die funktioniert, wenn Sie die Datei log4net.config auf Änderungen überwachen ( logging.apache.org/log4net/release/sdk/html/… )
cbp
7

Wie wäre es mit einem einfachen:

XmlConfigurator.LogFullFilename = @"c:\ProgramData\MyApp\Myapp.log";

Warum ist es so komplex, etwas wirklich Einfaches zu tun?

Eric
quelle
12
Weil sich der Pfad ändert, je nachdem, ob Sie sich in Windows Server 2008 oder WinXP oder Vista usw. befinden. In XP gibt es keinen Ordner c: \ ProgramData, nur in Win7 / Vista. Wenn wir diese App bereitstellen möchten, muss sie für alle Betriebssysteme kompatibel sein. :-) Hardcodierung ist keine gute Praxis.
Irshad
7
Ich verwende log4net v2.0.3 und diese Eigenschaft ist in XmlConfigurator nicht verfügbar. Muss entfernt worden sein, seit die Antwort veröffentlicht wurde.
Appetere
Danke Jason für die Korrektur meines Englisch, das ist nicht meine Hauptsprache;) Danke!
Eric
@Irshad: Ja, aber wir können die Konfiguration (zum Beispiel) aus einer Datenbank lesen. Wenn wir diesen Pfad programmgesteuert ändern können, ist es manchmal besser
Hoàng Long
4

Das hat bei mir funktioniert:

  <log4net>
    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
..
      <file value="${APPDATA}\MyApp\MyApp Client\logs\Log.txt"/>
..
  </log4net>

Wenn ich in spezielle Ordner schreiben muss, habe ich hier Hilfe gefunden (2. und 3. Beispiel).

Bearbeiten:

Um OP zu beantworten. Dies funktioniert für den Bereich "Alle Benutzer":

      ...
      <file value="${ALLUSERSPROFILE}\MyApp\MyApp Client\logs\Log.txt"/>
      ...

Dies ist normalerweise "C: \ ProgramData" in neueren Windows-Versionen.

Siehe auch diese:
Wie gebe ich einen allgemeinen Anwendungsdatenordner für log4net an? == https://stackoverflow.com/a/1889591/503621 und Kommentare
&
https://superuser.com/q/405097/47628
https://stackoverflow.com/a/5550502/503621

bshea
quelle
Environment.SpecialFolder.CommonApplicationDataund $(APPDATA)sind nicht der gleiche Ordner
Sebastian Negraszus
Sie können stattdessen auch "$ {ALLUSERSPROFILE}" ausprobieren. .
bshea
3

So ändern Sie auch den Pfad des Fehlerprotokolls (basierend auf der Antwort von JackAce):

private static void SetLogPath(string path, string errorPath)
{
    XmlConfigurator.Configure();
    log4net.Repository.Hierarchy.Hierarchy h =
    (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
    foreach (var a in h.Root.Appenders)
    {
        if (a is log4net.Appender.FileAppender)
        {
            if (a.Name.Equals("LogFileAppender"))
            { 
                log4net.Appender.FileAppender fa = (log4net.Appender.FileAppender)a;                    
                string logFileLocation = path; 
                fa.File = logFileLocation;                   
                fa.ActivateOptions();
            }
            else if (a.Name.Equals("ErrorFileAppender"))
            {
                log4net.Appender.FileAppender fa = (log4net.Appender.FileAppender)a;
                string logFileLocation = errorPath;
                fa.File = logFileLocation;
                fa.ActivateOptions();
            }
        }
    }
}
Jim109
quelle
3

JackAces Antwort, nur präziser mit Linq:

C #

XmlConfigurator.Configure();
var appender = (LogManager.GetRepository() as Hierarchy).Root.Appenders
    .OfType<FileAppender>()
    .First();

appender.File = logPath;
appender.ActivateOptions();

VB.NET

XmlConfigurator.Configure()
Dim appender = CType(LogManager.GetRepository(), Hierarchy).Root.Appenders _
    .OfType(FileAppender)() _
    .First()

appender.File = logPath
appender.ActivateOptions()
jmjohnson85
quelle
2

Großartiger Anwendungsfall für LINQs- OfType<T>Filter:

/// <summary>
/// Applies a transformation to the filenames of all FileAppenders.
/// </summary>
public static void ChangeLogFile(Func<string,string> transformPath)
{
    // iterate over all FileAppenders
    foreach (var fileAppender in LogManager.GetRepository().GetAppenders().OfType<FileAppender>())
    {
        // apply transformation to the filename
        fileAppender.File = transformPath(fileAppender.File);
        // notify the logging subsystem of the configuration change
        fileAppender.ActivateOptions();
    }
}

Wenn der Dateiname in der app.config lautet log.txt, ändert sich die Protokollausgabe in logs/some_name_log.txt:

ChangeLogFile(path => Path.Combine("logs", $"some_name_{Path.GetFileName(path)}"));

Um das ursprüngliche Problem des OP zu beantworten, wäre dies:

ChangeLogFile(path => Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), path));
Gigo
quelle
Als ich dies tat, erstellte es einfach eine leere Datei mit dem neuen Namen neu, schrieb dann aber Logger.GetLoggerstattdessen weiter auf den alten Namen.
Jay Sullivan
1

Alternativ dazu können Sie Umgebungsvariablen und anpassbare Muster in der Konfigurationsdatei verwenden. Siehe diese Antwort auf eine ähnliche Frage .

Weitere Informationen finden Sie in den Versionshinweisen zu Log4Net V1.2.10 unter "PatternString für musterbasierte Konfiguration" .

Auch wenn Sie in ein Verzeichnis wie Enviroment.SpecialFolder.CommonApplicationData schreiben möchten, müssen Sie Folgendes berücksichtigen:

  • Haben alle Instanzen Ihrer Anwendung für alle Benutzer Schreibzugriff auf die Protokolldatei? Ich glaube beispielsweise nicht, dass Nicht-Administratoren in Enviroment.SpecialFolder.CommonApplicationData schreiben können.

  • Konflikt, wenn mehrere Instanzen Ihrer Anwendung (für denselben oder verschiedene Benutzer) versuchen, dieselbe Datei zu verwenden. Sie können das "Minimal Locking Model" verwenden (siehehttp://logging.apache.org/log4net/release/config-examples.html , damit mehrere Prozesse in dieselbe Protokolldatei schreiben können, dies hat jedoch wahrscheinlich Auswirkungen auf die Leistung. Oder Sie können jedem Prozess eine andere Protokolldatei zuweisen, z. B. indem Sie die Prozess-ID mithilfe eines anpassbaren Musters in den Dateinamen aufnehmen.

Joe
quelle
1

In der aktuellen Version von Log4Net (2.0.8.0) können Sie einfach <file value="${ProgramData}\myFolder\LogFiles\" />für C:\ProgramData\..und ${LocalAppData}für verwendenC:\Users\user\AppData\Local\

Apfelkuacha
quelle
0

Wenn Sie auf unbekannten Systemen bereitstellen müssen und die einfache Lösung von Philipp M auch mit verschiedenen speziellen Ordnern verwenden möchten, können Sie den gewünschten speziellen Ordnerpfad abrufen und eine benutzerdefinierte env-Variable festlegen, bevor Sie die log4net-Konfiguration laden. string localData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); Environment.SetEnvironmentVariable("MY_FOLDER_DATA", localData); XmlConfigurator.Configure( ...

... nur um sicherzugehen, dass die env-Variable existiert und den gewünschten Wert hat.

user3529289
quelle