lib/racknga/exception_mail_notifier.rb in racknga-0.9.1 vs lib/racknga/exception_mail_notifier.rb in racknga-0.9.2

- old
+ new

@@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2010 Kouhei Sutou <kou@clear-code.com> +# Copyright (C) 2010-2011 Kouhei Sutou <kou@clear-code.com> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. @@ -27,53 +27,86 @@ module Racknga # Ruby 1.9 only. 1.8 isn't supported. class ExceptionMailNotifier def initialize(options) @options = Utils.normalize_options(options || {}) + reset_limitation end def notify(exception, environment) - host = @options[:host] || "localhost" return if to.empty? - mail = format(exception, environment) + + if limitation_expired? + send_summaries unless @summaries.empty? + reset_limitation + end + + if @mail_count < max_mail_count_in_limit_duration + send_notification(exception, environment) + else + @summaries << summarize(exception, environment) + end + + @mail_count += 1 + end + + private + def reset_limitation + @mail_count = 0 + @count_start_time = Time.now + @summaries = [] + end + + def limitation_expired? + (Time.now - @count_start_time) > limit_duration + end + + def send(mail) + host = @options[:host] || "localhost" Net::SMTP.start(host, @options[:port]) do |smtp| smtp.send_message(mail, from, *to) end end - private - def format(exception, environment) - header = format_header(exception, environment) - body = format_body(exception, environment) + def create_mail(options) + subject = [@options[:subject_label], options[:subject]].compact.join(' ') + header = header(:subject => subject) + + body = options[:body] + mail = "#{header}\r\n#{body}" mail.force_encoding("utf-8") begin mail = mail.encode(charset) rescue EncodingError end mail.force_encoding("ASCII-8BIT") end - def format_header(exception, environment) + def header(options) <<-EOH MIME-Version: 1.0 Content-Type: Text/Plain; charset=#{charset} Content-Transfer-Encoding: #{transfer_encoding} From: #{from} To: #{to.join(', ')} -Subject: #{encode_subject(subject(exception, environment))} +Subject: #{encode_subject(options[:subject])} Date: #{Time.now.rfc2822} EOH end - def format_body(exception, environment) + def send_notification(exception, environment) + mail = create_mail(:subject => exception.to_s, + :body => notification_body(exception, environment)) + send(mail) + end + + def notification_body(exception, environment) request = Rack::Request.new(environment) body = <<-EOB -URL: #{request.url} +#{summarize(exception, environment)} -- -#{exception.class}: #{exception} --- #{exception.backtrace.join("\n")} EOB params = request.params max_key_size = (environment.keys.collect(&:size) + params.keys.collect(&:size)).max @@ -96,14 +129,32 @@ end body end - def subject(exception, environment) - [@options[:subject_label], exception.to_s].compact.join(' ') + def send_summaries + subject = "summaries of #{@summaries.size} notifications" + mail = create_mail(:subject => subject, + :body => report_body) + send(mail) end + def report_body + @summaries[0..10].join("\n\n") + end + + def summarize(exception, environment) + request = Rack::Request.new(environment) + <<-EOB +Timestamp: #{Time.now.rfc2822} +-- +URL: #{request.url} +-- +#{exception.class}: #{exception} +EOB + end + def to @to ||= ensure_array(@options[:to]) || [] end def ensure_array(maybe_array) @@ -121,9 +172,19 @@ "#{name}@#{host}" end def charset @options[:charset] || 'utf-8' + end + + DEFAULT_MAX_MAIL_COUNT_IN_LIMIT_DURATION = 2 + def max_mail_count_in_limit_duration + @options[:max_mail_count_in_limit_duration] || DEFAULT_MAX_MAIL_COUNT_IN_LIMIT_DURATION + end + + DEFAULT_LIMIT_DURATION = 60 # one minute + def limit_duration + @options[:limit_duration] || DEFAULT_LIMIT_DURATION end def transfer_encoding case charset when /\Autf-8\z/i