loggability 0.0.1

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.
@@ -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
+