tennpipes-base 3.6.6
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +294 -0
- data/Rakefile +1 -0
- data/bin/tennpipes +8 -0
- data/lib/tennpipes-base.rb +196 -0
- data/lib/tennpipes-base/application.rb +175 -0
- data/lib/tennpipes-base/application/application_setup.rb +202 -0
- data/lib/tennpipes-base/application/authenticity_token.rb +25 -0
- data/lib/tennpipes-base/application/flash.rb +229 -0
- data/lib/tennpipes-base/application/params_protection.rb +129 -0
- data/lib/tennpipes-base/application/routing.rb +1002 -0
- data/lib/tennpipes-base/application/show_exceptions.rb +50 -0
- data/lib/tennpipes-base/caller.rb +53 -0
- data/lib/tennpipes-base/cli/adapter.rb +33 -0
- data/lib/tennpipes-base/cli/base.rb +105 -0
- data/lib/tennpipes-base/cli/console.rb +20 -0
- data/lib/tennpipes-base/cli/launcher.rb +103 -0
- data/lib/tennpipes-base/cli/rake.rb +50 -0
- data/lib/tennpipes-base/cli/rake_tasks.rb +72 -0
- data/lib/tennpipes-base/command.rb +38 -0
- data/lib/tennpipes-base/ext/sinatra.rb +29 -0
- data/lib/tennpipes-base/filter.rb +52 -0
- data/lib/tennpipes-base/images/404.png +0 -0
- data/lib/tennpipes-base/images/500.png +0 -0
- data/lib/tennpipes-base/loader.rb +202 -0
- data/lib/tennpipes-base/logger.rb +492 -0
- data/lib/tennpipes-base/module.rb +58 -0
- data/lib/tennpipes-base/mounter.rb +308 -0
- data/lib/tennpipes-base/path_router.rb +119 -0
- data/lib/tennpipes-base/path_router/compiler.rb +110 -0
- data/lib/tennpipes-base/path_router/error_handler.rb +8 -0
- data/lib/tennpipes-base/path_router/matcher.rb +123 -0
- data/lib/tennpipes-base/path_router/route.rb +169 -0
- data/lib/tennpipes-base/reloader.rb +309 -0
- data/lib/tennpipes-base/reloader/rack.rb +26 -0
- data/lib/tennpipes-base/reloader/storage.rb +55 -0
- data/lib/tennpipes-base/router.rb +98 -0
- data/lib/tennpipes-base/server.rb +119 -0
- data/lib/tennpipes-base/tasks.rb +21 -0
- data/lib/tennpipes-base/version.rb +20 -0
- data/lib/tennpipes-base/version.rb~ +20 -0
- data/test/fixtures/app_gem/Gemfile +4 -0
- data/test/fixtures/app_gem/app/app.rb +3 -0
- data/test/fixtures/app_gem/app_gem.gemspec +17 -0
- data/test/fixtures/app_gem/lib/app_gem.rb +7 -0
- data/test/fixtures/app_gem/lib/app_gem/version.rb +3 -0
- data/test/fixtures/apps/complex.rb +32 -0
- data/test/fixtures/apps/demo_app.rb +7 -0
- data/test/fixtures/apps/demo_demo.rb +7 -0
- data/test/fixtures/apps/demo_project/api/app.rb +7 -0
- data/test/fixtures/apps/demo_project/api/lib/api_lib.rb +3 -0
- data/test/fixtures/apps/demo_project/app.rb +7 -0
- data/test/fixtures/apps/external_apps/fake_lib.rb +1 -0
- data/test/fixtures/apps/external_apps/fake_root.rb +2 -0
- data/test/fixtures/apps/helpers/class_methods_helpers.rb +4 -0
- data/test/fixtures/apps/helpers/instance_methods_helpers.rb +4 -0
- data/test/fixtures/apps/helpers/support.rb +1 -0
- data/test/fixtures/apps/helpers/system_helpers.rb +8 -0
- data/test/fixtures/apps/kiq.rb +3 -0
- data/test/fixtures/apps/lib/myklass.rb +2 -0
- data/test/fixtures/apps/lib/myklass/mysubklass.rb +4 -0
- data/test/fixtures/apps/models/child.rb +2 -0
- data/test/fixtures/apps/models/parent.rb +5 -0
- data/test/fixtures/apps/mountable_apps/rack_apps.rb +15 -0
- data/test/fixtures/apps/mountable_apps/static.html +1 -0
- data/test/fixtures/apps/precompiled_app.rb +19 -0
- data/test/fixtures/apps/simple.rb +32 -0
- data/test/fixtures/apps/static.rb +10 -0
- data/test/fixtures/apps/system.rb +13 -0
- data/test/fixtures/apps/system_class_methods_demo.rb +7 -0
- data/test/fixtures/apps/system_instance_methods_demo.rb +7 -0
- data/test/fixtures/dependencies/a.rb +9 -0
- data/test/fixtures/dependencies/b.rb +4 -0
- data/test/fixtures/dependencies/c.rb +1 -0
- data/test/fixtures/dependencies/circular/e.rb +13 -0
- data/test/fixtures/dependencies/circular/f.rb +2 -0
- data/test/fixtures/dependencies/circular/g.rb +2 -0
- data/test/fixtures/dependencies/d.rb +4 -0
- data/test/fixtures/reloadable_apps/external/app/app.rb +6 -0
- data/test/fixtures/reloadable_apps/external/app/controllers/base.rb +6 -0
- data/test/fixtures/reloadable_apps/main/app.rb +10 -0
- data/test/helper.rb +30 -0
- data/test/test_application.rb +185 -0
- data/test/test_core.rb +93 -0
- data/test/test_csrf_protection.rb +208 -0
- data/test/test_dependencies.rb +57 -0
- data/test/test_filters.rb +389 -0
- data/test/test_flash.rb +168 -0
- data/test/test_locale.rb +21 -0
- data/test/test_logger.rb +295 -0
- data/test/test_mounter.rb +302 -0
- data/test/test_params_protection.rb +195 -0
- data/test/test_reloader_complex.rb +74 -0
- data/test/test_reloader_external.rb +21 -0
- data/test/test_reloader_simple.rb +101 -0
- data/test/test_reloader_system.rb +113 -0
- data/test/test_restful_routing.rb +33 -0
- data/test/test_router.rb +281 -0
- data/test/test_routing.rb +2328 -0
- metadata +301 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'rbconfig'
|
|
2
|
+
|
|
3
|
+
module Tennpipes
|
|
4
|
+
##
|
|
5
|
+
# This method return the correct location of tennpipes bin or
|
|
6
|
+
# exec it using Kernel#system with the given args.
|
|
7
|
+
#
|
|
8
|
+
# @param [Array] args
|
|
9
|
+
# command or commands to execute
|
|
10
|
+
#
|
|
11
|
+
# @return [Boolean]
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# Tennpipes.bin('start', '-e production')
|
|
15
|
+
#
|
|
16
|
+
def self.bin(*args)
|
|
17
|
+
@_tennpipes_bin ||= [self.ruby_command, File.expand_path("../../../bin/tennpipes", __FILE__)]
|
|
18
|
+
args.empty? ? @_tennpipes_bin : system(args.unshift(@_tennpipes_bin).join(" "))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Return the path to the ruby interpreter taking into account multiple
|
|
23
|
+
# installations and windows extensions.
|
|
24
|
+
#
|
|
25
|
+
# @return [String]
|
|
26
|
+
# path to ruby bin executable
|
|
27
|
+
#
|
|
28
|
+
def self.ruby_command
|
|
29
|
+
@ruby_command ||= begin
|
|
30
|
+
ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
|
|
31
|
+
ruby << RbConfig::CONFIG['EXEEXT']
|
|
32
|
+
|
|
33
|
+
# escape string in case path to ruby executable contain spaces.
|
|
34
|
+
ruby.sub!(/.*\s.*/m, '"\&"')
|
|
35
|
+
ruby
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'sinatra/base'
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Adds to Sinatra +controller+ informations
|
|
5
|
+
#
|
|
6
|
+
class Sinatra::Request
|
|
7
|
+
attr_accessor :route_obj
|
|
8
|
+
|
|
9
|
+
def controller
|
|
10
|
+
route_obj && route_obj.controller
|
|
11
|
+
end
|
|
12
|
+
def action
|
|
13
|
+
route_obj && route_obj.action
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# This patches Sinatra to accept UTF-8 urls on JRuby 1.7.6
|
|
19
|
+
#
|
|
20
|
+
if RUBY_ENGINE == 'jruby' && defined?(JRUBY_VERSION) && JRUBY_VERSION > '1.7.4'
|
|
21
|
+
class Sinatra::Base
|
|
22
|
+
class << self
|
|
23
|
+
alias_method :old_generate_method, :generate_method
|
|
24
|
+
def generate_method(method_name, &block)
|
|
25
|
+
old_generate_method(method_name.to_sym, &block)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Tennpipes
|
|
2
|
+
class Filter
|
|
3
|
+
attr_reader :block
|
|
4
|
+
|
|
5
|
+
def initialize(mode, scoped_controller, options, args, &block)
|
|
6
|
+
@mode = mode
|
|
7
|
+
@scoped_controller = scoped_controller
|
|
8
|
+
@options = options
|
|
9
|
+
@args = args
|
|
10
|
+
@block = block
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def apply?(request)
|
|
14
|
+
detect = match_with_arguments?(request) || match_with_options?(request)
|
|
15
|
+
detect ^ !@mode
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_proc
|
|
19
|
+
if @args.empty? && @options.empty?
|
|
20
|
+
block
|
|
21
|
+
else
|
|
22
|
+
filter = self
|
|
23
|
+
proc { instance_eval(&filter.block) if filter.apply?(request) }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def scoped_controller_name
|
|
30
|
+
@scoped_controller_name ||= Array(@scoped_controller).join("_")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def match_with_arguments?(request)
|
|
34
|
+
route = request.route_obj
|
|
35
|
+
path = request.path_info
|
|
36
|
+
@args.any? do |argument|
|
|
37
|
+
if argument.instance_of?(Symbol)
|
|
38
|
+
next unless route
|
|
39
|
+
name = route.name
|
|
40
|
+
argument == name || name == [scoped_controller_name, argument].join(" ").to_sym
|
|
41
|
+
else
|
|
42
|
+
argument === path
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def match_with_options?(request)
|
|
48
|
+
user_agent = request.user_agent
|
|
49
|
+
@options.any?{|name, value| value === (name == :agent ? user_agent : request.send(name)) }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
module Tennpipes
|
|
2
|
+
module Loader
|
|
3
|
+
##
|
|
4
|
+
# Hooks to be called before a load/reload.
|
|
5
|
+
#
|
|
6
|
+
# @yield []
|
|
7
|
+
# The given block will be called before Tennpipes was loaded/reloaded.
|
|
8
|
+
#
|
|
9
|
+
# @return [Array<Proc>]
|
|
10
|
+
# The load/reload before hooks.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# before_load do
|
|
14
|
+
# pre_initialize_something
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
def before_load(&block)
|
|
18
|
+
@_before_load ||= []
|
|
19
|
+
@_before_load << block if block_given?
|
|
20
|
+
@_before_load
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Hooks to be called after a load/reload.
|
|
25
|
+
#
|
|
26
|
+
# @yield []
|
|
27
|
+
# The given block will be called after Tennpipes was loaded/reloaded.
|
|
28
|
+
#
|
|
29
|
+
# @return [Array<Proc>]
|
|
30
|
+
# The load/reload hooks.
|
|
31
|
+
#
|
|
32
|
+
# @example
|
|
33
|
+
# after_load do
|
|
34
|
+
# DataMapper.finalize
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
def after_load(&block)
|
|
38
|
+
@_after_load ||= []
|
|
39
|
+
@_after_load << block if block_given?
|
|
40
|
+
@_after_load
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Requires necessary dependencies as well as application files from root
|
|
45
|
+
# lib and models.
|
|
46
|
+
#
|
|
47
|
+
# @return [Boolean]
|
|
48
|
+
# returns true if Tennpipes is not already bootstraped otherwise else.
|
|
49
|
+
#
|
|
50
|
+
def load!
|
|
51
|
+
return false if loaded?
|
|
52
|
+
began_at = Time.now
|
|
53
|
+
@_called_from = first_caller
|
|
54
|
+
set_encoding
|
|
55
|
+
Tennpipes.logger
|
|
56
|
+
Reloader.lock!
|
|
57
|
+
before_load.each(&:call)
|
|
58
|
+
require_dependencies(*dependency_paths)
|
|
59
|
+
after_load.each(&:call)
|
|
60
|
+
logger.devel "Loaded Tennpipes in #{Time.now - began_at} seconds"
|
|
61
|
+
precompile_all_routes!
|
|
62
|
+
Thread.current[:tennpipes_loaded] = true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# Precompiles all routes if :precompile_routes is set to true
|
|
67
|
+
#
|
|
68
|
+
def precompile_all_routes!
|
|
69
|
+
mounted_apps.each do |app|
|
|
70
|
+
app_obj = app.app_obj
|
|
71
|
+
next unless app_obj.respond_to?(:precompile_routes?) && app_obj.precompile_routes?
|
|
72
|
+
app_obj.setup_application!
|
|
73
|
+
logger.devel "Precompiled routes of #{app_obj} (routes size #{app_obj.compiled_router.routes.size})"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# Clear the tennpipes env.
|
|
79
|
+
#
|
|
80
|
+
# @return [NilClass]
|
|
81
|
+
#
|
|
82
|
+
def clear!
|
|
83
|
+
clear_middleware!
|
|
84
|
+
mounted_apps.clear
|
|
85
|
+
@_dependency_paths = nil
|
|
86
|
+
before_load.clear
|
|
87
|
+
after_load.clear
|
|
88
|
+
global_configurations.clear
|
|
89
|
+
Reloader.clear!
|
|
90
|
+
Thread.current[:tennpipes_loaded] = nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# Method for reloading required applications and their files.
|
|
95
|
+
#
|
|
96
|
+
def reload!
|
|
97
|
+
return unless Reloader.changed?
|
|
98
|
+
before_load.each(&:call)
|
|
99
|
+
Reloader.reload!
|
|
100
|
+
after_load.each(&:call)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
# This adds the ability to instantiate {Tennpipes.load!} after
|
|
105
|
+
# {Tennpipes::Application} definition.
|
|
106
|
+
#
|
|
107
|
+
def called_from
|
|
108
|
+
@_called_from || first_caller
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
##
|
|
112
|
+
# Determines whether Tennpipes was loaded with {Tennpipes.load!}.
|
|
113
|
+
#
|
|
114
|
+
# @return [Boolean]
|
|
115
|
+
# Specifies whether Tennpipes was loaded.
|
|
116
|
+
#
|
|
117
|
+
def loaded?
|
|
118
|
+
Thread.current[:tennpipes_loaded]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
##
|
|
122
|
+
# Attempts to require all dependency libs that we need.
|
|
123
|
+
# If you use this method we can perform correctly a Tennpipes.reload!
|
|
124
|
+
# Another good thing that this method are dependency check, for example:
|
|
125
|
+
#
|
|
126
|
+
# # models
|
|
127
|
+
# # \-- a.rb => require something of b.rb
|
|
128
|
+
# # \-- b.rb
|
|
129
|
+
#
|
|
130
|
+
# In the example above if we do:
|
|
131
|
+
#
|
|
132
|
+
# Dir["/models/*.rb"].each { |r| require r }
|
|
133
|
+
#
|
|
134
|
+
# We get an error, because we try to require first +a.rb+ that need
|
|
135
|
+
# _something_ of +b.rb+.
|
|
136
|
+
#
|
|
137
|
+
# With this method we don't have this problem.
|
|
138
|
+
#
|
|
139
|
+
# @param [Array<String>] paths
|
|
140
|
+
# The paths to require.
|
|
141
|
+
#
|
|
142
|
+
# @example For require all our app libs we need to do:
|
|
143
|
+
# require_dependencies("#{Tennpipes.root}/lib/**/*.rb")
|
|
144
|
+
#
|
|
145
|
+
def require_dependencies(*paths)
|
|
146
|
+
options = paths.extract_options!.merge( :cyclic => true )
|
|
147
|
+
files = paths.flatten.flat_map{ |path| Dir.glob(path).sort_by{ |filename| filename.count('/') } }.uniq
|
|
148
|
+
|
|
149
|
+
until files.empty?
|
|
150
|
+
error = fatal = loaded = nil
|
|
151
|
+
|
|
152
|
+
files.dup.each do |file|
|
|
153
|
+
begin
|
|
154
|
+
Reloader.safe_load(file, options)
|
|
155
|
+
files.delete(file)
|
|
156
|
+
loaded = true
|
|
157
|
+
rescue NameError, LoadError => error
|
|
158
|
+
logger.devel "Cyclic dependency reload for #{error.class}: #{error.message}"
|
|
159
|
+
rescue Exception => fatal
|
|
160
|
+
break
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
if fatal || !loaded
|
|
165
|
+
exception = fatal || error
|
|
166
|
+
logger.exception exception, :short
|
|
167
|
+
raise exception
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
##
|
|
173
|
+
# Returns default list of path globs to load as dependencies.
|
|
174
|
+
# Appends custom dependency patterns to the be loaded for Tennpipes.
|
|
175
|
+
#
|
|
176
|
+
# @return [Array<String>]
|
|
177
|
+
# The dependencey paths.
|
|
178
|
+
#
|
|
179
|
+
# @example
|
|
180
|
+
# Tennpipes.dependency_paths << "#{Tennpipes.root}/uploaders/*.rb"
|
|
181
|
+
#
|
|
182
|
+
def dependency_paths
|
|
183
|
+
@_dependency_paths ||= default_dependency_paths + modules_dependency_paths
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
def modules_dependency_paths
|
|
189
|
+
modules.map(&:dependency_paths).flatten
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def default_dependency_paths
|
|
193
|
+
@default_dependency_paths ||= [
|
|
194
|
+
"#{root}/config/database.rb",
|
|
195
|
+
"#{root}/lib/**/*.rb",
|
|
196
|
+
"#{root}/models/**/*.rb",
|
|
197
|
+
"#{root}/shared/**/*.rb",
|
|
198
|
+
"#{root}/config/apps.rb",
|
|
199
|
+
]
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
|
|
3
|
+
# Defines the log level for a Tennpipes project.
|
|
4
|
+
TENNPIPES_LOG_LEVEL = ENV['TENNPIPES_LOG_LEVEL'] unless defined?(TENNPIPES_LOG_LEVEL)
|
|
5
|
+
|
|
6
|
+
# Defines the logger used for a Tennpipes project.
|
|
7
|
+
TENNPIPES_LOGGER = ENV['TENNPIPES_LOGGER'] unless defined?(TENNPIPES_LOGGER)
|
|
8
|
+
|
|
9
|
+
module Tennpipes
|
|
10
|
+
##
|
|
11
|
+
# @return [Tennpipes::Logger]
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# logger.debug "foo"
|
|
15
|
+
# logger.warn "bar"
|
|
16
|
+
#
|
|
17
|
+
def self.logger
|
|
18
|
+
Tennpipes::Logger.logger
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Set the tennpipes logger.
|
|
23
|
+
#
|
|
24
|
+
# @param [Object] value
|
|
25
|
+
# an object that respond to <<, write, puts, debug, warn etc..
|
|
26
|
+
#
|
|
27
|
+
# @return [Object]
|
|
28
|
+
# The given value.
|
|
29
|
+
#
|
|
30
|
+
# @example using ruby default logger
|
|
31
|
+
# require 'logger'
|
|
32
|
+
# Tennpipes.logger = Logger.new(STDOUT)
|
|
33
|
+
#
|
|
34
|
+
# @example using ActiveSupport
|
|
35
|
+
# require 'active_support/buffered_logger'
|
|
36
|
+
# Tennpipes.logger = Buffered.new(STDOUT)
|
|
37
|
+
#
|
|
38
|
+
def self.logger=(value)
|
|
39
|
+
Tennpipes::Logger.logger = value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# Tennpipess internal logger, using all of Tennpipes log extensions.
|
|
44
|
+
#
|
|
45
|
+
class Logger
|
|
46
|
+
##
|
|
47
|
+
# Ruby (standard) logger levels:
|
|
48
|
+
#
|
|
49
|
+
# :fatal:: An not handleable error that results in a program crash
|
|
50
|
+
# :error:: A handleable error condition
|
|
51
|
+
# :warn:: A warning
|
|
52
|
+
# :info:: generic (useful) information about system operation
|
|
53
|
+
# :debug:: low-level information for developers
|
|
54
|
+
# :devel:: Development-related information that is unnecessary in debug mode
|
|
55
|
+
#
|
|
56
|
+
Levels = {
|
|
57
|
+
:fatal => 4,
|
|
58
|
+
:error => 3,
|
|
59
|
+
:warn => 2,
|
|
60
|
+
:info => 1,
|
|
61
|
+
:debug => 0,
|
|
62
|
+
:devel => -1,
|
|
63
|
+
} unless defined?(Levels)
|
|
64
|
+
|
|
65
|
+
module Extensions
|
|
66
|
+
##
|
|
67
|
+
# Generate the logging methods for {Tennpipes.logger} for each log level.
|
|
68
|
+
#
|
|
69
|
+
Tennpipes::Logger::Levels.each_pair do |name, number|
|
|
70
|
+
define_method(name) do |*args|
|
|
71
|
+
return if number < level
|
|
72
|
+
if args.size > 1
|
|
73
|
+
bench(args[0], args[1], args[2], name)
|
|
74
|
+
else
|
|
75
|
+
if location = resolve_source_location(caller(1).shift)
|
|
76
|
+
args.prepend(location)
|
|
77
|
+
end if enable_source_location?
|
|
78
|
+
push(args * '', name)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
define_method(:"#{name}?") do
|
|
83
|
+
number >= level
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Returns true if :source_location is set to true.
|
|
91
|
+
#
|
|
92
|
+
def enable_source_location?
|
|
93
|
+
respond_to?(:source_location?) && source_location?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# Resolves a filename and line-number from caller.
|
|
98
|
+
#
|
|
99
|
+
def resolve_source_location(message)
|
|
100
|
+
path, line = *message.scan(SOURCE_LOCATION_REGEXP).first
|
|
101
|
+
return unless path && line
|
|
102
|
+
root = Tennpipes.root
|
|
103
|
+
path = File.realpath(path) if Pathname.new(path).relative?
|
|
104
|
+
if path.start_with?(root) && !path.start_with?(Tennpipes.root("vendor"))
|
|
105
|
+
"[#{path.gsub("#{root}/", "")}:#{line}] "
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# Append a to development logger a given action with time.
|
|
111
|
+
#
|
|
112
|
+
# @param [string] action
|
|
113
|
+
# The action.
|
|
114
|
+
#
|
|
115
|
+
# @param [float] time
|
|
116
|
+
# Time duration for the given action.
|
|
117
|
+
#
|
|
118
|
+
# @param [message] string
|
|
119
|
+
# The message that you want to log.
|
|
120
|
+
#
|
|
121
|
+
# @example
|
|
122
|
+
# logger.bench 'GET', started_at, '/blog/categories'
|
|
123
|
+
# # => DEBUG - GET (0.0056s) - /blog/categories
|
|
124
|
+
#
|
|
125
|
+
def bench(action, began_at, message, level=:debug, color=:yellow)
|
|
126
|
+
@_pad ||= 8
|
|
127
|
+
@_pad = action.to_s.size if action.to_s.size > @_pad
|
|
128
|
+
duration = Time.now - began_at
|
|
129
|
+
color = :red if duration > 1
|
|
130
|
+
action = colorize(action.to_s.upcase.rjust(@_pad), color)
|
|
131
|
+
duration = colorize('%0.4fs' % duration, color, :bold)
|
|
132
|
+
push "#{action} (#{duration}) #{message}", level
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
##
|
|
136
|
+
# Appends a message to the log. The methods yield to an optional block and
|
|
137
|
+
# the output of this block will be appended to the message.
|
|
138
|
+
#
|
|
139
|
+
# @param [String] message
|
|
140
|
+
# The message that you want write to your stream.
|
|
141
|
+
#
|
|
142
|
+
# @param [String] level
|
|
143
|
+
# The level one of :debug, :warn etc. ...
|
|
144
|
+
#
|
|
145
|
+
#
|
|
146
|
+
def push(message = nil, level = nil)
|
|
147
|
+
add(Tennpipes::Logger::Levels[level], format(message, level))
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
##
|
|
151
|
+
# Formats the log message. This method is a noop and should be implemented by other
|
|
152
|
+
# logger components such as {Tennpipes::Logger}.
|
|
153
|
+
#
|
|
154
|
+
# @param [String] message
|
|
155
|
+
# The message to format.
|
|
156
|
+
#
|
|
157
|
+
# @param [String,Symbol] level
|
|
158
|
+
# The log level, one of :debug, :warn ...
|
|
159
|
+
def format(message, level)
|
|
160
|
+
message
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
##
|
|
164
|
+
# The debug level, with some style added. May be reimplemented.
|
|
165
|
+
#
|
|
166
|
+
# @example
|
|
167
|
+
# stylized_level(:debug) => DEBUG
|
|
168
|
+
#
|
|
169
|
+
# @param [String,Symbol] level
|
|
170
|
+
# The log level.
|
|
171
|
+
#
|
|
172
|
+
def stylized_level(level)
|
|
173
|
+
level.to_s.upcase.rjust(7)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
##
|
|
177
|
+
# Colorizes a string for colored console output. This is a noop and can be reimplemented
|
|
178
|
+
# to colorize the string as needed.
|
|
179
|
+
#
|
|
180
|
+
# @see
|
|
181
|
+
# ColorizedLogger
|
|
182
|
+
#
|
|
183
|
+
# @param [string]
|
|
184
|
+
# The string to be colorized.
|
|
185
|
+
#
|
|
186
|
+
# @param [Array<Symbol>]
|
|
187
|
+
# The colors to use. Should be applied in the order given.
|
|
188
|
+
def colorize(string, *colors)
|
|
189
|
+
string
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
##
|
|
193
|
+
# Turns a logger with LoggingExtensions into a logger with colorized output.
|
|
194
|
+
#
|
|
195
|
+
# @example
|
|
196
|
+
# Tennpipes.logger = Logger.new($stdout)
|
|
197
|
+
# Tennpipes.logger.colorize!
|
|
198
|
+
# Tennpipes.logger.debug("Fancy Tennpipes debug string")
|
|
199
|
+
def colorize!
|
|
200
|
+
self.extend(Colorize)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
##
|
|
204
|
+
# Logs an exception.
|
|
205
|
+
#
|
|
206
|
+
# @param [Exception] exception
|
|
207
|
+
# The exception to log
|
|
208
|
+
#
|
|
209
|
+
# @param [Symbol] verbosity
|
|
210
|
+
# :short or :long, default is :long
|
|
211
|
+
#
|
|
212
|
+
# @example
|
|
213
|
+
# Tennpipes.logger.exception e
|
|
214
|
+
# Tennpipes.logger.exception(e, :short)
|
|
215
|
+
def exception(boom, verbosity = :long, level = :error)
|
|
216
|
+
return unless Levels.has_key?(level)
|
|
217
|
+
text = ["#{boom.class} - #{boom.message}:"]
|
|
218
|
+
trace = boom.backtrace
|
|
219
|
+
case verbosity
|
|
220
|
+
when :long
|
|
221
|
+
text += trace
|
|
222
|
+
when :short
|
|
223
|
+
text << trace.first
|
|
224
|
+
end if trace.kind_of?(Array)
|
|
225
|
+
send level, text.join("\n ")
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
module Colorize
|
|
230
|
+
# Colors for levels
|
|
231
|
+
ColoredLevels = {
|
|
232
|
+
:fatal => [:bold, :red],
|
|
233
|
+
:error => [:default, :red],
|
|
234
|
+
:warn => [:default, :yellow],
|
|
235
|
+
:info => [:default, :green],
|
|
236
|
+
:debug => [:default, :cyan],
|
|
237
|
+
:devel => [:default, :magenta]
|
|
238
|
+
} unless defined?(ColoredLevels)
|
|
239
|
+
|
|
240
|
+
##
|
|
241
|
+
# Colorize our level.
|
|
242
|
+
#
|
|
243
|
+
# @param [String, Symbol] level
|
|
244
|
+
#
|
|
245
|
+
# @see Tennpipes::Logging::ColorizedLogger::ColoredLevels
|
|
246
|
+
#
|
|
247
|
+
def colorize(string, *colors)
|
|
248
|
+
string.colorize(:color => colors[0], :mode => colors[1])
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def stylized_level(level)
|
|
252
|
+
style = "\e[%d;%dm" % ColoredLevels[level].map{|color| String::Colorizer.modes[color] || String::Colorizer.colors[color] }
|
|
253
|
+
[style, super, "\e[0m"] * ''
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
include Extensions
|
|
258
|
+
|
|
259
|
+
attr_accessor :auto_flush, :level, :log_static
|
|
260
|
+
attr_reader :buffer, :colorize_logging, :init_args, :log
|
|
261
|
+
|
|
262
|
+
##
|
|
263
|
+
# Configuration for a given environment, possible options are:
|
|
264
|
+
#
|
|
265
|
+
# :log_level:: Once of [:fatal, :error, :warn, :info, :debug]
|
|
266
|
+
# :stream:: Once of [:to_file, :null, :stdout, :stderr] our your custom stream
|
|
267
|
+
# :log_level::
|
|
268
|
+
# The log level from, e.g. :fatal or :info. Defaults to :warn in the
|
|
269
|
+
# production environment and :debug otherwise.
|
|
270
|
+
# :auto_flush::
|
|
271
|
+
# Whether the log should automatically flush after new messages are
|
|
272
|
+
# added. Defaults to true.
|
|
273
|
+
# :format_datetime:: Format of datetime. Defaults to: "%d/%b/%Y %H:%M:%S"
|
|
274
|
+
# :format_message:: Format of message. Defaults to: ""%s - - [%s] \"%s\"""
|
|
275
|
+
# :log_static:: Whether or not to show log messages for static files. Defaults to: false
|
|
276
|
+
# :colorize_logging:: Whether or not to colorize log messages. Defaults to: true
|
|
277
|
+
#
|
|
278
|
+
# @example
|
|
279
|
+
# Tennpipes::Logger::Config[:development] = { :log_level => :debug, :stream => :to_file }
|
|
280
|
+
# # or you can edit our defaults
|
|
281
|
+
# Tennpipes::Logger::Config[:development][:log_level] = :error
|
|
282
|
+
# # or you can use your stream
|
|
283
|
+
# Tennpipes::Logger::Config[:development][:stream] = StringIO.new
|
|
284
|
+
#
|
|
285
|
+
# Defaults are:
|
|
286
|
+
#
|
|
287
|
+
# :production => { :log_level => :warn, :stream => :to_file }
|
|
288
|
+
# :development => { :log_level => :debug, :stream => :stdout }
|
|
289
|
+
# :test => { :log_level => :fatal, :stream => :null }
|
|
290
|
+
#
|
|
291
|
+
# In some cases, configuring the loggers before loading the framework is necessary.
|
|
292
|
+
# You can do so by setting TENNPIPES_LOGGER:
|
|
293
|
+
#
|
|
294
|
+
# TENNPIPES_LOGGER = { :staging => { :log_level => :debug, :stream => :to_file }}
|
|
295
|
+
#
|
|
296
|
+
Config = {
|
|
297
|
+
:production => { :log_level => :warn, :stream => :to_file },
|
|
298
|
+
:development => { :log_level => :debug, :stream => :stdout, :format_datetime => '' },
|
|
299
|
+
:test => { :log_level => :debug, :stream => :null }
|
|
300
|
+
}
|
|
301
|
+
Config.merge!(TENNPIPES_LOGGER) if TENNPIPES_LOGGER
|
|
302
|
+
|
|
303
|
+
@@mutex = Mutex.new
|
|
304
|
+
def self.logger
|
|
305
|
+
@_logger || setup!
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def self.logger=(logger)
|
|
309
|
+
logger.extend(Tennpipes::Logger::Extensions)
|
|
310
|
+
|
|
311
|
+
@_logger = logger
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
##
|
|
315
|
+
# Setup a new logger.
|
|
316
|
+
#
|
|
317
|
+
# @return [Tennpipes::Logger]
|
|
318
|
+
# A {Tennpipes::Logger} instance
|
|
319
|
+
#
|
|
320
|
+
def self.setup!
|
|
321
|
+
self.logger = begin
|
|
322
|
+
config_level = (TENNPIPES_LOG_LEVEL || Tennpipes.env || :test).to_sym # need this for TENNPIPES_LOG_LEVEL
|
|
323
|
+
config = Config[config_level]
|
|
324
|
+
|
|
325
|
+
unless config
|
|
326
|
+
warn("No logging configuration for :#{config_level} found, falling back to :production")
|
|
327
|
+
config = Config[:production]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
stream = case config[:stream]
|
|
331
|
+
when :to_file
|
|
332
|
+
FileUtils.mkdir_p(Tennpipes.root('log')) unless File.exist?(Tennpipes.root('log'))
|
|
333
|
+
File.new(Tennpipes.root('log', "#{Tennpipes.env}.log"), 'a+')
|
|
334
|
+
when :null then StringIO.new
|
|
335
|
+
when :stdout then $stdout
|
|
336
|
+
when :stderr then $stderr
|
|
337
|
+
else config[:stream] # return itself, probabilly is a custom stream.
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
Tennpipes::Logger.new(config.merge(:stream => stream))
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
##
|
|
345
|
+
# To initialize the logger you create a new object, proxies to set_log.
|
|
346
|
+
#
|
|
347
|
+
# @param [Hash] options
|
|
348
|
+
#
|
|
349
|
+
# @option options [Symbol] :stream ($stdout)
|
|
350
|
+
# Either an IO object or a name of a logfile. Defaults to $stdout
|
|
351
|
+
#
|
|
352
|
+
# @option options [Symbol] :log_level (:production in the production environment and :debug otherwise)
|
|
353
|
+
# The log level from, e.g. :fatal or :info.
|
|
354
|
+
#
|
|
355
|
+
# @option options [Symbol] :auto_flush (true)
|
|
356
|
+
# Whether the log should automatically flush after new messages are
|
|
357
|
+
# added. Defaults to true.
|
|
358
|
+
#
|
|
359
|
+
# @option options [Symbol] :format_datetime (" [%d/%b/%Y %H:%M:%S] ")
|
|
360
|
+
# Format of datetime.
|
|
361
|
+
#
|
|
362
|
+
# @option options [Symbol] :format_message ("%s -%s%s")
|
|
363
|
+
# Format of message.
|
|
364
|
+
#
|
|
365
|
+
# @option options [Symbol] :log_static (false)
|
|
366
|
+
# Whether or not to show log messages for static files.
|
|
367
|
+
#
|
|
368
|
+
# @option options [Symbol] :colorize_logging (true)
|
|
369
|
+
# Whether or not to colorize log messages. Defaults to: true.
|
|
370
|
+
#
|
|
371
|
+
def initialize(options={})
|
|
372
|
+
@buffer = []
|
|
373
|
+
@auto_flush = options.has_key?(:auto_flush) ? options[:auto_flush] : true
|
|
374
|
+
@level = options[:log_level] ? Tennpipes::Logger::Levels[options[:log_level]] : Tennpipes::Logger::Levels[:debug]
|
|
375
|
+
@log = options[:stream] || $stdout
|
|
376
|
+
@log.sync = true
|
|
377
|
+
@format_datetime = options[:format_datetime] || "%d/%b/%Y %H:%M:%S"
|
|
378
|
+
@format_message = options[:format_message] || "%s - %s %s"
|
|
379
|
+
@log_static = options.has_key?(:log_static) ? options[:log_static] : false
|
|
380
|
+
@colorize_logging = options.has_key?(:colorize_logging) ? options[:colorize_logging] : true
|
|
381
|
+
@source_location = options[:source_location]
|
|
382
|
+
colorize! if @colorize_logging
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def source_location?
|
|
386
|
+
!!@source_location
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
##
|
|
390
|
+
# Flush the entire buffer to the log object.
|
|
391
|
+
#
|
|
392
|
+
def flush
|
|
393
|
+
return unless @buffer.size > 0
|
|
394
|
+
@@mutex.synchronize do
|
|
395
|
+
@log.write(@buffer.join(''))
|
|
396
|
+
@buffer.clear
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
##
|
|
401
|
+
# Close and remove the current log object.
|
|
402
|
+
#
|
|
403
|
+
# @return [NilClass]
|
|
404
|
+
#
|
|
405
|
+
def close
|
|
406
|
+
flush
|
|
407
|
+
@log.close if @log.respond_to?(:close) && !@log.tty?
|
|
408
|
+
@log = nil
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
##
|
|
412
|
+
# Adds a message to the log - for compatibility with other loggers.
|
|
413
|
+
#
|
|
414
|
+
def add(level, message = nil)
|
|
415
|
+
write(message)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
##
|
|
419
|
+
# Directly append message to the log.
|
|
420
|
+
#
|
|
421
|
+
# @param [String] message
|
|
422
|
+
# The message
|
|
423
|
+
#
|
|
424
|
+
def <<(message = nil)
|
|
425
|
+
message << "\n" unless message[-1] == ?\n
|
|
426
|
+
@@mutex.synchronize {
|
|
427
|
+
@buffer << message
|
|
428
|
+
}
|
|
429
|
+
flush if @auto_flush
|
|
430
|
+
message
|
|
431
|
+
end
|
|
432
|
+
alias :write :<<
|
|
433
|
+
|
|
434
|
+
def format(message, level)
|
|
435
|
+
@format_message % [stylized_level(level), colorize(Time.now.strftime(@format_datetime), :yellow), message.to_s.strip]
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
##
|
|
439
|
+
# Tennpipes::Logger::Rack forwards every request to an +app+ given, and
|
|
440
|
+
# logs a line in the Apache common log format to the +logger+, or
|
|
441
|
+
# rack.errors by default.
|
|
442
|
+
#
|
|
443
|
+
class Rack
|
|
444
|
+
def initialize(app, uri_root)
|
|
445
|
+
@app = app
|
|
446
|
+
@uri_root = uri_root.sub(/\/$/,"")
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def call(env)
|
|
450
|
+
env['rack.logger'] = Tennpipes.logger
|
|
451
|
+
began_at = Time.now
|
|
452
|
+
status, header, body = @app.call(env)
|
|
453
|
+
log(env, status, header, began_at) if logger.debug?
|
|
454
|
+
[status, header, body]
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
private
|
|
458
|
+
|
|
459
|
+
def log(env, status, header, began_at)
|
|
460
|
+
return if env['sinatra.static_file'] && (!logger.respond_to?(:log_static) || !logger.log_static)
|
|
461
|
+
logger.bench(
|
|
462
|
+
env["REQUEST_METHOD"],
|
|
463
|
+
began_at,
|
|
464
|
+
[
|
|
465
|
+
@uri_root.to_s,
|
|
466
|
+
env["PATH_INFO"],
|
|
467
|
+
env["QUERY_STRING"].empty? ? "" : "?" + env["QUERY_STRING"],
|
|
468
|
+
' - ',
|
|
469
|
+
logger.colorize(status.to_s[0..3], :default, :bold),
|
|
470
|
+
' ',
|
|
471
|
+
code_to_name(status)
|
|
472
|
+
] * '',
|
|
473
|
+
:debug,
|
|
474
|
+
:magenta
|
|
475
|
+
)
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def code_to_name(status)
|
|
479
|
+
::Rack::Utils::HTTP_STATUS_CODES[status.to_i] || ''
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
module Kernel
|
|
486
|
+
##
|
|
487
|
+
# Define a logger available every where in our app
|
|
488
|
+
#
|
|
489
|
+
def logger
|
|
490
|
+
Tennpipes.logger
|
|
491
|
+
end
|
|
492
|
+
end
|