22from contextlib import contextmanager
33import sys
44import logging
5- import copy
6- import time
75import datetime
86import atexit
7+ from typing import Optional , Dict , Any , List
98
109from honeybadger .plugins import default_plugin_manager
1110import honeybadger .connection as connection
1211import honeybadger .fake_connection as fake_connection
1312from .events_worker import EventsWorker
1413from .config import Configuration
1514from .notice import Notice
15+ from .context_store import ContextStore
1616
1717logger = logging .getLogger ("honeybadger" )
1818logger .addHandler (logging .NullHandler ())
1919
20+ error_context = ContextStore ("honeybadger_error_context" )
21+ event_context = ContextStore ("honeybadger_event_context" )
22+
2023
2124class Honeybadger (object ):
2225 TS_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
2326
2427 def __init__ (self ):
28+ error_context .clear ()
29+ event_context .clear ()
30+
2531 self .config = Configuration ()
26- self .thread_local = threading .local ()
27- self .thread_local .context = {}
2832 self .events_worker = EventsWorker (
2933 self ._connection (), self .config , logger = logging .getLogger ("honeybadger" )
3034 )
@@ -47,21 +51,16 @@ def _send_notice(self, notice):
4751
4852 self ._connection ().send_notice (self .config , notice )
4953
50- def _get_context (self ):
51- return getattr (self .thread_local , "context" , {})
52-
53- def begin_request (self , request ):
54- self .thread_local .context = self ._get_context ()
54+ def begin_request (self , _ ):
55+ error_context .clear ()
56+ event_context .clear ()
5557
5658 def wrap_excepthook (self , func ):
5759 self .existing_except_hook = func
5860 sys .excepthook = self .exception_hook
5961
6062 def exception_hook (self , type , exception , exc_traceback ):
61- notice = Notice (
62- exception = exception , thread_local = self .thread_local , config = self .config
63- )
64- self ._send_notice (notice )
63+ self .notify (exception = exception )
6564 self .existing_except_hook (type , exception , exc_traceback )
6665
6766 def shutdown (self ):
@@ -72,18 +71,22 @@ def notify(
7271 exception = None ,
7372 error_class = None ,
7473 error_message = None ,
75- context = {} ,
74+ context : Optional [ Dict [ str , Any ]] = None ,
7675 fingerprint = None ,
77- tags = [] ,
76+ tags : Optional [ List [ str ]] = None ,
7877 ):
78+ base = error_context .get ()
79+ tag_ctx = base .pop ("_tags" , [])
80+ merged_ctx = {** base , ** (context or {})}
81+ merged_tags = list ({* tag_ctx , * (tags or [])})
82+
7983 notice = Notice (
8084 exception = exception ,
8185 error_class = error_class ,
8286 error_message = error_message ,
83- context = context ,
87+ context = merged_ctx ,
8488 fingerprint = fingerprint ,
85- tags = tags ,
86- thread_local = self .thread_local ,
89+ tags = merged_tags ,
8790 config = self .config ,
8891 )
8992 return self ._send_notice (notice )
@@ -124,7 +127,9 @@ def event(self, event_type=None, data=None, **kwargs):
124127 if isinstance (payload ["ts" ], datetime .datetime ):
125128 payload ["ts" ] = payload ["ts" ].strftime (self .TS_FORMAT )
126129
127- return self .events_worker .push (payload )
130+ final_payload = {** self ._get_event_context (), ** payload }
131+
132+ return self .events_worker .push (final_payload )
128133
129134 def configure (self , ** kwargs ):
130135 self .config .set_config_from_dict (kwargs )
@@ -141,28 +146,37 @@ def auto_discover_plugins(self):
141146 if self .config .is_aws_lambda_environment :
142147 default_plugin_manager .register (contrib .AWSLambdaPlugin ())
143148
144- def set_context (self , ctx = None , ** kwargs ):
145- # This operation is an update, not a set!
146- if not ctx :
147- ctx = kwargs
148- else :
149- ctx .update (kwargs )
150- self .thread_local .context = self ._get_context ()
151- self .thread_local .context .update (ctx )
149+ # Error context
150+ #
151+ def _get_context (self ):
152+ return error_context .get ()
153+
154+ def set_context (self , ctx : Optional [Dict [str , Any ]] = None , ** kwargs ):
155+ error_context .update (ctx , ** kwargs )
152156
153157 def reset_context (self ):
154- self . thread_local . context = {}
158+ error_context . clear ()
155159
156160 @contextmanager
157- def context (self , ** kwargs ):
158- original_context = copy .copy (self ._get_context ())
159- self .set_context (** kwargs )
160- try :
161+ def context (self , ctx : Optional [Dict [str , Any ]] = None , ** kwargs ):
162+ with error_context .override (ctx , ** kwargs ):
163+ yield
164+
165+ # Event context
166+ #
167+ def _get_event_context (self ):
168+ return event_context .get ()
169+
170+ def set_event_context (self , ctx : Optional [Dict [str , Any ]] = None , ** kwargs ):
171+ event_context .update (ctx , ** kwargs )
172+
173+ def reset_event_context (self ):
174+ event_context .clear ()
175+
176+ @contextmanager
177+ def event_context (self , ctx : Optional [Dict [str , Any ]] = None , ** kwargs ):
178+ with event_context .override (ctx , ** kwargs ):
161179 yield
162- except :
163- raise
164- else :
165- self .thread_local .context = original_context
166180
167181 def _connection (self ):
168182 if self .config .is_dev () and not self .config .force_report_data :
0 commit comments