startback 0.19.0 → 0.19.3

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: 8d8442d4dcacbab7c562a5656fce452ecb45907458cba4dd88e617ec56007dfd
4
- data.tar.gz: c01bfdc67f1dc66cf6e0e4173495b5102623b5a5dce5e9023d616432ecbe042f
3
+ metadata.gz: a46ae64f74d522af60d8efac5d0ea966587c556a12d791a4817c77a27b4eb74b
4
+ data.tar.gz: 528fd26764c05ae46e88c4009dcfe801ad8c8fa95b57eb3ae158ae205b26be49
5
5
  SHA512:
6
- metadata.gz: 02a69c56aa4ca38c959e5879de04045576fa992e281b398a71e08478919ff7a97f6d85e083259d540e4e555d095158c131867d7466ef923eabc8a3924b4d2cb2
7
- data.tar.gz: b9aadd2aaf265655d398e112775384da9f52c8a058830cffcbf93f69f2cd13312710fbef91e64c9c5ac20d1f502195627022b405616dd30092ced479d9d117b1
6
+ metadata.gz: e64ba6da11affe1a7639cd1c5bec8ff9b1088c74eaad3f08f70e7280c95a5af624bcd1bd3d9ca965aec844aaaae3957ada94bcb4ac55ebd0a133b6f0154a99b4
7
+ data.tar.gz: 405daa018fb688b1f00a2b814881cd7f30e7f43892d91d27ef4fbf0367579c160866674130ee5ac187002f0b36e978c840a9329721f9dd7b36668be1345cfd9e
@@ -7,7 +7,8 @@ module Startback
7
7
  # Prometheus exporter abstraction, that can be registered as an around
8
8
  # hook on OperationRunner and as a prometheus client on Context instances.
9
9
  #
10
- # The exporter uses the ruby client for prometheus to expose metrics regarding Operation runs.
10
+ # The exporter uses the ruby client for prometheus to expose metrics
11
+ # regarding Operation runs.
11
12
  #
12
13
  # The following metrics are exported:
13
14
  #
@@ -1,3 +1,7 @@
1
+ # For backward compatibility, may be removed in next released
2
+ require_relative 'listeners'
3
+ require_relative 'logger'
4
+
1
5
  module Startback
2
6
  module Caching
3
7
  #
@@ -22,12 +26,15 @@ module Startback
22
26
  # An EntityCache takes an actual store at construction. The object must meet the
23
27
  # specification writtern in Store. The 'cache' ruby gem can be used in practice.
24
28
  #
25
- # Cache hits, outdated and miss are logged in debug, info, and info severity.
26
- # The `cache_hit`, `cache_outdated`, `cache_miss` protected methods MAY be
27
- # overriden to change that behavior.
29
+ # This class supports listeners to track cache hits, misses, outdates and
30
+ # failures. Listeners can be provided at construction via the options, or by
31
+ # overriding the `default_listeners` method. The default implementation simply
32
+ # logs.
33
+ #
34
+ # By default, this class raises an error if something goes wrong with the cache.
35
+ # You can disable this by using the `raise_on_cache_fail` option.
28
36
  #
29
37
  class EntityCache
30
- include Support::Robustness
31
38
 
32
39
  class << self
33
40
 
@@ -40,9 +47,22 @@ module Startback
40
47
 
41
48
  end # class DSL
42
49
 
43
- def initialize(store, context = nil)
50
+ DEFAULT_OPTIONS = {
51
+
52
+ # Whether a cache fail raises an exception or not
53
+ raise_on_cache_fail: true,
54
+
55
+ # Default listeners to use, if any. When nil is used, `default_listener`
56
+ # method is used to create them.
57
+ listeners: nil,
58
+
59
+ }
60
+
61
+ def initialize(store, context = nil, options = {})
44
62
  @store = store
45
63
  @context = context
64
+ @options = DEFAULT_OPTIONS.merge(options)
65
+ @options[:listeners] ||= default_listeners
46
66
  end
47
67
  attr_reader :store, :context
48
68
 
@@ -66,6 +86,10 @@ module Startback
66
86
  load_entity(pkey).tap{|to_cache|
67
87
  store.set(cache_key, to_cache, caching_options)
68
88
  }
89
+ rescue => ex
90
+ cache_fail(pkey, ex)
91
+ raise if raise_on_cache_fail?
92
+ load_entity(pkey)
69
93
  end
70
94
 
71
95
  # Invalidates the cache under a given key.
@@ -75,18 +99,33 @@ module Startback
75
99
  store.delete(cache_key)
76
100
  end
77
101
 
102
+ protected
103
+
104
+ def raise_on_cache_fail?
105
+ @options[:raise_on_cache_fail]
106
+ end
107
+
108
+ def register(listener)
109
+ @options[:listeners].register(listener)
110
+ self
111
+ end
112
+
78
113
  protected
79
114
 
80
115
  def cache_hit(pkey, cached)
81
- log(:debug, self, "cache_hit", context, op_data: pkey)
116
+ @options[:listeners].cache_hit(self, context, pkey, cached)
82
117
  end
83
118
 
84
119
  def cache_outdated(pkey, cached)
85
- log(:info, self, "cache_outdated", context, op_data: pkey)
120
+ @options[:listeners].cache_outdated(self, context, pkey, cached)
86
121
  end
87
122
 
88
123
  def cache_miss(pkey)
89
- log(:info, self, "cache_miss", context, op_data: pkey)
124
+ @options[:listeners].cache_miss(self, context, pkey)
125
+ end
126
+
127
+ def cache_fail(pkey, ex)
128
+ @options[:listeners].cache_fail(self, context, pkey, ex)
90
129
  end
91
130
 
92
131
  def default_caching_options
@@ -152,6 +191,12 @@ module Startback
152
191
  raise NotImplementedError, "#{self.class.name}#load_entity"
153
192
  end
154
193
 
194
+ protected
195
+
196
+ def default_listeners
197
+ Listeners.new << Logger.new
198
+ end
199
+
155
200
  end # class EntityCache
156
201
  end # module Caching
157
202
  end # module Startback
@@ -0,0 +1,41 @@
1
+ module Startback
2
+ module Caching
3
+ class Listeners
4
+
5
+ def initialize
6
+ @listeners = []
7
+ end
8
+
9
+ def register(listener)
10
+ @listeners << listener
11
+ self
12
+ end
13
+ alias :<< :register
14
+
15
+ def cache_hit(*args, &bl)
16
+ @listeners.each do |l|
17
+ l.cache_hit(*args, &bl)
18
+ end
19
+ end
20
+
21
+ def cache_outdated(*args, &bl)
22
+ @listeners.each do |l|
23
+ l.cache_outdated(*args, &bl)
24
+ end
25
+ end
26
+
27
+ def cache_miss(*args, &bl)
28
+ @listeners.each do |l|
29
+ l.cache_miss(*args, &bl)
30
+ end
31
+ end
32
+
33
+ def cache_fail(*args, &bl)
34
+ @listeners.each do |l|
35
+ l.cache_fail(*args, &bl)
36
+ end
37
+ end
38
+
39
+ end # class Listeners
40
+ end # module Caching
41
+ end # module Startback
@@ -0,0 +1,24 @@
1
+ module Startback
2
+ module Caching
3
+ class Logger
4
+ include Support::Robustness
5
+
6
+ def cache_hit(entity_cache, context, pkey, cached)
7
+ log(:debug, entity_cache, "cache_hit", context, op_data: pkey)
8
+ end
9
+
10
+ def cache_outdated(entity_cache, context, pkey, cached)
11
+ log(:info, entity_cache, "cache_outdated", context, op_data: pkey)
12
+ end
13
+
14
+ def cache_miss(entity_cache, context, pkey)
15
+ log(:info, entity_cache, "cache_miss", context, op_data: pkey)
16
+ end
17
+
18
+ def cache_fail(entity_cache, context, pkey, ex)
19
+ log(:error, entity_cache, "cache_fail", context, op_data: pkey, error: ex)
20
+ end
21
+
22
+ end # class Logger
23
+ end # module Caching
24
+ end # module Startback
@@ -0,0 +1,53 @@
1
+ require 'prometheus/client'
2
+
3
+ module Startback
4
+ module Caching
5
+ class Prometheus
6
+
7
+ def initialize
8
+ @cache_hit_counter = cache_counter('hit')
9
+ @cache_outdated_counter = cache_counter('outdated')
10
+ @cache_miss_counter = cache_counter('miss')
11
+ @cache_fail_counter = cache_counter('fail')
12
+ end
13
+
14
+ def cache_hit(entity_cache, *args, &bl)
15
+ @cache_hit_counter.increment(labels: {
16
+ entity_cache: entity_cache.class.name,
17
+ })
18
+ end
19
+
20
+ def cache_outdated(entity_cache, *args, &bl)
21
+ @cache_outdated_counter.increment(labels: {
22
+ entity_cache: entity_cache.class.name,
23
+ })
24
+ end
25
+
26
+ def cache_miss(entity_cache, *args, &bl)
27
+ @cache_miss_counter.increment(labels: {
28
+ entity_cache: entity_cache.class.name,
29
+ })
30
+ end
31
+
32
+ def cache_fail(entity_cache, *args, &bl)
33
+ @cache_fail_counter.increment(labels: {
34
+ entity_cache: entity_cache.class.name,
35
+ })
36
+ end
37
+
38
+ private
39
+
40
+ def cache_counter(what)
41
+ name = "entity_cache_#{what}".to_sym
42
+ ::Prometheus::Client::Counter.new(
43
+ name,
44
+ docstring: "A counter of EntityCache #{what}",
45
+ labels: [:entity_cache],
46
+ ).tap do |counter|
47
+ ::Prometheus::Client.registry.register(counter)
48
+ end
49
+ end
50
+
51
+ end # class Prometheus
52
+ end # module Caching
53
+ end # module Startback
@@ -0,0 +1,6 @@
1
+ require_relative 'caching/store'
2
+ require_relative 'caching/no_store'
3
+ require_relative 'caching/listeners'
4
+ require_relative 'caching/logger'
5
+ require_relative 'caching/prometheus'
6
+ require_relative 'caching/entity_cache'
@@ -36,6 +36,21 @@ module Startback
36
36
  end
37
37
  module_function :env
38
38
 
39
+ def staging?
40
+ ENV['RACK_ENV'] == 'staging'
41
+ end
42
+ module_function :staging?
43
+
44
+ def production?
45
+ ENV['RACK_ENV'] =~ /^prod/
46
+ end
47
+ module_function :production?
48
+
49
+ def development?
50
+ ENV['RACK_ENV'] =~ /^dev/
51
+ end
52
+ module_function :development?
53
+
39
54
  end # module Env
40
55
  end # module Support
41
56
  end # module Startback
@@ -49,7 +49,7 @@ module Startback
49
49
  private
50
50
 
51
51
  def auto_pretty_print
52
- ENV['RACK_ENV'] == 'development'
52
+ development?
53
53
  end
54
54
 
55
55
  end # class LogFormatter
@@ -43,7 +43,10 @@ module Startback
43
43
  @@default_logger ||= begin
44
44
  l = ::Logger.new(STDOUT)
45
45
  l.formatter = LogFormatter.new
46
- l.warn(op: "#{self}", op_data: { msg: "Using default logger to STDOUT" })
46
+ l.warn(op: "#{self}", op_data: {
47
+ msg: "Using default logger to STDOUT",
48
+ caller: caller
49
+ })
47
50
  @@default_logger = l
48
51
  end
49
52
  @@default_logger
@@ -2,7 +2,7 @@ module Startback
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 19
5
- TINY = 0
5
+ TINY = 3
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
@@ -1,14 +1,13 @@
1
1
  require 'spec_helper'
2
- require 'startback/caching/entity_cache'
3
- require 'startback/caching/store'
2
+ require 'startback/caching'
4
3
  module Startback
5
4
  module Caching
6
5
  describe EntityCache do
7
6
 
8
7
  class BaseCache < EntityCache
9
8
 
10
- def initialize(context = nil)
11
- super(Store.new, context)
9
+ def initialize(options = {})
10
+ super(Store.new, context, options)
12
11
  @called = 0
13
12
  @last_key = nil
14
13
  end
@@ -76,9 +75,9 @@ module Startback
76
75
 
77
76
  describe "get" do
78
77
 
79
- subject{
78
+ subject do
80
79
  cache.get("a key")
81
- }
80
+ end
82
81
 
83
82
  it 'yields to load_raw_data only once with the short key' do
84
83
  expect(subject).to eql("a value")
@@ -87,6 +86,13 @@ module Startback
87
86
  expect(cache.last_key).to eql("a key")
88
87
  end
89
88
 
89
+ it 'raises when an error occurs' do
90
+ expect_any_instance_of(Store).to receive(:exist?).and_raise("Cache failed")
91
+ expect {
92
+ subject
93
+ }.to raise_error(/Cache failed/)
94
+ end
95
+
90
96
  end
91
97
 
92
98
  describe "primary_key" do
@@ -131,6 +137,48 @@ module Startback
131
137
 
132
138
  end
133
139
 
140
+ describe 'when disabling error raising' do
141
+ let(:cache) do
142
+ BaseCache.new(:raise_on_cache_fail => false)
143
+ end
144
+
145
+ subject do
146
+ cache.get("a key")
147
+ end
148
+
149
+ it 'yields to load_raw_data only once with the short key' do
150
+ expect(subject).to eql("a value")
151
+ end
152
+
153
+ it 'does not raise when an error occurs' do
154
+ expect_any_instance_of(Store).to receive(:exist?).and_raise("Cache failed")
155
+ expect_any_instance_of(Caching::Logger).to receive(:cache_fail)
156
+ expect(subject).to eql("a value")
157
+ end
158
+ end
159
+
160
+ describe "with prometheus listener too" do
161
+
162
+ let(:listener) do
163
+ Caching::Prometheus.new
164
+ end
165
+
166
+ let(:cache) do
167
+ BaseCache.new.send(:register, listener)
168
+ end
169
+
170
+ before do
171
+ expect(listener).to receive(:cache_miss)
172
+ expect(listener).to receive(:cache_hit)
173
+ end
174
+
175
+ it 'yields to load_raw_data only once with the extend key' do
176
+ expect(cache.get("a key")).to eql("a value")
177
+ expect(cache.get("a key")).to eql("a value")
178
+ end
179
+
180
+ end
181
+
134
182
  end
135
183
  end
136
184
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: startback
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.19.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-26 00:00:00.000000000 Z
11
+ date: 2023-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -150,7 +150,7 @@ dependencies:
150
150
  requirements:
151
151
  - - ">="
152
152
  - !ruby/object:Gem::Version
153
- version: '2.0'
153
+ version: '2.1'
154
154
  - - "<"
155
155
  - !ruby/object:Gem::Version
156
156
  version: '3.0'
@@ -160,7 +160,7 @@ dependencies:
160
160
  requirements:
161
161
  - - ">="
162
162
  - !ruby/object:Gem::Version
163
- version: '2.0'
163
+ version: '2.1'
164
164
  - - "<"
165
165
  - !ruby/object:Gem::Version
166
166
  version: '3.0'
@@ -392,8 +392,12 @@ files:
392
392
  - lib/startback/audit/span.rb
393
393
  - lib/startback/audit/trace_logger.rb
394
394
  - lib/startback/audit/tracer.rb
395
+ - lib/startback/caching.rb
395
396
  - lib/startback/caching/entity_cache.rb
397
+ - lib/startback/caching/listeners.rb
398
+ - lib/startback/caching/logger.rb
396
399
  - lib/startback/caching/no_store.rb
400
+ - lib/startback/caching/prometheus.rb
397
401
  - lib/startback/caching/store.rb
398
402
  - lib/startback/context.rb
399
403
  - lib/startback/context/h_factory.rb