fix(feature-flags): make evaluation logging non-throwing - #4071
Conversation
|
Benchmarks [ tracer ]Benchmark execution time: 2026-07-28 14:49:32 Comparing candidate commit bf360ef in PR branch Found 2 performance improvements and 1 performance regressions! Performance is the same for 190 metrics, 1 unstable metrics.
|
…arning-exception' into leo.romanovsky/fix-openfeature-warning-exception
pavlokhrebto
left a comment
There was a problem hiding this comment.
Reviewed the diff — solid, well-tested fix for a real footgun (trigger_error -> ErrorException -> OpenFeature discards the value). A few non-blocking notes inline. The two I would most want confirmed before approval: (1) the not-ready warning is now effectively silent by default (NullLogger unless DD_TRACE_DEBUG), and (2) light end-to-end coverage of the native config-missing branch. No interaction with the merged span-enrichment work.
|
|
||
| $this->evaluator = NativeEvaluator::create(); | ||
| $this->logger = $logger ?: new TriggerErrorLogger(); | ||
| $this->logger = new NonThrowingLogger($logger ?: GlobalLogger::get()); |
There was a problem hiding this comment.
Behavior change worth calling out: Logger::get() returns NullLogger(EMERGENCY) unless DD_TRACE_DEBUG is set, so the not-ready warning is now effectively silent by default (previously TriggerErrorLogger always emitted E_USER_WARNING).
As far as i remember this is intended, but please note in the PR that not-ready state is now surfaced via exposures/metrics rather than a userland warning. Same applies to the equivalent line in DataDogProvider.
| 'hasConfig' => $hasConfig, | ||
| 'configVersion' => $configVersion, | ||
| 'productionRuntime' => false, | ||
| 'productionRuntime' => true, |
There was a problem hiding this comment.
After this PR nothing reads productionRuntime for logic (the old warnIfNonProductionRuntime checks are removed); it is only surfaced as metadata / asserted in the .phpt. Two small things: (a) consider => $hasConfig instead of a constant true — a config-missing result (reason='configuration_missing' -> PROVIDER_NOT_READY) currently reports productionRuntime=true alongside a not-ready error, which is contradictory; (b) a one-line comment that it is informational-only would help the next reader.
| namespace DDTrace\Log; | ||
|
|
||
| /** | ||
| * Prevents logger failures from escaping into application code. |
There was a problem hiding this comment.
Nice boundary. Note it swallows all Throwables silently on every method, so a genuinely broken/misconfigured underlying logger becomes invisible. Acceptable trade-off here (and I would not add an error_log fallback — recursion risk), just flagging the blind spot.
| ); | ||
|
|
||
| $providerState = $details->getProviderState(); | ||
| if (($providerState['productionRuntime'] ?? null) !== true) { |
There was a problem hiding this comment.
This asserts the hasConfig=true path (productionRuntime=true/reason=ready). The hasConfig=false native branch — config-missing -> PROVIDER_NOT_READY with a returned value, which is exactly the scenario the warning re-keying governs — is not covered end-to-end. Consider a case that exercises it to lock the contract (the unit tests cover UnavailableEvaluator's PROVIDER_NOT_READY, but not native config-missing).
chasdevs
left a comment
There was a problem hiding this comment.
Missed the boat on approval, but that is pretty wild.
Motivation
A customer found that successful OpenFeature evaluations returned the code default in Laravel. Laravel converts the Datadog warning into
ErrorException. OpenFeature catches that exception asGENERALand discards the evaluated value.Existing FFE system tests called the native Datadog client. That path bypassed the OpenFeature provider, its logger boundary, and framework error handling. DataDog/system-tests#7393 makes OpenFeature the default
/ffepath and adds that path to Laravel and Symfony. PHP 8 must return the configured value without an error. A runtime without the provider must returnPROVIDER_NOT_READY. This contract makes future regressions fail in the framework path.PR #3906 added this warning as a temporary guardrail. The reviewer asked the implementation to reuse
LoggerInterfaceand suggestedTriggerErrorLoggeronly iftrigger_error()remained the default. Exposure delivery and evaluation metrics are now complete, so the guardrail and its logger are obsolete.Changes and Decisions
Successful native evaluations now report
productionRuntime=trueandreason=ready. OnlyPROVIDER_NOT_READYresults produce the not-ready warning.The change removes
TriggerErrorLoggerand uses the existing tracer logger. Both feature-flag entry points wrap their logger withNonThrowingLogger. This boundary prevents current and future logger failures from changing evaluation results. The provider retainsLoggerInterfaceand usesdatadogLoggerbecause the OpenFeature parent class owns$logger.Validation
DataDog/system-tests#7393 passed locally with system-tests commit
0dad5d48bec63cffdd206b724013d26f497c0c8cand tracer commitbf360ef80839f483179e37215536d1713116167c: