We have the following scenario where we encounter an issue with Serilog due to .NET's AppDomain isolation:
|App| --> |3rd| --> |Class| --> Log.Debug("...") --> [NOK]
`-------------> |Class| --> Log.Debug("...") --> [OK]
(1) (2) (3) (4) (5)
We create a global Logger object in our app's Main() method and assign it to Log.Logger.
We invoke a third-party tool that internally creates a new AppDomain. (We have no control over this and cannot change this behavior.)
Instance objects of Class is created both within our app and from the third-party tool.
Class executes code for logging using Serilog's static Log class.
Log.Logger references the correctly configured Logger when accessed from the Class object created from our app, but is just set to a default SilentLogger in the Class object created via the third-party tool.
This, I guess, is the expected behavior since the two Class objects belongs to two different AppDomains and static variables are isolated to their own AppDomain.
Is there some way to work around this? Can we somehow make use of Serilog's convenient static Log class? Is there something else in the Serilog framework other than the Log class solving this kind of issue, that we might have missed? I found a similar issue in #380 but without a final solution, as far as I could see.
We considered just creating a new Logger object in the new AppDomain. However, that means properties that we pushed to the Logger somewhere between 1-2 and 1-3 is not included in the new Logger's log events.
Related
I have a C# project that outputs a dll: foo.dll. This foo.dll internally uses a legacy library legacy.dll. Here is how my library foo.dll will be used: first I upload these files: foo.dll, legacy.dll, and legacy.dll.config to a third party; then the third party starts up a process which loads my main library foo.dll and executes some functions. When foo.dll is being run, I see exception thrown in legacy.dll saying some configuration "baz" cannot be found. However, I can verify that the configuration "baz" is defined in the legacy.dll.config file. So I think the file legacy.dll.config is not loaded by the process.
So I wonder how config.dll files are used. In my case, considering foo.dll is the only thing within my control, is there a way to load the legacy.dll.config file?
One solution is to place the configuration section that your legacy.dll requires in the configuration (app.config or web.config) for the application that references it.
Do you have control over your legacy application - in other words, are you able to modify it? One occasional challenge is that when all of these extra values are dumped into the configuration it can be more difficult to tell where they are used. They can even get left in the configuration long after you stop using legacy.dll because no one knows what they are and they're afraid to remove them.
Going the other way, if the values are missing from the configuration, it's best not to throw a confusing NullReferenceException or configuration exception that requires someone to dig into the legacy code and figure out why it doesn't work.
There are a few things you can do to make this easier:
One is that your legacy.dll can look for configuration values that are distinct and separate from the rest of your configuration.
That could mean a separate configuration section like
<section name="stuffTheLegacyDllNeeds" type="Legacy.LegacySettingsConfiguration, Legacy" />
<stuffTheLegacyDllNeeds>
....
</stuffTheLegacyDllNeeds>
Or you can disambiguate the appSettings keys with some convention like
<add key="myLegacyLibrary:someSetting" value="true" />
You can also make it easier for other developers by throwing useful exception messages if a required key is missing, like "Legacy requires appSettings key 'xyz' which was not found." They still have to figure out where to find the values but at least they have a clear understanding of the problem.
Also, if you find that Legacy.dll doesn't change settings often or ever, you can code it to replace missing values with default values. That way they can override the defaults if they need to by adding configuration values. But if they don't need the defaults they can just not supply configuration values.
One more approach - this is the one I personally prefer:
Make your legacy class depend on an interface for settings like
public interface ISettings
{
bool SomeSetting { get; }
int SomeOtherSetting { get; }
}
and make your legacy class require an instance of ISettings in its constructor. Now the app that references it "knows" about the settings because it can't use the class without providing it. Your legacy library can provide an implementation like
public class ConfigurationSettings : ISettings
that reads from configuration, and the referencing app could choose to use it. Or it could supply another class that implements the interface. It could also provide a class that contains default values which the referencing app could change. It could also have a set of defaults that it uses internally if the referencing app doesn't provide any values.
Now,
The referencing application knows that your class needs these settings.
The referencing application can choose how to provide them.
Your legacy library no longer depends on the configuration. It depends on an abstraction - ISettings - which can be implemented using configuration or some other way. That means your legacy library will be easier to unit test. Code that explicitly depends on configuration values is much harder to test, or likely never even gets tested.
I'm having at the moment a big solution with different projects sending logs with Nlog to LogReceiverService target.
I would like to use an interface so I have only 1 installation of Nlog in 1 project of my solution.
So I created a new interface to call instead of NLog.LogReceiverService.ILogReceiverClient (inherited from it) but when I initialize my logger, it's still looking for the contract NLog.LogReceiverService.ILogReceiverClient
Is this a normal operation. Is there a way to change this contract?
Our team has created a library that automates the implementation of exception handling and logging for WCF Services. Using this library, developers need only to decorate a service with a custom attribute, setup some simple configuration file entries, and they can already take advantage of a generic exception handling and logging mechanism.
Here's an example of how the library is used:
[ErrorHandlingBehavior (LogWriterOption.EmailLogWriter, LogWriterOption.SQLLogWriter)]
public class SampleService : ISampleService
{
public string GetData(int value)
{
throw new DivideByZeroException();
//return string.Format("You entered: {0}", value);
}
}
The ErrorHandlingBehavior class makes use of a Logger object that takes in the LogWriterOption enums in the parameters to figure out where to log.
Our original intention was to allow the developer to specify his own logging mechanism and supply it to the ErrorHandlingBehavior, so as to remove the dependency of the solution on the Logger class (instead it takes in any class that implements ILogger). However, specifying attributes in the manner below produces an error:
[ErrorHandlingBehavior (new Logger (new HashSet<LogWriterOptions>
{LogWriterOption.EmailLogWriter, LogWriterOption.SQLLogWriter}))]
It seems we cannot instantiate anything when specifying attributes, and therefore we are now unable to let users specify their own logging mechanism.
Would anyone know of a way around this? How can we feed an instance of a class implementing ILogger to our attribute instead of hard-wiring the depenendency?
I also wrote some logging exception handling behaviors. In situations like this I always asked myself:
What would log4net do?
Your LogWriterOptions appear to translate to log4net appenders. Appenders are generally best done through xml configuration because their requirements change by environment. (Its the logging equivalent of don't put your WCF client binding in code.) In other words, when developing locally: don’t send email and just output to a local text file. When running in QA: output to the DB but don’t send email with a tester breaks something on purpose. In production: do something else completely different. Log4net appenders support all these types of post compile changes (and more).
Back to your question:
In your approach, I would pass the ErrorHandlingBehavior a behavior name as a string like “StandardLogging” which would look up a configurable behavior that results in EmailLogWriter and SQLLogWritter being used.
An alternate approach which is common in logging frameworks is to pass the type of the class being logged. If that type is not explicitly configured, it gets the default appenders.
Note that this configuration approach has the added benefit of
Centralizing logging output options for the entire application. If you change your standards, you don't have to update many class files.
Standardizing what log writer options different pieces of code are using. In the code review meeting you simply ask “Are you using standard logging output?” Check.
Update in response to your "keep it simple" comment:
If keep it simple is the goal, I would say pass nothing to your behavior's constructor. Instead put all the log4net config information in its own log4net.config file and store that as a part of the common logging libraries in source control. Then new projects (or junior devs) are just required to add
<configuration>
<log4net configSource="log4net.config" />
</configuration>
to app.config. The bonus to this approach is that as a part of our build process we defined different log4net.config files for deployment to different environments.
You could use the factory pattern. Have the developer specify a type that will be used to provide an ILogger instance:
[ErrorHandlingBehavior(LoggerFactoryType = "FooBar.MyLoggerFactory")]
This type could implement an interface of yours:
public interface ILoggerFactory
{
ILogger GetLogger();
}
and then inside your custom attribute you could first get the factory type using the Type.GetType method, check if it implements the ILoggerFactory interface, instantiate the factory using the Activator.CreateInstance method and finally call the GetLogger method on that instance.
My current C# application is a single executable (EXE) and a few DLLs (so multiple binaries) and I want to create a LOGGER (some simple custom logger that writes to a single text file no matter where it is being called from - and this should be available in all the binaries (exe and dll's) - note that it is a single threaded application.
Right now I have a DLL (Logger) and it has a class (Log) with a method (trace) which logs, each project adds a reference to this DLL (Logger) and creates its own instance (pass in a file name) and then calls the .Trace(...) function - works fine ...
But I would rather not have to create many different trace files (minimum one per project) and having to create a new instance of Logger each time seems repetitive ... So I was looking into either creating a STATIC logger class or using a SINGLTON ... I am just not sure which is best and why ...
I was hoping someone could maybe point me in the right direction on this, what is the best way to create a logger class (it will be its own DLL) that will be used by many projects (referenced) and they should all write to the same log file...?
Any help would be much appreciated.
Thanks,
You choose between Singleton and Static logger the same way you always choose between the two: Do you want to be able to override methods (or use a Logging interface)? If you use a singleton, you have the opportunity to override methods to change functionality. You can even abstract the behavior away behind an interface.
With a static class you are now and forever tied to that class, and any changes affect everyone.
When dealing with my own systems, i have moved towards singleton instanced objects. It gives a level of flexibility not available with static classes.
I have a C# solution containing multiple C# projects. I am planning to add logging in it. This logging should be available in all the projects and preferably use log4Net with rolling file logs.
With the above said premise, I could think of two ways to do that.
Initialize logger in entry point (Program class) of the solution & Get the logger instance & use it as a member variable for every class that needs logging.
Add another project, Utilities & define a Logging class with static logging methods. This class should be initialized in entry point (Program class) of the solution.
What could be the best possible solution?
I have a similar situation. What we've done is use 1 app config for all the projects and use links to reference it.
In the app.config for your app you set the log4net config Section
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
And later set the Appender:
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
...
And in each class you want to log you put a line similar to this:
private static readonly ILog log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Then each class grabs the same logger (singleton).
Would that work for you?
In my projects, I wrote a wrapper around LOG4NET to (theoretically) exchange it with other logging frameworks like NLOG without breaking my code.
My own logging class is accessible through a static singleton. It does initialization in this singleton.
(BTW: I published the source code over at the Code Project some time ago, maybe it gives you some inspiration)
If you're thinking about initializing something at the entry point every single time, then you're writing yourself a case for dependency injection. Using a DI library (like Unity, Castle, or whatever flavor you personally like), you could do either constructor injection or property injection to get your logger class (or Log4Net) initialized automatically.
Additionally, you can use the DI kernel to make it a singleton, so that you only have one instance of it active.
Building off of Queso's answer, we had .dll's that were imported with reflection at runtime. In order to get them to use Log4Net we created a log4net.config file with all the appropriate config sections. We also used the same line of code to intialize the log in each class that Queso references. The seperate config allowed us to use it throughout the entire app domain.
EDIT we also had to make a modifcation to the appsettings file to allow for this.
http://haacked.com/archive/2005/03/07/ConfiguringLog4NetForWebApplications.aspx
Option 1 is the way to go.
If you use static methods you loose the ability to use hierarchical loggers. This feature allows you to configure your log output differently for individual classes or even for entire sub systems e.g. YourNameSpace.Security. This tutorial elaborates on this topic (also the other chapters are a good read).
Building a wrapper is certainly not a bad idea, but it is not strictly necessary. It does allow for a few things though: change log framework, use DI, add additional log levels (e.g. log.Verbose()) or different overloads for logging...
Btw. I would initialize the loggers like this:
ILog log = LogManager.GetLogger(typeof(YourClass));