My solution in Visual Studio 2012 currently contains two projects:
- DLL
- WPF application (which requires methods of the DLL)
Both, the DLL and the WPF application, use NLog
for logging. Currently each project contains the NLog
DLL itself.
Here is what I don't understand:
- It seems unnecessary to me including the identical
NLog
DLL in each project.
- The DLL however shall be reusable in other solutions, i.e. somehow the
NLog
DLL must be contained in the DLL project.
What would be an adequate way of setting up the Visual Studio solution and/or projects?
well you need the DLL in all projects where you use it and surely you need it deployed with the binaries of the executable (WPF application in your case) so that it can be found and used at runtime.
what I tend to do in all my projects is create a wrapper around the logging engine so that I do not need to reference and depend on specific third party logging APIs, like Log4Net or NLog, so I use my wrapper logging class everywhere and then I have a reference to the logging asembly only in the wrapper class's project and in the executable project to have the assembly deployed to the bin folder.
hope this helps ;-)
If your DLL is just a core library you plan on sharing among various projects, it may be wise to add an NLog reference and wrapper code to just that library, then make sure that any consumer application (such as your WPF project) has an NLog.config file associated with it.
Since you're using VS2012 I'm assuming you're also most likely working with .NET 4.5 which allows you to take advantage of the new caller info attributes. I've written the following code below for a basic NLog wrapper and believe it has the perfect balance of efficiency (doesn't use StackTrace) and usability.
using System;
using System.Runtime.CompilerServices;
using NLog;
namespace ProjectName.Core.Utilities
{
/// <summary>
/// Generic NLog wrapper.
/// </summary>
public static class Logger
{
/// <summary>
/// Gets or sets the enabled status of the logger.
/// </summary>
public static bool Enabled
{
get { return LogManager.IsLoggingEnabled(); }
set
{
if (value)
{
while (!Enabled) LogManager.EnableLogging();
}
else
{
while (Enabled) LogManager.DisableLogging();
}
}
}
/// <summary>
/// Writes the diagnostic message at the Trace level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Trace(string message, Exception exception = null,
[CallerFilePath] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Trace, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Debug level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Debug(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Debug, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Info level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Info(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Info, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Warn level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Warn(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Warn, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Error level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Error(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Error, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Fatal level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Fatal(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Fatal, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the specified diagnostic message.
/// </summary>
/// <param name="level"></param>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
private static void Log(LogLevel level, string message, Exception exception = null, string callerPath = "", string callerMember = "", int callerLine = 0)
{
// get the source-file-specific logger
var logger = LogManager.GetLogger(callerPath);
// quit processing any further if not enabled for the requested logging level
if (!logger.IsEnabled(level)) return;
// log the event with caller information bound to it
var logEvent = new LogEventInfo(level, callerPath, message) {Exception = exception};
logEvent.Properties.Add("callerpath", callerPath);
logEvent.Properties.Add("callermember", callerMember);
logEvent.Properties.Add("callerline", callerLine);
logger.Log(logEvent);
}
}
}
Then try throwing this into the layout field of one of the targets in your NLog.config to grab the detailed caller information.
${event-context:item=callerpath}:${event-context:item=callermember}(${event-context:item=callerline})
You better abstract the use of your logging mechanism. I described this in this blog post, it's about log4net but is the same principle whatever framework you use. In any case, you need the log assembly in every project where you use it, but by abstracting it it's easy to replace it by something else (when testing for example). Logging is infrastructure, so you would put the interfaces and concrete implementation in an infrastructure project, and reference that one from the projects in which you want to log.