Skip to content

Commit 9b21090

Browse files
📌 ISSUE-#173: Resolve merge conflict with develop — keep Providers removed from How-to
2 parents 119f81d + f74ea3c commit 9b21090

15 files changed

Lines changed: 796 additions & 0 deletions

File tree

‎docs/nav/integrations/index.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,28 @@
8686

8787
</div>
8888

89+
## Sentry
90+
91+
<div class="grid cards dotflow-integration-grid" markdown>
92+
93+
- :material-bug-outline: __Error Monitoring__
94+
95+
---
96+
97+
Send task errors to **Sentry** for real-time error tracking in production workflows.
98+
99+
[:octicons-arrow-right-24: How-to](../tutorial/log-sentry.md)
100+
101+
- :material-speedometer: __Performance Monitoring__
102+
103+
---
104+
105+
Track workflow transactions and task spans with **Sentry Performance** — durations, waterfalls, and bottlenecks.
106+
107+
[:octicons-arrow-right-24: How-to](../tutorial/tracer-sentry.md)
108+
109+
</div>
110+
89111
## Built-in
90112

91113
<div class="grid cards dotflow-integration-grid" markdown>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# LogSentry
2+
3+
::: dotflow.providers.log_sentry.LogSentry
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# TracerSentry
2+
3+
::: dotflow.providers.tracer_sentry.TracerSentry
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Log Sentry
2+
3+
`LogSentry` sends task errors to [Sentry](https://sentry.io) for real-time error monitoring. Status changes are recorded as breadcrumbs for context.
4+
5+
Use this when your workflows run in production (Lambda, Cloud Run, servers) and you need error tracking beyond local logs.
6+
7+
/// note
8+
Requires `pip install dotflow[sentry]`
9+
///
10+
11+
## Setup
12+
13+
```bash
14+
pip install dotflow[sentry]
15+
```
16+
17+
## Parameters
18+
19+
| Parameter | Type | Default | Description |
20+
|-----------|------|---------|-------------|
21+
| `dsn` | `str` | — | Sentry DSN for the project (required) |
22+
| `environment` | `str \| None` | `None` | Environment tag sent to Sentry |
23+
| `traces_sample_rate` | `float` | `0.0` | Sample rate for performance traces (0.0 to 1.0) |
24+
25+
## Basic example
26+
27+
{* ./docs_src/config/log_sentry.py hl[2,16:20] *}
28+
29+
## What gets captured
30+
31+
| Event | Sentry action |
32+
|-------|---------------|
33+
| Task status changes (info) | Breadcrumb |
34+
| Task retries (warning) | Breadcrumb |
35+
| Task failures (error) | `capture_message` with extras |
36+
| Debug events | Ignored |
37+
38+
Each error capture includes:
39+
40+
- `workflow_id` — which workflow failed
41+
- `task_id` — which task failed
42+
- `exception` — exception type
43+
- `attempt` — retry attempt number
44+
- `traceback` — full traceback
45+
46+
## Combining with other log providers
47+
48+
Sentry is for error monitoring, not general logging. Use it alongside `LogDefault` or `LogOpenTelemetry` by choosing one per workflow:
49+
50+
```python
51+
from dotflow import Config, DotFlow
52+
from dotflow.providers import LogSentry
53+
54+
# Production: errors go to Sentry
55+
prod_config = Config(
56+
log=LogSentry(dsn="https://xxx@sentry.io/123", environment="production"),
57+
)
58+
59+
# Development: logs go to console
60+
from dotflow.providers import LogDefault
61+
dev_config = Config(
62+
log=LogDefault(output="console", format="json"),
63+
)
64+
```
65+
66+
## References
67+
68+
- [LogSentry](https://dotflow-io.github.io/dotflow/nav/reference/log-sentry/)
69+
- [Log Default](https://dotflow-io.github.io/dotflow/nav/tutorial/log-default/)
70+
- [Log OpenTelemetry](https://dotflow-io.github.io/dotflow/nav/tutorial/log-opentelemetry/)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Tracer Sentry
2+
3+
`TracerSentry` uses [Sentry Performance Monitoring](https://docs.sentry.io/product/performance/) to create transactions per workflow and spans per task.
4+
5+
Use this when you want to track task durations, identify slow tasks, and see the full workflow waterfall in the Sentry dashboard.
6+
7+
/// note
8+
Requires `pip install dotflow[sentry]`
9+
///
10+
11+
## Setup
12+
13+
```bash
14+
pip install dotflow[sentry]
15+
```
16+
17+
## Parameters
18+
19+
| Parameter | Type | Default | Description |
20+
|-----------|------|---------|-------------|
21+
| `dsn` | `str \| None` | `None` | Sentry DSN. If None, reuses the SDK already initialized (e.g. by LogSentry) |
22+
| `environment` | `str \| None` | `None` | Environment tag sent to Sentry |
23+
| `traces_sample_rate` | `float` | `1.0` | Sample rate for performance traces (0.0 to 1.0) |
24+
25+
## Basic example
26+
27+
{* ./docs_src/config/tracer_sentry.py hl[2,17:22] *}
28+
29+
## What gets captured
30+
31+
| Event | Sentry action |
32+
|-------|---------------|
33+
| Workflow starts | Transaction created (`op="workflow"`) |
34+
| Workflow ends | Transaction finished with status `ok` or `internal_error` |
35+
| Task starts | Child span created (`op="task"`) |
36+
| Task ends | Span finished with duration, retry count, and error details |
37+
38+
## Full Sentry stack
39+
40+
Use both `LogSentry` and `TracerSentry` together for error tracking + performance monitoring:
41+
42+
{* ./docs_src/config/sentry_full.py hl[2,17:24] *}
43+
44+
/// tip
45+
When using both providers, pass the `dsn` only to `LogSentry`. `TracerSentry` reuses the already initialized SDK — no need to pass `dsn` again.
46+
///
47+
48+
## References
49+
50+
- [TracerSentry](https://dotflow-io.github.io/dotflow/nav/reference/tracer-sentry/)
51+
- [LogSentry](https://dotflow-io.github.io/dotflow/nav/tutorial/log-sentry/)
52+
- [Tracer OpenTelemetry](https://dotflow-io.github.io/dotflow/nav/tutorial/tracer-opentelemetry/)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from dotflow import Config, DotFlow, action
2+
from dotflow.providers import LogSentry
3+
4+
5+
@action
6+
def extract():
7+
return {"data": "fetched"}
8+
9+
10+
@action
11+
def transform(previous_context):
12+
return {"result": previous_context.storage}
13+
14+
15+
def main():
16+
config = Config(
17+
log=LogSentry(
18+
dsn="https://xxx@sentry.io/123",
19+
environment="production",
20+
),
21+
)
22+
23+
workflow = DotFlow(config=config)
24+
workflow.task.add(step=extract)
25+
workflow.task.add(step=transform)
26+
workflow.start()
27+
28+
return workflow
29+
30+
31+
if __name__ == "__main__":
32+
main()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from dotflow import Config, DotFlow, action
2+
from dotflow.providers import LogSentry, TracerSentry
3+
4+
5+
@action
6+
def extract():
7+
return {"data": "fetched"}
8+
9+
10+
@action
11+
def transform(previous_context):
12+
return {"result": previous_context.storage}
13+
14+
15+
def main():
16+
config = Config(
17+
log=LogSentry(
18+
dsn="https://xxx@sentry.io/123",
19+
environment="production",
20+
),
21+
tracer=TracerSentry(
22+
traces_sample_rate=1.0,
23+
),
24+
)
25+
26+
workflow = DotFlow(config=config)
27+
workflow.task.add(step=extract)
28+
workflow.task.add(step=transform)
29+
workflow.start()
30+
31+
return workflow
32+
33+
34+
if __name__ == "__main__":
35+
main()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from dotflow import Config, DotFlow, action
2+
from dotflow.providers import TracerSentry
3+
4+
5+
@action
6+
def extract():
7+
return {"data": "fetched"}
8+
9+
10+
@action
11+
def transform(previous_context):
12+
return {"result": previous_context.storage}
13+
14+
15+
def main():
16+
config = Config(
17+
tracer=TracerSentry(
18+
dsn="https://xxx@sentry.io/123",
19+
environment="production",
20+
traces_sample_rate=1.0,
21+
),
22+
)
23+
24+
workflow = DotFlow(config=config)
25+
workflow.task.add(step=extract)
26+
workflow.task.add(step=transform)
27+
workflow.start()
28+
29+
return workflow
30+
31+
32+
if __name__ == "__main__":
33+
main()

‎dotflow/providers/__init__.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
__all__ = [
1414
"LogDefault",
1515
"LogOpenTelemetry",
16+
"LogSentry",
1617
"NotifyDefault",
1718
"NotifyDiscord",
1819
"NotifyTelegram",
@@ -26,6 +27,7 @@
2627
"MetricsOpenTelemetry",
2728
"TracerDefault",
2829
"TracerOpenTelemetry",
30+
"TracerSentry",
2931
]
3032

3133

@@ -62,4 +64,14 @@ def __getattr__(name):
6264

6365
return LogOpenTelemetry
6466

67+
if name == "LogSentry":
68+
from dotflow.providers.log_sentry import LogSentry
69+
70+
return LogSentry
71+
72+
if name == "TracerSentry":
73+
from dotflow.providers.tracer_sentry import TracerSentry
74+
75+
return TracerSentry
76+
6577
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""Log Sentry"""
2+
3+
from __future__ import annotations
4+
5+
from dotflow.abc.log import Log
6+
from dotflow.core.exception import ModuleNotFound
7+
8+
9+
class LogSentry(Log):
10+
"""
11+
Import:
12+
You can import the **LogSentry** class with:
13+
14+
from dotflow.providers import LogSentry
15+
16+
Example:
17+
`class` dotflow.providers.log_sentry.LogSentry
18+
19+
from dotflow import Config, DotFlow
20+
from dotflow.providers import LogSentry
21+
22+
config = Config(
23+
log=LogSentry(
24+
dsn="https://xxx@sentry.io/123",
25+
environment="production",
26+
),
27+
)
28+
29+
workflow = DotFlow(config=config)
30+
31+
Args:
32+
dsn (str): Sentry DSN for the project.
33+
environment (str): Environment tag sent to Sentry.
34+
Defaults to None.
35+
traces_sample_rate (float): Sample rate for performance traces (0.0 to 1.0).
36+
Defaults to 0.0.
37+
"""
38+
39+
def __init__(
40+
self,
41+
dsn: str,
42+
environment: str | None = None,
43+
traces_sample_rate: float = 0.0,
44+
) -> None:
45+
try:
46+
import sentry_sdk
47+
except ImportError as err:
48+
raise ModuleNotFound(
49+
module="sentry-sdk", library="dotflow[sentry]"
50+
) from err
51+
52+
sentry_sdk.init(
53+
dsn=dsn,
54+
environment=environment,
55+
traces_sample_rate=traces_sample_rate,
56+
)
57+
self._sentry = sentry_sdk
58+
59+
def info(self, **kwargs) -> None:
60+
task = kwargs.get("task")
61+
if task:
62+
self._sentry.add_breadcrumb(
63+
category="dotflow",
64+
message=f"task:{task.task_id} {task.status}",
65+
level="info",
66+
)
67+
68+
def error(self, **kwargs) -> None:
69+
task = kwargs.get("task")
70+
if not task or not task.errors:
71+
return
72+
73+
last_error = task.errors[-1]
74+
self._sentry.capture_message(
75+
f"Task {task.task_id} failed: {last_error.message}",
76+
level="error",
77+
extras={
78+
"workflow_id": str(task.workflow_id),
79+
"task_id": str(task.task_id),
80+
"exception": str(last_error.exception),
81+
"attempt": last_error.attempt,
82+
"traceback": last_error.traceback,
83+
},
84+
)
85+
86+
def warning(self, **kwargs) -> None:
87+
task = kwargs.get("task")
88+
if task:
89+
self._sentry.add_breadcrumb(
90+
category="dotflow",
91+
message=f"task:{task.task_id} {task.status}",
92+
level="warning",
93+
)
94+
95+
def debug(self, **kwargs) -> None:
96+
pass

0 commit comments

Comments
 (0)