loggr 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSES.md +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +230 -0
- data/Rakefile +14 -0
- data/lib/logback-classic-0.9.29.jar +0 -0
- data/lib/logback-core-0.9.29.jar +0 -0
- data/lib/loggr.rb +38 -0
- data/lib/loggr/adapter.rb +114 -0
- data/lib/loggr/adapter/abstract.rb +25 -0
- data/lib/loggr/adapter/base.rb +54 -0
- data/lib/loggr/adapter/buffered.rb +25 -0
- data/lib/loggr/adapter/nop.rb +39 -0
- data/lib/loggr/adapter/rails.rb +36 -0
- data/lib/loggr/adapter/slf4j.rb +30 -0
- data/lib/loggr/factory.rb +9 -0
- data/lib/loggr/lint.rb +54 -0
- data/lib/loggr/severity.rb +16 -0
- data/lib/loggr/slf4j.rb +7 -0
- data/lib/loggr/slf4j/jars.rb +44 -0
- data/lib/loggr/slf4j/logger.rb +88 -0
- data/lib/loggr/slf4j/mdc.rb +35 -0
- data/lib/loggr/version.rb +3 -0
- data/lib/slf4j-api-1.6.1.jar +0 -0
- data/loggr.gemspec +26 -0
- data/test/logback_helper.rb +54 -0
- data/test/test_helper.rb +50 -0
- data/test/unit/adapter/abstract_test.rb +12 -0
- data/test/unit/adapter/base_test.rb +65 -0
- data/test/unit/adapter/buffered_test.rb +44 -0
- data/test/unit/adapter/nop_test.rb +24 -0
- data/test/unit/adapter/rails_test.rb +38 -0
- data/test/unit/adapter/slf4j_test.rb +25 -0
- data/test/unit/factory_test.rb +60 -0
- data/test/unit/slf4j/jars_test.rb +22 -0
- data/test/unit/slf4j/logger_test.rb +139 -0
- data/test/unit/slf4j/mdc_test.rb +77 -0
- metadata +151 -0
@@ -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
|
data/lib/loggr/slf4j.rb
ADDED
@@ -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
|