LaunchDarkly Developer Documentation

Get started in under 30 minutes.
LaunchDarkly provides feature flags as a service for Java · Python · Ruby · Go · Node.js · PHP · .NET. Control feature launches -- who sees what and when -- without multiple code deploys. Easy dashboard for phased rollouts, targeting and segmenting.
Need more help? write us at support@launchdarkly.com

Get Started    Documentation

Evaluation reasons

With LaunchDarkly, you can examine the reason why each user received the particular variation of a feature flag they received. This information can be useful for diagnostic or statistical purposes.

This information is accessible programmatically from the LaunchDarkly SDKs, or from Data Export events.

Programmatic access

For each of the SDK methods that you would normally call to evaluate a feature flag, there is a corresponding "detail" method. This method returns three pieces of information:

  • The computed flag variation (what you would get if you simply evaluated the flag).
  • The variation index. This is a zero-based integer indicating which variation was selected; for instance, if the flag's possible variations are "a", "b", and "c" (in that order), and the current variation is "c", the variation index would be 2. This may be convenient for tabulation. Note that this value may be absent—see "Error conditions" below.
  • A "reason" object containing information about why that variation was selected. This data structure is described below.

Here are examples of calling these methods in each SDK.

value, detail, err := client.BoolVariationDetail("flag-key", myUser, false)
// or StringVariationDetail for a string-valued flag, etc.

index := detail.VariationIndex   // may be nil
reason := detail.Reason          // will always be present

EvaluationDetail<Boolean> detail =
  client.boolVariationDetail("flag-key", myUser, false);
  // or stringVariationDetail for a string-valued flag, etc.

boolean value = detail.getValue();
Integer index = detail.getVariationIndex();   // may be null
EvaluationReason reason = detail.getReason(); // will always be present

EvaluationDetail<bool> detail =
  client.BoolVariationDetail("flag-key", myUser, false);
  // or StringVariationDetail for a string-valued flag, etc.

bool value = detail.Value;
int? index = detail.VariationIndex;       // may be null
EvaluationReason reason = detail.Reason;  // will always be present

detail = client.variation_detail("flag-key", my_user, false)

value = detail.value
index = detail.variation_index  # may be nil
reason = detail.reason          # will always be present

detail = client.variation_detail("flag-key", my_user, False)

value = detail.value
index = detail.variation_index  # may be None
reason = detail.reason          # will always be present

var detail = client.variationDetail("flag-key", myUser, false);

var value = detail.value;
var index = detail.variationIndex;  // may be null
var reason = detail.reason;         // will always be present

// Note that in order for this to work, your client configuration must
// include the property "evaluationReasons: true"

var detail = client.variationDetail("flag-key", false);

var value = detail.value;
var index = detail.variationIndex;  // may be null
var reason = detail.reason;         // will always be present


$detail = $client->variationDetail("flag-key", $myUser, false);

$value = $detail->getValue();
$index = $detail->getVariationIndex();  // may be null
$reason = $detail->getReason();         // will always be present
// Note that in order for this to work, your client configuration must
// include the property WithEvaluationReasons(true)

EvaluationDetail<bool> detail =
  client.BoolVariationDetail("flag-key", false);
  // or StringVariationDetail for a string-valued flag, etc.

bool value = detail.Value;
int? index = detail.VariationIndex;       // may be null
EvaluationReason reason = detail.Reason;  // will always be present
EvaluationDetail<Boolean> detail =
  client.boolVariationDetail("flag-key", false);
  // or stringVariationDetail for a string-valued flag, etc.

boolean value = detail.getValue();
Integer index = detail.getVariationIndex();   // may be null
EvaluationReason reason = detail.getReason(); // will always be present

The reason data

In strongly typed languages, the reason object is made up of specific classes, while in the other languages it is simply a hash (a.k.a dictionary or object). However, the JSON representation is the same in every language, so we will describe it here as if it were a JSON object.

The reason object's only required property is kind. This describes the general reason that LaunchDarkly selected this variation. The possible values for kind (enums in the strongly typed languages, strings otherwise) are:

  • "OFF" - The flag is off and therefore returned its configured off value, that is, the one that appears on the dashboard next to "If targeting is off, serve:".
  • "FALLTHROUGH" - The flag is on, but the user did not match any targets or rules, so it returned the value that appears on the dashboard under "Default rule." (Note that "default rule" is not the same thing as the default value discussed in "Error conditions".)
  • "TARGET_MATCH" - The user key was specifically targeted for this flag, in the "Target individual users" section.
  • "RULE_MATCH" - The user matched one of the flag's rules.
  • "PREREQUISITE_FAILED" - The flag had at least one prerequisite flag that either was off or did not return the desired variation; therefore, the flag returned its "off" value.
  • "ERROR" - The flag could not be evaluated, so the default value was returned. See "Error conditions".

If the kind is RULE_MATCH, the reason object also has these properties:

  • ruleIndex - The positional index of the matched rule (0 for the first rule).
  • ruleId - The rule's unique identifier, which will stay the same even if you rearrange the order of the rules.

If the kind is PREREQUISITE_FAILED, the reason object also has this property:

  • prerequisiteKey - The key of the prerequisite flag that failed.

Here are examples of how you could access the details of a reason object in each SDK:


func PrintReason(reason ldclient.EvaluationReason) {
  switch r := reason.(type) {
  case EvaluationReasonOff:
    fmt.Println("it's off")
  case EvaluationReasonFallthrough:
    fmt.Println("fell through")
  case EvaluationReasonTargetMatch:
    fmt.Println("targeted")
  case EvaluationReasonRuleMatch:
    fmt.Printf("matched rule %d/%s\n", r.RuleIndex, r.RuleID)
  case EvaluationReasonPrerequisiteFailed:
    fmt.Printf("prereq failed: %s\n", r.PrerequisiteKey)
  case EvaluationReasonError:
    fmt.Printf("error: %s\n", r.ErrorKind)
  }
  // or, if all you want is a simple descriptive string:
  fmt.Println(reason)
}

void printReason(EvaluationReason reason) {
  switch (reason.getKind()) {
    case OFF:
      System.out.println("it's off");
      break;
    case FALLTHROUGH:
      System.out.println("fell through");
      break;
    case TARGET_MATCH:
      System.out.println("targeted");
      break;
    case RULE_MATCH:
      EvaluationReason.RuleMatch rm = (EvaluationReason.RuleMatch)reason;
      System.out.println("matched rule " + rm.getRuleIndex()
        + "/" + rm.getRuleId());
      break;
    case PREREQUISITE_FAILED:
      EvaluationReason.PrerequisiteFailed pf =
        (EvaluationReason.PrerequisiteFailed)reason;
      System.out.println("prereq failed: " + pf.getPrerequisiteKey());
      break;
    case ERROR:
      EvaluationReason.Error e = (EvaluationReason.Error)reason;
      System.out.println("error: " + e.getErrorKind());
  }
  // or, if all you want is a simple descriptive string:
  System.out.println(reason.toString());
}

void PrintReason(EvaluationReason reason)
{
  switch (reason.Kind)
  {
    case EvaluationReasonKind.OFF:
      Console.WriteLine("it's off");
      break;
    case EvaluationReasonKind.FALLTHROUGH:
      Console.WriteLine("fell through");
      break;
    case EvaluationReasonKind.TARGET_MATCH:
      Console.WriteLine("targeted");
      break;
    case EvaluationReasonKind.RULE_MATCH:
      var rm = reason as EvaluationReason.RuleMatch;
      Console.WriteLine("matched rule " + rm.RuleIndex + "/" + rm.RuleID);
      break;
    case EvaluationReasonKind.PREREQUISITE_FAILED:
			var pf = reason as EvaluationReason.PrerequisiteFailed;
      Console.WriteLine("prereq failed: " + pf.PrerequisiteKey);
      break;
    case EvaluationReasonKind.ERROR:
      var e = reason as EvaluationReason.Error;
      Console.WriteLine("error: " + e.ErrorKind);
      break;
  }
  // or, if all you want is a simple descriptive string:
  System.out.println(reason.ToString());
}

def print_reason(reason)
  case reason[:kind]
  when "OFF"
    puts "it's off"
  when "FALLTHROUGH"
    puts "fell through"
  when "TARGET_MATCH"
    puts "targeted"
  when "RULE_MATCH"
    puts "matched rule #{reason[:ruleIndex]}/#{reason[:ruleId]}"
	when "PREREQUISITE_FAILED"
		puts "prereq failed: #{reason[:prerequisiteKey]}"
	when "ERROR"
		puts "error: #{reason[:errorKind]}"
  end
end

def print_reason(reason):
  kind = reason["kind"]
  if kind == "OFF":
    print "it's off"
  elif kind == "FALLTHROUGH":
    print "fell through"
  elif kind == "TARGET_MATCH":
    print "targeted"
  elif kind == "RULE_MATCH":
    print "matched rule %d/%s" % (reason["ruleIndex"], reason["ruleId"])
  elif kind == "PREREQUISITE_FAILED":
    print "prereq failed: %s" % reason["prerequisiteKey"]
  elif kind == "ERROR":
    print "error: %s" % reason["errorKind"]

function printReason(reason) {
  switch(reason.kind) {
    case "OFF":
      console.log("it's off");
      break;
    case "FALLTHROUGH":
      console.log("fell through");
      break;
    case "TARGET_MATCH":
      console.log("targeted");
      break;
    case "RULE_MATCH":
      console.log("matched rule " + reason.ruleIndex + ", "  + reason.ruleId);
      break;
    case "PREREQUISITE_FAILED":
      console.log("prereq failed: " + reason.prerequisiteKey);
      break;
    case "ERROR":
      console.log("error: " + reason.errorKind);
      break;
  }
}

function printReason($reason) {
  switch ($reason->getKind()) {
		case EvaluationReason::OFF:
			echo("it's off");
			break;
		case EvaluationReason::FALLTHROUGH:
			echo("fell through");
			break;
		case EvaluationReason::TARGET_MATCH:
			echo("targeted");
			break;
		case EvaluationReason::RULE_MATCH:
			echo("matched rule " . $reason->getRuleIndex() .
				"/" . $reason->getRuleId());
			break;
		case EvaluationReason::PREREQUISITE_FAILED:
			echo("prereq failed: " . $reason->getPrerequisiteKey());
			break;
		case EvaluationReason::ERROR:
			echo("error: " . $reason->getErrorKind());
			break;
	}
  // or, if all you want is a simple descriptive string:
  echo($reason);
}

void printReason(EvaluationReason reason) {
  switch (reason.getKind()) {
    case OFF:
    	Timber.d("it's off");
    	break;
    case FALLTHROUGH:
    	Timber.d("fell through");
    	break;
    case TARGET_MATCH:
    	Timber.d("targeted");
      break;
    case RULE_MATCH:
    	EvaluationReason.RuleMatch rm = 
        (EvaluationReason.RuleMatch)reason;
    	Timber.d("matched rule %d/%s", 
      	       rm.getRuleIndex(), 
        	     rm.getRuleId());
    	break;
    case PREREQUISITE_FAILED:
    	EvaluationReason.PrerequisiteFailed pf = 
        (EvaluationReason.PrerequisiteFailed)reason;
    	Timber.d("prereq failed: %s", pf.getPrerequisiteKey());
    	break;
    case ERROR:
    	EvaluationReason.Error e = (EvaluationReason.Error)reason;
    	Timber.d("error: %s", e.getErrorKind());
  }
  // or, if all you want is a simple descriptive string:
  Timber.d(reason.toString());
}

Error conditions

If the kind is ERROR, it means that the SDK was unable to select any of the flag's variations, due to some abnormal condition. In this case, the returned value will be the default value that you specified in your code (that is, the last parameter of the method you called to evaluate the flag), rather than any value that you specified on your dashboard. Also, the variation index will be null/nil.

When there is an error, the reason object also has an errorKind property which will be one of the following:

  • "CLIENT_NOT_READY" - The client has not yet been able to establish a connection to LaunchDarkly (and, if there is a persistent feature store, the store does not yet contain flag data).
  • "FLAG_NOT_FOUND" - The flag key did not match any known flag.
  • "USER_NOT_SPECIFIED" - The user object or user key was not provided.
  • "MALFORMED_FLAG" - There was an internal inconsistency in the flag data, e.g. a rule specified a nonexistent variation. This is an unusual condition that may require assistance from LaunchDarkly support.
  • "WRONG_TYPE" - The application code requested the flag value with a different data type than it actually is (e.g., wanted a boolean but it is really a string). This can only happen in strongly typed languages, such as Go, Java, and C#.
  • "EXCEPTION" - An unexpected error stopped flag evaluation; this could happen if, for instance, you are using a persistent feature store and the database stops working. The SDK will always print the specific error to the log in this case.

Data export events

Calling any of the "variation detail" methods not only makes this extra information available to your code; it also causes the SDK to include it in analytics events, where you can see it if you use the event debugger or Data Export. The JSON representation of the reason data will be included in the feature evaluation event as an extra property called reason. For instance, a debug event might look like this:

{
  "type": "debug",
  "creationDate": 1548195712000,
  "key": "my-flag-key",
  "userKey": "test@example.com",
  "version": 1000,
  "variation": 0,
  "value": true,
  "default": false,
  "reason": {
    "kind": "TARGET_MATCH"
  }
}

Evaluation reasons


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.