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
Related
I am using NServicebus(version 4.6.3) with SQLTransport in my ASP.net web api project. I have different connectionstrings for the queues for different environments (Dev,QA,etc). My configuration looks like below:
public class BusConfigurator
{
public static IStartableBus Bus { get; private set; }
public static void DisposeBus()
{
if (Bus == null)
return;
Bus.Shutdown();
Bus.Dispose();
Bus = null;
}
public static void InitializeServiceBus(string connectionString)
{
var configure = Configure.With()
.DefineEndpointName("MyEndPoint")
.Log4Net(new DebugAppender { Threshold = Level.Warn })
.UseTransport<SqlServer>(connectionString)
.PurgeOnStartup(false)
.SetDefaultTransactionLevel()
.UnicastBus(); // Error is thrown here on second call
configure.MyCustomSQLServerPersistence();
Bus = configure.CreateBus();
}
public static void StartBus()
{
Bus.Start(() => Configure.Instance.ForInstallationOn<NServiceBus.Installation.Environments.Windows>().Install());
}
}
I have a dropdown in the app so that the user can select the environment. Based on the selection, I want to reconfigure the bus. So, I call DisposeBus then pass the connection string to the IntializeServiceBus method followed by the startBus. It works first time but throws error below when it gets called again with different connectionstring:
Unable to set the value for key: NServiceBus.Transport.ConnectionString. The settings has been locked for modifications. Please move any configuration code earlier in the configuration pipeline
Source=NServiceBus.Core
Line=0
BareMessage=Unable to set the value for key: NServiceBus.Transport.ConnectionString. The settings has been locked for modifications. Please move any configuration code earlier in the configuration pipeline
Is NServicebus intended to be used/configured this way? (I am guessing probably not) If not then is there a workaround/different approach for this?
In V4 or below, there is no way to do it by normal human means. There is only one Bus per AppDomain. All of the configuration API is static, so if you try, you get exactly the problems you ran into.
By "human means", I mean that it might be possible to do something crazy with spinning up a new AppDomain within your process, setting up a Bus within that, and then tearing it down when you're finished. It might be possible. I haven't tried it. I wouldn't recommend it.
In V5, the configuration API is completely redesigned, is not static, and so this is possible:
var cfg = new BusConfiguration();
// Set up all the settings with the new V5 Configuration API
using (var justOneBus = NServiceBus.Bus.Create(cfg).Start())
{
// Use justOneBus, then it gets disposed when done.
}
That's right. It's disposable. Then you can do it again. In your case you wouldn't want to put it in a using block - you would want to set it up somewhere, and when the dropdown gets switched, call Dispose on the current instance and rebuild it with the new parameters.
Keep in mind, however, that the Bus is still pretty expensive to create. It's definitely still something you want to treat as an application-wide singleton (or singleton-like) instance. You definitely wouldn't want to spin up a separate one per web request.
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.
I have been looking at creating a common logging library for the company I work for, based on a blog by Daniel Cazzulino. so we can switch one out for another without to much disruption.
The first library I looked to use is log4net, but I cannot work out how or where you would setup a call to the XmlConfigurator.
I have tried adding an assembly on the project being logged, which kind of defeats the object of the exercise I feel, but that doesn't appear to work any way.
I have tried adding it as an assembly of the log4net Logging library, but that doesn't appear to work.
I have also tried calling log4net.Config.XmlConfigurator.Configure(); from the TraceManager.Get method, but all the log options (IsDebugEnabled, IsWarnEnabled, ...) are disabled.
public partial class TracerManager : ITracerManager
{
/// <summary>
/// Gets a tracer instance with the specified name.
/// </summary>
public ITracer Get(string name)
{
log4net.Config.XmlConfigurator.Configure();
var logger = LogManager.GetLogger(name);
return new Log4NetAdapter(logger);
}
/// The rest
}
Do I need to do something else?
Does the app config need to be in in the logging library?
[Edit 1]
Feel very silly....
I'd added [assembly: XmlConfigurator(Watch = true)] to my Logging.Log4Net library, but I wasnt instantiating the TracerManager in my application on the tests I was performing... ID-10Tango issue
I'd added [assembly: XmlConfigurator(Watch = true)] to my Logging.Log4Net library, but I wasnt instantiating the TracerManager in my application on the tests I was performing...
ID-10Tango issue
I am using NserviceBus 2.5 and was facing the problem NSB caching the Nhibernate Sessions.
I spent sometime on internet and found that class implementing IMessageModule interface is the way to solve this. I also saw the implementation of such at https://github.com/NServiceBus/NServiceBus/blob/v2.5/src/impl/SagaPersisters/NHibernateSagaPersister/NServiceBus.SagaPersisters.NHibernate/NHibernateMessageModule.cs
MyEndPoint is defined like
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantCustomLogging, IWantCustomInitialization
{
public void Init()
{
var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
var windsorContainer = IoCBootstrapper.InitializeForSession(Path.Combine(location, "MyDll.config")); //This line creates Windsor container without Nhibernate Session I have not written real dll.config name but my code contains
ISessionFactory sessionFactory = MessageSessionFactory.ConfigureSessionFactory();
windsorContainer.Kernel.AddComponentInstance<ISessionFactory>(sessionFactory);
windsorContainer.Register(Component.For(typeof(NHibernateMessageModule)).LifeStyle.Singleton);
windsorContainer.Register(Component.For(typeof(MessageHandler)).LifeStyle.Transient);
NServiceBus.Configure.With(AllAssemblies.Except("XYZ.dll"))
.CastleWindsorBuilder(windsorContainer)
.XmlSerializer()
.MsmqTransport()
.UnicastBus()
.LoadMessageHandlers();
SetLoggingLibrary.Log4Net(log4net.Config.XmlConfigurator.Configure);
}
}
I have defined following class to create SessionFactory
public class MessageSessionFactory
{
protected static ISessionFactory sessionFactory;
private static ILog log = LogManager.GetLogger(typeof(MessageSessionFactory));
public static ISessionFactory ConfigureSessionFactory()
{
try
{
if (sessionFactory != null) return sessionFactory;
string connectionString = System.Configuration.ConfigurationManager
.ConnectionStrings["SessionFactoryCS"].ToString();
NHibernate.Cfg.Configuration nHibernateConfiguration =
new NHibernate.Cfg.Configuration();
nHibernateConfiguration.SetProperty(
NHibernate.Cfg.Environment.ProxyFactoryFactoryClass,
typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory).AssemblyQualifiedName);
nHibernateConfiguration.SetProperty(
NHibernate.Cfg.Environment.Dialect,
typeof(NHibernate.Dialect.MsSql2005Dialect).AssemblyQualifiedName);
nHibernateConfiguration.SetProperty(
NHibernate.Cfg.Environment.ConnectionString, connectionString);
nHibernateConfiguration.SetProperty(
NHibernate.Cfg.Environment.FormatSql, "true");
nHibernateConfiguration.SetProperty(NHibernate.Cfg.Environment.CurrentSessionContextClass,
typeof(NHibernate.Context.ThreadStaticSessionContext).AssemblyQualifiedName);
nHibernateConfiguration.AddAssembly(Assembly.GetCallingAssembly());
sessionFactory = nHibernateConfiguration
.BuildSessionFactory();
return sessionFactory;
}
catch (TypeInitializationException ex)
{
throw new Exception("TO DO :Enter message");
}
}
}
Whenever I try to start the service I see messages like at HandleEndMessage
NHibernate.HibernateException: No current session context configured.
at NHibernate.Context.CurrentSessionContext.GetCurrentSessionContext(ISessionFactory factory)
at NHibernate.Context.CurrentSessionContext.HasBind(ISessionFactory factory)
If I catch the exception here then this error shifts to HandleError
Could you anybody tell me where I could be wrong?
that message means that you haven't configured nhibernate to tell it how to use contextual sessions. the nhibernate contextual sessions feature means that nhibernate will manage keeping track of the current session for you and you only need to worry about binding and unbinding the current session to/from the context and any time you ask the session factory for the current session within that context, you will get the same one. the message module andreas wrote makes use of this feature (and you should too in your handlers if that is how you are managing your sessions - meaning that if you have a dependency in your handler classes on ISessionFactory and get sessions from there, you should use ISessionFactory.GetCurrentSession() instead of ISessionFactory.OpenSesion()).
to fix the problem you are seeing, you need to tell NHibernate how to manage the session context. there are several built in options. the one andreas recommends in his blog post is ThreadStatic. this is fine in your case, as it seems you are only connecting to one database and using one session factory. note that this context class only supports one session factory, so it wouldn't work if you are dealing with more than one. thread static means each thread will have its own session context - you will get the same session as long as you are on the same thread. this works nicely with nservicebus as the handler will execute entirely on a thread and the message module will make sure you are getting a new session with each message and not using the same one from the previous message handled by that thread.
to configure nhibernate for this, you need to set the current_session_context_class property to thread_static. if you are configuring nhibernate directly, you know how you are doing it. if you are using fluent nhibernate, you will need to use the FluentConfiguration.ExposeConfiguration method to do this:
Fluently.Configure()
// whatever else you are doing
.ExposeConfiguration(
c => c.SetProperty("current_session_context_class", "thread_static")
);
here is andreas's post about it:
http://andreasohlund.net/2010/02/03/nhibernate-session-management-in-nservicebus/
I have some integration tests where I want to verify certain requires are made against a third-[arty webserver. I was thinking I would replace the third-party server with a stub server that simply logs calls made to it. The calls do not need to succeed, but I do need a record of the requests made (mainly just the path+querystring).
I was considering just using IIS for this. I could 1) set up an empty site, 2) modify the system's host file to redirect requests to that site 3) parse the log file at the end of each test.
This is problematic as for IIS the log files are not written to immediately, and the files are written to continuosly. I'll need to locate the file, read the contents before the test, wait a nondeterministic amount of time after the test, read the update contents, etc.
Can someone think of a simpler way?
You could use the System.Net.HttpListener ( MSDN LINK ).
It works as embedded WebServer, this means you can even check the access on-the-fly without having to parse log files.
A class i used in my Code recently:
class Listener
{
private HttpListener listener = null;
public event EventHandler CommandReceived;
public Listener()
{
this.listener = new HttpListener();
this.listener.Prefixes.Add("http://localhost:12345/");
}
public void ContextReceived(IAsyncResult result)
{
if (!this.listener.IsListening)
{
return;
}
HttpListenerContext context = this.listener.EndGetContext(result);
this.listener.BeginGetContext(this.ContextReceived, this.listener);
if (context != null)
{
EventHandler handler = this.CommandReceived;
handler(context, new EventArgs());
}
}
public void Start()
{
this.listener.Start();
this.listener.BeginGetContext(this.ContextReceived, this.listener);
}
public void Stop()
{
this.listener.Stop();
}
}
Yeah, I don't think you need a whole webserver. You don't need to test HTTP.
What you do need to test is the underlying data structure that you're sending and receiving. So just create tests for that (i.e. make a point at which you can validate your generate dataformat with what is expected, and also with what you intend to receive, etc).
Test the data, not the protocol (unless, obviously, the protocol is custom).
I've done something very similar to this in a number of projects.
You don't want to create stubbed web service. That's just adding a dependency you don't need. What I did was create an interface which mimics the web service's API. I then created a proxy class that will call the web service in the live system. For testing I used RhinoMocks to create mocked classes that return the results I wanted to test for. This was very useful for me, as I could then produce all sorts of 'unexpected' behaviour which wouldn't be possible with the live system.
public interface IServiceFacade {
string Assignments();
}
public class ServiceFacade : IServiceFacade {
private readonly Service _service;
public ServiceFacade(Service service) {
_service = service;
}
public string Assignments() {
return _service.Assignments();
}
}
Then my test code contained stuff like this:
var serviceFacade = MockRepository.GenerateMock<IServiceFacade>();
serviceFacade.Stub(sf => sf.Assignments()).Return("BLAH BLAH BLAH");
or
serviceFacade.Stub(sf => sf.Assignments()).Return(null);
or
serviceFacade.Stub(sf => sf.Assignments()).Throw(new Exception("Some exception"));
I found this very useful.