event_tracer 0.3.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5207c9165b5c41811f3128fdf9d22c1dd21f805131aab1d2dfa2861bc55a43b
4
- data.tar.gz: 3cda4e4b5dcb3c994797e9cae5ef3d31253e49d09a5d7527b49f107fc430e1df
3
+ metadata.gz: c33ea83663eda9d96f8055d957ab688d6be6f957023ea689b5f119abedfb7ad8
4
+ data.tar.gz: f6f328085fdc3de07dafa12e27bac3752b18249bcd5bbd3cd81046222c853f19
5
5
  SHA512:
6
- metadata.gz: 6333e12e55d047a9d4b6f5a6cb8af0781218a5be884b06a1b96fa3e994a73901f72d14e47269929df96284c0a8bb5ffc8f7aa6677413d2952a87b1b54b9f81c4
7
- data.tar.gz: 8a27bbcf11291c1ca89c39944b784fb44661b00496f10412da4efbe933c7a014bac7a7fa186d55580e0fd2e766b1fcbe18565c13475091d1a01e6c1d0369c653
6
+ metadata.gz: 49bf70ea7a94f1a88410b8b014c77763fd4f9f581503fb48500c07e02000db3325c3b113baa1df5f733b1326e02e28441137512972f8e1b8c07a0917f662c59c
7
+ data.tar.gz: 70a852a9f2de92aa90ce63381ac7b63252d0f889b9f0bf34eb51d4d43e4e9740b5532029571992150ebb50dae035fb1a9a5b12383c3e48eaeb25c15045cffcb5
@@ -0,0 +1,65 @@
1
+ require 'concurrent'
2
+
3
+ module EventTracer
4
+ # This is an implementation of buffer storage. We use Concurrent::Array underneath
5
+ # to ensure thread-safe behavior. Data is stored until certain size / interval
6
+ # before flushing.
7
+ #
8
+ # Caveats: We should only store non-important data like logs in this buffer
9
+ # because if a process is killed, the data in this buffer is lost.
10
+ class Buffer
11
+ # Buffer can store maximum 10 items.
12
+ # Bigger size requires more memory to store, so choose a reasonable number
13
+ DEFAULT_BUFFER_SIZE = 10
14
+ # An item can live in buffer for at least 10s between each `Buffer#add` if the buffer is not full
15
+ # If there are larger interval between the calls, it can live longer.
16
+ DEFAULT_FLUSH_INTERVAL = 10
17
+
18
+ def initialize(
19
+ buffer_size: DEFAULT_BUFFER_SIZE,
20
+ flush_interval: DEFAULT_FLUSH_INTERVAL
21
+ )
22
+ @buffer_size = buffer_size
23
+ @flush_interval = flush_interval
24
+ @buffer = Concurrent::Array.new
25
+ end
26
+
27
+ # Add an item to buffer
28
+ #
29
+ # @param item: data to be added to buffer
30
+ # @return true if the item can be added, otherwise false
31
+ def add(item)
32
+ if add_item?
33
+ buffer.push({ item: item, created_at: Time.now })
34
+ true
35
+ else
36
+ false
37
+ end
38
+ end
39
+
40
+ # Remove all existing items from buffer
41
+ #
42
+ # @return all items in buffer
43
+ def flush
44
+ data = []
45
+
46
+ data << buffer.shift[:item] until buffer.empty?
47
+
48
+ data
49
+ end
50
+
51
+ # This method is only used to facilitate testing
52
+ def size
53
+ buffer.size
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :buffer_size, :flush_interval, :buffer
59
+
60
+ def add_item?
61
+ buffer.size < buffer_size &&
62
+ (buffer.empty? || buffer.first[:created_at] > Time.now - flush_interval)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module EventTracer
6
+ class BufferedLogger
7
+ def initialize(log_processor:, worker:, buffer: Buffer.new(buffer_size: 0))
8
+ @buffer = buffer
9
+ @worker = worker
10
+ @log_processor = log_processor
11
+ end
12
+
13
+ EventTracer::LOG_TYPES.each do |log_type|
14
+ define_method log_type do |**args|
15
+ save_message log_type, **args
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :buffer, :log_processor, :worker
22
+
23
+ def save_message(log_type, action:, message:, **args)
24
+ payload = log_processor.call(log_type, action: action, message: message, args: args)
25
+
26
+ unless buffer.add(payload)
27
+ all_payloads = buffer.flush + [payload]
28
+ execute_payload(all_payloads)
29
+ end
30
+
31
+ LogResult.new(true)
32
+ end
33
+
34
+ def execute_payload(payloads)
35
+ worker.perform_async(payloads)
36
+ rescue JSON::GeneratorError => e
37
+ filtered_payloads = filter_invalid_data(payloads)
38
+
39
+ EventTracer.warn(
40
+ loggers: %i(base),
41
+ action: self.class.name,
42
+ app: EventTracer::Config.config.app_name,
43
+ error: e.class.name,
44
+ message: e.message,
45
+ payload: payloads - filtered_payloads
46
+ )
47
+
48
+ worker.perform_async(filtered_payloads) if filtered_payloads.any?
49
+ end
50
+
51
+ def filter_invalid_data(payloads)
52
+ payloads.select { |payload| payload.to_json rescue false }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,14 @@
1
+ require 'dry-configurable'
2
+
3
+ module EventTracer
4
+ class Config
5
+ extend Dry::Configurable
6
+
7
+ setting :app_name, default: 'app_name'
8
+
9
+ # TODO: switch to namespace in v1.0
10
+ setting :dynamo_db_table_name, default: 'logs'
11
+ setting :dynamo_db_client
12
+ setting :dynamo_db_queue_name, default: 'low'
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module EventTracer
2
+ module DynamoDB
3
+ class Client
4
+ class << self
5
+ extend Gem::Deprecate
6
+
7
+ def call
8
+ Aws::DynamoDB::Client.new
9
+ end
10
+ deprecate :call, 'EventTracer::Config.config.dynamo_db_client', 2021, 12
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module EventTracer
2
+ module DynamoDB
3
+ class DefaultProcessor
4
+ def call(log_type, action:, message:, args:)
5
+ args.merge(
6
+ timestamp: Time.now.utc.iso8601(6),
7
+ action: action,
8
+ message: message,
9
+ log_type: log_type,
10
+ app: EventTracer::Config.config.app_name
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require_relative 'client'
5
+ require_relative 'worker'
6
+ require_relative 'default_processor'
7
+
8
+ module EventTracer
9
+ module DynamoDB
10
+ class Logger < BufferedLogger
11
+ def initialize(buffer: Buffer.new(buffer_size: 0), log_processor: DefaultProcessor.new)
12
+ super(buffer: buffer, log_processor: log_processor, worker: Worker)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'client'
4
+
5
+ begin
6
+ require 'sidekiq'
7
+ require 'aws-sdk-dynamodb'
8
+ rescue LoadError => e
9
+ puts "Please add the missing gem into your app Gemfile: #{e.message}"
10
+ raise
11
+ end
12
+
13
+ module EventTracer
14
+ module DynamoDB
15
+ class Worker
16
+ include ::Sidekiq::Worker
17
+
18
+ sidekiq_options retry: 1, queue: EventTracer::Config.config.dynamo_db_queue_name
19
+
20
+ # See https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#batch_write_item-instance_method
21
+ MAX_DYNAMO_DB_ITEM_PER_REQUEST = 25
22
+
23
+ def initialize(client = nil)
24
+ @config = EventTracer::Config.config
25
+ @client = client || @config.dynamo_db_client || Client.call
26
+ end
27
+
28
+ def perform(items)
29
+ wrap(items).each_slice(MAX_DYNAMO_DB_ITEM_PER_REQUEST) do |batch|
30
+ data = batch.map do |item|
31
+ { put_request: { item: clean_empty_values(item) } }
32
+ end
33
+
34
+ client.batch_write_item(
35
+ request_items: { config.dynamo_db_table_name => data }
36
+ )
37
+
38
+ rescue Aws::DynamoDB::Errors::ServiceError => e
39
+ EventTracer.error(
40
+ loggers: %i(base),
41
+ action: 'DynamoDBWorker',
42
+ app: EventTracer::Config.config.app_name,
43
+ error: e.class.name,
44
+ message: e.message
45
+ )
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :client, :config
52
+
53
+ def wrap(items)
54
+ # NOTE: This allows us to handle either buffered or unbuffered payloads
55
+ if items.is_a?(Hash)
56
+ [items]
57
+ else
58
+ Array(items)
59
+ end
60
+ end
61
+
62
+ # dynamo can't serialise empty strings/ non-zero numerics
63
+ def clean_empty_values(data)
64
+ data.delete_if do |_key, value|
65
+ case value
66
+ when Hash
67
+ clean_empty_values(value)
68
+ false
69
+ when String then value.empty?
70
+ else false
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -1,3 +1,3 @@
1
1
  module EventTracer
2
- VERSION = '0.3.1'.freeze
2
+ VERSION = '0.4.2'.freeze
3
3
  end
data/lib/event_tracer.rb CHANGED
@@ -3,7 +3,7 @@ require 'event_tracer/log_result'
3
3
 
4
4
  module EventTracer
5
5
 
6
- LOG_TYPES ||= %i(info warn error)
6
+ LOG_TYPES = %i(info warn error)
7
7
 
8
8
  @loggers = {}
9
9
 
@@ -70,4 +70,4 @@ module EventTracer
70
70
  end
71
71
 
72
72
  project_root = File.dirname(File.absolute_path(__FILE__))
73
- Dir.glob("#{project_root}/event_tracer/*") {|file| require file}
73
+ Dir.glob("#{project_root}/event_tracer/*.rb") {|file| require file}
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: event_tracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - melvrickgoh
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-19 00:00:00.000000000 Z
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-configurable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -53,7 +81,7 @@ dependencies:
53
81
  - !ruby/object:Gem::Version
54
82
  version: '3.0'
55
83
  description: 'Thin wrapper for formatted logging/ metric services to be used as a
56
- single service. External service(s) supported: Appsignal'
84
+ single service. External service(s) supported: Appsignal, Datadog, DynamoDB'
57
85
  email:
58
86
  - melvrickgoh@hotmail.com
59
87
  executables: []
@@ -64,7 +92,14 @@ files:
64
92
  - lib/event_tracer/appsignal_logger.rb
65
93
  - lib/event_tracer/base_logger.rb
66
94
  - lib/event_tracer/basic_decorator.rb
95
+ - lib/event_tracer/buffer.rb
96
+ - lib/event_tracer/buffered_logger.rb
97
+ - lib/event_tracer/config.rb
67
98
  - lib/event_tracer/datadog_logger.rb
99
+ - lib/event_tracer/dynamo_db/client.rb
100
+ - lib/event_tracer/dynamo_db/default_processor.rb
101
+ - lib/event_tracer/dynamo_db/logger.rb
102
+ - lib/event_tracer/dynamo_db/worker.rb
68
103
  - lib/event_tracer/log_result.rb
69
104
  - lib/event_tracer/version.rb
70
105
  homepage: https://github.com/melvrickgoh/event_tracer