right_support 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,3 +32,7 @@ end
32
32
  require 'right_support/log/system_logger'
33
33
  require 'right_support/log/filter_logger'
34
34
  require 'right_support/log/tag_logger'
35
+ require 'right_support/log/null_logger'
36
+ require 'right_support/log/multiplexer'
37
+ require 'right_support/log/exception_logger'
38
+ require 'right_support/log/mixin'
@@ -0,0 +1,86 @@
1
+ #
2
+ # Copyright (c) 2012 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightSupport::Log
24
+ # A logger that prepends a tag to every message that is emitted. Can be used to
25
+ # correlate logs with a Web session ID, transaction ID or other context.
26
+ #
27
+ # The user of this logger is responsible for calling #tag= to set the tag as
28
+ # appropriate, e.g. in a Web request around-filter.
29
+ #
30
+ # This logger uses thread-local storage (TLS) to provide tagging on a per-thread
31
+ # basis; however, it does not account for EventMachine, neverblock, the use of
32
+ # Ruby fibers, or any other phenomenon that can "hijack" a thread's call stack.
33
+ #
34
+ class ExceptionLogger < FilterLogger
35
+ # Format exception information
36
+ #
37
+ # === Parameters
38
+ # description(String):: Error description
39
+ # exception(Exception|String):: Associated exception or other parenthetical error information
40
+ # backtrace(Symbol):: Exception backtrace extent: :no_trace, :caller, or :trace,
41
+ # defaults to :caller
42
+ #
43
+ # === Return
44
+ # (String):: Information about the exception in a format suitable for logging
45
+ def self.format_exception(description, exception = nil, backtrace = :caller)
46
+ if exception
47
+ if exception.respond_to?(:message)
48
+ description += " (#{exception.class}: #{exception.message}"
49
+ else
50
+ description += " (#{exception}"
51
+ end
52
+
53
+ unless exception.respond_to?(:backtrace) && exception.backtrace
54
+ backtrace = :no_trace
55
+ end
56
+
57
+ case backtrace
58
+ when :no_trace
59
+ description += ")"
60
+ when :caller
61
+ description += " IN " + exception.backtrace[0] + ")"
62
+ when :trace
63
+ description += " IN\n " + exception.backtrace.join("\n ") + ")"
64
+ else
65
+ raise ArgumentError, "Unknown backtrace value #{backtrace.inspect}"
66
+ end
67
+ end
68
+
69
+ description
70
+ end
71
+
72
+ # Log information about an exception with ERROR severity.
73
+ #
74
+ # === Parameters
75
+ # description(String):: Error description
76
+ # exception(Exception|String):: Associated exception or other parenthetical error information
77
+ # backtrace(Symbol):: Exception backtrace extent: :no_trace, :caller, or :trace,
78
+ # defaults to :caller
79
+ #
80
+ # === Return
81
+ # Forwards the return value of its underlying logger's #error method
82
+ def exception(description, exception = nil, backtrace = :caller)
83
+ error(self.class.format_exception(description, exception, backtrace))
84
+ end
85
+ end
86
+ end
@@ -27,7 +27,19 @@ module RightSupport::Log
27
27
  # before they are passed to the underlying Logger. Can be used to for various log-
28
28
  # processing tasks such as filtering sensitive data or tagging log lines with a
29
29
  # context marker.
30
+ #
31
+ # FilterLogger implements method_missing and respond_to? and proxies unknown
32
+ # method calls to its underlying logger; this allows it to be used as a
33
+ # decorator.
30
34
  class FilterLogger < Logger
35
+ SEVERITY_TO_METHOD = {
36
+ DEBUG => :debug,
37
+ INFO => :info,
38
+ WARN => :warn,
39
+ ERROR => :error,
40
+ FATAL => :fatal,
41
+ }
42
+
31
43
  # Initialize a new instance of this class.
32
44
  #
33
45
  # === Parameters
@@ -35,7 +47,70 @@ module RightSupport::Log
35
47
  #
36
48
  def initialize(actual_logger)
37
49
  @actual_logger = actual_logger
50
+ end
51
+
52
+ def method_missing(meth, *args)
53
+ return @actual_logger.__send__(meth, *args) if @actual_logger.respond_to?(meth)
54
+ super
55
+ end
56
+
57
+ def respond_to?(meth)
58
+ super(meth) || @actual_logger.respond_to?(meth)
59
+ end
38
60
 
61
+ # Log a message, filtering the severity and/or message and dispatching
62
+ # to the corresponding severity-method of the underlying logger.
63
+ #
64
+ # See #info for more information.
65
+ def debug(message = nil, &block)
66
+ severity, message = filter(DEBUG, message)
67
+ meth = SEVERITY_TO_METHOD[severity]
68
+ raise ArgumentError, "Filter emitted unknown severity #{severity.inspect}" unless meth
69
+ @actual_logger.__send__(meth, message, &block)
70
+ end
71
+
72
+ # Log a message, filtering the severity and/or message and dispatching
73
+ # to the corresponding severity-method of the underlying logger.
74
+ #
75
+ # See #info for more information.
76
+ def info(message = nil, &block)
77
+ severity, message = filter(INFO, message)
78
+ meth = SEVERITY_TO_METHOD[severity]
79
+ raise ArgumentError, "Filter emitted unknown severity #{severity.inspect}" unless meth
80
+ @actual_logger.__send__(meth, message, &block)
81
+ end
82
+
83
+ # Log a message, filtering the severity and/or message and dispatching
84
+ # to the corresponding severity-method of the underlying logger.
85
+ #
86
+ # See #info for more information.
87
+ def warn(message = nil, &block)
88
+ severity, message = filter(WARN, message)
89
+ meth = SEVERITY_TO_METHOD[severity]
90
+ raise ArgumentError, "Filter emitted unknown severity #{severity.inspect}" unless meth
91
+ @actual_logger.__send__(meth, message, &block)
92
+ end
93
+
94
+ # Log a message, filtering the severity and/or message and dispatching
95
+ # to the corresponding severity-method of the underlying logger.
96
+ #
97
+ # See #info for more information.
98
+ def error(message = nil, &block)
99
+ severity, message = filter(ERROR, message)
100
+ meth = SEVERITY_TO_METHOD[severity]
101
+ raise ArgumentError, "Filter emitted unknown severity #{severity.inspect}" unless meth
102
+ @actual_logger.__send__(meth, message, &block)
103
+ end
104
+
105
+ # Log a message, filtering the severity and/or message and dispatching
106
+ # to the corresponding severity-method of the underlying logger.
107
+ #
108
+ # See #info for more information.
109
+ def fatal(message = nil, &block)
110
+ severity, message = filter(FATAL, message)
111
+ meth = SEVERITY_TO_METHOD[severity]
112
+ raise ArgumentError, "Filter emitted unknown severity #{severity.inspect}" unless meth
113
+ @actual_logger.__send__(meth, message, &block)
39
114
  end
40
115
 
41
116
  # Add a log line, filtering the severity and message before calling through
@@ -0,0 +1,104 @@
1
+ #
2
+ # Copyright (c) 2012 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightSupport::Log
24
+ # A mixin that facilitates access to a logger for classes that want logging functionality.
25
+ #
26
+ # === Basic Usage
27
+ # Your class must opt into logging by including the mixin:
28
+ # class AwesomenessProcessor
29
+ # include RightSupport::Log::Mixin
30
+ #
31
+ # Having opted in, your class now has a #logger instance method, as well as a .logger
32
+ # class method, which allows logging from either instance or class methods:
33
+ #
34
+ # def self.prepare_awesomeness_for_processing(input)
35
+ # logger.info "Preparing a #{input.class.name} for additional awesomeness"
36
+ # end
37
+ #
38
+ # def process_awesomeness(input)
39
+ # input = self.class.prepare_awesomeness(input)
40
+ # logger.info "Processing #{input.size} units of awesomeness"
41
+ # end
42
+ #
43
+ # === Controlling Where Log Messages Go
44
+ # By default, your class shares a Logger object with all other classes that include the mixin.
45
+ # This process-wide default logger can be set or retrieved using module-level accessors:
46
+ #
47
+ # # default_logger starts out as a NullLogger; you probably want to set it to something different
48
+ # puts "Current logger: "+ RightSupport::Log::Mixin.default_logger.class
49
+ # RightSupport::Log::Mixin.default_logger = SyslogLogger.new('my program')
50
+ #
51
+ # It is good form to set the default logger; however, if your class needs additional or different
52
+ # logging functionality, you can override the logger on a per-class level:
53
+ # AwesomenessProcessor.logger = Logger.new(File.open('awesomeness.log', 'w'))
54
+ #
55
+ # Finally, you can override the logger on a per-instance level for truly fine-grained control.
56
+ # This is generally useless, but just in case:
57
+ # processor = AwesomenessProcessor.new
58
+ # processor.logger = Logger.new(File.open("#{processor.object_id}.log", 'w'))
59
+ #
60
+ module Mixin
61
+ # A decorator class which will be wrapped around any logger that is
62
+ # provided to any of the setter methods. This ensures that ExceptionLogger's
63
+ # methods will always be available to anyone who uses this mixin for logging.
64
+ Decorator = RightSupport::Log::ExceptionLogger
65
+
66
+ # Class methods that become available to classes that include Mixin.
67
+ module ClassMethods
68
+ def logger
69
+ @logger || RightSupport::Log::Mixin.default_logger
70
+ end
71
+
72
+ def logger=(logger)
73
+ logger = Decorator.new(logger) unless logger.nil? || logger.is_a?(Decorator)
74
+ @logger = logger
75
+ end
76
+ end
77
+
78
+ # Instance methods that become available to classes that include Mixin.
79
+ module InstanceMethods
80
+ def logger
81
+ @logger || self.class.logger
82
+ end
83
+
84
+ def logger=(logger)
85
+ logger = Decorator.new(logger) unless logger.nil? || logger.is_a?(Decorator)
86
+ @logger = logger
87
+ end
88
+ end
89
+
90
+ def self.default_logger
91
+ @default_logger ||= Decorator.new(RightSupport::Log::NullLogger.new)
92
+ end
93
+
94
+ def self.default_logger=(logger)
95
+ logger = Decorator.new(logger) unless logger.nil? || logger.is_a?(Decorator)
96
+ @default_logger = logger
97
+ end
98
+
99
+ def self.included(base)
100
+ base.extend(ClassMethods)
101
+ base.__send__(:include, InstanceMethods)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,93 @@
1
+ #
2
+ # Copyright (c) 2009-2012 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightSupport::Log
24
+ # A log multiplexer that uses method_missing to dispatch method calls to zero or more
25
+ # target loggers. Caveat emptor: _you_ are responsible for ensuring that all of the targets
26
+ # respond to all of the messages you care to send; this class will perform no error checking
27
+ # for you!
28
+ class Multiplexer
29
+
30
+ # Access to underlying list of multiplexed objects
31
+ attr_reader :targets
32
+
33
+ # Prevent Kernel#warn from being called; #warn should be multiplexed to targets.
34
+ undef warn rescue nil
35
+
36
+ # Create a new multiplexer with a default list of targets.
37
+ #
38
+ # === Parameters
39
+ # targets(Object):: Targets that should receive the method calls
40
+ def initialize(*targets)
41
+ @targets = targets || []
42
+ end
43
+
44
+ # Add object to list of multiplexed targets
45
+ #
46
+ # === Parameters
47
+ # target(Object):: Add target to list of multiplexed targets
48
+ #
49
+ # === Return
50
+ # self(RightScale::Multiplexer):: self so operation can be chained
51
+ def add(target)
52
+ @targets << target unless @targets.include?(target)
53
+ self
54
+ end
55
+
56
+ # Remove object from list of multiplexed targets
57
+ #
58
+ # === Parameters
59
+ # target(Object):: Remove target from list of multiplexed targets
60
+ #
61
+ # === Return
62
+ # self(RightScale::Multiplexer):: self so operation can be chained
63
+ def remove(target)
64
+ @targets.delete_if { |t| t == target }
65
+ self
66
+ end
67
+
68
+ # Access target at given index
69
+ #
70
+ # === Parameters
71
+ # index(Integer):: Target index
72
+ #
73
+ # === Return
74
+ # target(Object):: Target at index 'index' or nil if none
75
+ def [](index)
76
+ target = @targets[index]
77
+ end
78
+
79
+ # Forward any method invocation to targets
80
+ #
81
+ # === Parameters
82
+ # m(Symbol):: Method that should be multiplexed
83
+ # args(Array):: Arguments
84
+ #
85
+ # === Return
86
+ # res(Object):: Result of first target in list
87
+ def method_missing(m, *args)
88
+ res = @targets.inject([]) { |res, t| res << t.__send__(m, *args) }
89
+ res[0]
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # Copyright (c) 2012 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'logger'
24
+
25
+ module RightSupport::Log
26
+ # A logger than does not perform any logging. This is useful to send log entries
27
+ # to "the bit bucket" or "to /dev/null" -- hence the name.
28
+ class NullLogger < Logger
29
+ # Initialize a new instance of this class.
30
+ #
31
+ def initialize
32
+ super(nil)
33
+ end
34
+
35
+ # Do nothing. This method exists for interface compatibility with Logger.
36
+ #
37
+ # === Parameters
38
+ # severity(Integer):: one of the Logger severity constants
39
+ # message(String):: the message to log, or nil
40
+ # progname(String):: the program name, or nil
41
+ #
42
+ # === Block
43
+ # If message == nil and a block is given, yields to the block. The block's
44
+ # output value is ignored.
45
+ #
46
+ # === Return
47
+ # always returns true
48
+ def add(severity, message = nil, progname = nil, &block)
49
+ #act like a real Logger w/r/t the block
50
+ yield if message.nil? && block_given?
51
+ true
52
+ end
53
+
54
+ # Do nothing. This method exists for interface compatibility with Logger.
55
+ #
56
+ # === Parameters
57
+ # msg(String):: the message to log, or nil
58
+ #
59
+ # === Block
60
+ # If message == nil and a block is given, yields to the block. The block's
61
+ # output value is ignored.
62
+ #
63
+ # === Return
64
+ def <<(msg)
65
+ msg.to_s.size
66
+ end
67
+
68
+ # Do nothing. This method exists for interface compatibility with Logger.
69
+ #
70
+ # === Return
71
+ # always returns true
72
+ def close
73
+ nil
74
+ end
75
+ end
76
+ end