loggability 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'logger'
6
+ require 'loggability' unless defined?( Loggability )
7
+
8
+
9
+ module Loggability::Constants
10
+
11
+ # Log level names and their Logger constant equivalents
12
+ LOG_LEVELS = {
13
+ debug: Logger::DEBUG,
14
+ info: Logger::INFO,
15
+ warn: Logger::WARN,
16
+ error: Logger::ERROR,
17
+ fatal: Logger::FATAL,
18
+ }.freeze
19
+
20
+ # Logger levels -> names
21
+ LOG_LEVEL_NAMES = LOG_LEVELS.invert.freeze
22
+
23
+
24
+ end # module Loggability::Constants
25
+
26
+
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'pluginfactory'
6
+
7
+ require 'loggability' unless defined?( Loggability )
8
+
9
+
10
+ ### An abstract base class for Loggability log formatters.
11
+ class Loggability::Formatter
12
+ extend PluginFactory
13
+
14
+ # The default sprintf pattern
15
+ DEFAULT_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
16
+
17
+
18
+ ### PluginFactory API -- return the Array of paths prefixes to use when searching
19
+ ### formatter plugins.
20
+ def self::derivative_dirs
21
+ return ['loggability/formatter']
22
+ end
23
+
24
+
25
+ ### Initialize a new Loggability::Formatter. The specified +logformat+ should
26
+ ### be a sprintf pattern with positional placeholders:
27
+ ###
28
+ ### [<tt>%1$s</tt>] Time (pre-formatted using strftime with the +datetime_format+)
29
+ ### [<tt>%2$d</tt>] Time microseconds
30
+ ### [<tt>%3$d</tt>] PID
31
+ ### [<tt>%4$s</tt>] Thread ID
32
+ ### [<tt>%5$s</tt>] Severity
33
+ ### [<tt>%6$s</tt>] Object/Program Name
34
+ ### [<tt>%7$s</tt>] Message
35
+ ###
36
+ def initialize( logformat, datetime_format=DEFAULT_DATETIME_FORMAT )
37
+ @format = logformat.dup
38
+ @datetime_format = datetime_format.dup
39
+ end
40
+
41
+
42
+ ######
43
+ public
44
+ ######
45
+
46
+ # Main log sprintf format
47
+ attr_accessor :format
48
+
49
+ # Strftime format for log messages
50
+ attr_accessor :datetime_format
51
+
52
+
53
+ ### Create a log message from the given +severity+, +time+, +progname+, and +message+
54
+ ### and return it.
55
+ def call( severity, time, progname, message )
56
+ timeformat = self.datetime_format
57
+ args = [
58
+ time.strftime( timeformat ), # %1$s
59
+ time.usec, # %2$d
60
+ Process.pid, # %3$d
61
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
62
+ severity, # %5$s
63
+ progname, # %6$s
64
+ self.msg2str(message, severity) # %7$s
65
+ ]
66
+
67
+ return self.format % args
68
+ end
69
+
70
+
71
+ #########
72
+ protected
73
+ #########
74
+
75
+ ### Format the specified +msg+ for output to the log.
76
+ def msg2str( msg, severity )
77
+ case msg
78
+ when String
79
+ return msg
80
+ when Exception
81
+ bt = severity == 'DEBUG' ? msg.backtrace.join("\n") : msg.backtrace.first
82
+ return "%p: %s from %s" % [ msg.class, msg.message, bt ]
83
+ else
84
+ return msg.inspect
85
+ end
86
+ end
87
+
88
+ end # class Loggability::Formatter
89
+
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'loggability' unless defined?( Loggability )
6
+ require 'loggability/formatter' unless defined?( Loggability::Formatter )
7
+ require 'loggability/formatter/default'
8
+
9
+
10
+ # The default log formatter class.
11
+ class Loggability::Formatter::Color < Loggability::Formatter::Default
12
+
13
+ # ANSI reset
14
+ RESET = "\e[0m"
15
+
16
+ # ANSI color escapes keyed by severity
17
+ LEVEL_CODES = {
18
+ debug: "\e[1;30m", # bold black
19
+ info: "\e[37m", # white
20
+ warn: "\e[1;33m", # bold yellow
21
+ error: "\e[31m", # red
22
+ fatal: "\e[1;31m", # bold red
23
+ }
24
+
25
+ # Pattern for matching color terminals
26
+ COLOR_TERMINAL_NAMES = /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i
27
+
28
+
29
+ ### Create a new formatter.
30
+ def initialize( * )
31
+ super
32
+ @color_enabled = COLOR_TERMINAL_NAMES.match( ENV['TERM'] ) ? true : false
33
+ end
34
+
35
+
36
+ ######
37
+ public
38
+ ######
39
+
40
+ ### Format the specified +msg+ for output to the log.
41
+ def msg2str( msg, severity )
42
+ msg = super
43
+ if @color_enabled
44
+ color = severity.downcase.to_sym
45
+ msg = [ LEVEL_CODES[color], msg, RESET ].join( '' )
46
+ end
47
+
48
+ return msg
49
+ end
50
+
51
+ end # class Loggability::Formatter::Color
52
+
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'loggability' unless defined?( Loggability )
6
+ require 'loggability/formatter' unless defined?( Loggability::Formatter )
7
+
8
+
9
+ # The default log formatter class.
10
+ class Loggability::Formatter::Default < Loggability::Formatter
11
+
12
+ # The format to output unless debugging is turned on
13
+ FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"
14
+
15
+ ### Specify the format for the default formatter.
16
+ def initialize( format=FORMAT )
17
+ super
18
+ end
19
+
20
+ end # class Loggability::Formatter::Default
21
+
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'loggability' unless defined?( Loggability )
6
+ require 'loggability/formatter' unless defined?( Loggability::Formatter )
7
+
8
+
9
+ # The default log formatter class.
10
+ class Loggability::Formatter::HTML < Loggability::Formatter
11
+
12
+ # The default HTML fragment that'll be used as the template for each log message.
13
+ HTML_LOG_FORMAT = %q{
14
+ <div class="log-message %5$s-log-message">
15
+ <span class="log-time">%1$s.%2$06d</span>
16
+ [
17
+ <span class="log-pid">%3$d</span>
18
+ /
19
+ <span class="log-tid">%4$s</span>
20
+ ]
21
+ <span class="log-level">%5$s</span>
22
+ :
23
+ <span class="log-name">%6$s</span>
24
+ <span class="log-message-text">%7$s</span>
25
+ </div>
26
+ }.gsub( /[\t\n]/, ' ' )
27
+
28
+ # The format for dumping exceptions
29
+ HTML_EXCEPTION_FORMAT = %Q{
30
+ <span class="log-exc">%1$p</span>:
31
+ <span class="log-exc-message">%2$s</span>
32
+ from <span class="log-exc-firstframe">%3$s</span>
33
+ }.gsub( /[\t\n]/, ' ' )
34
+
35
+
36
+ ### Override the logging formats with ones that generate HTML fragments
37
+ def initialize( format=HTML_LOG_FORMAT ) # :notnew:
38
+ super
39
+ end
40
+
41
+
42
+ #########
43
+ protected
44
+ #########
45
+
46
+ ### Format the specified +object+ for output to the log.
47
+ def msg2str( object, severity )
48
+ case object
49
+ when String
50
+ return escape_html( object )
51
+ when Exception
52
+ return HTML_EXCEPTION_FORMAT % [
53
+ object.class,
54
+ escape_html(object.message),
55
+ escape_html(object.backtrace.first)
56
+ ]
57
+ else
58
+ return escape_html(object.inspect)
59
+ end
60
+ end
61
+
62
+
63
+ #######
64
+ private
65
+ #######
66
+
67
+ ### Escape any HTML special characters in +string+.
68
+ def escape_html( string )
69
+ return string.
70
+ gsub( '&', '&amp;' ).
71
+ gsub( '<', '&lt;' ).
72
+ gsub( '>', '&gt;' )
73
+ end
74
+
75
+ end # class Loggability::Formatter::Default
76
+
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'logger'
6
+ require 'loggability' unless defined?( Loggability )
7
+ require 'loggability/constants'
8
+ require 'loggability/formatter'
9
+
10
+
11
+ # A subclass of Logger that provides additional API for configuring outputters,
12
+ # formatters, etc.
13
+ class Loggability::Logger < ::Logger
14
+ include Loggability::Constants,
15
+ Logger::Severity
16
+
17
+ # Default log 'device'
18
+ DEFAULT_DEVICE = $stderr
19
+
20
+ # Default 'shift age'
21
+ DEFAULT_SHIFT_AGE = 0
22
+
23
+ # Default 'shift size'
24
+ DEFAULT_SHIFT_SIZE = 1048576
25
+
26
+
27
+ # A log device that appends to the object it's constructed with instead of writing
28
+ # to a file descriptor or a file.
29
+ class AppendingLogDevice
30
+
31
+ ### Create a new AppendingLogDevice that will append content to +array+.
32
+ def initialize( target )
33
+ @target = target
34
+ end
35
+
36
+ ### Append the specified +message+ to the target.
37
+ def write( message )
38
+ @target << message
39
+ end
40
+
41
+ ### No-op -- this is here just so Logger doesn't complain
42
+ def close; end
43
+
44
+ end # class AppendingLogDevice
45
+
46
+
47
+ # Proxy for the Logger that injects the name of the object it wraps as the 'progname'
48
+ # of each log message.
49
+ class ObjectNameProxy
50
+
51
+ ### Create a proxy for the given +logger+ that will inject the name of the
52
+ ### specified +object+ into the 'progname' of each log message.
53
+ def initialize( logger, object )
54
+ @logger = logger
55
+ @progname = make_progname( object )
56
+ end
57
+
58
+ ######
59
+ public
60
+ ######
61
+
62
+ # The Loggability::Logger this proxy is for.
63
+ attr_reader :logger
64
+
65
+ # The progname of the object the proxy is for.
66
+ attr_reader :progname
67
+
68
+
69
+ ### Delegate debug messages
70
+ def debug( msg=nil, &block )
71
+ @logger.add( Logger::DEBUG, msg, @progname, &block )
72
+ end
73
+
74
+ ### Delegate info messages
75
+ def info( msg=nil, &block )
76
+ @logger.add( Logger::INFO, msg, @progname, &block )
77
+ end
78
+
79
+ ### Delegate warn messages
80
+ def warn( msg=nil, &block )
81
+ @logger.add( Logger::WARN, msg, @progname, &block )
82
+ end
83
+
84
+ ### Delegate error messages
85
+ def error( msg=nil, &block )
86
+ @logger.add( Logger::ERROR, msg, @progname, &block )
87
+ end
88
+
89
+ ### Delegate fatal messages
90
+ def fatal( msg=nil, &block )
91
+ @logger.add( Logger::FATAL, msg, @progname, &block )
92
+ end
93
+
94
+
95
+ ### Return a human-readable string representation of the object, suitable for debugging.
96
+ def inspect
97
+ return "#<%p:%#016x for %s>" % [ self.class, self.object_id * 2, @progname ]
98
+ end
99
+
100
+
101
+ #######
102
+ private
103
+ #######
104
+
105
+ ### Make a progname for the specified object.
106
+ def make_progname( object )
107
+ case object
108
+ when Class
109
+ object.inspect
110
+ else
111
+ "%p:%#x" % [ object.class, object.object_id * 2 ]
112
+ end
113
+ end
114
+
115
+
116
+ end # class ObjectNameProxy
117
+
118
+
119
+ ### Create a new Logger wrapper that will output to the specified +logdev+.
120
+ def initialize( logdev=DEFAULT_DEVICE, *args )
121
+ super
122
+ self.level = ($DEBUG ? DEBUG : WARN)
123
+ @default_formatter = Loggability::Formatter.create( :default )
124
+ end
125
+
126
+
127
+ ######
128
+ public
129
+ ######
130
+
131
+ #
132
+ # :section: Severity Level
133
+ #
134
+
135
+ ### Return the logger's level as a Symbol.
136
+ def level
137
+ numeric_level = super
138
+ return LOG_LEVEL_NAMES[ numeric_level ]
139
+ end
140
+
141
+
142
+ ### Set the logger level to +newlevel+, which can be a numeric level (e.g., Logger::DEBUG, etc.),
143
+ ### or a symbolic level (e.g., :debug, :info, etc.)
144
+ def level=( newlevel )
145
+ newlevel = LOG_LEVELS[ newlevel.to_sym ] if newlevel.respond_to?( :to_sym )
146
+ super( newlevel )
147
+ end
148
+
149
+
150
+ #
151
+ # :section: Output Device
152
+ #
153
+
154
+ # The raw log device
155
+ attr_accessor :logdev
156
+
157
+
158
+ ### Change the log device to log to +target+ instead of what it was before. Any additional
159
+ ### +args+ are passed to the LogDevice's constructor. In addition to Logger's support for
160
+ ### logging to IO objects and files (given a filename in a String), this method can also
161
+ ### set up logging to any object that responds to #<<.
162
+ def output_to( target, *args )
163
+ if target.respond_to?( :write ) || target.is_a?( String )
164
+ opts = { shift_age: args.shift, shift_size: args.shift }
165
+ self.logdev = Logger::LogDevice.new( target, opts )
166
+ elsif target.respond_to?( :<< )
167
+ self.logdev = AppendingLogDevice.new( target )
168
+ else
169
+ raise ArgumentError, "don't know how to output to %p (a %p)" % [ target, target.class ]
170
+ end
171
+ end
172
+ alias_method :write_to, :output_to
173
+
174
+
175
+ #
176
+ # :section: Output Formatting API
177
+ #
178
+
179
+ ### Return the current formatter used to format log messages.
180
+ def formatter
181
+ return ( @formatter || @default_formatter )
182
+ end
183
+
184
+
185
+ ### Format a log message using the current formatter and return it.
186
+ def format_message( severity, datetime, progname, msg )
187
+ self.formatter.call(severity, datetime, progname, msg)
188
+ end
189
+
190
+
191
+ ### Set a new +formatter+ for the logger. If +formatter+ is +nil+ or +:default+, this causes the
192
+ ### logger to fall back to its default formatter. If it's a Symbol other than +:default+, it looks
193
+ ### for a similarly-named formatter under loggability/formatter/ and uses that. If +formatter+ is
194
+ ### an object that responds to #call (e.g., a Proc or a Method object), that object is used directly.
195
+ ###
196
+ ### Procs and methods should have the method signature: (severity, datetime, progname, msg).
197
+ ###
198
+ ### # Load and use the HTML formatter
199
+ ### MyProject.logger.format_with( :html )
200
+ ###
201
+ ### # Call self.format_logmsg(...) to format messages
202
+ ### MyProject.logger.format_with( self.method(:format_logmsg) )
203
+ ###
204
+ ### # Reset to the default
205
+ ### MyProject.logger.format_with( :default )
206
+ ###
207
+ def format_with( formatter=nil, &block ) # :yield: severity, datetime, progname, msg
208
+ formatter ||= block
209
+
210
+ if formatter.nil? || formatter == :default
211
+ @formatter = nil
212
+
213
+ elsif formatter.respond_to?( :call )
214
+ @formatter = formatter
215
+
216
+ elsif formatter.respond_to?( :to_sym )
217
+ @formatter = Loggability::Formatter.create( formatter )
218
+
219
+ else
220
+ raise ArgumentError, "don't know what to do with a %p formatter (%p)" %
221
+ [ formatter.class, formatter ]
222
+ end
223
+ end
224
+ alias_method :format_as, :format_with
225
+ alias_method :formatter=, :format_with
226
+
227
+
228
+ #
229
+ # :section: Progname Proxy
230
+ # Rather than require that the caller provide the 'progname' part of the log message
231
+ # on every call, you can grab a Proxy object for a particular object and Logger combination
232
+ # that will include the object's name with every log message.
233
+ #
234
+
235
+
236
+ ### Create a logging proxy for +object+ that will include its name as the 'progname' of
237
+ ### each message.
238
+ def proxy_for( object )
239
+ return ObjectNameProxy.new( self, object )
240
+ end
241
+
242
+
243
+ end # class Loggability::Logger
244
+