chook 1.0.1.b2 → 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGES.md +56 -0
- data/README.md +363 -127
- data/bin/chook-server +31 -1
- data/data/chook.conf.example +183 -0
- data/data/com.pixar.chook-server.plist +20 -0
- data/data/sample_handlers/RestAPIOperation.rb +11 -11
- data/data/sample_handlers/SmartGroupComputerMembershipChange.rb +3 -6
- data/data/sample_jsons/SmartGroupComputerMembershipChange.json +3 -1
- data/data/sample_jsons/SmartGroupMobileDeviceMembershipChange.json +3 -1
- data/lib/chook/configuration.rb +27 -8
- data/lib/chook/event.rb +6 -1
- data/lib/chook/event/handled_event.rb +36 -9
- data/lib/chook/event/handled_event/handlers.rb +260 -98
- data/lib/chook/event/handled_event_logger.rb +86 -0
- data/lib/chook/event_handling.rb +1 -0
- data/lib/chook/foundation.rb +3 -0
- data/lib/chook/procs.rb +17 -1
- data/lib/chook/server.rb +73 -72
- data/lib/chook/server/auth.rb +164 -0
- data/lib/chook/server/log.rb +215 -0
- data/lib/chook/server/public/css/chook.css +133 -0
- data/lib/chook/server/public/imgs/ChookLogoAlMcWhiggin.png +0 -0
- data/lib/chook/server/public/js/chook.js +126 -0
- data/lib/chook/server/public/js/logstream.js +101 -0
- data/lib/chook/server/routes.rb +28 -0
- data/lib/chook/server/routes/handle_by_name.rb +65 -0
- data/lib/chook/server/routes/handle_webhook_event.rb +27 -3
- data/lib/chook/server/routes/handlers.rb +52 -0
- data/lib/chook/server/routes/home.rb +48 -1
- data/lib/chook/server/routes/log.rb +105 -0
- data/lib/chook/server/routes/login_logout.rb +48 -0
- data/lib/chook/server/views/admin.haml +11 -0
- data/lib/chook/server/views/bak.haml +48 -0
- data/lib/chook/server/views/config.haml +15 -0
- data/lib/chook/server/views/handlers.haml +63 -0
- data/lib/chook/server/views/layout.haml +64 -0
- data/lib/chook/server/views/logstream.haml +33 -0
- data/lib/chook/server/views/sketch_admin +44 -0
- data/lib/chook/subject.rb +13 -2
- data/lib/chook/subject/dep_device.rb +81 -0
- data/lib/chook/subject/policy_finished.rb +43 -0
- data/lib/chook/subject/smart_group.rb +6 -0
- data/lib/chook/version.rb +1 -1
- metadata +79 -19
data/bin/chook-server
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
+
Process.setproctitle('chook')
|
4
|
+
|
3
5
|
### Copyright 2017 Pixar
|
4
6
|
|
5
7
|
###
|
@@ -24,5 +26,33 @@
|
|
24
26
|
### language governing permissions and limitations under the Apache License.
|
25
27
|
###
|
26
28
|
###
|
29
|
+
|
30
|
+
require 'getoptlong'
|
31
|
+
|
32
|
+
# The CLI options for GetoptLong
|
33
|
+
OPTS = GetoptLong.new(
|
34
|
+
['--log', '-l', GetoptLong::REQUIRED_ARGUMENT],
|
35
|
+
['--dev', '-d', GetoptLong::NO_ARGUMENT]
|
36
|
+
)
|
37
|
+
|
38
|
+
env = :production
|
39
|
+
log_level = nil
|
40
|
+
|
41
|
+
OPTS.each do |opt, arg|
|
42
|
+
case opt
|
43
|
+
when '--log'
|
44
|
+
log_level = arg
|
45
|
+
when '--dev'
|
46
|
+
env = :development
|
47
|
+
end # case
|
48
|
+
end # opts.each
|
49
|
+
|
50
|
+
ENV['APP_ENV'] = env.to_s
|
51
|
+
|
27
52
|
require 'chook/server'
|
28
|
-
|
53
|
+
begin
|
54
|
+
Chook::Server.run! log_level: log_level
|
55
|
+
rescue => e
|
56
|
+
Chook.logger.fatal e.to_s
|
57
|
+
e.backtrace.each { |line| Chook.logger.fatal "..#{line}" }
|
58
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
#################
|
2
|
+
# This sample config file uses default settings.
|
3
|
+
#
|
4
|
+
# Each setting is defined on one line like so:
|
5
|
+
#
|
6
|
+
# key: value
|
7
|
+
#
|
8
|
+
# Values may have spaces.
|
9
|
+
#
|
10
|
+
# Blank lines and those starting with # are ignored.
|
11
|
+
|
12
|
+
#################
|
13
|
+
# The port used by the server.
|
14
|
+
# Default: 443 when use_ssl is true, 80 otherwise
|
15
|
+
#
|
16
|
+
port: 80
|
17
|
+
|
18
|
+
#################
|
19
|
+
# By default incoming webhooks are handled in parallel
|
20
|
+
# If you have problems (e.g. a handler isn't thread-safe) try setting this
|
21
|
+
# to false and hooks will be handled one at a time, in the order received.
|
22
|
+
# Default: true
|
23
|
+
#
|
24
|
+
concurrency: true
|
25
|
+
|
26
|
+
#################
|
27
|
+
# The directory holding the internal and external handlers.
|
28
|
+
# Their filenames must start with a known webhook name, and
|
29
|
+
# their executability determines internal (not executable)
|
30
|
+
# versus external (executable). See README.md for details.
|
31
|
+
# Default: /Library/Application Support/Chook
|
32
|
+
#
|
33
|
+
handler_dir: /Library/Application Support/Chook
|
34
|
+
|
35
|
+
#################
|
36
|
+
# Should the server use SSL (https)? Ignored unless the engine is 'thin'
|
37
|
+
# Default: false
|
38
|
+
#
|
39
|
+
use_ssl: false
|
40
|
+
|
41
|
+
#################
|
42
|
+
# When using SSL, the path to the SSL certificate to use, in PEM format
|
43
|
+
# Required if use_ssl == true and engine == thin
|
44
|
+
# Default: none
|
45
|
+
#
|
46
|
+
ssl_cert_path:
|
47
|
+
|
48
|
+
#################
|
49
|
+
# When using SSL, the path to the private key for the SSL certificate, in PEM format
|
50
|
+
# Required if use_ssl == true and engine == thin
|
51
|
+
# Default: none
|
52
|
+
#
|
53
|
+
ssl_private_key_path:
|
54
|
+
|
55
|
+
#################
|
56
|
+
# The path to the file used for chook server logging
|
57
|
+
# Default: /var/log/chook-server.log
|
58
|
+
#
|
59
|
+
log_file: /var/log/chook-server.log
|
60
|
+
|
61
|
+
#################
|
62
|
+
# The detail level for the log. One of:
|
63
|
+
# fatal (only fatal errors logged), error, warn, info, or debug (everything logged)
|
64
|
+
# Default: info
|
65
|
+
#
|
66
|
+
log_level: info
|
67
|
+
|
68
|
+
#################
|
69
|
+
# How many old log files to keep when rotating?
|
70
|
+
# Set to 0, or don't set at all, to disable auto-rotating
|
71
|
+
# of log files.
|
72
|
+
# Default: 10
|
73
|
+
#
|
74
|
+
logs_to_keep: 10
|
75
|
+
|
76
|
+
#################
|
77
|
+
# The log file rotates automatically when it reaches this size in megabytes.
|
78
|
+
# Default: 10
|
79
|
+
#
|
80
|
+
log_max_megs: 10
|
81
|
+
|
82
|
+
#################
|
83
|
+
# Any value here will turn on 'HTTP Basic Authentication' for webhooks,
|
84
|
+
# and this will be the username required for a Jamf Pro server to send
|
85
|
+
# webhooks to the Chook server.
|
86
|
+
#
|
87
|
+
# Leaving this empty will allow receiving of webooks with no authentication
|
88
|
+
#
|
89
|
+
# Default: none
|
90
|
+
#
|
91
|
+
webhooks_user:
|
92
|
+
|
93
|
+
#################
|
94
|
+
# When 'HTTP Basic Authentication' is enabled by setting webhooks_user, this
|
95
|
+
# tells chook how to learn the password for that user:
|
96
|
+
#
|
97
|
+
# - If its a path to a file, the file contains the password and nothing else.
|
98
|
+
# The file must be owned by the user running the chook server, and must have
|
99
|
+
# mode 0600.
|
100
|
+
#
|
101
|
+
# - If it ends with a pipe character (|), everything execpt the pipe is considered
|
102
|
+
# to be a shell command, executable by the user running the chook server.
|
103
|
+
# The standard-output of the command will be the password.
|
104
|
+
#
|
105
|
+
# Default: none
|
106
|
+
#
|
107
|
+
webhooks_user_pw:
|
108
|
+
|
109
|
+
#################
|
110
|
+
# Any value here will require authentication to view the admin web page.
|
111
|
+
#
|
112
|
+
# Leaving this empty will allow anyone to see the admin web page.
|
113
|
+
#
|
114
|
+
# If the value here is 'use_jamf' then anyone can log in with their
|
115
|
+
# Jamf Pro credentials, and the admin_pw setting is ignored
|
116
|
+
# (see the jamf_* settings below)
|
117
|
+
#
|
118
|
+
# If the value is anything else, it is the username required to
|
119
|
+
# log in to the admin web page.
|
120
|
+
#
|
121
|
+
# Default: none
|
122
|
+
#
|
123
|
+
admin_user:
|
124
|
+
|
125
|
+
#################
|
126
|
+
# When admin page authentication is enabled by setting admin_user, this
|
127
|
+
# tells chook how to learn the password for that user:
|
128
|
+
#
|
129
|
+
# - if the admin_user is 'use_jamf' then this is ignored, see the jamf_* settings below
|
130
|
+
#
|
131
|
+
# - If its a path to a file, the file contains the password and nothing else.
|
132
|
+
# The file must be owned by the user running the chook server, and must have
|
133
|
+
# mode 0600.
|
134
|
+
#
|
135
|
+
# - If it ends with a pipe character (|), everything execpt the pipe is considered
|
136
|
+
# to be a shell command, executable by the user running the chook server.
|
137
|
+
# The standard-output of the command will be the password.
|
138
|
+
#
|
139
|
+
# Default: none
|
140
|
+
#
|
141
|
+
admin_pw:
|
142
|
+
|
143
|
+
#################
|
144
|
+
# When admin page authentication is enabled by setting admin_user,
|
145
|
+
# this is how many seconds before the browser session expires and the
|
146
|
+
# admin must log in again.
|
147
|
+
#
|
148
|
+
# Default: 86400 (24 hours)
|
149
|
+
#
|
150
|
+
admin_session_expires: 86400
|
151
|
+
|
152
|
+
#################
|
153
|
+
# When the admin_user is 'use_jamf', this is the hostname of the
|
154
|
+
# Jamf Pro server to use for authentication
|
155
|
+
#
|
156
|
+
# Default: none, but /etc/ruby-jss.conf will be honored
|
157
|
+
#
|
158
|
+
jamf_server:
|
159
|
+
|
160
|
+
#################
|
161
|
+
# When the admin_user is 'use_jamf', this is the port of the
|
162
|
+
# Jamf Pro server to use for Authentication
|
163
|
+
#
|
164
|
+
# Default: none, but /etc/ruby-jss.conf will be honored
|
165
|
+
#
|
166
|
+
jamf_port:
|
167
|
+
|
168
|
+
#################
|
169
|
+
# When the admin_user is 'use_jamf', should SSL be used when
|
170
|
+
# connection to the jamf_server? true or false
|
171
|
+
#
|
172
|
+
# Default: none, but /etc/ruby-jss.conf will be honored
|
173
|
+
#
|
174
|
+
jamf_use_ssl:
|
175
|
+
|
176
|
+
#################
|
177
|
+
# When the admin_user is 'use_jamf', and jamf_use_ssl is true,
|
178
|
+
# should SSL certificates from the jamf server be
|
179
|
+
# validated?
|
180
|
+
#
|
181
|
+
# Default: none, but /etc/ruby-jss.conf will be honored
|
182
|
+
#
|
183
|
+
jamf_verify_cert:
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Label</key>
|
6
|
+
<string>com.pixar.chook-server</string>
|
7
|
+
<key>StandardOutPath</key>
|
8
|
+
<string>/var/log/chook-server.log</string>
|
9
|
+
<key>StandardErrorPath</key>
|
10
|
+
<string>/var/log/chook-server.log</string>
|
11
|
+
<key>ProgramArguments</key>
|
12
|
+
<array>
|
13
|
+
<string>/usr/local/pixar/bin/chook-server</string>
|
14
|
+
</array>
|
15
|
+
<key>RunAtLoad</key>
|
16
|
+
<true/>
|
17
|
+
<key>KeepAlive</key>
|
18
|
+
<true/>
|
19
|
+
</dict>
|
20
|
+
</plist>
|
@@ -23,23 +23,23 @@
|
|
23
23
|
###
|
24
24
|
###
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
whook = event.webhook
|
26
|
+
# this module is just a namespace so we don't conflict with other handlers
|
27
|
+
module APIOpHandler
|
29
28
|
|
30
29
|
REPORT_ACTIONS = {
|
31
30
|
'PUT' => 'update',
|
32
31
|
'POST' => 'create',
|
33
32
|
'DELETE' => 'delete'
|
34
|
-
}.freeze
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
Chook.event_handler do |event|
|
38
|
+
event.logger.debug "This is a handler-level debug message for event #{event.object_id}"
|
35
39
|
|
36
|
-
action = REPORT_ACTIONS[
|
40
|
+
action = APIOpHandler::REPORT_ACTIONS[event.subject.restAPIOperationType]
|
37
41
|
|
38
|
-
|
42
|
+
break unless action
|
39
43
|
|
40
|
-
|
41
|
-
The JSS WebHook named '#{whook.name}' was just triggered.
|
42
|
-
It indicates that Casper user '#{eobject.authorizedUsername}' just used the JSS API to #{action}
|
43
|
-
the JSS #{eobject.objectTypeName} named '#{eobject.objectName}' (id #{eobject.objectID})
|
44
|
-
ENDMSG
|
44
|
+
event.logger.info "API #{action}: #{event.subject.objectTypeName} '#{event.subject.objectName}' (id #{event.subject.objectID})"
|
45
45
|
end
|
@@ -23,11 +23,8 @@
|
|
23
23
|
###
|
24
24
|
###
|
25
25
|
|
26
|
-
HANDLER_NAME = File.basename __FILE__
|
27
|
-
CHANGELOG = '/tmp/smart-computer-group-changes.log'.freeze
|
28
|
-
|
29
26
|
Chook.event_handler do |event|
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
event.logger.debug "Computer Smart Group Changed: #{event.subject.name}"
|
28
|
+
event.logger.debug " Additions: #{event.subject.groupAddedDevicesIds.join ', '}"
|
29
|
+
event.logger.debug " Removals: #{event.subject.groupRemovedDevicesIds.join ', '}"
|
33
30
|
end
|
data/lib/chook/configuration.rb
CHANGED
@@ -35,16 +35,33 @@ module Chook
|
|
35
35
|
# The location of the default config file
|
36
36
|
DEFAULT_CONF_FILE = Pathname.new '/etc/chook.conf'
|
37
37
|
|
38
|
-
|
38
|
+
SAMPLE_CONF_FILE = Pathname.new(__FILE__).parent.parent.parent + 'data/chook.conf.example'
|
39
|
+
|
40
|
+
# The attribute keys we maintain, and how they should be converted to
|
41
|
+
# the value used by chook internally.
|
42
|
+
#
|
43
|
+
# For descriptions of the keys, see data/chook.conf.example
|
44
|
+
#
|
39
45
|
CONF_KEYS = {
|
40
|
-
|
41
|
-
|
42
|
-
handler_dir:
|
46
|
+
port: :to_i,
|
47
|
+
concurrency: Chook::Procs::STRING_TO_BOOLEAN,
|
48
|
+
handler_dir: Chook::Procs::STRING_TO_PATHNAME,
|
43
49
|
use_ssl: Chook::Procs::STRING_TO_BOOLEAN,
|
44
|
-
ssl_private_key_path: Chook::Procs::STRING_TO_PATHNAME,
|
45
|
-
ssl_private_key_pw_path: nil,
|
46
50
|
ssl_cert_path: Chook::Procs::STRING_TO_PATHNAME,
|
47
|
-
|
51
|
+
ssl_private_key_path: Chook::Procs::STRING_TO_PATHNAME,
|
52
|
+
log_file: Chook::Procs::STRING_TO_PATHNAME,
|
53
|
+
log_level: Chook::Procs::STRING_TO_LOG_LEVEL,
|
54
|
+
log_max_megs: :to_i,
|
55
|
+
logs_to_keep: :to_i,
|
56
|
+
webhooks_user: nil,
|
57
|
+
webhooks_user_pw: nil,
|
58
|
+
admin_user: nil,
|
59
|
+
admin_pw: nil,
|
60
|
+
admin_session_expires: :to_i,
|
61
|
+
jamf_server: nil,
|
62
|
+
jamf_port: :to_i,
|
63
|
+
jamf_use_ssl: Chook::Procs::STRING_TO_BOOLEAN,
|
64
|
+
jamf_verify_cert: Chook::Procs::STRING_TO_BOOLEAN
|
48
65
|
}.freeze
|
49
66
|
|
50
67
|
# Class Variables
|
@@ -193,6 +210,8 @@ module Chook
|
|
193
210
|
end # class Configuration
|
194
211
|
|
195
212
|
# The single instance of Configuration
|
196
|
-
|
213
|
+
def self.config
|
214
|
+
Chook::Configuration.instance
|
215
|
+
end
|
197
216
|
|
198
217
|
end # module
|
data/lib/chook/event.rb
CHANGED
@@ -65,8 +65,9 @@ module Chook
|
|
65
65
|
'ComputerAdded' => Chook::Subject::COMPUTER,
|
66
66
|
'ComputerCheckIn' => Chook::Subject::COMPUTER,
|
67
67
|
'ComputerInventoryCompleted' => Chook::Subject::COMPUTER,
|
68
|
-
'ComputerPolicyFinished' => Chook::Subject::
|
68
|
+
'ComputerPolicyFinished' => Chook::Subject::POLICY_FINISHED,
|
69
69
|
'ComputerPushCapabilityChanged' => Chook::Subject::COMPUTER,
|
70
|
+
'DeviceAddedToDEP' => Chook::Subject::DEP_DEVICE,
|
70
71
|
'JSSShutdown' => Chook::Subject::JAMF_SOFTWARE_SERVER,
|
71
72
|
'JSSStartup' => Chook::Subject::JAMF_SOFTWARE_SERVER,
|
72
73
|
'MobileDeviceCheckIn' => Chook::Subject::MOBILE_DEVICE,
|
@@ -94,6 +95,9 @@ module Chook
|
|
94
95
|
|
95
96
|
#### Attrbutes common to all events
|
96
97
|
|
98
|
+
# @return [String] A unique identifier for this chook event
|
99
|
+
attr_reader :id
|
100
|
+
|
97
101
|
# @return [Integer] The webhook id in the JSS that caused this event
|
98
102
|
attr_reader :webhook_id
|
99
103
|
|
@@ -128,6 +132,7 @@ module Chook
|
|
128
132
|
# event. Any not provided will be nil.
|
129
133
|
#
|
130
134
|
def initialize(**args)
|
135
|
+
@id = "#{Time.now.to_i}-#{SecureRandom.urlsafe_base64 8}"
|
131
136
|
if args[:raw_json]
|
132
137
|
@raw_json = args[:raw_json]
|
133
138
|
@parsed_json = JSON.parse @raw_json, symbolize_names: true
|
@@ -25,7 +25,6 @@
|
|
25
25
|
|
26
26
|
require 'chook/event/handled_event/handlers'
|
27
27
|
|
28
|
-
#
|
29
28
|
module Chook
|
30
29
|
|
31
30
|
# Load sample JSON files, one per event type
|
@@ -62,9 +61,6 @@ module Chook
|
|
62
61
|
|
63
62
|
#### Class Methods
|
64
63
|
|
65
|
-
# Given some raw_json from the jss, create and return the correct
|
66
|
-
# HandledEvent subclass
|
67
|
-
|
68
64
|
# For each event type in Chook::Event::EVENTS
|
69
65
|
# generate a class for it, set its SUBJECT_CLASS constant
|
70
66
|
# and add it to the HandledEvents module.
|
@@ -99,6 +95,7 @@ module Chook
|
|
99
95
|
# @return [JSSWebHooks::Event subclass] the Event subclass matching the event
|
100
96
|
#
|
101
97
|
def self.parse_event(raw_event_json)
|
98
|
+
return nil if raw_event_json.to_s.empty?
|
102
99
|
event_json = JSON.parse(raw_event_json, symbolize_names: true)
|
103
100
|
event_name = event_json[:webhook][:webhookEvent]
|
104
101
|
Chook::HandledEvents.const_get(event_name).new raw_event_json
|
@@ -121,13 +118,21 @@ module Chook
|
|
121
118
|
super raw_json: raw_event_json
|
122
119
|
end # init
|
123
120
|
|
121
|
+
def event_class_name
|
122
|
+
self.class.const_get(Chook::Event::EVENT_NAME_CONST)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Run all the general handlers for this event class
|
126
|
+
#
|
124
127
|
def handle
|
125
|
-
handlers = Handlers.handlers[
|
128
|
+
handlers = Handlers.handlers[event_class_name]
|
129
|
+
return "No handlers loaded for #{event_class_name} events" unless handlers.is_a? Array
|
130
|
+
|
126
131
|
handlers.each do |handler|
|
127
132
|
case handler
|
128
133
|
when Pathname
|
129
134
|
pipe_to_executable handler
|
130
|
-
when
|
135
|
+
when Object
|
131
136
|
handle_with_proc handler
|
132
137
|
end # case
|
133
138
|
end # @handlers.each do |handler|
|
@@ -135,18 +140,40 @@ module Chook
|
|
135
140
|
# the handle method should return a string,
|
136
141
|
# which is the body of the HTTP result for
|
137
142
|
# POSTing the event
|
138
|
-
"Processed by #{handlers.count} handlers
|
143
|
+
"Processed by #{handlers.count} general handlers"
|
139
144
|
end # def handle
|
140
145
|
|
141
|
-
#
|
146
|
+
# run a single handler specified by filename
|
147
|
+
#
|
148
|
+
def handle_by_name(handler_to_run)
|
149
|
+
handler = Handlers.named_handlers[handler_to_run]
|
150
|
+
return "No named handler '#{handler_to_run}'" unless handler
|
151
|
+
|
152
|
+
if handler.is_a? Pathname
|
153
|
+
pipe_to_executable handler
|
154
|
+
else
|
155
|
+
handle_with_proc handler
|
156
|
+
end # if
|
157
|
+
"Processed by named handler '#{handler_to_run}'"
|
158
|
+
end
|
159
|
+
|
160
|
+
# TODO: these threads will die midstream when the server stops.
|
161
|
+
# Find a way to .join them or otherwise clean them up.
|
162
|
+
|
142
163
|
def pipe_to_executable(handler)
|
164
|
+
logger.debug "EXTERNAL: Sending JSON to stdin of '#{handler.basename}'"
|
143
165
|
_thread = Thread.new do
|
144
166
|
IO.popen([handler.to_s], 'w') { |h| h.puts @raw_json }
|
145
167
|
end
|
146
168
|
end
|
147
169
|
|
148
170
|
def handle_with_proc(handler)
|
149
|
-
|
171
|
+
logger.debug "INTERNAL: Running Handler defined in #{handler.handler_file}"
|
172
|
+
_thread = Thread.new { handler.handle self }
|
173
|
+
end
|
174
|
+
|
175
|
+
def logger
|
176
|
+
@logger ||= Chook::HandledEventLogger.new self
|
150
177
|
end
|
151
178
|
|
152
179
|
end # class HandledEvent
|