harbinger 0.0.1.pre → 0.1.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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.hound.yml +818 -0
- data/.travis.yml +20 -0
- data/Gemfile +23 -3
- data/README.md +8 -27
- data/Rakefile +47 -1
- data/app/controllers/harbinger/messages_controller.rb +24 -0
- data/app/models/harbinger/database_channel_message.rb +51 -0
- data/app/models/harbinger/database_channel_message_element.rb +19 -0
- data/app/views/harbinger/messages/index.html.erb +43 -0
- data/app/views/harbinger/messages/show.html.erb +24 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20140310185338_create_harbinger_database_channel_message.rb +11 -0
- data/db/migrate/20140310185339_create_harbinger_database_channel_message_elements.rb +14 -0
- data/gemfiles/rails4.1.gemfile +12 -0
- data/gemfiles/rails4.gemfile +13 -0
- data/harbinger.gemspec +22 -7
- data/lib/generators/harbinger/install/install_generator.rb +23 -0
- data/lib/generators/harbinger/install/templates/harbinger_initializer.rb.erb +6 -0
- data/lib/harbinger.rb +100 -1
- data/lib/harbinger/channels.rb +25 -0
- data/lib/harbinger/channels/database_channel.rb +15 -0
- data/lib/harbinger/channels/logger_channel.rb +31 -0
- data/lib/harbinger/channels/null_channel.rb +7 -0
- data/lib/harbinger/configuration.rb +58 -0
- data/lib/harbinger/engine.rb +8 -1
- data/lib/harbinger/exceptions.rb +4 -0
- data/lib/harbinger/message.rb +20 -0
- data/lib/harbinger/reporters.rb +34 -0
- data/lib/harbinger/reporters/exception_reporter.rb +26 -0
- data/lib/harbinger/reporters/null_reporter.rb +14 -0
- data/lib/harbinger/reporters/request_reporter.rb +20 -0
- data/lib/harbinger/reporters/user_reporter.rb +20 -0
- data/lib/harbinger/version.rb +1 -1
- data/run_each_spec_in_isolation +9 -0
- data/script/fast_specs +20 -0
- data/spec/controllers/harbinger/messages_controller_spec.rb +26 -0
- data/spec/features/end_to_end_exception_handling_spec.rb +39 -0
- data/spec/lib/harbinger/channels/database_channel_spec.rb +18 -0
- data/spec/lib/harbinger/channels/logger_channel_spec.rb +21 -0
- data/spec/lib/harbinger/channels/null_channel_spec.rb +8 -0
- data/spec/lib/harbinger/channels_spec.rb +40 -0
- data/spec/lib/harbinger/configuration_spec.rb +53 -0
- data/spec/lib/harbinger/message_spec.rb +15 -0
- data/spec/lib/harbinger/reporters/exception_reporter_spec.rb +24 -0
- data/spec/lib/harbinger/reporters/null_reporter_spec.rb +21 -0
- data/spec/lib/harbinger/reporters/request_reporter_spec.rb +23 -0
- data/spec/lib/harbinger/reporters/user_reporter_spec.rb +17 -0
- data/spec/lib/harbinger/reporters_spec.rb +46 -0
- data/spec/lib/harbinger_spec.rb +60 -0
- data/spec/models/harbinger/database_channel_message_element_spec.rb +16 -0
- data/spec/models/harbinger/database_channel_message_spec.rb +68 -0
- data/spec/routing/harbinger/messages_routing_spec.rb +16 -0
- data/spec/spec_active_record_helper.rb +41 -0
- data/spec/spec_fast_helper.rb +70 -0
- data/spec/spec_slow_helper.rb +57 -0
- data/spec/spec_view_helper.rb +38 -0
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +13 -0
- data/spec/views/harbinger/messages/index.html.erb_spec.rb +31 -0
- data/spec/views/harbinger/messages/show.html.erb_spec.rb +36 -0
- metadata +205 -20
- data/MIT-LICENSE +0 -20
data/lib/harbinger.rb
CHANGED
@@ -1,5 +1,104 @@
|
|
1
|
-
require "harbinger/engine"
|
1
|
+
require "harbinger/engine" if defined?(Rails)
|
2
2
|
require "harbinger/version"
|
3
|
+
require "harbinger/reporters"
|
4
|
+
require "harbinger/channels"
|
5
|
+
require "harbinger/exceptions"
|
6
|
+
require "harbinger/configuration"
|
3
7
|
|
4
8
|
module Harbinger
|
9
|
+
module_function
|
10
|
+
class << self
|
11
|
+
attr_writer :configuration
|
12
|
+
|
13
|
+
def configuration
|
14
|
+
@configuration ||= Configuration.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module_function
|
19
|
+
def configure
|
20
|
+
yield(configuration)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Responsible for building a :message from the various :contexts and then
|
24
|
+
# delivering the :message to the appropriate :channels.
|
25
|
+
#
|
26
|
+
# @see .build_message
|
27
|
+
# @see .deliver_message
|
28
|
+
#
|
29
|
+
# @param [Hash] options
|
30
|
+
# @option options [Message] :message The message you want to amend.
|
31
|
+
# If none is provided, then one is created.
|
32
|
+
# @option options [Object, Array<Object>] :contexts One or more Objects that
|
33
|
+
# Harbinger will visit and extract message elements from.
|
34
|
+
# @option options [Symbol, Array<Symbol>] :channels One or more channels that
|
35
|
+
# Harbinger will deliver the :message to
|
36
|
+
def call(options)
|
37
|
+
message = build_message(options)
|
38
|
+
deliver_message(message, options)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Responsible for building a :message from the various :contexts.
|
42
|
+
#
|
43
|
+
# @see .call
|
44
|
+
#
|
45
|
+
# @param [Hash] options
|
46
|
+
# @option options [Message] :message The message you want to amend.
|
47
|
+
# If none is provided, then one is created.
|
48
|
+
# @option options [Object, Array<Object>] :contexts One or more Objects that
|
49
|
+
# Harbinger will visit and extract message elements from.
|
50
|
+
def build_message(options = {})
|
51
|
+
contexts = Array(options.fetch(:contexts)).flatten.compact
|
52
|
+
message = options.fetch(:message) { default_message }
|
53
|
+
|
54
|
+
contexts.each { |context| reporter_for(context).accept(message) }
|
55
|
+
message
|
56
|
+
end
|
57
|
+
|
58
|
+
# Responsible for delivering a :message to the appropriate :channels.
|
59
|
+
#
|
60
|
+
# @see .call
|
61
|
+
#
|
62
|
+
# @param message [Message] The message you want to amend.
|
63
|
+
# If none is provided, then one is created.
|
64
|
+
# @param [Hash] options
|
65
|
+
# @option options [Symbol, Array<Symbol>] :channels One or more channels that
|
66
|
+
# Harbinger will deliver the :message to
|
67
|
+
def deliver_message(message, options = {})
|
68
|
+
channels = options.fetch(:channels) { default_channels }
|
69
|
+
Array(channels).flatten.compact.each do |channel_name|
|
70
|
+
channel = channel_for(channel_name)
|
71
|
+
channel.deliver(message)
|
72
|
+
end
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
def default_message
|
77
|
+
require 'harbinger/message'
|
78
|
+
Message.new
|
79
|
+
end
|
80
|
+
private_class_method :default_message
|
81
|
+
|
82
|
+
def reporter_for(context)
|
83
|
+
Reporters.find_for(context)
|
84
|
+
end
|
85
|
+
private_class_method :reporter_for
|
86
|
+
|
87
|
+
def channel_for(name)
|
88
|
+
Channels.find_for(name)
|
89
|
+
end
|
90
|
+
private_class_method :channel_for
|
91
|
+
|
92
|
+
def default_channels
|
93
|
+
configuration.default_channels
|
94
|
+
end
|
95
|
+
|
96
|
+
def logger
|
97
|
+
configuration.logger
|
98
|
+
end
|
99
|
+
|
100
|
+
def database_storage
|
101
|
+
configuration.database_storage
|
102
|
+
end
|
103
|
+
|
5
104
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Harbinger
|
2
|
+
module Channels
|
3
|
+
module_function
|
4
|
+
def find_for(channel_name)
|
5
|
+
channel_class_name = channel_name_for_instance(channel_name)
|
6
|
+
if const_defined?(channel_class_name)
|
7
|
+
const_get(channel_class_name)
|
8
|
+
else
|
9
|
+
NullChannel
|
10
|
+
end
|
11
|
+
rescue StandardError
|
12
|
+
NullChannel
|
13
|
+
end
|
14
|
+
|
15
|
+
def channel_name_for_instance(channel_name)
|
16
|
+
channel_name.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase } + "Channel"
|
17
|
+
end
|
18
|
+
private_class_method :channel_name_for_instance
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Note: I am not requiring all of the channels for this Gem. Some
|
23
|
+
# implementations my want to override the defaults. Look to
|
24
|
+
# `lib/harbinger/engine.rb` for how things are required for Rails applications.
|
25
|
+
require 'harbinger/channels/null_channel'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Harbinger::Channels
|
2
|
+
module DatabaseChannel
|
3
|
+
module_function
|
4
|
+
def deliver(message, options = {})
|
5
|
+
storage = options.fetch(:storage) { default_storage }
|
6
|
+
storage.store_message(message)
|
7
|
+
end
|
8
|
+
|
9
|
+
def default_storage
|
10
|
+
Harbinger.database_storage
|
11
|
+
end
|
12
|
+
private_class_method :default_storage
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Harbinger::Channels
|
2
|
+
module LoggerChannel
|
3
|
+
module_function
|
4
|
+
def deliver(message, options = {})
|
5
|
+
logger = options.fetch(:logger) { default_logger }
|
6
|
+
severity = options.fetch(:severity) { default_severity }
|
7
|
+
read(message) do |line|
|
8
|
+
logger.add(severity, line)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def read(message)
|
13
|
+
yield("BEGIN MESSAGE OBJECT ID=#{message.object_id}")
|
14
|
+
message.attributes.each do |key, value|
|
15
|
+
yield("#{key.inspect} => #{value.inspect}")
|
16
|
+
end
|
17
|
+
yield("END MESSAGE OBJECT ID=#{message.object_id}")
|
18
|
+
end
|
19
|
+
private_class_method :read
|
20
|
+
|
21
|
+
def default_logger
|
22
|
+
Harbinger.logger
|
23
|
+
end
|
24
|
+
private_class_method :default_logger
|
25
|
+
|
26
|
+
def default_severity
|
27
|
+
5 # ::Logger::UNKNOWN
|
28
|
+
end
|
29
|
+
private_class_method :default_severity
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'harbinger/exceptions'
|
2
|
+
module Harbinger
|
3
|
+
class Configuration
|
4
|
+
|
5
|
+
def default_channels
|
6
|
+
@default_channels || __default_channels
|
7
|
+
end
|
8
|
+
|
9
|
+
def default_channels=(*channel_names)
|
10
|
+
@default_channels = Array(channel_names).flatten.compact.collect{|name|
|
11
|
+
word = name.to_s
|
12
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
13
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
14
|
+
word.tr!("-", "_")
|
15
|
+
word.downcase!
|
16
|
+
word.to_sym
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def logger
|
21
|
+
@logger || default_logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger=(object)
|
25
|
+
raise ConfigurationError.new("Expected Harbinger.database_storage to respond_to #add. #{object.inspect} does not respond to #add") unless object.respond_to?(:add)
|
26
|
+
@logger = object
|
27
|
+
end
|
28
|
+
|
29
|
+
def database_storage
|
30
|
+
@database_storage || default_database_storage
|
31
|
+
end
|
32
|
+
|
33
|
+
def database_storage=(object)
|
34
|
+
raise ConfigurationError.new("Expected Harbinger.database_storage to respond_to #store_message. #{object.inspect} does not respond to #add") unless object.respond_to?(:store_message)
|
35
|
+
@database_storage = object
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def default_logger
|
41
|
+
if defined?(Rails)
|
42
|
+
Rails.logger
|
43
|
+
else
|
44
|
+
require 'logger'
|
45
|
+
::Logger.new(STDOUT)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def default_database_storage
|
50
|
+
require 'harbinger/database_channel_message'
|
51
|
+
DatabaseChannelMessage
|
52
|
+
end
|
53
|
+
|
54
|
+
def __default_channels
|
55
|
+
[:logger]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/harbinger/engine.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
module Harbinger
|
2
2
|
class Engine < ::Rails::Engine
|
3
|
-
|
3
|
+
engine_name 'harbinger'
|
4
|
+
|
5
|
+
config.to_prepare do
|
6
|
+
# Because I don't want to auto-require all of the dependent channels
|
7
|
+
Harbinger.default_channels.each do |channel_name|
|
8
|
+
require "harbinger/channels/#{channel_name}_channel"
|
9
|
+
end
|
10
|
+
end
|
4
11
|
end
|
5
12
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Harbinger
|
2
|
+
class Message
|
3
|
+
attr_reader :attributes
|
4
|
+
def initialize
|
5
|
+
@attributes = {}
|
6
|
+
yield(self) if block_given?
|
7
|
+
end
|
8
|
+
|
9
|
+
def append(container, key, value)
|
10
|
+
composite_key = "#{container}.#{key}"
|
11
|
+
@attributes[composite_key] ||= []
|
12
|
+
@attributes[composite_key] << value
|
13
|
+
end
|
14
|
+
|
15
|
+
def contexts
|
16
|
+
attributes.keys.collect { |key| key.split('.')[0] }.uniq
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Harbinger
|
2
|
+
module Reporters
|
3
|
+
module_function
|
4
|
+
def find_for(context)
|
5
|
+
if context.respond_to?(:to_harbinger_reporter)
|
6
|
+
context.to_harbinger_reporter
|
7
|
+
else
|
8
|
+
# @TODO - Handle inheritence; KeyError is not an Exception
|
9
|
+
reporter_class_name = reporter_name_for_instance(context)
|
10
|
+
if const_defined?(reporter_class_name)
|
11
|
+
const_get(reporter_class_name).new(context)
|
12
|
+
else
|
13
|
+
NullReporter.new(context)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
rescue StandardError
|
17
|
+
NullReporter.new(context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def reporter_name_for_instance(context)
|
21
|
+
if context.is_a?(Exception)
|
22
|
+
"ExceptionReporter"
|
23
|
+
else
|
24
|
+
context.class.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase } + "Reporter"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
private_class_method :reporter_name_for_instance
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require "harbinger/reporters/user_reporter"
|
32
|
+
require "harbinger/reporters/request_reporter"
|
33
|
+
require "harbinger/reporters/exception_reporter"
|
34
|
+
require "harbinger/reporters/null_reporter"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Harbinger::Reporters
|
2
|
+
class ExceptionReporter
|
3
|
+
attr_reader :exception
|
4
|
+
|
5
|
+
def initialize(exception)
|
6
|
+
@exception = exception
|
7
|
+
end
|
8
|
+
|
9
|
+
def accept(message)
|
10
|
+
|
11
|
+
if exception.respond_to?(:class)
|
12
|
+
message.append('exception', 'class_name', exception.class.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
if exception.respond_to?(:backtrace)
|
16
|
+
message.append('exception', 'backtrace', Array(exception.backtrace).join("\n"))
|
17
|
+
end
|
18
|
+
|
19
|
+
if exception.respond_to?(:message)
|
20
|
+
message.append('exception', 'message', exception.message)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Harbinger
|
2
|
+
module Reporters
|
3
|
+
class RequestReporter
|
4
|
+
attr_reader :request, :method_names
|
5
|
+
private :method_names
|
6
|
+
def initialize(request, config = {})
|
7
|
+
@request = request
|
8
|
+
@method_names = config.fetch(:method_names) { ['path', 'params', 'user_agent'] }
|
9
|
+
end
|
10
|
+
|
11
|
+
def accept(message)
|
12
|
+
method_names.each do |method_name|
|
13
|
+
if request.respond_to?(method_name)
|
14
|
+
message.append('request', method_name, request.public_send(method_name))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Harbinger::Reporters
|
2
|
+
class UserReporter
|
3
|
+
attr_reader :user, :method_names
|
4
|
+
private :method_names
|
5
|
+
|
6
|
+
def initialize(user, config = {})
|
7
|
+
@user = user
|
8
|
+
@method_names = config.fetch(:method_names) { ['username'] }
|
9
|
+
end
|
10
|
+
|
11
|
+
def accept(message)
|
12
|
+
method_names.each do |method_name|
|
13
|
+
if user.respond_to?(method_name)
|
14
|
+
message.append('user', method_name, user.public_send(method_name))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/lib/harbinger/version.rb
CHANGED
data/script/fast_specs
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
module Fast
|
6
|
+
module_function
|
7
|
+
def spec?(fn)
|
8
|
+
open(fn) { |f| f.gets =~ /fast_helper/ }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
fast_specs = FileList['spec/**/*_spec.rb'].select { |fn|
|
13
|
+
Fast.spec?(fn)
|
14
|
+
}
|
15
|
+
if fast_specs.any?
|
16
|
+
system "rspec #{fast_specs}"
|
17
|
+
else
|
18
|
+
puts "Unable to find any fast specs"
|
19
|
+
exit(-1)
|
20
|
+
end
|