Loggr

Loggr provides a factory for creating logger instances. It:

  • Provides a wrapper for SLF4J (on JRuby)
  • Has adapters for Stdlib Logger and ActiveSupport::BufferedLogger
  • Supports Rails, including the new tagged method
  • Handles using a Mapped Diagnostic Context (MDC)

So, why should I use a logger factory instead of just Logger.new('out.log') or Rails.logger? Deploying the same application to different environments, with different logging requirements, might make such a step necessary. Or when trying to take advante of SLF4J-only features like the MDC or markers.

Information

Bug Reports No software is without bugs, if you discover a problem please report the issue.

https://github.com/at-point/loggr/issues

Documentation The latest RDocs are available on rubydoc.info.

http://rubydoc.info/github/at-point/loggr/master/frames

Gem The latest stable gem is always on rubygems.org.

http://rubygems.org/gems/loggr

Installation

Like any gem, just add it to the Gemfile:

# stable version
gem 'loggr', '~> 1.1.0'

# latest development version
gem 'loggr', :git => 'git://github.com/at-point/loggr.git'

Getting started

Guides through creating some sample Logger instances and integration with Rails, if interested in using the SLF4J Logger directly (without the factory), skip to The SLF4J Wrapper.

An example, application runs in development on MRI and production in a Jetty container (Java/JRuby), lets assume you want to log there SQL using a logger named jruby.rails.ActiveRecord.

# config/initializers/logging.rb:
LoggerFactory.adapter = Rails.env.production? ? :slf4j : :rails
ActiveRecord::Base.logger = LoggerFactory.logger 'jruby.rails.ActiveRecord'

Once an adapter has been specified new logger instances can be easily created using:

def logger
  @logger ||= LoggerFactory.logger 'my.app.SomeClass', :marker => "WORKER"
end

The adapter then handles creating new logger instances and uses features like the ability to set markers (if supported), or setting a logger name. Based on the adapter a new logger will be returned with all things defined like the marker, or a custom logger name - if the adapter supports it, else it just tries to return a logger which has an API compatible with those found by Stdlibs Logger.

The LoggerFactory

Yap, is responsible for creating new loggers, a logger factory has an adapter, the adapter defines how logger instances are really created.

Creating new loggers

LoggerFactory.logger 'app'                      # create a logger with named app
LoggerFactory.logger 'app', :to => 'debug.out'  # write to debug.out
LoggerFactory.logger 'app', :to => $stderr      # write to stderr

Note: not all adapters support all options, so some adapters might just ignore certain options, but this is intended :)

Bundled Adapters

LoggerFactory.adapter = :base

The base adapter creates ruby stdlib Logger instances. Supported options for LoggerFactory.logger(name, options = {}) are:

  • :to, String or IO, where to log should be written to (default: "#{name}.log")
  • :level, Fixnum, one of Logger::Severity, the minimum severity to log (default: Logger::Severity::INFO)

LoggerFactory.adapter = :buffered

Creates ActiveSupport::BufferedLogger instances and supports the same options as the :base adapter.

LoggerFactory.adapter = :rails

This adapter alwasy returns the Rails.logger, which is very useful e.g. in development environments or testing, where we just care that it's somewhere in our logs/development.log. Note: Rails is automatically detected and the rails adapter is the default adapter when ::Rails is present - else the base adapter is the default.

LoggerFactory.adapter = :slf4j

SLF4J only works with JRuby (because it's Java) and you are responsible for a) having an SLF4J implementation on the classpath and it's configuration. Furthermore slf4j supports these options for LoggerFactory.logger(name, options = {}):

  • :marker, String, additional marker logged with each statement (default: nil)

Using the Mapped Diagnostic Context (MDC)

Some loggers provide a MDC (or mapped diagnostic context) or a similar feature like ActiveSupport 3.2s TaggedLogging#tagged which can be used to annotate log outputs with additional bits of information. At the moment only SLF4J and ActiveSupport 3.2 can make use of this. Though, to provide a clean and consistent API all adapters must provide access to an MDC and each logger must respond to both tagged and mapped, so these features can be used in code no matter the adapter.

A sample use case:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  around_filter :push_ip_to_mdc
  def logger; @logger ||= LoggerFactory.logger('sample') end

  private
    def push_ip_to_mdc
      logger.mapped(:ip => request.ip) do
        yield
      end
    end
end

When using SLF4J all statements would now be annotated with the IP from where the request was made from, when using Rails 3.2 it would use it's support for tagged and add a tag named ip=.....

The SLF4J Wrapper

Apart from the logger factory, this gem provides a ruby wrapper for logging using SLF4J and taking advantage of:

  • Same API as exposed by Stdlib Logger, AS::BufferedLogger and AS::TaggedLogging
  • SLF4J markers
  • The Mapped Diagnostic Context (MDC)
  • Access to SLF4J & Logback implementation JARs

The Logger

Creating a new logger is as simple as creating instances of Loggr::SLF4J::Logger:

# logger named "my.package.App"
@logger = Loggr::SLF4J::Logger.new 'my.package.App'

# logger named "some.sample.Application" => classes are converted to java notation
@logger = Loggr::SLF4J::Logger.new Some::Sample::Application

# logger with a default marker named "APP"
@logger = Loggr::SLF4J::Logger.new 'my.package.App', :marker => 'APP'

Logging events is like using Stdlib Logger:

# log with level INFO
@logger.info "some info message"

# log with level DEBUG, if enabled
@logger.debug "verbose information" if @logger.debug?

# log with level DEBUG and marker "QUEUE" (masking as progname)
@logger.debug "do something", "QUEUE"

The MDC

A wrapper hash for SLF4Js Mapped Diagnostic Context is available using Loggr::SLF4J::MDC like a hash:

begin
  Loggr::SLF4J::MDC[:user] = username
  do_some_stuff
ensure
  Loggr::SLF4J::MDC.delete(:user)
end

It's a good practice to wrap MDC set/get into begin/ensure blocks to ensure the value is cleared afterwards, even in case of errors. The user is responsible for getting rid of these values. To just clear all values use Loggr::SLF4J::MDC.clear

Tagging and mapping

As an alternative to using the MDC directly, each logger exposes a method which is more ruby-like than setting the MDC and having to handle the ensure all by itself.

logger.mapped(:user => username) do
  do_some_stuff
end

This ensures that the key is cleared at the end, these calls can also be easily nested. Starting with ActiveSupport 3.2 there's support for TaggedLogging, SLF4J mimics this behaviour by using the mapped diagnostic context:

logger.tagged("some", "values") do
  do_some_stuff
end

Within the block the MDC has been assigned some, values to the key :tags. Nested values are just appended to this key.

Extending & Contributing

Of course any custom adapters (e.g. for log4r or the logging gem) are greatly appreciated. To write a custom adapter just do something like:

class MyModule::MyCustomAdapter < Loggr::Adpater::AbstractAdapter
  def logger(name, options = {})
    # build logger instances and return it
  end
end

# use custom adapter
LoggerFactory.adapter = MyModule::MyCustomAdapter.new

Extending from Loggr::Adapter::AbstractAdapter provides the adapter with a default MDC implementation (backed by a hash stored in a thread local).

Similar to ActiveModel there are also Lint tests available to verify if your adapter and the logger and mdc returned adhere to the API.

class MyModule::MyCustomAdapterTest < Test::Unit::TestCase
   include Loggr::Lint::Tests

   def setup
     # required, so the Lint Tests can pick it up
     @adapter = MyModule::MyCustomAdapter.new
   end
end

Contribute

  1. Fork this project and hack away
  2. Ensure that the changes are well tested
  3. Send pull request

Loggr is licensed under the MIT License, (c) 2011 by at-point ag.