Comment configurer log4net par programmation à partir de zéro (pas de configuration)

87

C'est une mauvaise idée, je sais, mais ... je veux configurer log4net par programmation à partir de zéro sans fichier de configuration. Je travaille sur une application de journalisation simple pour moi et mon équipe à utiliser pour un tas d'applications ministérielles relativement petites dont nous sommes responsables. Je veux qu'ils se connectent tous à la même base de données. L'application de journalisation est juste un wrapper autour de log4net avec AdoNetAppender préconfiguré.

Toutes les applications sont déployées ClickOnce, ce qui pose un petit problème avec le déploiement du fichier de configuration. Si le fichier de configuration faisait partie du projet principal, je pourrais définir ses propriétés à déployer avec l'assembly. Mais cela fait partie d'une application liée, je n'ai donc pas la possibilité de la déployer avec l'application principale. (Si ce n'est pas vrai, faites-le moi savoir)

Probablement parce que c'est une mauvaise idée, il ne semble pas y avoir beaucoup d'exemples de code disponibles pour la configuration par programmation de log4net à partir de zéro. Voici ce que j'ai jusqu'ici.

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...

Après avoir configuré tous les paramètres pour apndr, j'ai d'abord essayé ceci ...

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

Ça n'a pas marché. Puis, comme une photo dans le noir, j'ai essayé cela à la place.

BasicConfigurator.Configure(apndr)

Cela n'a pas fonctionné non plus. Quelqu'un a-t-il de bonnes références sur la façon de configurer log4net par programmation à partir de zéro sans fichier de configuration?

John M Gant
la source
Voir aussi stackoverflow.com/questions/1436713/…
Pavel Chuchuva

Réponses:

37

Une façon dont j'ai fait cela dans le passé est d'inclure le fichier de configuration en tant que ressource intégrée et d'utiliser simplement log4net.Config.Configure (Stream) .

De cette façon, je pourrais utiliser la syntaxe de configuration que je connaissais et je n'avais pas à me soucier du déploiement d'un fichier.

Jonathan Rupp
la source
2
Le nom complet de la méthode est log4net.Config.XmlConfigurator.Configure (comme dans le lien)
olorin
122

Voici un exemple de classe qui crée complètement la configuration de log4net dans le code. Je dois mentionner que la création d'un enregistreur via une méthode statique est généralement considérée comme mauvaise, mais dans mon contexte, c'est ce que je voulais. Quoi qu'il en soit, vous pouvez découper le code pour répondre à vos besoins.

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
la source
6
+1 de ma part, il semble que vous ayez la réponse ici sur la façon de faire cela purement par programme sans fichier de configuration.
Wil P
eh bien, cela ne fonctionnait toujours pas, un fichier texte vide est créé, mais rien n'y est écrit :(
Ivan G.
8
+1 pour hierarchy.Configured = true;qui fait l'affaire pour moi
Firo
1
Le truc pour moi était roller.ActivateOptions () ... Un vaudou sombre.
Asaf
1
@Legends "dnsservices.txt" est juste un nom relatif pour votre fichier journal. Semble être relatif au répertoire de travail actuel. Je l'ai changé en un chemin absolu sur le système de l'utilisateur afin que les journaux soient toujours dirigés vers un répertoire connu.
Colm Bhandal
32

Solution plus concise:

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);

N'oubliez pas d'appeler la méthode ActivateOptions :

La méthode ActivateOptions doit être appelée sur cet objet une fois que les propriétés de configuration ont été définies. Jusqu'à ce que ActivateOptions soit appelé, cet objet est dans un état indéfini et ne doit pas être utilisé.

Pavel Chuchuva
la source
L'utilisation de la surcharge BasicConfigurator.Configure (IAppender) évite beaucoup de déconner, bravo.
Shaun
1
+1 pour celui-là. L'appel ActivateOptions()est définitivement absent ou du moins pas assez souligné dans la documentation.
fbmd
5

Comme le dit Jonathan , utiliser une ressource est une bonne solution.

C'est un peu restrictif dans la mesure où le contenu des ressources intégrées sera corrigé au moment de la compilation. J'ai un composant de journalisation qui génère un XmlDocument avec une configuration de base Log4Net, en utilisant des variables définies comme appSettings (par exemple le nom de fichier pour un RollingFileAppender, le niveau de journalisation par défaut, peut-être le nom de la chaîne de connexion si vous souhaitez utiliser un AdoNetAppender). Et puis j'appelle log4net.Config.XmlConfigurator.Configurepour configurer Log4Net en utilisant l'élément racine du XmlDocument généré.

Ensuite, les administrateurs peuvent personnaliser la configuration "standard" en modifiant quelques appSettings (généralement niveau, nom de fichier, ...) ou peuvent spécifier un fichier de configuration externe pour obtenir plus de contrôle.

Joe
la source
3

Je ne peux pas dire dans l'extrait de code de la question si le "'Et ainsi de suite ..." inclut le très important apndr.ActivateOptions () qui est indiqué dans la réponse de Todd Stout. Sans ActivateOptions (), l'Appender est inactif et ne fera rien qui pourrait expliquer pourquoi il échoue.

RodKnee
la source
Je ne pense pas que j'avais ça là-dedans. Cela a peut-être été le problème. Merci.
John M Gant
3

Un peu tard pour la fête. Mais voici une configuration minimale qui a fonctionné pour moi.

Exemple de classe

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

Configuration minimale de la trace log4net (à l'intérieur du test NUnit)

[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();
}

Imprime sur l'écouteur de trace

Namespace+Bar: Logged
oleksii
la source
2
Cela fonctionne presque, mais j'avais besoin d'appeler .ActiveOptions sur PatternLayout et Appender avant que cela ne fonctionne complètement.
cjb110
Pas certain de pourquoi. Cela a fonctionné pour moi tel quel, peut-être que nous avons utilisé différentes versions.
oleksii
2

Dr. Netjes a ceci pour définir la chaîne de connexion par programme:

// 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
la source
1

// J'ai intégré trois fichiers de configuration en tant que ressource intégrée et j'y ai accès comme ceci:

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
la source
0

Il est étrange que cela BasicConfigurator.Configure(apndr)n'ait pas fonctionné. Dans mon cas, il a fait son travail ... Mais, de toute façon, voici la réponse - vous auriez dû écrire hier.Configured = true;(code c #) après avoir terminé toute la configuration.

vlad2135
la source
0

Voici un exemple de la façon dont vous pouvez créer et utiliser un AdoNetAdaptercode entièrement intégré, complètement en l'absence de App.configfichier (même pas pour Common.Logging). Allez-y, supprimez-le!

Cela présente l'avantage supplémentaire d'être résilient aux mises à jour sous les nouvelles conventions de dénomination , où le nom de l'assembly reflète désormais la version. ( Common.Logging.Log4Net1213, etc.)

[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
)

[Principale]

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
la source
0

'Solution pour 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
la source