loggr 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ module Loggr
2
+ module Adapter
3
+
4
+ # A basically abstract base class for logger backend
5
+ # implementations, provides a default implementation for MDC (hash & thread local based).
6
+ #
7
+ # Ensure to implement `#logger`.
8
+ #
9
+ class AbstractAdapter
10
+
11
+ # Implement which creates a new instance of a logger.
12
+ def logger(name, options = {})
13
+ raise "#{self.class.name}#logger is declared `abstract': implement #logger method"
14
+ end
15
+
16
+ # Use a simple thread local hash as fake MDC, because it's
17
+ # not supported by the logger anyway - but it should be available
18
+ # for consistency and usage.
19
+ def mdc
20
+ mdc_key = "#{self.class.name}.mdc"
21
+ Thread.current[mdc_key] ||= Hash.new
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,54 @@
1
+ require 'loggr/adapter/abstract'
2
+ require 'logger'
3
+
4
+ module Loggr
5
+ module Adapter
6
+
7
+ # Default backend which is backed Rubys Stdlib Logger.
8
+ #
9
+ class BaseAdapter < AbstractAdapter
10
+
11
+ #
12
+ #
13
+ def logger(name, options = {})
14
+ name = normalize_name(name)
15
+ @loggers ||= {}
16
+ @loggers[name] ||= build_new_logger(name, options)
17
+ end
18
+
19
+ protected
20
+ # Constructs a new logger instance for the supplied options, is called
21
+ # by `#loggers` when no logger with this name already exists - instead of
22
+ # creating a new logger...
23
+ #
24
+ def build_new_logger(name, options = {})
25
+ ::Logger.new(options[:to] || "#{name.to_s.gsub(/[:\s\/]+/, '_')}.log").tap do |logger|
26
+ logger.level = options[:level] || Logger::INFO
27
+ logger.progname = name
28
+ end
29
+ end
30
+
31
+ # Because we should also allow using class names, or objects
32
+ # to construct new loggers (like in SLF4J), it makes sense to
33
+ # have a method which normalizes some input, be it a string or
34
+ # whatnot to convert to an appropriate name.
35
+ #
36
+ # Subclasses are of course allowed to override this, of course
37
+ # loggers themself are allowed to tweak that name further - this
38
+ # is basically to enable creating loggers from classes/objects
39
+ # for AS::BufferedLoggers and Stdlib Loggers.
40
+ #
41
+ def normalize_name(name)
42
+ return name.to_str if name.respond_to?(:to_str)
43
+ case name
44
+ when Symbol then name.to_s
45
+ when Module then name.name.to_s
46
+ else name.class.name.to_s
47
+ end
48
+ end
49
+ end
50
+
51
+ # Okay, basically a singleton thus create instance
52
+ Base = BaseAdapter.new
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ require 'loggr/adapter/base'
2
+ require 'active_support/buffered_logger'
3
+
4
+ module Loggr
5
+ module Adapter
6
+
7
+ # Backend for `ActiveSupport::BufferedLogger`.
8
+ #
9
+ class BufferedAdapter < BaseAdapter
10
+
11
+ protected
12
+ # Creates a new `AS::BufferedLogger` instance, note that BufferedLogger has
13
+ # no support for setting a default progname, so `name` is basically ignored.
14
+ #
15
+ def build_new_logger(name, options = {})
16
+ ActiveSupport::BufferedLogger.new(options[:to] || "#{name.to_s.gsub(/[\s\/]+/, '_')}.log").tap do |logger|
17
+ logger.level = options[:level] || ActiveSupport::BufferedLogger::INFO
18
+ end
19
+ end
20
+ end
21
+
22
+ # THE instance
23
+ Buffered = BufferedAdapter.new
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ require 'loggr/adapter/abstract'
2
+
3
+ module Loggr
4
+ module Adapter
5
+
6
+ # Silences all logging operations, nothing is written at all.
7
+ #
8
+ class NOPAdapter < AbstractAdapter
9
+
10
+ class NOPLogger
11
+ # Has no impact anyway :)
12
+ attr_accessor :level
13
+
14
+ # Just to ensure compatiability with AS::BufferedLogger
15
+ attr_reader :auto_flushing, :flush, :close
16
+
17
+ # Yields empty implementations for all severities
18
+ %w{trace debug info warn error fatal}.each do |severity|
19
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
20
+ def #{severity}(*args, &block) # def debug(*args, &block)
21
+ end # end
22
+
23
+ def #{severity}? # def debug?
24
+ false # false
25
+ end # end
26
+ EOT
27
+ end
28
+ end
29
+
30
+ # Get single NOPLogger instance
31
+ def logger(name, options = {})
32
+ @logger ||= NOPLogger.new
33
+ end
34
+ end
35
+
36
+ # THE instance
37
+ NOP = NOPAdapter.new
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ require 'loggr/adapter/abstract'
2
+
3
+ module Loggr
4
+ module Adapter
5
+
6
+ # Always uses the logger defined on `Rails.logger`, CAVEAT ensure to never
7
+ # ever set `Rails.logger` to this backend, i.e.:
8
+ #
9
+ # # IF YOU DID THIS...
10
+ # Loggr.adapter = Loggr::Adapter::Rails
11
+ #
12
+ # MyApp::Application.configure do
13
+ # # ...NEVER DO THIS !!!
14
+ # config.logger = Loggr.logger('rails')
15
+ # end
16
+ #
17
+ # If you are using the rails adapter, ensure that you do not override `config.logger`
18
+ # with an instance of the Rails adapter logger factory. Keep the default logger, or
19
+ # create a new one using:
20
+ #
21
+ # config.logger = Loggr.logger('rails', :backend => Loggr::Adapter::Buffered)
22
+ #
23
+ class RailsAdapter < AbstractAdapter
24
+
25
+ # The rails backend ignores all options as it just returns
26
+ # always returns `Rails.logger` :)
27
+ #
28
+ def logger(name, options = {})
29
+ ::Rails.logger
30
+ end
31
+ end
32
+
33
+ # THE Rails backed implementation instance
34
+ Rails = RailsAdapter.new
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ require 'loggr/slf4j'
2
+ require 'loggr/adapter/base'
3
+
4
+ module Loggr
5
+ module Adapter
6
+
7
+ # Provides an adapter for the SLF4J Logger.
8
+ #
9
+ class SLF4JAdapter < BaseAdapter
10
+ # Use the SLF4J backed real MDC.
11
+ def mdc
12
+ @mdc ||= Loggr::SLF4J::MDC
13
+ end
14
+
15
+ protected
16
+ # Create a new SLF4JLogger instance.
17
+ def build_new_logger(name, options = {})
18
+ Loggr::SLF4J::Logger.new(name, options)
19
+ end
20
+
21
+ # Uses Logger#in_java_notation on name
22
+ def normalize_name(name)
23
+ Loggr::SLF4J::Logger.in_java_notation(name)
24
+ end
25
+ end
26
+
27
+ # THE instance of it
28
+ SLF4J = SLF4JAdapter.new
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ require 'loggr/adapter'
2
+
3
+ # This is basically a factory facade to create new Logger instances
4
+ # which behave all like standard Ruby Stdlib Loggers.
5
+ #
6
+ class LoggerFactory
7
+ # Ensure we get all those factory methods directly here
8
+ extend Loggr::Adapter
9
+ end
data/lib/loggr/lint.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'tempfile'
2
+
3
+ module Loggr
4
+ module Lint
5
+
6
+ # == Adapter and Logger Lint Tests
7
+ #
8
+ # You can test whether an object provides a compliant adapter and logger
9
+ # by including <tt>Logger::Lint::Tests</tt>
10
+ # in your tests.
11
+ #
12
+ # Ensure you set the instance variable <tt>@adapter</tt> to your adapter.
13
+ #
14
+ module Tests
15
+ def test_adapter_logger
16
+ assert adapter.respond_to?(:logger), "The adapter should respond to #logger"
17
+ assert adapter.method('logger').arity == -2, "The adapter should accept two parameters for #logger, name and options hash"
18
+ end
19
+
20
+ def test_adapter_mdc
21
+ assert adapter.respond_to?(:mdc), "The adapter should respond to #mdc"
22
+ assert adapter.method('mdc').arity == 0, "The adapter should accept no parameters for #mdc"
23
+ end
24
+
25
+ def test_mdc_methods
26
+ mdc = adapter.mdc
27
+ assert mdc.respond_to?(:[]=), "The mdc should respond to #[]="
28
+ assert mdc.respond_to?(:[]), "The mdc should respond to #[]"
29
+ assert mdc.respond_to?(:delete), "The mdc should respond to #delete"
30
+ assert mdc.respond_to?(:clear), "The mdc should respond to #clear"
31
+ assert mdc.respond_to?(:to_hash), "The mdc should respond to #to_hash"
32
+ end
33
+
34
+ def test_logger_methods
35
+ tempfile = Tempfile.new('lint')
36
+ logger = adapter.logger('lint', :to => tempfile.path)
37
+ %w{debug info warn error fatal}.each do |level|
38
+ assert logger.respond_to?(level), "The logger should respond to ##{level}"
39
+ assert logger.respond_to?("#{level}?"), "The logger should respond to ##{level}?"
40
+ end
41
+ ensure
42
+ tempfile.unlink
43
+ end
44
+
45
+ protected
46
+
47
+ # Access the adapter, defined by <tt>@adapter</tt>.
48
+ def adapter
49
+ assert !!@adapter, "An adapter must be defined"
50
+ @adapter
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ module Loggr
2
+
3
+ # Exactly the same severities as Ruby Stdlib Logger.
4
+ #
5
+ module Severity
6
+ # Okay, trace is new :)
7
+ TRACE = -1
8
+
9
+ DEBUG = 0
10
+ INFO = 1
11
+ WARN = 2
12
+ ERROR = 3
13
+ FATAL = 4
14
+ UNKNOWN = 5
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module Loggr
2
+ module SLF4J
3
+
4
+ autoload :Logger, 'loggr/slf4j/logger'
5
+ autoload :MDC, 'loggr/slf4j/mdc'
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ module Loggr
2
+ module SLF4J
3
+
4
+ # Simple access to both SLF4J and Logback implementations, for testing
5
+ # and/or warbler integration.
6
+ #
7
+ module Jars
8
+
9
+ # Base dir, where the jar files reside, this is "lib/" ergo "lib/loggr/slf4j/jars.rb/../../../"
10
+ SLF4J_LIB_PATH = File.expand_path(File.dirname(File.dirname(File.dirname(__FILE__))))
11
+
12
+
13
+ # Path to SLF4J API
14
+ def slf4j_api_jar_path
15
+ @api_jar_path ||= Dir[File.join(SLF4J_LIB_PATH, 'slf4j-api-*.jar')].first
16
+ end
17
+ module_function :slf4j_api_jar_path
18
+
19
+ # Logback Core JAR
20
+ def logback_core_jar_path
21
+ @logback_core_jar_path ||= Dir[File.join(SLF4J_LIB_PATH, 'logback-core-*.jar')].first
22
+ end
23
+ module_function :logback_core_jar_path
24
+
25
+ # Logback Classic JAR
26
+ def logback_jar_path
27
+ @logback_jar_path ||= Dir[File.join(SLF4J_LIB_PATH, 'logback-classic-*.jar')].first
28
+ end
29
+ module_function :logback_jar_path
30
+
31
+ # Require all JARs, if `all` is set to `false`, then only
32
+ # the SLF4J API jar is loaded.
33
+ def require_slf4j_jars!(all = true)
34
+ require self.slf4j_api_jar_path
35
+ if all
36
+ require self.logback_core_jar_path
37
+ require self.logback_jar_path
38
+ end
39
+ end
40
+ module_function :require_slf4j_jars!
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,88 @@
1
+ require 'loggr/severity'
2
+
3
+ module Loggr
4
+ module SLF4J
5
+
6
+ # Simple marker factory which uses `org.slf4j.MarkerFactory`, but
7
+ # caches the result in a local ruby hash, by name.
8
+ class MarkerFactory
9
+
10
+ # Get marker for any non-empty string.
11
+ def self.[](name)
12
+ name = name.to_s.strip
13
+ return nil if name.length == 0
14
+ @markers ||= {}
15
+ @markers[name] ||= Java::OrgSlf4j::MarkerFactory.getMarker(name)
16
+ end
17
+ end
18
+
19
+ # A logger which is backed by SLF4J, thus only useable in a JRuby environment.
20
+ #
21
+ class Logger
22
+
23
+ # Get severities
24
+ include Loggr::Severity
25
+
26
+ # Basically has *no* impact, because is handled by SLF4J
27
+ attr_accessor :level
28
+
29
+ # Just to ensure compatiability with AS::BufferedLogger
30
+ attr_reader :auto_flushing, :flush, :close
31
+
32
+ # Access raw SLF4J logger & marker instances
33
+ attr_reader :java_logger, :java_marker
34
+
35
+ # Create a new Logger instance for the given name
36
+ #
37
+ def initialize(name, options = {})
38
+ name = self.class.in_java_notation(name)
39
+ @java_logger = Java::OrgSlf4j::LoggerFactory.getLogger(name.to_s)
40
+ @java_marker = MarkerFactory[options[:marker]]
41
+
42
+ # seriously, this is handled by slf4j and pretty dynamic
43
+ @level = Logger::UNKNOWN
44
+ @auto_flushing = true
45
+ end
46
+
47
+ # Create the logger methods via meta programming, sweet.
48
+ %w{trace debug info warn error}.each do |severity|
49
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
50
+ def #{severity}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
51
+ marker = (progname ? MarkerFactory[progname] : nil) || java_marker # marker = (progname ? MarkerFactory[progname] : nil) || java_marker
52
+ if java_logger.is_#{severity}_enabled(marker) # if java_logger.is_debug_enabled(marker)
53
+ java_logger.#{severity}(marker, build_message(message, progname, &block)) # java_logger.debug(marker, build_message(message, progname, &block))
54
+ end # end
55
+ end # end
56
+
57
+ def #{severity}? # def debug?
58
+ !!java_logger.is_#{severity}_enabled(java_marker) # !!java_logger.is_debug_enabled(java_marker)
59
+ end # end
60
+ EOT
61
+ end
62
+
63
+ # Add support for fatal, just alias to error
64
+ alias_method :fatal, :error
65
+ alias_method :fatal?, :error?
66
+
67
+ # If a class, module or object is used converts `Foo::Bar::SomeThing` to
68
+ # java notation: `foo.bar.SomeThing`. Symbols and Strings are left as is!
69
+ #
70
+ def self.in_java_notation(name)
71
+ return name.to_s if name.respond_to?(:to_str) || name.is_a?(Symbol)
72
+ name = name.is_a?(Module) ? name.name.to_s : name.class.name.to_s
73
+ parts = name.split('::')
74
+ last = parts.pop
75
+ parts.map { |p| p.downcase }.push(last).join('.')
76
+ end
77
+
78
+ protected
79
+
80
+ # Construct the message, note that progname will be ignored, maybe set as
81
+ # MDC?
82
+ def build_message(message = nil, progname = nil, &block)
83
+ message = yield if message.nil? && block_given?
84
+ message.to_s.gsub(/$\s*^/, '')
85
+ end
86
+ end
87
+ end
88
+ end