Skip to content

Commit e309bd7

Browse files
feat: add code variables capture (#365)
* feat: add code variables capture * feat: bump version and add changelog
1 parent 0cfd678 commit e309bd7

File tree

7 files changed

+656
-3
lines changed

7 files changed

+656
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 6.9.0 - 2025-11-06
2+
3+
- feat(error-tracking): add local variables capture
4+
15
# 6.8.0 - 2025-11-03
26

37
- feat(llma): send web search calls to be used for LLM cost calculations

posthog/__init__.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
tag as inner_tag,
1111
set_context_session as inner_set_context_session,
1212
identify_context as inner_identify_context,
13+
set_capture_exception_code_variables_context as inner_set_capture_exception_code_variables_context,
14+
set_code_variables_mask_patterns_context as inner_set_code_variables_mask_patterns_context,
15+
set_code_variables_ignore_patterns_context as inner_set_code_variables_ignore_patterns_context,
1316
)
1417
from posthog.feature_flags import InconclusiveMatchError, RequiresServerEvaluation
1518
from posthog.types import FeatureFlag, FlagsAndPayloads, FeatureFlagResult
@@ -20,13 +23,14 @@
2023
"""Context management."""
2124

2225

23-
def new_context(fresh=False, capture_exceptions=True):
26+
def new_context(fresh=False, capture_exceptions=True, client=None):
2427
"""
2528
Create a new context scope that will be active for the duration of the with block.
2629
2730
Args:
2831
fresh: Whether to start with a fresh context (default: False)
2932
capture_exceptions: Whether to capture exceptions raised within the context (default: True)
33+
client: Optional Posthog client instance to use for this context (default: None)
3034
3135
Examples:
3236
```python
@@ -39,7 +43,9 @@ def new_context(fresh=False, capture_exceptions=True):
3943
Category:
4044
Contexts
4145
"""
42-
return inner_new_context(fresh=fresh, capture_exceptions=capture_exceptions)
46+
return inner_new_context(
47+
fresh=fresh, capture_exceptions=capture_exceptions, client=client
48+
)
4349

4450

4551
def scoped(fresh=False, capture_exceptions=True):
@@ -103,6 +109,27 @@ def identify_context(distinct_id: str):
103109
return inner_identify_context(distinct_id)
104110

105111

112+
def set_capture_exception_code_variables_context(enabled: bool):
113+
"""
114+
Set whether code variables are captured for the current context.
115+
"""
116+
return inner_set_capture_exception_code_variables_context(enabled)
117+
118+
119+
def set_code_variables_mask_patterns_context(mask_patterns: list):
120+
"""
121+
Variable names matching these patterns will be masked with *** when capturing code variables.
122+
"""
123+
return inner_set_code_variables_mask_patterns_context(mask_patterns)
124+
125+
126+
def set_code_variables_ignore_patterns_context(ignore_patterns: list):
127+
"""
128+
Variable names matching these patterns will be ignored completely when capturing code variables.
129+
"""
130+
return inner_set_code_variables_ignore_patterns_context(ignore_patterns)
131+
132+
106133
def tag(name: str, value: Any):
107134
"""
108135
Add a tag to the current context.

posthog/client.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
handle_in_app,
2020
exception_is_already_captured,
2121
mark_exception_as_captured,
22+
try_attach_code_variables_to_frames,
23+
DEFAULT_CODE_VARIABLES_MASK_PATTERNS,
24+
DEFAULT_CODE_VARIABLES_IGNORE_PATTERNS,
2225
)
2326
from posthog.feature_flags import (
2427
InconclusiveMatchError,
@@ -39,6 +42,9 @@
3942
_get_current_context,
4043
get_context_distinct_id,
4144
get_context_session_id,
45+
get_capture_exception_code_variables_context,
46+
get_code_variables_mask_patterns_context,
47+
get_code_variables_ignore_patterns_context,
4248
new_context,
4349
)
4450
from posthog.types import (
@@ -178,6 +184,9 @@ def __init__(
178184
before_send=None,
179185
flag_fallback_cache_url=None,
180186
enable_local_evaluation=True,
187+
capture_exception_code_variables=False,
188+
code_variables_mask_patterns=None,
189+
code_variables_ignore_patterns=None,
181190
):
182191
"""
183192
Initialize a new PostHog client instance.
@@ -233,6 +242,18 @@ def __init__(
233242
self.privacy_mode = privacy_mode
234243
self.enable_local_evaluation = enable_local_evaluation
235244

245+
self.capture_exception_code_variables = capture_exception_code_variables
246+
self.code_variables_mask_patterns = (
247+
code_variables_mask_patterns
248+
if code_variables_mask_patterns is not None
249+
else DEFAULT_CODE_VARIABLES_MASK_PATTERNS
250+
)
251+
self.code_variables_ignore_patterns = (
252+
code_variables_ignore_patterns
253+
if code_variables_ignore_patterns is not None
254+
else DEFAULT_CODE_VARIABLES_IGNORE_PATTERNS
255+
)
256+
236257
if project_root is None:
237258
try:
238259
project_root = os.getcwd()
@@ -979,6 +1000,34 @@ def capture_exception(
9791000
**properties,
9801001
}
9811002

1003+
context_enabled = get_capture_exception_code_variables_context()
1004+
context_mask = get_code_variables_mask_patterns_context()
1005+
context_ignore = get_code_variables_ignore_patterns_context()
1006+
1007+
enabled = (
1008+
context_enabled
1009+
if context_enabled is not None
1010+
else self.capture_exception_code_variables
1011+
)
1012+
mask_patterns = (
1013+
context_mask
1014+
if context_mask is not None
1015+
else self.code_variables_mask_patterns
1016+
)
1017+
ignore_patterns = (
1018+
context_ignore
1019+
if context_ignore is not None
1020+
else self.code_variables_ignore_patterns
1021+
)
1022+
1023+
if enabled:
1024+
try_attach_code_variables_to_frames(
1025+
all_exceptions_with_trace_and_in_app,
1026+
exc_info,
1027+
mask_patterns=mask_patterns,
1028+
ignore_patterns=ignore_patterns,
1029+
)
1030+
9821031
if self.log_captured_exceptions:
9831032
self.log.exception(exception, extra=kwargs)
9841033

posthog/contexts.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ def __init__(
2222
self.session_id: Optional[str] = None
2323
self.distinct_id: Optional[str] = None
2424
self.tags: Dict[str, Any] = {}
25+
self.capture_exception_code_variables: Optional[bool] = None
26+
self.code_variables_mask_patterns: Optional[list] = None
27+
self.code_variables_ignore_patterns: Optional[list] = None
2528

2629
def set_session_id(self, session_id: str):
2730
self.session_id = session_id
@@ -32,6 +35,15 @@ def set_distinct_id(self, distinct_id: str):
3235
def add_tag(self, key: str, value: Any):
3336
self.tags[key] = value
3437

38+
def set_capture_exception_code_variables(self, enabled: bool):
39+
self.capture_exception_code_variables = enabled
40+
41+
def set_code_variables_mask_patterns(self, mask_patterns: list):
42+
self.code_variables_mask_patterns = mask_patterns
43+
44+
def set_code_variables_ignore_patterns(self, ignore_patterns: list):
45+
self.code_variables_ignore_patterns = ignore_patterns
46+
3547
def get_parent(self):
3648
return self.parent
3749

@@ -59,6 +71,27 @@ def collect_tags(self) -> Dict[str, Any]:
5971
tags.update(new_tags)
6072
return tags
6173

74+
def get_capture_exception_code_variables(self) -> Optional[bool]:
75+
if self.capture_exception_code_variables is not None:
76+
return self.capture_exception_code_variables
77+
if self.parent is not None and not self.fresh:
78+
return self.parent.get_capture_exception_code_variables()
79+
return None
80+
81+
def get_code_variables_mask_patterns(self) -> Optional[list]:
82+
if self.code_variables_mask_patterns is not None:
83+
return self.code_variables_mask_patterns
84+
if self.parent is not None and not self.fresh:
85+
return self.parent.get_code_variables_mask_patterns()
86+
return None
87+
88+
def get_code_variables_ignore_patterns(self) -> Optional[list]:
89+
if self.code_variables_ignore_patterns is not None:
90+
return self.code_variables_ignore_patterns
91+
if self.parent is not None and not self.fresh:
92+
return self.parent.get_code_variables_ignore_patterns()
93+
return None
94+
6295

6396
_context_stack: contextvars.ContextVar[Optional[ContextScope]] = contextvars.ContextVar(
6497
"posthog_context_stack", default=None
@@ -243,6 +276,54 @@ def get_context_distinct_id() -> Optional[str]:
243276
return None
244277

245278

279+
def set_capture_exception_code_variables_context(enabled: bool) -> None:
280+
"""
281+
Set whether code variables are captured for the current context.
282+
"""
283+
current_context = _get_current_context()
284+
if current_context:
285+
current_context.set_capture_exception_code_variables(enabled)
286+
287+
288+
def set_code_variables_mask_patterns_context(mask_patterns: list) -> None:
289+
"""
290+
Variable names matching these patterns will be masked with *** when capturing code variables.
291+
"""
292+
current_context = _get_current_context()
293+
if current_context:
294+
current_context.set_code_variables_mask_patterns(mask_patterns)
295+
296+
297+
def set_code_variables_ignore_patterns_context(ignore_patterns: list) -> None:
298+
"""
299+
Variable names matching these patterns will be ignored completely when capturing code variables.
300+
"""
301+
current_context = _get_current_context()
302+
if current_context:
303+
current_context.set_code_variables_ignore_patterns(ignore_patterns)
304+
305+
306+
def get_capture_exception_code_variables_context() -> Optional[bool]:
307+
current_context = _get_current_context()
308+
if current_context:
309+
return current_context.get_capture_exception_code_variables()
310+
return None
311+
312+
313+
def get_code_variables_mask_patterns_context() -> Optional[list]:
314+
current_context = _get_current_context()
315+
if current_context:
316+
return current_context.get_code_variables_mask_patterns()
317+
return None
318+
319+
320+
def get_code_variables_ignore_patterns_context() -> Optional[list]:
321+
current_context = _get_current_context()
322+
if current_context:
323+
return current_context.get_code_variables_ignore_patterns()
324+
return None
325+
326+
246327
F = TypeVar("F", bound=Callable[..., Any])
247328

248329

0 commit comments

Comments
 (0)