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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +3 -2
  4. data/CHANGELOG.md +43 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +111 -60
  7. data/README.md +48 -17
  8. data/Rakefile +2 -2
  9. data/TODO.md +18 -0
  10. data/bin/console +3 -3
  11. data/lib/generators/USAGE +8 -0
  12. data/lib/generators/stenotype/initializer/initializer_generator.rb +23 -0
  13. data/lib/generators/stenotype/initializer/templates/initializer.rb.erb +82 -0
  14. data/lib/stenotype.rb +24 -85
  15. data/lib/stenotype/adapters.rb +3 -3
  16. data/lib/stenotype/adapters/base.rb +25 -2
  17. data/lib/stenotype/adapters/google_cloud.rb +70 -22
  18. data/lib/stenotype/adapters/stdout_adapter.rb +36 -2
  19. data/lib/stenotype/at_exit.rb +8 -0
  20. data/lib/stenotype/configuration.rb +127 -36
  21. data/lib/stenotype/context_handlers.rb +6 -8
  22. data/lib/stenotype/context_handlers/base.rb +14 -3
  23. data/lib/stenotype/context_handlers/collection.rb +78 -34
  24. data/lib/stenotype/context_handlers/rails/active_job.rb +3 -11
  25. data/lib/stenotype/context_handlers/rails/controller.rb +10 -11
  26. data/lib/stenotype/dispatcher.rb +1 -2
  27. data/lib/stenotype/emitter.rb +166 -0
  28. data/lib/stenotype/event.rb +48 -25
  29. data/lib/stenotype/event_serializer.rb +31 -11
  30. data/lib/stenotype/frameworks/rails/action_controller.rb +44 -21
  31. data/lib/stenotype/frameworks/rails/active_job.rb +3 -5
  32. data/lib/stenotype/railtie.rb +37 -0
  33. data/lib/stenotype/version.rb +1 -1
  34. data/stenotype.gemspec +30 -26
  35. metadata +70 -19
  36. data/lib/stenotype/exceptions.rb +0 -31
  37. data/lib/stenotype/frameworks/object_ext.rb +0 -145
@@ -5,12 +5,46 @@ module Stenotype
5
5
  #
6
6
  # An adapter implementing method {#publish} to send data to STDOUT
7
7
  #
8
+ # @example
9
+ # class SomeClassWithEvents
10
+ # def method_emitting_enent
11
+ # result_of_calculations = collect_some_data
12
+ # # This will print the data to STDOUT by default
13
+ # stdout_adapter.publish(result_of_calculation, additional: :data, more: :data)
14
+ # result_of_calculations
15
+ # end
16
+ #
17
+ # def stdout_adapter
18
+ # Stenotype::Adapters::StdoutAdapter.new
19
+ # end
20
+ # end
21
+ #
8
22
  class StdoutAdapter < Base
9
23
  #
10
24
  # @param event_data {Hash} The data to be published to STDOUT
11
25
  #
12
- def publish(event_data, **additional_arguments)
13
- client.info(event_data, **additional_arguments)
26
+ # @example Publishing to default client (STDOUT)
27
+ # adapter = Stenotype::Adapters::StdoutAdapter.new
28
+ # adapter.publish({ event: :data }, { additional: :data })
29
+ #
30
+ # @example Publishing to custom client (STDERR)
31
+ # adapter = Stenotype::Adapters::StdoutAdapter.new(client: STDERR)
32
+ # adapter.publish({ event: :data }, { additional: :data })
33
+ #
34
+ def publish(event_data, **additional_attrs)
35
+ client.info("[Stenotype::Event] emitted with the following attributes") do
36
+ {
37
+ **event_data,
38
+ **additional_attrs,
39
+ }
40
+ end
41
+ end
42
+
43
+ #
44
+ # Does nothing
45
+ #
46
+ def flush!
47
+ # noop
14
48
  end
15
49
 
16
50
  private
@@ -0,0 +1,8 @@
1
+ require 'stenotype'
2
+
3
+ # :nocov:
4
+ at_exit do
5
+ targets = Stenotype.config.targets
6
+ targets.each(&:flush!)
7
+ end
8
+ # :nocov:
@@ -4,46 +4,137 @@ module Stenotype
4
4
  #
5
5
  # A module containing freshly-event gem configuration
6
6
  #
7
+ # @example Configuring the library
8
+ # Stenotype.configure do |config|
9
+ # config.enabled = true
10
+ # config.targets = [Target1.new, Target2.new]
11
+ # config.uuid_generator = SecureRandom
12
+ #
13
+ # config.google_cloud do |gc|
14
+ # gc.credentials = "abc"
15
+ # gc.project_id = "project"
16
+ # gc.topic = "42"
17
+ # gc.async = true
18
+ # end
19
+ #
20
+ # config.rails do |rc|
21
+ # rc.enable_action_controller_ext = true
22
+ # rc.enable_active_job_ext = false
23
+ # end
24
+ # end
25
+ #
7
26
  module Configuration
8
- class << self
9
- # @return {Array<#publish>} a list of targets responding to method [#publish]
10
- attr_writer :targets
11
- # @return {Sting} a string with GC API credential. Refer to GC PubSub documentation
12
- attr_accessor :gc_credentials
13
- # @return {String} a name of the project in GC PubSub
14
- attr_accessor :gc_project_id
15
- # @return {String} a name of the topic in GC PubSub
16
- attr_accessor :gc_topic
17
- # @return [:sync, :async] GC publish mode
18
- attr_accessor :gc_mode
19
- # @return {#publish} as object responding to method [#publish]
20
- attr_accessor :dispatcher
21
- # @return {#uuid} an object responding to method [#uuid]
22
- attr_accessor :uuid_generator
23
-
24
- #
25
- # Yields control to the caller
26
- # @yield {Stenotype::Configuration}
27
- # @return {Stenotype::Configuration}
28
- #
29
- def configure
30
- yield self
31
- self
27
+ extend Spicerack::Configurable
28
+
29
+ # @!attribute graceful_error_handling
30
+ # @return {true, false} a flag for suppressing error raised withing the gem
31
+
32
+ # @!attribute logger
33
+ # @return {Logger} a logger with default severity methods to output gem level messages
34
+
35
+ # @!attribute enabled
36
+ # @return {true, false} a flag indicating whether event emission is enabled
37
+
38
+ # @!attribute targets
39
+ # @return {Array<#publish>} a list of targets responding to method [#publish]
40
+
41
+ # @!attribute [rw] dispatcher
42
+ # @return {#publish} as object responding to method [#publish]
43
+
44
+ # @!attribute [rw] uuid_generator
45
+ # @return {#uuid} an object responding to method [#uuid]
46
+
47
+ # @!attribute [rw] google_cloud
48
+ # @return [NestedConfiguration] google cloud configuration.
49
+
50
+ # @!attribute [rw] google_cloud.credentials
51
+ # @return {String} a string with GC API credential. Refer to GC PubSub documentation
52
+
53
+ # @!attribute [rw] google_cloud.project_id
54
+ # @return {String} a name of the project in GC PubSub
55
+
56
+ # @!attribute [rw] google_cloud.topic
57
+ # @return {String} a name of the topic in GC PubSub
58
+
59
+ # @!attribute [rw] google_cloud.async
60
+ # @return [true, false] GC publish mode, either async if true, sync if false
61
+
62
+
63
+ # @!attribute [rw] rails
64
+ # @return [NestedConfiguration] Rails configuration.
65
+
66
+ # @!attribute [rw] rails.enable_action_controller_ext
67
+ # @return [true, false] A flag of whether ActionController ext is enabled
68
+
69
+ # @!attribute [rw] rails.enable_active_job_ext
70
+ # @return [true, false] A flag of whether ActiveJob ext is enabled
71
+
72
+ configuration_options do
73
+ option :graceful_error_handling, default: true
74
+ option :enabled, default: true
75
+ option :targets, default: []
76
+ option :dispatcher, default: Stenotype::Dispatcher
77
+ option :uuid_generator, default: SecureRandom
78
+ option :logger
79
+
80
+ nested :google_cloud do
81
+ option :credentials, default: nil
82
+ option :project_id, default: nil
83
+ option :topic, default: nil
84
+ option :async, default: true
32
85
  end
33
86
 
34
- #
35
- # @raise {Stenotype::Exceptions::NoTargetsSpecified} in case no targets are configured
36
- # @return {Array<#publish>} An array of targets implementing method [#publish]
37
- #
38
- def targets
39
- if @targets.nil? || @targets.empty?
40
- raise Stenotype::Exceptions::NoTargetsSpecified,
41
- 'Please configure a target(s) for events to be sent to. ' \
42
- 'See Stenotype::Configuration for reference.'
43
- else
44
- @targets
45
- end
87
+ nested :rails do
88
+ option :enable_action_controller_ext, default: true
89
+ option :enable_active_job_ext, default: true
46
90
  end
47
91
  end
92
+
93
+ module_function
94
+
95
+ #
96
+ # @example With default logger
97
+ # Stenotype.configure do |config|
98
+ # # config.logger = nil # logger not set manually
99
+ # end
100
+ # Stenotype.config.logger #=> `Logger.new(STDOUT)` instance
101
+ #
102
+ # @example With custom logger
103
+ # Stenotype.configure do |config|
104
+ # config.logger = custom_logger_instance
105
+ # end
106
+ # Stenotype.config.logger #=> custom_logger_instance
107
+ #
108
+ # @return [{Logger, CustomLogger}] a logger object. Logger.new(STDOUT) by
109
+ # default if another is not set during configuration
110
+ #
111
+ def logger
112
+ return config.logger if config.logger
113
+ config.logger || Logger.new(STDOUT)
114
+ end
115
+
116
+ #
117
+ # @example When at least one target is present
118
+ # Stenotype.configure do |config|
119
+ # config.targets = [Target1.new, Target2.new]
120
+ # end
121
+ # Stenotype.config.targets #=> [target_obj1, target_obj2]
122
+ #
123
+ # @example When no targets have been configured
124
+ # Stenotype.configure { |config| config.targets = [] }
125
+ # Stenotype.config.targets #=> Stenotype::NoTargetsSpecifiedError
126
+ #
127
+ # @raise {Stenotype::NoTargetsSpecifiedError} in case no targets are configured
128
+ # @return {Array<#publish>} An array of targets implementing method [#publish]
129
+ #
130
+ # @todo THIS NEVER GETS CALLED, needs a fix
131
+ #
132
+ def targets
133
+ return config.targets unless config.targets.empty?
134
+
135
+ raise Stenotype::NoTargetsSpecifiedError,
136
+ "Please configure a target(s) for events to be sent to. "\
137
+ "See #{Stenotype::Configuration} for reference."
138
+ end
48
139
  end
49
140
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
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'
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
- def register(handler)
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
- class Collection < Array
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::Exceptions::UnknownHandler} in case a handler is not registered
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 = detect { |e| e.handler_name == handler_name }
16
- handler || raise(Stenotype::Exceptions::UnknownHandler,
17
- "Handler '#{handler_name}' is not found. " \
18
- "Please make sure the handler you've specified is " \
19
- 'registered in the list of known handlers. ' \
20
- "See #{Stenotype::ContextHandlers} for more information.")
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
- # @param handler {#as_json} a new handler to be added to collection
25
- # @raise {ArgumentError} in case handler does not inherit from {Stenotype::ContextHandlers::Base}
26
- # @return {Stenotype::ContextHandlers::Collection} a collection object
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
- def register(handler)
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
- unless handler < Stenotype::ContextHandlers::Base
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
- # @param handler {#as_json} a handler to be checked for presence in a collection
57
- # @return [true, false]
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
- def registered?(handler)
60
- include?(handler)
61
- end
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
- private
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