log4net custom logging - c#

Someone please recommend a better title for this question. I'm not sure what to put.
Right now we have a log wrapper around an instance of ILog that prepends some text to the logged messages. What I'd like to do instead is implement either ILayout or ILogger or IAppender, which could then be specified in our configuration XML. The text I want to prepend isn't static. Because it's used in every log entry, we want to implement it once rather than everywhere we make a log message in the code.
Does this make sense? Which interface should I implement? Right now we use the PatternLayout.

It depends on how you plan to reuse it (for example, when using multiple appenders), but since you are changing the text of the log message, ILayout sounds like the best choice.
You could inherit PatternLayout and do your stuff in Format.

I agree with implementing a custom PatternLayoutConverter. Here a couple of examples:
This one adds the System.Diagnostics.Trace.CorrelationManager.ActivityId to the output:
public class ActivityIdLayoutConverter : PatternLayoutConverter
{
protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
{
writer.Write(Trace.CorrelationManager.ActivityId.ToString());
}
}
This one is parameterized (it can be configured with a key which can be used to retrieve a value from a dictionary - similar to the GDC or MDC):
class KeyLookupPatternConverter : PatternLayoutConverter
{
protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
{
string setting;
//Option is the key name specified in the config file
if (SomeDictionaryWithYourValues.TryGetValue(Option, out setting))
{
writer.Write(setting);
}
}
}
Here is a link to a question that I asked about creating a PatternLayoutConverter that can take a key value. It shows how to do it in log4net and NLog as well as how to configure.
Alternatively, you could wrap a log4net logger and in your wrapper's "Log" method, you could modify the input message or your could put your custom values in the GlobalDiagnosticContext.Properties or ThreadDiagnosticContext.Properties and then reference the values in the output via the normal properties token method.

You might want to use dependency injection on your app which you can change the way you are logging later on to whichever you want.

Related

Retrieving HttpContext in a Custom NLog Target

I may me missing something basic here - but is it possible to retrieve the HttpContext.Current in a custom NLog event?
I am trying to give each request a unique Guid so that I can correlate logging messages to a single event (i.e, tie together each log event for a single request). So, I want to store this Guid in HttpContext.Current.Items, then retrieve it in the NLog target and include it in the log message.
Here is my example target where I'd like to access HttpContext.Current:
[Target("AzureTableTarget")]
public class AzureTableTarget : TargetWithLayout
{
public AzureTableTarget()
{
_appSettings = IoCResolver.Get<IAppSettings>();
}
protected override void Write(LogEventInfo logEvent)
{
var correlationId = HttpContext.Current; //This is always null
var batchOperation = new TableBatchOperation();
CxLogEventBuilder.Build(_appSettings, logEvent).ForEach(batchOperation.Insert);
_loggingTable.ExecuteBatchAsync(batchOperation);
}
}
Nowadays it's easier to retrieve the HTTP Context in a NLog target (works for ASP.NET and ASP.NET Core)
Install NLog.Web (ASP.NET) or NLog.Web.AspNetCore (ASP.NET Core) package
For ASP.NET core, follow the ASP.NET Core - NLog setup
Inherit from AspNetLayoutRendererBase (namespace NLog.Web.LayoutRenderers)
Get the request by calling var context = HttpContextAccessor.HttpContext;
Example:
[LayoutRenderer("aspnet-sessionid")]
[ThreadSafe]
public class AspNetSessionIdLayoutRenderer : AspNetLayoutRendererBase
{
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
var context = HttpContextAccessor.HttpContext;
var contextSession = context?.Session();
if (contextSession == null)
{
InternalLogger.Debug("HttpContext Session Lookup returned null");
return;
}
builder.Append(contextSession.SessionID); // ASP.NET Core: contextSession.Id
}
}
PS: there are currently many predefined renderers for ASP.NET (Core): https://nlog-project.org/config/?tab=layout-renderers&search=aspnet
If your custom target should capture one (or more) context-specific values, then I recommend that your target inherits from TargetWithContext (or AsyncTaskTarget).
It gives the ability to setup and capture contextproperty-items. Where the Layout can be assigned to capture context-details. Examples of possible context-details easily available from HttpContext:
https://nlog-project.org/config/?tab=layout-renderers&search=package:nlog.web.aspnetcore
For more details about writing custom-targets:
https://github.com/NLog/NLog/wiki/How-to-write-a-custom-target-for-structured-logging
https://github.com/NLog/NLog/wiki/How-to-write-a-custom-async-target
Btw. there already exists this custom target that nicely inherits from AsyncTaskTarget:
https://www.nuget.org/packages/NLog.Extensions.AzureCosmosTable/
This article about Working with HttpContext.Current might help. The key, for you, might be that when control passes from one thread to another HttpContext.Current in the new thread can be null.
Here is another question/answer from here on SO that describes HttpContext.Current being null in the context of a web service. The accepted answer suggests turning on ASP.Net compatibility in your web.config file.
I don't know of either of these will help, but they might. I found them by googling for "HttpContext.Current is null", which yielded quite a number of hits. I have done very little ASP.NET development, so I can't really comment on HttpContext.Current from my own personal experience.
Given your use case, I would suggest that you look into System.Diagnostics.CorrelationManager.ActivityId.
One nice feature of ActivityId is that it is "flowed" from parent threads to child threads (including thread pool threads). I think that it works well with Tasks and Parallel operations. Works well meaning that the ActivityId, as set in a parent thread, has the expected value in a child thread.
There is not a LayoutRenderer for ActivityId, but it easy enough to write one. See an example (written against NLog 1.0) here:
Most useful NLog configurations
I'm pretty sure that the "EstimatedBufferSize" stuff is no longer needed, so something like will probably work:
[LayoutRenderer("ActivityId")]
class ActivityIdLayoutRenderer : LayoutRenderer
{
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
builder.Append(Trace.CorrelationManager.ActivityId);
}
}
If you go this route, you might consider adding a Format property to the ActivityIdLayoutRenderer to allow you to specify the guid format. See this answer (from me). It contains a lot of useful information about working with guids.
NewGuid vs System.Guid.NewGuid().ToString("D");
See this source file (in NLog's git repository) for an example of how you can implement and use such a Format property:
https://github.com/NLog/NLog/blob/master/src/NLog/LayoutRenderers/GuidLayoutRenderer.cs

How do I make log4net.dll optional?

I use log4net to implement logging in my .NET app.
However I do not want to distribute the 300kb log4net.dll for every user but instead send this dll to the user if he has problems and I want him to provide logs.
So, is it possible to make my app run whether or not the dll is there?
Of course for logging the dll would be needed, but if no logging is needed, the app should run without the dll.
Yes, it is possible.
First create an interfase with all your log methods:
public interface ILogger
{
void Write(string message);
// And much more methods.
}
Now create two instances, a dummy instance (lets call it DummyLogger), and a instance which will send its messages to Log4Net (Log4NetLogger).
To finish, create a factory class:
static public class LogFactory
{
static public ILogger CreateLogger()
{
if (/*Check if Log4Net is available*/)
return new Log4NetLogger();
else
return new DummyLogger();
}
}
You could check if Log4Net is available by simply checking if the file is in your bin-folder. Something like:
File.Exists(AppDomain.CurrentDomain.BaseDirectory + "Log4Net.dll")
But I can imagine that you want to do other checks, like if it exists in the GAC or whatever.
Now you can use your factory to create your logger and "write" messages to the log:
ILogger logger = LoggerFactory.CreateLogger();
logger.Write("I am logging something!");

How can I get the URL of the current page from within a C# App_Code class?

I have a logging class that, well, logs things. I would like to add the ability to automatically have the current page be logged with the messages.
Is there a way to get the information I'm looking for?
Thanks,
From your class you can use the HttpContext.Current property (in System.Web.dll). From there, you can create a chain of properties:
Request
Url and RawUrl
The underlying object is a Page object, so if you cast it to that, then use any object you would normally use from within a Page object, such as the Request property.
It's brittle and hard to test but you can use System.Web.HttpContext.Current which will give you a Request property which in turn has the RawUrl property.
public static class MyClass
{
public static string GetURL()
{
HttpRequest request = HttpContext.Current.Request;
string url = request.Url.ToString();
return url;
}
}
I tried to break it down a little :)
In the past I've also rolled my own logging classes and used Console.Writeln() but really there are a number of good logging options that already exist so why go there? I use NLog pretty much everywhere; it is extremely flexible with various log output destinations including console and file, lots of log format options, and is trivial to set up with versions targeting the various .net frameworks including compact. Running the installer will add NLog config file options to the Visual Studio Add New Item dialog. Using in your code is simple:
// declare in your class
private static Logger logger = LogManager.GetCurrentClassLogger();
...
// use in your code
logger.Debug(() => string.Format("Url: {0}", HttpContext.Current.Request.Url));

Creating a protected link

Is there a way to create a protected download link which is random, expiry, requires a password and pointing to a specific file in C# that is associated with IIS 7.0?
Several random links can link to the same file.
Built-in codes or perhaps 3rd party libraries?
For example, http://www.example.com/<some random gibberish>/<md5 of file>/file.jpg
One way to do this would be to use GUIDs. GUIDs are designed not to collide, and that design also leads to a difficulty in guessing valid GUIDs. I'm sure someone out there will tell me that this is not very secure! Well, you are also protecting with a password. It is pretty easy to generate a GUID in C#.
I guess what you need is firstly a way of ingesting the files that you want to protect in this way, and secondly a handler that will respond to requests in a given path and inspect the GUID in the path to determine if it's valid.
You'd then need a database back end to maintain lists of GUIDs corresponding to URLs, the password (preferably crypted) and the expiry date. The handler would inspect the entry for the requested URL/GUID to see if the link has expired, then prompt the user (could do this via a web form easily enough) for the password and check this against the crypted password stored in the database.
To generate a GUID, you want:
System.Guid.NewGuid().ToString()
To create a module that is called before every request (for IIS7) you can add an entry to your web.config like so:
<modules>
<add name="MyDownloadModule" type="Example.MyDownloadModule, Example"/>
</modules>
where MyDownloadModule is the class containing your handler, in the namespace Example.
Inside that class you then need to implement the IHttpModule interface, in particular overriding the methods:
public string ModuleName {
get { return "MyDownloadModule"; }
}
public void Init(HttpApplication app) {
// Add an event handle which is called at the beginning of each request
app.BeginRequest += new EventHandler(this.AppBeginRequest);
}
//
// Our event handler for the BeginRequest event
//
private void AppBeginRequest(Object source, EventArgs e)
{
HttpRequest request = app.Context.Request;
//
// Is this a file download?
//
if (request.AppRelativeCurrentExecutionFilePath == "~/downloads") // or whatever
{
// this is where you work your GUID inspecting magic
}
}
Going about it this way means this will be called for every request to the server, which may not be what you want.
You could always create your own HttpHandler, and then implement your own proprietary expiration/validation code.
Something like:
http://www.example.com/download?token={your_token}
It would then be a trivial matter to have the handler intercept the request and grab the file from disk, and deliver it to the client if the ?token querystring value is correct.
For more information on the IHttpHandler interface, see MSDN http://msdn.microsoft.com/en-us/library/system.web.ihttphandler.aspx

Singleton logger, static logger, factory logger... how to log?

I am wrapping the patterns & practices Enterprise Library Logging Application Block for an application written in .NET.
I want to be able to subclass a logger (i.e to provide domain specific logging).
What is the best way to do this?
For e.g, I have a static Logger class at the moment, but this does not allow me to specialize it for domain specific logging.
For example,
Log(MyDomainObj obj, string msg)
Check out NLog. They use this sort of pattern:
private static Logger myDomainLogger = LogManager.GetCurrentClassLogger();
You can then specialize the output based on the class that myDomainLogger belongs to.
More detail:
class MyDomain
{
private static Logger _logger = LogManager.GetCurrentClassLogger();
private void SomeFunc()
{
_logger.Trace("this is a test");
}
}
Then in your output you can have it output "MyDomain.SomeFunc" as part of the "this is a test" message.
Also, checkout log4net. I never found the EL's logging to be as flexible as log4net. I chose log4net since I was already familiar with using log4j.
protected readonly log4net.ILog LOG = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Doing it this way, I can get logs like this:
2009-07-15 09:48:51,674 [4420] DEBUG
SampleNamespace.SampleClass [(null)] -
Sample message you want to output
You could even do better than that. Write a wrapper class that wraps either Nlog or log4net or whatnot. You can then use that wrapper class (maybe use an interface to it if you really want to decouple things) in your code. This way, if you decide to change logger class, you need to change just one class and not edit all your classes.

Categories