PageRenderTime 1605ms CodeModel.GetById 37ms RepoModel.GetById 1ms app.codeStats 0ms

/padrino-core/lib/padrino-core/logger.rb

https://github.com/donfrancisco/padrino-framework
Ruby | 347 lines | 177 code | 31 blank | 139 comment | 25 complexity | eb7a19ccbfb6510eb207076ee11e8250 MD5 | raw file
  1. # Defines our PADRINO_LOGGER constants
  2. PADRINO_LOG_LEVEL = ENV['PADRINO_LOG_LEVEL'] unless defined?(PADRINO_LOG_LEVEL)
  3. PADRINO_LOGGER = ENV['PADRINO_LOGGER'] unless defined?(PADRINO_LOGGER)
  4. module Padrino
  5. ##
  6. # Returns the padrino logger
  7. #
  8. # ==== Examples
  9. #
  10. # logger.debug "foo"
  11. # logger.warn "bar"
  12. #
  13. def self.logger
  14. Padrino::Logger.setup! if Thread.current[:padrino_logger].nil?
  15. Thread.current[:padrino_logger]
  16. end
  17. ##
  18. # Set the padrino logger
  19. #
  20. def self.logger=(value)
  21. Thread.current[:padrino_logger] = value
  22. end
  23. ##
  24. # Extensions to the built in Ruby logger.
  25. #
  26. # ==== Examples
  27. #
  28. # logger.debug "foo"
  29. # logger.warn "bar"
  30. #
  31. class Logger
  32. attr_accessor :level
  33. attr_accessor :auto_flush
  34. attr_reader :buffer
  35. attr_reader :log
  36. attr_reader :init_args
  37. attr_accessor :log_static
  38. ##
  39. # Ruby (standard) logger levels:
  40. #
  41. # :fatal:: An unhandleable error that results in a program crash
  42. # :error:: A handleable error condition
  43. # :warn:: A warning
  44. # :info:: generic (useful) information about system operation
  45. # :debug:: low-level information for developers
  46. #
  47. Levels = {
  48. :fatal => 7,
  49. :error => 6,
  50. :warn => 4,
  51. :info => 3,
  52. :debug => 0,
  53. :devel => -1,
  54. } unless const_defined?(:Levels)
  55. @@mutex = {}
  56. ##
  57. # Configuration for a given environment, possible options are:
  58. #
  59. # :log_level:: Once of [:fatal, :error, :warn, :info, :debug]
  60. # :stream:: Once of [:to_file, :null, :stdout, :stderr] our your custom stream
  61. # :log_level::
  62. # The log level from, e.g. :fatal or :info. Defaults to :warn in the
  63. # production environment and :debug otherwise.
  64. # :auto_flush::
  65. # Whether the log should automatically flush after new messages are
  66. # added. Defaults to true.
  67. # :format_datetime:: Format of datetime. Defaults to: "%d/%b/%Y %H:%M:%S"
  68. # :format_message:: Format of message. Defaults to: ""%s - - [%s] \"%s\"""
  69. # :log_static:: Whether or not to show log messages for static files. Defaults to: false
  70. #
  71. # ==== Examples
  72. #
  73. # Padrino::Logger::Config[:development] = { :log_level => :debug, :stream => :to_file }
  74. # # or you can edit our defaults
  75. # Padrino::Logger::Config[:development][:log_level] = :error
  76. # # or you can use your stream
  77. # Padrino::Logger::Config[:development][:stream] = StringIO.new
  78. #
  79. # Defaults are:
  80. #
  81. # :production => { :log_level => :warn, :stream => :to_file }
  82. # :development => { :log_level => :debug, :stream => :stdout }
  83. # :test => { :log_level => :fatal, :stream => :null }
  84. #
  85. # In some cases, configuring the loggers before loading the framework is necessary.
  86. # You can do so by setting PADRINO_LOGGER:
  87. #
  88. # PADRINO_LOGGER = { :staging => { :log_level => :debug, :stream => :to_file }}
  89. #
  90. Config = {
  91. :production => { :log_level => :warn, :stream => :to_file },
  92. :development => { :log_level => :debug, :stream => :stdout },
  93. :test => { :log_level => :debug, :stream => :null }
  94. }
  95. Config.merge!(PADRINO_LOGGER) if PADRINO_LOGGER
  96. # Embed in a String to clear all previous ANSI sequences.
  97. CLEAR = "\e[0m"
  98. BOLD = "\e[1m"
  99. BLACK = "\e[30m"
  100. RED = "\e[31m"
  101. GREEN = "\e[32m"
  102. YELLOW = "\e[33m"
  103. BLUE = "\e[34m"
  104. MAGENTA = "\e[35m"
  105. CYAN = "\e[36m"
  106. WHITE = "\e[37m"
  107. # Colors for levels
  108. ColoredLevels = {
  109. :fatal => [BOLD, RED],
  110. :error => [RED],
  111. :warn => [YELLOW],
  112. :info => [GREEN],
  113. :debug => [CYAN],
  114. :devel => [MAGENTA]
  115. } unless defined?(ColoredLevels)
  116. ##
  117. # Setup a new logger
  118. #
  119. def self.setup!
  120. config_level = (PADRINO_LOG_LEVEL || Padrino.env || :test).to_sym # need this for PADRINO_LOG_LEVEL
  121. config = Config[config_level]
  122. unless config
  123. warn("No logging configuration for :#{config_level} found, falling back to :production")
  124. config = Config[:production]
  125. end
  126. stream = case config[:stream]
  127. when :to_file
  128. FileUtils.mkdir_p(Padrino.root("log")) unless File.exists?(Padrino.root("log"))
  129. File.new(Padrino.root("log", "#{Padrino.env}.log"), "a+")
  130. when :null then StringIO.new
  131. when :stdout then $stdout
  132. when :stderr then $stderr
  133. else config[:stream] # return itself, probabilly is a custom stream.
  134. end
  135. Thread.current[:padrino_logger] = Padrino::Logger.new(config.merge(:stream => stream))
  136. end
  137. ##
  138. # To initialize the logger you create a new object, proxies to set_log.
  139. #
  140. # ==== Options
  141. #
  142. # :stream:: Either an IO object or a name of a logfile. Defaults to $stdout
  143. # :log_level::
  144. # The log level from, e.g. :fatal or :info. Defaults to :debug in the
  145. # production environment and :debug otherwise.
  146. # :auto_flush::
  147. # Whether the log should automatically flush after new messages are
  148. # added. Defaults to true.
  149. # :format_datetime:: Format of datetime. Defaults to: "%d/%b/%Y %H:%M:%S"
  150. # :format_message:: Format of message. Defaults to: ""%s - - [%s] \"%s\"""
  151. # :log_static:: Whether or not to show log messages for static files. Defaults to: false
  152. #
  153. def initialize(options={})
  154. @buffer = []
  155. @auto_flush = options.has_key?(:auto_flush) ? options[:auto_flush] : true
  156. @level = options[:log_level] ? Levels[options[:log_level]] : Levels[:debug]
  157. @log = options[:stream] || $stdout
  158. @log.sync = true
  159. @mutex = @@mutex[@log] ||= Mutex.new
  160. @format_datetime = options[:format_datetime] || "%d/%b/%Y %H:%M:%S"
  161. @format_message = options[:format_message] || "%s - [%s] \"%s\""
  162. @log_static = options.has_key?(:log_static) ? options[:log_static] : false
  163. end
  164. ##
  165. # Colorize our level
  166. #
  167. def colored_level(level)
  168. style = ColoredLevels[level.to_s.downcase.to_sym].join("")
  169. "#{style}#{level.to_s.upcase.rjust(7)}#{CLEAR}"
  170. end
  171. ##
  172. # Set a color for our string. Color can be a symbol/string
  173. #
  174. def set_color(string, color, bold=false)
  175. color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
  176. bold = bold ? BOLD : ""
  177. "#{bold}#{color}#{string}#{CLEAR}"
  178. end
  179. ##
  180. # Flush the entire buffer to the log object.
  181. #
  182. def flush
  183. return unless @buffer.size > 0
  184. @mutex.synchronize do
  185. @log.write(@buffer.slice!(0..-1).join(''))
  186. end
  187. end
  188. ##
  189. # Close and remove the current log object.
  190. #
  191. def close
  192. flush
  193. @log.close if @log.respond_to?(:close) && !@log.tty?
  194. @log = nil
  195. end
  196. ##
  197. # Appends a message to the log. The methods yield to an optional block and
  198. # the output of this block will be appended to the message.
  199. #
  200. def push(message = nil, level = nil)
  201. self << @format_message % [colored_level(level), set_color(Time.now.strftime(@format_datetime), :yellow), message.to_s.strip]
  202. end
  203. ##
  204. # Directly append message to the log.
  205. #
  206. def <<(message = nil)
  207. message << "\n" unless message[-1] == ?\n
  208. @buffer << message
  209. flush if @auto_flush
  210. message
  211. end
  212. alias :write :<<
  213. ##
  214. # Generate the logging methods for Padrino.logger for each log level.
  215. #
  216. Levels.each_pair do |name, number|
  217. class_eval <<-LEVELMETHODS, __FILE__, __LINE__
  218. # Appends a message to the log if the log level is at least as high as
  219. # the log level of the logger.
  220. #
  221. # ==== Parameters
  222. # message:: The message to be logged. Defaults to nil.
  223. #
  224. # ==== Returns
  225. # self:: The logger object for chaining.
  226. def #{name}(message = nil)
  227. if #{number} >= level
  228. message = block_given? ? yield : message
  229. self.push(message, :#{name}) if #{number} >= level
  230. end
  231. self
  232. end
  233. # Appends a message to the log if the log level is at least as high as
  234. # the log level of the logger. The bang! version of the method also auto
  235. # flushes the log buffer to disk.
  236. #
  237. # ==== Parameters
  238. # message:: The message to be logged. Defaults to nil.
  239. #
  240. # ==== Returns
  241. # self:: The logger object for chaining.
  242. def #{name}!(message = nil)
  243. if #{number} >= level
  244. message = block_given? ? yield : message
  245. self.push(message, :#{name}) if #{number} >= level
  246. flush if #{number} >= level
  247. end
  248. self
  249. end
  250. # ==== Returns
  251. # Boolean:: True if this level will be logged by this logger.
  252. def #{name}?
  253. #{number} >= level
  254. end
  255. LEVELMETHODS
  256. end
  257. ##
  258. # Padrino::Loggger::Rack forwards every request to an +app+ given, and
  259. # logs a line in the Apache common log format to the +logger+, or
  260. # rack.errors by default.
  261. #
  262. class Rack
  263. ##
  264. # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
  265. # "lilith.local - - GET / HTTP/1.1 500 -"
  266. # %{%s - %s %s %s%s %s - %d %s %0.4f}
  267. #
  268. FORMAT = %{%s (%0.4fms) %s - %s %s%s%s %s - %d %s}
  269. def initialize(app, uri_root)
  270. @app = app
  271. @uri_root = uri_root.sub(/\/$/,"")
  272. end
  273. def call(env)
  274. env['rack.logger'] = Padrino.logger
  275. env['rack.errors'] = Padrino.logger.log
  276. began_at = Time.now
  277. status, header, body = @app.call(env)
  278. log(env, status, header, began_at)
  279. [status, header, body]
  280. end
  281. private
  282. def log(env, status, header, began_at)
  283. now = Time.now
  284. length = extract_content_length(header)
  285. return if env['sinatra.static_file'] and !logger.log_static
  286. logger.debug FORMAT % [
  287. env["REQUEST_METHOD"],
  288. now - began_at,
  289. env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
  290. env["REMOTE_USER"] || "-",
  291. @uri_root || "",
  292. env["PATH_INFO"],
  293. env["QUERY_STRING"].empty? ? "" : "?" + env["QUERY_STRING"],
  294. env["HTTP_VERSION"],
  295. status.to_s[0..3],
  296. length]
  297. end
  298. def extract_content_length(headers)
  299. headers.each do |key, value|
  300. if key.downcase == 'content-length'
  301. return value.to_s == '0' ? '-' : value
  302. end
  303. end
  304. '-'
  305. end
  306. end # Rack
  307. end # Logger
  308. end # Padrino
  309. module Kernel # @private
  310. ##
  311. # Define a logger available every where in our app
  312. #
  313. def logger
  314. Padrino.logger
  315. end
  316. end # Kernel