2

In order to eliminate human error and avoid the tedium of maintaining who-knows-how-many App.config files, I've decided to set all of my Quartz.NET jobs' Log4Net configurations at runtime, completely in code. No App.config files allowed. I've been successful in getting a standalone console app to do this, as demonstrated here.

However, it's a different story in the scheduler itself. For some reason it's not using the configuration that I'm specifying:

Dim oProperties As New NameValueCollection

' ... Set Quartz.NET properties here

BasicConfigurator.Configure(New Logger.DbAppender)

With New StdSchedulerFactory(oProperties)
  Manager.Scheduler = .GetScheduler
End With

I'm running it as a TopShelf service and I'd like the Scheduler's logs to go to the same destination as each of the IJobs. Here, Quartz.NET stubbornly continues to send its output to TopShelf's debugging console window (STDOUT).

This construct works in the console app (linked above); why isn't it working here?

Community
  • 1
  • 1
InteXX
  • 6,135
  • 6
  • 43
  • 80

2 Answers2

1

OK, got it. (That didn't take long!)

For some reason, the TopShelf/Quartz.NET combo insists on suppressing logging output if the requisite App.config entries are missing.

In this case, since it's the central scheduler we're dealing with and not a plethora of jobs, I'll bite the bullet and go ahead and use an App.config file for this one.

So the root problem remains unsolved; this is just a workaround for the time being. If you know the answer I'd still be pleased to hear from you. Thanks.

InteXX
  • 6,135
  • 6
  • 43
  • 80
1

OK, got it. (For REAL this time.)

Clarification: the suppression of logging in the absence of a .config file occurs in Common.Logging when run under the TopShelf/Quartz.NET combination. For some reason it consistently refuses to recognize a runtime-loaded Appender, even when the first line of code in the service configures logging. Still a mystery, but not important. (At least not for me, with this latest discovery—YMMV.)

The only logging that's suppressed is that which follows the buildup and teardown of the Common.Logging library itself; everything that occurs in the service itself is still logged.

I was able to get it to work using the code below (in combination with the code linked to above, of course).

As I said, YMMV.

[Service]

Public Module Main
  Sub Main()
    Try
      BasicConfigurator.Configure(New Logger.DbAppender("ConnectionString"))
      Logger = LogManager.GetLogger(GetType(Main))

      Environment.ExitCode = HostFactory.Run(Sub(Configurator)
                                               Configurator.Service(Of Manager)(Sub(Service)
                                                                                  Service.ConstructUsing(Function(Factory) As ServiceControl
                                                                                                           Return New Manager
                                                                                                         End Function)

                                                                                  Service.WhenStarted(Function(Manager, HostControl) As Boolean
                                                                                                        Return Manager.StartService(HostControl)
                                                                                                      End Function)

                                                                                  Service.WhenStopped(Function(Manager, HostControl) As Boolean
                                                                                                        Return Manager.StopService(HostControl)
                                                                                                      End Function)
                                                                                End Sub)

                                               Configurator.SetDescription(ServiceInfo.Description)
                                               Configurator.SetServiceName(ServiceInfo.Product)
                                               Configurator.SetDisplayName(ServiceInfo.Title)
                                               Configurator.StartAutomatically()
                                               Configurator.RunAsLocalSystem()
                                             End Sub)

    Catch ex As Exception
      EventLog.WriteEntry(ServiceInfo.Product, ex.Message, EventLogEntryType.Error, 1, 1, ex.ToBytes)

    End Try
  End Sub



  Public Property Logger As ILog
End Module

[DbAppender] (updated w/add'l constructor that accepts a connection string)

Public Class DbAppender
  Inherits AdoNetAppender

  Public Sub New()
    Me.New(String.Empty)
  End Sub



  Public Sub New(ConnectionString As String)
    If Trim(ConnectionString) <> String.Empty Then
      MyBase.ConnectionString = ConnectionString
    End If

    MyBase.CommandText = Me.CommandText
    MyBase.BufferSize = 1

    Me.Parameters.ForEach(Sub(Parameter As DbParameter)
                            MyBase.AddParameter(Parameter)
                          End Sub)

    Me.ActivateOptions()
  End Sub



  Protected Overrides Function CreateConnection(ConnectionType As Type, ConnectionString As String) As IDbConnection
    Return MyBase.CreateConnection(GetType(System.Data.SqlClient.SqlConnection), MyBase.ConnectionString)
  End Function



  Private Overloads ReadOnly Property CommandText As String
    Get
      Dim _
        sColumns,
        sValues As String

      sColumns = Join(Me.Parameters.Select(Function(P As DbParameter) P.DbColumn).ToArray, ",")
      sValues = Join(Me.Parameters.Select(Function(P As DbParameter) P.ParameterName).ToArray, ",")

      Return COMMAND_TEXT.ToFormat(sColumns, sValues)
    End Get
  End Property



  Private ReadOnly Property Parameters As List(Of DbParameter)
    Get
      Parameters = New List(Of DbParameter)
      Parameters.Add(Me.Date)
      Parameters.Add(Me.Thread)
      Parameters.Add(Me.Level)
      Parameters.Add(Me.Source)
      Parameters.Add(Me.Message)
      Parameters.Add(Me.Exception)
    End Get
  End Property



  Private ReadOnly Property [Date] As DbParameter
    Get
      Return New DbParameter("Date", New DbPatternLayout(PATTERN_DATE), DbType.Date, 0)
    End Get
  End Property



  Private ReadOnly Property Thread As DbParameter
    Get
      Return New DbParameter("Thread", New DbPatternLayout(PATTERN_THREAD), DbType.String, 255)
    End Get
  End Property



  Private ReadOnly Property Level As DbParameter
    Get
      Return New DbParameter("Level", New DbPatternLayout(PATTERN_LEVEL), DbType.String, 50)
    End Get
  End Property



  Private ReadOnly Property Source As DbParameter
    Get
      Return New DbParameter("Source", New DbPatternLayout(PATTERN_SOURCE), DbType.String, 255)
    End Get
  End Property



  Private ReadOnly Property Message As DbParameter
    Get
      Return New DbParameter("Message", New DbPatternLayout(PATTERN_MESSAGE), DbType.String, 4000)
    End Get
  End Property



  Private ReadOnly Property Exception As DbParameter
    Get
      Return New DbParameter("Exception", New DbExceptionLayout)
    End Get
  End Property



  Private Const PATTERN_MESSAGE As String = "%message"
  Private Const PATTERN_THREAD As String = "%thread"
  Private Const PATTERN_SOURCE As String = "%logger.%M()"
  Private Const PATTERN_LEVEL As String = "%level"
  Private Const PATTERN_DATE As String = "%date{yyyy-MM-dd HH:mm:ss.fff}"
  Private Const COMMAND_TEXT As String = "INSERT INTO Log ({0}) VALUES ({1})"
  '======================================================================================
  ' Available patterns:
  ' http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html
  '======================================================================================
End Class
InteXX
  • 6,135
  • 6
  • 43
  • 80