Cómo configurar log4net programáticamente desde cero (sin configuración)

87

Esta es una mala idea, lo sé, pero ... quiero configurar log4net programáticamente desde cero sin ningún archivo de configuración. Estoy trabajando en una aplicación de registro simple para que mi equipo y yo la usemos para un montón de aplicaciones departamentales relativamente pequeñas de las que somos responsables. Quiero que todos inicien sesión en la misma base de datos. La aplicación de registro es solo una envoltura de log4net con AdoNetAppender preconfigurado.

Todas las aplicaciones están implementadas con ClickOnce, lo que presenta un pequeño problema con la implementación del archivo de configuración. Si el archivo de configuración fuera parte del proyecto principal, podría configurar sus propiedades para implementar con el ensamblaje. Pero es parte de una aplicación vinculada, por lo que no tengo la opción de implementarla con la aplicación principal. (Si eso no es cierto, alguien hágamelo saber).

Probablemente porque es una mala idea, no parece haber mucho código de muestra disponible para configurar log4net mediante programación desde cero. Esto es lo que tengo hasta ahora.

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

Después de configurar todos los parámetros apndr, al principio probé esto ...

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

No funcionó. Luego, como un tiro en la oscuridad, probé esto en su lugar.

BasicConfigurator.Configure(apndr)

Eso tampoco funcionó. ¿Alguien tiene buenas referencias sobre cómo configurar log4net programáticamente desde cero sin un archivo de configuración?

John M. Gant
fuente
Véase también stackoverflow.com/questions/1436713/…
Pavel Chuchuva

Respuestas:

37

Una forma en la que hice esto en el pasado es incluir el archivo de configuración como un recurso incrustado y solo usé log4net.Config.Configure (Stream) .

De esa manera, podía usar la sintaxis de configuración con la que estaba familiarizado y no tenía que preocuparme por la implementación de un archivo.

Jonathan Rupp
fuente
2
El nombre completo del método es log4net.Config.XmlConfigurator.Configure (como en el enlace)
olorin
122

Aquí hay una clase de ejemplo que crea la configuración de log4net completamente en código. Debo mencionar que crear un registrador a través de un método estático generalmente se considera malo, pero en mi contexto, esto es lo que quería. Independientemente, puede dividir el código para satisfacer sus necesidades.

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
fuente
6
+1 de mí, parece que aquí tienes la respuesta de cómo hacer esto puramente programáticamente sin un archivo de configuración.
Wil P
bueno, todavía no funcionó, se crea un archivo de texto vacío, sin embargo, no se escribe nada en él :(
Ivan G.
8
+1 para hierarchy.Configured = true;lo que me funciona
Firo
1
El truco para mí fue roller.ActivateOptions () ... Un vudú oscuro.
Asaf
1
@Legends "dnsservices.txt" es solo un nombre relativo para su archivo de registro. Parece ser relativo al directorio de trabajo actual. Lo cambié a una ruta absoluta en el sistema del usuario para que los registros siempre vayan a un directorio conocido.
Colm Bhandal
32

Solución más concisa:

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

No olvide llamar al método ActivateOptions :

El método ActivateOptions debe llamarse en este objeto después de que se hayan establecido las propiedades de configuración. Hasta que se llame ActivateOptions, este objeto está en un estado indefinido y no se debe utilizar.

Pavel Chuchuva
fuente
El uso de la sobrecarga de BasicConfigurator.Configure (IAppender) ahorra mucho tiempo, salud.
Shaun
1
+1 para ese. La llamada ActivateOptions()definitivamente falta o al menos no se señala lo suficiente en los documentos.
fbmd
5

Como dice Jonathan , usar un recurso es una buena solución.

Es un poco restrictivo porque el contenido de los recursos incrustados se corregirá en el momento de la compilación. Tengo un componente de registro que genera un XmlDocument con una configuración básica de Log4Net, usando variables definidas como appSettings (por ejemplo, nombre de archivo para un RollingFileAppender, nivel de registro predeterminado, tal vez el nombre de la cadena de conexión si desea usar un AdoNetAppender). Y luego llamo log4net.Config.XmlConfigurator.Configurepara configurar Log4Net usando el elemento raíz del XmlDocument generado.

Luego, los administradores pueden personalizar la configuración "estándar" modificando algunos ajustes de aplicación (normalmente nivel, nombre de archivo, ...) o pueden especificar un archivo de configuración externo para obtener más control.

Joe
fuente
3

No puedo decir en el fragmento de código de la pregunta si "'Y así sucesivamente ..." incluye el apndr.ActivateOptions () muy importante que se indica en la respuesta de Todd Stout. Sin ActivateOptions (), el Appender está inactivo y no hará nada que pueda explicar por qué está fallando.

RodKnee
fuente
No creo que tuviera eso ahí. Ese puede haber sido el problema. Gracias.
John M Gant
3

Un poco tarde para la fiesta. Pero aquí hay una configuración mínima que funcionó para mí.

Clase de muestra

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

Configuración mínima de seguimiento log4net (dentro de la prueba 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 al oyente de seguimiento

Namespace+Bar: Logged
oleksii
fuente
2
Eso casi funciona, pero necesitaba llamar .ActiveOptions en PatternLayout y Appender antes de que funcionara por completo.
cjb110
No estoy seguro de por qué. Me funcionó tal como está, tal vez usamos diferentes versiones.
oleksii
2

Dr. Netjes tiene esto para configurar la cadena de conexión mediante programación:

// 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
fuente
1

// He incrustado tres archivos de configuración como un recurso incrustado y accedo a ellos así:

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
fuente
0

Es extraño que BasicConfigurator.Configure(apndr)no haya funcionado. En mi caso, hizo su trabajo ... Pero, de todos modos, aquí va la respuesta: debería haber escrito hier.Configured = true;(código c #) después de haber terminado toda la configuración.

vlad2135
fuente
0

Aquí hay un ejemplo de sopa a nueces de cómo puede crear y usar un AdoNetAdaptercódigo completamente en, completamente en ausencia de cualquier App.configarchivo (ni siquiera para Common.Logging). ¡Adelante, bórralo!

Esto tiene el beneficio adicional de ser resistente a las actualizaciones bajo las nuevas convenciones de nomenclatura , donde el nombre del ensamblado ahora refleja la versión. ( 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
)

[Principal]

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
fuente
0

'Solución para 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
fuente