From 12889e729c939c3862f7209b162b5ae3f2320482 Mon Sep 17 00:00:00 2001 From: Oliver Browne Date: Mon, 25 Nov 2024 11:38:01 +0200 Subject: [PATCH 1/9] set personless and use a random value --- posthog/client.py | 4 ++-- posthog/exception_capture.py | 17 ++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/posthog/client.py b/posthog/client.py index 8aad8a9f..bb91758d 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -362,7 +362,7 @@ def page( def capture_exception( self, exception=None, - distinct_id=DEFAULT_DISTINCT_ID, + distinct_id=None, properties=None, context=None, timestamp=None, @@ -385,7 +385,7 @@ def capture_exception( self.log.warning("No exception information available") return - # Format stack trace like sentry + # Format stack trace for cymbal all_exceptions_with_trace = exceptions_from_error_tuple(exc_info) # Add in-app property to frames in the exceptions diff --git a/posthog/exception_capture.py b/posthog/exception_capture.py index 54d50e10..8fb3b715 100644 --- a/posthog/exception_capture.py +++ b/posthog/exception_capture.py @@ -1,6 +1,7 @@ import logging import sys import threading +import uuid from enum import Enum from typing import TYPE_CHECKING, List, Optional @@ -61,14 +62,16 @@ def exception_receiver(self, exc_info, extra_properties): def capture_exception(self, exception, metadata=None): try: - # if hasattr(sys, "ps1"): - # # Disable the excepthook for interactive Python shells - # return + distinct_id = metadata.get("distinct_id") if metadata else None - distinct_id = metadata.get("distinct_id") if metadata else DEFAULT_DISTINCT_ID - # Make sure we have a distinct_id if its empty in metadata - distinct_id = distinct_id or DEFAULT_DISTINCT_ID + # if there's no distinct_id, we'll generate one and set personless mode + # via $process_person_profile = false + properties = {} + if distinct_id is None: + properties["$process_person_profile"] = False + distinct_id = uuid.uuid4() - self.client.capture_exception(exception, distinct_id) + + self.client.capture_exception(exception, distinct_id, properties) except Exception as e: self.log.exception(f"Failed to capture exception: {e}") From 77bcef397a91064e0c889421d46864e4b80d9e08 Mon Sep 17 00:00:00 2001 From: Oliver Browne Date: Mon, 25 Nov 2024 11:44:47 +0200 Subject: [PATCH 2/9] ran black --- posthog/exception_capture.py | 1 - posthog/exception_integrations/django.py | 2 -- posthog/exception_utils.py | 1 - posthog/sentry/posthog_integration.py | 6 +++--- posthog/test/test_client.py | 7 ------- 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/posthog/exception_capture.py b/posthog/exception_capture.py index 8fb3b715..1a57d139 100644 --- a/posthog/exception_capture.py +++ b/posthog/exception_capture.py @@ -71,7 +71,6 @@ def capture_exception(self, exception, metadata=None): properties["$process_person_profile"] = False distinct_id = uuid.uuid4() - self.client.capture_exception(exception, distinct_id, properties) except Exception as e: self.log.exception(f"Failed to capture exception: {e}") diff --git a/posthog/exception_integrations/django.py b/posthog/exception_integrations/django.py index a2ee40d9..166a5dad 100644 --- a/posthog/exception_integrations/django.py +++ b/posthog/exception_integrations/django.py @@ -27,7 +27,6 @@ class DjangoIntegration: identifier = "django" def __init__(self, capture_exception_fn=None): - if DJANGO_VERSION < (4, 2): raise IntegrationEnablingError("Django 4.2 or newer is required.") @@ -55,7 +54,6 @@ def uninstall(self): class DjangoRequestExtractor: - def __init__(self, request): # type: (Any) -> None self.request = request diff --git a/posthog/exception_utils.py b/posthog/exception_utils.py index 17b7630d..9233f30b 100644 --- a/posthog/exception_utils.py +++ b/posthog/exception_utils.py @@ -21,7 +21,6 @@ if TYPE_CHECKING: - from types import FrameType, TracebackType from typing import ( # noqa: F401 Any, diff --git a/posthog/sentry/posthog_integration.py b/posthog/sentry/posthog_integration.py index 69d861c6..f5d8e1e0 100644 --- a/posthog/sentry/posthog_integration.py +++ b/posthog/sentry/posthog_integration.py @@ -43,9 +43,9 @@ def processor(event, hint): not not Hub.current.client.dsn and Dsn(Hub.current.client.dsn).project_id ) if project_id: - properties["$sentry_url"] = ( - f"{PostHogIntegration.prefix}{PostHogIntegration.organization}/issues/?project={project_id}&query={event['event_id']}" - ) + properties[ + "$sentry_url" + ] = f"{PostHogIntegration.prefix}{PostHogIntegration.organization}/issues/?project={project_id}&query={event['event_id']}" posthog.capture(posthog_distinct_id, "$exception", properties) diff --git a/posthog/test/test_client.py b/posthog/test/test_client.py index feb5dd2e..995eb362 100644 --- a/posthog/test/test_client.py +++ b/posthog/test/test_client.py @@ -100,7 +100,6 @@ def test_basic_super_properties(self): self.assertEqual(msg["properties"]["source"], "repo-name") def test_basic_capture_exception(self): - with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = self.client exception = Exception("test exception") @@ -128,7 +127,6 @@ def test_basic_capture_exception(self): ) def test_basic_capture_exception_with_distinct_id(self): - with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = self.client exception = Exception("test exception") @@ -156,7 +154,6 @@ def test_basic_capture_exception_with_distinct_id(self): ) def test_basic_capture_exception_with_correct_host_generation(self): - with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, host="https://aloha.com") exception = Exception("test exception") @@ -184,7 +181,6 @@ def test_basic_capture_exception_with_correct_host_generation(self): ) def test_basic_capture_exception_with_correct_host_generation_for_server_hosts(self): - with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, host="https://app.posthog.com") exception = Exception("test exception") @@ -212,7 +208,6 @@ def test_basic_capture_exception_with_correct_host_generation_for_server_hosts(s ) def test_basic_capture_exception_with_no_exception_given(self): - with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = self.client try: @@ -249,10 +244,8 @@ def test_basic_capture_exception_with_no_exception_given(self): self.assertEqual(capture_call[2]["$exception_list"][0]["stacktrace"]["frames"][0]["in_app"], True) def test_basic_capture_exception_with_no_exception_happening(self): - with mock.patch.object(Client, "capture", return_value=None) as patch_capture: with self.assertLogs("posthog", level="WARNING") as logs: - client = self.client client.capture_exception() From ee28995ffdeef9e7686e9be05349f94b471e46df Mon Sep 17 00:00:00 2001 From: Oliver Browne Date: Mon, 25 Nov 2024 11:53:36 +0200 Subject: [PATCH 3/9] rm default_distinct_id --- posthog/client.py | 2 +- posthog/exception_capture.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/posthog/client.py b/posthog/client.py index bb91758d..dc82c80b 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -10,7 +10,7 @@ from six import string_types from posthog.consumer import Consumer -from posthog.exception_capture import DEFAULT_DISTINCT_ID, ExceptionCapture +from posthog.exception_capture import ExceptionCapture from posthog.exception_utils import exc_info_from_error, exceptions_from_error_tuple, handle_in_app from posthog.feature_flags import InconclusiveMatchError, match_feature_flag_properties from posthog.poller import Poller diff --git a/posthog/exception_capture.py b/posthog/exception_capture.py index 1a57d139..775cb29c 100644 --- a/posthog/exception_capture.py +++ b/posthog/exception_capture.py @@ -13,9 +13,6 @@ class Integrations(str, Enum): Django = "django" -DEFAULT_DISTINCT_ID = "python-exceptions" - - class ExceptionCapture: # TODO: Add client side rate limiting to prevent spamming the server with exceptions From 555b6a4a38e0bdcbc81a122c4641de8a487fa83d Mon Sep 17 00:00:00 2001 From: Oliver Browne Date: Mon, 25 Nov 2024 11:56:47 +0200 Subject: [PATCH 4/9] fix init --- posthog/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/posthog/__init__.py b/posthog/__init__.py index 062e137b..fa56e8b8 100644 --- a/posthog/__init__.py +++ b/posthog/__init__.py @@ -2,7 +2,7 @@ from typing import Callable, Dict, List, Optional, Tuple # noqa: F401 from posthog.client import Client -from posthog.exception_capture import DEFAULT_DISTINCT_ID, Integrations # noqa: F401 +from posthog.exception_capture import Integrations # noqa: F401 from posthog.version import VERSION __version__ = VERSION @@ -289,7 +289,7 @@ def capture_exception( return _proxy( "capture_exception", exception=exception, - distinct_id=distinct_id or DEFAULT_DISTINCT_ID, + distinct_id=distinct_id, properties=properties, context=context, timestamp=timestamp, From 3043385ac6dfd2b2cf37b3fbe1715f0c75a7bffe Mon Sep 17 00:00:00 2001 From: Oliver Browne Date: Mon, 25 Nov 2024 12:05:42 +0200 Subject: [PATCH 5/9] fix tests --- posthog/test/test_client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/posthog/test/test_client.py b/posthog/test/test_client.py index 995eb362..d909804c 100644 --- a/posthog/test/test_client.py +++ b/posthog/test/test_client.py @@ -103,11 +103,11 @@ def test_basic_capture_exception(self): with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = self.client exception = Exception("test exception") - client.capture_exception(exception) + client.capture_exception(exception, distinct_id="distinct_id") self.assertTrue(patch_capture.called) capture_call = patch_capture.call_args[0] - self.assertEqual(capture_call[0], "python-exceptions") + self.assertEqual(capture_call[0], "distinct_id") self.assertEqual(capture_call[1], "$exception") self.assertEqual( capture_call[2], @@ -122,7 +122,7 @@ def test_basic_capture_exception(self): "value": "test exception", } ], - "$exception_personURL": "https://us.i.posthog.com/project/random_key/person/python-exceptions", + "$exception_personURL": "https://us.i.posthog.com/project/random_key/person/distinct_id", }, ) @@ -213,11 +213,11 @@ def test_basic_capture_exception_with_no_exception_given(self): try: raise Exception("test exception") except Exception: - client.capture_exception() + client.capture_exception(distinct_id="distinct_id") self.assertTrue(patch_capture.called) capture_call = patch_capture.call_args[0] - self.assertEqual(capture_call[0], "python-exceptions") + self.assertEqual(capture_call[0], "distinct_id") self.assertEqual(capture_call[1], "$exception") self.assertEqual(capture_call[2]["$exception_type"], "Exception") self.assertEqual(capture_call[2]["$exception_message"], "test exception") From 61eba6cc2cfcad033fb686450ee44bfa6598f044 Mon Sep 17 00:00:00 2001 From: David Newell Date: Mon, 25 Nov 2024 11:32:55 +0000 Subject: [PATCH 6/9] fix linting --- posthog/sentry/posthog_integration.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/posthog/sentry/posthog_integration.py b/posthog/sentry/posthog_integration.py index f5d8e1e0..69d861c6 100644 --- a/posthog/sentry/posthog_integration.py +++ b/posthog/sentry/posthog_integration.py @@ -43,9 +43,9 @@ def processor(event, hint): not not Hub.current.client.dsn and Dsn(Hub.current.client.dsn).project_id ) if project_id: - properties[ - "$sentry_url" - ] = f"{PostHogIntegration.prefix}{PostHogIntegration.organization}/issues/?project={project_id}&query={event['event_id']}" + properties["$sentry_url"] = ( + f"{PostHogIntegration.prefix}{PostHogIntegration.organization}/issues/?project={project_id}&query={event['event_id']}" + ) posthog.capture(posthog_distinct_id, "$exception", properties) From 8db3e315c118af697a30e117618c98bc5e5690f6 Mon Sep 17 00:00:00 2001 From: David Newell Date: Mon, 25 Nov 2024 11:40:01 +0000 Subject: [PATCH 7/9] undo line changes --- posthog/exception_integrations/django.py | 2 ++ posthog/exception_utils.py | 1 + posthog/test/test_client.py | 7 +++++++ 3 files changed, 10 insertions(+) diff --git a/posthog/exception_integrations/django.py b/posthog/exception_integrations/django.py index 166a5dad..a2ee40d9 100644 --- a/posthog/exception_integrations/django.py +++ b/posthog/exception_integrations/django.py @@ -27,6 +27,7 @@ class DjangoIntegration: identifier = "django" def __init__(self, capture_exception_fn=None): + if DJANGO_VERSION < (4, 2): raise IntegrationEnablingError("Django 4.2 or newer is required.") @@ -54,6 +55,7 @@ def uninstall(self): class DjangoRequestExtractor: + def __init__(self, request): # type: (Any) -> None self.request = request diff --git a/posthog/exception_utils.py b/posthog/exception_utils.py index 9233f30b..17b7630d 100644 --- a/posthog/exception_utils.py +++ b/posthog/exception_utils.py @@ -21,6 +21,7 @@ if TYPE_CHECKING: + from types import FrameType, TracebackType from typing import ( # noqa: F401 Any, diff --git a/posthog/test/test_client.py b/posthog/test/test_client.py index d909804c..6bdb7388 100644 --- a/posthog/test/test_client.py +++ b/posthog/test/test_client.py @@ -100,6 +100,7 @@ def test_basic_super_properties(self): self.assertEqual(msg["properties"]["source"], "repo-name") def test_basic_capture_exception(self): + with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = self.client exception = Exception("test exception") @@ -127,6 +128,7 @@ def test_basic_capture_exception(self): ) def test_basic_capture_exception_with_distinct_id(self): + with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = self.client exception = Exception("test exception") @@ -154,6 +156,7 @@ def test_basic_capture_exception_with_distinct_id(self): ) def test_basic_capture_exception_with_correct_host_generation(self): + with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, host="https://aloha.com") exception = Exception("test exception") @@ -181,6 +184,7 @@ def test_basic_capture_exception_with_correct_host_generation(self): ) def test_basic_capture_exception_with_correct_host_generation_for_server_hosts(self): + with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = Client(FAKE_TEST_API_KEY, on_error=self.set_fail, host="https://app.posthog.com") exception = Exception("test exception") @@ -208,6 +212,7 @@ def test_basic_capture_exception_with_correct_host_generation_for_server_hosts(s ) def test_basic_capture_exception_with_no_exception_given(self): + with mock.patch.object(Client, "capture", return_value=None) as patch_capture: client = self.client try: @@ -244,8 +249,10 @@ def test_basic_capture_exception_with_no_exception_given(self): self.assertEqual(capture_call[2]["$exception_list"][0]["stacktrace"]["frames"][0]["in_app"], True) def test_basic_capture_exception_with_no_exception_happening(self): + with mock.patch.object(Client, "capture", return_value=None) as patch_capture: with self.assertLogs("posthog", level="WARNING") as logs: + client = self.client client.capture_exception() From 9fe08120ed10ae8f2be050ce3ea966e3e2461bd9 Mon Sep 17 00:00:00 2001 From: David Newell Date: Mon, 25 Nov 2024 11:42:01 +0000 Subject: [PATCH 8/9] update version --- CHANGELOG.md | 4 ++++ posthog/version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5008e64..63b0c111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.7.3 - 2024-11-25 + +1. Use personless mode when sending an exception without a provided `distinct_id`. + ## 3.7.2 - 2024-11-19 1. Add `type` property to exception stacks. diff --git a/posthog/version.py b/posthog/version.py index f0f5a9e2..74b62d61 100644 --- a/posthog/version.py +++ b/posthog/version.py @@ -1,4 +1,4 @@ -VERSION = "3.7.2" +VERSION = "3.7.3" if __name__ == "__main__": print(VERSION, end="") # noqa: T201 From 6dbbfc3e668f3fe327a740e85578fae383d6e005 Mon Sep 17 00:00:00 2001 From: David Newell Date: Mon, 25 Nov 2024 12:00:03 +0000 Subject: [PATCH 9/9] move distinct_id protection lower --- posthog/client.py | 9 ++++++++- posthog/exception_capture.py | 11 +---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/posthog/client.py b/posthog/client.py index dc82c80b..3707d555 100644 --- a/posthog/client.py +++ b/posthog/client.py @@ -4,7 +4,7 @@ import os import sys from datetime import datetime, timedelta -from uuid import UUID +from uuid import UUID, uuid4 from dateutil.tz import tzutc from six import string_types @@ -373,6 +373,13 @@ def capture_exception( # this is important to ensure we don't unexpectedly re-raise exceptions in the user's code. try: properties = properties or {} + + # if there's no distinct_id, we'll generate one and set personless mode + # via $process_person_profile = false + if distinct_id is None: + properties["$process_person_profile"] = False + distinct_id = uuid4() + require("distinct_id", distinct_id, ID_TYPES) require("properties", properties, dict) diff --git a/posthog/exception_capture.py b/posthog/exception_capture.py index 775cb29c..26e7c1a3 100644 --- a/posthog/exception_capture.py +++ b/posthog/exception_capture.py @@ -1,7 +1,6 @@ import logging import sys import threading -import uuid from enum import Enum from typing import TYPE_CHECKING, List, Optional @@ -60,14 +59,6 @@ def exception_receiver(self, exc_info, extra_properties): def capture_exception(self, exception, metadata=None): try: distinct_id = metadata.get("distinct_id") if metadata else None - - # if there's no distinct_id, we'll generate one and set personless mode - # via $process_person_profile = false - properties = {} - if distinct_id is None: - properties["$process_person_profile"] = False - distinct_id = uuid.uuid4() - - self.client.capture_exception(exception, distinct_id, properties) + self.client.capture_exception(exception, distinct_id) except Exception as e: self.log.exception(f"Failed to capture exception: {e}")