-
Notifications
You must be signed in to change notification settings - Fork 0
Diagnostics
The SDK exposes three complementary debugging mechanisms: structured diagnostic
logging, typed errors with .code and .context attributes, and *Diagnostic
result objects that describe evaluation decisions without raising. This page
covers all three and walks through what to gather when filing a support ticket.
All internal SDK activity emits structured DEBUG-level log records through the
Python logging module on the logger named convert_sdk.diagnostics.
Enable diagnostic output by setting that logger to DEBUG:
import logging
logging.getLogger("convert_sdk.diagnostics").setLevel(logging.DEBUG)
logging.basicConfig(
level=logging.DEBUG,
format="%(name)s %(message)s %(extra)s", # adjust to your formatter
)Or in Django settings.py:
LOGGING = {
"version": 1,
"handlers": {"console": {"class": "logging.StreamHandler"}},
"loggers": {
"convert_sdk.diagnostics": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": False,
},
},
}Loggers used by the SDK:
| Logger name | Content |
|---|---|
convert_sdk.diagnostics |
Evaluation, bucketing, and tracking lifecycle events |
convert_sdk.tracking |
Delivery warnings when HTTP transport fails |
convert_sdk.refresh |
Background config-refresh callback errors |
Each record has two extra fields:
| Field | Description |
|---|---|
sdk_event |
Dot-separated event name, e.g. evaluation.experience.completed
|
sdk_details |
Redacted dict of event-specific fields |
Diagnostic logs never emit raw visitor ids, SDK keys, secrets, cookies, or raw
attribute mappings. Sensitive values are replaced with "<redacted>". Visitor
ids are replaced with a 16-character SHA-256 prefix (visitor_ref).
| sdk_event | Fired by | Useful fields |
|---|---|---|
sdk.initialization.started |
Core.__init__ |
source, transport_type
|
sdk.initialization.succeeded |
Core.__init__ |
is_ready, entity_counts
|
sdk.initialization.failed |
Core.__init__ |
error_type, error_code
|
context.created |
Core.create_context |
had_existing_state, supplied_visitor_attribute_count
|
evaluation.experience.completed |
Context.run_experience / diagnose_experience
|
matched, reason, variation_key, bucket_value
|
evaluation.experiences.completed |
Context.run_experiences |
result_count |
evaluation.feature.completed |
Context.run_feature / diagnose_feature
|
matched, reason, status
|
evaluation.features.completed |
Context.run_features |
result_count |
evaluation.custom_segments.completed |
Context.run_custom_segments |
matched_segment_count |
tracking.conversion.started |
Context.track_conversion |
has_conversion_data |
tracking.conversion.queued |
Context.track_conversion |
event_count, queued_event_count
|
tracking.conversion.deduplicated |
Context.track_conversion |
reason |
tracking.queue.release.started |
release_queues |
pending_event_count, batch_size
|
tracking.queue.release.succeeded |
release_queues |
delivered_event_count |
tracking.delivery.failed |
release_queues |
error_type, remaining_event_count
|
lookup.goal.completed |
Context.diagnose_goal |
resolved, reason
|
lookup.entity.completed |
Context.diagnose_config_entity |
entity_type, resolved
|
All SDK errors derive from ConvertSDKError and carry structured metadata:
class ConvertSDKError(Exception):
code: str | None # machine-readable error code
context: Mapping[str, Any] # structured metadata (immutable)ConvertSDKError
├── InitializationError
│ ├── ConfigValidationError (code: "config.validation_error")
│ └── ConfigLoadError (code: "config.load_error")
└── TrackingError
├── GoalNotFoundError (code: "goal.not_found")
├── ConversionDataError (code: "conversion.data_error")
└── TrackingDeliveryError (code: "tracking.delivery_error")
Example structured error handling:
from convert_sdk import GoalNotFoundError, ConfigLoadError
try:
context.track_conversion("unknown-goal")
except GoalNotFoundError as exc:
print(exc.code) # "goal.not_found"
print(exc.context["goal_key"]) # "unknown-goal"
print(exc.context["available_goal_count"]) # how many goals existInstead of calling run_experience() (which returns ExperienceResult | None),
call diagnose_experience() to get the full decision record without raising:
diag = context.diagnose_experience(
"checkout-flow",
location_attributes={"path": "/checkout"},
)
print(diag.resolved) # bool — True when bucketed
print(diag.reason) # str — why decision was made
print(diag.message) # human-readable description
print(diag.result) # ExperienceResult | None
print(diag.details) # Mapping with bucket_value, etc.The same pattern applies to features and goals:
feat_diag = context.diagnose_feature("checkout-banner")
goal_diag = context.diagnose_goal("purchase")
entity_diag = context.diagnose_config_entity("experience", "checkout-flow")
entity_by_id = context.diagnose_config_entity_by_id("experience", "exp-checkout")See Type Hints for the field-by-field reference of each diagnostic class.
reason |
Meaning | What to check |
|---|---|---|
bucketed |
Visitor is in the experience and received a variation | Variation key is in result
|
experience_not_found |
The experience key was not in the config snapshot | Verify experience key in the dashboard |
experience_inactive |
Experience status is not active
|
Check experience status in the dashboard |
environment_miss |
The experience does not include the requested environment | Check environment in SDKConfig and the experience's environments list |
audience_miss |
Visitor attributes did not satisfy the audience rules | Print visitor_attributes and compare to audience rules |
location_miss |
Location attributes did not match the site-area rules | Print location_attributes and compare to site-area rules |
outside_traffic |
Bucket value exceeded the total allocated traffic | Check traffic_allocation sum in variations; verify bucket_value in details
|
all_variations_excluded |
All variations have status set to exclude them |
Check variation statuses in the dashboard |
Feature evaluation backs every feature lookup through an experience. The reason
codes are the same as the experience codes above, plus:
reason |
What it means |
|---|---|
feature_not_found |
Key typo or feature not in config |
no_backing_experience |
Feature exists but no experience references it |
The ExperienceDiagnostic.details dict always includes bucket_value when
bucketing was attempted. This value is deterministic across all Convert SDKs for
the same (visitor_id, experience_id) pair — you can compare the Python SDK's
bucket_value to the JavaScript SDK's bucketing output to verify parity.
The parity test suite in the python-sdk repository under tests/parity/
exercises this contract with shared test vectors.
To re-derive a bucket value by hand:
from convert_sdk.evaluation.bucketing import get_bucket_value
bucket = get_bucket_value(
visitor_id="visitor-abc123",
experience_id="exp-checkout",
)
print("bucket_value:", bucket) # compare to JS SDK outputThe Python SDK uses a pure-Python MurmurHash3 32-bit implementation with seed
9999. The hash input is always f"{experience_id}{visitor_id}" (experience id
first). The JavaScript and PHP SDKs use the same algorithm and input order.
from convert_sdk import Core, SDKConfig, ConfigLoadError
try:
core = Core(SDKConfig(sdk_key=os.environ["CONVERT_SDK_KEY"]))
except ConfigLoadError as exc:
print("code:", exc.code)
print("context:", dict(exc.context))
print("is_ready:", core.is_ready)
snapshot = core.snapshot
print("experiences:", len(snapshot.experiences_by_key))
print("features:", len(snapshot.features_by_key))
print("goals:", len(snapshot.goals_by_key))If core.is_ready is True but entity counts are zero, the config was loaded
but is empty — check whether the correct environment was requested.
ConfigSnapshot (the type returned by core.snapshot) lives at
convert_sdk.domain.config_snapshot and is treated as a stable internal type
for support / introspection use. Its field names (experiences_by_key,
features_by_key, goals_by_key) are part of that informal contract — they
will not be renamed without notice — but they are not re-exported from the
top-level convert_sdk package, so import them only from this submodule when
you need a type annotation.
If events are not appearing in the Convert dashboard:
- Check
flush_result.remaining_event_count— non-zero means delivery was interrupted. - Subscribe to
LifecycleEvent.TRACKING_DELIVERY_FAILEDto capture theerror_type(see Code Examples — Queue Control). - Confirm that
release_queues()is called before the process exits or the request completes. - Confirm that
sdk_keyoraccount_id/project_idis present in the config snapshot — the transport requires at least one routing identifier.
Collect the following before opening an issue:
-
SDK version —
python -c "import convert_sdk; print(convert_sdk.__version__)" -
Python version —
python --version - Initialization mode — SDK key or direct config?
- Diagnostic log output — Enable debug logging (above) and capture the output for the failing request.
-
Diagnostic result — For evaluation or tracking failures, use the
diagnose_*methods and include the fullreason,message, anddetailsfields. -
Error details — If an exception was raised, include
exc.codeanddict(exc.context). - Privacy note — Raw visitor ids and SDK keys are automatically redacted in diagnostic logs; do not manually re-add them to your bug report.
When filing a bug, include a minimal standalone script that reproduces the issue using direct config mode (no network dependency):
import os
from convert_sdk import Core, SDKConfig
config = {
"account_id": "YOUR_ACCOUNT_ID",
"project": {"id": "YOUR_PROJECT_ID", "name": "Repro"},
"experiences": [
# paste the minimal experience definition from your config
],
"features": [],
"goals": [],
}
core = Core(SDKConfig(config_data=config))
context = core.create_context("repro-visitor-1", {"tier": "premium"})
diag = context.diagnose_experience("your-experience-key")
print(diag.resolved, diag.reason, dict(diag.details))Using config_data instead of sdk_key avoids network dependencies and
confidential key exposure in bug reports.
-
Type Hints — full reference for
*Diagnosticand result types - Code Examples — Queue Control — lifecycle events for delivery monitoring
- Extending — replace the logger or transport for custom observability
- Troubleshooting — common cross-SDK issues and fixes
Copyrights © 2025 All Rights Reserved by Convert Insights, Inc.
Getting Started
Python SDK
- Quickstart
- Installation
- Initialization
- Configuration
- Code Examples
- Type Hints
- Diagnostics
- Extending
- Testing
- Async & Frameworks
Migration
Core Concepts
- Experiences & Variations
- Feature Flags
- Bucketing Algorithm
- Rule Evaluation
- Segments
- Data Management
- Event System
- API Communication
How-To Guides
- Running Experiences
- Running Features
- Tracking Conversions
- Visitor Context
- Persistent DataStore
- Troubleshooting
Edge & Integrations
Maintainers