startback 0.5.0 → 0.5.1

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: 492399cde5cbfe2861575824a32c7788fc16137902d5a4194b3662e5306a5968
4
- data.tar.gz: 192d23a34d668bb3ce495c0bb3c724ef58c4e7857bf8e0ed63d5948a76b43f49
3
+ metadata.gz: 61d4c08ece5a3e956448ef53ca8bfa1a0e4e3b5e9280eb39e705858b9939b708
4
+ data.tar.gz: 3c481e062a5ab699d8c72ba9625f7528f41b94f075177df284f238d9117cacce
5
5
  SHA512:
6
- metadata.gz: 54ff69ead9f9b90eccbc9700a46229621cdfe31eb7b887207f6c69c0f755735ae80fc6257ff9ca6e3430d9fc75a2e04fd7b9ea4ecf6744557cb79d418e1ac3b4
7
- data.tar.gz: 71de967d2ebee0cd0596b51532d91a1abc1197cd69d1b6db528fcf6bcd00977c484067a83ba266e37e8d807c3c2bba8efc3b67f822b0540eef9fea3c9578fc5b
6
+ metadata.gz: cc079a6ced8d0cbec9a23b552056d06a4ebc139a5afb54cbc4eadb7c416ca390263e3c3c29c47009e53954db8176305a398fb8ceaddb3696a676bf6d414f0a45
7
+ data.tar.gz: 6ed71d2a2b742a0634cb3fca8c92e1e0b9a835bbf55f9ada5a9144f40184d01338065ce587d98035e6409a31424ef2a01818e566f5a1cb56e618962f7ac360df
@@ -52,6 +52,7 @@ module Startback
52
52
 
53
53
  def call(severity, time, progname, msg)
54
54
  if msg[:error] && msg[:error].respond_to?(:message, true)
55
+ msg[:backtrace] = msg[:error].backtrace[0..25] if severity == "FATAL"
55
56
  msg[:error] = msg[:error].message
56
57
  end
57
58
  {
@@ -5,16 +5,29 @@ module Startback
5
5
  #
6
6
  # This class MUST be overriden:
7
7
  #
8
- # * the `load_raw_data` protected method MUST be implemented.
9
- # * the `full_key` protected method MAY be overriden to provide specific caching
10
- # keys, e.g. by using the context.
8
+ # * the `load_entity` protected method MUST be implemented to load data from
9
+ # a primary & context unaware key.
10
+ #
11
+ # * the `primary_key` protected method MAY be implemented to convert candidate
12
+ # keys (received from ultimate callers) to primary keys. The method is also
13
+ # a good place to check and/or log the keys actually used by callers.
14
+ #
15
+ # * the `context_free_key` protected method MAY be overriden to provide
16
+ # domain unrelated caching keys from primary keys, e.g. by encoding the
17
+ # context into the caching key itself, if needed.
18
+ #
11
19
  # * the `valid?` protected method MAY be overriden to check validity of data
12
- # extracted from the cache.
20
+ # extracted from the cache and force a refresh even if found.
13
21
  #
14
22
  # An EntityCache takes an actual store at construction. The object must meet the
15
23
  # specification writtern in Store. The 'cache' ruby gem can be used in practice.
16
24
  #
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.
28
+ #
17
29
  class EntityCache
30
+ include Support::Robustness
18
31
 
19
32
  class << self
20
33
 
@@ -37,42 +50,106 @@ module Startback
37
50
  #
38
51
  # If the entity is not in cache, loads it and puts it in cache using
39
52
  # the caching options passed as second parameter.
40
- def get(short_key, caching_options = default_caching_options)
41
- cache_key = encode_key(full_key(short_key))
53
+ def get(candidate_key, caching_options = default_caching_options)
54
+ pkey = primary_key(candidate_key)
55
+ cache_key = encode_key(context_free_key(pkey))
42
56
  if store.exist?(cache_key)
43
57
  cached = store.get(cache_key)
44
- return cached if valid?(cache_key, cached)
58
+ if valid?(pkey, cached)
59
+ cache_hit(pkey, cached)
60
+ return cached
61
+ else
62
+ cache_outdated(pkey, cached)
63
+ end
45
64
  end
46
- load_raw_data(short_key).tap{|to_cache|
65
+ cache_miss(pkey)
66
+ load_entity(pkey).tap{|to_cache|
47
67
  store.set(cache_key, to_cache, caching_options)
48
68
  }
49
69
  end
50
70
 
51
71
  # Invalidates the cache under a given key.
52
- def invalidate(key)
53
- store.delete(encode_key(full_key(key)))
72
+ def invalidate(candidate_key)
73
+ pkey = primary_key(candidate_key)
74
+ cache_key = encode_key(context_free_key(pkey))
75
+ store.delete(cache_key)
54
76
  end
55
77
 
56
78
  protected
57
79
 
58
- def encode_key(key)
59
- JSON.fast_generate(key)
80
+ def cache_hit(pkey, cached)
81
+ log(:debug, self, "cache_hit", op_data: pkey)
82
+ end
83
+
84
+ def cache_outdated(pkey, cached)
85
+ log(:info, self, "cache_outdated", op_data: pkey)
86
+ end
87
+
88
+ def cache_miss(pkey)
89
+ log(:info, self, "cache_miss", op_data: pkey)
60
90
  end
61
91
 
62
92
  def default_caching_options
63
93
  { ttl: self.class.default_ttl }
64
94
  end
65
95
 
66
- def valid?(cache_key, cached)
96
+ # Converts a candidate key to a primary key, so as to prevent
97
+ # cache duplicates if callers are allowed to request an entity
98
+ # through various keys.
99
+ #
100
+ # The default implementation returns the candidate key and MAY
101
+ # be overriden.
102
+ def primary_key(candidate_key)
103
+ candidate_key
104
+ end
105
+
106
+ # Encodes a context free key to an actual cache key.
107
+ #
108
+ # Default implementation uses JSON.fast_generate but MAY be
109
+ # overriden.
110
+ def encode_key(context_free_key)
111
+ JSON.fast_generate(context_free_key)
112
+ end
113
+
114
+ # Returns whether `cached` entity seems fresh enough to
115
+ # be returned as a cache hit.
116
+ #
117
+ # This method provides a way to check freshness using, e.g.
118
+ # `updated_at` or `etag` kind of entity fields. The default
119
+ # implementation returns true and MAY be overriden.
120
+ def valid?(primary_key, cached)
67
121
  true
68
122
  end
69
123
 
70
- def full_key(key)
71
- key
124
+ # Converts a primary_key to a context_free_key, using the
125
+ # context (instance variable) to encode the context itself
126
+ # into the actual cache key.
127
+ #
128
+ # The default implementation simply returns the primary key
129
+ # and MAY be overriden.
130
+ def context_free_key(primary_key)
131
+ full_key(primary_key)
132
+ end
133
+
134
+ # Deprecated, will be removed in 0.6.0. Use context_free_key
135
+ # instead.
136
+ def full_key(primary_key)
137
+ primary_key
138
+ end
139
+
140
+ # Actually loads the entity using the given primary key, and
141
+ # possibly the cache context.
142
+ #
143
+ # This method MUST be implemented and raises a NotImplementedError
144
+ # by default.
145
+ def load_entity(primary_key)
146
+ load_raw_data(primary_key)
72
147
  end
73
148
 
74
- def load_raw_data(short_key)
75
- raise NotImplementedError, "#{self.class.name}#load_raw_data"
149
+ # Deprecated, will be removed in 0.6.0. Use load_entity
150
+ # instead.
151
+ def load_raw_data(*args, &bl)
152
+ raise NotImplementedError, "#{self.class.name}#load_entity"
76
153
  end
77
154
 
78
155
  end # class EntityCache
@@ -24,12 +24,13 @@ module Startback
24
24
  #
25
25
  # Examples:
26
26
  #
27
- # log(op: "hello", op_data: {foo: 12}) => logged as such on STDOUT
28
- # log("A simple message") => { op: "A simple message" } on STDOUT
29
- # log(Startback, "hello") => { op: "Startback#hello" } on STDOUT
30
- # log(Event.new, "hello") => { op: "Event#hello" } on STDOUT
31
- # log(self, context) => { op: "..." } on context's logger or STDOUT
32
- # log(self, event) => { op: "..." } on event context's logger or STDOUT
27
+ # log(:info, op: "hello", op_data: {foo: 12}) => logged as such on STDOUT
28
+ # log(:info, "A simple message") => { op: "A simple message" } on STDOUT
29
+ # log(:info, Startback, "hello") => { op: "Startback#hello" } on STDOUT
30
+ # log(:info, Event.new, "hello") => { op: "Event#hello" } on STDOUT
31
+ # log(:info, Event.new, "hello", "hello world") => { op: "Event#hello", op_data: { message: "hello world" } } on STDOUT
32
+ # log(:info, self, context) => { op: "..." } on context's logger or STDOUT
33
+ # log(:info, self, event) => { op: "..." } on event context's logger or STDOUT
33
34
  # ...
34
35
  #
35
36
  module Robustness
@@ -41,7 +42,6 @@ module Startback
41
42
  def default_logger
42
43
  @@default_logger ||= ::Logger.new(STDOUT)
43
44
  from = caller.reject{|x| x =~ /lib\/startback/ }.first
44
- @@default_logger.debug("Watch out, using default logger from: #{from}")
45
45
  @@default_logger
46
46
  end
47
47
  module_function :default_logger
@@ -56,7 +56,8 @@ module Startback
56
56
 
57
57
  def parse_args(log_msg, method = nil, context = nil, extra = nil)
58
58
  method, context, extra = nil, method, context unless method.is_a?(String)
59
- context, extra = nil, context if context.is_a?(Hash) && extra.nil?
59
+ context, extra = nil, context if context.is_a?(Hash) || context.is_a?(String) && extra.nil?
60
+ extra = { op_data: { message: extra } } if extra.is_a?(String)
60
61
  logger = logger_for(context) || logger_for(log_msg)
61
62
  log_msg = if log_msg.is_a?(Hash)
62
63
  log_msg.dup
@@ -98,10 +99,9 @@ module Startback
98
99
  took = Benchmark.realtime {
99
100
  result = bl.call
100
101
  }
101
- Tools.debug(args, op_took: took)
102
+ Tools.info(args, op_took: took)
102
103
  result
103
104
  rescue => ex
104
- Tools.error(args, error: ex)
105
105
  raise
106
106
  end
107
107
 
@@ -117,7 +117,7 @@ module Startback
117
117
  }
118
118
  result
119
119
  rescue => ex
120
- Tools.error(args, op_took: took, error: ex)
120
+ Tools.fatal(args, op_took: took, error: ex)
121
121
  nil
122
122
  end
123
123
 
@@ -2,7 +2,7 @@ module Startback
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 5
5
- TINY = 0
5
+ TINY = 1
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
@@ -33,7 +33,6 @@ module Startback
33
33
  self.body FATAL_ERROR
34
34
 
35
35
  self.ensure(true) do |ex|
36
- STDERR.puts(ex.message)
37
36
  context = env[Context::Middleware::RACK_ENV_KEY]
38
37
  begin
39
38
  if context && context.respond_to?(:error_handler, true) && context.error_handler
@@ -16,6 +16,18 @@ module Startback
16
16
 
17
17
  protected
18
18
 
19
+ def primary_key(ckey)
20
+ case ckey
21
+ when Integer then "a key"
22
+ when String then ckey
23
+ else
24
+ raise "Invalid key `#{ckey}`"
25
+ end
26
+ end
27
+
28
+ # We use the deprecated methods below to test
29
+ # backward compatibility with 0.5.0.
30
+
19
31
  def full_key(key)
20
32
  { k: key }
21
33
  end
@@ -77,6 +89,21 @@ module Startback
77
89
 
78
90
  end
79
91
 
92
+ describe "primary_key" do
93
+
94
+ subject{
95
+ cache.get(12)
96
+ }
97
+
98
+ it 'allows using candidate keys' do
99
+ expect(subject).to eql("a value")
100
+ expect(subject).to eql("a value")
101
+ expect(cache.called).to eql(1)
102
+ expect(cache.last_key).to eql("a key")
103
+ end
104
+
105
+ end
106
+
80
107
  describe "invalidate" do
81
108
 
82
109
  it 'strips the key on the store, yielding a cache miss' do
@@ -61,7 +61,7 @@ module Startback
61
61
  seen += 1
62
62
  raise "Test"
63
63
  end
64
- }.to raise_error
64
+ }.to raise_error("Test")
65
65
  expect(seen).to eql(2)
66
66
  expect(the_logger.last_msg.keys).to eql([:op, :op_took, :error])
67
67
  end
@@ -116,6 +116,16 @@ module Startback
116
116
  expect(logger).to be_a(::Logger)
117
117
  end
118
118
 
119
+ it 'works fine with an string, a method name, and a message' do
120
+ expected = {
121
+ op: "AClass#method",
122
+ op_data: { message: "Hello world" }
123
+ }
124
+ log_msg, logger = parse_args("AClass", "method", "Hello world")
125
+ expect(log_msg).to eql(expected)
126
+ expect(logger).to be_a(::Logger)
127
+ end
128
+
119
129
  it 'works fine with a string only' do
120
130
  expected = {
121
131
  op: "a message"
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.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-16 00:00:00.000000000 Z
11
+ date: 2019-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake