stenotype 0.1.0 → 0.1.6
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/.rubocop.yml +3 -2
- data/CHANGELOG.md +43 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +111 -60
- data/README.md +48 -17
- data/Rakefile +2 -2
- data/TODO.md +18 -0
- data/bin/console +3 -3
- data/lib/generators/USAGE +8 -0
- data/lib/generators/stenotype/initializer/initializer_generator.rb +23 -0
- data/lib/generators/stenotype/initializer/templates/initializer.rb.erb +82 -0
- data/lib/stenotype.rb +24 -85
- data/lib/stenotype/adapters.rb +3 -3
- data/lib/stenotype/adapters/base.rb +25 -2
- data/lib/stenotype/adapters/google_cloud.rb +70 -22
- data/lib/stenotype/adapters/stdout_adapter.rb +36 -2
- data/lib/stenotype/at_exit.rb +8 -0
- data/lib/stenotype/configuration.rb +127 -36
- data/lib/stenotype/context_handlers.rb +6 -8
- data/lib/stenotype/context_handlers/base.rb +14 -3
- data/lib/stenotype/context_handlers/collection.rb +78 -34
- data/lib/stenotype/context_handlers/rails/active_job.rb +3 -11
- data/lib/stenotype/context_handlers/rails/controller.rb +10 -11
- data/lib/stenotype/dispatcher.rb +1 -2
- data/lib/stenotype/emitter.rb +166 -0
- data/lib/stenotype/event.rb +48 -25
- data/lib/stenotype/event_serializer.rb +31 -11
- data/lib/stenotype/frameworks/rails/action_controller.rb +44 -21
- data/lib/stenotype/frameworks/rails/active_job.rb +3 -5
- data/lib/stenotype/railtie.rb +37 -0
- data/lib/stenotype/version.rb +1 -1
- data/stenotype.gemspec +30 -26
- metadata +70 -19
- data/lib/stenotype/exceptions.rb +0 -31
- data/lib/stenotype/frameworks/object_ext.rb +0 -145
@@ -14,20 +14,19 @@ module Stenotype
|
|
14
14
|
#
|
15
15
|
def as_json(*_args)
|
16
16
|
{
|
17
|
-
class:
|
18
|
-
method:
|
19
|
-
url:
|
20
|
-
referer:
|
21
|
-
params:
|
22
|
-
ip:
|
17
|
+
class: controller_class.name,
|
18
|
+
method: method,
|
19
|
+
url: url,
|
20
|
+
referer: referer,
|
21
|
+
params: params.except("controller", "action"),
|
22
|
+
ip: remote_ip,
|
23
23
|
}
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
26
|
+
delegate :request, to: :context
|
27
|
+
delegate :method, :url, :referer, :remote_ip, :params,
|
28
|
+
:controller_class, to: :request
|
29
|
+
private :request, :method, :url, :referer, :remote_ip, :params, :controller_class
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
data/lib/stenotype/dispatcher.rb
CHANGED
@@ -9,8 +9,7 @@ module Stenotype
|
|
9
9
|
#
|
10
10
|
# Publishes an event to the list of configured targets.
|
11
11
|
#
|
12
|
-
# @example
|
13
|
-
#
|
12
|
+
# @example Manually dispatching an event
|
14
13
|
# event = Stenotype::Event.new(data, options, eval_context)
|
15
14
|
# Stenotype::Dispatcher.new.publish(event)
|
16
15
|
#
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stenotype
|
4
|
+
#
|
5
|
+
# An extension for a plain Ruby class in order to track invocation of
|
6
|
+
# instance methods.
|
7
|
+
#
|
8
|
+
module Emitter
|
9
|
+
#
|
10
|
+
# Class methods for target to be extended by
|
11
|
+
#
|
12
|
+
ClassMethodsExtension = Class.new(Module)
|
13
|
+
#
|
14
|
+
# Instance methods to be included into target class ancestors chain
|
15
|
+
#
|
16
|
+
InstanceMethodsExtension = Class.new(Module)
|
17
|
+
|
18
|
+
# @!visibility private
|
19
|
+
def self.included(klass)
|
20
|
+
instance_mod = InstanceMethodsExtension.new
|
21
|
+
class_mod = ClassMethodsExtension.new
|
22
|
+
|
23
|
+
build_instance_methods(instance_mod)
|
24
|
+
build_class_methods(class_mod)
|
25
|
+
|
26
|
+
klass.const_set(:InstanceProxy, Module.new)
|
27
|
+
klass.const_set(:ClassProxy, Module.new)
|
28
|
+
|
29
|
+
klass.public_send(:include, instance_mod)
|
30
|
+
klass.extend(class_mod)
|
31
|
+
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# @!method emit_event(name, attributes = {}, method: caller_locations.first.label, eval_context: nil)
|
37
|
+
# A method injected into target class to manually track events
|
38
|
+
# @!scope instance
|
39
|
+
# @param name {[String, Symbol]} Event name.
|
40
|
+
# @param attributes {Hash} Data to be sent to the targets.
|
41
|
+
# @param method {String} An optional method name.
|
42
|
+
# @param eval_context {Hash} A hash linking object to context handler
|
43
|
+
# @return {Stenotype::Event} An instance of emitted event
|
44
|
+
# @example Usage of emit_event
|
45
|
+
# class SomeRubyClass
|
46
|
+
# include Stenotype::Emitter
|
47
|
+
#
|
48
|
+
# def some_method
|
49
|
+
# data = collection_data
|
50
|
+
# emit_event(data, eval_context: self) # Track event with given data
|
51
|
+
# data
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
|
56
|
+
#
|
57
|
+
# @!method emit_event_before(*methods)
|
58
|
+
# A class method injected into target class to track instance methods invocation
|
59
|
+
# @param methods {Array<Symbol>} A list of method before which an event will be emitted
|
60
|
+
# @!scope class
|
61
|
+
# @example Usage of emit_event_before
|
62
|
+
# class SomeRubyClass
|
63
|
+
# include Stenotype::Emitter
|
64
|
+
# emit_event_before :some_method # Triggers an event upon calling some_method
|
65
|
+
#
|
66
|
+
# def some_method
|
67
|
+
# # do something
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
|
72
|
+
#
|
73
|
+
# @!method emit_klass_event_before(*class_methods)
|
74
|
+
# A class method injected into a target class to track class methods invocation
|
75
|
+
# @!scope class
|
76
|
+
# @param class_methods {Array<Symbol>} A list of class method before which
|
77
|
+
# an event will be emitted
|
78
|
+
# @example Usage emit_klass_event_before
|
79
|
+
# class SomeRubyClass
|
80
|
+
# include Stenotype::Emitter
|
81
|
+
# emit_klass_event_before :some_method # Triggers an event upon calling some_method
|
82
|
+
#
|
83
|
+
# def self.some_method
|
84
|
+
# # do something
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
|
89
|
+
#
|
90
|
+
# Adds an instance method: [#emit_event] to a target class
|
91
|
+
# where {Stenotype::Emitter} in included in
|
92
|
+
#
|
93
|
+
def self.build_instance_methods(instance_mod)
|
94
|
+
instance_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
95
|
+
def emit_event(event_name, attributes = {}, method: caller_locations.first.label, eval_context: nil)
|
96
|
+
Stenotype::Event.emit!(
|
97
|
+
event_name,
|
98
|
+
{
|
99
|
+
type: "instance_method",
|
100
|
+
class: self.class.name,
|
101
|
+
method: method.to_sym,
|
102
|
+
**attributes,
|
103
|
+
},
|
104
|
+
eval_context: (eval_context || { klass: self })
|
105
|
+
)
|
106
|
+
end
|
107
|
+
RUBY
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Adds class method [#emit_klass_event_before] to every class
|
112
|
+
# inherited from [Object]
|
113
|
+
#
|
114
|
+
def self.build_class_methods(class_mod)
|
115
|
+
class_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
116
|
+
def emit_event_before(*methods)
|
117
|
+
proxy = const_get(:InstanceProxy)
|
118
|
+
|
119
|
+
methods.each do |method|
|
120
|
+
proxy.module_eval do
|
121
|
+
define_method(method) do |*args, **rest_args, &block|
|
122
|
+
Stenotype::Event.emit!(
|
123
|
+
# @todo How do we name such events?
|
124
|
+
"instance_method",
|
125
|
+
{
|
126
|
+
type: "instance_method",
|
127
|
+
class: self.class.name,
|
128
|
+
method: __method__,
|
129
|
+
},
|
130
|
+
eval_context: { klass: self },
|
131
|
+
)
|
132
|
+
super(*args, **rest_args, &block)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
send(:prepend, proxy)
|
138
|
+
end
|
139
|
+
|
140
|
+
def emit_klass_event_before(*class_methods)
|
141
|
+
proxy = const_get(:ClassProxy)
|
142
|
+
|
143
|
+
class_methods.each do |method|
|
144
|
+
proxy.module_eval do
|
145
|
+
define_method(method) do |*args, **rest_args, &block|
|
146
|
+
Stenotype::Event.emit!(
|
147
|
+
# @todo How do we name such events?
|
148
|
+
"class_method",
|
149
|
+
{
|
150
|
+
type: "class_method",
|
151
|
+
class: self.name,
|
152
|
+
method: __method__,
|
153
|
+
},
|
154
|
+
eval_context: { klass: self },
|
155
|
+
)
|
156
|
+
super(*args, **rest_args, &block)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
singleton_class.send(:prepend, proxy)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
RUBY
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/lib/stenotype/event.rb
CHANGED
@@ -8,52 +8,75 @@ module Stenotype
|
|
8
8
|
#
|
9
9
|
# Delegates event to instance of {Stenotype::Event}.
|
10
10
|
#
|
11
|
-
# @example
|
12
|
-
#
|
11
|
+
# @example Emit an event using class method
|
13
12
|
# Stenotype::Event.emit!(data, options, eval_context)
|
14
13
|
#
|
15
|
-
# @param
|
16
|
-
# @param
|
17
|
-
# @param
|
18
|
-
# @param dispatcher {#publish} A dispatcher object responding to [#publish]
|
14
|
+
# @param {[String, Symbol]} name Event name.
|
15
|
+
# @param {Hash} attributes Data to be published to the targets.
|
16
|
+
# @param {Hash} eval_context A context having handler defined in {Stenotype::ContextHandlers}.
|
17
|
+
# @param dispatcher {#publish} A dispatcher object responding to [#publish].
|
19
18
|
# @return {Stenotype::Event} An instance of {Stenotype::Event}
|
20
19
|
#
|
21
|
-
def self.emit!(
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
def self.emit!(name, attributes = {}, eval_context: {}, dispatcher: Stenotype.config.dispatcher)
|
21
|
+
return unless Stenotype.config.enabled
|
22
|
+
|
23
|
+
begin
|
24
|
+
event = new(name, attributes, eval_context: eval_context, dispatcher: dispatcher)
|
25
|
+
event.emit!
|
26
|
+
event
|
27
|
+
rescue => error
|
28
|
+
#
|
29
|
+
# @todo This is a temporary solution to enable conditional logger fetching
|
30
|
+
# needs a fix to use default Spicerack::Configuration functionality
|
31
|
+
#
|
32
|
+
Stenotype::Configuration.logger.error(error)
|
33
|
+
|
34
|
+
raise Stenotype::Error unless Stenotype.config.graceful_error_handling
|
35
|
+
end
|
25
36
|
end
|
26
37
|
|
27
|
-
attr_reader :
|
38
|
+
attr_reader :name, :attributes, :eval_context, :dispatcher
|
28
39
|
|
29
40
|
#
|
30
|
-
# @example
|
31
|
-
#
|
32
|
-
#
|
41
|
+
# @example Create an event
|
42
|
+
# event = Stenotype::Event.new(data, options, eval_context)
|
43
|
+
# @example Create an event with custom dispatcher
|
44
|
+
# event = Stenotype::Event.new(data, options, eval_context, dispatcher: MyDispatcher.new)
|
33
45
|
#
|
34
|
-
# @param {
|
35
|
-
# @param {Hash}
|
46
|
+
# @param {[String, Symbol]} name Event name.
|
47
|
+
# @param {Hash} attributes Data to be published to the targets.
|
36
48
|
# @param {Hash} eval_context A context having handler defined in {Stenotype::ContextHandlers}.
|
37
49
|
# @param dispatcher {#publish} A dispatcher object responding to [#publish].
|
38
50
|
# @return {Stenotype::Event} An instance of event
|
39
51
|
#
|
40
|
-
def initialize(
|
41
|
-
@
|
42
|
-
@
|
52
|
+
def initialize(name, attributes = {}, eval_context: {}, dispatcher: Stenotype.config.dispatcher)
|
53
|
+
@name = name
|
54
|
+
@attributes = attributes
|
43
55
|
@eval_context = eval_context
|
44
|
-
@dispatcher = dispatcher
|
56
|
+
@dispatcher = dispatcher.new
|
45
57
|
end
|
46
58
|
|
47
59
|
#
|
48
60
|
# Emits a {Stenotype::Event}.
|
49
61
|
#
|
50
|
-
# @example
|
51
|
-
#
|
52
|
-
# event
|
53
|
-
# event.emit!
|
62
|
+
# @example Emit an instance of event
|
63
|
+
# event = Stenotype::Event.new('events_name', { key: :value }, eval_context: { controller: ctrl })
|
64
|
+
# event.emit! #=> Publishes the event to targets
|
54
65
|
#
|
55
66
|
def emit!
|
56
|
-
|
67
|
+
return unless Stenotype.config.enabled
|
68
|
+
|
69
|
+
begin
|
70
|
+
dispatcher.publish(self)
|
71
|
+
rescue => error
|
72
|
+
#
|
73
|
+
# @todo This is a temporary solution to enable conditional logger fetching
|
74
|
+
# needs a fix to use default Spicerack::Configuration functionality
|
75
|
+
#
|
76
|
+
Stenotype::Configuration.logger.error(error)
|
77
|
+
|
78
|
+
raise Stenotype::Error unless Stenotype.config.graceful_error_handling
|
79
|
+
end
|
57
80
|
end
|
58
81
|
end
|
59
82
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'securerandom'
|
3
2
|
|
4
3
|
module Stenotype
|
5
4
|
#
|
@@ -9,6 +8,14 @@ module Stenotype
|
|
9
8
|
class EventSerializer
|
10
9
|
attr_reader :event, :uuid_generator
|
11
10
|
|
11
|
+
#
|
12
|
+
# @example Serializing an event with default UUID generator
|
13
|
+
# event = Stenotype::Event.new(data, attributes, eval_context)
|
14
|
+
# serializer = Stenotype::EventSerializer.new(event)
|
15
|
+
#
|
16
|
+
# @example Serializing an event with custom UUID generator
|
17
|
+
# event = Stenotype::Event.new(data, attributes, eval_context)
|
18
|
+
# serializer = Stenotype::EventSerializer.new(event, uuid_generator: CustomUUIDGen)
|
12
19
|
#
|
13
20
|
# @param event {Stenotype::Event}
|
14
21
|
# @param uuid_generator {#uuid} an object responding to [#uuid]
|
@@ -18,26 +25,38 @@ module Stenotype
|
|
18
25
|
@uuid_generator = uuid_generator
|
19
26
|
end
|
20
27
|
|
28
|
+
#
|
29
|
+
# @example Serializing an event with default uuid generator (SecureRandom)
|
30
|
+
# event = Stenotype::Event.new(data, attributes, eval_context)
|
31
|
+
# serializer = Stenotype::EventSerializer.new(event)
|
32
|
+
# serializer.serialize #=> A hash with event.data, event.options,
|
33
|
+
# # default_options and eval_context_options
|
34
|
+
#
|
35
|
+
# @example Serializing an event with custom uuid generator
|
36
|
+
# event = Stenotype::Event.new(data, attributes, eval_context)
|
37
|
+
# serializer = Stenotype::EventSerializer.new(event, uuid_generator: CustomUUIDGen)
|
38
|
+
# serializer.serialize #=> A hash with event.data, event.options,
|
39
|
+
# # default_options and eval_context_options
|
21
40
|
#
|
22
41
|
# @return {Hash} A hash representation of the event and its context
|
23
42
|
#
|
24
43
|
def serialize
|
25
44
|
{
|
26
|
-
|
27
|
-
**
|
45
|
+
name: event_name,
|
46
|
+
**event_attributes,
|
28
47
|
**default_options,
|
29
|
-
**eval_context_options
|
48
|
+
**eval_context_options,
|
30
49
|
}
|
31
50
|
end
|
32
51
|
|
33
52
|
private
|
34
53
|
|
35
|
-
def
|
36
|
-
event.
|
54
|
+
def event_name
|
55
|
+
event.name
|
37
56
|
end
|
38
57
|
|
39
|
-
def
|
40
|
-
event.
|
58
|
+
def event_attributes
|
59
|
+
event.attributes
|
41
60
|
end
|
42
61
|
|
43
62
|
def eval_context
|
@@ -45,16 +64,17 @@ module Stenotype
|
|
45
64
|
end
|
46
65
|
|
47
66
|
def eval_context_options
|
48
|
-
eval_context.map do |context_name, context|
|
67
|
+
context_attributes = eval_context.map do |context_name, context|
|
49
68
|
handler = Stenotype::ContextHandlers.known.choose(handler_name: context_name)
|
50
69
|
handler.new(context).as_json
|
51
|
-
end
|
70
|
+
end
|
71
|
+
context_attributes.reduce(:merge!) || {}
|
52
72
|
end
|
53
73
|
|
54
74
|
def default_options
|
55
75
|
{
|
56
76
|
timestamp: Time.now.utc,
|
57
|
-
uuid: uuid_generator.uuid
|
77
|
+
uuid: uuid_generator.uuid,
|
58
78
|
}
|
59
79
|
end
|
60
80
|
end
|
@@ -3,6 +3,10 @@
|
|
3
3
|
require "active_support/concern"
|
4
4
|
|
5
5
|
module Stenotype
|
6
|
+
#
|
7
|
+
# A namespace containing extensions of various frameworks.
|
8
|
+
# For example Rails components could be extended
|
9
|
+
#
|
6
10
|
module Frameworks
|
7
11
|
module Rails
|
8
12
|
#
|
@@ -12,12 +16,15 @@ module Stenotype
|
|
12
16
|
module ActionControllerExtension
|
13
17
|
extend ActiveSupport::Concern
|
14
18
|
|
19
|
+
private
|
20
|
+
|
15
21
|
#
|
16
22
|
# Emits and event with given data
|
17
|
-
# @param
|
23
|
+
# @param name {[String, Symbol]} Event name
|
24
|
+
# @todo What is really the name here?
|
18
25
|
#
|
19
|
-
def
|
20
|
-
Stenotype::Event.emit!(
|
26
|
+
def _record_freshly_event(name)
|
27
|
+
Stenotype::Event.emit!(name, { type: "controller_action" }, { eval_context: { controller: self }})
|
21
28
|
end
|
22
29
|
|
23
30
|
#
|
@@ -28,12 +35,19 @@ module Stenotype
|
|
28
35
|
# Adds a before_action to each action from the passed list. A before action
|
29
36
|
# is emitting a {Stenotype::Event}. Please note that in case track_view is
|
30
37
|
# used several times, it will keep track of the actions which emit events.
|
31
|
-
# Each time a new track_view is called it will find a symmetric difference
|
32
|
-
# of two sets: set of 'used' actions and a set passed to `track_view`.
|
33
38
|
#
|
34
|
-
# @
|
39
|
+
# @note Each time a new track_view is called it will find a symmetric difference
|
40
|
+
# of two sets: set of already tracked actions and a set passed to `track_view`.
|
41
|
+
#
|
42
|
+
# @example Tracking multiple actions with track_view
|
43
|
+
# Stenotype.configure do |config|
|
44
|
+
# config.enable_action_controller_extension = true
|
45
|
+
# config.enable_active_job_extension = true
|
46
|
+
# end
|
47
|
+
#
|
35
48
|
# class MyController < ActionController::Base
|
36
|
-
# track_view :index, :show
|
49
|
+
# track_view :index, :show # Emits an event upon calling index and show actions,
|
50
|
+
# # but does not trigger an event on create
|
37
51
|
#
|
38
52
|
# def index
|
39
53
|
# # do_something
|
@@ -59,36 +73,36 @@ module Stenotype
|
|
59
73
|
return if delta.empty?
|
60
74
|
|
61
75
|
before_action only: delta do
|
62
|
-
|
76
|
+
_record_freshly_event("view")
|
63
77
|
end
|
64
78
|
|
65
79
|
_tracked_actions.merge(delta)
|
66
80
|
end
|
67
81
|
|
68
|
-
#
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
# Note this action will only define a symmetric difference of
|
74
|
-
# the covered with events actions and the ones not used yet.
|
82
|
+
#
|
83
|
+
# @note This action will only define a symmetric difference of
|
84
|
+
# the tracked actions and the ones not tracked yet.
|
75
85
|
# @see #track_view
|
76
86
|
#
|
77
|
-
# @example
|
78
|
-
# class
|
79
|
-
# track_all_views
|
87
|
+
# @example Emitting an event before all actions in controller
|
88
|
+
# class UsersController < ApplicationController
|
89
|
+
# track_all_views # Emits an event before all actions in a controller
|
80
90
|
#
|
81
91
|
# def index
|
82
|
-
# #
|
92
|
+
# # do something
|
83
93
|
# end
|
84
94
|
#
|
85
95
|
# def show
|
86
96
|
# # do something
|
87
97
|
# end
|
98
|
+
#
|
99
|
+
# def create
|
100
|
+
# # do something
|
101
|
+
# end
|
88
102
|
# end
|
89
103
|
#
|
90
104
|
def track_all_views
|
91
|
-
actions =
|
105
|
+
actions = action_methods
|
92
106
|
|
93
107
|
# A symmetric difference of two sets.
|
94
108
|
# This prevents accidental duplicating of events
|
@@ -97,12 +111,21 @@ module Stenotype
|
|
97
111
|
return if delta.empty?
|
98
112
|
|
99
113
|
before_action only: delta.to_a do
|
100
|
-
|
114
|
+
_record_freshly_event("view")
|
101
115
|
end
|
102
116
|
|
103
117
|
# merge is a mutating op
|
104
118
|
_tracked_actions.merge(delta)
|
105
119
|
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
#
|
124
|
+
# @return {Set<Symbol>} a set of tracked actions
|
125
|
+
#
|
126
|
+
def _tracked_actions
|
127
|
+
@_tracked_actions ||= Set.new
|
128
|
+
end
|
106
129
|
end
|
107
130
|
end
|
108
131
|
end
|