confeature 0.0.0 → 0.1.0

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.
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ module Confeature
6
+ module Internal
7
+ # Evaluation logger surface (ADR 0003 / epic #494, Ruby slice of #501).
8
+ #
9
+ # The WARN / ERROR / INFO lines the federated evaluator emits during a trace,
10
+ # ported from `packages/sdk-shared/src/logger.ts` (the `LoggerWrapper`
11
+ # helpers `userObjectIsMissing` / `userObjectAttributeIsMissing` /
12
+ # `userObjectAttributeIsMissingForPercentage` / `userObjectAttributeIsInvalid`
13
+ # / `comparisonValueIsInvalid` / `circularDependency`) and the INFO 5000 trace
14
+ # emission point in `evaluator.ts`. Mirrors the Go / Python ports so the
15
+ # federation ports stay consistent.
16
+ #
17
+ # The strings are ported VERBATIM from logger.ts (Principle II —
18
+ # byte-identical to the canonical). The capture format the parity test
19
+ # reconstructs is `"<LEVEL>: [<eventId>] <message>"` per emitted call, joined
20
+ # by "\n", with the INFO 5000 entry carrying the multi-line indented trace.
21
+ #
22
+ # This trace-logging seam is intentionally distinct from the public
23
+ # `Confeature::Logger`: a customer-supplied logger has the
24
+ # `error/warning/info/debug` shape, while the trace test taps a structured
25
+ # `(level, event_id, message)` recorder. Keeping them separate matches the Go
26
+ # / Python factoring and keeps the no-trace hot path zero-cost.
27
+ module EvalLogger
28
+ # Mirrors configcat-common's numeric ordering (logger.ts `LogLevel`). Level
29
+ # filtering ("Info and above") works identically across SDKs. Distinct from
30
+ # the public `Confeature::LogLevel` (stdlib ordering), used ONLY for the
31
+ # trace seam so the gating matches the canonical reference exactly.
32
+ module TraceLevel
33
+ OFF = -1
34
+ ERROR = 1
35
+ WARN = 2
36
+ INFO = 3
37
+ DEBUG = 4
38
+ end
39
+
40
+ # Internal logger adapter threaded through the evaluation context. Mirrors
41
+ # logger.ts `LoggerWrapper`: a `nil` inner means tracing is off (zero
42
+ # cost), and every helper is level-gated at the boundary.
43
+ #
44
+ # The injected `inner` is any object responding to `level` (→ a
45
+ # `TraceLevel`) and `log(level, event_id, message)` — the seam the parity
46
+ # test's capture logger implements.
47
+ class TraceLog
48
+ def initialize(inner)
49
+ @inner = inner
50
+ end
51
+
52
+ def enabled?(level)
53
+ return false if @inner.nil?
54
+
55
+ threshold = @inner.level
56
+ return false if threshold == TraceLevel::OFF
57
+
58
+ level <= threshold
59
+ end
60
+
61
+ # WARN 3001 (verbatim from logger.ts `userObjectIsMissing`).
62
+ def user_object_is_missing(key)
63
+ emit(
64
+ TraceLevel::WARN, "3001",
65
+ "cannot evaluate targeting rules and % options for setting '#{key}' " \
66
+ "(User Object is missing); you should pass a User Object to the evaluation " \
67
+ "methods like `getValue()` in order to make targeting work properly; read more: " \
68
+ "https://configcat.com/docs/advanced/user-object/"
69
+ )
70
+ end
71
+
72
+ # WARN 3003 (verbatim from logger.ts `userObjectAttributeIsMissing`).
73
+ def user_object_attribute_is_missing(key, attribute_name)
74
+ emit(
75
+ TraceLevel::WARN, "3003",
76
+ "cannot evaluate certain targeting rules of setting '#{key}' " \
77
+ "(the User.#{attribute_name} attribute is missing); you should set the " \
78
+ "User.#{attribute_name} attribute in order to make targeting work properly; " \
79
+ "read more: https://configcat.com/docs/advanced/user-object/"
80
+ )
81
+ end
82
+
83
+ # WARN 3003, % options variant (verbatim from logger.ts).
84
+ def user_object_attribute_is_missing_for_percentage(key, attribute_name)
85
+ emit(
86
+ TraceLevel::WARN, "3003",
87
+ "cannot evaluate % options for setting '#{key}' " \
88
+ "(the User.#{attribute_name} attribute is missing); you should set the " \
89
+ "User.#{attribute_name} attribute in order to make targeting work properly; " \
90
+ "read more: https://configcat.com/docs/advanced/user-object/"
91
+ )
92
+ end
93
+
94
+ # WARN 3004 (verbatim from logger.ts `userObjectAttributeIsInvalid`).
95
+ def user_object_attribute_is_invalid(key, attribute_name, reason)
96
+ emit(
97
+ TraceLevel::WARN, "3004",
98
+ "cannot evaluate certain targeting rules of setting '#{key}' " \
99
+ "(the User.#{attribute_name} attribute is invalid (#{reason})); please check the " \
100
+ "User.#{attribute_name} attribute and make sure that its value corresponds to the " \
101
+ "comparison operator"
102
+ )
103
+ end
104
+
105
+ # WARN 3004, malformed config operand (#509). Note there is NO "please
106
+ # check the User.<attr>…" suffix (verbatim from logger.ts
107
+ # `comparisonValueIsInvalid`). `inner_message` is the full
108
+ # `comparison value '<v>' is invalid (<reason>)` text.
109
+ def comparison_value_is_invalid(key, inner_message)
110
+ emit(
111
+ TraceLevel::WARN, "3004",
112
+ "cannot evaluate certain targeting rules of setting '#{key}' (#{inner_message})"
113
+ )
114
+ end
115
+
116
+ # ERROR 1002 (verbatim from logger.ts `circularDependency`).
117
+ def circular_dependency(key, cycle)
118
+ emit(
119
+ TraceLevel::ERROR, "1002",
120
+ "failed to evaluate setting '#{key}' (circular dependency detected between the " \
121
+ "following depending flags: [#{cycle}])"
122
+ )
123
+ end
124
+
125
+ # INFO line (the 5000 trace). Mirrors logger.ts `LoggerWrapper.info`.
126
+ def info(event_id, message)
127
+ emit(TraceLevel::INFO, event_id, message)
128
+ end
129
+
130
+ private
131
+
132
+ def emit(level, event_id, message)
133
+ return unless enabled?(level)
134
+
135
+ @inner.log(level, event_id, message)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end