def _generate_rule_label(project, rule, data): rule_cls = rules.get(data['id']) if rule_cls is None: return rule_inst = rule_cls(project, data=data, rule=rule) return rule_inst.render_label()
def apply_rule(self, rule): match = rule.data.get('action_match') or Rule.DEFAULT_ACTION_MATCH condition_list = rule.data.get('conditions', ()) frequency = rule.data.get('frequency') or Rule.DEFAULT_FREQUENCY # XXX(dcramer): if theres no condition should we really skip it, # or should we just apply it blindly? if not condition_list: return status = self.get_rule_status(rule) now = timezone.now() freq_offset = now - timedelta(minutes=frequency) if status.last_active and status.last_active > freq_offset: return state = self.get_state() condition_iter = (self.condition_matches(c, state, rule) for c in condition_list) if match == 'all': passed = all(condition_iter) elif match == 'any': passed = any(condition_iter) elif match == 'none': passed = not any(condition_iter) else: self.logger.error('Unsupported action_match %r for rule %d', match, rule.id) return if passed: passed = GroupRuleStatus.objects.filter( id=status.id, ).exclude( last_active__gt=freq_offset, ).update(last_active=now) if not passed: return for action in rule.data.get('actions', ()): action_cls = rules.get(action['id']) if action_cls is None: self.logger.warn('Unregistered action %r', action['id']) continue action_inst = action_cls(self.project, data=action, rule=rule) results = safe_execute( action_inst.after, event=self.event, state=state, _with_transaction=False ) if results is None: self.logger.warn('Action %s did not return any futures', action['id']) continue for future in results: self.futures_by_cb[future.callback ].append(RuleFuture(rule=rule, kwargs=future.kwargs))
def condition_matches(self, condition, state, rule): condition_cls = rules.get(condition['id']) if condition_cls is None: self.logger.warn('Unregistered condition %r', condition['id']) return condition_inst = condition_cls(self.project, data=condition, rule=rule) return safe_execute(condition_inst.passes, self.event, state, _with_transaction=False)
def condition_matches(project, condition, event, state): condition_cls = rules.get(condition['id']) if condition_cls is None: rules_logger.error('Unregistered condition %r', condition['id']) return condition_inst = condition_cls(project) return safe_execute(condition_inst.passes, event, state)
def condition_matches(project, condition, event, state): condition_cls = rules.get(condition['id']) if condition_cls is None: rules_logger.error('Unregistered condition %r', condition['id']) return condition_inst = condition_cls(project, data=condition) return safe_execute(condition_inst.passes, event, state)
def _generate_rule_label(project, rule, data): from sentry.rules import rules rule_cls = rules.get(data['id']) if rule_cls is None: return rule_inst = rule_cls(project, data=data, rule=rule) return rule_inst.render_label()
def get_rule_type(self, condition: Mapping[str, Any]) -> str | None: rule_cls = rules.get(condition["id"]) if rule_cls is None: self.logger.warning("Unregistered condition or filter %r", condition["id"]) return None rule_type: str = rule_cls.rule_type return rule_type
def condition_matches(self, condition: Mapping[str, Any], state: EventState, rule: Rule) -> bool | None: condition_cls = rules.get(condition["id"]) if condition_cls is None: self.logger.warning("Unregistered condition %r", condition["id"]) return None condition_inst = condition_cls(self.project, data=condition, rule=rule) passes: bool = safe_execute(condition_inst.passes, self.event, state, _with_transaction=False) return passes
def from_native(self, data): if not isinstance(data, dict): msg = 'Incorrect type. Expected a mapping, but got %s' raise ValidationError(msg % type(data).__name__) if 'id' not in data: raise ValidationError("Missing attribute 'id'") cls = rules.get(data['id'], self.type_name) if cls is None: msg = "Invalid node. Could not find '%s'" raise ValidationError(msg % data['id']) if not cls(self.context['project'], data).validate_form(): raise ValidationError('Node did not pass validation') return data
def from_native(self, data): if not isinstance(data, dict): msg = "Incorrect type. Expected a mapping, but got %s" raise ValidationError(msg % type(data).__name__) if "id" not in data: raise ValidationError("Missing attribute 'id'") cls = rules.get(data["id"], self.type_name) if cls is None: msg = "Invalid node. Could not find '%s'" raise ValidationError(msg % data["id"]) if not cls(self.context["project"], data).validate_form(): raise ValidationError("Node did not pass validation") return data
def to_internal_value(self, data): if not isinstance(data, dict): msg = "Incorrect type. Expected a mapping, but got %s" raise ValidationError(msg % type(data).__name__) if "id" not in data: raise ValidationError("Missing attribute 'id'") cls = rules.get(data["id"], self.type_name) if cls is None: msg = "Invalid node. Could not find '%s'" raise ValidationError(msg % data["id"]) node = cls(self.context["project"], data) # Nodes with user-declared fields will manage their own validation if node.id in SCHEMA_FORM_ACTIONS: if not data.get("hasSchemaFormConfig"): raise ValidationError( "Please configure your integration settings.") node.self_validate() return data if not node.form_cls: return data form = node.get_form_instance() if not form.is_valid(): # XXX(epurkhiser): Very hacky, but we really just want validation # errors that are more specific, not just 'this wasn't filled out', # give a more generic error for those. first_error = next(iter(form.errors.values()))[0] if first_error != "This field is required.": raise ValidationError(first_error) raise ValidationError("Ensure all required fields are filled in.") # Update data from cleaned form values data.update(form.cleaned_data) if getattr(form, "_pending_save", False): data["pending_save"] = True return data
def execute_rule(rule_id, event, state): """ Fires post processing hooks for a rule. """ from sentry.models import Project, Rule rule = Rule.objects.get(id=rule_id) project = Project.objects.get_from_cache(id=event.project_id) event.project = project event.group.project = project for action in rule.data.get('actions', ()): action_cls = rules.get(action['id']) if action_cls is None: rules_logger.error('Unregistered action %r', action['id']) continue action_inst = action_cls(project, data=action) safe_execute(action_inst.after, event=event, state=state)
def execute_rule(rule_id, event, state): """ Fires post processing hooks for a rule. """ from sentry.models import Project, Rule rule = Rule.objects.get(id=rule_id) project = Project.objects.get_from_cache(id=event.project_id) event.project = project event.group.project = project for action in rule.data.get('actions', ()): action_cls = rules.get(action['id']) if action_cls is None: rules_logger.error('Unregistered action %r', action['id']) continue action_inst = action_cls(project) safe_execute(action_inst.after, event=event, state=state)
def to_internal_value(self, data): if not isinstance(data, dict): msg = "Incorrect type. Expected a mapping, but got %s" raise ValidationError(msg % type(data).__name__) if "id" not in data: raise ValidationError("Missing attribute 'id'") cls = rules.get(data["id"], self.type_name) if cls is None: msg = "Invalid node. Could not find '%s'" raise ValidationError(msg % data["id"]) node = cls(self.context["project"], data) if not node.form_cls: return data form = node.get_form_instance() if not form.is_valid(): # XXX(epurkhiser): Very hacky, but we really just want validation # errors that are more specific, not just 'this wasn't filled out', # give a more generic error for those. first_error = next(six.itervalues(form.errors))[0] if first_error != "This field is required.": raise ValidationError(first_error) raise ValidationError( "Ensure at least one action is enabled and all required fields are filled in." ) # Update data from cleaned form values data.update(form.cleaned_data) if getattr(form, "_pending_save", False): data["pending_save"] = True return data
def from_native(self, data): if not isinstance(data, dict): msg = 'Incorrect type. Expected a mapping, but got %s' raise ValidationError(msg % type(data).__name__) if 'id' not in data: raise ValidationError("Missing attribute 'id'") cls = rules.get(data['id'], self.type_name) if cls is None: msg = "Invalid node. Could not find '%s'" raise ValidationError(msg % data['id']) node = cls(self.context['project'], data) if not node.form_cls: return data form = node.get_form_instance() if not form.is_valid(): # XXX(epurkhiser): Very hacky, but we really just want validation # errors that are more specific, not just 'this wasn't filled out', # give a more generic error for those. first_error = next(six.itervalues(form.errors))[0] if first_error != 'This field is required.': raise ValidationError(first_error) raise ValidationError( 'Ensure at least one action is enabled and all required fields are filled in.' ) # Update data from cleaned form values data.update(form.cleaned_data) return data
def apply_rule(self, rule: Rule, status: GroupRuleStatus) -> None: """ If all conditions and filters pass, execute every action. :param rule: `Rule` object :return: void """ condition_match = rule.data.get( "action_match") or Rule.DEFAULT_CONDITION_MATCH filter_match = rule.data.get( "filter_match") or Rule.DEFAULT_FILTER_MATCH rule_condition_list = rule.data.get("conditions", ()) frequency = rule.data.get("frequency") or Rule.DEFAULT_FREQUENCY if (rule.environment_id is not None and self.event.get_environment().id != rule.environment_id): return now = timezone.now() freq_offset = now - timedelta(minutes=frequency) if status.last_active and status.last_active > freq_offset: return state = self.get_state() condition_list = [] filter_list = [] for rule_cond in rule_condition_list: if self.get_rule_type(rule_cond) == "condition/event": condition_list.append(rule_cond) else: filter_list.append(rule_cond) # Sort `condition_list` so that most expensive conditions run last. condition_list.sort( key=lambda condition: any(condition_match in condition[ "id"] for condition_match in SLOW_CONDITION_MATCHES)) for predicate_list, match, name in ( (filter_list, filter_match, "filter"), (condition_list, condition_match, "condition"), ): if not predicate_list: continue predicate_iter = (self.condition_matches(f, state, rule) for f in predicate_list) predicate_func = self.get_match_function(match) if predicate_func: if not predicate_func(predicate_iter): return else: self.logger.error( f"Unsupported {name}_match {match!r} for rule {rule.id}", filter_match, rule.id) return updated = (GroupRuleStatus.objects.filter(id=status.id).exclude( last_active__gt=freq_offset).update(last_active=now)) if not updated: return if randrange(10) == 0: analytics.record( "issue_alert.fired", issue_id=self.group.id, project_id=rule.project.id, organization_id=rule.project.organization.id, rule_id=rule.id, ) history.record(rule, self.group) for action in rule.data.get("actions", ()): action_cls = rules.get(action["id"]) if action_cls is None: self.logger.warning("Unregistered action %r", action["id"]) continue action_inst = action_cls(self.project, data=action, rule=rule) results = safe_execute(action_inst.after, event=self.event, state=state, _with_transaction=False) if results is None: self.logger.warning("Action %s did not return any futures", action["id"]) continue for future in results: key = future.key if future.key is not None else future.callback rule_future = RuleFuture(rule=rule, kwargs=future.kwargs) if key not in self.grouped_futures: self.grouped_futures[key] = (future.callback, [rule_future]) else: self.grouped_futures[key][1].append(rule_future)
def apply_rule(self, rule): match = rule.data.get("action_match") or Rule.DEFAULT_ACTION_MATCH condition_list = rule.data.get("conditions", ()) frequency = rule.data.get("frequency") or Rule.DEFAULT_FREQUENCY # XXX(dcramer): if theres no condition should we really skip it, # or should we just apply it blindly? if not condition_list: return if ( rule.environment_id is not None and self.event.get_environment().id != rule.environment_id ): return status = self.get_rule_status(rule) now = timezone.now() freq_offset = now - timedelta(minutes=frequency) if status.last_active and status.last_active > freq_offset: return state = self.get_state() condition_iter = (self.condition_matches(c, state, rule) for c in condition_list) if match == "all": passed = all(condition_iter) elif match == "any": passed = any(condition_iter) elif match == "none": passed = not any(condition_iter) else: self.logger.error("Unsupported action_match %r for rule %d", match, rule.id) return if passed: passed = ( GroupRuleStatus.objects.filter(id=status.id) .exclude(last_active__gt=freq_offset) .update(last_active=now) ) if not passed: return if randrange(10) == 0: analytics.record( "issue_alert.fired", issue_id=self.group.id, project_id=rule.project.id, organization_id=rule.project.organization.id, rule_id=rule.id, ) for action in rule.data.get("actions", ()): action_cls = rules.get(action["id"]) if action_cls is None: self.logger.warn("Unregistered action %r", action["id"]) continue action_inst = action_cls(self.project, data=action, rule=rule) results = safe_execute( action_inst.after, event=self.event, state=state, _with_transaction=False ) if results is None: self.logger.warn("Action %s did not return any futures", action["id"]) continue for future in results: key = future.key if future.key is not None else future.callback rule_future = RuleFuture(rule=rule, kwargs=future.kwargs) if key not in self.grouped_futures: self.grouped_futures[key] = (future.callback, [rule_future]) else: self.grouped_futures[key][1].append(rule_future)
def apply_rule(self, rule): match = rule.data.get('action_match') or Rule.DEFAULT_ACTION_MATCH condition_list = rule.data.get('conditions', ()) frequency = rule.data.get('frequency') or Rule.DEFAULT_FREQUENCY # XXX(dcramer): if theres no condition should we really skip it, # or should we just apply it blindly? if not condition_list: return if rule.environment_id is not None \ and self.event.get_environment().id != rule.environment_id: return status = self.get_rule_status(rule) now = timezone.now() freq_offset = now - timedelta(minutes=frequency) if status.last_active and status.last_active > freq_offset: return state = self.get_state() condition_iter = (self.condition_matches(c, state, rule) for c in condition_list) if match == 'all': passed = all(condition_iter) elif match == 'any': passed = any(condition_iter) elif match == 'none': passed = not any(condition_iter) else: self.logger.error('Unsupported action_match %r for rule %d', match, rule.id) return if passed: passed = GroupRuleStatus.objects.filter( id=status.id, ).exclude( last_active__gt=freq_offset, ).update(last_active=now) if not passed: return for action in rule.data.get('actions', ()): action_cls = rules.get(action['id']) if action_cls is None: self.logger.warn('Unregistered action %r', action['id']) continue action_inst = action_cls(self.project, data=action, rule=rule) results = safe_execute( action_inst.after, event=self.event, state=state, _with_transaction=False ) if results is None: self.logger.warn('Action %s did not return any futures', action['id']) continue for future in results: key = future.key is not None or future.callback rule_future = RuleFuture(rule=rule, kwargs=future.kwargs) if key not in self.grouped_futures: self.grouped_futures[key] = (future.callback, [rule_future]) else: self.grouped_futures[key][1].append(rule_future)
def apply_rule(self, rule): match = rule.data.get('action_match', 'all') condition_list = rule.data.get('conditions', ()) # XXX(dcramer): if theres no condition should we really skip it, # or should we just apply it blindly? if not condition_list: return rule_status = self.get_rule_status(rule) state = self.get_state(rule_status) condition_iter = (self.condition_matches(c, state, rule) for c in condition_list) if match == 'all': passed = all(condition_iter) elif match == 'any': passed = any(condition_iter) elif match == 'none': passed = not any(condition_iter) else: self.logger.error('Unsupported action_match %r for rule %d', match, rule.id) return now = timezone.now() if passed and rule_status.status == GroupRuleStatus.INACTIVE: # we only fire if we're able to say that the state has changed GroupRuleStatus.objects.filter( id=rule_status.id, status=GroupRuleStatus.INACTIVE, ).update( status=GroupRuleStatus.ACTIVE, last_active=now, ) rule_status.last_active = now rule_status.status = GroupRuleStatus.ACTIVE elif not passed and rule_status.status == GroupRuleStatus.ACTIVE: # update the state to suggest this rule can fire again GroupRuleStatus.objects.filter( id=rule_status.id, status=GroupRuleStatus.ACTIVE, ).update(status=GroupRuleStatus.INACTIVE) rule_status.status = GroupRuleStatus.INACTIVE elif passed: GroupRuleStatus.objects.filter( id=rule_status.id, status=GroupRuleStatus.ACTIVE, ).update(last_active=now) rule_status.last_active = now if not passed: return for action in rule.data.get('actions', ()): action_cls = rules.get(action['id']) if action_cls is None: self.logger.warn('Unregistered action %r', action['id']) continue action_inst = action_cls(self.project, data=action, rule=rule) results = safe_execute(action_inst.after, event=self.event, state=state) if results is None: self.logger.warn('Action %s did not return any futures', action['id']) continue for future in results: self.futures_by_cb[future.callback].append( RuleFuture(rule=rule, kwargs=future.kwargs))
def apply_rule(self, rule): match = rule.data.get('action_match', 'all') condition_list = rule.data.get('conditions', ()) # XXX(dcramer): if theres no condition should we really skip it, # or should we just apply it blindly? if not condition_list: return rule_status = self.get_rule_status(rule) state = self.get_state(rule_status) condition_iter = ( self.condition_matches(c, state, rule) for c in condition_list ) if match == 'all': passed = all(condition_iter) elif match == 'any': passed = any(condition_iter) elif match == 'none': passed = not any(condition_iter) else: self.logger.error('Unsupported action_match %r for rule %d', match, rule.id) return now = timezone.now() if passed and rule_status.status == GroupRuleStatus.INACTIVE: # we only fire if we're able to say that the state has changed GroupRuleStatus.objects.filter( id=rule_status.id, status=GroupRuleStatus.INACTIVE, ).update( status=GroupRuleStatus.ACTIVE, last_active=now, ) rule_status.last_active = now rule_status.status = GroupRuleStatus.ACTIVE elif not passed and rule_status.status == GroupRuleStatus.ACTIVE: # update the state to suggest this rule can fire again GroupRuleStatus.objects.filter( id=rule_status.id, status=GroupRuleStatus.ACTIVE, ).update(status=GroupRuleStatus.INACTIVE) rule_status.status = GroupRuleStatus.INACTIVE elif passed: GroupRuleStatus.objects.filter( id=rule_status.id, status=GroupRuleStatus.ACTIVE, ).update(last_active=now) rule_status.last_active = now if not passed: return for action in rule.data.get('actions', ()): action_cls = rules.get(action['id']) if action_cls is None: self.logger.warn('Unregistered action %r', action['id']) continue action_inst = action_cls(self.project, data=action, rule=rule) results = safe_execute(action_inst.after, event=self.event, state=state, _with_transaction=False) if results is None: self.logger.warn('Action %s did not return any futures', action['id']) continue for future in results: self.futures_by_cb[future.callback].append( RuleFuture(rule=rule, kwargs=future.kwargs) )
def apply_rule(self, rule, status): """ If all conditions and filters pass, execute every action. :param rule: `Rule` object :return: void """ condition_match = rule.data.get("action_match") or Rule.DEFAULT_CONDITION_MATCH filter_match = rule.data.get("filter_match") or Rule.DEFAULT_FILTER_MATCH rule_condition_list = rule.data.get("conditions", ()) frequency = rule.data.get("frequency") or Rule.DEFAULT_FREQUENCY if ( rule.environment_id is not None and self.event.get_environment().id != rule.environment_id ): return now = timezone.now() freq_offset = now - timedelta(minutes=frequency) if status.last_active and status.last_active > freq_offset: return state = self.get_state() condition_list = [] filter_list = [] for rule_cond in rule_condition_list: if self.get_rule_type(rule_cond) == "condition/event": condition_list.append(rule_cond) else: filter_list.append(rule_cond) # if conditions exist evaluate them, otherwise move to the filters section if condition_list: condition_iter = (self.condition_matches(c, state, rule) for c in condition_list) condition_func = self.get_match_function(condition_match) if condition_func: condition_passed = condition_func(condition_iter) else: self.logger.error( "Unsupported condition_match %r for rule %d", condition_match, rule.id ) return if not condition_passed: return # if filters exist evaluate them, otherwise pass if filter_list: filter_iter = (self.condition_matches(f, state, rule) for f in filter_list) filter_func = self.get_match_function(filter_match) if filter_func: passed = filter_func(filter_iter) else: self.logger.error("Unsupported filter_match %r for rule %d", filter_match, rule.id) return else: passed = True if passed: passed = ( GroupRuleStatus.objects.filter(id=status.id) .exclude(last_active__gt=freq_offset) .update(last_active=now) ) if not passed: return if randrange(10) == 0: analytics.record( "issue_alert.fired", issue_id=self.group.id, project_id=rule.project.id, organization_id=rule.project.organization.id, rule_id=rule.id, ) for action in rule.data.get("actions", ()): action_cls = rules.get(action["id"]) if action_cls is None: self.logger.warning("Unregistered action %r", action["id"]) continue action_inst = action_cls(self.project, data=action, rule=rule) results = safe_execute( action_inst.after, event=self.event, state=state, _with_transaction=False ) if results is None: self.logger.warning("Action %s did not return any futures", action["id"]) continue for future in results: key = future.key if future.key is not None else future.callback rule_future = RuleFuture(rule=rule, kwargs=future.kwargs) if key not in self.grouped_futures: self.grouped_futures[key] = (future.callback, [rule_future]) else: self.grouped_futures[key][1].append(rule_future)
def _is_filter(data): from sentry.rules import rules rule_cls = rules.get(data["id"]) return rule_cls.rule_type == "filter/event"