sentry-structlog 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sentry_structlog-3.0.0/LICENSE +22 -0
- sentry_structlog-3.0.0/PKG-INFO +611 -0
- sentry_structlog-3.0.0/README.md +579 -0
- sentry_structlog-3.0.0/pyproject.toml +89 -0
- sentry_structlog-3.0.0/sentry_structlog/__init__.py +429 -0
- sentry_structlog-3.0.0/sentry_structlog/py.typed +0 -0
- sentry_structlog-3.0.0/sentry_structlog.egg-info/PKG-INFO +611 -0
- sentry_structlog-3.0.0/sentry_structlog.egg-info/SOURCES.txt +11 -0
- sentry_structlog-3.0.0/sentry_structlog.egg-info/dependency_links.txt +1 -0
- sentry_structlog-3.0.0/sentry_structlog.egg-info/requires.txt +2 -0
- sentry_structlog-3.0.0/sentry_structlog.egg-info/top_level.txt +1 -0
- sentry_structlog-3.0.0/setup.cfg +4 -0
- sentry_structlog-3.0.0/test/test_sentry_processor.py +1613 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Kiwi.com
|
|
4
|
+
Copyright (c) 2026 Barsoomx
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sentry-structlog
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Sentry integration for structlog (thread-safe fork of structlog-sentry)
|
|
5
|
+
Author-email: "Kiwi.com platform" <platform@kiwi.com>
|
|
6
|
+
Maintainer: Barsoomx
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/Barsoomx/sentry-structlog
|
|
9
|
+
Project-URL: Repository, https://github.com/Barsoomx/sentry-structlog
|
|
10
|
+
Project-URL: Issues, https://github.com/Barsoomx/sentry-structlog/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/Barsoomx/sentry-structlog/blob/master/CHANGELOG.md
|
|
12
|
+
Project-URL: Upstream, https://github.com/kiwicom/structlog-sentry
|
|
13
|
+
Keywords: sentry,structlog,logging,observability
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Environment :: Web Environment
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: sentry-sdk<3,>=2.15
|
|
30
|
+
Requires-Dist: structlog>=23.1.0
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# sentry-structlog
|
|
34
|
+
|
|
35
|
+
[](https://github.com/Barsoomx/sentry-structlog/actions/workflows/tests.yml)
|
|
36
|
+
[](https://pypi.org/project/sentry-structlog/)
|
|
37
|
+
|
|
38
|
+
Send structlog messages to Sentry as Error events and breadcrumbs, with structured
|
|
39
|
+
context and optional tags.
|
|
40
|
+
|
|
41
|
+
Fork of [kiwicom/structlog-sentry](https://github.com/kiwicom/structlog-sentry),
|
|
42
|
+
originally authored by Kiwi.com platform and maintained here by
|
|
43
|
+
[@Barsoomx](https://github.com/Barsoomx). Distributed under the [MIT license](LICENSE).
|
|
44
|
+
Based on [Hynek Schlawack's processor](https://gist.github.com/hynek/a1f3f92d57071ebc5b91).
|
|
45
|
+
See the [changelog](CHANGELOG.md) for release history.
|
|
46
|
+
|
|
47
|
+
This fork builds tags and `contexts.structlog` from a snapshot of each call,
|
|
48
|
+
fixing cross-thread substitution of another log line's data on a shared processor.
|
|
49
|
+
An explicitly shared Sentry `Scope` is still mutable shared context; see
|
|
50
|
+
[Scopes and thread isolation](#scopes-and-thread-isolation).
|
|
51
|
+
|
|
52
|
+
## Installation and compatibility
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
pip install sentry-structlog
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Component | Supported versions / dependency constraint |
|
|
59
|
+
| ---------- | ------------------------------------------- |
|
|
60
|
+
| Python | 3.10–3.14 tested; package requires `>=3.10` |
|
|
61
|
+
| sentry-sdk | `>=2.15,<3` |
|
|
62
|
+
| structlog | `>=23.1.0` |
|
|
63
|
+
|
|
64
|
+
These dependency ranges are broader than the individual versions exercised in CI.
|
|
65
|
+
The [CI matrix](#ci) covers the lockfile, minimum and latest stable dependencies;
|
|
66
|
+
the SDK 3 prerelease check is advisory and does not expand the supported range.
|
|
67
|
+
|
|
68
|
+
## Quick start
|
|
69
|
+
|
|
70
|
+
Replace the example DSN with your project's DSN, then run this complete script:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import logging
|
|
74
|
+
|
|
75
|
+
import sentry_sdk
|
|
76
|
+
import structlog
|
|
77
|
+
from sentry_sdk.integrations.logging import LoggingIntegration
|
|
78
|
+
|
|
79
|
+
from sentry_structlog import SentryProcessor
|
|
80
|
+
|
|
81
|
+
sentry_sdk.init(
|
|
82
|
+
dsn="https://public@example.com/1",
|
|
83
|
+
disabled_integrations=[LoggingIntegration()],
|
|
84
|
+
)
|
|
85
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
86
|
+
structlog.configure(
|
|
87
|
+
processors=[
|
|
88
|
+
structlog.stdlib.add_logger_name,
|
|
89
|
+
structlog.stdlib.add_log_level,
|
|
90
|
+
SentryProcessor(event_level=logging.ERROR),
|
|
91
|
+
structlog.processors.StackInfoRenderer(),
|
|
92
|
+
structlog.processors.format_exc_info,
|
|
93
|
+
structlog.processors.JSONRenderer(),
|
|
94
|
+
],
|
|
95
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
96
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
log = structlog.get_logger("example")
|
|
100
|
+
log.info("job started", job="demo")
|
|
101
|
+
try:
|
|
102
|
+
1 / 0
|
|
103
|
+
except ZeroDivisionError:
|
|
104
|
+
log.error("job failed", job="demo", exc_info=True)
|
|
105
|
+
sentry_sdk.flush()
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`add_log_level` must precede `SentryProcessor`. Keep the processor before
|
|
109
|
+
`format_exc_info` and `StackInfoRenderer`: those processors consume the raw
|
|
110
|
+
exception and stack flags. Finish with a renderer for local output.
|
|
111
|
+
|
|
112
|
+
With an active SDK and no filtering, these two calls capture **one Error event**
|
|
113
|
+
and record **two breadcrumbs**, with **no Sentry Logs**. The error event contains
|
|
114
|
+
the earlier info breadcrumb; its own breadcrumb is added after capture for later
|
|
115
|
+
events. The example overrides the processor's default event threshold of
|
|
116
|
+
`WARNING` with `ERROR`.
|
|
117
|
+
|
|
118
|
+
## Error events, breadcrumbs, and Sentry Logs
|
|
119
|
+
|
|
120
|
+
`SentryProcessor` captures events at `event_level` and above and records
|
|
121
|
+
breadcrumbs at `level` and above. These thresholds are independent. Breadcrumbs
|
|
122
|
+
are stored on the scope for subsequent events, not sent as separate Error events.
|
|
123
|
+
The processor does not emit native Sentry Logs.
|
|
124
|
+
|
|
125
|
+
When structlog uses standard logging, the SDK's default `LoggingIntegration` can
|
|
126
|
+
capture the same record independently, causing duplicate events or breadcrumbs.
|
|
127
|
+
The quick start disables that integration entirely, which works throughout the
|
|
128
|
+
supported SDK range and also disables its Sentry Logs handler.
|
|
129
|
+
|
|
130
|
+
If you keep the integration, `LoggingIntegration(level=None, event_level=None)`
|
|
131
|
+
disables only its breadcrumbs and Error events. On SDK versions with Sentry Logs,
|
|
132
|
+
`enable_logs=True` (including the older experimental option) or an explicit
|
|
133
|
+
`capture_sentry_logs=True` on `LoggingIntegration` can still enable Log items.
|
|
134
|
+
A Log item is distinct from an Error event. On versions that accept them,
|
|
135
|
+
`capture_sentry_logs=False` or `sentry_logs_level=None` disables that integration's
|
|
136
|
+
Logs capture. These keywords, and the top-level `enable_logs` option, are not all
|
|
137
|
+
available in SDK 2.15; do not pass them unconditionally when supporting the full
|
|
138
|
+
range. See the [SDK logging integration](https://getsentry.github.io/sentry-python/_modules/sentry_sdk/integrations/logging.html).
|
|
139
|
+
|
|
140
|
+
`sentry_skip=True`, `active=False`, and `ignore_loggers` affect this processor's
|
|
141
|
+
events and breadcrumbs only. They do not suppress another integration, an
|
|
142
|
+
explicit `sentry_sdk.capture_exception()`, or a later unhandled exception.
|
|
143
|
+
Configure those capture paths separately if enabled.
|
|
144
|
+
|
|
145
|
+
## Migrating from structlog-sentry
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
pip uninstall structlog-sentry
|
|
149
|
+
pip install sentry-structlog
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Replace `from structlog_sentry import SentryProcessor` with
|
|
153
|
+
`from sentry_structlog import SentryProcessor`. Existing constructor arguments
|
|
154
|
+
keep their positions. Review these 3.0.0 behavior changes:
|
|
155
|
+
|
|
156
|
+
- Python 3.7–3.9 are no longer supported; check the dependency ranges above.
|
|
157
|
+
- `scrub=True` is now the default for context and tag protection.
|
|
158
|
+
- `tag_keys="__all__"` excludes `RESERVED_TAG_KEYS`; explicit selections can
|
|
159
|
+
include them. Supported scalar tag values become strings; invalid keys,
|
|
160
|
+
oversized or newline-containing values, and unsupported types are dropped.
|
|
161
|
+
- `tag_keys` accepts any iterable of keys, but strings other than `"__all__"`
|
|
162
|
+
raise `ValueError`.
|
|
163
|
+
- Events and breadcrumbs use Sentry severities, including `fatal` for critical
|
|
164
|
+
levels. Unknown levels are skipped safely.
|
|
165
|
+
- `hint["log_record"]` is present only for an actual `logging.LogRecord`;
|
|
166
|
+
structured callback data is in `hint["structlog"]`.
|
|
167
|
+
- `scope=` is deprecated and will be removed in 4.0.
|
|
168
|
+
|
|
169
|
+
See [Tags policy](#tags-policy), [Log levels](#log-levels), and the
|
|
170
|
+
[changelog](CHANGELOG.md) for the full contract.
|
|
171
|
+
|
|
172
|
+
## Processor API
|
|
173
|
+
|
|
174
|
+
The `SentryProcessor` class takes the following arguments:
|
|
175
|
+
|
|
176
|
+
- `level` Events of this or higher levels will be reported as Sentry
|
|
177
|
+
breadcrumbs. Default is `logging.INFO`.
|
|
178
|
+
- `event_level` Events of this or higher levels will be reported to Sentry
|
|
179
|
+
as events. Default is `logging.WARNING`.
|
|
180
|
+
- `active` Enable or disable this processor. Default is `True`.
|
|
181
|
+
- `as_context` Send `event_dict` as `contexts.structlog` in Sentry events.
|
|
182
|
+
Default is `True`.
|
|
183
|
+
- `ignore_breadcrumb_data` Any iterable of data keys that will be excluded from
|
|
184
|
+
[breadcrumb data](https://docs.sentry.io/platforms/python/enriching-events/breadcrumbs/#manual-breadcrumbs).
|
|
185
|
+
Defaults to keys which are already sent separately, i.e. `level`, `logger`,
|
|
186
|
+
`event` and `timestamp`. All other data in `event_dict` will be sent as
|
|
187
|
+
breadcrumb data, except for the callback-only `_record` object. Copied into a
|
|
188
|
+
`frozenset` at construction, so iterators can be used safely across calls and
|
|
189
|
+
later changes to the source collection have no effect. This option does not
|
|
190
|
+
exclude keys from contexts or tags.
|
|
191
|
+
- `tag_keys` Any iterable of keys to send as tags (including lists, tuples, sets,
|
|
192
|
+
and generators), or `"__all__"` for all eligible keys. Defaults to `None` (no
|
|
193
|
+
structlog tags). Any other string raises `ValueError` during construction.
|
|
194
|
+
- `exclude_tag_keys` Additional keys to exclude from tags in either mode.
|
|
195
|
+
Defaults to `()`.
|
|
196
|
+
- `scrub` Apply the client's event scrubber to `contexts.structlog` and remove
|
|
197
|
+
denylisted tags. Defaults to `True`.
|
|
198
|
+
- `ignore_loggers` Any iterable of logger names or wildcard patterns to ignore
|
|
199
|
+
events and breadcrumbs from. Default is `None`. Copied into a `frozenset` at
|
|
200
|
+
construction.
|
|
201
|
+
- `verbose` Report the action taken by the logger in the `event_dict`.
|
|
202
|
+
Default is `False`.
|
|
203
|
+
- `scope` Deprecated optional `sentry_sdk.Scope`. Passing a scope emits
|
|
204
|
+
`DeprecationWarning`; this parameter will be removed in **4.0**. See
|
|
205
|
+
[Scopes and thread isolation](#scopes-and-thread-isolation).
|
|
206
|
+
|
|
207
|
+
Add `structlog.stdlib.add_log_level` (or `add_log_level_number`) before the
|
|
208
|
+
processor. `add_logger_name` is optional and also belongs before it. Names are
|
|
209
|
+
resolved from non-empty strings in `event_dict["logger"]`, `_record.name`, then
|
|
210
|
+
the wrapped logger's `name`. If the event dict has no `logger` key, the resolved
|
|
211
|
+
name supplies the Sentry event logger and breadcrumb category without changing
|
|
212
|
+
original event data. `CapturingLogger` and mock loggers without a string name
|
|
213
|
+
are supported.
|
|
214
|
+
|
|
215
|
+
### Capture status
|
|
216
|
+
|
|
217
|
+
With `verbose=True`, `sentry="sent"` means the SDK returned an event ID, which is
|
|
218
|
+
also added as `sentry_id`. This indicates SDK acceptance, not guaranteed network
|
|
219
|
+
delivery. `sentry="dropped"` means the SDK returned no event ID; no `sentry_id` is
|
|
220
|
+
added, and the SDK's reason is not inferred. `sentry="ignored"` marks an ignored
|
|
221
|
+
logger, while `sentry="skipped"` covers disabled processing, `sentry_skip`, and
|
|
222
|
+
level filtering. Breadcrumbs are recorded independently according to `level`,
|
|
223
|
+
even when the SDK drops an event. With `verbose=False`, user-supplied `sentry`
|
|
224
|
+
metadata is left unchanged.
|
|
225
|
+
|
|
226
|
+
### Log levels
|
|
227
|
+
|
|
228
|
+
Levels are resolved in this order:
|
|
229
|
+
|
|
230
|
+
1. An integer `level_number`, as supplied by `structlog.stdlib.add_log_level_number`.
|
|
231
|
+
This takes precedence over `level` and works without a level name.
|
|
232
|
+
2. A case-insensitive name from `structlog.processors.NAME_TO_LEVEL`
|
|
233
|
+
(`_NAME_TO_LEVEL` on older versions), including `exception` as `error` and
|
|
234
|
+
`warn` as `warning`.
|
|
235
|
+
3. A name registered with Python logging: first the original spelling, then its
|
|
236
|
+
uppercase spelling. Only integer lookup results are accepted, so mixed-case
|
|
237
|
+
custom names such as `logging.addLevelName(25, "LeVeL")` work.
|
|
238
|
+
|
|
239
|
+
A non-integer `level_number` falls back to the name. Missing or unrecognized
|
|
240
|
+
levels (including `basic_format` and `nonsense`) produce neither an event nor a
|
|
241
|
+
breadcrumb. The original event data is retained, with `sentry="skipped"` added in
|
|
242
|
+
verbose mode. As with other calls, `sentry_skip` is consumed by the processor.
|
|
243
|
+
Errors during processing are contained so application logging can continue.
|
|
244
|
+
|
|
245
|
+
Both thresholds use the resolved number. Events and breadcrumbs use the same
|
|
246
|
+
Sentry severity mapping, including for custom levels:
|
|
247
|
+
|
|
248
|
+
| Numeric level | Sentry severity |
|
|
249
|
+
| ------------- | --------------- |
|
|
250
|
+
| Below 20 | `debug` |
|
|
251
|
+
| 20–29 | `info` |
|
|
252
|
+
| 30–39 | `warning` |
|
|
253
|
+
| 40–49 | `error` |
|
|
254
|
+
| 50 and above | `fatal` |
|
|
255
|
+
|
|
256
|
+
The original `level` and `level_number` remain unchanged for downstream
|
|
257
|
+
processors and in `contexts.structlog` (subject to the configured scrubber).
|
|
258
|
+
|
|
259
|
+
### Exceptions and per-call stacks
|
|
260
|
+
|
|
261
|
+
The following calls use `log` from the quick start:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
try:
|
|
265
|
+
1 / 0
|
|
266
|
+
except ZeroDivisionError:
|
|
267
|
+
log.error("division failed", exc_info=True)
|
|
268
|
+
|
|
269
|
+
log.error("current call site", stack_info=True)
|
|
270
|
+
log.error("current call site", exc_info=True) # no active exception here
|
|
271
|
+
log.error("local output only", sentry_skip=True)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
A message inside an exception handler does not automatically capture the
|
|
275
|
+
exception: pass `exc_info=True`, an exception instance, or an exception tuple.
|
|
276
|
+
Exception events use `mechanism={"type": "structlog", "handled": True}`.
|
|
277
|
+
When both `exc_info` and `stack_info` are requested and an exception is available,
|
|
278
|
+
its traceback takes priority; no additional thread stack is attached.
|
|
279
|
+
|
|
280
|
+
Without an active exception, `exc_info=True` or `stack_info=True` attaches one
|
|
281
|
+
stack in `threads.values`, with `crashed=False` and `current=True`, even when
|
|
282
|
+
`attach_stacktrace=False`. Per-call capture respects the client's
|
|
283
|
+
`include_local_variables`, `include_source_context`, and `max_value_length`
|
|
284
|
+
options. With `attach_stacktrace=True`, the SDK supplies the stack instead.
|
|
285
|
+
Plain events without either flag only get a stack when that client option is on.
|
|
286
|
+
|
|
287
|
+
The processor leaves `exc_info`, `stack_info`, and `stack` unchanged for
|
|
288
|
+
subsequent processors. If `StackInfoRenderer` ran first, the rendered `stack`
|
|
289
|
+
string remains context, subject to scrubbing and serialization, but cannot be
|
|
290
|
+
converted back into a structured traceback. Global `attach_stacktrace` still
|
|
291
|
+
works. Both stack keys are reserved for tag selection.
|
|
292
|
+
|
|
293
|
+
Captured events use one snapshot for tags, `contexts.structlog`, and breadcrumb
|
|
294
|
+
data, taken after consuming `sentry_skip` and before adding `sentry_id` or verbose
|
|
295
|
+
status. Calls below `event_level` skip the event snapshot; recorded breadcrumbs
|
|
296
|
+
still get a separate callback snapshot.
|
|
297
|
+
|
|
298
|
+
### Callback hints
|
|
299
|
+
|
|
300
|
+
Both `before_send(event, hint)` and `before_breadcrumb(breadcrumb, hint)` receive
|
|
301
|
+
`hint["structlog"]`: a shallow copy of the current call's event dict, taken before
|
|
302
|
+
adding `sentry_id` or verbose `sentry` status and after consuming `sentry_skip`.
|
|
303
|
+
Changing its top-level keys does not change data passed to downstream processors
|
|
304
|
+
or the other callback. Nested values are shared; the hint is not scrubbed.
|
|
305
|
+
|
|
306
|
+
When the event dict contains a real `logging.LogRecord` under `_record`, such as
|
|
307
|
+
with `structlog.stdlib.ProcessorFormatter`, `hint["log_record"]` contains that
|
|
308
|
+
record. Otherwise `log_record` is absent; pure structlog logs no longer put a
|
|
309
|
+
dict under this key. A callback shared with the SDK's `LoggingIntegration` can
|
|
310
|
+
use `hint.get("log_record")` for logger/level filtering and
|
|
311
|
+
`hint.get("structlog")` for structured metadata. Place `SentryProcessor` before
|
|
312
|
+
`ProcessorFormatter.remove_processors_meta` to retain access to `_record`.
|
|
313
|
+
|
|
314
|
+
Exception events also retain the SDK's `hint["exc_info"]`. Hints are callback
|
|
315
|
+
metadata, not additional event payload fields; a `LogRecord` stored in `_record`
|
|
316
|
+
is excluded from `contexts.structlog` and breadcrumb data.
|
|
317
|
+
|
|
318
|
+
### Selecting tags and context
|
|
319
|
+
|
|
320
|
+
Replace the processor in the quick start to select tags explicitly:
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
SentryProcessor(event_level=logging.ERROR, tag_keys=("city", "timezone"))
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Then `log.error("job failed", city="Prague", timezone="Europe/Prague")` adds those
|
|
327
|
+
two tags. Use `tag_keys="__all__"` for all eligible keys, or `as_context=False` to
|
|
328
|
+
omit `contexts.structlog`.
|
|
329
|
+
|
|
330
|
+
| Option | Affects | Does not remove fields from |
|
|
331
|
+
| ------------------------------ | ------------------------------------- | --------------------------- |
|
|
332
|
+
| `tag_keys`, `exclude_tag_keys` | Event tags | Context and breadcrumb data |
|
|
333
|
+
| `as_context=False` | `contexts.structlog` | Tags and breadcrumb data |
|
|
334
|
+
| `ignore_breadcrumb_data` | Breadcrumb data | Tags and context |
|
|
335
|
+
| `scrub=True` | Processor context and denylisted tags | Unscrubbed callback hints |
|
|
336
|
+
|
|
337
|
+
`ignore_breadcrumb_data` defaults to `level`, `logger`, `event`, and `timestamp`,
|
|
338
|
+
which are already separate breadcrumb fields. A real `_record` is also excluded
|
|
339
|
+
from context and breadcrumb data. The SDK's event scrubber, when configured,
|
|
340
|
+
handles breadcrumb data while preparing the containing event.
|
|
341
|
+
Avoid placing secrets in messages or other fields merely because tags exclude
|
|
342
|
+
them; this policy is not a general-purpose sanitizer for all channels.
|
|
343
|
+
|
|
344
|
+
To ignore logger names or wildcard patterns in this processor, replace it with:
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
SentryProcessor(ignore_loggers=("noisy.logger", "noisy.worker.*"))
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Tags policy
|
|
351
|
+
|
|
352
|
+
`RESERVED_TAG_KEYS` is exported from `sentry_structlog`. In version 3.0.0,
|
|
353
|
+
`tag_keys="__all__"` always excludes these reserved keys:
|
|
354
|
+
`event`, `level`, `logger`, `timestamp`, `exc_info`, `exception`, `stack`,
|
|
355
|
+
`stack_info`, `sentry_skip`, `sentry`, `sentry_id`, `_record`, and `_from_structlog`.
|
|
356
|
+
An explicit iterable may select reserved keys. `sentry_skip` is consumed before
|
|
357
|
+
the snapshot and is never included in tags or `contexts.structlog`.
|
|
358
|
+
|
|
359
|
+
`exclude_tag_keys` adds consumer exclusions in both selection modes. For example,
|
|
360
|
+
exclude high-cardinality or personal fields:
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
SentryProcessor(
|
|
364
|
+
tag_keys="__all__",
|
|
365
|
+
exclude_tag_keys=("date", "username", "phone", "user_agent", "ip"),
|
|
366
|
+
)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Tag values of type `str`, `int`, `float`, `bool`, `UUID`, `Decimal`, or `Enum`
|
|
370
|
+
are converted with `str()`. `None` and other types (including dictionaries and
|
|
371
|
+
lists) are dropped from tags. Values longer than **200 characters** or containing
|
|
372
|
+
`\n` are also dropped, rather than truncated. Tag keys must match
|
|
373
|
+
`^[a-zA-Z0-9_.:-]{1,32}$`: **1–32 characters**, using only ASCII letters, digits,
|
|
374
|
+
underscores, periods, colons, and hyphens.
|
|
375
|
+
|
|
376
|
+
Excluded keys and rejected values remain in `contexts.structlog` when
|
|
377
|
+
`as_context=True`, subject to scrubbing; tag conversion does not stringify the
|
|
378
|
+
context values.
|
|
379
|
+
|
|
380
|
+
The SDK's `EventScrubber.scrub_event()` does not scrub arbitrary `tags` or
|
|
381
|
+
`contexts`; this processor adds that protection for its own payload.
|
|
382
|
+
`scrub=True` is the default in 3.0.0. If the Sentry client has an `event_scrubber`,
|
|
383
|
+
its `scrub_dict()` cleans a separate copy of `contexts.structlog`, respecting the
|
|
384
|
+
scrubber's `recursive` setting. Tag keys matching its denylist (case-insensitively)
|
|
385
|
+
are removed entirely, rather than assigned `[Filtered]`. For example:
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from sentry_sdk.scrubber import EventScrubber
|
|
389
|
+
|
|
390
|
+
# Replace the sentry_sdk.init() call in the quick start with this one.
|
|
391
|
+
sentry_sdk.init(
|
|
392
|
+
dsn="https://public@example.com/1",
|
|
393
|
+
disabled_integrations=[LoggingIntegration()],
|
|
394
|
+
event_scrubber=EventScrubber(denylist=["value"], recursive=True),
|
|
395
|
+
)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
With this scrubber, `value="secret"` and `nested={"value": "s"}` are filtered in
|
|
399
|
+
the context, and `value` is omitted from tags. `scrub=False` disables these
|
|
400
|
+
processor-level protections; it does not disable the tag policy above. If the
|
|
401
|
+
client has no event scrubber, the processor leaves context values and eligible
|
|
402
|
+
tags unchanged. When configured, the SDK's event scrubber handles breadcrumb
|
|
403
|
+
data while preparing the containing event, even with `scrub=False`; the
|
|
404
|
+
processor does not scrub it a second time.
|
|
405
|
+
|
|
406
|
+
### Scopes and thread isolation
|
|
407
|
+
|
|
408
|
+
Configure shared processors as `SentryProcessor()` without `scope=`. In
|
|
409
|
+
sentry-sdk 2.x, the client attached to a pinned scope is ignored: the SDK selects
|
|
410
|
+
the client from its current, isolation, or global scope. The pinned scope still
|
|
411
|
+
merges its breadcrumbs, tags, and user into every event captured through it,
|
|
412
|
+
including events from other threads.
|
|
413
|
+
|
|
414
|
+
For a temporary client override, use
|
|
415
|
+
`with sentry_sdk.new_scope() as scope:` followed by `scope.set_client(client)`.
|
|
416
|
+
For separate threads or requests, enter an isolation scope in each worker and
|
|
417
|
+
bind its client there:
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
# log uses a shared SentryProcessor() configured without scope=.
|
|
421
|
+
def handle_request(client):
|
|
422
|
+
with sentry_sdk.isolation_scope() as scope:
|
|
423
|
+
scope.set_client(client)
|
|
424
|
+
log.error("request failed")
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Record request-specific breadcrumbs, tags, and user inside that isolation scope.
|
|
428
|
+
The per-call event snapshot does not transfer request context to another thread:
|
|
429
|
+
pass the needed context explicitly and bind it inside the worker's isolation scope.
|
|
430
|
+
The processor resolves the active scope on each call. `scope=` remains supported
|
|
431
|
+
with a caller-located deprecation warning until its removal in 4.0.
|
|
432
|
+
|
|
433
|
+
## Output recipes
|
|
434
|
+
|
|
435
|
+
### JSON and ConsoleRenderer
|
|
436
|
+
|
|
437
|
+
The quick start emits JSON. For a development console, replace its processor list
|
|
438
|
+
with the following, keeping the same SDK and standard logging setup:
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
structlog.configure(
|
|
442
|
+
processors=[
|
|
443
|
+
structlog.stdlib.add_logger_name,
|
|
444
|
+
structlog.stdlib.add_log_level,
|
|
445
|
+
SentryProcessor(event_level=logging.ERROR),
|
|
446
|
+
structlog.processors.StackInfoRenderer(),
|
|
447
|
+
structlog.dev.ConsoleRenderer(),
|
|
448
|
+
],
|
|
449
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
450
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
451
|
+
)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
`ConsoleRenderer` handles exception formatting itself; omit `format_exc_info`
|
|
455
|
+
when using it. Renderer choice does not change the Sentry capture paths.
|
|
456
|
+
|
|
457
|
+
### Standard logging with ProcessorFormatter
|
|
458
|
+
|
|
459
|
+
After the imports and SDK initialization from the quick start, use this logging
|
|
460
|
+
configuration instead of its `basicConfig` and `structlog.configure` calls. It
|
|
461
|
+
processes both structlog and standard logging records through one handler:
|
|
462
|
+
|
|
463
|
+
```python
|
|
464
|
+
shared_processors = [
|
|
465
|
+
structlog.stdlib.add_logger_name,
|
|
466
|
+
structlog.stdlib.add_log_level,
|
|
467
|
+
]
|
|
468
|
+
formatter = structlog.stdlib.ProcessorFormatter(
|
|
469
|
+
foreign_pre_chain=shared_processors,
|
|
470
|
+
processors=[
|
|
471
|
+
SentryProcessor(event_level=logging.ERROR),
|
|
472
|
+
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
|
473
|
+
structlog.processors.StackInfoRenderer(),
|
|
474
|
+
structlog.processors.format_exc_info,
|
|
475
|
+
structlog.processors.JSONRenderer(),
|
|
476
|
+
],
|
|
477
|
+
)
|
|
478
|
+
handler = logging.StreamHandler()
|
|
479
|
+
handler.setFormatter(formatter)
|
|
480
|
+
root = logging.getLogger()
|
|
481
|
+
root.handlers.clear()
|
|
482
|
+
root.addHandler(handler)
|
|
483
|
+
root.setLevel(logging.INFO)
|
|
484
|
+
structlog.configure(
|
|
485
|
+
processors=[
|
|
486
|
+
*shared_processors,
|
|
487
|
+
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
|
488
|
+
],
|
|
489
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
490
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
structlog.get_logger("example").error("structured error", job="demo")
|
|
494
|
+
logging.getLogger("example").error("standard logging error")
|
|
495
|
+
sentry_sdk.flush()
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
With `LoggingIntegration` disabled as above and no SDK filtering, these calls
|
|
499
|
+
capture two Error events and record two breadcrumbs, with no Sentry Logs. Keep
|
|
500
|
+
`SentryProcessor` only in the formatter for this recipe; putting another instance
|
|
501
|
+
in `shared_processors`, or applying this formatter on multiple handlers, captures
|
|
502
|
+
the same record more than once. Placing it before `remove_processors_meta`
|
|
503
|
+
preserves `_record` for callback hints.
|
|
504
|
+
|
|
505
|
+
## Typing
|
|
506
|
+
|
|
507
|
+
The package ships inline annotations and a PEP 561 `py.typed` marker in both the
|
|
508
|
+
wheel and source distribution. CI installs the wheel into a separate environment,
|
|
509
|
+
runs [the consumer](scripts/wheel_consumer.py) outside the checkout with Python's
|
|
510
|
+
isolated mode, and checks it with strict Mypy targeting Python 3.10. This verifies
|
|
511
|
+
that installed consumers can discover and use the shipped types; it does not
|
|
512
|
+
promise full strictness for every API or mypyc compatibility.
|
|
513
|
+
|
|
514
|
+
## Development
|
|
515
|
+
|
|
516
|
+
Use Python 3.10 or newer and [uv](https://docs.astral.sh/uv/). The artifact checker
|
|
517
|
+
uses `tomllib`, so run the full development checklist with Python 3.11 or newer
|
|
518
|
+
(CI's quality and packaging jobs use Python 3.12):
|
|
519
|
+
|
|
520
|
+
```sh
|
|
521
|
+
uv sync --locked --all-groups
|
|
522
|
+
uv run --no-sync pytest --cov --cov-report=term-missing --cov-report=xml --junitxml=junit.xml
|
|
523
|
+
uv run --no-sync ruff check .
|
|
524
|
+
uv run --no-sync ruff format --check .
|
|
525
|
+
uv run --no-sync mypy sentry_structlog
|
|
526
|
+
uv build
|
|
527
|
+
uv run --no-sync twine check --strict dist/*
|
|
528
|
+
uv run --no-sync python scripts/check_artifacts.py
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
`uv sync` installs the default `dev` group. For a quick test run use
|
|
532
|
+
`uv run --no-sync pytest -q`; to format locally use `uv run --no-sync ruff format .`.
|
|
533
|
+
When intentionally updating dependencies, run `uv lock` and commit `uv.lock`.
|
|
534
|
+
Mypy is the configured type checker; there is no Pyright CI job or project config.
|
|
535
|
+
|
|
536
|
+
Optional [pre-commit hooks](.pre-commit-config.yaml) run Ruff, Mypy, basic file
|
|
537
|
+
checks, and Prettier for Markdown. Pre-commit is not in the dev dependency group:
|
|
538
|
+
|
|
539
|
+
```sh
|
|
540
|
+
uv tool run pre-commit install
|
|
541
|
+
uv tool run pre-commit run --all-files
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### CI
|
|
545
|
+
|
|
546
|
+
The [Tests workflow](.github/workflows/tests.yml) runs on pushes to `master`, pull
|
|
547
|
+
requests, a weekly schedule, and calls from the publish workflow. Its blocking
|
|
548
|
+
checks are:
|
|
549
|
+
|
|
550
|
+
| Job | Coverage |
|
|
551
|
+
| ---------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
552
|
+
| `Code Quality (Ruff, Mypy)` | Lint, formatting, package typing on Python 3.12 |
|
|
553
|
+
| `Python <version> (<os>)` | Locked dependencies on Ubuntu with Python 3.10–3.14 and Windows 2022 with Python 3.12 |
|
|
554
|
+
| `Dependencies (min)` | Python 3.10, `sentry-sdk==2.15.0`, `structlog==23.1.0` |
|
|
555
|
+
| `Dependencies (latest)` | Python 3.10, latest stable versions within the declared ranges |
|
|
556
|
+
| `Package and typed wheel consumer` | Build, strict Twine check, artifact contents, isolated installed-wheel consumer and strict Mypy |
|
|
557
|
+
|
|
558
|
+
`Sentry SDK prerelease` additionally tries SDK 3 prereleases on Python 3.12 with
|
|
559
|
+
`continue-on-error`; it is advisory. Dependency lanes use `uv run --no-sync`
|
|
560
|
+
after installing their selected versions so the lock cannot replace them.
|
|
561
|
+
|
|
562
|
+
Coverage includes branches and has a 90% gate. `coverage.xml` and `junit.xml` are
|
|
563
|
+
uploaded even on failures as `reports-<os>-py<version>` and
|
|
564
|
+
`reports-dependencies-<endpoint>` workflow artifacts. There is no external
|
|
565
|
+
coverage badge service configured.
|
|
566
|
+
|
|
567
|
+
### Docker
|
|
568
|
+
|
|
569
|
+
Build the development image and run the full test suite:
|
|
570
|
+
|
|
571
|
+
```sh
|
|
572
|
+
docker compose build
|
|
573
|
+
docker compose run --rm app
|
|
574
|
+
docker compose run --rm app uv run --no-sync ruff check .
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
The [Docker setup](docker-compose.yml) uses Python 3.12 and bind-mounts the checkout
|
|
578
|
+
at `/app`, so source edits are available immediately. The image keeps its locked
|
|
579
|
+
Python environment at `/opt/venv`, separate from any host `.venv`. Rebuild after
|
|
580
|
+
changing `pyproject.toml` or `uv.lock`.
|
|
581
|
+
|
|
582
|
+
## Contributing
|
|
583
|
+
|
|
584
|
+
Open an [issue](https://github.com/Barsoomx/sentry-structlog/issues) or
|
|
585
|
+
[pull request](https://github.com/Barsoomx/sentry-structlog/pulls) in this fork.
|
|
586
|
+
The maintainer is [@Barsoomx](https://github.com/Barsoomx). Include a reproduction
|
|
587
|
+
for bugs and run the relevant development checks before submitting changes.
|
|
588
|
+
|
|
589
|
+
## Releasing
|
|
590
|
+
|
|
591
|
+
Set `project.version` in `pyproject.toml`, update [CHANGELOG.md](CHANGELOG.md),
|
|
592
|
+
refresh `uv.lock` as needed, commit, and push to `master`. Wait for the checks on
|
|
593
|
+
that commit, then push a matching version tag, for example:
|
|
594
|
+
|
|
595
|
+
```sh
|
|
596
|
+
git tag v3.0.0
|
|
597
|
+
git push origin v3.0.0
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
The [Publish to PyPI workflow](.github/workflows/publish.yml) requires the tag to
|
|
601
|
+
match `v<project.version>` exactly. `Build and check distributions` validates the
|
|
602
|
+
wheel and source distribution; the reusable Tests workflow runs on the same
|
|
603
|
+
commit. `Publish with PyPI Trusted Publishing` depends on both build and tests,
|
|
604
|
+
including the required jobs above, and publishes the already checked artifacts.
|
|
605
|
+
Only `v*` tag pushes in `Barsoomx/sentry-structlog` publish. Branch pushes, pull
|
|
606
|
+
requests, and manual runs check the build without publishing.
|
|
607
|
+
|
|
608
|
+
Configure PyPI Trusted Publishing for owner `Barsoomx`, repository
|
|
609
|
+
`sentry-structlog`, workflow filename `publish.yml`, and GitHub environment `pypi`.
|
|
610
|
+
Configure that environment to allow deployment only from `v*` tags. The workflow
|
|
611
|
+
requests `id-token: write` for OIDC; no PyPI API token is required.
|