loggability 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +4 -0
- data/.gemtest +0 -0
- data/ChangeLog +9353 -0
- data/History.rdoc +4 -0
- data/Manifest.txt +19 -0
- data/README.rdoc +200 -0
- data/Rakefile +49 -0
- data/bin/loggability +3 -0
- data/lib/loggability.rb +223 -0
- data/lib/loggability/constants.rb +26 -0
- data/lib/loggability/formatter.rb +89 -0
- data/lib/loggability/formatter/color.rb +52 -0
- data/lib/loggability/formatter/default.rb +21 -0
- data/lib/loggability/formatter/html.rb +76 -0
- data/lib/loggability/logger.rb +244 -0
- data/spec/lib/helpers.rb +42 -0
- data/spec/loggability/formatter/color_spec.rb +38 -0
- data/spec/loggability/formatter/html_spec.rb +45 -0
- data/spec/loggability/formatter_spec.rb +50 -0
- data/spec/loggability/logger_spec.rb +149 -0
- data/spec/loggability_spec.rb +84 -0
- metadata +220 -0
- metadata.gz.sig +0 -0
@@ -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( '&', '&' ).
|
71
|
+
gsub( '<', '<' ).
|
72
|
+
gsub( '>', '>' )
|
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
|
+
|