How to use aspnet-session pattern layout? - log4net

I have adonet appender and I defined additional column. I want to get the userId from the asp.net session and do log.
According to this page there is %aspnet-session{key} pattern which I use like this:
<parameter>
<parameterName value="#userId" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%aspnet-session{current_member}" />
</layout>
</parameter>
and I got the following result in the database:
/LM/W3SVC/1/ROOT/trunk-1-129718741958458380spnet-session{current_member}
What I am doing wrong here?

As of log4net version 1.2.11.0, you can use %aspnet-request{ASP.NET_SessionId} of the ASP.NET pattern layout converters like so:
<conversionPattern value="%date %level %logger [%aspnet-request{ASP.NET_SessionId}] - %message%newline" />
See the release notes:
[LOG4NET-87] - Support ASP.Net related PatternConverters to allow items from the HttpContext.Current.Session, Cache, Request, etc. to be captured.

/LM/W3SVC/1/ROOT/trunk-1-129718741958458380spnet-session{current_member}
is getting logged because in your log4net definition you have something like this right?
<parameter>
<parameterName value="#user" />
<dbType value="String" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%aspnet-session{current_memeber}" />
</layout>
</parameter>
and log4net is taking "%a" out of "%aspnet-session{current_memeber}" and thinking you want the application domain. %a is the log4net pattern that converts to the application domain. This is really annoying and i recently ran into this and do not know a way around it.
See here:
https://logging.apache.org/log4net/log4net-1.2.13/release/sdk/log4net.Layout.PatternLayout.html
Found a solution to this problem. If you use log4net 1.2.11 or greater declaring your parameter like this should work.
<parameter>
<parameterName value="#session" />
<dbType value="String" />
<size value="2147483647"/>
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value ="AspNetSessionPatternConverter"/>
<type value="log4net.Layout.Pattern.AspNetSessionPatternConverter"/>
</converter>
<conversionPattern value="%aspnet-session{gwsession}" />
</layout>
</parameter>
I don't know if %a being short circuited to appdomain is a bug or if you're supposed to declare your params like this with the aspnetsessionpatternconverter, but it would be nice if the log4net documentation was updated. Hope this helps someone else.

You need to use log4net 1.2.11 or higher to get access to the ASP.NET pattern converters.
You'll get the LM/W3SVC/1/ROOT/trunk-1-129718741958458380spnet-session{current_member} message when you use an older version of log4net.

I found a solution to my problem.
I just refactored it to serve my needs:
public class Log4NetAspNetProperty
{
private const string PropertyNamePrefix = "log4net_app_";
private const string PropertyDefaultValue = null;
private readonly string propertyName;
private readonly object propertyValue;
public string Name { get { return propertyName; } }
private Log4NetAspNetProperty(string propertyName, object propertyValue)
{
if (String.IsNullOrWhiteSpace(propertyName)) throw new ArgumentNullException("propertyName");
this.propertyName = propertyName;
this.propertyValue = propertyValue;
if (HttpContext.Current != null)
HttpContext.Current.Items[GetPrefixedPropertyName()] = propertyValue;
}
public override string ToString()
{
if (HttpContext.Current == null)
return PropertyDefaultValue;
var item = HttpContext.Current.Items[GetPrefixedPropertyName()];
return item != null ? item.ToString() : PropertyDefaultValue;
}
private static string GetPrefixedPropertyName()
{
return String.Format("{0}{1}", PropertyNamePrefix, PropertyDefaultValue);
}
public static void CurrentUserId(object userId)
{
var property = new Log4NetAspNetProperty("CurrentUserId", userId);
log4net.ThreadContext.Properties[property.Name] = property;
}
public static void CurrentUrl(object url)
{
var property = new Log4NetAspNetProperty("CurrentUrl", url);
log4net.ThreadContext.Properties[property.Name] = property;
}
}

Related

Custom PatternLayoutConverter with log4net.Ext.Json?

I have the following log4net configuration:
<log4net>
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<layout type='log4net.Layout.SerializedLayout, log4net.Ext.Json'>
<renderer type='log4net.ObjectRenderer.JsonDotNetRenderer, log4net.Ext.Json.Net'>
<DateFormatHandling value="IsoDateFormat" />
<NullValueHandling value="Ignore" />
</renderer>
<converter>
<name value="preparedMessage" />
<type value="JsonLogs.CustomLayoutConverter" />
</converter>
<default />
<remove value='message' />
<remove value='ndc' />
<member value='message:messageObject' />
<member value='details:preparedMessage' />
</layout>
</appender>
<appender name="Console2" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value="preparedMessage" />
<type value="JsonLogs.CustomLayoutConverter" />
</converter>
<conversionPattern value="%level %thread %logger - %preparedMessage%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="Console" />
<appender-ref ref="Console2" />
</root>
</log4net>
with the following implementation of my custom PatternLayoutConverter:
namespace JsonLogs
{
using System.IO;
using log4net.Core;
using log4net.Layout.Pattern;
public class CustomLayoutConverter : PatternLayoutConverter
{
#region Methods
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
if (loggingEvent.MessageObject is string stringMessage)
{
writer.Write(new { message = stringMessage });
}
else
{
writer.Write(loggingEvent.RenderedMessage);
}
}
#endregion
}
}
For some reason, the converter works perfectly fine with the Console2 appender(which is not JSON driven) but it doesn't work with the Console appender whose output is JSON.
Example of the output:
Console -> {"date":"2018-12-09T12:25:28.0529041+03:00","level":"INFO","appname":"JsonLogs.exe","logger":"JsonLogs.Program","thread":"1","message":"Test","details":"preparedMessage"}
Console2 -> INFO 1 JsonLogs.Program - { message = Test }
My goal is to have details always in JSON that's why I introduced my own converter to catch primitive values and wrap them in a custom object.
Is my configuration wrong? Or I'm missing something? Could you help me, please, to figure this out?
Thank you
The issue seems to be a bug of log4net.Ext.Json. I'm going to report it on their GitLab.
So far, I ended up with my custom log4net layout which looks like this
public class CustomLayout : PatternLayout
{
#region Public Methods and Operators
public override void Format(TextWriter writer, LoggingEvent loggingEvent)
{
var message = loggingEvent.MessageObject.GetType().IsPrimitive || loggingEvent.MessageObject is string || loggingEvent.MessageObject is decimal || loggingEvent.MessageObject is BigInteger
? new { message = loggingEvent.MessageObject }
: loggingEvent.MessageObject;
writer.WriteLine(JsonConvert.SerializeObject(new
{
timestamp = loggingEvent.TimeStampUtc,
threadId = loggingEvent.ThreadName,
details = message,
logger = loggingEvent.LoggerName,
level = loggingEvent.Level.DisplayName,
user = loggingEvent.UserName
}));
}
#endregion
}
it meets my needs and does exactly what I want.
The exact place of this problem is AddMember Method and its implementation. Here is SerializedLayout source code for that:
public virtual void AddMember(string value)
{
var arrangement = log4net.Util.TypeConverters.ArrangementConverter.GetArrangement(value, new ConverterInfo[0]);
m_arrangement.AddArrangement(arrangement);
}
As you can see the second parameter of GetArrangment is empty array of ConverterInfo, Though there must be our custom attached ones (by AddConverter method or by xml).
As the solution you can implement your own subclass that will derive from SerializedLayout with overridden AddMember like this:
public override void AddMember(string value)
{
var customConverter = new ConverterInfo("lookup", typeof(CustomPatternConverter));
var arrangement = log4net.Util.TypeConverters.ArrangementConverter.GetArrangement(value, new ConverterInfo[] { customConverter });
m_arrangement.AddArrangement(arrangement);
}
Hope it helps as it did with my case!

Setting up Log4Net Colored Console Made Easy

I am looking for a Log4Net viewer that I can watch in realtime in a separate console window not in the Visual Studio output window where its mixed with hundreds of other messages.
I see there is a ManagedColoredConsoleAppender and ColoredConsoleAppender out of the box, but unsure how to get the output to be directed to an external console window. Ideally, when you debug the console window would launch, but not a requirement.
I have used the OutputDebugger back in the day, but have not found an easy way to get all this working.
If somebody could share how to get all this wired up and working quickly that would be much appreciated.
This would mainly be used for development locally, but would be nice if we could redirect errors from staging and qa to a nice little window on my machine to see whats going on in realtime!
The ColoredConsoleAppender (and it's successor, ManagedColoredConsoleAppender) won't start a console for you.
However, if you start a console up manually, they will use it - here's a sample:
class Program {
[DllImport("kernel32.dll", EntryPoint = "GetStdHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", EntryPoint = "AllocConsole", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();
private const int STD_OUTPUT_HANDLE = -11;
private const int MY_CODE_PAGE = 437;
private static readonly object lockObj = new object();
static void Main(string[] args) {
AllocConsole();
IntPtr stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);
FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);
Encoding encoding = Encoding.GetEncoding(MY_CODE_PAGE);
StreamWriter standardOutput = new StreamWriter(fileStream, encoding) { AutoFlush = true };
Console.SetOut(standardOutput);
XmlConfigurator.Configure();
var log = LogManager.GetLogger("test");
log.Debug("Starting Program");
log.Error("Oh no, an error");
// etc
This is the output:
The configuration is really simple:
<log4net>
<appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<mapping>
<level value="ERROR" />
<foreColor value="White" />
<backColor value="Red, HighIntensity" />
</mapping>
<mapping>
<level value="DEBUG" />
<backColor value="Green" />
</mapping>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="ColoredConsoleAppender" />
</root>

log4net Azure Blob Appender

When saving logs into Azure Blob Storage, is there a way to create a different folder per day?
Right now I'm using this configuration, and it works fine
<appender name="AzureAppender2" type="log4net.Appender.AzureBlobAppender, log4net.Appender.Azure">
<param name="ContainerName" value="testcon" />
<param name="DirectoryName" value="myfolder/logs.txt" />
<param name="ConnectionString" value="DefaultEndpointsProtocol=https;AccountName=testcon;AccountKey="rftgdfgdfgfdg78=="/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
I've already tried this
<param name="DirectoryName" value=%date/" />
But it doesn't work
What I want is to be able to dynamically use different folders per day: something like
DirectoryName = 2016-05-13
DirectoryName = 2016-05-12
DirectoryName = 2016-05-11
Is this achievable?
Thanks
Here's the code for the appender on GitHub.
Here's the DirectoryName class property that maps from the configuration value:
private string _directoryName;
public string DirectoryName
{
get
{
if (String.IsNullOrEmpty(_directoryName))
throw new ApplicationException(Resources.DirectoryNameNotSpecified);
return _directoryName;
}
set
{
_directoryName = value;
}
}
And the relevant Filename method that actually creates the file name for the blob:
private static string Filename(LoggingEvent loggingEvent, string directoryName)
{
return string.Format("{0}/{1}.{2}.entry.log.xml",
directoryName,
loggingEvent.TimeStamp.ToString("yyyy_MM_dd_HH_mm_ss_fffffff",
DateTimeFormatInfo.InvariantInfo),
Guid.NewGuid().ToString().ToLower());
}
So it looks like directoryName only accepts static values. Good thing it's open source ...

Log4net - dynamically switch appender between AdoNetAppender and RollingFileAppender

I am using AdoNetAppender (SQL server) in my asp.net application and would like use to RollingFileAppender incase of any connection issue with SQL. Is there any way to configure to use RollingFileAppender only when there is an issue with AdoNetAppender?
Thanks
por
There is no built in support for this kind of failover scenario in log4net, the problem being that appenders are quite isolated from each other in the log4net architecture.
A common setup though is to have both appenders logging in parallel, only that the file appender only keeps, say, a weeks worth of data. Should the AdoNetAppender fail you will always have the latest data in files.
But I definitively see the case here for an appender that could have a priority list of sub-appenders doing some simple failover in case of failure. This should not be too hard to implement either building on the AppenderSkeleton.
I've implemented such an appender and blogged about it here and here (mirror). The code can be found here.
I've extended AppenderSkeleton and created a new Appender called FailoverAppender that has two members of type AppenderSkeleton.
A default appender called "PrimaryAppender" - used by default, until it fails.
A failover appender called "FailoverAppender" - used only after the primary fails.
The actual type of the PrimaryAppender and the FailoverAppender are configured using log4net's xml configuration syntax (see an example below).
A snippet:
public class FailoverAppender : AppenderSkeleton
{
private AppenderSkeleton _primaryAppender;
private AppenderSkeleton _failOverAppender;
....
}
In the implementation of the Append method, I send by default LoggingEvents only to the PrimaryAppender and surround it with a try-catch. If the PrimaryAppender throws (fails), I signal a flag and send the LoggingEvent to the FailoverAppender.
The next LoggingEvents will be sent directly and only to the FailoverAppender.
protected override void Append(LoggingEvent loggingEvent)
{
if (LogToFailOverAppender)
{
_failOverAppender?.DoAppend(loggingEvent);
}
else
{
try
{
_primaryAppender?.DoAppend(loggingEvent);
}
catch
{
ActivateFailOverMode();
Append(loggingEvent);
}
}
}
In addition, I created a custom ErrorHandler that will propagate inner-appender exceptions to signal that an appender has failed internally, which will let LoggingEvents to be sent only to the FailoverAppender.
class FailOverErrorHandler : IErrorHandler
{
public FailOverAppender FailOverAppender { get; set; }
public FailOverErrorHandler(FailOverAppender failOverAppender)
{
FailOverAppender = failOverAppender;
}
public void Error(string message, Exception e, ErrorCode errorCode)
=> FailOverAppender.ActivateFailOverMode();
public void Error(string message, Exception e)
=> FailOverAppender.ActivateFailOverMode();
public void Error(string message)
=> FailOverAppender.ActivateFailOverMode();
}
Configuration example:
<!--This custom appender handles failovers. If the first appender fails, it'll delegate the message to the back appender-->
<appender name="FailoverAppender" type="MoreAppenders.FailoverAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
</layout>
<!--This is a custom test appender that will always throw an exception -->
<!--The first and the default appender that will be used.-->
<PrimaryAppender type="MoreAppenders.ExceptionThrowerAppender" >
<ThrowExceptionForCount value="1" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
</layout>
</PrimaryAppender>
<!--This appender will be used only if the PrimaryAppender has failed-->
<FailOverAppender type="log4net.Appender.RollingFileAppender">
<file value="log.txt"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="10"/>
<maximumFileSize value="100mb"/>
<appendToFile value="true"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
</layout>
</FailOverAppender>
</appender>

log4net: Logging two messages in one row in the database?

I'm trying to log the input and output of a particular method to the database. I'd like to have this information in separate columns. I've investigated the PatternLayout and it seems that it only caters for a single %message parameter, meaning that if you do:
log.Debug("This is a message");
then log4net sees "This is a message" as the message to be logged. I want to do something like:
log.Debug(request, response);
Is this possible using log4net? Keep in mind that my goal is to have "request" and "response" in separate columns.
Your PatternConverter way is a step in the right direction, though the use of the static Input and Output properties makes it all a bit shaky (thread-safety wise).
The trick here is to realize that the message parameter on logger.Debug(...) is object and that you can pass in whatever you like.
You could define a custom message type
public class InputOutput
{
public string Input {get;set;}
public string Output {get;set;}
}
and then let your converters read either property
public class InputPatternConverter : PatternConverter
{
protected override void Convert(System.IO.TextWriter writer, object state)
{
var msg = ((LoggingEvent)state).MessageObject as InputOutput;
if (msg != null)
writer.Write(msg.Input);
}
}
public class OutputPatternConverter : PatternConverter
{
protected override void Convert(System.IO.TextWriter writer, object state)
{
var msg = ((LoggingEvent)state).MessageObject as InputOutput;
if (msg != null)
writer.Write(msg.Output);
}
}
the logging then becomes much cleaner
logger.Debug(new InputOutput { Input = ..., Output = ...});
your config would be the same.
A tip though is to subclass the PatternLayout and add the converters in the constructor of that class. That way you can also trim down your config. This will not cause you to loose the %message token, your %input and %output tokens will come in addition to all the tokens that PatternLayout supports. So you could actually have a pattern like this:
"%date %message %newline%newline %input %newline%newline %output
Here's a quick implementation of a custom pattern layout:
public class InputOutputPatternLayout : PatternLayout
{
public InputOutputPatternLayout()
{
AddConverter("input", typeof(InputPatternConverter));
AddConverter("output", typeof(OutputPatternConverter));
}
}
I've come up with one way to do this using custom PatternConverters
public class InputPatternConverter : PatternConverter
{
private static string _input;
public static string Input
{
get { return _input; }
set { _input = value; }
}
protected override void Convert(System.IO.TextWriter writer, object state)
{
writer.Write(Input);
}
}
public class OutputPatternConverter : PatternConverter
{
private static string _output;
public static string Output
{
get { return _output; }
set { _output = value; }
}
protected override void Convert(System.IO.TextWriter writer, object state)
{
writer.Write(Output);
}
}
Appender Specification:
<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="data source=servername;initial catalog=database;Integrated Security=SSPI;" />
<commandText value="INSERT INTO RequestLog ([input], [output]) VALUES (#input, #output)" />
<parameter>
<parameterName value="#input" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value="input" />
<type value="InputPatternConverter, ApplicationName" />
</converter>
<conversionPattern value="%input" />
</layout>
</parameter>
<parameter>
<parameterName value="#output" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value="output" />
<type value="OutputPatternConverter, ApplicationName" />
</converter>
<conversionPattern value="%output" />
</layout>
</parameter>
</appender>
Call it using:
InputPatternConverter.Input = inputString;
OutputPatternConverter.Output = outputString;
XmlConfigurator.Configure();
ILog logger = LogManager.GetLogger(typeof(ApplicationClassName));
logger.Debug("");

Resources