So konfigurieren Sie log4net programmgesteuert von Grund auf neu (keine Konfiguration)

86

Ich weiß, dass dies eine schlechte Idee ist, aber ... Ich möchte log4net programmgesteuert von Grund auf ohne Konfigurationsdatei konfigurieren. Ich arbeite an einer einfachen Protokollierungsanwendung, die ich und mein Team für eine Reihe relativ kleiner Abteilungsanwendungen verwenden können, für die wir verantwortlich sind. Ich möchte, dass sie sich alle in derselben Datenbank anmelden. Die Protokollierungsanwendung ist nur ein Wrapper um log4net mit dem vorkonfigurierten AdoNetAppender.

Alle Anwendungen werden von ClickOnce bereitgestellt, was ein kleines Problem bei der Bereitstellung der Konfigurationsdatei darstellt. Wenn die Konfigurationsdatei Teil des Kernprojekts wäre, könnte ich ihre Eigenschaften für die Bereitstellung mit der Assembly festlegen. Da es jedoch Teil einer verknüpften Anwendung ist, habe ich nicht die Möglichkeit, es mit der Hauptanwendung bereitzustellen. (Wenn das nicht stimmt, lass es mich bitte wissen).

Wahrscheinlich, weil es eine schlechte Idee ist, scheint nicht viel Beispielcode für die programmgesteuerte Konfiguration von log4net von Grund auf verfügbar zu sein. Folgendes habe ich bisher.

Dim apndr As New AdoNetAppender()
apndr.CommandText = "INSERT INTO LOG_ENTRY (LOG_DTM, LOG_LEVEL, LOGGER, MESSAGE, PROGRAM, USER_ID, MACHINE, EXCEPTION) VALUES (@log_date, @log_level, @logger, @message, @program, @user, @machine, @exception)"
apndr.ConnectionString = connectionString
apndr.ConnectionType = "System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
apndr.CommandType = CommandType.Text
Dim logDate As New AdoNetAppenderParameter()
logDate.ParameterName = "@log_date"
logDate.DbType = DbType.DateTime
logDate.Layout = New RawTimeStampLayout()
apndr.AddParameter(logDate)
Dim logLevel As New AdoNetAppenderParameter()
logLevel.ParameterName = "@log_level"
'And so forth...

Nachdem apndrich alle Parameter für konfiguriert hatte , habe ich dies zuerst versucht ...

Dim hier As Hierarchy = DirectCast(LogManager.GetRepository(), Hierarchy)
hier.Root.AddAppender(apndr)

Es hat nicht funktioniert. Dann, als ein Schuss in die Dunkelheit, versuchte ich dies stattdessen.

BasicConfigurator.Configure(apndr)

Das hat auch nicht funktioniert. Hat jemand gute Referenzen, wie man log4net programmgesteuert von Grund auf ohne Konfigurationsdatei konfiguriert?

John M Gant
quelle
Siehe auch stackoverflow.com/questions/1436713/…
Pavel Chuchuva

Antworten:

37

Eine Möglichkeit, wie ich dies in der Vergangenheit getan habe, besteht darin, die Konfigurationsdatei als eingebettete Ressource einzuschließen und nur log4net.Config.Configure (Stream) zu verwenden. .

Auf diese Weise konnte ich die mir vertraute Konfigurationssyntax verwenden und musste mir keine Sorgen um die Bereitstellung einer Datei machen.

Jonathan Rupp
quelle
2
Der vollständige Methodenname lautet log4net.Config.XmlConfigurator.Configure (wie im Link)
Olorin
122

Hier ist eine Beispielklasse, die die log4net-Konfiguration vollständig im Code erstellt. Ich sollte erwähnen, dass das Erstellen eines Loggers über eine statische Methode im Allgemeinen als schlecht angesehen wird, aber in meinem Kontext ist dies das, was ich wollte. Unabhängig davon können Sie den Code entsprechend Ihren Anforderungen zusammenstellen.

using log4net;
using log4net.Repository.Hierarchy;
using log4net.Core;
using log4net.Appender;
using log4net.Layout;

namespace dnservices.logging
{
public class Logger
{
    private PatternLayout _layout = new PatternLayout();
    private const string LOG_PATTERN = "%d [%t] %-5p %m%n";

    public string DefaultPattern
    {
        get { return LOG_PATTERN; }
    }

    public Logger()
    {
        _layout.ConversionPattern = DefaultPattern;
        _layout.ActivateOptions();
    }

    public PatternLayout DefaultLayout
    {
        get { return _layout; }
    }

    public void AddAppender(IAppender appender)
    {
        Hierarchy hierarchy = 
            (Hierarchy)LogManager.GetRepository();

        hierarchy.Root.AddAppender(appender);
    }

    static Logger()
    {
        Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
        TraceAppender tracer = new TraceAppender();
        PatternLayout patternLayout = new PatternLayout();

        patternLayout.ConversionPattern = LOG_PATTERN;
        patternLayout.ActivateOptions();

        tracer.Layout = patternLayout;
        tracer.ActivateOptions();
        hierarchy.Root.AddAppender(tracer);

        RollingFileAppender roller = new RollingFileAppender();
        roller.Layout = patternLayout;
        roller.AppendToFile = true;
        roller.RollingStyle = RollingFileAppender.RollingMode.Size;
        roller.MaxSizeRollBackups = 4;
        roller.MaximumFileSize = "100KB";
        roller.StaticLogFileName = true;
        roller.File = "dnservices.txt";
        roller.ActivateOptions();
        hierarchy.Root.AddAppender(roller);

        hierarchy.Root.Level = Level.All;
        hierarchy.Configured = true;
    }

    public static ILog Create()
    {
        return LogManager.GetLogger("dnservices");
    }
}

}}

Todd Stout
quelle
6
+1 von mir, es scheint, als hätten Sie hier die Antwort, wie Sie dies rein programmgesteuert ohne Konfigurationsdatei tun können.
Wil P
Nun, es hat immer noch nicht funktioniert, eine leere Textdatei wird erstellt, es wird jedoch nichts darauf geschrieben :(
Ivan G.
8
+1 für hierarchy.Configured = true;die der Trick für mich
tut
1
Der Trick für mich war roller.ActivateOptions () ... Etwas dunkles Voodoo.
Asaf
1
@Legends "dnsservices.txt" ist nur ein relativer Name für Ihre Protokolldatei. Scheint relativ zum aktuellen Arbeitsverzeichnis zu sein. Ich habe es in einen absoluten Pfad auf dem System des Benutzers geändert, damit die Protokolle immer in ein bekanntes Verzeichnis verschoben werden.
Colm Bhandal
31

Prägnantere Lösung:

var layout = new PatternLayout("%-4timestamp [%thread] %-5level %logger %ndc - %message%newline");
var appender = new RollingFileAppender {
    File = "my.log",
    Layout = layout
};
layout.ActivateOptions();
appender.ActivateOptions();
BasicConfigurator.Configure(appender);

Vergessen Sie nicht, die ActivateOptions- Methode aufzurufen :

Die ActivateOptions-Methode muss für dieses Objekt aufgerufen werden, nachdem die Konfigurationseigenschaften festgelegt wurden. Bis zum Aufruf von ActivateOptions befindet sich dieses Objekt in einem undefinierten Zustand und darf nicht verwendet werden.

Pavel Chuchuva
quelle
Die Verwendung der Überlastung von BasicConfigurator.Configure (IAppender) erspart viel Durcheinander, Prost.
Shaun
1
+1 für diesen. Anrufe ActivateOptions()fehlen definitiv oder werden in den Dokumenten zumindest nicht ausreichend hervorgehoben.
fbmd
5

Als Jonathan sagt, ist die Verwendung einer Ressource eine gute Lösung.

Es ist insofern etwas einschränkend, als der Inhalt der eingebetteten Ressourcen beim Kompilieren korrigiert wird. Ich habe eine Protokollierungskomponente, die ein XmlDocument mit einer grundlegenden Log4Net-Konfiguration unter Verwendung von als appSettings definierten Variablen generiert (z. B. Dateiname für einen RollingFileAppender, Standardprotokollierungsstufe, möglicherweise Name der Verbindungszeichenfolge, wenn Sie einen AdoNetAppender verwenden möchten). Und dann rufe ich log4net.Config.XmlConfigurator.Configureauf, um Log4Net mit dem Stammelement des generierten XmlDocument zu konfigurieren.

Anschließend können Administratoren die "Standard" -Konfiguration anpassen, indem sie einige appSettings (normalerweise Ebene, Dateiname, ...) ändern oder eine externe Konfigurationsdatei angeben, um mehr Kontrolle zu erhalten.

Joe
quelle
3

Ich kann im Code-Snippet der Frage nicht sagen, ob das "'Und so weiter ..." das sehr wichtige apndr.ActivateOptions () enthält, das in Todd Stouts Antwort angegeben ist. Ohne ActivateOptions () ist der Appender inaktiv und unternimmt nichts, was erklären könnte, warum er fehlschlägt.

RodKnee
quelle
Ich glaube nicht, dass ich das da drin hatte. Das könnte das Problem gewesen sein. Vielen Dank.
John M Gant
3

Ein bisschen spät für die Party. Aber hier ist eine minimale Konfiguration, die für mich funktioniert hat.

Beispielklasse

public class Bar
{
    private readonly ILog log = LogManager.GetLogger(typeof(Bar));
    public void DoBar() { log.Info("Logged"); }
}

Minimale log4net-Trace-Konfiguration (innerhalb des NUnit-Tests)

[Test]
public void Foo()
{
    var tracer = new TraceAppender();
    var hierarchy = (Hierarchy)LogManager.GetRepository();
    hierarchy.Root.AddAppender(tracer);
    var patternLayout = new PatternLayout {ConversionPattern = "%m%n"};
    tracer.Layout = patternLayout;
    hierarchy.Configured = true;

    var bar = new Bar();
    bar.DoBar();
}

Druckt in den Trace-Listener

Namespace+Bar: Logged
oleksii
quelle
2
Das funktioniert fast, aber ich musste .ActiveOptions für das PatternLayout und den Appender aufrufen, bevor es vollständig funktionieren würde.
cjb110
Nicht sicher warum. Es hat bei mir so funktioniert wie es ist, vielleicht haben wir verschiedene Versionen verwendet.
Oleksii
2

Dr. Netjes hat dies zum programmgesteuerten Einstellen des Verbindungsstrings:

// Get the Hierarchy object that organizes the loggers
log4net.Repository.Hierarchy.Hierarchy hier = 
  log4net.LogManager.GetLoggerRepository() as log4net.Repository.Hierarchy.Hierarchy;

if (hier != null)
{
  //get ADONetAppender
  log4net.Appender.ADONetAppender adoAppender = 
    (log4net.Appender.ADONetAppender)hier.GetLogger("MyProject",
      hier.LoggerFactory).GetAppender("ADONetAppender");
  if (adoAppender != null)
  {
    adoAppender.ConnectionString =
      System.Configuration.ConfigurationSettings.AppSettings["MyConnectionString"];
    adoAppender.ActivateOptions(); //refresh settings of appender
  }
}
Jeroen K.
quelle
1

// Ich habe drei Konfigurationsdateien als eingebettete Ressource eingebettet und greife folgendermaßen darauf zu:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Resources;
using System.IO;

namespace Loader
{
  class Program
  {
    private static log4net.ILog CustomerLog = log4net.LogManager.GetLogger("CustomerLogging");
    private static log4net.ILog OrderLog = log4net.LogManager.GetLogger("OrderLogging");
    private static log4net.ILog DetailsLog = log4net.LogManager.GetLogger("OrderDetailLogging");


    static void Main(string[] args)
    {
      // array of embedded log4net config files
      string[] configs = { "Customer.config", "Order.config", "Detail.config"};

      foreach (var config in configs)
      {
        // build path to assembly config
        StringBuilder sb = new StringBuilder();
        sb.Append(System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
        sb.Append(".");
        sb.Append(config);

        // convert to a stream
        Stream configStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(sb.ToString());

        // configure logger with ocnfig stream
        log4net.Config.XmlConfigurator.Configure(configStream);

        // test logging
        CustomerLog.Info("Begin logging with: " + config);
        OrderLog.Info("Begin logging with: " + config);
        DetailsLog.Info("Begin logging with: " + config);
        for (int iX = 0; iX < 10; iX++)
        {
          CustomerLog.Info("iX=" + iX);
          OrderLog.Info("iX=" + iX);
          DetailsLog.Info("iX=" + iX);
        }
        CustomerLog.Info("Ending logging with: " + config);
        OrderLog.Info("Ending logging with: " + config);
        DetailsLog.Info("Ending logging with: " + config);
      }

    }
  }
}
zeb ula
quelle
0

Es ist seltsam, dass BasicConfigurator.Configure(apndr)das nicht funktioniert hat. In meinem Fall hat es seinen Job gemacht ... Aber hier ist die Antwort - Sie sollten hier.Configured = true;(c # -Code) geschrieben haben, nachdem Sie alle Einstellungen abgeschlossen haben.

vlad2135
quelle
0

Hier ist ein Beispiel dafür, wie Sie einen AdoNetAdaptervollständig in Code enthaltenen Code erstellen und verwenden können, ohne dass eine App.configDatei vorhanden ist (nicht einmal fürCommon.Logging ). Mach weiter, lösche es!

Dies hat den zusätzlichen Vorteil, dass es gemäß den neuen Namenskonventionen , bei denen der Assemblyname jetzt die Version widerspiegelt, gegenüber Aktualisierungen stabil ist. ( Common.Logging.Log4Net1213usw.)

[SQL]

CREATE TABLE [Log](
  [Id] [int] IDENTITY(1,1) NOT NULL,
  [Date] [datetime] NOT NULL,
  [Thread] [varchar](255) NOT NULL,
  [Level] [varchar](20) NOT NULL,
  [Source] [varchar](255) NOT NULL,
  [Message] [varchar](max) NOT NULL,
  [Exception] [varchar](max) NOT NULL
)

[Main]

Imports log4net
Imports log4net.Core
Imports log4net.Layout
Imports log4net.Config
Imports log4net.Appender

Module Main
  Sub Main()
    Dim oLogger As ILog
    Dim sInput As String
    Dim iOops As Integer

    BasicConfigurator.Configure(New DbAppender)
    oLogger = LogManager.GetLogger(GetType(Main))

    Console.Write("Command: ")

    Do
      Try
        sInput = Console.ReadLine.Trim

        Select Case sInput.ToUpper
          Case "QUIT" : Exit Do
          Case "OOPS" : iOops = String.Empty
          Case Else : oLogger.Info(sInput)
        End Select

      Catch ex As Exception
        oLogger.Error(ex.Message, ex)

      End Try

      Console.Clear()
      Console.Write("Command: ")
    Loop
  End Sub
End Module

[DbAppender]

Imports log4net
Imports log4net.Core
Imports log4net.Layout
Imports log4net.Appender
Imports log4net.Repository.Hierarchy

Public Class DbAppender
  Inherits AdoNetAppender

  Public Sub New()
    MyBase.BufferSize = 1
    MyBase.CommandText = Me.CommandText

    Me.Parameters.ForEach(Sub(Parameter As DbParameter)
                            MyBase.AddParameter(Parameter)
                          End Sub)

    Me.ActivateOptions()
  End Sub



  Protected Overrides Function CreateConnection(ConnectionType As Type, ConnectionString As String) As IDbConnection
    Return MyBase.CreateConnection(GetType(System.Data.SqlClient.SqlConnection), "Data Source=(local);Initial Catalog=Logger;Persist Security Info=True;User ID=username;Password=password")
  End Function



  Private Overloads ReadOnly Property CommandText As String
    Get
      Dim _
        sColumns,
        sValues As String

      sColumns = Join(Me.Parameters.Select(Function(P As DbParameter) P.DbColumn).ToArray, ",")
      sValues = Join(Me.Parameters.Select(Function(P As DbParameter) P.ParameterName).ToArray, ",")

      Return String.Format(COMMAND_TEXT, sColumns, sValues)
    End Get
  End Property



  Private ReadOnly Property Parameters As List(Of DbParameter)
    Get
      Parameters = New List(Of DbParameter)
      Parameters.Add(Me.LogDate)
      Parameters.Add(Me.Thread)
      Parameters.Add(Me.Level)
      Parameters.Add(Me.Source)
      Parameters.Add(Me.Message)
      Parameters.Add(Me.Exception)
    End Get
  End Property



  Private ReadOnly Property LogDate As DbParameter
    Get
      Return New DbParameter("Date", DbType.Date, 0, New DbPatternLayout("%date{yyyy-MM-dd HH:mm:ss.fff}"))
    End Get
  End Property



  Private ReadOnly Property Thread As DbParameter
    Get
      Return New DbParameter("Thread", DbType.String, 255, New DbPatternLayout("%thread"))
    End Get
  End Property



  Private ReadOnly Property Level As DbParameter
    Get
      Return New DbParameter("Level", DbType.String, 50, New DbPatternLayout("%level"))
    End Get
  End Property



  Private ReadOnly Property Source As DbParameter
    Get
      Return New DbParameter("Source", DbType.String, 255, New DbPatternLayout("%logger.%M()"))
    End Get
  End Property



  Private ReadOnly Property Message As DbParameter
    Get
      Return New DbParameter("Message", DbType.String, 4000, New DbPatternLayout("%message"))
    End Get
  End Property



  Private ReadOnly Property Exception As DbParameter
    Get
      Return New DbParameter("Exception", DbType.String, 2000, New DbExceptionLayout)
    End Get
  End Property



  Private Const COMMAND_TEXT As String = "INSERT INTO Log ({0}) VALUES ({1})"
End Class

[DbParameter]

Imports log4net
Imports log4net.Core
Imports log4net.Layout
Imports log4net.Appender
Imports log4net.Repository.Hierarchy

Public Class DbParameter
  Inherits AdoNetAppenderParameter

  Private ReadOnly Name As String

  Public Sub New(Name As String, Type As DbType, Size As Integer, Layout As ILayout)
    With New RawLayoutConverter
      Me.Layout = .ConvertFrom(Layout)
    End With

    Me.Name = Name.Replace("@", String.Empty)
    Me.ParameterName = String.Format("@{0}", Me.Name)
    Me.DbType = Type
    Me.Size = Size
  End Sub



  Public ReadOnly Property DbColumn As String
    Get
      Return String.Format("[{0}]", Me.Name)
    End Get
  End Property
End Class

[DbPatternLayout]

Imports log4net
Imports log4net.Core
Imports log4net.Layout
Imports log4net.Appender
Imports log4net.Repository.Hierarchy

Public Class DbPatternLayout
  Inherits PatternLayout

  Public Sub New(Pattern As String)
    Me.ConversionPattern = Pattern
    Me.ActivateOptions()
  End Sub
End Class

[DbExceptionLayout]

Imports log4net
Imports log4net.Core
Imports log4net.Layout
Imports log4net.Appender
Imports log4net.Repository.Hierarchy

Public Class DbExceptionLayout
  Inherits ExceptionLayout

  Public Sub New()
    Me.ActivateOptions()
  End Sub
End Class
InteXX
quelle
0

'Lösung für Vb.Net

Private Shared EscanerLog As log4net.ILog = log4net.LogManager.GetLogger("Log4Net.Config")

Public Sub New(ByVal sIDSesion As String)
    Dim sStream As Stream
    Dim JsText As String
    Using reader As New StreamReader((GetType(ClsGestorLogsTraza).Assembly).GetManifestResourceStream("Comun.Log4Net.Config"))
        JsText = reader.ReadToEnd()
        sStream = GenerateStreamFromString(JsText)
        log4net.Config.XmlConfigurator.Configure(sStream)
    End Using
End Sub

Public Function GenerateStreamFromString(ByVal s As String) As Stream
    Dim stream = New MemoryStream()
    Dim writer = New StreamWriter(stream)
    writer.Write(s)
    writer.Flush()
    stream.Position = 0
    Return stream
End Function

Public Function StreamFromResource(ByVal sFilename As String) As Stream
    Dim nAssembly As System.Reflection.Assembly = System.Reflection.Assembly.GetExecutingAssembly()
    Dim s As Stream = nAssembly.GetManifestResourceStream(System.Reflection.MethodBase.GetCurrentMethod.DeclaringType, sFilename)
    Return s
End Function
Juver Paredes
quelle