1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15+ import hashlib
16+ import os
1517import random
18+ import traceback
1619
1720from opencensus .trace .utils import _get_truncatable_str
1821
22+ MAX_FRAMES = 128
23+
24+ BUILD_ID = os .environ .get ('BUILD_ID' , 'unknown' )
25+ SOURCE_VERSION = os .environ .get ('SOURCE_VERSION' , 'unknown' )
26+
1927
2028class StackFrame (object ):
2129 """Represents a single stack frame in a stack trace.
@@ -84,7 +92,7 @@ def format_stack_frame_json(self):
8492 self .original_func_name )
8593 stack_frame_json ['file_name' ] = _get_truncatable_str (self .file_name )
8694 stack_frame_json ['line_number' ] = self .line_num
87- stack_frame_json ['col_number ' ] = self .col_num
95+ stack_frame_json ['column_number ' ] = self .col_num
8896 stack_frame_json ['load_module' ] = {
8997 'module' : _get_truncatable_str (self .load_module ),
9098 'build_id' : _get_truncatable_str (self .build_id ),
@@ -110,23 +118,57 @@ class StackTrace(object):
110118 def __init__ (self , stack_frames = None , stack_trace_hash_id = None ):
111119 if stack_frames is None :
112120 stack_frames = []
121+ if len (stack_frames ) > MAX_FRAMES :
122+ self .dropped_frames_count = len (stack_frames ) - MAX_FRAMES
123+ stack_frames = stack_frames [- MAX_FRAMES :]
124+ else :
125+ self .dropped_frames_count = 0
113126
114127 if stack_trace_hash_id is None :
115128 stack_trace_hash_id = generate_hash_id ()
116129
117130 self .stack_frames = stack_frames
118131 self .stack_trace_hash_id = stack_trace_hash_id
119132
133+ @classmethod
134+ def from_traceback (cls , tb ):
135+ """Initializes a StackTrace from a python traceback instance"""
136+ stack_trace = cls (
137+ stack_trace_hash_id = generate_hash_id_from_traceback (tb )
138+ )
139+ # use the add_stack_frame so that json formatting is applied
140+ for tb_frame_info in traceback .extract_tb (tb ):
141+ filename , line_num , fn_name , _ = tb_frame_info
142+ stack_trace .add_stack_frame (
143+ StackFrame (
144+ func_name = fn_name ,
145+ original_func_name = fn_name ,
146+ file_name = filename ,
147+ line_num = line_num ,
148+ col_num = 0 , # I don't think this is available in python
149+ load_module = filename ,
150+ build_id = BUILD_ID ,
151+ source_version = SOURCE_VERSION
152+ )
153+ )
154+ return stack_trace
155+
120156 def add_stack_frame (self , stack_frame ):
121157 """Add StackFrame to frames list."""
122- self .stack_frames .append (stack_frame .format_stack_frame_json ())
158+ if len (self .stack_frames ) >= MAX_FRAMES :
159+ self .dropped_frames_count += 1
160+ else :
161+ self .stack_frames .append (stack_frame .format_stack_frame_json ())
123162
124163 def format_stack_trace_json (self ):
125164 """Convert a StackTrace object to json format."""
126165 stack_trace_json = {}
127166
128167 if self .stack_frames :
129- stack_trace_json ['stack_frames' ] = self .stack_frames
168+ stack_trace_json ['stack_frames' ] = {
169+ 'frame' : self .stack_frames ,
170+ 'dropped_frames_count' : self .dropped_frames_count
171+ }
130172
131173 stack_trace_json ['stack_trace_hash_id' ] = self .stack_trace_hash_id
132174
@@ -136,3 +178,12 @@ def format_stack_trace_json(self):
136178def generate_hash_id ():
137179 """Generate a hash id."""
138180 return random .getrandbits (64 )
181+
182+
183+ def generate_hash_id_from_traceback (tb ):
184+ m = hashlib .md5 ()
185+ for tb_line in traceback .format_tb (tb ):
186+ m .update (tb_line .encode ('utf-8' ))
187+ # truncate the hash for easier compatibility with StackDriver,
188+ # should still be unique enough to avoid collisions
189+ return int (m .hexdigest ()[:12 ], 16 )
0 commit comments