smplkit 3.0.18 → 3.0.19

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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/smplkit/logging/client.rb +46 -61
  3. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9aac495079938ab8bc622b249b90945d435ed134ce29f3a4583cea7ab3340422
4
- data.tar.gz: dbb0c297f8bcfe63909cdfcabad6524baf4d40a35ad1c6d5efbcb7e416acbc85
3
+ metadata.gz: 8dc0e8067c66c43e659ad29aff4756448763e90c251e300d598762a7aad1130b
4
+ data.tar.gz: 0a658e3773fcb670b7891c6fc5b18ffeea16780a3195c8e0aa866132e2018fe9
5
5
  SHA512:
6
- metadata.gz: bc342b324650e1e75ef46662b5ec3b39ed71b987cb2e6d9e61f59e4747c6cded00efbed43bbf9bdeee24cf2a05a3cad4a30d62ef5d6cd035784cb8fca0f5ad20
7
- data.tar.gz: b7ea349571de8d67fa56376fc1e4c7a66d5636e8581f59546b35d11f3031362f047b3a052b1bbe718397c45e016fc2929ae8d352191fe304309ff1bb4a0f10ca
6
+ metadata.gz: 6f1ef7cc9470990ee57fbe8587fff5f4a0ac5f9ab2199186b4f585fbe8dfc8dc9efd74acc8787f3c4a3fab4b9acc4c2860118e6f5b91c4934f8684d0c8d88f7f
7
+ data.tar.gz: 9c8cf307db3e470c047b518aeb079b558d7c19f1687293e1103d1570104fc2c2bfde6f2591f090a94bf46cc3b438123d38e7caa7db2f7b1a923aad91b096e4c5
@@ -14,6 +14,17 @@ module Smplkit
14
14
  # and the SDK walks the chain (env override → base → group chain →
15
15
  # dot-notation ancestry) to compute each managed logger's effective
16
16
  # level. See {Smplkit::Logging::Resolution}.
17
+ #
18
+ # Change-listener contract — every call the SDK makes to
19
+ # +adapter.apply_level(logger_id, new_level)+ is paired with exactly one
20
+ # listener notification for that logger, and every notification
21
+ # corresponds to exactly one adapter apply. A trigger that moves N
22
+ # loggers' effective levels invokes the global listener N times (once
23
+ # per logger), each invocation also fires every matching key-scoped
24
+ # listener for that id. There are no batch / summary events and no
25
+ # deletion-flavored events — logger / group deletions only emit
26
+ # listener invocations for *dependents* whose computed effective level
27
+ # actually moved; the deleted key itself emits nothing.
17
28
  class LoggingClient
18
29
  def initialize(parent, manage:, metrics:, logging_base_url:, app_base_url:)
19
30
  @parent = parent
@@ -30,18 +41,16 @@ module Smplkit
30
41
  # We keep originals so adapter.apply_level receives whatever the
31
42
  # framework's registry indexes by.
32
43
  @name_map = {}
33
- # normalized_id → resolution-cache entry. Populated by
34
- # +_fetch_and_apply+ and mutated by the +logger_changed+ /
35
- # +logger_deleted+ WS handlers.
44
+ # normalized_id → resolution-cache entry.
36
45
  @loggers_cache = {}
37
46
  # group id → resolution-cache entry. Without this, any managed
38
47
  # logger with +level=null+ that inherits from a group silently
39
48
  # keeps whatever level its adapter had at startup.
40
49
  @groups_cache = {}
41
- # normalized_id → resolved level (string). Used to decide whether
42
- # to fire change listeners on a re-resolution — a group-driven
43
- # change isn't visible in the raw +loggers_cache+ but moves the
44
- # resolved value.
50
+ # normalized_id → last-applied resolved level (string). Drives the
51
+ # lockstep between adapter.apply_level and listener notifications:
52
+ # we only push (and fire) when the freshly-resolved level differs
53
+ # from what's recorded here.
45
54
  @resolved_levels = {}
46
55
  end
47
56
 
@@ -62,7 +71,7 @@ module Smplkit
62
71
  end
63
72
 
64
73
  flush_initial_registration
65
- fetch_and_apply(trigger: "install")
74
+ fetch_and_apply(trigger: "install", source: "manual")
66
75
 
67
76
  @ws_manager = @parent._ensure_ws
68
77
  @ws_manager.on("logger_changed") { |data| handle_logger_changed(data) }
@@ -101,9 +110,9 @@ module Smplkit
101
110
  end
102
111
 
103
112
  # Re-fetch all loggers and groups and re-apply resolved levels. Fires
104
- # change listeners for any logger whose resolved level moved.
113
+ # listeners only for loggers whose effective level moved.
105
114
  def refresh
106
- fetch_and_apply(trigger: "refresh")
115
+ fetch_and_apply(trigger: "refresh", source: "manual")
107
116
  end
108
117
 
109
118
  def on_change(name = nil, &block)
@@ -164,27 +173,25 @@ module Smplkit
164
173
  end
165
174
 
166
175
  # Full re-fetch of loggers + groups, then apply resolved levels.
167
- # Fires change listeners for any logger whose resolved level moved.
168
- def fetch_and_apply(trigger:)
176
+ def fetch_and_apply(trigger:, source: "websocket")
169
177
  Smplkit.debug("resolution", "full resolution pass starting (trigger: #{trigger})")
170
178
  loggers = @manage.loggers.list_logger_entries
171
179
  groups = @manage.log_groups.list_group_entries
172
180
  @loggers_cache = loggers
173
181
  @groups_cache = groups
174
- apply_levels(source: "websocket")
182
+ apply_levels(source: source)
175
183
  rescue StandardError => e
176
184
  Smplkit.debug("resolution", "fetch_and_apply failed (trigger: #{trigger}): #{e.class}: #{e.message}")
177
185
  end
178
186
 
179
- # Resolve the effective level for every locally-known managed logger
180
- # and push it to every adapter. Returns the list of normalized ids
181
- # whose resolved level changed.
182
- #
183
- # +source+ is the +LoggerChangeEvent#source+ for any change-listener
184
- # event we fire. The default reflects callers that arrived through a
185
- # server event (WebSocket).
187
+ # Apply newly-resolved levels in lockstep with listener
188
+ # notifications. For every locally-tracked managed logger whose
189
+ # freshly-computed effective level differs from the last applied
190
+ # value: push to adapters, then fire each global + matching
191
+ # key-scoped listener. No adapter push happens without a paired
192
+ # listener notification, and no notification fires without a
193
+ # paired adapter push.
186
194
  def apply_levels(source: "websocket")
187
- changed = []
188
195
  @name_map.each do |raw_name, normalized_id|
189
196
  entry = @loggers_cache[normalized_id]
190
197
  next if entry.nil?
@@ -193,16 +200,14 @@ module Smplkit
193
200
  resolved_string = Resolution.resolve_level(
194
201
  normalized_id, @parent._environment, @loggers_cache, @groups_cache
195
202
  )
203
+ previous = @resolved_levels[normalized_id]
204
+ next if previous == resolved_string
205
+
196
206
  coerced = LogLevel.coerce(resolved_string)
207
+ @resolved_levels[normalized_id] = resolved_string
197
208
  push_to_adapters(raw_name, coerced)
198
- previous = @resolved_levels[normalized_id]
199
- if previous != resolved_string
200
- @resolved_levels[normalized_id] = resolved_string
201
- changed << [normalized_id, coerced]
202
- end
209
+ fire_change_event(normalized_id, coerced, source: source)
203
210
  end
204
- fire_resolved_change_events(changed, source: source)
205
- changed
206
211
  end
207
212
 
208
213
  def push_to_adapters(raw_name, coerced_level)
@@ -213,14 +218,12 @@ module Smplkit
213
218
  end
214
219
  end
215
220
 
216
- def fire_resolved_change_events(changed, source:)
217
- changed.each do |(normalized_id, coerced_level)|
218
- event = LoggerChangeEvent.new(name: normalized_id, level: coerced_level, source: source)
219
- (@global_listeners + @key_listeners[normalized_id]).each do |cb|
220
- cb.call(event)
221
- rescue StandardError => e
222
- Smplkit.debug("logging", "listener raised: #{e.class}: #{e.message}")
223
- end
221
+ def fire_change_event(normalized_id, level, source:)
222
+ event = LoggerChangeEvent.new(name: normalized_id, level: level, source: source)
223
+ (@global_listeners + @key_listeners[normalized_id]).each do |cb|
224
+ cb.call(event)
225
+ rescue StandardError => e
226
+ Smplkit.debug("logging", "listener raised: #{e.class}: #{e.message}")
224
227
  end
225
228
  end
226
229
 
@@ -240,17 +243,16 @@ module Smplkit
240
243
  apply_levels(source: "websocket")
241
244
  end
242
245
 
246
+ # Deletion is a pure cache eviction. The deleted key itself fires
247
+ # nothing; dependents whose effective level moves fire through the
248
+ # normal apply path.
243
249
  def handle_logger_deleted(data)
244
250
  key = data["id"] || data["name"] || ""
245
251
  normalized = Normalize.normalize_logger_name(key)
246
252
  return if normalized.empty?
247
253
 
248
- existed = @loggers_cache.delete(normalized)
249
- @resolved_levels.delete(normalized)
250
- return unless existed
251
-
254
+ @loggers_cache.delete(normalized)
252
255
  apply_levels(source: "websocket")
253
- fire_deletion_event(normalized)
254
256
  end
255
257
 
256
258
  def handle_group_changed(data)
@@ -272,36 +274,19 @@ module Smplkit
272
274
  key = data["id"] || data["key"] || ""
273
275
  return if key.to_s.empty?
274
276
 
275
- existed = @groups_cache.delete(key)
276
- return unless existed
277
-
277
+ @groups_cache.delete(key)
278
278
  apply_levels(source: "websocket")
279
- fire_deletion_event(key)
280
279
  end
281
280
 
282
281
  def handle_loggers_changed(_data)
283
282
  fetch_and_apply(trigger: "loggers_changed WS event")
284
283
  end
285
-
286
- def fire_deletion_event(key)
287
- event = LoggerChangeEvent.new(name: key, level: nil, source: "websocket", deleted: true)
288
- (@global_listeners + @key_listeners[key]).each do |cb|
289
- cb.call(event)
290
- rescue StandardError => e
291
- Smplkit.debug("logging", "listener raised: #{e.class}: #{e.message}")
292
- end
293
- end
294
284
  end
295
285
 
296
- LoggerChangeEvent = Struct.new(:name, :level, :source, :deleted, keyword_init: true) do
297
- def initialize(name:, level:, source:, deleted: false)
298
- super
299
- end
300
-
286
+ LoggerChangeEvent = Struct.new(:name, :level, :source, keyword_init: true) do
301
287
  def ==(other)
302
288
  other.is_a?(LoggerChangeEvent) &&
303
- name == other.name && level == other.level &&
304
- source == other.source && deleted == other.deleted
289
+ name == other.name && level == other.level && source == other.source
305
290
  end
306
291
  end
307
292
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smplkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.18
4
+ version: 3.0.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Smpl Solutions LLC