I am working within a solution that has a static logging object in a library that is shared among the projects. This is how it is structured:
public class AppLog
{
private static string _logFile;
private static string _appName;
public static string AppName
{
get { return _appName; }
set
{
_appName = value;
InitLogFilePath(); // initializes _logFile according to _appName
}
}
public static Write(string msg)
{
// writes to _logFile
}
}
It works fine for the various Windows apps and Windows services: They can initialize AppLog.AppName upon startup and AppLog.Write can be called throughout the code. Shared modules write to a file named according to the initialization of AppName.
The problem I have is using this within WCF web services. The web services are configured for InstanceContextMode.PerCall. AppLog.AppName is being initialized according to ServiceHostBase.Description.Name. But since multiple web services run within the same AppDomain this static data is shared. So one ws call sets AppLog.AppName and it is changed by the next call, which may have a different ServiceHostBase.Description.Name.
How can this be restructured so that AppLog.Write can still be used throughout the projects in my solution but handle the naming differently for each web service?
If could tell whether the code is running within a web service, and if I could retrieve the ServiceHostBase.Description of the service, then I could maintain a lookup for the appropriate file name. But I have not yet found a way to do this.
Given the way your logging is structured there is not a good solution.
Most logging libraries are structured so that you create an instance of the logger, pass the instance any application specific data (like AppName), and then store that instance in a private static member. The static storage is in the application, not the logging library. This avoids the sharing conflict that you have and still only creates a small fixed number of logger instances.
To illustrate the point, here's a standard log4net example from CodeProject log4net tutorial. This code passes the current class name to the instance of the logger.
private static readonly log4net.ILog log = log4net.LogManager.GetLogger
(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
My suggestion is to look at changing to log4net or any of the other logging packages available on NuGet.
Given your situation, AppName is not where is should be. You need a per-webservice logging facade that hold the AppName and pass the core "Write" logic down to your current AppLog. Then each of the web service has its own LogFacade instance.
class LogFacade
{
public string AppName {get; private set;}
LogFacade(string appName)
{
AppName = appName;
}
public void Write(string msg)
{
AppLog.Write(string.format("[{0}]{1}", AppName, msg));
}
}
Or as ErnieL said, take a look at log4net.
Related
I'm currently playing around with the IoC concept (with a WPF app) and I haven't decided on the tool I'll used with it just yet as I'm still trying to get the grasp of it but I'm confused as to how this would be configured regarding the specific parameters each component.
I understand how you define the relevant library in the config file and how it will determine which one should be used by the app and what its lifespan should be but what about each library requiring its own specific set of parameters.
Where do you get these from and when do you pass them on?
Taking your typical logger for example.
I have the following interface:
public interface ILogger
{
void Write(string message);
}
I have the logger class itself:
public class Logger : ILogger
{
private readonly ILogger _logger;
public Logger (ILogger logger)
{
_logger = logger;
}
public void Write(string message)
{
_logger.Write(message);
}
}
I then define multiple loggers each requiring their own parameter, so I implemented the following:
a) database logger: where a connection string is required so that I can log my message to a database.
public void LoggerDb: ILogger
{
public void Write(string message)
{
}
public ConnectionString {get; set;}
}
b) file logger: where a filename is required so that I can log my message to the relevant log file.
public void LoggerFile: ILogger
{
public void Write(string message)
{
}
public Filename {get; set;}
}
c) console logger: where no parameter is required as I just want to output my message to a console window.
public void LoggerConsole: ILogger
{
public void Write(string message)
{
}
}
In my console test app, I've got the following code in the Program.cs:
static void Main(string[] args)
{
string logTypeId = "d";
ILogger logType;
if (logTypeId == "d")
{
logType = new LoggerDb("Data Source=....");
}
else if (logTypeId == "f"
{
logType = new LoggerFile("c:\\mylog.txt");
}
else
{
logType = new LoggerConsole();
}
Logger logger = new Logger(logType);
logger.Write("Message 1");
logger.Write("Message 2");
logger.Write("Message 3");
}
I understand this is not how the code would be if I used an IoC tool. I'm just trying to highlight what I'm trying to achieve and I'm trying to get answers to the following questions:
Can this be achieved using an IoC tool i.e. pass specific parameter depending on the logger type that's used/defined in the IoC section of the app.config?
Is this the correct approach i.e. Having specific loggers with their own constructors parameters? If not, can you explain why and what should be the correct approach. I don't mind the IoC tool you use. I just want to understand how this should be done.
Where should these additional parameters be stored in the app.config?
First, note that in order to implement DI via an IoC, it is by no means required to configure your container in a configuration file (although it's certainly an option and many containers support it).
Most IoC containers these days also allow you to specify your setup in code. So I guess the answer is: it really depends on the IoC container you plan to use. My opinion: avoid xml-based configuration if you can; it's often a pain to maintain and brings little value if you ask me. In your code-based configuration you can still refer to configuration parameters from app.config or other.
You can also turn the question around: is it a requirement to have the container configuration in a separate file (and why)? If yes, look for a container that supports this well. But most do.
Some examples of configuration using a code-based DSL:
Autofac modules: http://docs.autofac.org/en/latest/configuration/modules.html
StructureMap: http://structuremap.github.io/registration/registry-dsl/
Some examples of xml configuration:
Autofac: http://docs.autofac.org/en/latest/configuration/xml.html
Spring.NET container: http://www.springframework.net/doc-latest/reference/html/objects.html
structuremap: http://docs.structuremap.net/configuring-structuremap/structuremap-xml-configuration/
It depends ;)
I can't speak for all DependencyInjection Tools, but many of them should support this functionality.
I don't see anything that speak against this. If you want to call different Loggers explicitly, you can do this. But you can also use some kind of LogListeners. One for DB, one for File and so on. And your Logger just delegates the LogMessage to all Loggers. But this depends on what you want or need ;)
This also depends on the implementation of the Logger. It's common to store the ConnectionString in the config. The other parameters are too specific, but you you can store them in config, too.
I have a solution with multiple projects. One of them is a Services project and within that most of my backend code resides and thus, the settings for most of those services reside in its app.config file.
I created a class to access all those settings, so they would be available outside the Services project.
public static class ServicesAppSettings
{
/// <summary>
/// Simple App Settings classes for strong typing
/// When creating a new class please ENSURE THAT THE PROPERTY NAME EXACTLY MATCHES THE APPSETTINGS KEY!
/// </summary>
public static class SmsSettings
{
public static string SmsProvider => ConfigurationManager.AppSettings["SmsProvider"];
public static string SmsAccountId => ConfigurationManager.AppSettings["SmsAccountId"];
public static string SmsPassword => ConfigurationManager.AppSettings["SmsAccountPassword"];
public static string SmsFromPhone => ConfigurationManager.AppSettings["SmsFromPhone"];
}
public static class EmailSettings
{
public static string EmailProvider => ConfigurationManager.AppSettings["EmailProvider"];
public static string EmailProviderHost => ConfigurationManager.AppSettings["EmailProviderHost"];
public static string EmailAccount => ConfigurationManager.AppSettings["EmailAccount"];
public static string EmailAccountPassword => ConfigurationManager.AppSettings["EmailAccountPassword"];
public static string GetEmailAddressFromApplication()
{
return string.Format(EmailAccount + "#" + EmailProvider);
}
}
public static class UserIcons
{
public static string MaleUserIconSource => ConfigurationManager.AppSettings["MaleUserIcon"];
public static string FemaleUserIconSource => ConfigurationManager.AppSettings["FemaleUserIcon"];
}
}
and it works perfectly fine EXCEPT when the settings aren't in the WEB.CONFIG file. Why on earth is this drawing from the mvc's web.config file and NOT its own libraries app.config file. Trust me, this is the case. If I remove one setting from web.config and then try to access it in a test it's null, if i put it back in web.config it works.
Anyone know why this is? Can you specify to the ConfigurationManager the name of the file you want to retrieve from?
It happens because only one configuration file is actually accessible - config of your executable project. That's why in mvc application you can read settings from web.config of your executable mvc application. If you would have service application (ms service) it will be app.config of you startup service.
As I know, you can not read settings from another config file.
One the possible solutions is to store your custom settings in .xml file and read it. This is also useful if you want to change your custom settings at a runtime.
Another one (in case you don't want to mess up your settings in appSettings section) is to create your custom config section and use it for your settings
Hope that helps
Web.config is the place to put your settings. Dlls that are part of a .net web solution will use it.
I am trying to access global.asax application variable from WCF, that's my goal at least. I've tried many type of solutions, but the one that I am trying now is using static variables.
I've created a StaticVariable.cs class like so:
public static class StaticVariables
{
private static string _Key = "name1";
public static Object someInfo
{
get
{
return HttpContext.Current.Application[_Key];
}
}
}
The Application["name1"] is initialized in the global.asax.cs. I can read it when I access my webservice but not in my WCF service.
In my WCF I call the StaticVariables someInfo to retrieve the data, but I get:
System.Web.HttpContext.Current is null error
My WCF is running asynchronously and its called from within a webservice using Task<int>.Factory.FromAsync. So I assume that the problem is that the WCF runs not on the main thread, but I am not sure.
So it seems that the Static class doesn't work in my case and I wanted to know how to solve this. Thanks
Why don't you simply use static variables ?
HttpContext is dependent on ASP.NET pipeline. In a host-agnostic model (OWIN or self-hosted) you don't have access to it.
Application storage in HttpApplicationState is only useful if you need to access the current HttpContext. If it's not necessary, you should simply use static properties.
Moreover, HttpApplicationState was initially created for backward compatibility with classic ASP.
public static class StaticVariables
{
public static object SomeInfo { get; set; }
}
See also Singleton and HttpApplicationState and http://forums.asp.net/t/1574797.aspx
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!");
I have an app that is using the log4net AdoNetAppender (DB appender) to write my logs to the database. It is writing the records to the DB, but the custom fields are all NULL. They work fine using the same code in my unit tests, but not when they are called when the application is running. It is a multi-threaded application that processes messages off of a message queue.
Is there any known issues (that anyone is aware of) regarding custom properties for the DB appender with multi-threaded applications? That is my only guess as to why they are not working when the app is spun up because I can't reproduce in unit tests, etc.
The default log4net fields come through fine.
I am setting the custom property values in a Singleton:
public sealed class Logger
{
private static readonly Logger instance = new Logger();
public static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Static constructor which sets the properties for the database logging using log4net. This enables analysts to view the log events
/// inside the database as well as in the local application log file.
/// </summary>
static Logger()
{
GlobalContext.Properties["Application"] = "MyApp";
GlobalContext.Properties["ApplicationVersion"] = Utility.GetApplicationVersion();
var windowsHost = System.Net.Dns.GetHostName();
if (!String.IsNullOrEmpty(windowsHost))
LogicalThreadContext.Properties["Host"] = windowsHost;
//ThreadContext.Properties["LogTime"] = DateTime.Now;
var windowsIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
if (windowsIdentity != null)
GlobalContext.Properties["CreatedBy"] = windowsIdentity.Name;
}
private Logger()
{
}
public static Logger Instance
{
get { return instance; }
}
}
EDIT:
Found this going to try and add additional log4net debugging.
http://logging.apache.org/log4net/release/faq.html#internalDebug
Not seeing any errors in log4nets internal logs so still unclear about what is going on.
I finally figured it out. It was how we were trying to set the custom properties globally. I instead set the properties on app startup prior to any of my instances binding to the queue and processing messages. That seemed to do it....not sure why I didn't think of that in the beginning. Just used the log4net GlobalContext and worked like a charm.
--S