def chained_exception(interface: ChainedException, event: Event, context: GroupingContext, **meta: Any) -> ReturnedVariants: # Case 1: we have a single exception, use the single exception # component directly to avoid a level of nesting exceptions = interface.exceptions() if len(exceptions) == 1: return context.get_grouping_component(exceptions[0], event=event, **meta) # Case 2: produce a component for each chained exception by_name: Dict[str, List[GroupingComponent]] = {} for exception in exceptions: for name, component in context.get_grouping_component(exception, event=event, **meta).items(): by_name.setdefault(name, []).append(component) rv = {} for name, component_list in by_name.items(): rv[name] = GroupingComponent( id="chained-exception", values=component_list, tree_label=calculate_tree_label(reversed(component_list)), ) return rv
def threads_legacy(interface: Threads, event: Event, context: GroupingContext, **meta: Any) -> ReturnedVariants: thread_count = len(interface.values) if thread_count != 1: return { context["variant"]: GroupingComponent( id="threads", contributes=False, hint="ignored because contains %d threads" % thread_count, ) } stacktrace = interface.values[0].get("stacktrace") if not stacktrace: return { context["variant"]: GroupingComponent(id="threads", contributes=False, hint="thread has no stacktrace") } return { context["variant"]: GroupingComponent(id="threads", values=[ context.get_grouping_component(stacktrace, event=event, **meta) ]) }
def _single_stacktrace_variant(stacktrace: Stacktrace, event: Event, context: GroupingContext, meta: Dict[str, Any]) -> ReturnedVariants: variant = context["variant"] frames = stacktrace.frames values: List[GroupingComponent] = [] prev_frame = None frames_for_filtering = [] for frame in frames: with context: context["is_recursion"] = is_recursion_v1(frame, prev_frame) frame_component = context.get_grouping_component(frame, event=event, **meta) if not context[ "hierarchical_grouping"] and variant == "app" and not frame.in_app: frame_component.update(contributes=False, hint="non app frame") values.append(frame_component) frames_for_filtering.append(frame.get_raw_data()) prev_frame = frame # Special case for JavaScript where we want to ignore single frame # stacktraces in certain cases where those would be of too low quality # for grouping. if (len(frames) == 1 and values[0].contributes and get_behavior_family_for_platform( frames[0].platform or event.platform) == "javascript" and not frames[0].function and frames[0].is_url()): values[0].update(contributes=False, hint="ignored single non-URL JavaScript frame") main_variant, inverted_hierarchy = context.config.enhancements.assemble_stacktrace_component( values, frames_for_filtering, event.platform, exception_data=context["exception_data"], similarity_self_encoder=_stacktrace_encoder, ) if inverted_hierarchy is None: inverted_hierarchy = stacktrace.snapshot inverted_hierarchy = bool(inverted_hierarchy) if not context["hierarchical_grouping"]: return {variant: main_variant} all_variants: ReturnedVariants = get_stacktrace_hierarchy( main_variant, values, frames_for_filtering, inverted_hierarchy) # done for backwards compat to find old groups all_variants["system"] = main_variant return all_variants
def stacktrace_legacy(interface: Stacktrace, event: Event, context: GroupingContext, **meta: Any) -> ReturnedVariants: variant = context["variant"] frames = interface.frames contributes = None hint = None all_frames_considered_in_app = False # TODO(dcramer): this should apply only to platform=javascript # Browser JS will often throw errors (from inlined code in an HTML page) # which contain only a single frame, no function name, and have the HTML # document as the filename. In this case the hash is often not usable as # the context cannot be trusted and the URL is dynamic (this also means # the line number cannot be trusted). if len(frames) == 1 and not frames[0].function and frames[0].is_url(): contributes = False hint = "ignored single frame stack" elif variant == "app": total_frames = len(frames) in_app_count = sum(1 if f.in_app else 0 for f in frames) if in_app_count == 0: in_app_count = total_frames all_frames_considered_in_app = True # if app frames make up less than 10% of the stacktrace discard # the hash as invalid if total_frames > 0 and in_app_count / float(total_frames) < 0.10: contributes = False hint = "less than 10% of frames are in-app" values = [] prev_frame: Optional[Frame] = None frames_for_filtering = [] for frame in frames: frame_component: GroupingComponent = context.get_grouping_component( frame, event=event, variant=variant, **meta) if variant == "app" and not frame.in_app and not all_frames_considered_in_app: frame_component.update(contributes=False, hint="non app frame") elif prev_frame is not None and is_recursion_legacy(frame, prev_frame): frame_component.update(contributes=False, hint="ignored due to recursion") elif variant == "app" and not frame.in_app and all_frames_considered_in_app: frame_component.update( hint="frame considered in-app because no frame is in-app") values.append(frame_component) frames_for_filtering.append(frame.get_raw_data()) prev_frame = frame rv, _ = context.config.enhancements.assemble_stacktrace_component( values, frames_for_filtering, event.platform) rv.update(contributes=contributes, hint=hint) return {variant: rv}
def chained_exception_legacy(interface: ChainedException, event: Event, context: GroupingContext, **meta: Any) -> ReturnedVariants: # Case 1: we have a single exception, use the single exception # component directly exceptions = interface.exceptions() if len(exceptions) == 1: single_variant: GroupingComponent = context.get_grouping_component( exceptions[0], event=event, **meta) return {context["variant"]: single_variant} # Case 2: try to build a new component out of the individual # errors however with a trick. In case any exception has a # stacktrace we want to ignore all other exceptions. any_stacktraces = False values = [] for exception in exceptions: exception_component: GroupingComponent = context.get_grouping_component( exception, event=event, **meta) stacktrace_component = exception_component.get_subcomponent( "stacktrace") if stacktrace_component is not None and stacktrace_component.contributes: any_stacktraces = True values.append(exception_component) if any_stacktraces: for value in values: stacktrace_component = value.get_subcomponent("stacktrace") if stacktrace_component is None or not stacktrace_component.contributes: value.update(contributes=False, hint="exception has no stacktrace") return { context["variant"]: GroupingComponent(id="chained-exception", values=values) }
def single_exception_legacy(interface: SingleException, event: Event, context: GroupingContext, **meta: Any) -> ReturnedVariants: type_component = GroupingComponent( id="type", values=[interface.type] if interface.type else [], similarity_encoder=ident_encoder, contributes=False, ) value_component = GroupingComponent( id="value", values=[interface.value] if interface.value else [], similarity_encoder=text_shingle_encoder(5), contributes=False, ) stacktrace_component = GroupingComponent(id="stacktrace") if interface.stacktrace is not None: stacktrace_component = context.get_grouping_component( interface.stacktrace, event=event, **meta) if stacktrace_component.contributes: if interface.type: type_component.update(contributes=True) if interface.value: value_component.update( hint="stacktrace and type take precedence") elif interface.value: value_component.update(hint="stacktrace takes precedence") if not stacktrace_component.contributes: if interface.type: type_component.update(contributes=True) if interface.value: value_component.update(contributes=True) return { context["variant"]: GroupingComponent( id="exception", values=[stacktrace_component, type_component, value_component]) }
def _filtered_threads(threads: List[Dict[str, Any]], event: Event, context: GroupingContext, meta: Dict[str, Any]) -> Optional[ReturnedVariants]: if len(threads) != 1: return None stacktrace = threads[0].get("stacktrace") if not stacktrace: return { "app": GroupingComponent(id="threads", contributes=False, hint="thread has no stacktrace") } rv = {} for name, stacktrace_component in context.get_grouping_component( stacktrace, event=event, **meta).items(): rv[name] = GroupingComponent(id="threads", values=[stacktrace_component]) return rv
def get_grouping_variants_for_event(event, config=None): """Returns a dict of all grouping variants for this event.""" # If a checksum is set the only variant that comes back from this # event is the checksum variant. checksum = event.data.get("checksum") if checksum: if HASH_RE.match(checksum): return {"checksum": ChecksumVariant(checksum)} rv = { "hashed-checksum": ChecksumVariant(hash_from_values(checksum), hashed=True), } # The legacy code path also supported arbitrary values here but # it will blow up if it results in more than 32 bytes of data # as this cannot be inserted into the database. (See GroupHash.hash) if len(checksum) <= 32: rv["checksum"] = ChecksumVariant(checksum) return rv # Otherwise we go to the various forms of fingerprint handling. If the event carries # a materialized fingerprint info from server side fingerprinting we forward it to the # variants which can export additional information about them. fingerprint = event.data.get("fingerprint") or ["{{ default }}"] fingerprint_info = event.data.get("_fingerprint_info") defaults_referenced = sum(1 if is_default_fingerprint_var(d) else 0 for d in fingerprint) if config is None: config = load_default_grouping_config() context = GroupingContext(config) # At this point we need to calculate the default event values. If the # fingerprint is salted we will wrap it. components = _get_calculated_grouping_variants_for_event(event, context) # If no defaults are referenced we produce a single completely custom # fingerprint and mark all other variants as non-contributing if defaults_referenced == 0: rv = {} for (key, component) in components.items(): component.update( contributes=False, contributes_to_similarity=True, hint="custom fingerprint takes precedence", ) rv[key] = ComponentVariant(component, context.config) fingerprint = resolve_fingerprint_values(fingerprint, event.data) rv["custom-fingerprint"] = CustomFingerprintVariant(fingerprint, fingerprint_info) # If the fingerprints are unsalted, we can return them right away. elif defaults_referenced == 1 and len(fingerprint) == 1: rv = {} for (key, component) in components.items(): rv[key] = ComponentVariant(component, context.config) # Otherwise we need to salt each of the components. else: rv = {} fingerprint = resolve_fingerprint_values(fingerprint, event.data) for (key, component) in components.items(): rv[key] = SaltedComponentVariant( fingerprint, component, context.config, fingerprint_info ) # Ensure we have a fallback hash if nothing else works out if not any(x.contributes for x in rv.values()): rv["fallback"] = FallbackVariant() return rv
def single_exception(interface: SingleException, event: Event, context: GroupingContext, **meta: Any) -> ReturnedVariants: type_component = GroupingComponent( id="type", values=[interface.type] if interface.type else [], similarity_encoder=ident_encoder, ) system_type_component = type_component.shallow_copy() ns_error_component = None if interface.mechanism: if interface.mechanism.synthetic: # Ignore synthetic exceptions as they are produced from platform # specific error codes. # # For example there can be crashes with EXC_ACCESS_VIOLATION_* on Windows with # the same exact stacktrace as a crash with EXC_BAD_ACCESS on macOS. # # Do not update type component of system variant, such that regex # can be continuously modified without unnecessarily creating new # groups. type_component.update( contributes=False, hint="ignored because exception is synthetic") if interface.mechanism.meta and "ns_error" in interface.mechanism.meta: ns_error_component = GroupingComponent( id="ns-error", values=[ interface.mechanism.meta["ns_error"].get("domain"), interface.mechanism.meta["ns_error"].get("code"), ], ) if interface.stacktrace is not None: with context: context["exception_data"] = interface.to_json() stacktrace_variants = context.get_grouping_component( interface.stacktrace, event=event, **meta) else: stacktrace_variants = { "app": GroupingComponent(id="stacktrace"), } rv = {} for variant, stacktrace_component in stacktrace_variants.items(): values = [ stacktrace_component, system_type_component if variant == "system" else type_component, ] if ns_error_component is not None: values.append(ns_error_component) if context["with_exception_value_fallback"]: value_component = GroupingComponent( id="value", similarity_encoder=text_shingle_encoder(5)) value_in = interface.value if value_in is not None: value_trimmed = trim_message_for_grouping(value_in) hint = "stripped common values" if value_in != value_trimmed else None if value_trimmed: value_component.update(values=[value_trimmed], hint=hint) if stacktrace_component.contributes and value_component.contributes: value_component.update( contributes=False, contributes_to_similarity=True, hint="ignored because stacktrace takes precedence", ) if (ns_error_component is not None and ns_error_component.contributes and value_component.contributes): value_component.update( contributes=False, contributes_to_similarity=True, hint="ignored because ns-error info takes precedence", ) values.append(value_component) rv[variant] = GroupingComponent(id="exception", values=values) return rv