tengine_event 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +64 -0
- data/README.rdoc +21 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/bin/tengine_event_sucks +48 -0
- data/bin/tengine_fire +46 -0
- data/lib/tengine/event/model_notifiable.rb +49 -0
- data/lib/tengine/event/sender.rb +125 -0
- data/lib/tengine/event.rb +184 -0
- data/lib/tengine/mq/suite.rb +1102 -0
- data/lib/tengine/mq.rb +5 -0
- data/lib/tengine/null_logger.rb +10 -0
- data/lib/tengine_event.rb +13 -0
- data/spec/.gitignore +1 -0
- data/spec/mq_config.yml.example +13 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/tengine/event/model_notifiable_spec.rb +80 -0
- data/spec/tengine/event/sender_spec.rb +124 -0
- data/spec/tengine/event_spec.rb +397 -0
- data/spec/tengine/mq/connect_actually_spec.rb +38 -0
- data/spec/tengine/mq/suite_spec.rb +981 -0
- data/spec/tengine/null_logger_spec.rb +13 -0
- data/spec/tengine_spec.rb +17 -0
- data/tengine_event.gemspec +96 -0
- metadata +203 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
source "http://rubygems.org"
|
3
|
+
|
4
|
+
# Add dependencies required to use your gem here.
|
5
|
+
# Example:
|
6
|
+
# gem "activesupport", ">= 2.3.5"
|
7
|
+
|
8
|
+
gem "activesupport", ">= 3.0.0"
|
9
|
+
gem "tengine_support", ">= 0.3.24"
|
10
|
+
|
11
|
+
gem "uuid", "~> 2.3.4"
|
12
|
+
|
13
|
+
gem "amqp", "~> 0.8.0"
|
14
|
+
|
15
|
+
# Add dependencies to develop your gem here.
|
16
|
+
# Include everything needed to run rake, tests, features, etc.
|
17
|
+
group :development do
|
18
|
+
gem "rspec", "~> 2.6.0"
|
19
|
+
gem "yard", "~> 0.7.2"
|
20
|
+
gem "bundler", "~> 1.0.18"
|
21
|
+
gem "jeweler", "~> 1.6.4"
|
22
|
+
# gem "rcov", ">= 0"
|
23
|
+
gem "simplecov", "~> 0.5.3"
|
24
|
+
gem "ZenTest", "~> 4.6.2"
|
25
|
+
gem "ci_reporter", "~>1.6.5"
|
26
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
ZenTest (4.6.2)
|
5
|
+
activesupport (3.2.1)
|
6
|
+
i18n (~> 0.6)
|
7
|
+
multi_json (~> 1.0)
|
8
|
+
amq-client (0.8.7)
|
9
|
+
amq-protocol (>= 0.8.4)
|
10
|
+
eventmachine
|
11
|
+
amq-protocol (0.8.4)
|
12
|
+
amqp (0.8.4)
|
13
|
+
amq-client (~> 0.8.7)
|
14
|
+
amq-protocol (~> 0.8.4)
|
15
|
+
eventmachine
|
16
|
+
builder (3.0.0)
|
17
|
+
ci_reporter (1.6.9)
|
18
|
+
builder (>= 2.1.2)
|
19
|
+
diff-lcs (1.1.3)
|
20
|
+
eventmachine (0.12.10)
|
21
|
+
git (1.2.5)
|
22
|
+
i18n (0.6.0)
|
23
|
+
jeweler (1.6.4)
|
24
|
+
bundler (~> 1.0)
|
25
|
+
git (>= 1.2.5)
|
26
|
+
rake
|
27
|
+
macaddr (1.5.0)
|
28
|
+
systemu (>= 2.4.0)
|
29
|
+
multi_json (1.0.4)
|
30
|
+
rake (0.9.2.2)
|
31
|
+
rspec (2.6.0)
|
32
|
+
rspec-core (~> 2.6.0)
|
33
|
+
rspec-expectations (~> 2.6.0)
|
34
|
+
rspec-mocks (~> 2.6.0)
|
35
|
+
rspec-core (2.6.4)
|
36
|
+
rspec-expectations (2.6.0)
|
37
|
+
diff-lcs (~> 1.1.2)
|
38
|
+
rspec-mocks (2.6.0)
|
39
|
+
simplecov (0.5.4)
|
40
|
+
multi_json (~> 1.0.3)
|
41
|
+
simplecov-html (~> 0.5.3)
|
42
|
+
simplecov-html (0.5.3)
|
43
|
+
systemu (2.4.2)
|
44
|
+
tengine_support (0.3.24)
|
45
|
+
activesupport (>= 3.0.0)
|
46
|
+
uuid (2.3.5)
|
47
|
+
macaddr (~> 1.0)
|
48
|
+
yard (0.7.5)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
ZenTest (~> 4.6.2)
|
55
|
+
activesupport (>= 3.0.0)
|
56
|
+
amqp (~> 0.8.0)
|
57
|
+
bundler (~> 1.0.18)
|
58
|
+
ci_reporter (~> 1.6.5)
|
59
|
+
jeweler (~> 1.6.4)
|
60
|
+
rspec (~> 2.6.0)
|
61
|
+
simplecov (~> 0.5.3)
|
62
|
+
tengine_support (>= 0.3.24)
|
63
|
+
uuid (~> 2.3.4)
|
64
|
+
yard (~> 0.7.2)
|
data/README.rdoc
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
= tengine_event
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to tengine_event
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== License
|
16
|
+
tengine_event is distributed under MPL2.0 or LGPLv3 or the dual license of MPL2.0/LGPLv3
|
17
|
+
|
18
|
+
== Copyright
|
19
|
+
|
20
|
+
Copyright (c) 2011 nautilus-technologies.com
|
21
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "tengine_event"
|
18
|
+
gem.homepage = "https://github.com/tengine/tengine_event"
|
19
|
+
gem.license = "MPL2.0/LGPLv3"
|
20
|
+
gem.summary = %Q{Tengine Event API to access the queue}
|
21
|
+
gem.description = %Q{Tengine Event API to access the queue}
|
22
|
+
gem.email = "tengine@nautilus-technologies.com"
|
23
|
+
gem.authors = %w[taigou totty g-morita shyouhei akm]
|
24
|
+
gem.bindir = 'bin'
|
25
|
+
gem.executables = ['tengine_fire', 'tengine_event_sucks']
|
26
|
+
# dependencies defined in Gemfile
|
27
|
+
end
|
28
|
+
Jeweler::RubygemsDotOrgTasks.new
|
29
|
+
|
30
|
+
require 'rspec/core'
|
31
|
+
require 'rspec/core/rake_task'
|
32
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
33
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
34
|
+
end
|
35
|
+
|
36
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
37
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
38
|
+
spec.rcov = true
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => :spec
|
42
|
+
|
43
|
+
require 'yard'
|
44
|
+
YARD::Rake::YardocTask.new
|
45
|
+
|
46
|
+
require 'ci/reporter/rake/rspec'
|
47
|
+
|
48
|
+
task :sucks do
|
49
|
+
fp = File.expand_path '../bin/tengine_event_sucks', __FILE__
|
50
|
+
load fp
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.6
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'tengine_event'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
# MQ �Τ���κ���¤�����Τ�
|
10
|
+
cfg = Hash.new
|
11
|
+
qn = nil
|
12
|
+
op = OptionParser.new $0 do |this|
|
13
|
+
# tengined compatible option names.
|
14
|
+
this.on '-o host', '--event-queue-connection-host=host', 'where to connect' do |arg| cfg[:host] = arg end
|
15
|
+
this.on '-p port', '--evant-queue-connection-port=port', 'where to connect', Integer do |arg| cfg[:port] = arg end
|
16
|
+
this.on '-u user', '--evant-queue-connection-user=user', 'whom to connect' do |arg| cfg[:user] = arg end
|
17
|
+
this.on '-s pass', '--evant-queue-connection-pass=pass', 'whom to connect' do |arg| cfg[:pass] = arg end
|
18
|
+
this.on '-q queue', '--evant-queue-connection-queue=queue', 'what to connect' do |arg| qn = arg end
|
19
|
+
end
|
20
|
+
|
21
|
+
op.parse! ARGV
|
22
|
+
|
23
|
+
hash = { :connection => cfg }
|
24
|
+
hash[:queue] = { :name => qn } if qn
|
25
|
+
suite = Tengine::Mq::Suite.new hash
|
26
|
+
|
27
|
+
# main loop
|
28
|
+
EM.run do
|
29
|
+
i = 0
|
30
|
+
j = false
|
31
|
+
suite.subscribe :confirm => proc{j = true} do |hdr, bdy|
|
32
|
+
hdr.ack
|
33
|
+
STDOUT.puts bdy
|
34
|
+
i += 1
|
35
|
+
end
|
36
|
+
timer = EM.add_periodic_timer 0.1 do
|
37
|
+
if j and i.zero?
|
38
|
+
EM.cancel_timer timer
|
39
|
+
suite.unsubscribe do
|
40
|
+
suite.stop
|
41
|
+
end
|
42
|
+
else
|
43
|
+
i = 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Process.exit true
|
data/bin/tengine_fire
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'eventmachine'
|
5
|
+
|
6
|
+
__DIR__ = File.dirname(__FILE__)
|
7
|
+
$LOAD_PATH << File.expand_path('../lib', __DIR__)
|
8
|
+
require 'tengine/event'
|
9
|
+
|
10
|
+
if ARGV.empty? || ARGV.include?("-h") || ARGV.include?("--help")
|
11
|
+
puts "#{__FILE__} <event_type_name> [opt1:foo]..."
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
|
15
|
+
event_type_name = ARGV.shift
|
16
|
+
options = ARGV.inject({}) do |d, arg|
|
17
|
+
key, value = *arg.split(/:/, 2)
|
18
|
+
d[key] = value
|
19
|
+
d
|
20
|
+
end
|
21
|
+
|
22
|
+
# see https://github.com/eventmachine/eventmachine/blob/master/tests/test_error_handler.rb
|
23
|
+
EM.error_handler{ |e|
|
24
|
+
puts "[error] tengine.fire Error raised during event loop: #{e.class}, #{e.message}\n"
|
25
|
+
puts "#{e.backtrace}\n"
|
26
|
+
EM.error_handler(nil)
|
27
|
+
EM.stop
|
28
|
+
}
|
29
|
+
|
30
|
+
EM.run do
|
31
|
+
if interval = options.delete('interval')
|
32
|
+
EM.add_periodic_timer(interval.to_i) do
|
33
|
+
puts "-" * 100
|
34
|
+
options[:keep_connection] = true
|
35
|
+
options[:retry_interval] = 3
|
36
|
+
Tengine::Event.fire(event_type_name, options)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
EM.next_tick do
|
40
|
+
Tengine::Event.fire(event_type_name, options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Signal.trap("TERM") { connection.close { EM.stop } }
|
46
|
+
Signal.trap("INT") { connection.close { EM.stop } }
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'tengine/event'
|
3
|
+
|
4
|
+
# activemodelなどのObserverの仕組みを使ってイベントキューにモデルの
|
5
|
+
# 登録、変更、削除を通知するための実装を提供するモジュールです。
|
6
|
+
#
|
7
|
+
# http://guides.rubyonrails.org/active_record_validations_callbacks.html#observers
|
8
|
+
# http://mongoid.org/docs/callbacks/observers.html
|
9
|
+
module Tengine::Event::ModelNotifiable
|
10
|
+
def after_create(record)
|
11
|
+
fire_event(:created, record)
|
12
|
+
end
|
13
|
+
|
14
|
+
def after_update(record)
|
15
|
+
fire_event(:updated, record)
|
16
|
+
end
|
17
|
+
|
18
|
+
def after_destroy(record)
|
19
|
+
fire_event(:destroyed, record)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def fire_event(event_base, record)
|
24
|
+
event_properties = {
|
25
|
+
:class_name => record.class.name,
|
26
|
+
:attributes => record.attributes
|
27
|
+
}
|
28
|
+
if event_base == :updated
|
29
|
+
event_properties[:changes] = record.changes
|
30
|
+
end
|
31
|
+
event_sender.fire(event_type_name(event_base, record),
|
32
|
+
:level_key => :info,
|
33
|
+
:properties => event_properties
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
# def event_sender
|
38
|
+
# raise NotImplementedError
|
39
|
+
# end
|
40
|
+
|
41
|
+
def event_type_name(event_base, record)
|
42
|
+
"#{record.class.name}.#{event_base}.#{event_type_name_suffix}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def event_type_name_suffix
|
46
|
+
self.class.name
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'tengine/event'
|
3
|
+
|
4
|
+
require 'active_support/core_ext/array/extract_options'
|
5
|
+
|
6
|
+
# MQと到達保証について by @shyouhei on 2nd Nov., 2011.
|
7
|
+
#
|
8
|
+
# 端的に言ってAMQPプロトコルにはパケットの到達保証が、ありません。したがってAMQP::Exchangeを普通に使うだけでは、fireしたイベントがどこま
|
9
|
+
# で確実に届くかが保証されません。たとえばEventMachineのリアクターが停止してしまったとか、ピアプロセスがSEGVしたとか、TCPのセッションが
|
10
|
+
# 切れてしまったとか、ハードウエアの物理線がセミの産卵で断線したとか、様々な理由でイベントはbrokerに到達しないことがありえるし、それを検
|
11
|
+
# 知する手段がありません。
|
12
|
+
#
|
13
|
+
# この問題に対してRabbitMQは、それ単体では全体の到達保証をしませんが、以下の追加手段を提供してくれています。
|
14
|
+
#
|
15
|
+
# * RabbitMQ自体の実装の努力により、RabbitMQのサーバにデータが到達して以降は、RabbitMQが到達性を保証してくれます
|
16
|
+
#
|
17
|
+
# * AMQPプロトコルを勝手に拡張していて、RabbitMQのサーバにパケットが届いたことをackしてくれるようにできます
|
18
|
+
#
|
19
|
+
# したがってAMQPブローカーにRabbitMQを使っている限りは、MQサーバにパケットが到着したことを、クライアント側でackを読みながら確認すること
|
20
|
+
# で、全体としての到達保証が可能になるわけです。
|
21
|
+
#
|
22
|
+
# Tengine::Event::Sender#fireを実行すると、イベントを送信して、このackを確認する部分までを自動的に行います。したがって所謂
|
23
|
+
# fire-and-forgetの動作が達成されています。ただし、以下のように制限があります
|
24
|
+
#
|
25
|
+
# * AMQP gemの制約上、おおむね非同期的に動作します。つまり、fireは送信を予約するだけで、実のところ送信が終わるのは(再送等で)fireが終了し
|
26
|
+
# てからだいぶ先の話になります。
|
27
|
+
#
|
28
|
+
# * あるときだれかが EM.stop すると、それ以上はackを読めなくなり、再送信ができなくなります。
|
29
|
+
#
|
30
|
+
# * そうは言ってもEM.stopできないとプロセスが終了できないので、stop可能かどうかを調査できるようにしました(新API)。
|
31
|
+
#
|
32
|
+
# * fireメソッドの戻り値のTengine::Eventに新メソッド #transmitted? が追加になっていますので個別のイベントの送信が終わったかどうかはこ
|
33
|
+
# れで確認できます。
|
34
|
+
#
|
35
|
+
# * senderが送信中のイベント一覧は sender.pending_events で入手できます
|
36
|
+
#
|
37
|
+
# * もうsenderが送り終わったらそのままEM.stopしてよい場合(だいたいそうだと思いますが)のために、 sender.stop_after_transmission があり
|
38
|
+
# ます
|
39
|
+
#
|
40
|
+
# APIは今後も使い勝手のために追加する可能性があります
|
41
|
+
|
42
|
+
class Tengine::Event::Sender
|
43
|
+
|
44
|
+
# 現在不使用。やがて消します。
|
45
|
+
RetryError = Class.new StandardError
|
46
|
+
|
47
|
+
attr_reader :mq_suite
|
48
|
+
attr_reader :logger
|
49
|
+
attr_accessor :default_keep_connection
|
50
|
+
|
51
|
+
def initialize(*args)
|
52
|
+
options = args.extract_options!
|
53
|
+
config_or_mq_suite = args.first
|
54
|
+
@mq_suite =
|
55
|
+
case config_or_mq_suite when Tengine::Mq::Suite then
|
56
|
+
config_or_mq_suite
|
57
|
+
else
|
58
|
+
Tengine::Mq::Suite.new(options)
|
59
|
+
end
|
60
|
+
@default_keep_connection = @mq_suite.config[:sender][:keep_connection]
|
61
|
+
@logger = options[:logger] || Tengine::NullLogger.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop(&block)
|
65
|
+
@mq_suite.stop(&block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# publish an event message to AMQP exchange
|
69
|
+
# @param [String/Tengine::Event] event_or_event_type_name
|
70
|
+
# @param [Hash] options the options for attributes
|
71
|
+
# @option options [String] :key attriute key
|
72
|
+
# @option options [String] :source_name source_name
|
73
|
+
# @option options [Time] :occurred_at occurred_at
|
74
|
+
# @option options [Integer] :level level
|
75
|
+
# @option options [Symbol] :level_key level_key
|
76
|
+
# @option options [String] :sender_name sender_name
|
77
|
+
# @option options [Hash] :properties properties
|
78
|
+
# @option options [Hash] :keep_connection
|
79
|
+
# @option options [Hash] :retry_interval
|
80
|
+
# @option options [Hash] :retry_count
|
81
|
+
# @return [Tengine::Event]
|
82
|
+
def fire(event_or_event_type_name, options = {}, &block)
|
83
|
+
# @logger.info("fire(#{event_or_event_type_name.inspect}, #{options}) called")
|
84
|
+
opts = (options || {}).dup
|
85
|
+
cfg = {
|
86
|
+
:keep_connection => (opts.delete(:keep_connection) || default_keep_connection),
|
87
|
+
:retry_interval => opts.delete(:retry_interval),
|
88
|
+
:retry_count => opts.delete(:retry_count),
|
89
|
+
}
|
90
|
+
event =
|
91
|
+
case event_or_event_type_name
|
92
|
+
when Tengine::Event then event_or_event_type_name
|
93
|
+
else
|
94
|
+
Tengine::Event.new(opts.update(
|
95
|
+
:event_type_name => event_or_event_type_name.to_s))
|
96
|
+
end
|
97
|
+
@mq_suite.fire self, event, cfg, block
|
98
|
+
# @logger.info("fire(#{event_or_event_type_name.inspect}, #{options}) complete")
|
99
|
+
event
|
100
|
+
rescue Exception => e
|
101
|
+
@logger.warn("fire(#{event_or_event_type_name.inspect}, #{options}) raised [#{e.class.name}] #{e.message}")
|
102
|
+
raise e
|
103
|
+
end
|
104
|
+
|
105
|
+
def pending_events
|
106
|
+
@mq_suite.pending_events_for self
|
107
|
+
end
|
108
|
+
|
109
|
+
# fireの中で勝手に待つようにしましたので、今後不要です。
|
110
|
+
# 使っている箇所はやがて消していきましょう。
|
111
|
+
def wait_for_connection
|
112
|
+
yield
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Local Variables:
|
118
|
+
# mode: ruby
|
119
|
+
# coding: utf-8-unix
|
120
|
+
# indent-tabs-mode: nil
|
121
|
+
# tab-width: 4
|
122
|
+
# ruby-indent-level: 2
|
123
|
+
# fill-column: 135
|
124
|
+
# default-justification: full
|
125
|
+
# End:
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'tengine_event'
|
3
|
+
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'active_support/core_ext/hash/keys'
|
6
|
+
require 'active_support/json'
|
7
|
+
require 'uuid'
|
8
|
+
|
9
|
+
# Serializable Class of object to send to an MQ or to receive from MQ.
|
10
|
+
class Tengine::Event
|
11
|
+
|
12
|
+
autoload :Sender, 'tengine/event/sender'
|
13
|
+
autoload :ModelNotifiable, 'tengine/event/model_notifiable'
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# see Tengine::Event::Sender#fire
|
17
|
+
def fire(*args, &block)
|
18
|
+
default_sender.fire(*args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def config; @config ||= {}; end
|
22
|
+
def config=(v); @config = v; end
|
23
|
+
|
24
|
+
attr_writer :mq_suite
|
25
|
+
def mq_suite; @mq_suite ||= Tengine::Mq::Suite.new(config); end
|
26
|
+
|
27
|
+
attr_writer :default_sender
|
28
|
+
def default_sender
|
29
|
+
@default_sender ||= Tengine::Event::Sender.new(mq_suite)
|
30
|
+
end
|
31
|
+
|
32
|
+
def uuid_gen
|
33
|
+
# uuidtools と uuid のどちらが良いかは以下のサイトを参照して uuid を使うようにしました。
|
34
|
+
# http://d.hatena.ne.jp/kiwamu/20090205/1233826235
|
35
|
+
@uuid_gen ||= ::UUID.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# jsonの文字列からTengine::Eventのオブジェクトを解釈して生成します
|
39
|
+
def parse(str)
|
40
|
+
case raw_parsed = JSON.parse(str)
|
41
|
+
when Hash then
|
42
|
+
new(raw_parsed)
|
43
|
+
when Array then
|
44
|
+
raw_parsed.map{|hash| new(hash)}
|
45
|
+
else
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @attribute
|
50
|
+
# host_nameが実行するコマンド。デフォルトでは hostname。
|
51
|
+
def host_name_command; @host_name_command ||= "hostname"; end
|
52
|
+
attr_writer :host_name_command
|
53
|
+
|
54
|
+
# ホスト名を取得する
|
55
|
+
# 内部ではhost_name_commandで指定されたコマンドを実行しています。
|
56
|
+
# @return [String]
|
57
|
+
def host_name
|
58
|
+
`#{host_name_command}`.strip
|
59
|
+
end
|
60
|
+
|
61
|
+
# source_nameが指定されていない場合に設定される文字列を返します
|
62
|
+
# config[:default_source_name] に値が設定されていなかったらhost_nameの値が使用されます
|
63
|
+
def default_source_name; config[:default_source_name] || "#{host_name}/#{Process.pid}"; end
|
64
|
+
|
65
|
+
# sender_nameが指定されていない場合に設定される文字列を返します
|
66
|
+
# config[:default_sender_name] に値が設定されていなかったらhost_nameの値が使用されます
|
67
|
+
def default_sender_name; config[:default_sender_name] || "#{host_name}/#{Process.pid}"; end
|
68
|
+
|
69
|
+
# levelが指定されていない場合に設定される文字列を返します
|
70
|
+
# config[:default_level] に値が設定されていなかったらhost_nameの値が使用されます
|
71
|
+
def default_level
|
72
|
+
LEVELS_INV[(config[:default_level_key] || :info).to_sym]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# constructor
|
77
|
+
# @param [Hash] attrs the options for attributes
|
78
|
+
# @option attrs [String] :key attriute key
|
79
|
+
# @option attrs [String] :event_type_name event_type_name
|
80
|
+
# @option attrs [String] :source_name source_name
|
81
|
+
# @option attrs [Time] :occurred_at occurred_at
|
82
|
+
# @option attrs [Integer] :level level
|
83
|
+
# @option attrs [Symbol] :level_key level_key
|
84
|
+
# @option attrs [String] :sender_name sender_name
|
85
|
+
# @option attrs [Hash] :properties properties
|
86
|
+
# @return [Tengine::Event]
|
87
|
+
def initialize(attrs = nil)
|
88
|
+
if attrs
|
89
|
+
raise ArgumentError, "attrs must be a Hash but was #{attrs.inspect}" unless attrs.is_a?(Hash)
|
90
|
+
attrs.each do |key, value|
|
91
|
+
send("#{key}=", value)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
klass = self.class
|
95
|
+
@key ||= klass.uuid_gen.generate # Stringを返す
|
96
|
+
@source_name ||= klass.default_source_name
|
97
|
+
@sender_name ||= klass.default_sender_name
|
98
|
+
@level ||= klass.default_level
|
99
|
+
@occurred_at ||= Time.now.utc
|
100
|
+
end
|
101
|
+
|
102
|
+
# @attribute
|
103
|
+
# キー。インスタンス生成時に同じ意味のイベントには同じキーが割り振られます。
|
104
|
+
attr_accessor :key
|
105
|
+
|
106
|
+
# @attribute
|
107
|
+
# イベント種別名。
|
108
|
+
attr_reader :event_type_name
|
109
|
+
def event_type_name=(v); @event_type_name = v.nil? ? nil : v.to_s; end
|
110
|
+
|
111
|
+
# @attribute
|
112
|
+
# イベントの発生源名。
|
113
|
+
attr_reader :source_name
|
114
|
+
def source_name=(v); @source_name = v.nil? ? nil : v.to_s; end
|
115
|
+
|
116
|
+
# @attribute
|
117
|
+
# イベントの発生日時。
|
118
|
+
attr_accessor :occurred_at
|
119
|
+
def occurred_at=(v)
|
120
|
+
case v
|
121
|
+
when nil then @occurred_at = nil
|
122
|
+
when Time then @occurred_at = v.utc
|
123
|
+
when String then
|
124
|
+
@occurred_at = v.respond_to?(:to_time) ? v.to_time : Time.respond_to?(:parse) ? Time.parse(v) : v
|
125
|
+
else
|
126
|
+
raise ArgumentError, "occurred_at must be a Time but was #{v.inspect}" unless v.is_a?(Time)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# from level to level_key
|
131
|
+
LEVELS = {
|
132
|
+
0 => :gr_heartbeat,
|
133
|
+
1 => :debug,
|
134
|
+
2 => :info,
|
135
|
+
3 => :warn,
|
136
|
+
4 => :error,
|
137
|
+
5 => :fatal,
|
138
|
+
}.freeze
|
139
|
+
|
140
|
+
# from level_key to level
|
141
|
+
LEVELS_INV = LEVELS.invert.freeze
|
142
|
+
|
143
|
+
# @attribute
|
144
|
+
# イベントの通知レベル
|
145
|
+
attr_accessor :level
|
146
|
+
|
147
|
+
# @attribute
|
148
|
+
# イベントの通知レベルキー
|
149
|
+
# :gr_heartbeat/:debug/:info/:warn/:error/:fatal
|
150
|
+
def level_key; LEVELS[level];end
|
151
|
+
def level_key=(v); self.level = LEVELS_INV[v.to_sym]; end
|
152
|
+
|
153
|
+
# @attribute
|
154
|
+
# イベントの送信者名。
|
155
|
+
attr_reader :sender_name
|
156
|
+
def sender_name=(v); @sender_name = v.nil? ? nil : v.to_s; end
|
157
|
+
|
158
|
+
|
159
|
+
# @attribute
|
160
|
+
# プロパティ。他の属性だけでは表現できない諸属性を格納するHashです。
|
161
|
+
def properties
|
162
|
+
@properties ||= {}
|
163
|
+
end
|
164
|
+
|
165
|
+
def properties=(hash)
|
166
|
+
@properties = (hash || {}).stringify_keys
|
167
|
+
end
|
168
|
+
|
169
|
+
ATTRIBUTE_NAMES = [:event_type_name, :key, :source_name, :occurred_at, :level, :sender_name, :properties].freeze
|
170
|
+
|
171
|
+
# @return [Hash] attributes of this object
|
172
|
+
def attributes
|
173
|
+
ATTRIBUTE_NAMES.inject({}) do |d, attr_name|
|
174
|
+
v = send(attr_name)
|
175
|
+
d[attr_name] = v unless v.blank?
|
176
|
+
d
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def transmitted?
|
181
|
+
not Tengine::Mq::Suite.pending?(self)
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|