nHibernate logging with Log4Net, thread session issue

夙愿已清 提交于 2019-12-22 10:38:26

问题


Hey there folks, having a little issue here which I'm trying to wrap my head around.

I'm currently starting out with nHibernate, such I have to due to work requirements, and am getting a little stuck with nHibernate's Sessions and multiple threads. Well the task I want to complete here is to have Log4Net log everything to the database, including nHibernate's debug/errors etc.

So what I did was create a very simple Log4Net:AppenderSkeleton class which fires perfectly when I need it. My intial issue I ran into was that when I used GetCurrentSession, obviously since Log4Net runs on a seperate thread(s), it errored out with the initial thread's session. So I figured that I had to create a new nHiberante Session for the Log4Net AppenderSkeleton class. The code is below:

public class Custom : AppenderSkeleton
{
    protected override void Append(LoggingEvent loggingEvent)
    {
        if (loggingEvent != null)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                using (ITransaction tran = session.BeginTransaction())
                {
                    Log data = new Log
                    {
                        Date = loggingEvent.TimeStamp,
                        Level = loggingEvent.Level.ToString(),
                        Logger = loggingEvent.LoggerName,
                        Thread = loggingEvent.ThreadName,
                        Message = loggingEvent.MessageObject.ToString()
                    };

                    if (loggingEvent.ExceptionObject != null)
                    {
                        data.Exception = loggingEvent.ExceptionObject.ToString();
                    }

                    session.Save(data);
                    tran.Commit();
                }
            }
        }
    }

Simple enough idea really, while it is at its basic form now, I will have more error checking info etc but for now the issue is that while this works perfectly it creates multiple sessions. That is, it creates a new session per error logged since I can't use GetCurrentSession as this will get the calling Session (the main program flow). I'm sure there is a way for me to create a session globally for Log4Net's thread, but I'm unsure gow to. Keeping in mind that I already bind a Session to the intial thread using the below in Global.asax (Application_BeginRequest):

ISession session = NHibernateHelper.OpenSession();

CurrentSessionContext.Bind(session);

And for those that will ask, the contents of my helper is below (this is in a DLL):

public static class NHibernateHelper 
{
    private static Configuration _nHibernateConfig;
    private static ISessionFactory _nHibernateSessionFactory;

    private static ISessionFactory BuildNHibernateSessionFactory 
    { 
        get 
        {
            if (_nHibernateSessionFactory == null) 
            {
                if (_nHibernateConfig == null)
                {
                    BuildSessionFactory();
                }

                _nHibernateSessionFactory = _nHibernateConfig.BuildSessionFactory();
            } 

            return _nHibernateSessionFactory; 
        } 
    }

    private static Configuration BuildNHibernateConfig
    {
        get
        {
            if (_nHibernateConfig == null)
            {
                _nHibernateConfig = new ConfigurationBuilder().Build();
            }

            return _nHibernateConfig;
        }
    }

    public static Configuration nHibernateConfig
    {
        get
        {
            return _nHibernateConfig;
        }
    }

    public static ISessionFactory nHibernateSessionFactory
    {
        get
        {
            return _nHibernateSessionFactory;
        }
    }

    public static Configuration BuildConfiguration()
    {
        return BuildNHibernateConfig;
    }

    public static ISessionFactory BuildSessionFactory()
    {
        return BuildNHibernateSessionFactory; 
    }

    public static ISession OpenSession() 
    {
        return _nHibernateSessionFactory.OpenSession(); 
    }

    public static ISession GetCurrentSession() 
    {
        try
        {
            return _nHibernateSessionFactory.GetCurrentSession(); 
        }
        catch (HibernateException ex)
        {
            if(ex.Message == "No session bound to the current context")
            {
                //  See if we can bind a session before complete failure
                return _nHibernateSessionFactory.OpenSession();
            }
            else
            {
                throw;
            }
        }
        catch (Exception ex)
        {
            throw;
        }
    } 
}

I realise I could use the ADO appender in log4net but I wish to use nHibernate directly to add the data to the database. The reason being is I don't wish to mess around with connectionstrings etc when nHibernate is already working.

As always, any help is always appreciated.

-- Edit: --

So based on what I have been told initialy, I modified my custom Log4Net logger code. There are two versions below. My question, which is best or is there a better way?

  • The first, according to nHibernate Prof, creates only two sessions - The intial session is for the main program flow as intended and the second for my Log4Net logger code. Yet this has hundreds of enteries in the second session and complains about too many enteries and to many calls to the database.

  • The second, nHibernate prof shows many sessions, as many sessions as there are calls to the logger +1 for the main program flow. Yet no complaints anywhere on nHprof. Though I have this feeling that having that many sessions would have people frowning or is too much tasking.

Anyway the codes:

Code 1 -

protected override void Append(LoggingEvent loggingEvent)
    {
        if (!System.Web.HttpContext.Current.Items.Contains("Log4Net nHibernate Session"))
        {
            System.Web.HttpContext.Current.Items.Add("Log4Net nHibernate Session", NHibernateHelper.OpenStatelessSession());
        }

        IStatelessSession statelessSession = System.Web.HttpContext.Current.Items["Log4Net nHibernate Session"] as IStatelessSession;

        if (statelessSession != null && loggingEvent != null)
        {
            using (ITransaction tran = statelessSession.BeginTransaction())
            {
                Log data = new Log
                    {
                        Id = Guid.NewGuid(),
                        Date = loggingEvent.TimeStamp,
                        Level = loggingEvent.Level.ToString(),
                        Logger = loggingEvent.LoggerName,
                        Thread = loggingEvent.ThreadName,
                        Message = loggingEvent.MessageObject.ToString()
                    };

                if (loggingEvent.ExceptionObject != null)
                {
                    data.Exception = loggingEvent.ExceptionObject.ToString();
                }

                statelessSession.Insert(data);
                tran.Commit();
            }
        }
    }

Code 2 -

protected override void Append(LoggingEvent loggingEvent)
    {
        if (loggingEvent != null)
        {
            using (IStatelessSession statelessSession = NHibernateHelper.OpenStatelessSession())
            using (ITransaction tran = statelessSession.BeginTransaction())
            {
                Log data = new Log
                {
                    Id = Guid.NewGuid(),
                    Date = loggingEvent.TimeStamp,
                    Level = loggingEvent.Level.ToString(),
                    Logger = loggingEvent.LoggerName,
                    Thread = loggingEvent.ThreadName,
                    Message = loggingEvent.MessageObject.ToString()
                };

                if (loggingEvent.ExceptionObject != null)
                {
                    data.Exception = loggingEvent.ExceptionObject.ToString();
                }

                statelessSession.Insert(data);
                tran.Commit();
            }
        }
    }

回答1:


You are right about creating a new session. You definitely don't want to share the same session across threads. In your logging instance I would even say to use an IStatelessSession. Also sessions should be fairly lightweight so I wouldn't worry about creating new sessions each time you log a statement.




回答2:


NHibernate already uses Log4Net internally so you just need to enable the logger and use an AdoNetAppender to send the logs to your database.

<log4net>
    <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
        ...
    </appender>
    <logger name="NHibernate">
        <level value="WARN"/>
        <appender-ref ref="AdoNetAppender"/>
    </logger>
</log4net>


来源:https://stackoverflow.com/questions/5756544/nhibernate-logging-with-log4net-thread-session-issue

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!