Setting up Log4Net Colored Console Made Easy - log4net

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>

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!

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

event is not generated in windows event

I am using log4net for logging, I have two appenders one file and other eventlog appender.
I have refered here this link
My problem is that the log is generated in file but not in windows event.
My code is:
<log4net>
<logger name="FileLogger">
<level value="ERROR" />
<appender-ref ref="RollingLogFileAppender" />
</logger>
<logger name="EventLogger" additivity="False">
<level value="ALL" />
<appender-ref ref="EventLogAppender" />
</logger>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Logs\logfile.xml"/>
<appendToFile value="true"/>
<rollingStyle value="Date"/>
<datePattern value="yyyyMMdd"/>
<maxSizeRollBackups value="10"/>
<maximumFileSize value="5MB"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="<Event><Date>%date</Date><Message>%message</Message><Stack>%exception</Stack></Event>%newline" />
</layout>
</appender>
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
<!--<param name="MvcApplication1" value="eventlog" />-->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender></log4net>
code side is:
public class LogManager
{
private static readonly ILog eventLogger = log4net.LogManager.GetLogger("EventLogAppender");
private static readonly ILog fileLogger = LogManager.GetLogger("FileLogger");
public void Error(Exception ex)
{
eventLogger.Logger.Log(eventLogger.GetType(), Level.Error, ex.Source, ex);
}
public void Info(string message)
{
LoggingEventData e = new LoggingEventData()
{
Level = Level.Fatal,
Message = message
};
eventLogger.Logger.Log(new LoggingEvent(e));
fileLogger.Logger.Log(new LoggingEvent(e));
}
}
I am using application_Error for global level error
protected void Application_Error(object sender, EventArgs e)
{
var httpContext = ((MvcApplication)sender).Context;
var currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
var currentController = " ";
var currentAction = " ";
if (currentRouteData != null)
{
if (currentRouteData.Values["controller"] != null && !String.IsNullOrEmpty(currentRouteData.Values["controller"].ToString()))
{
currentController = currentRouteData.Values["controller"].ToString();
}
if (currentRouteData.Values["action"] != null && !String.IsNullOrEmpty(currentRouteData.Values["action"].ToString()))
{
currentAction = currentRouteData.Values["action"].ToString();
}
}
var ex = Server.GetLastError();
var controller = new ErrorController();
var routeData = new RouteData();
var action = "Index";
if (ex is HttpException)
{
var httpEx = ex as HttpException;
switch (httpEx.GetHttpCode())
{
case 404:
action = "NotFound";
break;
case 401:
action = " UnAuthorized";
break;
// others if any
default:
action = "Index";
break;
}
}
new LogManager().Error(ex);
httpContext.ClearError();
httpContext.Response.Clear();
httpContext.Response.StatusCode = ex is HttpException ? ((HttpException)ex).GetHttpCode() : 500;
httpContext.Response.TrySkipIisCustomErrors = true;
routeData.Values["controller"] = "Error";
routeData.Values["action"] = action;
controller.ViewData.Model = new HandleErrorInfo(ex, currentController, currentAction);
((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
}
Please help where I am wrong why error and Info is not logged in windows event
I think you have not referenced the EventLogger correctly in code:
ILog eventLogger = log4net.LogManager.GetLogger("EventLogAppender");
Should be changed to:
ILog eventLogger = log4net.LogManager.GetLogger("EventLogger");

How to use aspnet-session pattern layout?

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

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>

Resources