Skip to content

Commit be27816

Browse files
committed
update to have a to_log method so it's a 1 line change in tasks
1 parent a558175 commit be27816

File tree

5 files changed

+88
-48
lines changed

5 files changed

+88
-48
lines changed

src/sentry/workflow_engine/processors/workflow.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -591,40 +591,3 @@ def process_workflows(
591591

592592
fire_actions(actions, detector, event_data)
593593
return WorkflowEvaluation(tainted=False, **extra_data)
594-
595-
596-
def process_workflows_with_logs(
597-
batch_client: DelayedWorkflowClient,
598-
event_data: WorkflowEventData,
599-
event_start_time: datetime,
600-
detector: Detector | None = None,
601-
) -> WorkflowEvaluation:
602-
"""
603-
This method is used to create improved logging for `process_workflows`,
604-
where we can capture the `WorkflowEvaluation` results, and log them.
605-
606-
This should create a log for each issue, so we can determine why a workflow
607-
did or did not trigger.
608-
609-
TODO - further refactor the `process_actions` portion, to improve handling / logging.
610-
"""
611-
from sentry.workflow_engine.processors.workflow import process_workflows
612-
613-
evaluation = process_workflows(batch_client, event_data, event_start_time, detector)
614-
615-
if isinstance(evaluation, WorkflowEvaluation):
616-
# Attempted to evaluate, but no actions were triggered.
617-
618-
if evaluation.triggered_workflows is None:
619-
# No workflow trigger conditions were met
620-
logger.info(
621-
"process_workflows.evaluation.workflow.not_triggered", extra=asdict(evaluation)
622-
)
623-
else:
624-
# Workflow conditions met, but not action filters
625-
logger.info("process_workflows.evaluation.workflow.triggered", extra=asdict(evaluation))
626-
else:
627-
# Workflow was fully processed, and actions were triggered
628-
logger.info("process_workflows.evaluation.actions.triggered", extra=asdict(evaluation))
629-
630-
return evaluation

src/sentry/workflow_engine/tasks/workflows.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def process_workflow_activity(activity_id: int, group_id: int, detector_id: int)
4545
The task will get the Activity from the database, create a WorkflowEventData object,
4646
and then process the data in `process_workflows`.
4747
"""
48-
from sentry.workflow_engine.processors.workflow import process_workflows_with_logs
48+
from sentry.workflow_engine.processors.workflow import process_workflows
4949

5050
with transaction.atomic(router.db_for_write(Detector)):
5151
try:
@@ -69,10 +69,12 @@ def process_workflow_activity(activity_id: int, group_id: int, detector_id: int)
6969
)
7070
with quiet_redis_noise():
7171
batch_client = DelayedWorkflowClient()
72-
process_workflows_with_logs(
72+
evaluation = process_workflows(
7373
batch_client, event_data, event_start_time=activity.datetime, detector=detector
7474
)
7575

76+
evaluation.to_log(logger)
77+
7678
metrics.incr(
7779
"workflow_engine.tasks.process_workflows.activity_update.executed",
7880
tags={"activity_type": activity.type, "detector_type": detector.type},
@@ -104,7 +106,7 @@ def process_workflows_event(
104106
start_timestamp_seconds: float | None = None,
105107
**kwargs: dict[str, Any],
106108
) -> None:
107-
from sentry.workflow_engine.processors.workflow import process_workflows_with_logs
109+
from sentry.workflow_engine.processors.workflow import process_workflows
108110

109111
recorder = scopedstats.Recorder()
110112
start_time = time.time()
@@ -132,7 +134,11 @@ def process_workflows_event(
132134
)
133135
with quiet_redis_noise():
134136
batch_client = DelayedWorkflowClient()
135-
process_workflows_with_logs(batch_client, event_data, event_start_time=event_start_time)
137+
evaluation = process_workflows(
138+
batch_client, event_data, event_start_time=event_start_time
139+
)
140+
141+
evaluation.to_log(logger)
136142
duration = time.time() - start_time
137143
is_slow = duration > 1.0
138144
# We want full coverage for particularly slow cases, plus a random sampling.

src/sentry/workflow_engine/types.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from abc import ABC, abstractmethod
44
from dataclasses import dataclass, field
55
from enum import IntEnum, StrEnum
6+
from logging import Logger
67
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypedDict, TypeVar
78

89
from sentry.types.group import PriorityLevel
@@ -86,15 +87,48 @@ class WorkflowEvaluation:
8687

8788
tainted: bool
8889

89-
group_event: GroupEvent | None = None
9090
message: str | None = None
9191

92+
# data: WorkflowProcessingContextData
93+
94+
group_event: GroupEvent | None = None
9295
actions: list[Action] | None = None
9396
workflows: list[Workflow] | None = None
9497
triggered_actions: list[Action] | None = None
9598
triggered_workflows: list[Workflow] | None = None
9699
associated_detectors: list[Detector] | None = None
97100

101+
def to_log(self, logger: Logger) -> None:
102+
"""
103+
Determines how far in the process the evaluation got to
104+
and creates a structured log string to quickly find.
105+
106+
Then this will return the that log string, and the
107+
relevant processing data to be logged.
108+
"""
109+
log_str = "workflow_engine.process_workflows.evaluation"
110+
111+
if self.tainted:
112+
if self.triggered_workflows is None:
113+
log_str = f"{log_str}.workflows.not_triggered"
114+
else:
115+
log_str = f"{log_str}.workflows.triggered"
116+
else:
117+
log_str = f"{log_str}.actions.triggered"
118+
119+
# TODO - simplify the extra data with context data class
120+
logger.info(
121+
log_str,
122+
extra={
123+
"group_event": self.group_event,
124+
"actions": self.actions,
125+
"triggered_actions": self.triggered_actions,
126+
"workflows": self.workflows,
127+
"triggered_workflows": self.triggered_workflows,
128+
"associated_detectors": self.associated_detectors,
129+
},
130+
)
131+
98132

99133
class ConfigTransformer(ABC):
100134
"""

tests/sentry/workflow_engine/processors/test_workflow.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ def test_same_environment_only(self) -> None:
210210

211211
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
212212
assert result.triggered_workflows == {self.error_workflow, matching_env_workflow}
213-
assert result.message == "No actions to evaluate; filtered or not triggered"
214213

215214
def test_issue_occurrence_event(self) -> None:
216215
issue_occurrence = self.build_occurrence(evidence_data={"detector_id": self.detector.id})
@@ -235,9 +234,7 @@ def test_regressed_event(self) -> None:
235234
)
236235

237236
result = process_workflows(self.batch_client, self.event_data, FROZEN_TIME)
238-
assert result.tainted is True
239237
assert result.triggered_workflows == {self.error_workflow, workflow}
240-
assert result.message == "No actions to evaluate; filtered or not triggered"
241238

242239
@patch("sentry.utils.metrics.incr")
243240
@patch("sentry.workflow_engine.processors.detector.logger")

tests/sentry/workflow_engine/test_task.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ def setUp(self) -> None:
137137
self.activity.save()
138138
self.detector = self.create_detector(type=MetricIssue.slug)
139139

140-
def test_process_workflow_activity__no_workflows(self) -> None:
140+
@mock.patch("sentry.workflow_engine.tasks.workflows.logger")
141+
def test_process_workflow_activity__no_workflows(self, mock_logger) -> None:
141142
with mock.patch(
142143
"sentry.workflow_engine.processors.workflow.evaluate_workflow_triggers",
143144
return_value=set(),
@@ -150,6 +151,18 @@ def test_process_workflow_activity__no_workflows(self) -> None:
150151
# Short-circuit evaluation, no workflows associated
151152
assert mock_evaluate.call_count == 0
152153

154+
mock_logger.info.assert_called_once_with(
155+
"workflow_engine.process_workflows.evaluation.workflows.not_triggered",
156+
extra={
157+
"group_event": self.activity,
158+
"actions": None,
159+
"triggered_actions": None,
160+
"workflows": set(),
161+
"triggered_workflows": None,
162+
"associated_detectors": [self.detector],
163+
},
164+
)
165+
153166
@mock.patch(
154167
"sentry.workflow_engine.processors.workflow.evaluate_workflow_triggers",
155168
return_value=(set(), {}),
@@ -158,8 +171,9 @@ def test_process_workflow_activity__no_workflows(self) -> None:
158171
"sentry.workflow_engine.processors.workflow.evaluate_workflows_action_filters",
159172
return_value=set(),
160173
)
174+
@mock.patch("sentry.workflow_engine.tasks.workflows.logger")
161175
def test_process_workflow_activity__workflows__no_actions(
162-
self, mock_eval_actions, mock_evaluate
176+
self, mock_logger, mock_eval_actions, mock_evaluate
163177
):
164178
self.workflow = self.create_workflow(organization=self.organization)
165179
self.create_detector_workflow(
@@ -181,8 +195,23 @@ def test_process_workflow_activity__workflows__no_actions(
181195
mock_evaluate.assert_called_once_with({self.workflow}, event_data, mock.ANY)
182196
assert mock_eval_actions.call_count == 0
183197

198+
mock_logger.info.assert_called_once_with(
199+
"workflow_engine.process_workflows.evaluation.workflows.triggered",
200+
extra={
201+
"group_event": self.activity,
202+
"actions": None,
203+
"triggered_actions": None,
204+
"workflows": {self.workflow},
205+
"triggered_workflows": set(), # from the mock
206+
"associated_detectors": [self.detector],
207+
},
208+
)
209+
184210
@mock.patch("sentry.workflow_engine.processors.action.filter_recently_fired_workflow_actions")
185-
def test_process_workflow_activity(self, mock_filter_actions: mock.MagicMock) -> None:
211+
@mock.patch("sentry.workflow_engine.tasks.workflows.logger")
212+
def test_process_workflow_activity(
213+
self, mock_logger, mock_filter_actions: mock.MagicMock
214+
) -> None:
186215
self.workflow = self.create_workflow(organization=self.organization)
187216

188217
self.action_group = self.create_data_condition_group(logic_type="any-short")
@@ -210,6 +239,17 @@ def test_process_workflow_activity(self, mock_filter_actions: mock.MagicMock) ->
210239
)
211240

212241
mock_filter_actions.assert_called_once_with({self.action_group}, expected_event_data)
242+
mock_logger.info.assert_called_once_with(
243+
"workflow_engine.process_workflows.evaluation.actions.triggered",
244+
extra={
245+
"group_event": self.activity,
246+
"actions": {self.action_group},
247+
"triggered_actions": mock_filter_actions(), # the result of the mock
248+
"workflows": {self.workflow},
249+
"triggered_workflows": {self.workflow},
250+
"associated_detectors": [self.detector],
251+
},
252+
)
213253

214254
@mock.patch(
215255
"sentry.workflow_engine.models.incident_groupopenperiod.update_incident_based_on_open_period_status_change"

0 commit comments

Comments
 (0)