tennpipes-base 3.6.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|