tennpipes-base 3.6.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.rdoc +294 -0
  4. data/Rakefile +1 -0
  5. data/bin/tennpipes +8 -0
  6. data/lib/tennpipes-base.rb +196 -0
  7. data/lib/tennpipes-base/application.rb +175 -0
  8. data/lib/tennpipes-base/application/application_setup.rb +202 -0
  9. data/lib/tennpipes-base/application/authenticity_token.rb +25 -0
  10. data/lib/tennpipes-base/application/flash.rb +229 -0
  11. data/lib/tennpipes-base/application/params_protection.rb +129 -0
  12. data/lib/tennpipes-base/application/routing.rb +1002 -0
  13. data/lib/tennpipes-base/application/show_exceptions.rb +50 -0
  14. data/lib/tennpipes-base/caller.rb +53 -0
  15. data/lib/tennpipes-base/cli/adapter.rb +33 -0
  16. data/lib/tennpipes-base/cli/base.rb +105 -0
  17. data/lib/tennpipes-base/cli/console.rb +20 -0
  18. data/lib/tennpipes-base/cli/launcher.rb +103 -0
  19. data/lib/tennpipes-base/cli/rake.rb +50 -0
  20. data/lib/tennpipes-base/cli/rake_tasks.rb +72 -0
  21. data/lib/tennpipes-base/command.rb +38 -0
  22. data/lib/tennpipes-base/ext/sinatra.rb +29 -0
  23. data/lib/tennpipes-base/filter.rb +52 -0
  24. data/lib/tennpipes-base/images/404.png +0 -0
  25. data/lib/tennpipes-base/images/500.png +0 -0
  26. data/lib/tennpipes-base/loader.rb +202 -0
  27. data/lib/tennpipes-base/logger.rb +492 -0
  28. data/lib/tennpipes-base/module.rb +58 -0
  29. data/lib/tennpipes-base/mounter.rb +308 -0
  30. data/lib/tennpipes-base/path_router.rb +119 -0
  31. data/lib/tennpipes-base/path_router/compiler.rb +110 -0
  32. data/lib/tennpipes-base/path_router/error_handler.rb +8 -0
  33. data/lib/tennpipes-base/path_router/matcher.rb +123 -0
  34. data/lib/tennpipes-base/path_router/route.rb +169 -0
  35. data/lib/tennpipes-base/reloader.rb +309 -0
  36. data/lib/tennpipes-base/reloader/rack.rb +26 -0
  37. data/lib/tennpipes-base/reloader/storage.rb +55 -0
  38. data/lib/tennpipes-base/router.rb +98 -0
  39. data/lib/tennpipes-base/server.rb +119 -0
  40. data/lib/tennpipes-base/tasks.rb +21 -0
  41. data/lib/tennpipes-base/version.rb +20 -0
  42. data/lib/tennpipes-base/version.rb~ +20 -0
  43. data/test/fixtures/app_gem/Gemfile +4 -0
  44. data/test/fixtures/app_gem/app/app.rb +3 -0
  45. data/test/fixtures/app_gem/app_gem.gemspec +17 -0
  46. data/test/fixtures/app_gem/lib/app_gem.rb +7 -0
  47. data/test/fixtures/app_gem/lib/app_gem/version.rb +3 -0
  48. data/test/fixtures/apps/complex.rb +32 -0
  49. data/test/fixtures/apps/demo_app.rb +7 -0
  50. data/test/fixtures/apps/demo_demo.rb +7 -0
  51. data/test/fixtures/apps/demo_project/api/app.rb +7 -0
  52. data/test/fixtures/apps/demo_project/api/lib/api_lib.rb +3 -0
  53. data/test/fixtures/apps/demo_project/app.rb +7 -0
  54. data/test/fixtures/apps/external_apps/fake_lib.rb +1 -0
  55. data/test/fixtures/apps/external_apps/fake_root.rb +2 -0
  56. data/test/fixtures/apps/helpers/class_methods_helpers.rb +4 -0
  57. data/test/fixtures/apps/helpers/instance_methods_helpers.rb +4 -0
  58. data/test/fixtures/apps/helpers/support.rb +1 -0
  59. data/test/fixtures/apps/helpers/system_helpers.rb +8 -0
  60. data/test/fixtures/apps/kiq.rb +3 -0
  61. data/test/fixtures/apps/lib/myklass.rb +2 -0
  62. data/test/fixtures/apps/lib/myklass/mysubklass.rb +4 -0
  63. data/test/fixtures/apps/models/child.rb +2 -0
  64. data/test/fixtures/apps/models/parent.rb +5 -0
  65. data/test/fixtures/apps/mountable_apps/rack_apps.rb +15 -0
  66. data/test/fixtures/apps/mountable_apps/static.html +1 -0
  67. data/test/fixtures/apps/precompiled_app.rb +19 -0
  68. data/test/fixtures/apps/simple.rb +32 -0
  69. data/test/fixtures/apps/static.rb +10 -0
  70. data/test/fixtures/apps/system.rb +13 -0
  71. data/test/fixtures/apps/system_class_methods_demo.rb +7 -0
  72. data/test/fixtures/apps/system_instance_methods_demo.rb +7 -0
  73. data/test/fixtures/dependencies/a.rb +9 -0
  74. data/test/fixtures/dependencies/b.rb +4 -0
  75. data/test/fixtures/dependencies/c.rb +1 -0
  76. data/test/fixtures/dependencies/circular/e.rb +13 -0
  77. data/test/fixtures/dependencies/circular/f.rb +2 -0
  78. data/test/fixtures/dependencies/circular/g.rb +2 -0
  79. data/test/fixtures/dependencies/d.rb +4 -0
  80. data/test/fixtures/reloadable_apps/external/app/app.rb +6 -0
  81. data/test/fixtures/reloadable_apps/external/app/controllers/base.rb +6 -0
  82. data/test/fixtures/reloadable_apps/main/app.rb +10 -0
  83. data/test/helper.rb +30 -0
  84. data/test/test_application.rb +185 -0
  85. data/test/test_core.rb +93 -0
  86. data/test/test_csrf_protection.rb +208 -0
  87. data/test/test_dependencies.rb +57 -0
  88. data/test/test_filters.rb +389 -0
  89. data/test/test_flash.rb +168 -0
  90. data/test/test_locale.rb +21 -0
  91. data/test/test_logger.rb +295 -0
  92. data/test/test_mounter.rb +302 -0
  93. data/test/test_params_protection.rb +195 -0
  94. data/test/test_reloader_complex.rb +74 -0
  95. data/test/test_reloader_external.rb +21 -0
  96. data/test/test_reloader_simple.rb +101 -0
  97. data/test/test_reloader_system.rb +113 -0
  98. data/test/test_restful_routing.rb +33 -0
  99. data/test/test_router.rb +281 -0
  100. data/test/test_routing.rb +2328 -0
  101. 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
@@ -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