Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/opencensus/trace/stack_trace.py: 30%

64 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-06 06:04 +0000

1# Copyright 2017, OpenCensus Authors 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15import hashlib 

16import os 

17import random 

18import traceback 

19 

20from opencensus.common.utils import get_truncatable_str 

21 

22MAX_FRAMES = 128 

23 

24BUILD_ID = os.environ.get('BUILD_ID', 'unknown') 

25SOURCE_VERSION = os.environ.get('SOURCE_VERSION', 'unknown') 

26 

27 

28class StackFrame(object): 

29 """Represents a single stack frame in a stack trace. 

30 

31 :type func_name: str 

32 :param func_name: The fully-qualified name that uniquely identifies the 

33 function or method that is active in this frame (up to 

34 1024 bytes). 

35 

36 :type original_func_name: str 

37 :param original_func_name: An un-mangled function name, if functionName is 

38 mangled. The name can be fully-qualified 

39 (up to 1024 bytes). 

40 

41 :type file_name: str 

42 :param file_name: The name of the source file where the function call 

43 appears (up to 256 bytes). 

44 

45 :type line_num: int 

46 :param line_num: The line number in fileName where the function call 

47 appears. 

48 

49 :type col_num: int 

50 :param col_num: The column number where the function call appears, if 

51 available. This is important in JavaScript because of its 

52 anonymous functions. 

53 

54 :type load_module: str 

55 :param load_module: For example: main binary, kernel modules, and dynamic 

56 libraries such as libc.so, sharedlib.so 

57 (up to 256 bytes). 

58 

59 :type build_id: str 

60 :param build_id: A unique identifier for the module, usually a hash of its 

61 contents (up to 128 bytes). 

62 

63 

64 :type source_version: str 

65 :param source_version: The version of the deployed source code 

66 (up to 128 bytes). 

67 """ 

68 def __init__(self, 

69 func_name, 

70 original_func_name, 

71 file_name, 

72 line_num, 

73 col_num, 

74 load_module, 

75 build_id, 

76 source_version): 

77 self.func_name = func_name 

78 self.original_func_name = original_func_name 

79 self.file_name = file_name 

80 self.line_num = line_num 

81 self.col_num = col_num 

82 self.load_module = load_module 

83 self.build_id = build_id 

84 self.source_version = source_version 

85 

86 def format_stack_frame_json(self): 

87 """Convert StackFrame object to json format.""" 

88 stack_frame_json = {} 

89 stack_frame_json['function_name'] = get_truncatable_str( 

90 self.func_name) 

91 stack_frame_json['original_function_name'] = get_truncatable_str( 

92 self.original_func_name) 

93 stack_frame_json['file_name'] = get_truncatable_str(self.file_name) 

94 stack_frame_json['line_number'] = self.line_num 

95 stack_frame_json['column_number'] = self.col_num 

96 stack_frame_json['load_module'] = { 

97 'module': get_truncatable_str(self.load_module), 

98 'build_id': get_truncatable_str(self.build_id), 

99 } 

100 stack_frame_json['source_version'] = get_truncatable_str( 

101 self.source_version) 

102 

103 return stack_frame_json 

104 

105 

106class StackTrace(object): 

107 """A call stack appearing in a trace. 

108 

109 :type stack_frames: list 

110 :param stack_frames: Stack frames in this stack trace. A maximum of 128 

111 frames are allowed. 

112 

113 :type stack_trace_hash_id: str 

114 :param stack_trace_hash_id: The hash ID is used to conserve network 

115 bandwidth for duplicate stack traces within a 

116 single trace. 

117 """ 

118 def __init__(self, stack_frames=None, stack_trace_hash_id=None): 

119 if stack_frames is None: 

120 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 

126 

127 if stack_trace_hash_id is None: 

128 stack_trace_hash_id = generate_hash_id() 

129 

130 self.stack_frames = stack_frames 

131 self.stack_trace_hash_id = stack_trace_hash_id 

132 

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 

156 def add_stack_frame(self, stack_frame): 

157 """Add StackFrame to frames list.""" 

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()) 

162 

163 def format_stack_trace_json(self): 

164 """Convert a StackTrace object to json format.""" 

165 stack_trace_json = {} 

166 

167 if self.stack_frames: 

168 stack_trace_json['stack_frames'] = { 

169 'frame': self.stack_frames, 

170 'dropped_frames_count': self.dropped_frames_count 

171 } 

172 

173 stack_trace_json['stack_trace_hash_id'] = self.stack_trace_hash_id 

174 

175 return stack_trace_json 

176 

177 

178def generate_hash_id(): 

179 """Generate a hash id.""" 

180 return random.getrandbits(64) 

181 

182 

183def generate_hash_id_from_traceback(tb): 

184 m = hashlib.md5() # nosec 

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)