Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ default-members = [
]
[profile.wasm]
inherits = "release"
opt-level = "z"
lto = true
opt-level = "s" # Less aggressive than "z"
lto = false # Disable LTO to debug optimization issues
codegen-units = 1
strip = "symbols"
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ message TelemetryData {
Sdk sdk = 2 [
(google.api.field_behavior) = OPTIONAL
];

// OpenFeature error counts by error code.
// Used to report errors encountered during flag evaluation.
repeated ResolveErrorCount resolve_errors = 3 [
(google.api.field_behavior) = OPTIONAL
];
}

// OpenFeature error codes as defined in the OpenFeature specification.
// https://openfeature.dev/specification/types#error-code
enum OpenFeatureErrorCode {
// Unspecified error code.
OPEN_FEATURE_ERROR_CODE_UNSPECIFIED = 0;
// The value was resolved before the provider was ready.
OPEN_FEATURE_ERROR_CODE_PROVIDER_NOT_READY = 1;
// The flag could not be found.
OPEN_FEATURE_ERROR_CODE_FLAG_NOT_FOUND = 2;
// An error was encountered parsing data, such as a flag configuration.
OPEN_FEATURE_ERROR_CODE_PARSE_ERROR = 3;
// The type of the flag value does not match the expected type.
OPEN_FEATURE_ERROR_CODE_TYPE_MISMATCH = 4;
// The provider requires a targeting key and one was not provided in the evaluation context.
OPEN_FEATURE_ERROR_CODE_TARGETING_KEY_MISSING = 5;
// The evaluation context does not meet provider requirements.
OPEN_FEATURE_ERROR_CODE_INVALID_CONTEXT = 6;
// The error was for a reason not enumerated above.
OPEN_FEATURE_ERROR_CODE_GENERAL = 7;
}

// Count of errors for a specific error code.
message ResolveErrorCount {
// The OpenFeature error code.
OpenFeatureErrorCode error_code = 1 [
(google.api.field_behavior) = REQUIRED
];

// The count of errors with this code since the last telemetry report.
int64 count = 2 [
(google.api.field_behavior) = REQUIRED
];
}

message ResolveToken {
Expand Down
27 changes: 25 additions & 2 deletions confidence-resolver/src/flag_logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use crate::proto::confidence::flags::admin::v1::flag_resolve_info::{
};
use crate::proto::confidence::flags::admin::v1::{ClientResolveInfo, FlagResolveInfo};
use crate::proto::confidence::flags::resolver::v1::events::FlagAssigned;
use crate::proto::confidence::flags::resolver::v1::{TelemetryData, WriteFlagLogsRequest};
use crate::proto::confidence::flags::resolver::v1::{
ResolveErrorCount, TelemetryData, WriteFlagLogsRequest,
};
use std::collections::{HashMap, HashSet};

pub fn aggregate_batch(message_batch: Vec<WriteFlagLogsRequest>) -> WriteFlagLogsRequest {
Expand All @@ -14,12 +16,18 @@ pub fn aggregate_batch(message_batch: Vec<WriteFlagLogsRequest>) -> WriteFlagLog
let mut flag_resolve_map: HashMap<String, VariantRuleResolveInfo> = HashMap::new();
let mut flag_assigned: Vec<FlagAssigned> = vec![];
let mut first_sdk: Option<crate::proto::confidence::flags::resolver::v1::Sdk> = None;
// Aggregate error counts by error code
let mut error_counts: HashMap<i32, i64> = HashMap::new();

for flag_logs_message in message_batch {
if let Some(td) = &flag_logs_message.telemetry_data {
if first_sdk.is_none() && td.sdk.is_some() {
first_sdk = td.sdk.clone();
}
// Aggregate error counts
for error in &td.resolve_errors {
*error_counts.entry(error.error_code).or_insert(0) += error.count;
}
}

for c in &flag_logs_message.client_resolve_info {
Expand Down Expand Up @@ -98,7 +106,22 @@ pub fn aggregate_batch(message_batch: Vec<WriteFlagLogsRequest>) -> WriteFlagLog
})
}

let telemetry_data = first_sdk.map(|sdk| TelemetryData { sdk: Some(sdk) });
// Build resolve_errors from aggregated counts
let resolve_errors: Vec<ResolveErrorCount> = error_counts
.into_iter()
.filter(|(_, count)| *count > 0)
.map(|(error_code, count)| ResolveErrorCount { error_code, count })
.collect();

// Only include TelemetryData if we have SDK info or errors to report
let telemetry_data = if first_sdk.is_some() || !resolve_errors.is_empty() {
Some(TelemetryData {
sdk: first_sdk,
resolve_errors,
})
} else {
None
};

WriteFlagLogsRequest {
telemetry_data,
Expand Down
35 changes: 32 additions & 3 deletions confidence-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,7 @@ pub struct FlagToApply {
}

pub trait Host {
fn log(_: &str) {
// noop
}
fn log(message: &str);

#[cfg(not(feature = "std"))]
fn current_time() -> Timestamp;
Expand Down Expand Up @@ -267,6 +265,12 @@ pub trait Host {
sdk: &Option<flags_resolver::Sdk>,
);

/// Track FLAG_NOT_FOUND errors for flags that were requested but don't exist.
/// Default implementation is a no-op.
fn track_flag_not_found(_count: u32) {
// Default no-op
}

fn encrypt_resolve_token(token_data: &[u8], encryption_key: &[u8]) -> Result<Vec<u8>, String> {
#[cfg(feature = "std")]
{
Expand Down Expand Up @@ -510,6 +514,19 @@ impl<'a, H: Host> AccountResolver<'a, H> {
.filter(|flag| flag_names.is_empty() || flag_names.contains(&flag.name))
.collect::<Vec<&Flag>>();

// Track FLAG_NOT_FOUND errors for requested flags that don't exist
if !flag_names.is_empty() {
let resolved_flag_names: std::collections::HashSet<&String> =
flags_to_resolve.iter().map(|f| &f.name).collect();
let not_found_count = flag_names
.iter()
.filter(|name| !resolved_flag_names.contains(name))
.count();
if not_found_count > 0 {
H::track_flag_not_found(not_found_count as u32);
}
}

if flags_to_resolve.len() > MAX_NO_OF_FLAGS_TO_BATCH_RESOLVE {
return Err(format!(
"max {} flags allowed in a single resolve request, this request would return {} flags.",
Expand Down Expand Up @@ -1585,6 +1602,10 @@ mod tests {
struct L;

impl Host for L {
fn log(_message: &str) {
// In tests, we don't need to print anything
}

fn log_resolve(
_resolve_id: &str,
_evaluation_context: &Struct,
Expand Down Expand Up @@ -1974,6 +1995,10 @@ mod tests {
}

impl Host for TestLogger {
fn log(_message: &str) {
// Do nothing in tests
}

fn log_resolve(
_resolve_id: &str,
_evaluation_context: &Struct,
Expand Down Expand Up @@ -2116,6 +2141,10 @@ mod tests {
}

impl Host for TestLogger {
fn log(_message: &str) {
// Do nothing in tests
}

fn log_resolve(
_resolve_id: &str,
_evaluation_context: &Struct,
Expand Down
Loading
Loading