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.
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