I set up logging with log4net a while back and just didn't think of a particular scenario.
At present I have a global config file. There is a RollingFileAppender and an AdonetAppender.
When only one instance of the program is running on the database then all is fine.
Sometimes, two slightly different instances may be run on the same database. The appenders log the messages from both instances, but often (in the communal code) it is not possible to tell from which instance the message originated. Of course, I could add the instance name to the message or whatever, but it seems like a last resort.
My brain is like a fog. I think the solution should be rather simple, but I just can't think what would be best.
Could I create another appender, and "switch" during runtime which appender is being used based on the instance name?
Here is the config file:
<?xml version="1.0" encoding="utf-8" ?>
<log4net debug="true">
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<file value="D:\Medupi logs\CalcEngineLog.txt"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="2"/>
<maximumFileSize value="4MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger %location - %message%newline%exception"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="DEBUG"/>
<levelMax value="FATAL"/>
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<!-- <threshold value="Warn" /> -->
<bufferSize value="1" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="" />
<commandText value="INSERT INTO [Calculation Engine Log] ([Date],[Thread],[Level],[Logger],[Location],[Message],[Exception]) VALUES (#log_date, #thread, #log_level, #logger, #location, #message, #exception)" />
<parameter>
<parameterName value="#log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="#thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="#log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="#logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="#location" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%location" />
</layout>
</parameter>
<parameter>
<parameterName value="#message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="#exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
<root>
<level value="Debug"/>
<appender-ref ref="AdoNetAppender"/>
<appender-ref ref="RollingFileAppender"/>
<appender-ref ref="DebugAppender"/>
</root>
</log4net>
This is how it is configured in the start-up project:
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static void Main()
{
if (!log4net.LogManager.GetRepository().Configured)
{
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo("C:\\Program Files\\Symplexity\\bin\\Log4NetSettingsGlobal.xml"));
}
...
}
Additional information
In production, a Windows Service is started and this checks a config file for which instances must be run on a cluster.
The individual instances each have a separate process:
foreach (var cluster in clusters)
{
ProcessStartInfo startInfo = new ProcessStartInfo
{
CreateNoWindow = true,
FileName = processFileName,
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = string.Format("\"{0}\" \"{1}\"", cluster.Name, _ConfigPath)
};
_ClusterProcesses.Add(Process.Start(startInfo));
}
I assume I could add the appIdentifier (as per comments) to the Arguments of the startInfo?
Start by identifying the 2 application instances.
The easiest way to do so is to start each application instance with a unique commandline argument.
Start the first instance via eg. a .cmd file as MyApplication.exe instance1 and the second one as MyApplication.exe instance2.
Read this argument value from the args parameter of method Main.
static void Main(String[] args)
{
String appIdentifier = args[0];
// ...
}
Now you have multiple options.
Option 1: a shared Log4net configuration for both instances
Assign the appIdentifier to a Log4net context property.
The scope to use depends on your scenario, see the Log4net documentation.
log4net.GlobalContext.Properties["appIdentifier"] = appIdentifier;
RollingFileAppender
Include this context property in the layout of the RollingFileAppender via %property{appIdentifier}.
Each logged line will now contain the appropriate appIdentifier value 'instance1' or 'instance2'.
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="... %property{appIdentifier} ..."/>
</layout>
AdoNetAppender
First make sure that the table you are logging to contains a column to store the appIdentifier value,
eg. column appIdentifier VARCHAR(25).
Extend the configuration of the AdoNetAppender with an extra parameter to match the appIdentifier property.
<parameter>
<parameterName value="#appIdentifier"/>
<dbType value="String" />
<size value="25" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%property{appIdentifier}" />
</layout>
</parameter>
Extend the SQL INSERT statement with the corresponding column name and parameter.
INSERT INTO [Calculation Engine Log] ([Date],[Thread],[Level],[Logger],[Location],[Message],[Exception],[appIdentifier])
VALUES (#log_date, #thread, #log_level, #logger, #location, #message, #exception, #appIdentifier)
Option 2: a separate Log4net configuration file for each instance
This is not a pretty as option 1 ... but still an option.
Make a separate xml configuration file for each instance by matching the file name with the appIdentifier commandline argument.
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo("C:\\Program Files\\Symplexity\\bin\\" + appIdentifier + '.xml'));
RollingFileAppender
Configure a separate output file path for each instance.
For instance 1
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
...
<file value="D:\Medupi logs\Instance1.txt"/>
...
</appender>
For instance 2
<file value="D:\Medupi logs\Instance2.txt"/>
AdoNetAppender
See option 1 about how to set up a column to store the appIdentifier.
Include a constant value as identifier in the SQL INSERT statement.
INSERT INTO [Calculation Engine Log] ([Date],[Thread],[Level],[Logger],[Location],[Message],[Exception],[appIdentifier])
VALUES (#log_date, #thread, #log_level, #logger, #location, #message, #exception, 'Instance1')
Do the same in the xml configuration file for instance2 with value 'Instance2'.
Option 3: a single named logger
If you are only using a single ILog instance throughout your application, retrieve it by name using the value of the appIdentifier.
The %logger marker of the RollingFileAppender and #logger marker of the AdoNetAppender will contain the value 'Instance1' or 'Instance2' respectively.
I am not a fan of using a single logger, but I often see it being practiced this way.
private static log4net.ILog log;
static void Main(String args)
{
String appIdentifier = args[0];
if (!log4net.LogManager.GetRepository().Configured)
{
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo("C:\\Program Files\\Symplexity\\bin\\Log4NetSettingsGlobal.xml"));
log = log4net.LogManager.GetLogger(appIdentifier);
}
// ...
}
Option 4: a parametrized file name for the RollingFileAppender
This is an alternative option for the RollingFileAppender only, combine it with one of the options above for the AdoNetAppender.
Set the Log4net context property as explained in option 1.
It is possible to include such Log4net context properties in the path to the output file of the RollingFileAppender.
Doing so will generate a file 'Instance1.log' or 'Instance2.log' respectively.
(Instead of varying the file name, you can also apply this to the (sub)folder name.)
Not sharing the same output file between multipe application instances running concurrently might be a good idea if you are having or are concerned about locking and/or performance issues.
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
...
<file type="log4net.Util.PatternString" value="D:\Medupi logs\%property{appIdentifier}.log" />
...
</appender>
Related
I'm trying to implement logging with log4net in an asp.net core console app. I have a business library written in .net 4.7.1 with a logging wrapper that I'm using to do logging in all of my apps. I have a file appender, a console appender, an smtp appender, and an ado.net appender. All of these work on my asp.net core web app and they also work in my non-core console apps.
Only the console and file appenders work in .net core console. The ado.net and email appenders do not seem to work. I don't get emails and nothing is inserted into the DB.
This is my Main method:
static void Main(string[] args)
{
// Set up DI
var serviceProvider = new ServiceCollection()
.AddSingleton<IRepository, Repository>()
.AddSingleton<ITdnLogger, TdnLogger>()
.BuildServiceProvider();
var logger = serviceProvider.GetService<ITdnLogger>();
logger.Log(LoggingLevel.Info, "This is a test log error!");
var logga = new TdnLogger();
logga.Log(LoggingLevel.Error, "Error but not an error");
Console.ReadLine();
}
I first attempt to get the logger to log using dependency injection and then I instantiate a new one and try again. I get the same results both times. Console and File appenders work, email and ado do not. So I'm thinking it has nothing to do with DI, but is probably more of a set up issue. I don't understand where the setup could be wrong, because it works for everything else, but...
In my business library's AssemblyInfo.cs I have the log4net config set up like this:
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config")]
This is what the config looks like:
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="AdoNetAppender" />
<appender-ref ref="SmtpAppender" />
<appender-ref ref="console" />
<appender-ref ref="file" />
</root>
<appender name="console" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %level %logger - %message%newline" />
</layout>
</appender>
<appender name="file" type="log4net.Appender.RollingFileAppender">
<file value="ApplicationLog.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
</appender>
<appender name="SmtpAppender" type="Business.Logging.SimpleSmtpAppender">
<subject>**** Application Error Notification ****</subject>
<smtpHost value="{server}" />
<evaluator type="log4net.Core.LevelEvaluator">
<threshold value="ERROR"/>
</evaluator>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%newline%date [%thread] %level %logger - %message%newline%newline%exception" />
</layout>
</appender>
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="1" />
<threshold value="INFO" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="{valid con string}" />
<commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (#log_date, #thread, #log_level, #logger, #message, #exception)" />
<parameter>
<parameterName value="#log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="#thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="#log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="#logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="#message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="#exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
Any ideas of why the ado and email appenders might not work in a core console app?
EDIT #1:
I got the email appender to work, I had the email address setup incorrectly.
The ado appender is throwing this error:
System.TypeLoadException: Could not load type
System.Runtime.Remoting.Messaging.CallContext from assembly 'mscorlib,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.
at log4net.Util.LogicalThreadContextProperties.GetLogicalProperties()
at log4net.Util.LogicalThreadContextProperties.GetProperties(Boolean
create) at log4net.Core.LoggingEvent.CreateCompositeProperties()
at log4net.Core.LoggingEvent.CacheProperties() at
log4net.Core.LoggingEvent.FixVolatileData(FixFlags flags) at
log4net.Appender.BufferingAppenderSkeleton.Append(LoggingEvent
loggingEvent) at
log4net.Appender.AppenderSkeleton.DoAppend(LoggingEvent loggingEvent)
Try using https://github.com/microknights/Log4NetAdoNetAppender, instead of the standard log4net.Appender.AdoNetAppender. You will need to change the appender type definition in your config file to:
MicroKnights.Logging.AdoNetAppender, MicroKnights.Log4NetAdoNetAppender
as per the documentation on their project page.
We have a Windows Service that kicks off another process called ClusterProcess.
I want to get logs from the Windows Service and from the ClusterProcess, and I want those logs to go to the database and to files. For instance, if the database is not accessible, I want to be able to see the logs in the files.
I have an external config file for the Log4Net settings. There are two RollingFileAppenders and two AdoNetAppenders.
There is the default root logger and an additional logger called, ClusterProcessLogger.
The idea is that the Windows Service uses the root logger and logs to one of the AdoNetAppenders and one of the RollingFileAppenders, and that the ClusterProcess uses the ClusterProcessLogger and logs to the other AdoNetAppender and the other RollingFileAppender.
The AdoNetAppenders for both cases work fine. The RollingFileAppenders do not.
So here is the external config file for the log4net configuration:
<?xml version="1.0" encoding="utf-8" ?>
<log4net debug="true">
<appender name="RollingFileAppender"
type="log4net.Appender.RollingFileAppender">
<file value="C:\Program Files\Sym\Logging\CalcEngineLog.txt"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="5"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger %location -
%message%newline%exception"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="DEBUG"/>
<levelMax value="FATAL"/>
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
<appender name="SpecialRollingFileAppender"
type="log4net.Appender.RollingFileAppender">
<file value="C:\Program Files\Sym\Logging\SpecialCalcEngineLog.txt"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="5"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger %location -
%message%newline%exception"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="DEBUG"/>
<levelMax value="FATAL"/>
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<!--<threshold value="Error" />-->
<bufferSize value="1" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data,
Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="" />
<commandText value="INSERT INTO [Calculation Engine Log] ([Date],[Thread],
[Level],[Logger],[Location],[Message],[Exception]) VALUES (#log_date,
#thread, #log_level, #logger, #location, #message, #exception)" />
<parameter>
<parameterName value="#log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="#thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="#log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="#logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="#location" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%location" />
</layout>
</parameter>
<parameter>
<parameterName value="#message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="#exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
<appender name="ClusterProcessAdoNetAppender"
type="log4net.Appender.AdoNetAppender">
<bufferSize value="1" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data,
Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="" />
<commandText value="INSERT INTO [ClusterProcess Calculation Engine Log]
([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (#log_date,
#thread, #log_level, #logger, #message, #exception)" />
<parameter>
<parameterName value="#log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="#thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="#log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="#logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="#message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="#exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
<logger name="ClusterProcessLogger" additivity="false">
<level value="Debug" />
<appender-ref ref="ClusterProcessAdoNetAppender" />
<appender-ref ref="SpecialRollingFileAppender" />
</logger>
<root>
<level value="Debug"/>
<appender-ref ref="AdoNetAppender"/>
<appender-ref ref="RollingFileAppender"/>
</root>
</log4net>
Here is the Main method of the Windows Service:
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using System.Globalization;
using Sym.Core.Culture;
using System.Threading;
namespace Sym.WindowsService
{
static class Program
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static void Main()
{
if (!log4net.LogManager.GetRepository().Configured)
{
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo("C:\\Program Files\\Sym\\bin\\Log4NetSettingsGlobal.xml"));
}
log.Debug("Culture info before change: " + Thread.CurrentThread.CurrentCulture.Name);
...
}
}
}
Here is the App.config of the Windows Service:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="ConfigPath" value="C:\Program Files\Sym\Configs\Example.config"/>
<add key="log4net.Internal.Debug" value="true"/>
</appSettings>
<system.diagnostics>
<trace autoflush="true">
<listeners>
<add
name="textWriterTraceListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="C:\Program Files\Sym\Logging\Log4Net_Trace_WindowsServer.txt" />
</listeners>
</trace>
</system.diagnostics>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
For the ClusterProcess project, here is the Main method:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sym.Core.Config;
using Sym.Clustering.Framework;
using System.Diagnostics;
using Sym.Core.Services;
using System.Reflection;
using System.IO;
using log4net.Repository.Hierarchy;
using log4net;
using log4net.Appender;
using System.Configuration;
using System.Collections.Specialized;
using Sym.Core.Log4Net;
using log4net.Config;
namespace Sym.Clustering.ClusterProcess
{
class Program
{
private static ClusterProcessor _ClusterProcessor ;
private static SelectedConfiguration _Config;
private static readonly log4net.ILog log = log4net.LogManager.GetLogger("ClusterProcessLogger");
static void Main(string[] args)
{
try
{
...
try
{
log4net.Config.XmlConfigurator.Configure();
if (!log4net.LogManager.GetRepository().Configured)
{
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo("C:\\Program Files\\Sym\\bin\\Log4NetSettingsGlobal.xml"));
}
Log4NetConfiguration.ConfigureLog4Net(_Config); // Load connection strings
}
catch (Exception e)
{
Console.WriteLine(e);
}
log.Debug("ClusterProcess using DatabaseName: " + config.Database.DatabaseName);
...
}
catch(Exception exp)
{
log.Error(exp.Message, exp);
}
}
}
}
Here is the App.config of the ClusterProcess project:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="log4net.Internal.Debug" value="true"/>
</appSettings>
<system.diagnostics>
<trace autoflush="true">
<listeners>
<add
name="textWriterTraceListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="C:\Program Files\Sym\Logging\Log4Net_Trace_ClusterProcess.txt" />
</listeners>
</trace>
</system.diagnostics>
</configuration>
In the log4net trace of the ClusterProcess, these errors are given:
log4net:ERROR Failed to find configuration section 'log4net' in the application's .config file. Check your .config file for the <log4net> and <configSections> elements. The configuration section should look like:
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
log4net:ERROR [RollingFileAppender] ErrorCode: GenericFailure. Unable to acquire lock on file C:\Program Files\Sym\Logging\SpecialCalcEngineLog.txt. The process cannot access the file 'C:\Program Files\Sym\Logging\SpecialCalcEngineLog.txt' because it is being used by another process.
log4net:ERROR [RollingFileAppender] ErrorCode: GenericFailure. Unable to acquire lock on file C:\Program Files\Sym\Logging\CalcEngineLog.txt. The process cannot access the file 'C:\Program Files\Sym\Logging\CalcEngineLog.txt' because it is being used by another process.
I don't know what to do about the first error.
The second and third error shouldn't be there. The ClusterProcess should be able to access the SpecialCalcEngineLog.txt, and it shouldn't be bothered with the CalcEngineLog.txt, as that is the RollingFileAppender that the Windows Service is using.
How do I solve these errors? What am I overlooking?
First error can be mitigated by adding
<configSections>
<section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
</configSections>
somewhere.
Multiple RollingFileAppenders cannot write to the same file. However, you can distinguish by, for example, adding process id to the file name (and then collect logs from all processes):
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="C:\log\logfile_[%processid].log" />
...
</appender>
Can someone off a 2nd set of eyes? I am clearly missing something.
I'm using Log4Net in a Web API app. Whenever I invoke a Log.Error() it executes, but nothing actually gets written to my dbo.Log table. I am running a local database. When I replaced the configuration with one that wrote to a file, the code worked perfectly.
Here is what my configuration and code looks like.
The Log4Net.config file contains the following:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="100" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="Data Source=MUD-DAD\SQLEXPRESS;Initial Catalog=FitAchiever;Persist Security Info=True;User ID=sa;Password=asdadf334"/>
<commandText value="INSERT INTO dbo.Log ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (#log_date, #thread, #log_level, #logger, #message, #exception)" />
<parameter>
<parameterName value="#log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="#thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="#log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="#logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="#message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="#exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="AdoNetAppender"/>
</root>
</log4net>
</configuration>
The Global.asax.cs file contains the following, note the call to the Log4Net.Config() method which specifies which config to use.
public class WebApiApplication : System.Web.HttpApplication
{
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(WebApiApplication));
protected void Application_Start()
{
log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(Server.MapPath("Log4Net.config")));
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
The initialization of the logger in my Controller is:
public class FitDashboardController : ApiController
{
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
IFitDashboardService fitDashboardService = new FitDashboardService();
...
And the call is:
catch (System.Exception ex)
{
log.Error("Error:" + MethodBase.GetCurrentMethod().Name + " :: " + ex.Message);
}
Again, the code works when writing to a file, but not the database. Any ideas?
This question has been answered here. The problem is your <bufferSize> tag, which according to the linked answer...
<bufferSize value="100" />
It is saying that it will keep 100 logs in
memory until written into DB. Maybe that's why you don't see anything
in DB?
Change your code to say <bufferSize value="1" /> and this should cause log4net to write to your database after keeping 1 log in memory. See Apache's documentation on buffersize for more details.
Have you tried running sql server profiler and doing a trace against the database in the connection string? The insert statement log4net submits should show up along with values for the various parameters.
I have found other posts with similar problems, but thus far I haven't been able to solve my particular case.
If I put the log4net configuration in the app.config then all works fine, but I want it in a separate file.
I have looked at the following links, and have tried to implement some of the proposed solutions (but perhaps I didn't understand it correctly):
Similar SO question, but with web scenario
Apache documentation, 'Reading Files Directly' section
Tim Corey's log4net tutorial
So here is my Log4NetSettings.config file's content (wrapped in a configuration element):
<log4net>
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="1" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="" />
<commandText value="INSERT INTO [Calculation Engine Log] ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (#log_date, #thread, #log_level, #logger, #message, #exception)" />
<parameter>
<parameterName value="#log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="#thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="#log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="#logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="#message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="#exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
<root>
<level value="INFO"/>
<appender-ref ref="AdoNetAppender"/>
</root>
</log4net>
I change the connectionstring value during runtime.
Here is what I have in the app.config:
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<appSettings>
<add key="log4net.Config" value="Log4NetSettings.config" />
</appSettings>
<log4net configSource="Log4NetSettings.config" />
In the executable class I have this:
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "Log4NetSettings.config", Watch = true)]
and then as soon as I can get hold of the connectionstring and configure it to the AdoNetAppender, I call:
log.Info("Testing");
I also have:
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
in the class with the assembly attribute and first logging call.
The Log4NetSettings.config file has its Build Action set to Content and its 'Copy to Output Directory' set to 'Copy Always'.
I have been pulling my hair out, and I don't know what I am doing wrong.
Should I have Log4NetSettings.config as just a normal xml file?
Should I put the assembly attribute in the AssemblyInfo.cs file?
Should I rather call:
XmlConfigurator.ConfigureAndWatch(new System.IO.FileInfo(AppDomain.CurrentDomain.BaseDirectory + "Log4NetSettings.config"));
than have the assembly attribute?
It feels like I've tried so many different things, that perhaps I have mixed them up and gotten the wrong combination.
Adding debug = "true" to the <log4net> tag (i.e. <log4net debug="true"> ) gives a lot of helpful information which may help to resolve.
Yes, you should have the log4net config in an XML file. When you have log4net in a separate file, it's no longer related to the app.config so the configuration section is not required, and the config should not be wrapped in a configuration element. (It isn't necessary to have the assembly attribute in the AssemblyInfo.cs, and I note you have initialised the logging system in your startup code, which is required when using the assembly attribute technique)
Thus your log4net file will look like this:
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<!-- config .. -->
If you still have problems, log4net has diagnostic and debugging capability, see this question for how to enable it.
(Also, if you're using log4net 1.2.11 or later, you can set the connection string name in the log4net config, assuming it is in your app.config)
I have Web API that needs to write log data, both to a file and to the database.
Is it possible to configure such a setup? I am trying to understand ILoggerRepository but I am confused.
Can someone give me the some tips on how to configure it and how to use it in application?
It sounds to me like you want to write to two different style appenders at the same time.
The good news is that this is built right into the log4Net library.
What you want to do so configure you configuration file to two different appenders. A file Appender and a Database appender.
Examples of the different appenders can be found here.
http://logging.apache.org/log4net/release/config-examples.html
Here is an example of an web.config that i whipped together. You are going to have to tweak it to get it to work for you, but it will get you on the right start.
One last point, your log4net configuration does not have to be in your web.config but IMHO it just makes things easier to get started. Worry about breaking it out once your web.config starts to get a little cluttered.
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="log-file.txt" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="100" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="data source=[database server];initial catalog=[database name];integrated security=false;persist security info=True;User ID=[user];Password=[password]" />
<commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (#log_date, #thread, #log_level, #logger, #message, #exception)" />
<parameter>
<parameterName value="#log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="#thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="#log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="#logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="#message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="#exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="AdoNetAppender" />
<appender-ref ref="FileAppender" />
</root>
</log4net>
</configuration>