Como configurar log4net programaticamente do zero (sem configuração)

87

Esta é uma má ideia, eu sei, mas ... Quero configurar log4net programaticamente do zero, sem arquivo de configuração. Estou trabalhando em um aplicativo de registro simples para mim e minha equipe para usar em um monte de aplicativos departamentais relativamente pequenos pelos quais somos responsáveis. Quero que todos eles façam login no mesmo banco de dados. O aplicativo de registro é apenas um invólucro do log4net com o AdoNetAppender pré-configurado.

Todos os aplicativos são implantados com ClickOnce, o que apresenta um pequeno problema com a implantação do arquivo de configuração. Se o arquivo de configuração fizesse parte do projeto principal, eu poderia definir suas propriedades para implantar com o assembly. Mas é parte de um aplicativo vinculado, então não tenho a opção de implantá-lo com o aplicativo principal. (Se isso não for verdade, alguém por favor me avise).

Provavelmente por ser uma má ideia, não parece haver muitos códigos de amostra disponíveis para configurar programaticamente o log4net do zero. Aqui está o que eu tenho até agora.

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

Depois de configurar todos os parâmetros para apndr, tentei primeiro isso ...

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

Não funcionou. Então, como um tiro no escuro, tentei fazer isso.

BasicConfigurator.Configure(apndr)

Isso também não funcionou. Alguém tem boas referências sobre como configurar log4net programaticamente do zero, sem arquivo de configuração?

John M Gant
fonte
Consulte também stackoverflow.com/questions/1436713/…
Pavel Chuchuva

Respostas:

37

Uma maneira de fazer isso no passado é incluir o arquivo de configuração como um recurso incorporado e usar apenas log4net.Config.Configure (Stream) .

Dessa forma, eu poderia usar a sintaxe de configuração com a qual estava familiarizado e não precisava me preocupar em implantar um arquivo.

Jonathan Rupp
fonte
2
O nome completo do método é log4net.Config.XmlConfigurator.Configure (como no link)
olorin
122

Aqui está um exemplo de classe que cria a configuração log4net completamente em código. Devo mencionar que criar um logger por meio de um método estático geralmente é visto como ruim, mas no meu contexto, era isso que eu queria. Independentemente disso, você pode dividir o código para atender às suas necessidades.

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
fonte
6
+1 de minha parte, parece que você obteve a resposta aqui sobre como fazer isso de forma puramente programática, sem arquivo de configuração.
Wil P
bem, ainda não funcionou, um arquivo de texto vazio foi criado, mas nada foi escrito nele :(
Ivan G.
8
+1 para o hierarchy.Configured = true;qual funciona para mim
Firo
1
O truque para mim era roller.ActivateOptions () ... Some dark Voodoo.
Asaf de
1
@Legends "dnsservices.txt" é apenas um nome relativo para seu arquivo de log. Parece ser relativo ao diretório de trabalho atual. Eu mudei para um caminho absoluto no sistema do usuário para que os logs sempre fossem para um diretório conhecido.
Colm Bhandal
32

Solução mais 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);

Não se esqueça de chamar o método ActivateOptions :

O método ActivateOptions deve ser chamado neste objeto após a definição das propriedades de configuração. Até que ActivateOptions seja chamado, esse objeto está em um estado indefinido e não deve ser usado.

Pavel Chuchuva
fonte
Usar a sobrecarga BasicConfigurator.Configure (IAppender) evita muitas bagunças.
Shaun de
1
1 para aquele. ActivateOptions()Definitivamente, a chamada está faltando ou pelo menos não foi apontada o suficiente nos documentos.
fbmd
5

Como diz Jonathan , usar um recurso é uma boa solução.

É um pouco restritivo, pois o conteúdo do recurso embutido será corrigido no momento da compilação. Eu tenho um componente de registro que gera um XmlDocument com uma configuração Log4Net básica, usando variáveis ​​definidas como appSettings (por exemplo, nome de arquivo para um RollingFileAppender, nível de registro padrão, talvez nome de string de conexão se você quiser usar um AdoNetAppender). E então eu chamo log4net.Config.XmlConfigurator.Configurepara configurar o Log4Net usando o elemento raiz do XmlDocument gerado.

Em seguida, os administradores podem personalizar a configuração "padrão", modificando alguns appSettings (normalmente nível, nome do arquivo, ...) ou podem especificar um arquivo de configuração externo para obter mais controle.

Joe
fonte
3

Não posso dizer no trecho de código da pergunta se o "'E assim por diante ..." inclui o apndr.ActivateOptions () muito importante que é indicado na resposta de Todd Stout. Sem ActivateOptions (), o Appender fica inativo e não fará nada que possa explicar por que está falhando.

RodKnee
fonte
Eu não acho que tinha isso aí. Esse pode ter sido o problema. Obrigado.
John M Gant
3

Um pouco tarde para a festa. Mas aqui está uma configuração mínima que funcionou para mim.

Classe de amostra

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

Configuração mínima de rastreamento log4net (dentro do teste 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 para o ouvinte de rastreamento

Namespace+Bar: Logged
oleksii
fonte
2
Isso quase funciona, mas eu precisava chamar .ActiveOptions no PatternLayout e no Appender antes que funcionasse totalmente.
cjb110 de
Não sei por quê. Funcionou para mim do jeito que está, talvez tenhamos usado versões diferentes.
oleksii de
2

Dr. Netjes tem isso para definir a cadeia de conexões de forma programática:

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

// Eu embuti três arquivos de configuração como um recurso embutido e os acessei assim:

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

É estranho que BasicConfigurator.Configure(apndr)não funcionou. No meu caso, fez o seu trabalho ... Mas, de qualquer forma, aqui vai a resposta - você deveria ter escrito hier.Configured = true;(código c #) depois de terminar toda a configuração.

vlad2135
fonte
0

Aqui está um exemplo de como você pode criar e usar um AdoNetAdapterinteiramente em código, completamente na ausência de qualquer App.configarquivo (nem mesmo para Common.Logging). Vá em frente, exclua!

Isso tem o benefício adicional de ser resiliente contra atualizações sob as novas convenções de nomenclatura , onde o nome do assembly agora reflete a versão. ( 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
)

[A 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
fonte
0

'Solução 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
fonte