strelka 0.0.1pre4
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.
- 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
|
+
|