如何从头开始以编程方式配置 log4net (没有配置)

我知道这不是个好主意,但是..。 我想从头开始通过编程方式配置 log4net,不需要任何配置文件。我正在为我和我的团队开发一个简单的日志应用程序,用于我们负责的一些相对较小的部门应用程序。我要他们都登录到同一个数据库。日志应用程序只是预先配置了 AdoNetAppender 的 log4net 的包装器。

所有应用程序都是 ClickOnce 部署的,这在部署配置文件时会出现一个小问题。如果配置文件是核心项目的一部分,我可以将其属性设置为与程序集一起部署。但是它是链接应用程序的一部分,所以我不能选择将它与主应用程序一起部署。(如果这不是真的,请让我知道)。

可能是因为这是一个坏主意,所以似乎没有太多示例代码可用于从头开始通过编程方式配置 log4net。这是我目前掌握的情况。

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

在为 apndr配置了所有参数之后,我首先尝试了这个..。

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

但是没有用,然后,作为一个试验,我尝试了这个方法。

BasicConfigurator.Configure(apndr)

那也没用。是否有任何人有任何好的参考如何配置 log4net 从头开始没有配置文件?

55414 次浏览

One way I've done this in the past is to include the configuration file as an embedded resource, and just used log4net.Config.Configure(Stream).

That way, I could use the configuration syntax I was familiar with, and didn't have to worry about getting a file deployed.

As Jonathan says, using a resource is a good solution.

It's a bit restrictive in that the embedded resource contents will be fixed at compile time. I have a logging component that generates an XmlDocument with a basic Log4Net configuration, using variables defined as appSettings (e.g. filename for a RollingFileAppender, default logging level, maybe connection string name if you want to use an AdoNetAppender). And then I call log4net.Config.XmlConfigurator.Configure to configure Log4Net using the root element of the generated XmlDocument.

Then administrators can customise the "standard" configuration by modifying a few appSettings (typically level, filename, ...) or can specify an external configuration file to get more control.

It is strange that BasicConfigurator.Configure(apndr) did not work. In my case it did its job... But, anyway, here goes the answer - you should've wrote hier.Configured = true; (c# code) after you've finished all setup.

Here's an example class that creates log4net config completely in code. I should mention that creating a logger via a static method is generally viewed as bad, but in my context, this is what I wanted. Regardless, you can carve up the code to meet your needs.

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

}

I can't tell in the question's code snippet if the "'And so forth..." includes the very important apndr.ActivateOptions() which is indicated in Todd Stout's answer. Without ActivateOptions() the Appender is inactive and will not do anything which could explain why it is failing.

// I've embedded three config files as an Embedded Resource and access them like this:

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


}
}
}

I ended up using this:

http://www.mikebevers.be/blog/2010/09/log4net-custom-adonetappender/

After 4 hours fiddling with the config and getting progressively more frustrated.

Hope it helps someone.

Dr. Netjes has this for setting the connectionstring programmatically:

// 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
}
}

More concise solution:

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

Don't forget to call ActivateOptions method:

The ActivateOptions method must be called on this object after the configuration properties have been set. Until ActivateOptions is called this object is in an undefined state and must not be used.

A bit late for the party. But here is a minimal config that worked for me.

Sample class

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

Minimal log4net trace config (inside NUnit test)

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

Prints to the trace listener

Namespace+Bar: Logged

Here's a soup-to-nuts example of how you can create and use an AdoNetAdapter entirely in code, completely in the absence of any App.config file (not even for Common.Logging). Go ahead, delete it!

This has the added benefit of being resilient against updates under the new naming conventions, where the assembly name now reflects the 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
)

[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

'Solution for 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