right_support 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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