exceptify 1.0.0

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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +16 -0
  3. data/CODE_OF_CONDUCT.md +22 -0
  4. data/CONTRIBUTING.md +33 -0
  5. data/MIT-LICENSE +23 -0
  6. data/README.md +534 -0
  7. data/RELEASING.md +51 -0
  8. data/Rakefile +25 -0
  9. data/docs/notifiers/custom.md +42 -0
  10. data/docs/notifiers/datadog.md +51 -0
  11. data/docs/notifiers/email.md +195 -0
  12. data/docs/notifiers/slack.md +154 -0
  13. data/docs/notifiers/sns.md +37 -0
  14. data/docs/notifiers/teams.md +54 -0
  15. data/docs/notifiers/webhook.md +60 -0
  16. data/exceptify.gemspec +48 -0
  17. data/lib/exceptify/base_notifier.rb +30 -0
  18. data/lib/exceptify/configuration.rb +184 -0
  19. data/lib/exceptify/datadog_notifier.rb +160 -0
  20. data/lib/exceptify/dispatcher.rb +49 -0
  21. data/lib/exceptify/email_notifier.rb +208 -0
  22. data/lib/exceptify/modules/backtrace_cleaner.rb +13 -0
  23. data/lib/exceptify/modules/error_grouping.rb +170 -0
  24. data/lib/exceptify/modules/formatter.rb +119 -0
  25. data/lib/exceptify/notification.rb +71 -0
  26. data/lib/exceptify/notifier.rb +19 -0
  27. data/lib/exceptify/notifier_registry.rb +55 -0
  28. data/lib/exceptify/rack.rb +88 -0
  29. data/lib/exceptify/rails/runner_tie.rb +57 -0
  30. data/lib/exceptify/rails.rb +29 -0
  31. data/lib/exceptify/rake.rb +59 -0
  32. data/lib/exceptify/request_context.rb +35 -0
  33. data/lib/exceptify/resque.rb +25 -0
  34. data/lib/exceptify/sidekiq.rb +15 -0
  35. data/lib/exceptify/slack_notifier.rb +141 -0
  36. data/lib/exceptify/sns_notifier.rb +98 -0
  37. data/lib/exceptify/solid_queue.rb +68 -0
  38. data/lib/exceptify/teams_notifier.rb +209 -0
  39. data/lib/exceptify/version.rb +5 -0
  40. data/lib/exceptify/views/exceptify/_backtrace.html.erb +3 -0
  41. data/lib/exceptify/views/exceptify/_backtrace.text.erb +1 -0
  42. data/lib/exceptify/views/exceptify/_data.html.erb +6 -0
  43. data/lib/exceptify/views/exceptify/_data.text.erb +1 -0
  44. data/lib/exceptify/views/exceptify/_environment.html.erb +10 -0
  45. data/lib/exceptify/views/exceptify/_environment.text.erb +5 -0
  46. data/lib/exceptify/views/exceptify/_request.html.erb +36 -0
  47. data/lib/exceptify/views/exceptify/_request.text.erb +10 -0
  48. data/lib/exceptify/views/exceptify/_session.html.erb +10 -0
  49. data/lib/exceptify/views/exceptify/_session.text.erb +2 -0
  50. data/lib/exceptify/views/exceptify/_title.html.erb +3 -0
  51. data/lib/exceptify/views/exceptify/_title.text.erb +3 -0
  52. data/lib/exceptify/views/exceptify/background_exceptify.html.erb +53 -0
  53. data/lib/exceptify/views/exceptify/background_exceptify.text.erb +14 -0
  54. data/lib/exceptify/views/exceptify/exceptify.html.erb +52 -0
  55. data/lib/exceptify/views/exceptify/exceptify.text.erb +24 -0
  56. data/lib/exceptify/webhook_notifier.rb +63 -0
  57. data/lib/exceptify.rb +177 -0
  58. data/lib/generators/exceptify/install_generator.rb +24 -0
  59. data/lib/generators/exceptify/templates/exceptify.rb.erb +44 -0
  60. metadata +364 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resque/failure/base"
4
+ require "exceptify"
5
+
6
+ module Exceptify
7
+ class Resque < Resque::Failure::Base
8
+ def self.count
9
+ ::Resque::Stat[:failed]
10
+ end
11
+
12
+ def save
13
+ data = {
14
+ error_class: exception.class.name,
15
+ error_message: exception.message,
16
+ failed_at: Time.now.to_s,
17
+ payload: payload,
18
+ queue: queue,
19
+ worker: worker.to_s
20
+ }
21
+
22
+ Exceptify.notify_exception(exception, data: {resque: data})
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq"
4
+ require "exceptify"
5
+
6
+ ::Sidekiq.configure_server do |config|
7
+ config.error_handlers << proc do |ex, context, config|
8
+ # Before Sidekiq 7.1.5 the config was not passed to the proc
9
+ if config
10
+ Exceptify.notify_exception(ex, data: {sidekiq: {context: context, config: config}})
11
+ else
12
+ Exceptify.notify_exception(ex, data: {sidekiq: context})
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exceptify
4
+ class SlackNotifier < BaseNotifier
5
+ include Exceptify::BacktraceCleaner
6
+
7
+ attr_accessor :notifier
8
+
9
+ def initialize(options)
10
+ options = options.dup
11
+ fail_silently = options.delete(:fail_silently) { false }
12
+ injected_notifier = options.delete(:notifier)
13
+ super()
14
+ self.base_options = options
15
+
16
+ @ignore_data_if = options[:ignore_data_if]
17
+ @backtrace_lines = options.fetch(:backtrace_lines, 10)
18
+ @additional_fields = options[:additional_fields]
19
+ @message_opts = options.fetch(:additional_parameters, {}).dup
20
+ @color = @message_opts.delete(:color) { "danger" }
21
+
22
+ @notifier = injected_notifier || build_notifier(options)
23
+ rescue => e
24
+ raise unless fail_silently
25
+
26
+ log_configuration_error(e)
27
+ @notifier = nil
28
+ end
29
+
30
+ def call(exception, options = {})
31
+ notification = Notification.new(exception, options, backtrace_cleaner: self)
32
+ clean_message = exception.message.tr("`", "'")
33
+ attchs = attchs(notification, clean_message)
34
+
35
+ return unless valid?
36
+
37
+ args = [exception, options, clean_message, @message_opts.merge(attachments: attchs)]
38
+ send_notice(*args) do |_msg, message_opts|
39
+ message_opts[:channel] = options[:channel] if options.key?(:channel)
40
+
41
+ @notifier.ping "", message_opts
42
+ end
43
+ end
44
+
45
+ protected
46
+
47
+ def valid?
48
+ !@notifier.nil?
49
+ end
50
+
51
+ def deep_reject(hash, block)
52
+ hash.each do |k, v|
53
+ deep_reject(v, block) if v.is_a?(Hash)
54
+
55
+ hash.delete(k) if block.call(k, v)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def build_notifier(options)
62
+ webhook_url = options[:webhook_url]
63
+ raise ArgumentError, "You must provide 'webhook_url' option" if blank?(webhook_url)
64
+ unless defined?(::Slack::Notifier)
65
+ raise ArgumentError, "Slack notifier requires the 'slack-notifier' gem"
66
+ end
67
+
68
+ Slack::Notifier.new(webhook_url, options)
69
+ end
70
+
71
+ def blank?(value)
72
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
73
+ end
74
+
75
+ def log_configuration_error(error)
76
+ Exceptify.logger&.warn(
77
+ "Slack notifier disabled: #{error.class}: #{error.message}"
78
+ )
79
+ end
80
+
81
+ def attchs(notification, clean_message)
82
+ text, data = information_from_notification(notification)
83
+ backtrace = notification.backtrace
84
+ fields = fields(notification, clean_message, backtrace, data)
85
+
86
+ [color: @color, text: text, fields: fields, mrkdwn_in: %w[text fields]]
87
+ end
88
+
89
+ def information_from_notification(notification)
90
+ errors_count = notification.options[:accumulated_errors_count].to_i
91
+ exception_class = notification.exception.class
92
+
93
+ measure_word = if errors_count > 1
94
+ errors_count
95
+ else
96
+ /^[aeiou]/i.match?(exception_class.to_s) ? "An" : "A"
97
+ end
98
+
99
+ exception_name = "*#{measure_word}* `#{exception_class}`"
100
+ env = notification.env
101
+ data = notification.data
102
+
103
+ notification.options[:headers] ||= {}
104
+ notification.options[:headers]["Content-Type"] = "application/json"
105
+
106
+ if env.nil?
107
+ text = "#{exception_name} *occured in background*\n"
108
+ else
109
+ kontroller = env["action_controller.instance"]
110
+ request = "#{env["REQUEST_METHOD"]} <#{env["REQUEST_URI"]}>"
111
+ text = "#{exception_name} *occurred while* `#{request}`"
112
+ text += " *was processed by* `#{kontroller.controller_name}##{kontroller.action_name}`" if kontroller
113
+ text += "\n"
114
+ end
115
+
116
+ [text, data]
117
+ end
118
+
119
+ def fields(notification, clean_message, backtrace, data)
120
+ fields = [
121
+ {title: "Exception", value: clean_message},
122
+ {title: "Hostname", value: notification.hostname}
123
+ ]
124
+
125
+ unless backtrace.empty?
126
+ formatted_backtrace = "```#{backtrace.first(@backtrace_lines).join("\n")}```"
127
+ fields << {title: "Backtrace", value: formatted_backtrace}
128
+ end
129
+
130
+ unless data.empty?
131
+ deep_reject(data, @ignore_data_if) if @ignore_data_if.is_a?(Proc)
132
+ data_string = data.map { |k, v| "#{k}: #{v}" }.join("\n")
133
+ fields << {title: "Data", value: "```#{data_string}```"}
134
+ end
135
+
136
+ fields.concat(@additional_fields) if @additional_fields
137
+
138
+ fields
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exceptify
4
+ class SnsNotifier < BaseNotifier
5
+ def initialize(options)
6
+ options = options.dup
7
+ super
8
+
9
+ @notifier = options.delete(:client) || build_client(options)
10
+ @options = default_options.merge(options)
11
+ end
12
+
13
+ def call(exception, custom_opts = {})
14
+ custom_options = options.merge(custom_opts)
15
+ notification = Notification.new(exception, custom_options)
16
+
17
+ subject = build_subject(notification, custom_options)
18
+ message = build_message(notification, custom_options)
19
+
20
+ notifier.publish(
21
+ topic_arn: custom_options[:topic_arn],
22
+ message: message,
23
+ subject: subject
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :notifier, :options
30
+
31
+ def build_client(options)
32
+ raise ArgumentError, "You must provide 'region' option" unless options[:region]
33
+ raise ArgumentError, "You must provide 'access_key_id' option" unless options[:access_key_id]
34
+ raise ArgumentError, "You must provide 'secret_access_key' option" unless options[:secret_access_key]
35
+ raise ArgumentError, "SNS notifier requires the 'aws-sdk-sns' gem" unless defined?(::Aws::SNS::Client)
36
+
37
+ Aws::SNS::Client.new(
38
+ region: options[:region],
39
+ access_key_id: options[:access_key_id],
40
+ secret_access_key: options[:secret_access_key]
41
+ )
42
+ end
43
+
44
+ def build_subject(notification, options)
45
+ subject =
46
+ "#{options[:sns_prefix]} - #{accumulated_exception_name(notification, options)} occurred"
47
+ (subject.length > 120) ? subject[0...120] + "..." : subject
48
+ end
49
+
50
+ def build_message(notification, options)
51
+ exception = notification.exception
52
+ exception_name = accumulated_exception_name(notification, options)
53
+
54
+ if notification.env.nil?
55
+ text = "#{exception_name} occured in background\n"
56
+ data = notification.data
57
+ else
58
+ env = notification.env
59
+
60
+ kontroller = env["action_controller.instance"]
61
+ data = notification.data
62
+ request = "#{env["REQUEST_METHOD"]} <#{env["REQUEST_URI"]}>"
63
+
64
+ text = "#{exception_name} occurred while #{request}"
65
+ text += " was processed by #{kontroller.controller_name}##{kontroller.action_name}\n" if kontroller
66
+ end
67
+
68
+ text += "Exception: #{exception.message}\n"
69
+ text += "Hostname: #{notification.hostname}\n"
70
+ text += "Data: #{data}\n"
71
+
72
+ return text if notification.backtrace.empty?
73
+
74
+ formatted_backtrace = notification.backtrace.first(options[:backtrace_lines]).join("\n").to_s
75
+ text + "Backtrace:\n#{formatted_backtrace}\n"
76
+ end
77
+
78
+ def accumulated_exception_name(notification, options)
79
+ errors_count = options[:accumulated_errors_count].to_i
80
+ exception = notification.exception
81
+
82
+ measure_word = if errors_count > 1
83
+ errors_count
84
+ else
85
+ /^[aeiou]/i.match?(exception.class.to_s) ? "An" : "A"
86
+ end
87
+
88
+ "#{measure_word} #{exception.class}"
89
+ end
90
+
91
+ def default_options
92
+ {
93
+ sns_prefix: "[ERROR]",
94
+ backtrace_lines: 10
95
+ }
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job"
4
+ require "active_support/notifications"
5
+ require "exceptify"
6
+
7
+ module Exceptify
8
+ module SolidQueue
9
+ ADAPTER_NAME = "solid_queue"
10
+ EVENT_NAME = "perform.active_job"
11
+ JOB_ATTRIBUTES = %i[
12
+ job_id
13
+ provider_job_id
14
+ queue_name
15
+ priority
16
+ arguments
17
+ executions
18
+ exception_executions
19
+ locale
20
+ timezone
21
+ enqueued_at
22
+ scheduled_at
23
+ ].freeze
24
+
25
+ class << self
26
+ def install
27
+ return if installed?
28
+
29
+ @subscription = ActiveSupport::Notifications.subscribe(EVENT_NAME) do |*args|
30
+ notify(ActiveSupport::Notifications::Event.new(*args))
31
+ end
32
+ end
33
+
34
+ def installed?
35
+ !!@subscription
36
+ end
37
+
38
+ def notify(event)
39
+ exception = event.payload[:exception_object]
40
+ job = event.payload[:job]
41
+
42
+ return unless exception && solid_queue_job?(job)
43
+
44
+ Exceptify.notify_exception(exception, data: {solid_queue: job_data(job)})
45
+ end
46
+
47
+ private
48
+
49
+ def solid_queue_job?(job)
50
+ queue_adapter_name(job) == ADAPTER_NAME
51
+ end
52
+
53
+ def queue_adapter_name(job)
54
+ job.class.queue_adapter_name if job && job.class.respond_to?(:queue_adapter_name)
55
+ end
56
+
57
+ def job_data(job)
58
+ {adapter: queue_adapter_name(job), job_class: job.class.name}.tap do |data|
59
+ JOB_ATTRIBUTES.each do |attribute|
60
+ data[attribute] = job.public_send(attribute) if job.respond_to?(attribute)
61
+ end
62
+ end.compact
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ Exceptify::SolidQueue.install
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch"
4
+ require "active_support/core_ext/time"
5
+ require "json"
6
+
7
+ module Exceptify
8
+ class TeamsNotifier < BaseNotifier
9
+ include Exceptify::BacktraceCleaner
10
+
11
+ class MissingController
12
+ def method_missing(*args, &block)
13
+ end
14
+
15
+ def respond_to_missing?(*args)
16
+ end
17
+ end
18
+
19
+ attr_accessor :httparty
20
+
21
+ def initialize(options = {})
22
+ options = options.dup
23
+ @httparty = options.delete(:http_client) || HTTParty
24
+ super()
25
+ self.base_options = options
26
+ @default_options = options
27
+ end
28
+
29
+ def call(exception, options = {})
30
+ @options = options.merge(@default_options)
31
+ @exception = exception
32
+ @backtrace = exception.backtrace ? clean_backtrace(exception) : nil
33
+
34
+ @env = @options.delete(:env)
35
+
36
+ @application_name = @options.delete(:app_name) || rails_app_name
37
+ @gitlab_url = @options.delete(:git_url)
38
+ @jira_url = @options.delete(:jira_url)
39
+
40
+ @webhook_url = @options.delete(:webhook_url)
41
+ raise ArgumentError, "You must provide 'webhook_url' parameter." unless @webhook_url
42
+
43
+ if @env.nil?
44
+ @controller = @request_items = nil
45
+ else
46
+ @controller = @env["action_controller.instance"] || MissingController.new
47
+ @additional_exception_data = @env["exceptify.exception_data"]
48
+ request = ActionDispatch::Request.new(@env)
49
+
50
+ @request_items = {url: request.original_url,
51
+ http_method: request.method,
52
+ ip_address: request.remote_ip,
53
+ parameters: request.filtered_parameters,
54
+ timestamp: Time.current}
55
+
56
+ end
57
+
58
+ payload = message_text
59
+
60
+ @options[:body] = payload.to_json
61
+ @options[:headers] ||= {}
62
+ @options[:headers]["Content-Type"] = "application/json"
63
+ @options[:debug_output] = $stdout
64
+
65
+ @httparty.post(@webhook_url, @options)
66
+ end
67
+
68
+ private
69
+
70
+ def message_text
71
+ text = {
72
+ "@type" => "MessageCard",
73
+ "@context" => "http://schema.org/extensions",
74
+ "summary" => "#{@application_name} Exception Alert",
75
+ "title" => "⚠️ Exception Occurred in #{env_name} ⚠️",
76
+ "sections" => [
77
+ {
78
+ "activityTitle" => activity_title,
79
+ "activitySubtitle" => @exception.message.to_s
80
+ }
81
+ ],
82
+ "potentialAction" => []
83
+ }
84
+
85
+ text["sections"].push details
86
+ text["potentialAction"].push gitlab_view_link unless @gitlab_url.nil?
87
+ text["potentialAction"].push gitlab_issue_link unless @gitlab_url.nil?
88
+ text["potentialAction"].push jira_issue_link unless @jira_url.nil?
89
+
90
+ text
91
+ end
92
+
93
+ def details
94
+ details = {
95
+ "title" => "Details",
96
+ "facts" => []
97
+ }
98
+
99
+ details["facts"].push message_request unless @request_items.nil?
100
+ details["facts"].push message_backtrace unless @backtrace.nil?
101
+ details["facts"].push additional_exception_data unless @additional_exception_data.nil?
102
+ details
103
+ end
104
+
105
+ def activity_title
106
+ errors_count = @options[:accumulated_errors_count].to_i
107
+
108
+ "#{(errors_count > 1) ? errors_count : "A"} *#{@exception.class}* occurred" +
109
+ (@controller ? " in *#{controller_and_method}*." : ".")
110
+ end
111
+
112
+ def message_request
113
+ {
114
+ "name" => "Request",
115
+ "value" => "#{hash_presentation(@request_items)}\n "
116
+ }
117
+ end
118
+
119
+ def message_backtrace(size = 3)
120
+ text = []
121
+ size = (@backtrace.size < size) ? @backtrace.size : size
122
+ text << "```"
123
+ size.times { |i| text << "* " + @backtrace[i] }
124
+ text << "```"
125
+
126
+ {
127
+ "name" => "Backtrace",
128
+ "value" => text.join(" \n").to_s
129
+ }
130
+ end
131
+
132
+ def additional_exception_data
133
+ {
134
+ "name" => "Data",
135
+ "value" => "`#{@additional_exception_data}`\n "
136
+ }
137
+ end
138
+
139
+ def gitlab_view_link
140
+ {
141
+ "@type" => "ViewAction",
142
+ "name" => "\u{1F98A} View in GitLab",
143
+ "target" => [
144
+ "#{@gitlab_url}/#{@application_name}"
145
+ ]
146
+ }
147
+ end
148
+
149
+ def gitlab_issue_link
150
+ link = [@gitlab_url, @application_name, "issues", "new"].join("/")
151
+ params = {
152
+ "issue[title]" => ["[BUG] Error 500 :",
153
+ controller_and_method,
154
+ "(#{@exception.class})",
155
+ @exception.message].compact.join(" ")
156
+ }.to_query
157
+
158
+ {
159
+ "@type" => "ViewAction",
160
+ "name" => "\u{1F98A} Create Issue in GitLab",
161
+ "target" => [
162
+ "#{link}/?#{params}"
163
+ ]
164
+ }
165
+ end
166
+
167
+ def jira_issue_link
168
+ {
169
+ "@type" => "ViewAction",
170
+ "name" => "🐞 Create Issue in Jira",
171
+ "target" => [
172
+ "#{@jira_url}/secure/CreateIssue!default.jspa"
173
+ ]
174
+ }
175
+ end
176
+
177
+ def controller_and_method
178
+ if @controller
179
+ "#{@controller.controller_name}##{@controller.action_name}"
180
+ else
181
+ ""
182
+ end
183
+ end
184
+
185
+ def hash_presentation(hash)
186
+ text = []
187
+
188
+ hash.each do |key, value|
189
+ text << "* **#{key}** : `#{value}`"
190
+ end
191
+
192
+ text.join(" \n")
193
+ end
194
+
195
+ def rails_app_name
196
+ return unless defined?(Rails) && Rails.respond_to?(:application)
197
+
198
+ if ::Gem::Version.new(Rails.version) >= ::Gem::Version.new("6.0")
199
+ Rails.application.class.module_parent_name.underscore
200
+ else
201
+ Rails.application.class.parent_name.underscore
202
+ end
203
+ end
204
+
205
+ def env_name
206
+ Rails.env if defined?(Rails) && Rails.respond_to?(:env)
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Exceptify
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,3 @@
1
+ <pre style="font-size: 12px; padding: 10px; border: 1px solid #e1e1e8; background-color:#f5f5f5">
2
+ <%= @backtrace.join("\n") %>
3
+ </pre>
@@ -0,0 +1 @@
1
+ <%= raw @backtrace.join("\n") %>
@@ -0,0 +1,6 @@
1
+ <ul style="list-style: none">
2
+ <li>
3
+ <strong>data:</strong>
4
+ <span><%= PP.pp(@data, +"") %></span>
5
+ </li>
6
+ </ul>
@@ -0,0 +1 @@
1
+ * data: <%= raw PP.pp(@data, +"") %>
@@ -0,0 +1,10 @@
1
+ <% filtered_env = @request.filtered_env -%>
2
+
3
+ <ul style="list-style: none">
4
+ <% filtered_env.keys.map(&:to_s).sort.each do |key| -%>
5
+ <li>
6
+ <strong><%= key %>:</strong>
7
+ <span><%= inspect_object(filtered_env[key]) %></span>
8
+ </li>
9
+ <% end -%>
10
+ </ul>
@@ -0,0 +1,5 @@
1
+ <% filtered_env = @request.filtered_env -%>
2
+ <% max = filtered_env.keys.map(&:to_s).max { |a, b| a.length <=> b.length } -%>
3
+ <% filtered_env.keys.map(&:to_s).sort.each do |key| -%>
4
+ * <%= raw safe_encode("%-*s: %s" % [max.length, key, inspect_object(filtered_env[key])]).strip %>
5
+ <% end -%>
@@ -0,0 +1,36 @@
1
+ <ul style="list-style: none">
2
+ <li>
3
+ <strong>URL:</strong>
4
+ <span><%= @request.url %></span>
5
+ </li>
6
+ <li>
7
+ <strong>HTTP Method:</strong>
8
+ <span><%= @request.request_method %></span>
9
+ </li>
10
+ <li>
11
+ <strong>IP Address:</strong>
12
+ <span><%= @request.remote_ip %></span>
13
+ </li>
14
+ <li>
15
+ <strong>Parameters:</strong>
16
+ <span><%= @request.filtered_parameters.inspect %></span>
17
+ </li>
18
+ <li>
19
+ <strong>Timestamp:</strong>
20
+ <span><%= @timestamp %></span>
21
+ </li>
22
+ <li>
23
+ <strong>Server:</strong>
24
+ <span><%= Socket.gethostname %></span>
25
+ </li>
26
+ <% if defined?(Rails) && Rails.respond_to?(:root) %>
27
+ <li>
28
+ <strong>Rails root:</strong>
29
+ <span><%= Rails.root %></span>
30
+ </li>
31
+ <% end %>
32
+ <li>
33
+ <strong>Process:</strong>
34
+ <span><%= $$ %></span>
35
+ </li>
36
+ </ul>
@@ -0,0 +1,10 @@
1
+ * URL : <%= raw safe_encode @request.url %>
2
+ * HTTP Method: <%= raw @request.request_method %>
3
+ * IP address : <%= raw @request.remote_ip %>
4
+ * Parameters : <%= raw safe_encode @request.filtered_parameters.inspect %>
5
+ * Timestamp : <%= raw @timestamp %>
6
+ * Server : <%= raw Socket.gethostname %>
7
+ <% if defined?(Rails) && Rails.respond_to?(:root) %>
8
+ * Rails root : <%= raw Rails.root %>
9
+ <% end %>
10
+ * Process: <%= raw $$ %>
@@ -0,0 +1,10 @@
1
+ <ul style="list-style: none">
2
+ <li>
3
+ <strong>session_id: </strong>
4
+ <span><%= @request.ssl? ? "[FILTERED]" : (@request.session['session_id'] || (@request.env["rack.session.options"] and @request.env["rack.session.options"][:id]).inspect) %></span>
5
+ </li>
6
+ <li>
7
+ <strong>data: </strong>
8
+ <span><%= PP.pp(@request.session.to_hash, +"") %></span>
9
+ </li>
10
+ </ul>
@@ -0,0 +1,2 @@
1
+ * session id: <%= @request.ssl? ? "[FILTERED]" : (raw (@request.session['session_id'] || (@request.env["rack.session.options"] and @request.env["rack.session.options"][:id])).inspect.html_safe) %>
2
+ * data: <%= raw PP.pp(@request.session.to_hash, +"") %>
@@ -0,0 +1,3 @@
1
+ <h2>
2
+ <%= title.to_s.humanize %>
3
+ </h2>
@@ -0,0 +1,3 @@
1
+ -------------------------------
2
+ <%= raw title.to_s.humanize %>:
3
+ -------------------------------