Module: Smplkit::Logging::Resolution

Defined in:
lib/smplkit/logging/resolution.rb

Overview

Client-side level resolution per ADR-034 §3.1.

The server stores raw configuration and returns it as-is; the SDK is responsible for walking the inheritance chain. Mirrors the Python SDK’s smplkit.logging._resolution verbatim — both implementations MUST resolve identically for any given (loggers, groups, env) input.

Constant Summary collapse

FALLBACK_LEVEL =
"INFO"

Class Method Summary collapse

Class Method Details

.env_level_of(entry, environment) ⇒ Object



107
108
109
110
111
112
113
114
115
# File 'lib/smplkit/logging/resolution.rb', line 107

def env_level_of(entry, environment)
  envs = entry["environments"]
  return nil unless envs.is_a?(Hash)

  env_data = envs[environment]
  return nil unless env_data.is_a?(Hash)

  env_data["level"]
end

.find_resolution_source(logger_id, environment, loggers, groups) ⇒ Object

Human-readable label for which resolution step won. Only consulted when debug logging is enabled; mirrors Python’s _find_resolution_source.



94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/smplkit/logging/resolution.rb', line 94

def find_resolution_source(logger_id, environment, loggers, groups)
  entry = loggers[logger_id]
  return "not found" if entry.nil?

  return %(env override "#{environment}") if env_level_of(entry, environment)
  return "base level" if entry["level"]

  group_id = entry["group"]
  return %(group "#{group_id}") if resolve_group_chain(group_id, environment, groups)

  "unknown"
end

.resolve_for_entry(logger_id, environment, loggers, groups) ⇒ Object

Try to resolve a level for a single entry (logger or ancestor). Returns nil if no level is found at any step of 1–3.



59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/smplkit/logging/resolution.rb', line 59

def resolve_for_entry(logger_id, environment, loggers, groups)
  entry = loggers[logger_id]
  return nil if entry.nil?

  env_level = env_level_of(entry, environment)
  return env_level if env_level

  base = entry["level"]
  return base if base

  resolve_group_chain(entry["group"], environment, groups)
end

.resolve_group_chain(group_id, environment, groups) ⇒ Object

Walk the group chain looking for a level. Cycle-safe via visited.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/smplkit/logging/resolution.rb', line 73

def resolve_group_chain(group_id, environment, groups)
  visited = Set.new
  current_id = group_id
  while !current_id.nil? && !visited.include?(current_id)
    visited.add(current_id)
    group = groups[current_id]
    break if group.nil?

    env_level = env_level_of(group, environment)
    return env_level if env_level

    base = group["level"]
    return base if base

    current_id = group["group"]
  end
  nil
end

.resolve_level(logger_id, environment, loggers, groups) ⇒ Object

Resolve the effective level for logger_id in environment.

Resolution chain (first non-nil wins):

1. Logger's own +environments[env].level+
2. Logger's own +level+
3. Group chain (recursive: group env level → group level → parent group …)
4. Dot-notation ancestry (+com.acme.payments+ → +com.acme+ → +com+,
   applying steps 1–3 at each)
5. System fallback: +"INFO"+

loggers and groups are id-keyed Hashes whose values are Hashes with the same shape as the Python SDK: “level”, “group” (parent group id for loggers; parent_id for groups), “environments” (Hash keyed by env name with {“level” => “…”} values).



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/smplkit/logging/resolution.rb', line 33

def resolve_level(logger_id, environment, loggers, groups)
  result = resolve_for_entry(logger_id, environment, loggers, groups)
  if result
    if Smplkit::Debug.enabled
      source = find_resolution_source(logger_id, environment, loggers, groups)
      Smplkit.debug("resolution", "#{logger_id} -> #{result} (source: #{source})")
    end
    return result
  end

  parts = logger_id.split(".")
  (parts.length - 1).downto(1) do |i|
    ancestor_id = parts[0, i].join(".")
    result = resolve_for_entry(ancestor_id, environment, loggers, groups)
    if result
      Smplkit.debug("resolution", "#{logger_id} -> #{result} (source: ancestor \"#{ancestor_id}\")")
      return result
    end
  end

  Smplkit.debug("resolution", "#{logger_id} -> #{FALLBACK_LEVEL} (source: system default)")
  FALLBACK_LEVEL
end