stenotype 0.1.0 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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