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 +4 -4
- data/lib/startback/audit/prometheus.rb +2 -1
- data/lib/startback/caching/entity_cache.rb +53 -8
- data/lib/startback/caching/listeners.rb +41 -0
- data/lib/startback/caching/logger.rb +24 -0
- data/lib/startback/caching/prometheus.rb +53 -0
- data/lib/startback/caching.rb +6 -0
- data/lib/startback/support/env.rb +15 -0
- data/lib/startback/support/log_formatter.rb +1 -1
- data/lib/startback/support/robustness.rb +4 -1
- data/lib/startback/version.rb +1 -1
- data/spec/unit/caching/test_entity_cache.rb +54 -6
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a46ae64f74d522af60d8efac5d0ea966587c556a12d791a4817c77a27b4eb74b
|
4
|
+
data.tar.gz: 528fd26764c05ae46e88c4009dcfe801ad8c8fa95b57eb3ae158ae205b26be49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
#
|
26
|
-
#
|
27
|
-
#
|
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
|
-
|
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
|
-
|
116
|
+
@options[:listeners].cache_hit(self, context, pkey, cached)
|
82
117
|
end
|
83
118
|
|
84
119
|
def cache_outdated(pkey, cached)
|
85
|
-
|
120
|
+
@options[:listeners].cache_outdated(self, context, pkey, cached)
|
86
121
|
end
|
87
122
|
|
88
123
|
def cache_miss(pkey)
|
89
|
-
|
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
|
@@ -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
|
@@ -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: {
|
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
|
data/lib/startback/version.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'startback/caching
|
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(
|
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.
|
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-
|
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.
|
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.
|
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
|