diff --git a/sentry_sdk/integrations/launchdarkly.py b/sentry_sdk/integrations/launchdarkly.py index 6b9d654cd1..066464cc22 100644 --- a/sentry_sdk/integrations/launchdarkly.py +++ b/sentry_sdk/integrations/launchdarkly.py @@ -4,37 +4,39 @@ from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.flag_utils import flag_error_processor -if TYPE_CHECKING: - from typing import Any, Optional - try: + import ldclient from ldclient.hook import Hook, Metadata if TYPE_CHECKING: from ldclient import LDClient from ldclient.hook import EvaluationSeriesContext from ldclient.evaluation import EvaluationDetail + + from typing import Any except ImportError: raise DidNotEnable("LaunchDarkly is not installed") class LaunchDarklyIntegration(Integration): identifier = "launchdarkly" - _client = None # type: Optional[LDClient] + _ld_client = None # type: LDClient | None - def __init__(self, client): - # type: (LDClient) -> None + def __init__(self, ld_client=None): + # type: (LDClient | None) -> None """ - :param client: An initialized LDClient instance. + :param client: An initialized LDClient instance. If a client is not provided, this + integration will attempt to use the shared global instance. """ - self.__class__._client = client + self.__class__._ld_client = ld_client @staticmethod def setup_once(): # type: () -> None - client = LaunchDarklyIntegration._client - if not client: - raise DidNotEnable("Error getting LDClient instance") + try: + client = LaunchDarklyIntegration._ld_client or ldclient.get() + except Exception as exc: + raise DidNotEnable("Error getting LaunchDarkly client. " + repr(exc)) # Register the flag collection hook with the LD client. client.add_hook(LaunchDarklyHook()) diff --git a/tests/integrations/launchdarkly/test_launchdarkly.py b/tests/integrations/launchdarkly/test_launchdarkly.py index 73f4f71886..e7576bb469 100644 --- a/tests/integrations/launchdarkly/test_launchdarkly.py +++ b/tests/integrations/launchdarkly/test_launchdarkly.py @@ -10,6 +10,7 @@ from ldclient.integrations.test_data import TestData import sentry_sdk +from sentry_sdk.integrations import DidNotEnable from sentry_sdk.integrations.launchdarkly import LaunchDarklyIntegration @@ -26,10 +27,11 @@ def test_launchdarkly_integration( uninstall_integration(LaunchDarklyIntegration.identifier) if use_global_client: ldclient.set_config(config) + sentry_init(integrations=[LaunchDarklyIntegration()]) client = ldclient.get() else: client = LDClient(config=config) - sentry_init(integrations=[LaunchDarklyIntegration(client)]) + sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) # Set test values td.update(td.flag("hello").variation_for_all(True)) @@ -61,7 +63,7 @@ def test_launchdarkly_integration_threaded( context = Context.create("user1") uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(client)]) + sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) events = capture_events() def task(flag_key): @@ -120,7 +122,7 @@ def test_launchdarkly_integration_asyncio( context = Context.create("user1") uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(client)]) + sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) events = capture_events() async def task(flag_key): @@ -164,3 +166,23 @@ async def runner(): {"flag": "world", "result": False}, ] } + + +def test_launchdarkly_integration_did_not_enable(sentry_init, uninstall_integration): + """ + Setup should fail when using global client and ldclient.set_config wasn't called. + + We're accessing ldclient internals to set up this test, so it might break if launchdarkly's + implementation changes. + """ + + ldclient._reset_client() + try: + ldclient.__lock.lock() + ldclient.__config = None + finally: + ldclient.__lock.unlock() + + uninstall_integration(LaunchDarklyIntegration.identifier) + with pytest.raises(DidNotEnable): + sentry_init(integrations=[LaunchDarklyIntegration()])