django-structlog 8.0.0__tar.gz → 9.0.0.dev1__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.
- {django-structlog-8.0.0/django_structlog.egg-info → django_structlog-9.0.0.dev1}/PKG-INFO +13 -6
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/README.rst +7 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/__init__.py +1 -1
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/app_settings.py +7 -3
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/apps.py +1 -1
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/celery/receivers.py +75 -32
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/celery/steps.py +3 -1
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/commands.py +14 -5
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/middlewares/request.py +46 -18
- django_structlog-9.0.0.dev1/django_structlog/py.typed +0 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1/django_structlog.egg-info}/PKG-INFO +13 -6
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog.egg-info/SOURCES.txt +1 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog.egg-info/requires.txt +1 -1
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/pyproject.toml +34 -15
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/LICENSE.rst +0 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/MANIFEST.in +0 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/celery/__init__.py +0 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/celery/signals.py +0 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/middlewares/__init__.py +0 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/signals.py +0 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog.egg-info/dependency_links.txt +0 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog.egg-info/top_level.txt +0 -0
- {django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-structlog
|
|
3
|
-
Version:
|
|
3
|
+
Version: 9.0.0.dev1
|
|
4
4
|
Summary: Structured Logging for Django
|
|
5
5
|
Author-email: Jules Robichaud-Gagnon <j.robichaudg+pypi@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -11,24 +11,24 @@ Project-URL: tracker, https://github.com/jrobichaud/django-structlog/issues
|
|
|
11
11
|
Project-URL: changelog, https://django-structlog.readthedocs.io/en/latest/changelog.html
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Framework :: Django
|
|
14
|
-
Classifier: Framework :: Django :: 3.2
|
|
15
|
-
Classifier: Framework :: Django :: 4.1
|
|
16
14
|
Classifier: Framework :: Django :: 4.2
|
|
17
15
|
Classifier: Framework :: Django :: 5.0
|
|
16
|
+
Classifier: Framework :: Django :: 5.1
|
|
18
17
|
Classifier: Programming Language :: Python :: 3
|
|
19
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.9
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
24
|
Classifier: Topic :: System :: Logging
|
|
26
25
|
Classifier: License :: OSI Approved :: MIT License
|
|
27
26
|
Classifier: Operating System :: OS Independent
|
|
28
|
-
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
29
|
Description-Content-Type: text/x-rst
|
|
30
30
|
License-File: LICENSE.rst
|
|
31
|
-
Requires-Dist: django>=
|
|
31
|
+
Requires-Dist: django>=4.2
|
|
32
32
|
Requires-Dist: structlog>=21.4.0
|
|
33
33
|
Requires-Dist: asgiref>=3.6.0
|
|
34
34
|
Requires-Dist: django-ipware>=6.0.2
|
|
@@ -121,6 +121,13 @@ Additional Popular Integrations
|
|
|
121
121
|
|
|
122
122
|
See `#37 <https://github.com/jrobichaud/django-structlog/issues/37>`_ for details.
|
|
123
123
|
|
|
124
|
+
|
|
125
|
+
`django-ninja <https://django-ninja.dev/>`_
|
|
126
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
127
|
+
|
|
128
|
+
``django-ninja`` is supported by default 🥷.
|
|
129
|
+
|
|
130
|
+
|
|
124
131
|
`Celery <http://www.celeryproject.org/>`_
|
|
125
132
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
126
133
|
Celery's task logging requires additional configurations, see `documentation <https://django-structlog.readthedocs.io/en/latest/celery.html>`_ for details.
|
|
@@ -82,6 +82,13 @@ Additional Popular Integrations
|
|
|
82
82
|
|
|
83
83
|
See `#37 <https://github.com/jrobichaud/django-structlog/issues/37>`_ for details.
|
|
84
84
|
|
|
85
|
+
|
|
86
|
+
`django-ninja <https://django-ninja.dev/>`_
|
|
87
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
88
|
+
|
|
89
|
+
``django-ninja`` is supported by default 🥷.
|
|
90
|
+
|
|
91
|
+
|
|
85
92
|
`Celery <http://www.celeryproject.org/>`_
|
|
86
93
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
87
94
|
Celery's task logging requires additional configurations, see `documentation <https://django-structlog.readthedocs.io/en/latest/celery.html>`_ for details.
|
|
@@ -8,16 +8,20 @@ class AppSettings:
|
|
|
8
8
|
PREFIX = "DJANGO_STRUCTLOG_"
|
|
9
9
|
|
|
10
10
|
@property
|
|
11
|
-
def CELERY_ENABLED(self):
|
|
11
|
+
def CELERY_ENABLED(self) -> bool:
|
|
12
12
|
return getattr(settings, self.PREFIX + "CELERY_ENABLED", False)
|
|
13
13
|
|
|
14
14
|
@property
|
|
15
|
-
def STATUS_4XX_LOG_LEVEL(self):
|
|
15
|
+
def STATUS_4XX_LOG_LEVEL(self) -> int:
|
|
16
16
|
return getattr(settings, self.PREFIX + "STATUS_4XX_LOG_LEVEL", logging.WARNING)
|
|
17
17
|
|
|
18
18
|
@property
|
|
19
|
-
def COMMAND_LOGGING_ENABLED(self):
|
|
19
|
+
def COMMAND_LOGGING_ENABLED(self) -> bool:
|
|
20
20
|
return getattr(settings, self.PREFIX + "COMMAND_LOGGING_ENABLED", False)
|
|
21
21
|
|
|
22
|
+
@property
|
|
23
|
+
def USER_ID_FIELD(self) -> str:
|
|
24
|
+
return getattr(settings, self.PREFIX + "USER_ID_FIELD", "pk")
|
|
25
|
+
|
|
22
26
|
|
|
23
27
|
app_settings = AppSettings()
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Type, Any, Optional, cast, TYPE_CHECKING
|
|
4
|
+
|
|
1
5
|
import structlog
|
|
2
6
|
from celery import current_app
|
|
3
7
|
from celery.signals import (
|
|
@@ -14,23 +18,27 @@ from celery.signals import (
|
|
|
14
18
|
|
|
15
19
|
from . import signals
|
|
16
20
|
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from types import TracebackType
|
|
17
23
|
|
|
18
24
|
logger = structlog.getLogger(__name__)
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
class CeleryReceiver:
|
|
22
|
-
|
|
28
|
+
_priority: Optional[str]
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
23
31
|
self._priority = None
|
|
24
32
|
|
|
25
33
|
def receiver_before_task_publish(
|
|
26
34
|
self,
|
|
27
|
-
sender=None,
|
|
28
|
-
headers=None,
|
|
29
|
-
body=None,
|
|
30
|
-
properties=None,
|
|
31
|
-
routing_key=None,
|
|
32
|
-
**kwargs,
|
|
33
|
-
):
|
|
35
|
+
sender: Optional[Type[Any]] = None,
|
|
36
|
+
headers: Optional[dict[str, Any]] = None,
|
|
37
|
+
body: Optional[dict[str, str]] = None,
|
|
38
|
+
properties: Optional[dict[str, Optional[str]]] = None,
|
|
39
|
+
routing_key: Optional[str] = None,
|
|
40
|
+
**kwargs: dict[str, str],
|
|
41
|
+
) -> None:
|
|
34
42
|
if current_app.conf.task_protocol < 2:
|
|
35
43
|
return
|
|
36
44
|
|
|
@@ -46,12 +54,17 @@ class CeleryReceiver:
|
|
|
46
54
|
)
|
|
47
55
|
if properties:
|
|
48
56
|
self._priority = properties.get("priority", None)
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
if headers is not None:
|
|
58
|
+
headers["__django_structlog__"] = context
|
|
51
59
|
|
|
52
60
|
def receiver_after_task_publish(
|
|
53
|
-
self,
|
|
54
|
-
|
|
61
|
+
self,
|
|
62
|
+
sender: Optional[Type[Any]] = None,
|
|
63
|
+
headers: Optional[dict[str, Optional[str]]] = None,
|
|
64
|
+
body: Optional[dict[str, Optional[str]]] = None,
|
|
65
|
+
routing_key: Optional[str] = None,
|
|
66
|
+
**kwargs: Any,
|
|
67
|
+
) -> None:
|
|
55
68
|
properties = {}
|
|
56
69
|
if self._priority is not None:
|
|
57
70
|
properties["priority"] = self._priority
|
|
@@ -59,13 +72,23 @@ class CeleryReceiver:
|
|
|
59
72
|
|
|
60
73
|
logger.info(
|
|
61
74
|
"task_enqueued",
|
|
62
|
-
child_task_id=
|
|
63
|
-
|
|
75
|
+
child_task_id=(
|
|
76
|
+
headers.get("id")
|
|
77
|
+
if headers
|
|
78
|
+
else cast(dict[str, Optional[str]], body).get("id")
|
|
79
|
+
),
|
|
80
|
+
child_task_name=(
|
|
81
|
+
headers.get("task")
|
|
82
|
+
if headers
|
|
83
|
+
else cast(dict[str, Optional[str]], body).get("task")
|
|
84
|
+
),
|
|
64
85
|
routing_key=routing_key,
|
|
65
86
|
**properties,
|
|
66
87
|
)
|
|
67
88
|
|
|
68
|
-
def receiver_task_prerun(
|
|
89
|
+
def receiver_task_prerun(
|
|
90
|
+
self, task_id: str, task: Any, *args: Any, **kwargs: Any
|
|
91
|
+
) -> None:
|
|
69
92
|
structlog.contextvars.clear_contextvars()
|
|
70
93
|
structlog.contextvars.bind_contextvars(task_id=task_id)
|
|
71
94
|
metadata = getattr(task.request, "__django_structlog__", {})
|
|
@@ -75,10 +98,18 @@ class CeleryReceiver:
|
|
|
75
98
|
)
|
|
76
99
|
logger.info("task_started", task=task.name)
|
|
77
100
|
|
|
78
|
-
def receiver_task_retry(
|
|
101
|
+
def receiver_task_retry(
|
|
102
|
+
self,
|
|
103
|
+
request: Optional[Any] = None,
|
|
104
|
+
reason: Optional[str] = None,
|
|
105
|
+
einfo: Optional[Any] = None,
|
|
106
|
+
**kwargs: Any,
|
|
107
|
+
) -> None:
|
|
79
108
|
logger.warning("task_retrying", reason=reason)
|
|
80
109
|
|
|
81
|
-
def receiver_task_success(
|
|
110
|
+
def receiver_task_success(
|
|
111
|
+
self, result: Optional[str] = None, **kwargs: Any
|
|
112
|
+
) -> None:
|
|
82
113
|
signals.pre_task_succeeded.send(
|
|
83
114
|
sender=self.receiver_task_success, logger=logger, result=result
|
|
84
115
|
)
|
|
@@ -86,14 +117,14 @@ class CeleryReceiver:
|
|
|
86
117
|
|
|
87
118
|
def receiver_task_failure(
|
|
88
119
|
self,
|
|
89
|
-
task_id=None,
|
|
90
|
-
exception=None,
|
|
91
|
-
traceback=None,
|
|
92
|
-
einfo=None,
|
|
93
|
-
sender=None,
|
|
94
|
-
*args,
|
|
95
|
-
**kwargs,
|
|
96
|
-
):
|
|
120
|
+
task_id: Optional[str] = None,
|
|
121
|
+
exception: Optional[Exception] = None,
|
|
122
|
+
traceback: Optional[TracebackType] = None,
|
|
123
|
+
einfo: Optional[Any] = None,
|
|
124
|
+
sender: Optional[Type[Any]] = None,
|
|
125
|
+
*args: Any,
|
|
126
|
+
**kwargs: Any,
|
|
127
|
+
) -> None:
|
|
97
128
|
throws = getattr(sender, "throws", ())
|
|
98
129
|
if isinstance(exception, throws):
|
|
99
130
|
logger.info(
|
|
@@ -108,8 +139,13 @@ class CeleryReceiver:
|
|
|
108
139
|
)
|
|
109
140
|
|
|
110
141
|
def receiver_task_revoked(
|
|
111
|
-
self,
|
|
112
|
-
|
|
142
|
+
self,
|
|
143
|
+
request: Any,
|
|
144
|
+
terminated: Optional[bool] = None,
|
|
145
|
+
signum: Optional[Any] = None,
|
|
146
|
+
expired: Optional[Any] = None,
|
|
147
|
+
**kwargs: Any,
|
|
148
|
+
) -> None:
|
|
113
149
|
metadata = getattr(request, "__django_structlog__", {}).copy()
|
|
114
150
|
metadata["task_id"] = request.id
|
|
115
151
|
metadata["task"] = request.task
|
|
@@ -124,24 +160,31 @@ class CeleryReceiver:
|
|
|
124
160
|
)
|
|
125
161
|
|
|
126
162
|
def receiver_task_unknown(
|
|
127
|
-
self,
|
|
128
|
-
|
|
163
|
+
self,
|
|
164
|
+
message: Optional[str] = None,
|
|
165
|
+
exc: Optional[Exception] = None,
|
|
166
|
+
name: Optional[str] = None,
|
|
167
|
+
id: Optional[str] = None,
|
|
168
|
+
**kwargs: Any,
|
|
169
|
+
) -> None:
|
|
129
170
|
logger.error(
|
|
130
171
|
"task_not_found",
|
|
131
172
|
task=name,
|
|
132
173
|
task_id=id,
|
|
133
174
|
)
|
|
134
175
|
|
|
135
|
-
def receiver_task_rejected(
|
|
176
|
+
def receiver_task_rejected(
|
|
177
|
+
self, message: Any, exc: Optional[Exception] = None, **kwargs: Any
|
|
178
|
+
) -> None:
|
|
136
179
|
logger.exception(
|
|
137
180
|
"task_rejected", task_id=message.properties.get("correlation_id")
|
|
138
181
|
)
|
|
139
182
|
|
|
140
|
-
def connect_signals(self):
|
|
183
|
+
def connect_signals(self) -> None:
|
|
141
184
|
before_task_publish.connect(self.receiver_before_task_publish)
|
|
142
185
|
after_task_publish.connect(self.receiver_after_task_publish)
|
|
143
186
|
|
|
144
|
-
def connect_worker_signals(self):
|
|
187
|
+
def connect_worker_signals(self) -> None:
|
|
145
188
|
before_task_publish.connect(self.receiver_before_task_publish)
|
|
146
189
|
after_task_publish.connect(self.receiver_after_task_publish)
|
|
147
190
|
task_prerun.connect(self.receiver_task_prerun)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
from celery import bootsteps
|
|
2
4
|
|
|
3
5
|
from .receivers import CeleryReceiver
|
|
@@ -14,7 +16,7 @@ class DjangoStructLogInitStep(bootsteps.Step):
|
|
|
14
16
|
|
|
15
17
|
"""
|
|
16
18
|
|
|
17
|
-
def __init__(self, parent, **kwargs):
|
|
19
|
+
def __init__(self, parent: Any, **kwargs: Any) -> None:
|
|
18
20
|
super().__init__(parent, **kwargs)
|
|
19
21
|
self.receiver = CeleryReceiver()
|
|
20
22
|
self.receiver.connect_worker_signals()
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Tuple, Mapping, Any, Type, TYPE_CHECKING
|
|
4
|
+
|
|
1
5
|
import structlog
|
|
2
6
|
import uuid
|
|
3
7
|
|
|
4
8
|
from django_extensions.management.signals import pre_command, post_command
|
|
5
9
|
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
import contextvars
|
|
12
|
+
|
|
6
13
|
logger = structlog.getLogger(__name__)
|
|
7
14
|
|
|
8
15
|
|
|
9
16
|
class DjangoCommandReceiver:
|
|
10
|
-
def __init__(self):
|
|
11
|
-
self.stack = []
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
self.stack: List[Tuple[str, Mapping[str, contextvars.Token[Any]]]] = []
|
|
12
19
|
|
|
13
|
-
def pre_receiver(self, sender, *args, **kwargs):
|
|
20
|
+
def pre_receiver(self, sender: Type[Any], *args: Any, **kwargs: Any) -> None:
|
|
14
21
|
command_id = str(uuid.uuid4())
|
|
15
22
|
if len(self.stack):
|
|
16
23
|
parent_command_id, _ = self.stack[-1]
|
|
@@ -26,13 +33,15 @@ class DjangoCommandReceiver:
|
|
|
26
33
|
command_name=sender.__module__.replace(".management.commands", ""),
|
|
27
34
|
)
|
|
28
35
|
|
|
29
|
-
def post_receiver(
|
|
36
|
+
def post_receiver(
|
|
37
|
+
self, sender: Type[Any], outcome: str, *args: Any, **kwargs: Any
|
|
38
|
+
) -> None:
|
|
30
39
|
logger.info("command_finished")
|
|
31
40
|
|
|
32
41
|
if len(self.stack): # pragma: no branch
|
|
33
42
|
command_id, tokens = self.stack.pop()
|
|
34
43
|
structlog.contextvars.reset_contextvars(**tokens)
|
|
35
44
|
|
|
36
|
-
def connect_signals(self):
|
|
45
|
+
def connect_signals(self) -> None:
|
|
37
46
|
pre_command.connect(self.pre_receiver)
|
|
38
47
|
post_command.connect(self.post_receiver)
|
{django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/middlewares/request.py
RENAMED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import logging
|
|
3
5
|
import uuid
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Generator,
|
|
9
|
+
AsyncGenerator,
|
|
10
|
+
Callable,
|
|
11
|
+
Union,
|
|
12
|
+
Awaitable,
|
|
13
|
+
cast,
|
|
14
|
+
Iterator,
|
|
15
|
+
TYPE_CHECKING,
|
|
16
|
+
)
|
|
4
17
|
|
|
5
18
|
import structlog
|
|
6
19
|
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
|
|
@@ -11,17 +24,22 @@ from asgiref import sync
|
|
|
11
24
|
from .. import signals
|
|
12
25
|
from ..app_settings import app_settings
|
|
13
26
|
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from django.http import HttpRequest, HttpResponse
|
|
29
|
+
|
|
14
30
|
logger = structlog.getLogger(__name__)
|
|
15
31
|
|
|
16
32
|
|
|
17
|
-
def get_request_header(request, header_key, meta_key):
|
|
33
|
+
def get_request_header(request: HttpRequest, header_key: str, meta_key: str) -> Any:
|
|
18
34
|
if hasattr(request, "headers"):
|
|
19
35
|
return request.headers.get(header_key)
|
|
20
36
|
|
|
21
37
|
return request.META.get(meta_key)
|
|
22
38
|
|
|
23
39
|
|
|
24
|
-
def sync_streaming_content_wrapper(
|
|
40
|
+
def sync_streaming_content_wrapper(
|
|
41
|
+
streaming_content: Any, context: Any
|
|
42
|
+
) -> Generator[Any, None, None]:
|
|
25
43
|
with structlog.contextvars.bound_contextvars(**context):
|
|
26
44
|
logger.info("streaming_started")
|
|
27
45
|
try:
|
|
@@ -33,7 +51,9 @@ def sync_streaming_content_wrapper(streaming_content, context):
|
|
|
33
51
|
logger.info("streaming_finished")
|
|
34
52
|
|
|
35
53
|
|
|
36
|
-
async def async_streaming_content_wrapper(
|
|
54
|
+
async def async_streaming_content_wrapper(
|
|
55
|
+
streaming_content: Any, context: Any
|
|
56
|
+
) -> AsyncGenerator[Any, None]:
|
|
37
57
|
with structlog.contextvars.bound_contextvars(**context):
|
|
38
58
|
logger.info("streaming_started")
|
|
39
59
|
try:
|
|
@@ -61,30 +81,37 @@ class RequestMiddleware:
|
|
|
61
81
|
sync_capable = True
|
|
62
82
|
async_capable = True
|
|
63
83
|
|
|
64
|
-
def __init__(
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
get_response: Callable[
|
|
87
|
+
[HttpRequest], Union[HttpResponse, Awaitable[HttpResponse]]
|
|
88
|
+
],
|
|
89
|
+
) -> None:
|
|
65
90
|
self.get_response = get_response
|
|
66
91
|
if iscoroutinefunction(self.get_response):
|
|
67
92
|
markcoroutinefunction(self)
|
|
68
93
|
|
|
69
|
-
def __call__(
|
|
94
|
+
def __call__(
|
|
95
|
+
self, request: HttpRequest
|
|
96
|
+
) -> Union[HttpResponse, Awaitable[HttpResponse]]:
|
|
70
97
|
if iscoroutinefunction(self):
|
|
71
|
-
return self.__acall__(request)
|
|
98
|
+
return cast(RequestMiddleware, self).__acall__(request) # type: ignore[redundant-cast,unused-ignore]
|
|
72
99
|
self.prepare(request)
|
|
73
|
-
response = self.get_response(request)
|
|
100
|
+
response = cast("HttpResponse", self.get_response(request))
|
|
74
101
|
self.handle_response(request, response)
|
|
75
102
|
return response
|
|
76
103
|
|
|
77
|
-
async def __acall__(self, request):
|
|
104
|
+
async def __acall__(self, request: HttpRequest) -> HttpResponse:
|
|
78
105
|
await sync.sync_to_async(self.prepare)(request)
|
|
79
106
|
try:
|
|
80
|
-
response = await self.get_response(request)
|
|
107
|
+
response = await cast(Awaitable["HttpResponse"], self.get_response(request))
|
|
81
108
|
except asyncio.CancelledError:
|
|
82
109
|
logger.warning("request_cancelled")
|
|
83
110
|
raise
|
|
84
111
|
await sync.sync_to_async(self.handle_response)(request, response)
|
|
85
112
|
return response
|
|
86
113
|
|
|
87
|
-
def handle_response(self, request, response):
|
|
114
|
+
def handle_response(self, request: HttpRequest, response: HttpResponse) -> None:
|
|
88
115
|
if not hasattr(request, "_raised_exception"):
|
|
89
116
|
self.bind_user_id(request)
|
|
90
117
|
context = structlog.contextvars.get_merged_contextvars(logger)
|
|
@@ -114,7 +141,7 @@ class RequestMiddleware:
|
|
|
114
141
|
if isinstance(response, StreamingHttpResponse):
|
|
115
142
|
streaming_content = response.streaming_content
|
|
116
143
|
try:
|
|
117
|
-
iter(streaming_content)
|
|
144
|
+
iter(cast(Iterator[bytes], streaming_content))
|
|
118
145
|
except TypeError:
|
|
119
146
|
response.streaming_content = async_streaming_content_wrapper(
|
|
120
147
|
streaming_content, context
|
|
@@ -136,7 +163,7 @@ class RequestMiddleware:
|
|
|
136
163
|
)
|
|
137
164
|
structlog.contextvars.clear_contextvars()
|
|
138
165
|
|
|
139
|
-
def prepare(self, request):
|
|
166
|
+
def prepare(self, request: HttpRequest) -> None:
|
|
140
167
|
from ipware import get_client_ip
|
|
141
168
|
|
|
142
169
|
request_id = get_request_header(
|
|
@@ -161,20 +188,21 @@ class RequestMiddleware:
|
|
|
161
188
|
logger.info("request_started", **log_kwargs)
|
|
162
189
|
|
|
163
190
|
@staticmethod
|
|
164
|
-
def format_request(request):
|
|
191
|
+
def format_request(request: HttpRequest) -> str:
|
|
165
192
|
return f"{request.method} {request.get_full_path()}"
|
|
166
193
|
|
|
167
194
|
@staticmethod
|
|
168
|
-
def bind_user_id(request):
|
|
169
|
-
|
|
195
|
+
def bind_user_id(request: HttpRequest) -> None:
|
|
196
|
+
user_id_field = app_settings.USER_ID_FIELD
|
|
197
|
+
if hasattr(request, "user") and request.user is not None and user_id_field:
|
|
170
198
|
user_id = None
|
|
171
|
-
if hasattr(request.user,
|
|
172
|
-
user_id = request.user
|
|
199
|
+
if hasattr(request.user, user_id_field):
|
|
200
|
+
user_id = getattr(request.user, user_id_field)
|
|
173
201
|
if isinstance(user_id, uuid.UUID):
|
|
174
202
|
user_id = str(user_id)
|
|
175
203
|
structlog.contextvars.bind_contextvars(user_id=user_id)
|
|
176
204
|
|
|
177
|
-
def process_exception(self, request, exception):
|
|
205
|
+
def process_exception(self, request: HttpRequest, exception: Exception) -> None:
|
|
178
206
|
if isinstance(exception, (Http404, PermissionDenied)):
|
|
179
207
|
# We don't log an exception here, and we don't set that we handled
|
|
180
208
|
# an error as we want the standard `request_finished` log message
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-structlog
|
|
3
|
-
Version:
|
|
3
|
+
Version: 9.0.0.dev1
|
|
4
4
|
Summary: Structured Logging for Django
|
|
5
5
|
Author-email: Jules Robichaud-Gagnon <j.robichaudg+pypi@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -11,24 +11,24 @@ Project-URL: tracker, https://github.com/jrobichaud/django-structlog/issues
|
|
|
11
11
|
Project-URL: changelog, https://django-structlog.readthedocs.io/en/latest/changelog.html
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Framework :: Django
|
|
14
|
-
Classifier: Framework :: Django :: 3.2
|
|
15
|
-
Classifier: Framework :: Django :: 4.1
|
|
16
14
|
Classifier: Framework :: Django :: 4.2
|
|
17
15
|
Classifier: Framework :: Django :: 5.0
|
|
16
|
+
Classifier: Framework :: Django :: 5.1
|
|
18
17
|
Classifier: Programming Language :: Python :: 3
|
|
19
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.9
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
24
|
Classifier: Topic :: System :: Logging
|
|
26
25
|
Classifier: License :: OSI Approved :: MIT License
|
|
27
26
|
Classifier: Operating System :: OS Independent
|
|
28
|
-
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
29
|
Description-Content-Type: text/x-rst
|
|
30
30
|
License-File: LICENSE.rst
|
|
31
|
-
Requires-Dist: django>=
|
|
31
|
+
Requires-Dist: django>=4.2
|
|
32
32
|
Requires-Dist: structlog>=21.4.0
|
|
33
33
|
Requires-Dist: asgiref>=3.6.0
|
|
34
34
|
Requires-Dist: django-ipware>=6.0.2
|
|
@@ -121,6 +121,13 @@ Additional Popular Integrations
|
|
|
121
121
|
|
|
122
122
|
See `#37 <https://github.com/jrobichaud/django-structlog/issues/37>`_ for details.
|
|
123
123
|
|
|
124
|
+
|
|
125
|
+
`django-ninja <https://django-ninja.dev/>`_
|
|
126
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
127
|
+
|
|
128
|
+
``django-ninja`` is supported by default 🥷.
|
|
129
|
+
|
|
130
|
+
|
|
124
131
|
`Celery <http://www.celeryproject.org/>`_
|
|
125
132
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
126
133
|
Celery's task logging requires additional configurations, see `documentation <https://django-structlog.readthedocs.io/en/latest/celery.html>`_ for details.
|
|
@@ -10,10 +10,10 @@ build-backend = "setuptools.build_meta"
|
|
|
10
10
|
]
|
|
11
11
|
readme = "README.rst"
|
|
12
12
|
dynamic = ["version"]
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
14
|
license = { text = "MIT" }
|
|
15
15
|
dependencies = [
|
|
16
|
-
"django>=
|
|
16
|
+
"django>=4.2",
|
|
17
17
|
"structlog>=21.4.0",
|
|
18
18
|
"asgiref>=3.6.0",
|
|
19
19
|
"django-ipware>=6.0.2",
|
|
@@ -21,20 +21,20 @@ build-backend = "setuptools.build_meta"
|
|
|
21
21
|
classifiers = [
|
|
22
22
|
"Development Status :: 5 - Production/Stable",
|
|
23
23
|
"Framework :: Django",
|
|
24
|
-
"Framework :: Django :: 3.2",
|
|
25
|
-
"Framework :: Django :: 4.1",
|
|
26
24
|
"Framework :: Django :: 4.2",
|
|
27
25
|
"Framework :: Django :: 5.0",
|
|
26
|
+
"Framework :: Django :: 5.1",
|
|
28
27
|
"Programming Language :: Python :: 3",
|
|
29
28
|
"Programming Language :: Python :: 3 :: Only",
|
|
30
|
-
"Programming Language :: Python :: 3.8",
|
|
31
29
|
"Programming Language :: Python :: 3.9",
|
|
32
30
|
"Programming Language :: Python :: 3.10",
|
|
33
31
|
"Programming Language :: Python :: 3.11",
|
|
34
32
|
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
35
34
|
"Topic :: System :: Logging",
|
|
36
35
|
"License :: OSI Approved :: MIT License",
|
|
37
36
|
"Operating System :: OS Independent",
|
|
37
|
+
"Typing :: Typed",
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
[project.urls]
|
|
@@ -64,11 +64,11 @@ build-backend = "setuptools.build_meta"
|
|
|
64
64
|
[tool.black]
|
|
65
65
|
line-length = 88
|
|
66
66
|
target-version = [
|
|
67
|
-
'py38',
|
|
68
67
|
'py39',
|
|
69
68
|
'py310',
|
|
70
69
|
'py311',
|
|
71
70
|
'py312',
|
|
71
|
+
'py313',
|
|
72
72
|
]
|
|
73
73
|
include = '\.pyi?$'
|
|
74
74
|
exclude = '''
|
|
@@ -86,8 +86,8 @@ build-backend = "setuptools.build_meta"
|
|
|
86
86
|
|
|
87
87
|
[tool.ruff]
|
|
88
88
|
line-length = 88
|
|
89
|
-
target-version = "
|
|
90
|
-
ignore = [
|
|
89
|
+
target-version = "py313"
|
|
90
|
+
lint.ignore = [
|
|
91
91
|
'E501',
|
|
92
92
|
]
|
|
93
93
|
|
|
@@ -101,18 +101,18 @@ build-backend = "setuptools.build_meta"
|
|
|
101
101
|
#
|
|
102
102
|
# Also, make sure that all python versions used here are included in ./github/worksflows/main.yml
|
|
103
103
|
envlist =
|
|
104
|
-
py{
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
py{39,310,311}-django42-celery5{2,3}-redis{3,4}-kombu5,
|
|
105
|
+
py31{0,1}-django5{0,1}-celery5{3,4}-redis4-kombu5,
|
|
106
|
+
py312-django{42,50,51}-celery5{3,4}-redis4-kombu5,
|
|
107
|
+
py313-django{51}-celery5{3,4}-redis4-kombu5,
|
|
108
108
|
|
|
109
109
|
[gh-actions]
|
|
110
110
|
python =
|
|
111
|
-
3.8: py38
|
|
112
111
|
3.9: py39
|
|
113
112
|
3.10: py310
|
|
114
113
|
3.11: py311
|
|
115
114
|
3.12: py312
|
|
115
|
+
3.13: py313
|
|
116
116
|
|
|
117
117
|
[testenv]
|
|
118
118
|
setenv =
|
|
@@ -128,10 +128,10 @@ build-backend = "setuptools.build_meta"
|
|
|
128
128
|
celery51: Celery >=5.1, <5.2
|
|
129
129
|
celery52: Celery >=5.2, <5.3
|
|
130
130
|
celery53: Celery >=5.3, <5.4
|
|
131
|
-
|
|
132
|
-
django41: Django >=4.1, <4.2
|
|
131
|
+
celery54: Celery >=5.4, <5.5
|
|
133
132
|
django42: Django >=4.2, <5.0
|
|
134
133
|
django50: Django >=5.0, <5.1
|
|
134
|
+
django51: Django >=5.1, <5.2
|
|
135
135
|
-r{toxinidir}/requirements/ci.txt
|
|
136
136
|
|
|
137
137
|
commands = pytest --cov=./test_app --cov=./django_structlog --cov-append test_app
|
|
@@ -153,3 +153,22 @@ include = [
|
|
|
153
153
|
"./django_structlog_demo_project/*",
|
|
154
154
|
"./test_app/*",
|
|
155
155
|
]
|
|
156
|
+
|
|
157
|
+
[tool.mypy]
|
|
158
|
+
python_version=3.9
|
|
159
|
+
strict=true
|
|
160
|
+
packages=[
|
|
161
|
+
"django_structlog"
|
|
162
|
+
]
|
|
163
|
+
disallow_subclassing_any=false
|
|
164
|
+
|
|
165
|
+
[[tool.mypy.overrides]]
|
|
166
|
+
module = [
|
|
167
|
+
"django_extensions.*",
|
|
168
|
+
"ipware",
|
|
169
|
+
"django.*",
|
|
170
|
+
"structlog.*",
|
|
171
|
+
"celery.*",
|
|
172
|
+
"asgiref.*",
|
|
173
|
+
]
|
|
174
|
+
ignore_missing_imports = true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog/middlewares/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django-structlog-8.0.0 → django_structlog-9.0.0.dev1}/django_structlog.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|