Introducing RollingSqlAppender

time to read 6 min | 1193 words

I have explained before what the constraints I have when working with logging. I got several good comments about it, and several has hit the nail directly on the head.

Just to recap, here is the list of my requirements:

  • The output from the logs should be deliverable by email by a layman. This basically means a file.
  • The ouptut should allow me to slice / dice the data. The best way is if I could utilize existing SQL skills to do this.
  • The system may run unattended for weeks at a time, so the log file can't reach huge proportions.
  • I'm not really interested in historical data - something that happened long time ago is not important.
  • Plug into my current logging infrastructure (log4net).
  • Nice to have - Convention over configuration

After giving this much thought, I decided to implement a RollingSqliteAppender. This is based of the existing ADO.Net appender, but is focused on utilizing Sqlite only. Sqlite needs only a single file for its database, so it answers the first requirement, it also has a runtime (for .Net) of about less than 500Kb, so size / installation is not an issue. Despite writing to a file, it is a fully fledged database, meaning that I get to do all sorts of interesting things to it.

The third / forth requirements are what prompted the creation of this appender, rather than using the existing ADO.Net one. I took a look at RollingFileAppender and implemented similar functionality for my appender as well. Although I am checking rows, rather size/date, since this is a bit easier  to check. What is basically does is save all the logs to a sqlite file, until the amount of rows in the database is filled, then it rename the file and create a new one. Another reason for the need to create my own appender is that when creating a new file, the database structure is created for you automatically.From the name, you can figure out that this is a log4net appender, so integrating it is not an issue.

The last part is about minimum configuration. I tend to use the pretty standard logging table fairly often. That means [date, thread, level, logger, message, exception], so the appender is setup to support this (90% scenario, I would guess) out of the box. Here is how you configure the loggerin:

<log4net>

       <appender name="rolling-sqlite-appender"

                       type="Rhino.Commons.Logging.RollingSqlliteAppender, Rhino.Commons"/>

       <root>

              <appender-ref ref="rolling-sqlite-appender"/>

       </root>

</log4net>

This is it.

Now, it does provide a set of options that you can override (because I need that functionality fairly often).

Here is a list of the ones unique for the RollingSqliteAppender:

  • CreateScript - the script to generate the table(s) when creating a new database file. Default is to create the standard table (date, thread, level, logger, message, exception) named "logs".
  • TableName - must match to the name of the table in CreateScript. Default: "logs"
  • FileNameFormat - file format for the file. Default is {0}.log4net, where {0} is the current process name (which result is mostly correct results in most cases).
  • Directory - where to save the files. Default: Current Directory
  • MaxNumberOfRows - number of rows in the database that triggers rolling the file. Note that this is not a hard number, it depends on your BufferSize settings and flush options. It is close enough for my needs. Default: 50,000
  • MaxNumberOfBackUps - number of backup files to save. Backup files are given a numeric prefix where lower is newer. Default: 3
Note: The default buffer size is set to 512, you may want to change that. This is probably the most common reason why people thinks that log4net is not working.

In my tests, each file was about 5Mb - 10Mb in size, and they compressed nicely to a more emailable size.

By the way, I really like the log4net architecture.

The code is here