#-- # Copyright (c) 2008-2012 David Kellum # # rjack-logback is free software: you can redistribute it and/or # modify it under the terms of either of following licenses: # # GNU Lesser General Public License v3 or later # Eclipse Public License v1.0 # # rjack-logback is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. #++ require 'rjack-slf4j' require 'rjack-logback/base' require 'java' module RJack # Jruby wrapper module for the Logback[http://logback.qos.ch/] log writer. # Programmatic configuration and setting of logger output levels is supported. # # == Examples # # === High level configuration # # require 'rjack-logback' # RJack::Logback.config_console( :thread => true, :level => :info ) # # === Low level configuration # # require 'rjack-logback' # # log = RJack::SLF4J[ 'example' ] # log.info "About to reconfigure..." # # RJack::Logback.configure do # console = RJack::Logback::ConsoleAppender.new do |a| # a.target = "System.err" # a.layout = RJack::Logback::PatternLayout.new do |p| # p.pattern = "%r %-5level %logger{35} - %msg %ex%n" # end # end # RJack::Logback.root.add_appender( console ) # RJack::Logback.root.level = RJack::Logback::INFO # end # # # Adjust output levels (also works outside of configure ) # RJack::Logback[ 'example' ].level = RJack::Logback::DEBUG # # log.debug "...after reconfigure." # # Configure with Logback XML configuration: # # RJack::Logback.configure do # RJack::Logback.load_xml_config( 'sample-logback.xml' ) # end # # == Programmatic Configuration Support # # Logback java classes implement interfaces +LifeCycle+ and # +ContextAware+ for configurability with Joran (XML). To simplify # configuration in ruby, the following classes have been extended: # # * ConsoleAppender # * FileAppender # * PatternLayout # # The extensions provide a block initializer which sets sensible # defaults, yields to a block for customization, and then calls # +start+. Logback provides many other components not yet extended in # this way. These can be created directly and or extended in a # similar fashion externally. Consider providing a patch to the # rjack[http://rubyforge.org/projects/rjack] project with desired # extensions. # module Logback # Load logback jar. def self.require_jar( name ) require File.join( LOGBACK_DIR, "#{name}-#{ LOGBACK_VERSION }.jar" ) end require_jar 'logback-core' require_jar 'logback-classic' # ch.qos.logback.classic.Level Level = Java::ch.qos.logback.classic.Level # Level::TRACE TRACE = Level::TRACE # Level::DEBUG DEBUG = Level::DEBUG # Level::INFO INFO = Level::INFO # Level::WARN WARN = Level::WARN # Level::ERROR ERROR = Level::ERROR # Level::OFF OFF = Level::OFF DEFAULT_PATTERN = "%date [%thread] %-5level %logger{35} - %msg %ex%n" #:nodoc: @@context = SLF4J.linked_factory # Returns the LoggerContext def self.context @@context end # Utility mixin of Logback ch.qos.logback.core.spi.LifeCycle instances module Util # Start, raise if not started def self.start( lifecycle_obj ) lifecycle_obj.start raise "#{lifecycle_obj.class.name} did not start" if ! lifecycle_obj.started? end end # Wrapper for # ch.qos.logback.classic.Logger[http://logback.qos.ch/apidocs/ch/qos/logback/classic/Logger.html] class Logger # Initialize given ch.qos.logback.classic.Logger def initialize( jlogger ) @jlogger = jlogger end # Set output level # ==== Parameters # :level:: New output Level. def level=( level ) @jlogger.level = Logback.to_level( level ) end # Adjust output level temporarily for block. This is not # internally synchronized. # ==== Parameters # :level:: output Level. def with_level( level ) orig = @jlogger.level self.level = level yield ensure self.level = orig end # Add appender to this logger # ==== Parameters # :appender:: Appender def add_appender( appender ) @jlogger.add_appender( appender ) end # Set additive flag ( false means events stop at attached appender ) def additive=( is_additive ) @jlogger.additive = is_additive end end # ch.qos.logback.classic.joran.JoranConfigurator JoranConfigurator = Java::ch.qos.logback.classic.joran.JoranConfigurator # Load the specified Logback (Joran) XML configuration file. Should be # called within a configure {...} block. def self.load_xml_config( file ) cfger = JoranConfigurator.new cfger.context = @@context cfger.doConfigure( file ) end # ch.qos.logback.classic.PatternLayout JPatternLayout = Java::ch.qos.logback.classic.PatternLayout # Extends # ch.qos.logback.classic.PatternLayout[http://logback.qos.ch/apidocs/ch/qos/logback/classic/PatternLayout.html] # with a block initializer. class PatternLayout < JPatternLayout # Sets context and pattern, yields self to block, and calls self.start def initialize( pattern=DEFAULT_PATTERN ) super() self.context = Logback.context self.pattern = pattern yield( self ) if block_given? Util.start( self ) end end # Utility implementation mixin for Appenders. module AppenderUtil @@default_layout = Logback::PatternLayout.new Charset = Java::java.nio.charset.Charset # Set appender defaults. def set_defaults self.context = Logback.context self.name = self.class.name self.layout = @@default_layout end # Yield to block, then start. def finish( &block ) block.call( self ) unless block.nil? Util.start( self ) end def encoding=( enc ) self.encoder.charset = Charset::forName( enc ) end end # ch.qos.logback.core.ConsoleAppender JConsoleAppender = Java::ch.qos.logback.core.ConsoleAppender # Extends # ch.qos.logback.core.ConsoleAppender[http://logback.qos.ch/apidocs/ch/qos/logback/core/ConsoleAppender.html] # with a block initializer. class ConsoleAppender < JConsoleAppender include AppenderUtil # Sets context, default name and layout, yields self to block, and # calls self.start def initialize( &block ) super() set_defaults finish( &block ) end end # ch.qos.logback.core.FileAppender JFileAppender = Java::ch.qos.logback.core.FileAppender # Extends # ch.qos.logback.core.FileAppender[http://logback.qos.ch/apidocs/ch/qos/logback/core/FileAppender.html] # with a block initializer. # class FileAppender < JFileAppender include AppenderUtil # Sets defaults, yields self to block, and calls self.start def initialize( file_name, append = true, &block ) super() set_defaults self.file = file_name self.append = append self.encoding = "UTF-8" finish( &block ) end end JLevelChangePropagator = Java::ch.qos.logback.classic.jul.LevelChangePropagator # Extends # ch.qos.logback.classic.jul.LevelChangePropagator[http://logback.qos.ch/apidocs/ch/qos/logback/classic/jul/LevelChangePropagator.html] # with a block initializer. class LevelChangePropagator < JLevelChangePropagator def initialize( reset_jul = true ) super() self.context = Logback.context self.reset_jul = reset_jul yield( self ) if block_given? Util.start( self ) end end # Configure Logback with the specified block. The Logback context is # +reset+, yielded to block, and then started after return # from the block. def self.configure @@context.reset yield context Util.start( context ) nil end # Configure a single ConsoleAppender using options hash. # ==== Options # :stderr:: Output to standard error? (default: false) # :full:: Output full date? (default: false, milliseconds) # :thread:: Output thread name? (default: false) # :level:: Set root level (default: INFO) # :lwidth<~to_s>:: Logger width (default: :full ? 35 : 30) # :mdc:: One or more Mapped Diagnostic Context keys # :mdc_width<~to_s}:: MDC width (default: unspecified) # :propagate_to_jul:: Propagate level changes to # java.util.logging, additionally resetting # JUL levels if value is :reset (default: false) def self.config_console( options = {} ) configure do |ctx| if options[ :propagate_to_jul ] r = ( options[ :propagate_to_jul ] == :reset ) ctx.add_listener( LevelChangePropagator.new( r ) ) end console = Logback::ConsoleAppender.new do |a| a.target = "System.err" if options[ :stderr ] a.layout = Logback::PatternLayout.new do |layout| pat = [ options[ :full ] ? '%date' : '%-4r' ] pat << '[%thread]' if options[ :thread ] pat << '%-5level' w = ( options[ :lwidth ] || ( options[ :full ] ? 35 : 30 ) ) pat << "%logger{#{w}}" mdcs = Array( options[ :mdc ] ).map { |k| "%X{#{k}}" } unless mdcs.empty? mp = ( '\(' + mdcs.join(',') + '\)' ) mw = options[ :mdc_width ] mp = "%-#{mw}(#{mp})" if mw pat << mp end pat += [ '-', '%msg' '%ex%n' ] layout.pattern = pat.join( ' ' ) end end Logback.root.add_appender( console ) Logback.root.level = options[ :level ] || INFO end end # Returns the named Logger def self.logger( name ) Logger.new( @@context.getLogger( name ) ) end # Synonym for logger(name) def self.[](name) logger( name ) end # Returns the special "root" Logger def self.root logger( "root" ) end # Converts Symbol to Level constant, or return Level unaltered. def self.to_level( l ) if l.is_a?( Symbol ) const_get( l.to_s.upcase.to_sym ) else l end end end end