chook 1.0.1.b2 → 1.1.5
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 +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
|