stenotype 0.1.0 → 0.1.1
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/.rubocop.yml +3 -2
- data/CHANGELOG.md +26 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +111 -60
- data/README.md +5 -5
- 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 +65 -0
- data/lib/stenotype.rb +24 -85
- data/lib/stenotype/adapters.rb +3 -3
- data/lib/stenotype/adapters/base.rb +10 -0
- data/lib/stenotype/adapters/google_cloud.rb +53 -21
- data/lib/stenotype/adapters/stdout_adapter.rb +23 -1
- data/lib/stenotype/configuration.rb +91 -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 +162 -0
- data/lib/stenotype/event.rb +8 -9
- data/lib/stenotype/event_serializer.rb +25 -5
- data/lib/stenotype/frameworks/rails/action_controller.rb +41 -19
- 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 +69 -19
- data/lib/stenotype/exceptions.rb +0 -31
- data/lib/stenotype/frameworks/object_ext.rb +0 -145
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
3
|
+
require "stenotype/context_handlers/base"
|
4
|
+
require "stenotype/context_handlers/rails/controller"
|
5
|
+
require "stenotype/context_handlers/rails/active_job"
|
6
|
+
require "stenotype/context_handlers/klass"
|
7
|
+
require "stenotype/context_handlers/collection"
|
8
8
|
|
9
9
|
module Stenotype
|
10
10
|
#
|
@@ -24,9 +24,7 @@ module Stenotype
|
|
24
24
|
#
|
25
25
|
# @param handler {#publish} A handler with implemented method [#publish]
|
26
26
|
#
|
27
|
-
|
28
|
-
known.register(handler)
|
29
|
-
end
|
27
|
+
delegate :register, to: :known
|
30
28
|
end
|
31
29
|
end
|
32
30
|
end
|
@@ -9,6 +9,18 @@ module Stenotype
|
|
9
9
|
# @attr_reader {Object} context A context in which the event was emitted
|
10
10
|
# @attr_reader {Hash} options A hash of additional options
|
11
11
|
#
|
12
|
+
# @example Defining a custom Handler
|
13
|
+
# class MyCustomHandler < Stenotype::ContextHandlers::Base
|
14
|
+
# self.context_name = :custom_handler
|
15
|
+
#
|
16
|
+
# def as_json(*_args)
|
17
|
+
# {
|
18
|
+
# value1: context.value1,
|
19
|
+
# value2: context.value2
|
20
|
+
# }
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
12
24
|
class Base
|
13
25
|
attr_reader :context, :options
|
14
26
|
|
@@ -18,7 +30,7 @@ module Stenotype
|
|
18
30
|
#
|
19
31
|
# @return {#as_json} A context handler implementing [#as_json]
|
20
32
|
#
|
21
|
-
def initialize(context, options
|
33
|
+
def initialize(context, options: {})
|
22
34
|
@context = context
|
23
35
|
@options = options
|
24
36
|
end
|
@@ -43,8 +55,7 @@ module Stenotype
|
|
43
55
|
# @raise {NotImplementedError} in case handler name is not specified.
|
44
56
|
#
|
45
57
|
def handler_name
|
46
|
-
@handler_name || raise(NotImplementedError,
|
47
|
-
"Please, specify the handler_name of #{self}")
|
58
|
+
@handler_name || raise(NotImplementedError, "Please, specify the handler_name of #{self}")
|
48
59
|
end
|
49
60
|
end
|
50
61
|
end
|
@@ -4,61 +4,105 @@ module Stenotype
|
|
4
4
|
module ContextHandlers
|
5
5
|
#
|
6
6
|
# A class representing a list of available context handlers
|
7
|
+
# @example Complete usage overview
|
8
|
+
# class MyCustomHander
|
9
|
+
# self.handler_name = :custom_handler
|
7
10
|
#
|
8
|
-
|
11
|
+
# def as_json(*_args)
|
12
|
+
# {
|
13
|
+
# key: :value
|
14
|
+
# }
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# collection = Stenotype::ContextHandlers::Collection.new
|
19
|
+
# collection.register(MyCustomHandler)
|
20
|
+
#
|
21
|
+
# collection.registered?(MyCustomHandler) #=> true
|
22
|
+
# collection.choose(handler_name: :custom_handler) #=> MyCustomHandler
|
23
|
+
# collection.unregister(MyCustomHandler)
|
24
|
+
# collection.registered?(MyCustomHandler) #=> false
|
25
|
+
#
|
26
|
+
# collection.register(SomeRandomClass) #=> ArgumentError
|
27
|
+
# collection.unregister(SomeRandomClass) #=> ArgumentError
|
28
|
+
# collection.choose(handler_name: :unknown) #=> Stenotype::UnknownHandlerError
|
29
|
+
#
|
30
|
+
class Collection < ::Collectible::CollectionBase
|
31
|
+
#
|
32
|
+
# Return a handler with given handler_name if found in collection,
|
33
|
+
# raises if a handler is not registered
|
9
34
|
#
|
10
35
|
# @param handler_name {Symbol} a handler to be found in the collection
|
11
|
-
# @raise {Stenotype::
|
36
|
+
# @raise {Stenotype::Errors::UnknownHandler} in case a handler is not registered
|
12
37
|
# @return {#as_json} A handler which respond to #as_json
|
13
38
|
#
|
39
|
+
# @example When a handler is present in the collection
|
40
|
+
# collection = Stenotype::ContextHandlers::Collection.new
|
41
|
+
# collection.register(MyCustomHandler) # with handler_name = :custom_handler
|
42
|
+
# collection.choose(handler_name: :custom_handler) #=> MyCustomHandler
|
43
|
+
#
|
44
|
+
# @example When a handler is not present in the collection
|
45
|
+
# collection = Stenotype::ContextHandlers::Collection.new
|
46
|
+
# collection.choose(handler_name: :custom_handler) #=> MyCustomHandler
|
47
|
+
#
|
14
48
|
def choose(handler_name:)
|
15
|
-
handler =
|
16
|
-
handler || raise(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
49
|
+
handler = find_by(handler_name: handler_name)
|
50
|
+
handler || raise(
|
51
|
+
Stenotype::UnknownHandlerError,
|
52
|
+
"Handler '#{handler_name}' is not found. "\
|
53
|
+
"Please make sure the handler you've specified is "\
|
54
|
+
"registered in the list of known handlers. "\
|
55
|
+
"See #{Stenotype::ContextHandlers} for more information.",
|
56
|
+
)
|
21
57
|
end
|
22
58
|
|
23
59
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
60
|
+
# @!method register(handler)
|
61
|
+
# Registers a new handler.
|
62
|
+
# @!scope instance
|
63
|
+
# @param handler {#as_json} a new handler to be added to collection
|
64
|
+
# @return {Stenotype::ContextHandlers::Collection} a collection object
|
65
|
+
# @example Registering a new handler
|
66
|
+
# collection = Stenotype::ContextHandlers::Collection.new
|
67
|
+
# collection.register(MyCustomHandler) # with handler_name = :custom_handler
|
27
68
|
#
|
28
|
-
|
29
|
-
unless handler < Stenotype::ContextHandlers::Base
|
30
|
-
raise ArgumentError,
|
31
|
-
"Handler must inherit from #{Stenotype::ContextHandlers::Base}, " \
|
32
|
-
"but inherited from #{handler.superclass}"
|
33
|
-
end
|
34
|
-
|
35
|
-
push(handler) unless registered?(handler)
|
36
|
-
self
|
37
|
-
end
|
69
|
+
alias_method :register, :push
|
38
70
|
|
71
|
+
#
|
72
|
+
# Removes a registered handler.
|
73
|
+
#
|
74
|
+
# @example Register and unregister a handler
|
75
|
+
# collection = Stenotype::ContextHandlers::Collection.new
|
76
|
+
# collection.register(MyCustomHandler) # with handler_name = :custom_handler
|
77
|
+
# collection.unregister(MyCustomHandler) # removes MyCustomHandler from the list of handlers
|
39
78
|
#
|
40
79
|
# @param handler {#as_json} a handler to be removed from the collection of known handlers
|
41
|
-
# @raise {ArgumentError} in case handler does not inherit from {Stenotype::ContextHandlers::Base}
|
42
80
|
# @return {Stenotype::ContextHandlers::Collection} a collection object
|
43
81
|
#
|
82
|
+
# @todo Add delete to the collectible delegation list
|
83
|
+
# and then use `alias_method :unregister, :delete`
|
44
84
|
def unregister(handler)
|
45
|
-
|
46
|
-
raise ArgumentError,
|
47
|
-
"Handler must inherit from #{Stenotype::ContextHandlers::Base}, " \
|
48
|
-
"but inherited from #{handler.superclass}"
|
49
|
-
end
|
50
|
-
|
51
|
-
delete(handler) if registered?(handler)
|
85
|
+
items.delete(handler)
|
52
86
|
self
|
53
87
|
end
|
54
88
|
|
55
89
|
#
|
56
|
-
#
|
57
|
-
#
|
90
|
+
# @!method registered?(handler)
|
91
|
+
# Checks whether a given handler is registered in collection
|
92
|
+
# @!scope instance
|
93
|
+
# @param handler {#as_json} a handler to be checked for presence in a collection
|
94
|
+
# @return [true, false]
|
95
|
+
# @example When a handler is registered
|
96
|
+
# collection = Stenotype::ContextHandlers::Collection.new
|
97
|
+
# collection.register(MyCustomHandler) # with handler_name = :custom_handler
|
98
|
+
# collection.registered?(MyCustomHandler) # true
|
58
99
|
#
|
59
|
-
|
60
|
-
|
61
|
-
|
100
|
+
# @example When a handler is not registered
|
101
|
+
# collection = Stenotype::ContextHandlers::Collection.new
|
102
|
+
# collection.registered?(UnregisteredHandler) # false
|
103
|
+
#
|
104
|
+
|
105
|
+
alias_method :registered?, :include?
|
62
106
|
end
|
63
107
|
end
|
64
108
|
end
|
@@ -22,21 +22,13 @@ module Stenotype
|
|
22
22
|
def as_json(*_args)
|
23
23
|
{
|
24
24
|
job_id: job_id,
|
25
|
-
enqueued_at: Time.now,
|
25
|
+
enqueued_at: Time.now.utc,
|
26
26
|
queue_name: queue_name,
|
27
|
-
class: context.class.name
|
27
|
+
class: context.class.name,
|
28
28
|
}
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
def job_id
|
34
|
-
context.job_id
|
35
|
-
end
|
36
|
-
|
37
|
-
def queue_name
|
38
|
-
context.queue_name
|
39
|
-
end
|
31
|
+
delegate :job_id, :queue_name, to: :context
|
40
32
|
end
|
41
33
|
end
|
42
34
|
end
|
@@ -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,162 @@
|
|
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(data = {}, method: caller_locations.first.label, eval_context: nil)
|
37
|
+
# A method injected into target class to manually track events
|
38
|
+
# @!scope instance
|
39
|
+
# @param data {Hash} Data to be sent to the targets
|
40
|
+
# @param method {String} An optional method name
|
41
|
+
# @param eval_context {Hash} A hash linking object to context handler
|
42
|
+
# @return {Stenotype::Event} An instance of emitted event
|
43
|
+
# @example Usage of emit_event
|
44
|
+
# class SomeRubyClass
|
45
|
+
# include Stenotype::Emitter
|
46
|
+
#
|
47
|
+
# def some_method
|
48
|
+
# data = collection_data
|
49
|
+
# emit_event(data, eval_context: self) # Track event with given data
|
50
|
+
# data
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
|
55
|
+
#
|
56
|
+
# @!method emit_event_before(*methods)
|
57
|
+
# A class method injected into target class to track instance methods invocation
|
58
|
+
# @param methods {Array<Symbol>} A list of method before which an event will be emitted
|
59
|
+
# @!scope class
|
60
|
+
# @example Usage of emit_event_before
|
61
|
+
# class SomeRubyClass
|
62
|
+
# include Stenotype::Emitter
|
63
|
+
# emit_event_before :some_method # Triggers an event upon calling some_method
|
64
|
+
#
|
65
|
+
# def some_method
|
66
|
+
# # do something
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
|
71
|
+
#
|
72
|
+
# @!method emit_klass_event_before(*class_methods)
|
73
|
+
# A class method injected into a target class to track class methods invocation
|
74
|
+
# @!scope class
|
75
|
+
# @param class_methods {Array<Symbol>} A list of class method before which
|
76
|
+
# an event will be emitted
|
77
|
+
# @example Usage emit_klass_event_before
|
78
|
+
# class SomeRubyClass
|
79
|
+
# include Stenotype::Emitter
|
80
|
+
# emit_klass_event_before :some_method # Triggers an event upon calling some_method
|
81
|
+
#
|
82
|
+
# def self.some_method
|
83
|
+
# # do something
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
|
88
|
+
#
|
89
|
+
# Adds an instance method: [#emit_event] to a target class
|
90
|
+
# where {Stenotype::Emitter} in included in
|
91
|
+
#
|
92
|
+
def self.build_instance_methods(instance_mod)
|
93
|
+
instance_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
94
|
+
def emit_event(data = {}, method: caller_locations.first.label, eval_context: nil)
|
95
|
+
Stenotype::Event.emit!(
|
96
|
+
{
|
97
|
+
type: "class_instance",
|
98
|
+
**data,
|
99
|
+
},
|
100
|
+
options: {
|
101
|
+
class: self.class.name,
|
102
|
+
method: method.to_sym
|
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
|
+
{ type: "class_instance" },
|
124
|
+
options: {
|
125
|
+
class: self.class.name,
|
126
|
+
method: __method__
|
127
|
+
},
|
128
|
+
eval_context: { klass: self }
|
129
|
+
)
|
130
|
+
super(*args, **rest_args, &block)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
send(:prepend, proxy)
|
136
|
+
end
|
137
|
+
|
138
|
+
def emit_klass_event_before(*class_methods)
|
139
|
+
proxy = const_get(:ClassProxy)
|
140
|
+
|
141
|
+
class_methods.each do |method|
|
142
|
+
proxy.module_eval do
|
143
|
+
define_method(method) do |*args, **rest_args, &block|
|
144
|
+
Stenotype::Event.emit!(
|
145
|
+
{ type: "class" },
|
146
|
+
options: {
|
147
|
+
class: self.name,
|
148
|
+
method: __method__
|
149
|
+
},
|
150
|
+
eval_context: { klass: self }
|
151
|
+
)
|
152
|
+
super(*args, **rest_args, &block)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
singleton_class.send(:prepend, proxy)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
RUBY
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/lib/stenotype/event.rb
CHANGED
@@ -8,8 +8,7 @@ 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
14
|
# @param data {Hash} Data to be published to the targets.
|
@@ -27,9 +26,10 @@ module Stenotype
|
|
27
26
|
attr_reader :data, :options, :eval_context, :dispatcher
|
28
27
|
|
29
28
|
#
|
30
|
-
# @example
|
31
|
-
#
|
32
|
-
#
|
29
|
+
# @example Create an event
|
30
|
+
# event = Stenotype::Event.new(data, options, eval_context)
|
31
|
+
# @example Create an event with custom dispatcher
|
32
|
+
# event = Stenotype::Event.new(data, options, eval_context, dispatcher: MyDispatcher.new)
|
33
33
|
#
|
34
34
|
# @param {Hash} data Data to be published to the targets.
|
35
35
|
# @param {Hash} options A hash of additional options to be tracked.
|
@@ -41,16 +41,15 @@ module Stenotype
|
|
41
41
|
@data = data
|
42
42
|
@options = options
|
43
43
|
@eval_context = eval_context
|
44
|
-
@dispatcher = dispatcher
|
44
|
+
@dispatcher = dispatcher.new
|
45
45
|
end
|
46
46
|
|
47
47
|
#
|
48
48
|
# Emits a {Stenotype::Event}.
|
49
49
|
#
|
50
|
-
# @example
|
51
|
-
#
|
50
|
+
# @example Emit an instance of event
|
52
51
|
# event = Stenotype::Event.new(data, options, eval_context)
|
53
|
-
# event.emit!
|
52
|
+
# event.emit! #=> Publishes the event to targets
|
54
53
|
#
|
55
54
|
def emit!
|
56
55
|
dispatcher.publish(self)
|