strelka 0.0.1pre4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/History.rdoc +4 -0
- data/IDEAS.textile +174 -0
- data/Manifest.txt +38 -0
- data/README.rdoc +66 -0
- data/Rakefile +64 -0
- data/bin/leash +403 -0
- data/data/strelka/apps/strelka-admin +65 -0
- data/data/strelka/apps/strelka-setup +26 -0
- data/data/strelka/bootstrap-config.rb +34 -0
- data/data/strelka/templates/admin/console.tmpl +21 -0
- data/data/strelka/templates/layout.tmpl +30 -0
- data/lib/strelka/app/defaultrouter.rb +85 -0
- data/lib/strelka/app/filters.rb +70 -0
- data/lib/strelka/app/parameters.rb +64 -0
- data/lib/strelka/app/plugins.rb +205 -0
- data/lib/strelka/app/routing.rb +140 -0
- data/lib/strelka/app/templating.rb +157 -0
- data/lib/strelka/app.rb +175 -0
- data/lib/strelka/behavior/plugin.rb +36 -0
- data/lib/strelka/constants.rb +53 -0
- data/lib/strelka/httprequest.rb +52 -0
- data/lib/strelka/logging.rb +241 -0
- data/lib/strelka/mixins.rb +143 -0
- data/lib/strelka/process.rb +19 -0
- data/lib/strelka.rb +40 -0
- data/spec/data/layout.tmpl +3 -0
- data/spec/data/main.tmpl +1 -0
- data/spec/lib/constants.rb +32 -0
- data/spec/lib/helpers.rb +134 -0
- data/spec/strelka/app/defaultrouter_spec.rb +215 -0
- data/spec/strelka/app/parameters_spec.rb +74 -0
- data/spec/strelka/app/plugins_spec.rb +167 -0
- data/spec/strelka/app/routing_spec.rb +139 -0
- data/spec/strelka/app/templating_spec.rb +169 -0
- data/spec/strelka/app_spec.rb +160 -0
- data/spec/strelka/httprequest_spec.rb +54 -0
- data/spec/strelka/logging_spec.rb +72 -0
- data/spec/strelka_spec.rb +27 -0
- metadata +226 -0
data/lib/strelka/app.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mongrel2/handler'
|
4
|
+
require 'strelka' unless defined?( Strelka )
|
5
|
+
|
6
|
+
|
7
|
+
# The application base class.
|
8
|
+
class Strelka::App < Mongrel2::Handler
|
9
|
+
include Strelka::Loggable,
|
10
|
+
Strelka::Constants
|
11
|
+
|
12
|
+
# Load the plugin system
|
13
|
+
require 'strelka/app/plugins'
|
14
|
+
include Strelka::App::Plugins
|
15
|
+
|
16
|
+
|
17
|
+
@default_content_type = nil
|
18
|
+
|
19
|
+
### Get/set the Content-type of requests that don't set one. Leaving this unset will
|
20
|
+
### leave the Content-type unset.
|
21
|
+
def self::default_content_type( newtype=nil )
|
22
|
+
@default_content_type = newtype if newtype
|
23
|
+
return @default_content_type
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
#################################################################
|
28
|
+
### I N S T A N C E M E T H O D S
|
29
|
+
#################################################################
|
30
|
+
|
31
|
+
######
|
32
|
+
public
|
33
|
+
######
|
34
|
+
|
35
|
+
### The main Mongrel2 entrypoint -- accept Strelka::Requests and return
|
36
|
+
### Strelka::Responses.
|
37
|
+
def handle( request )
|
38
|
+
response = nil
|
39
|
+
|
40
|
+
# Run fixup hooks on the request
|
41
|
+
request = self.fixup_request( request )
|
42
|
+
|
43
|
+
# Dispatch the request after allowing plugins to to their thing
|
44
|
+
status_info = catch( :finish ) do
|
45
|
+
response = self.handle_request( request )
|
46
|
+
nil # rvalue for the catch
|
47
|
+
end
|
48
|
+
|
49
|
+
# Status response
|
50
|
+
if status_info
|
51
|
+
self.log.debug "Preparing a status response: %p" % [ status_info ]
|
52
|
+
return self.prepare_status_response( request, status_info )
|
53
|
+
|
54
|
+
# Normal response
|
55
|
+
else
|
56
|
+
self.log.debug "Preparing a regular response: %p" % [ response ]
|
57
|
+
response ||= request.response
|
58
|
+
return self.fixup_response( request, response )
|
59
|
+
end
|
60
|
+
|
61
|
+
rescue => err
|
62
|
+
# Propagate Spec failures
|
63
|
+
raise if err.class.name =~ /^RSpec::/
|
64
|
+
|
65
|
+
msg = "%s: %s %s" % [ err.class.name, err.message, err.backtrace.first ]
|
66
|
+
self.log.error( msg )
|
67
|
+
err.backtrace[ 1..-1 ].each {|frame| self.log.debug(' ' + frame) }
|
68
|
+
|
69
|
+
status_info = { :status => HTTP::SERVER_ERROR, :message => msg }
|
70
|
+
return self.prepare_status_response( request, status_info )
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
#########
|
75
|
+
protected
|
76
|
+
#########
|
77
|
+
|
78
|
+
### Make any changes to the +request+ that are necessary before handling it and
|
79
|
+
### return it. This is an alternate extension-point for plugins that
|
80
|
+
### wish to modify or replace the request before the request cycle is
|
81
|
+
### started.
|
82
|
+
def fixup_request( request )
|
83
|
+
self.log.debug "Fixing up request: %p" % [ request ]
|
84
|
+
request = super
|
85
|
+
self.log.debug " after fixup: %p" % [ request ]
|
86
|
+
|
87
|
+
return request
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
### Handle the request and return a +response+. This is the main extension-point
|
92
|
+
### for the plugin system. Without being overridden or extended by plugins, this
|
93
|
+
### method just returns the default Mongrel2::HTTPRequest#response.
|
94
|
+
def handle_request( request, &block )
|
95
|
+
self.log.debug "Strelka::App#handle_request"
|
96
|
+
if block
|
97
|
+
return super( request, &block )
|
98
|
+
else
|
99
|
+
return super( request ) {|r| r.response }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
### Make any changes to the +response+ that are necessary before handing it to
|
105
|
+
### Mongrel and return it. This is an alternate extension-point for plugins that
|
106
|
+
### wish to modify or replace the response after the whole request cycle is
|
107
|
+
### completed.
|
108
|
+
def fixup_response( request, response )
|
109
|
+
self.log.debug "Fixing up response: %p" % [ response ]
|
110
|
+
self.fixup_response_content_type( response )
|
111
|
+
self.fixup_head_response( response ) if request.verb == :HEAD
|
112
|
+
self.log.debug " after fixup: %p" % [ response ]
|
113
|
+
|
114
|
+
super
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
### If the +response+ doesn't yet have a Content-type header, and the app has
|
119
|
+
### defined a default (via App.default_content_type), set it to the default.
|
120
|
+
def fixup_response_content_type( response )
|
121
|
+
restype = response.content_type
|
122
|
+
|
123
|
+
if !restype
|
124
|
+
if (( default = self.class.default_content_type ))
|
125
|
+
self.log.debug "Setting default content type"
|
126
|
+
response.content_type = default
|
127
|
+
else
|
128
|
+
self.log.debug "No default content type"
|
129
|
+
end
|
130
|
+
else
|
131
|
+
self.log.debug "Content type already set: %p" % [ restype ]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
### Remove the entity body of responses to HEAD requests.
|
137
|
+
def fixup_head_response( response )
|
138
|
+
self.log.debug "Truncating entity body of HEAD response."
|
139
|
+
response.headers.content_length = response.get_content_length
|
140
|
+
response.body = ''
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
### Abort the current execution and return a response with the specified
|
145
|
+
### http_status code immediately. The specified +message+ will be logged,
|
146
|
+
### and will be included in any message that is returned as part of the
|
147
|
+
### response. The +otherstuff+ hash can be used to pass headers, etc.
|
148
|
+
def finish_with( http_status, message, otherstuff={} )
|
149
|
+
status_info = otherstuff.merge( :status => http_status, :message => message )
|
150
|
+
throw :finish, status_info
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
### Create a response to specified +request+ based on the specified +status_code+
|
155
|
+
### and +message+.
|
156
|
+
def prepare_status_response( request, status_info )
|
157
|
+
status_code, message = status_info.values_at( :status, :message )
|
158
|
+
self.log.info "Non-OK response: %d (%s)" % [ status_code, message ]
|
159
|
+
|
160
|
+
response = request.response
|
161
|
+
response.reset
|
162
|
+
response.status = status_code
|
163
|
+
|
164
|
+
# Some status codes allow explanatory text to be returned; some forbid it. Append the
|
165
|
+
# message for those that allow one.
|
166
|
+
unless request.verb == :HEAD || HTTP::BODILESS_HTTP_RESPONSE_CODES.include?( status_code )
|
167
|
+
response.content_type = 'text/plain'
|
168
|
+
response.puts( message )
|
169
|
+
end
|
170
|
+
|
171
|
+
return response
|
172
|
+
end
|
173
|
+
|
174
|
+
end # class Strelka::App
|
175
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
require 'strelka'
|
6
|
+
require 'strelka/app'
|
7
|
+
require 'strelka/app/plugins'
|
8
|
+
|
9
|
+
|
10
|
+
# This is a shared behavior for specs which different Strelka::App
|
11
|
+
# plugins share in common. If you're creating a Strelka::App plugin,
|
12
|
+
# you can test its conformity to the expectations placed on them by
|
13
|
+
# adding this to your spec:
|
14
|
+
#
|
15
|
+
# require 'strelka/behavior/plugin'
|
16
|
+
#
|
17
|
+
# describe YourPlugin do
|
18
|
+
#
|
19
|
+
# it_should_behave_like "A Strelka::App Plugin"
|
20
|
+
#
|
21
|
+
# end
|
22
|
+
|
23
|
+
shared_examples_for "A Strelka::App Plugin" do
|
24
|
+
|
25
|
+
let( :plugin ) do
|
26
|
+
described_class
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
it "extends Strelka::App::Plugin" do
|
31
|
+
plugin.should be_a( Strelka::App::Plugin )
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'mongrel2/constants'
|
5
|
+
require 'strelka' unless defined?( Strelka )
|
6
|
+
|
7
|
+
|
8
|
+
# A collection of constants that are shared across the library
|
9
|
+
module Strelka::Constants
|
10
|
+
|
11
|
+
# Import Mongrel2's constants, too
|
12
|
+
include Mongrel2::Constants
|
13
|
+
|
14
|
+
# Override the path to the default Sqlite configuration database
|
15
|
+
# remove_const( :DEFAULT_CONFIG_URI )
|
16
|
+
DEFAULT_CONFIG_URI = 'strelka.sqlite'
|
17
|
+
|
18
|
+
# The admin server port
|
19
|
+
DEFAULT_ADMIN_PORT = 7337
|
20
|
+
|
21
|
+
|
22
|
+
# Extend Mongrel2's HTTP constants collection
|
23
|
+
module HTTP
|
24
|
+
include Mongrel2::Constants::HTTP
|
25
|
+
|
26
|
+
# The list of valid verbs for regular HTTP
|
27
|
+
RFC2616_VERBS = %w[OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT]
|
28
|
+
|
29
|
+
# A regex for matching RFC2616 verbs
|
30
|
+
RFC2616_VERB_REGEX = Regexp.union( RFC2616_VERBS )
|
31
|
+
|
32
|
+
# The list of HTTP verbs considered "safe"
|
33
|
+
SAFE_RFC2616_VERBS = %w[GET HEAD]
|
34
|
+
|
35
|
+
# The list of HTTP verbs considered "idempotent"
|
36
|
+
IDEMPOTENT_RFC2616_VERBS = %w[OPTIONS GET HEAD PUT DELETE TRACE]
|
37
|
+
|
38
|
+
# A registry of HTTP status codes that don't allow an entity body
|
39
|
+
# in the response.
|
40
|
+
BODILESS_HTTP_RESPONSE_CODES = [
|
41
|
+
CONTINUE,
|
42
|
+
SWITCHING_PROTOCOLS,
|
43
|
+
PROCESSING,
|
44
|
+
NO_CONTENT,
|
45
|
+
RESET_CONTENT,
|
46
|
+
NOT_MODIFIED,
|
47
|
+
USE_PROXY,
|
48
|
+
]
|
49
|
+
|
50
|
+
end # module HTTP
|
51
|
+
|
52
|
+
end # module Strelka::Constants
|
53
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
require 'mongrel2/httprequest'
|
6
|
+
require 'strelka' unless defined?( Strelka )
|
7
|
+
|
8
|
+
# An HTTP request class.
|
9
|
+
class Strelka::HTTPRequest < Mongrel2::HTTPRequest
|
10
|
+
include Strelka::Loggable,
|
11
|
+
Strelka::Constants
|
12
|
+
|
13
|
+
# Set Mongrel2 to use Strelka's request class for HTTP requests
|
14
|
+
register_request_type( self, *HTTP::RFC2616_VERBS )
|
15
|
+
|
16
|
+
|
17
|
+
### Initialize some additional stuff for Strelka requests.
|
18
|
+
def initialize( * ) # :notnew:
|
19
|
+
super
|
20
|
+
@uri = nil
|
21
|
+
@verb = self.headers[:method].to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
######
|
26
|
+
public
|
27
|
+
######
|
28
|
+
|
29
|
+
# The HTTP verb of the request (as a Symbol)
|
30
|
+
attr_accessor :verb
|
31
|
+
|
32
|
+
|
33
|
+
### Return a URI object parsed from the URI of the request.
|
34
|
+
def uri
|
35
|
+
return @uri ||= URI( self.headers.uri )
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
### Return the portion of the Request's path that was routed by Mongrel2. This and the
|
40
|
+
### #app_path make up the #path.
|
41
|
+
def pattern
|
42
|
+
return self.headers.pattern
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
### Return the portion of the Request's path relative to the application's
|
47
|
+
### Mongrel2 route.
|
48
|
+
def app_path
|
49
|
+
return self.path[ self.pattern.length .. -1 ]
|
50
|
+
end
|
51
|
+
|
52
|
+
end # class Strelka::HTTPRequest
|
@@ -0,0 +1,241 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
require 'strelka' unless defined?( Strelka )
|
7
|
+
require 'strelka/mixins'
|
8
|
+
|
9
|
+
|
10
|
+
# A mixin that provides a top-level logging subsystem based on Logger.
|
11
|
+
module Strelka::Logging
|
12
|
+
|
13
|
+
### Logging
|
14
|
+
# Log levels
|
15
|
+
LOG_LEVELS = {
|
16
|
+
'debug' => Logger::DEBUG,
|
17
|
+
'info' => Logger::INFO,
|
18
|
+
'warn' => Logger::WARN,
|
19
|
+
'error' => Logger::ERROR,
|
20
|
+
'fatal' => Logger::FATAL,
|
21
|
+
}.freeze
|
22
|
+
LOG_LEVEL_NAMES = LOG_LEVELS.invert.freeze
|
23
|
+
|
24
|
+
|
25
|
+
### Inclusion hook
|
26
|
+
def self::extended( mod )
|
27
|
+
super
|
28
|
+
|
29
|
+
class << mod
|
30
|
+
# the log formatter that will be used when the logging subsystem is reset
|
31
|
+
attr_accessor :default_log_formatter
|
32
|
+
|
33
|
+
# the logger that will be used when the logging subsystem is reset
|
34
|
+
attr_accessor :default_logger
|
35
|
+
|
36
|
+
# the logger that's currently in effect
|
37
|
+
attr_accessor :logger
|
38
|
+
alias_method :log, :logger
|
39
|
+
alias_method :log=, :logger=
|
40
|
+
end
|
41
|
+
|
42
|
+
mod.default_logger = mod.logger = Logger.new( $stderr )
|
43
|
+
mod.default_logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
|
44
|
+
mod.default_log_formatter = Strelka::Logging::Formatter.new( mod.default_logger )
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
### Reset the global logger object to the default
|
49
|
+
def reset_logger
|
50
|
+
self.logger = self.default_logger
|
51
|
+
self.logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
|
52
|
+
self.logger.formatter = self.default_log_formatter
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
### Returns +true+ if the global logger has not been set to something other than
|
57
|
+
### the default one.
|
58
|
+
def using_default_logger?
|
59
|
+
return self.logger == self.default_logger
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# A alternate formatter for Logger instances.
|
64
|
+
class Formatter < Logger::Formatter
|
65
|
+
|
66
|
+
# The format to output unless debugging is turned on
|
67
|
+
DEFAULT_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"
|
68
|
+
|
69
|
+
# The format to output if debugging is turned on
|
70
|
+
DEFAULT_DEBUG_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"
|
71
|
+
|
72
|
+
|
73
|
+
### Initialize the formatter with a reference to the logger so it can check for log level.
|
74
|
+
def initialize( logger, format=DEFAULT_FORMAT, debug=DEFAULT_DEBUG_FORMAT ) # :notnew:
|
75
|
+
@logger = logger
|
76
|
+
@format = format
|
77
|
+
@debug_format = debug
|
78
|
+
|
79
|
+
super()
|
80
|
+
end
|
81
|
+
|
82
|
+
######
|
83
|
+
public
|
84
|
+
######
|
85
|
+
|
86
|
+
# The Logger object associated with the formatter
|
87
|
+
attr_accessor :logger
|
88
|
+
|
89
|
+
# The logging format string
|
90
|
+
attr_accessor :format
|
91
|
+
|
92
|
+
# The logging format string that's used when outputting in debug mode
|
93
|
+
attr_accessor :debug_format
|
94
|
+
|
95
|
+
|
96
|
+
### Log using either the DEBUG_FORMAT if the associated logger is at ::DEBUG level or
|
97
|
+
### using FORMAT if it's anything less verbose.
|
98
|
+
def call( severity, time, progname, msg )
|
99
|
+
args = [
|
100
|
+
time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
|
101
|
+
time.usec, # %2$d
|
102
|
+
Process.pid, # %3$d
|
103
|
+
Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
|
104
|
+
severity, # %5$s
|
105
|
+
progname, # %6$s
|
106
|
+
msg # %7$s
|
107
|
+
]
|
108
|
+
|
109
|
+
if @logger.level == Logger::DEBUG
|
110
|
+
return self.debug_format % args
|
111
|
+
else
|
112
|
+
return self.format % args
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end # class LogFormatter
|
116
|
+
|
117
|
+
|
118
|
+
# A ANSI-colorized formatter for Logger instances.
|
119
|
+
class ColorFormatter < Logger::Formatter
|
120
|
+
extend Strelka::ANSIColorUtilities
|
121
|
+
|
122
|
+
# Color settings
|
123
|
+
LEVEL_FORMATS = {
|
124
|
+
:debug => colorize( :bold, :black ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"},
|
125
|
+
:info => colorize( :normal ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
|
126
|
+
:warn => colorize( :bold, :yellow ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
|
127
|
+
:error => colorize( :red ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
|
128
|
+
:fatal => colorize( :bold, :red, :on_white ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
|
129
|
+
}
|
130
|
+
|
131
|
+
|
132
|
+
### Initialize the formatter with a reference to the logger so it can check for log level.
|
133
|
+
def initialize( logger, settings={} ) # :notnew:
|
134
|
+
settings = LEVEL_FORMATS.merge( settings )
|
135
|
+
|
136
|
+
@logger = logger
|
137
|
+
@settings = settings
|
138
|
+
|
139
|
+
super()
|
140
|
+
end
|
141
|
+
|
142
|
+
######
|
143
|
+
public
|
144
|
+
######
|
145
|
+
|
146
|
+
# The Logger object associated with the formatter
|
147
|
+
attr_accessor :logger
|
148
|
+
|
149
|
+
# The formats, by level
|
150
|
+
attr_accessor :settings
|
151
|
+
|
152
|
+
|
153
|
+
### Log using the format associated with the severity
|
154
|
+
def call( severity, time, progname, msg )
|
155
|
+
args = [
|
156
|
+
time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
|
157
|
+
time.usec, # %2$d
|
158
|
+
Process.pid, # %3$d
|
159
|
+
Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
|
160
|
+
severity, # %5$s
|
161
|
+
progname, # %6$s
|
162
|
+
msg # %7$s
|
163
|
+
]
|
164
|
+
|
165
|
+
return self.settings[ severity.downcase.to_sym ] % args
|
166
|
+
end
|
167
|
+
end # class LogFormatter
|
168
|
+
|
169
|
+
|
170
|
+
# An alternate formatter for Logger instances that outputs +div+ HTML
|
171
|
+
# fragments.
|
172
|
+
class HtmlFormatter < Logger::Formatter
|
173
|
+
|
174
|
+
# The default HTML fragment that'll be used as the template for each log message.
|
175
|
+
HTML_LOG_FORMAT = %q{
|
176
|
+
<div class="log-message %5$s">
|
177
|
+
<span class="log-time">%1$s.%2$06d</span>
|
178
|
+
[
|
179
|
+
<span class="log-pid">%3$d</span>
|
180
|
+
/
|
181
|
+
<span class="log-tid">%4$s</span>
|
182
|
+
]
|
183
|
+
<span class="log-level">%5$s</span>
|
184
|
+
:
|
185
|
+
<span class="log-name">%6$s</span>
|
186
|
+
<span class="log-message-text">%7$s</span>
|
187
|
+
</div>
|
188
|
+
}
|
189
|
+
|
190
|
+
### Override the logging formats with ones that generate HTML fragments
|
191
|
+
def initialize( logger, format=HTML_LOG_FORMAT ) # :notnew:
|
192
|
+
@logger = logger
|
193
|
+
@format = format
|
194
|
+
super()
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
######
|
199
|
+
public
|
200
|
+
######
|
201
|
+
|
202
|
+
# The HTML fragment that will be used as a format() string for the log
|
203
|
+
attr_accessor :format
|
204
|
+
|
205
|
+
|
206
|
+
### Return a log message composed out of the arguments formatted using the
|
207
|
+
### formatter's format string
|
208
|
+
def call( severity, time, progname, msg )
|
209
|
+
args = [
|
210
|
+
time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
|
211
|
+
time.usec, # %2$d
|
212
|
+
Process.pid, # %3$d
|
213
|
+
Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
|
214
|
+
severity.downcase, # %5$s
|
215
|
+
progname, # %6$s
|
216
|
+
html_escape( msg ).gsub(/\n/, '<br />') # %7$s
|
217
|
+
]
|
218
|
+
|
219
|
+
return self.format % args
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
#######
|
224
|
+
private
|
225
|
+
#######
|
226
|
+
|
227
|
+
### Return a copy of the specified +string+ with HTML special characters escaped as
|
228
|
+
### HTML entities.
|
229
|
+
def html_escape( string )
|
230
|
+
return string.
|
231
|
+
gsub( /&/, '&' ).
|
232
|
+
gsub( /</, '<' ).
|
233
|
+
gsub( />/, '>' )
|
234
|
+
end
|
235
|
+
|
236
|
+
end # class HtmlLogFormatter
|
237
|
+
|
238
|
+
end # module Strelka
|
239
|
+
|
240
|
+
# vim: set nosta noet ts=4 sw=4:
|
241
|
+
|
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
require 'strelka' unless defined?( Strelka )
|
6
|
+
require 'strelka/constants'
|
7
|
+
|
8
|
+
|
9
|
+
module Strelka
|
10
|
+
|
11
|
+
# Add logging to a Strelka class. Including classes get #log and #log_debug methods.
|
12
|
+
module Loggable
|
13
|
+
|
14
|
+
# A logging proxy class that wraps calls to the logger into calls that include
|
15
|
+
# the name of the calling class.
|
16
|
+
class ClassNameProxy
|
17
|
+
|
18
|
+
### Create a new proxy for the given +klass+.
|
19
|
+
def initialize( klass, force_debug=false )
|
20
|
+
@classname = klass.name
|
21
|
+
@force_debug = force_debug
|
22
|
+
end
|
23
|
+
|
24
|
+
### Delegate debug messages to the global logger with the appropriate class name.
|
25
|
+
def debug( msg=nil, &block )
|
26
|
+
Strelka.logger.add( Logger::DEBUG, msg, @classname, &block )
|
27
|
+
end
|
28
|
+
|
29
|
+
### Delegate info messages to the global logger with the appropriate class name.
|
30
|
+
def info( msg=nil, &block )
|
31
|
+
return self.debug( msg, &block ) if @force_debug
|
32
|
+
Strelka.logger.add( Logger::INFO, msg, @classname, &block )
|
33
|
+
end
|
34
|
+
|
35
|
+
### Delegate warn messages to the global logger with the appropriate class name.
|
36
|
+
def warn( msg=nil, &block )
|
37
|
+
return self.debug( msg, &block ) if @force_debug
|
38
|
+
Strelka.logger.add( Logger::WARN, msg, @classname, &block )
|
39
|
+
end
|
40
|
+
|
41
|
+
### Delegate error messages to the global logger with the appropriate class name.
|
42
|
+
def error( msg=nil, &block )
|
43
|
+
return self.debug( msg, &block ) if @force_debug
|
44
|
+
Strelka.logger.add( Logger::ERROR, msg, @classname, &block )
|
45
|
+
end
|
46
|
+
|
47
|
+
### Delegate fatal messages to the global logger with the appropriate class name.
|
48
|
+
def fatal( msg=nil, &block )
|
49
|
+
Strelka.logger.add( Logger::FATAL, msg, @classname, &block )
|
50
|
+
end
|
51
|
+
|
52
|
+
end # ClassNameProxy
|
53
|
+
|
54
|
+
#########
|
55
|
+
protected
|
56
|
+
#########
|
57
|
+
|
58
|
+
### Copy constructor -- clear the original's log proxy.
|
59
|
+
def initialize_copy( original )
|
60
|
+
@log_proxy = @log_debug_proxy = nil
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
### Return the proxied logger.
|
65
|
+
def log
|
66
|
+
@log_proxy ||= ClassNameProxy.new( self.class )
|
67
|
+
end
|
68
|
+
|
69
|
+
### Return a proxied "debug" logger that ignores other level specification.
|
70
|
+
def log_debug
|
71
|
+
@log_debug_proxy ||= ClassNameProxy.new( self.class, true )
|
72
|
+
end
|
73
|
+
|
74
|
+
end # module Loggable
|
75
|
+
|
76
|
+
# A collection of ANSI color utility functions
|
77
|
+
module ANSIColorUtilities
|
78
|
+
|
79
|
+
# Set some ANSI escape code constants (Shamelessly stolen from Perl's
|
80
|
+
# Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
|
81
|
+
ANSI_ATTRIBUTES = {
|
82
|
+
'clear' => 0,
|
83
|
+
'reset' => 0,
|
84
|
+
'bold' => 1,
|
85
|
+
'dark' => 2,
|
86
|
+
'underline' => 4,
|
87
|
+
'underscore' => 4,
|
88
|
+
'blink' => 5,
|
89
|
+
'reverse' => 7,
|
90
|
+
'concealed' => 8,
|
91
|
+
|
92
|
+
'black' => 30, 'on_black' => 40,
|
93
|
+
'red' => 31, 'on_red' => 41,
|
94
|
+
'green' => 32, 'on_green' => 42,
|
95
|
+
'yellow' => 33, 'on_yellow' => 43,
|
96
|
+
'blue' => 34, 'on_blue' => 44,
|
97
|
+
'magenta' => 35, 'on_magenta' => 45,
|
98
|
+
'cyan' => 36, 'on_cyan' => 46,
|
99
|
+
'white' => 37, 'on_white' => 47
|
100
|
+
}
|
101
|
+
|
102
|
+
###############
|
103
|
+
module_function
|
104
|
+
###############
|
105
|
+
|
106
|
+
### Create a string that contains the ANSI codes specified and return it
|
107
|
+
def ansi_code( *attributes )
|
108
|
+
attributes.flatten!
|
109
|
+
attributes.collect! {|at| at.to_s }
|
110
|
+
return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
|
111
|
+
attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
|
112
|
+
|
113
|
+
if attributes.empty?
|
114
|
+
return ''
|
115
|
+
else
|
116
|
+
return "\e[%sm" % attributes
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
### Colorize the given +string+ with the specified +attributes+ and return it, handling
|
122
|
+
### line-endings, color reset, etc.
|
123
|
+
def colorize( *args )
|
124
|
+
string = ''
|
125
|
+
|
126
|
+
if block_given?
|
127
|
+
string = yield
|
128
|
+
else
|
129
|
+
string = args.shift
|
130
|
+
end
|
131
|
+
|
132
|
+
ending = string[/(\s)$/] || ''
|
133
|
+
string = string.rstrip
|
134
|
+
|
135
|
+
return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
|
136
|
+
end
|
137
|
+
|
138
|
+
end # module ANSIColorUtilities
|
139
|
+
|
140
|
+
end # module Strelka
|
141
|
+
|
142
|
+
# vim: set nosta noet ts=4 sw=4:
|
143
|
+
|