safir-logging 6.3.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.
- safir_logging-6.3.0/LICENSE +21 -0
- safir_logging-6.3.0/PKG-INFO +60 -0
- safir_logging-6.3.0/README.md +13 -0
- safir_logging-6.3.0/pyproject.toml +50 -0
- safir_logging-6.3.0/setup.cfg +4 -0
- safir_logging-6.3.0/src/safir/logging/__init__.py +13 -0
- safir_logging-6.3.0/src/safir/logging/_models.py +46 -0
- safir_logging-6.3.0/src/safir/logging/_structlog.py +171 -0
- safir_logging-6.3.0/src/safir/logging/_uvicorn.py +143 -0
- safir_logging-6.3.0/src/safir/logging/py.typed +0 -0
- safir_logging-6.3.0/src/safir_logging.egg-info/PKG-INFO +60 -0
- safir_logging-6.3.0/src/safir_logging.egg-info/SOURCES.txt +13 -0
- safir_logging-6.3.0/src/safir_logging.egg-info/dependency_links.txt +1 -0
- safir_logging-6.3.0/src/safir_logging.egg-info/requires.txt +1 -0
- safir_logging-6.3.0/src/safir_logging.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Association of Universities for Research in Astronomy, Inc. (AURA)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: safir-logging
|
|
3
|
+
Version: 6.3.0
|
|
4
|
+
Summary: Logging for the Rubin Observatory SQuaRE framework, Safir.
|
|
5
|
+
Author-email: "Association of Universities for Research in Astronomy, Inc. (AURA)" <sqre-admin@lists.lsst.org>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2020 Association of Universities for Research in Astronomy, Inc. (AURA)
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://safir.lsst.io
|
|
29
|
+
Project-URL: Source, https://github.com/lsst-sqre/safir
|
|
30
|
+
Project-URL: Change log, https://safir.lsst.io/changelog.html
|
|
31
|
+
Project-URL: Issue tracker, https://github.com/lsst-sqre/safir/issues
|
|
32
|
+
Keywords: rubin,lsst
|
|
33
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Intended Audience :: Developers
|
|
40
|
+
Classifier: Natural Language :: English
|
|
41
|
+
Classifier: Operating System :: POSIX
|
|
42
|
+
Classifier: Typing :: Typed
|
|
43
|
+
Requires-Python: >=3.11
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
License-File: LICENSE
|
|
46
|
+
Requires-Dist: structlog!=23.3.0,>=21.2.0
|
|
47
|
+
|
|
48
|
+
# safir-logging
|
|
49
|
+
|
|
50
|
+
safir-logging is a subpackage of Safir, Rubin Observatory's library for building [FastAPI](https://fastapi.tiangolo.com/) services for the [Rubin Science Platform (Phalanx)](https://github.com/lsst-sqre/phalanx) and [Roundtable](https://github.com/lsst-sqre/roundtable) Kubernetes clusters.
|
|
51
|
+
It is a separate PyPI module so that it can be used as a dependency in contexts where the full Safir dependency is undesirable.
|
|
52
|
+
|
|
53
|
+
safir-logging is available from [PyPI](https://pypi.org/project/safir-logging/):
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
pip install safir-logging
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
safir-logging is developed and tested in conjunction with Safir.
|
|
60
|
+
Read more about Safir at https://safir.lsst.io.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# safir-logging
|
|
2
|
+
|
|
3
|
+
safir-logging is a subpackage of Safir, Rubin Observatory's library for building [FastAPI](https://fastapi.tiangolo.com/) services for the [Rubin Science Platform (Phalanx)](https://github.com/lsst-sqre/phalanx) and [Roundtable](https://github.com/lsst-sqre/roundtable) Kubernetes clusters.
|
|
4
|
+
It is a separate PyPI module so that it can be used as a dependency in contexts where the full Safir dependency is undesirable.
|
|
5
|
+
|
|
6
|
+
safir-logging is available from [PyPI](https://pypi.org/project/safir-logging/):
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install safir-logging
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
safir-logging is developed and tested in conjunction with Safir.
|
|
13
|
+
Read more about Safir at https://safir.lsst.io.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
# https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
|
|
3
|
+
name = "safir-logging"
|
|
4
|
+
description = "Logging for the Rubin Observatory SQuaRE framework, Safir."
|
|
5
|
+
license = {file = "LICENSE"}
|
|
6
|
+
readme= "README.md"
|
|
7
|
+
keywords = [
|
|
8
|
+
"rubin",
|
|
9
|
+
"lsst",
|
|
10
|
+
]
|
|
11
|
+
# https://pypi.org/classifiers/
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 5 - Production/Stable",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Natural Language :: English",
|
|
21
|
+
"Operating System :: POSIX",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
requires-python = ">=3.11"
|
|
25
|
+
dependencies = [
|
|
26
|
+
# 23.3.0 excluded due to https://github.com/hynek/structlog/issues/584
|
|
27
|
+
"structlog>=21.2.0,!=23.3.0",
|
|
28
|
+
]
|
|
29
|
+
dynamic = ["version"]
|
|
30
|
+
|
|
31
|
+
[[project.authors]]
|
|
32
|
+
name = "Association of Universities for Research in Astronomy, Inc. (AURA)"
|
|
33
|
+
email = "sqre-admin@lists.lsst.org"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://safir.lsst.io"
|
|
37
|
+
Source = "https://github.com/lsst-sqre/safir"
|
|
38
|
+
"Change log" = "https://safir.lsst.io/changelog.html"
|
|
39
|
+
"Issue tracker" = "https://github.com/lsst-sqre/safir/issues"
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = [
|
|
43
|
+
"setuptools>=61",
|
|
44
|
+
"wheel",
|
|
45
|
+
"setuptools_scm[toml]>=6.2"
|
|
46
|
+
]
|
|
47
|
+
build-backend = "setuptools.build_meta"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools_scm]
|
|
50
|
+
root = ".."
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Utilities for configuring structlog-based logging."""
|
|
2
|
+
|
|
3
|
+
from ._models import LogLevel, Profile
|
|
4
|
+
from ._structlog import add_log_severity, configure_logging
|
|
5
|
+
from ._uvicorn import configure_uvicorn_logging
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"LogLevel",
|
|
9
|
+
"Profile",
|
|
10
|
+
"add_log_severity",
|
|
11
|
+
"configure_logging",
|
|
12
|
+
"configure_uvicorn_logging",
|
|
13
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Models for structlog-based logging."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Self
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"LogLevel",
|
|
10
|
+
"Profile",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Profile(Enum):
|
|
15
|
+
"""Logging profile for the application."""
|
|
16
|
+
|
|
17
|
+
production = "production"
|
|
18
|
+
"""Log messages in JSON."""
|
|
19
|
+
|
|
20
|
+
development = "development"
|
|
21
|
+
"""Log messages in a format intended for human readability."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LogLevel(Enum):
|
|
25
|
+
"""Python logging level.
|
|
26
|
+
|
|
27
|
+
Any case variation is accepted when converting a string to an enum value
|
|
28
|
+
via the class constructor.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
DEBUG = "DEBUG"
|
|
32
|
+
INFO = "INFO"
|
|
33
|
+
WARNING = "WARNING"
|
|
34
|
+
ERROR = "ERROR"
|
|
35
|
+
CRITICAL = "CRITICAL"
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def _missing_(cls, value: Any) -> Self | None:
|
|
39
|
+
"""Allow strings in any case to be used to create the enum."""
|
|
40
|
+
if not isinstance(value, str):
|
|
41
|
+
return None
|
|
42
|
+
value = value.upper()
|
|
43
|
+
for member in cls:
|
|
44
|
+
if member.value == value:
|
|
45
|
+
return member
|
|
46
|
+
return None
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Utilities for configuring structlog-based logging."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import logging.config
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import structlog
|
|
11
|
+
from structlog.stdlib import add_log_level
|
|
12
|
+
from structlog.types import EventDict
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from safir.dependencies import logger as logger_dependency
|
|
16
|
+
|
|
17
|
+
_UPDATE_DEPENDENCY = True
|
|
18
|
+
except Exception:
|
|
19
|
+
_UPDATE_DEPENDENCY = False
|
|
20
|
+
|
|
21
|
+
from ._models import LogLevel, Profile
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"add_log_severity",
|
|
25
|
+
"configure_logging",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def add_log_severity(
|
|
30
|
+
logger: logging.Logger, method_name: str, event_dict: EventDict
|
|
31
|
+
) -> EventDict:
|
|
32
|
+
"""Add the log level to the event dict as ``severity``.
|
|
33
|
+
|
|
34
|
+
Intended for use as a structlog processor.
|
|
35
|
+
|
|
36
|
+
This is the same as `structlog.stdlib.add_log_level` except that it
|
|
37
|
+
uses the ``severity`` key rather than ``level`` for compatibility with
|
|
38
|
+
Google Log Explorer and its automatic processing of structured logs.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
logger
|
|
43
|
+
The wrapped logger object.
|
|
44
|
+
method_name
|
|
45
|
+
The name of the wrapped method (``warning`` or ``error``, for
|
|
46
|
+
example).
|
|
47
|
+
event_dict
|
|
48
|
+
Current context and current event. This parameter is also modified in
|
|
49
|
+
place, matching the normal behavior of structlog processors.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
``structlog.types.EventDict``
|
|
54
|
+
The modified ``structlog.types.EventDict`` with the added key.
|
|
55
|
+
"""
|
|
56
|
+
severity = add_log_level(logger, method_name, {})["level"]
|
|
57
|
+
event_dict["severity"] = severity
|
|
58
|
+
return event_dict
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def configure_logging(
|
|
62
|
+
*,
|
|
63
|
+
name: str,
|
|
64
|
+
profile: Profile | str = Profile.production,
|
|
65
|
+
log_level: LogLevel | str = LogLevel.INFO,
|
|
66
|
+
add_timestamp: bool = False,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Configure logging and structlog.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
name
|
|
73
|
+
Name of the logger, which is typically the name of your application's
|
|
74
|
+
root namespace.
|
|
75
|
+
profile
|
|
76
|
+
The name of the application profile:
|
|
77
|
+
|
|
78
|
+
development
|
|
79
|
+
Log messages are formatted for easier reading on the terminal.
|
|
80
|
+
production
|
|
81
|
+
Log messages are formatted as JSON objects.
|
|
82
|
+
|
|
83
|
+
May be given as a `Profile` enum value (preferred) or a string.
|
|
84
|
+
log_level
|
|
85
|
+
The Python log level. May be given as a `LogLevel` enum (preferred)
|
|
86
|
+
or a case-insensitive string.
|
|
87
|
+
add_timestamp
|
|
88
|
+
Whether to add an ISO-format timestamp to each log message.
|
|
89
|
+
|
|
90
|
+
Notes
|
|
91
|
+
-----
|
|
92
|
+
This function helps you configure a useful logging set up for your
|
|
93
|
+
application that's based on structlog_.
|
|
94
|
+
|
|
95
|
+
First, it configures the logger for your application to log to STDOUT.
|
|
96
|
+
Second, it configures the formatting of your log messages through
|
|
97
|
+
structlog.
|
|
98
|
+
|
|
99
|
+
In development mode, messages are key-value formatted, like this:
|
|
100
|
+
|
|
101
|
+
.. code-block:: text
|
|
102
|
+
|
|
103
|
+
[info ] Hello world [myapp] answer=42
|
|
104
|
+
|
|
105
|
+
Here, "Hello world" is the message. ``answer=42`` is a value bound to the
|
|
106
|
+
logger.
|
|
107
|
+
|
|
108
|
+
In production mode, messages are formatted as JSON objects:
|
|
109
|
+
|
|
110
|
+
.. code-block:: text
|
|
111
|
+
|
|
112
|
+
{"answer": 42, "event": "Hello world", "logger": "myapp",
|
|
113
|
+
"severity": "info"}
|
|
114
|
+
|
|
115
|
+
Examples
|
|
116
|
+
--------
|
|
117
|
+
.. code-block:: python
|
|
118
|
+
|
|
119
|
+
import structlog
|
|
120
|
+
from safir.logging import configure_logging
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
configure_logging(name="mybot")
|
|
124
|
+
logger = structlog.get_logger("mybot")
|
|
125
|
+
logger.info("Hello world")
|
|
126
|
+
"""
|
|
127
|
+
if not isinstance(log_level, LogLevel):
|
|
128
|
+
log_level = LogLevel[log_level.upper()]
|
|
129
|
+
if not isinstance(profile, Profile):
|
|
130
|
+
profile = Profile[profile]
|
|
131
|
+
|
|
132
|
+
stream_handler = logging.StreamHandler(stream=sys.stdout)
|
|
133
|
+
stream_handler.setFormatter(logging.Formatter("%(message)s"))
|
|
134
|
+
logger = logging.getLogger(name)
|
|
135
|
+
logger.handlers = []
|
|
136
|
+
logger.addHandler(stream_handler)
|
|
137
|
+
logger.setLevel(log_level.value)
|
|
138
|
+
|
|
139
|
+
processors: list[Any] = [
|
|
140
|
+
structlog.stdlib.filter_by_level,
|
|
141
|
+
structlog.stdlib.add_logger_name,
|
|
142
|
+
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
143
|
+
]
|
|
144
|
+
if add_timestamp:
|
|
145
|
+
processors.append(structlog.processors.TimeStamper(fmt="iso"))
|
|
146
|
+
processors.extend(
|
|
147
|
+
[
|
|
148
|
+
structlog.processors.StackInfoRenderer(),
|
|
149
|
+
structlog.processors.UnicodeDecoder(),
|
|
150
|
+
]
|
|
151
|
+
)
|
|
152
|
+
if profile == Profile.production:
|
|
153
|
+
# JSON-formatted logging
|
|
154
|
+
processors.append(add_log_severity)
|
|
155
|
+
processors.append(structlog.processors.format_exc_info)
|
|
156
|
+
processors.append(structlog.processors.JSONRenderer())
|
|
157
|
+
else:
|
|
158
|
+
# Key-value formatted logging
|
|
159
|
+
processors.append(structlog.stdlib.add_log_level)
|
|
160
|
+
processors.append(structlog.dev.ConsoleRenderer())
|
|
161
|
+
|
|
162
|
+
structlog.configure(
|
|
163
|
+
processors=processors,
|
|
164
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
165
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
166
|
+
cache_logger_on_first_use=True,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Set the configured name for the global logger.
|
|
170
|
+
if _UPDATE_DEPENDENCY:
|
|
171
|
+
logger_dependency._logger_name = name # noqa: SLF001
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Utilities for configuring uvicorn to use structlog."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import logging.config
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
import structlog
|
|
10
|
+
from structlog.types import EventDict
|
|
11
|
+
|
|
12
|
+
from ._models import LogLevel
|
|
13
|
+
from ._structlog import add_log_severity
|
|
14
|
+
|
|
15
|
+
__all__ = ["configure_uvicorn_logging"]
|
|
16
|
+
|
|
17
|
+
_UVICORN_ACCESS_REGEX = re.compile(r'^[0-9.]+:[0-9]+ - "([^"]+)" ([0-9]+)$')
|
|
18
|
+
"""Regex to parse Uvicorn access logs."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _process_uvicorn_access_log(
|
|
22
|
+
logger: logging.Logger, method_name: str, event_dict: EventDict
|
|
23
|
+
) -> EventDict:
|
|
24
|
+
"""Parse a Uvicorn access log entry into key/value pairs.
|
|
25
|
+
|
|
26
|
+
Intended for use as a structlog processor.
|
|
27
|
+
|
|
28
|
+
This checks whether the log message is a Uvicorn access log entry and, if
|
|
29
|
+
so, parses the message into key/value pairs for JSON logging so that the
|
|
30
|
+
details can be programmatically extracted. ``remoteIp`` is intentionally
|
|
31
|
+
omitted since it isn't aware of ``X-Forwarded-For`` and will therefore
|
|
32
|
+
always point to an uninteresting in-cluster IP.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
logger
|
|
37
|
+
The wrapped logger object.
|
|
38
|
+
method_name
|
|
39
|
+
The name of the wrapped method (``warning`` or ``error``, for
|
|
40
|
+
example).
|
|
41
|
+
event_dict
|
|
42
|
+
Current context and current event. This parameter is also modified in
|
|
43
|
+
place, matching the normal behavior of structlog processors.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
EventDict
|
|
48
|
+
The modified `~structlog.types.EventDict` with the added key.
|
|
49
|
+
"""
|
|
50
|
+
match = _UVICORN_ACCESS_REGEX.match(event_dict["event"])
|
|
51
|
+
if not match:
|
|
52
|
+
return event_dict
|
|
53
|
+
request = match.group(1)
|
|
54
|
+
method, rest = request.split(" ", 1)
|
|
55
|
+
url, protocol = rest.rsplit(" ", 1)
|
|
56
|
+
if "httpRequest" not in event_dict:
|
|
57
|
+
event_dict["httpRequest"] = {}
|
|
58
|
+
event_dict["httpRequest"]["protocol"] = protocol
|
|
59
|
+
event_dict["httpRequest"]["requestMethod"] = method
|
|
60
|
+
event_dict["httpRequest"]["requestUrl"] = url
|
|
61
|
+
event_dict["httpRequest"]["status"] = match.group(2)
|
|
62
|
+
return event_dict
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def configure_uvicorn_logging(
|
|
66
|
+
log_level: LogLevel | str = LogLevel.INFO,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Set up logging.
|
|
69
|
+
|
|
70
|
+
This configures Uvicorn to use structlog for output formatting and
|
|
71
|
+
installs a custom processor to parse its access log messages into
|
|
72
|
+
additional log context that matches the format of Google log messages.
|
|
73
|
+
This helps Google's Cloud Logging system understand the logs. Access logs
|
|
74
|
+
are sent to standard output and all other logs are sent to standard
|
|
75
|
+
error.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
log_level
|
|
80
|
+
The Python log level. May be given as a `LogLevel` enum (preferred)
|
|
81
|
+
or a case-insensitive string.
|
|
82
|
+
|
|
83
|
+
Notes
|
|
84
|
+
-----
|
|
85
|
+
This method should normally be called during FastAPI app creation, either
|
|
86
|
+
during Python module import or inside the function that creates and
|
|
87
|
+
returns the FastAPI app that Uvicorn will run. This ensures the logging
|
|
88
|
+
setup is complete before Uvicorn logs its first message.
|
|
89
|
+
"""
|
|
90
|
+
if not isinstance(log_level, LogLevel):
|
|
91
|
+
log_level = LogLevel[log_level.upper()]
|
|
92
|
+
|
|
93
|
+
processors = [
|
|
94
|
+
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
|
95
|
+
structlog.processors.format_exc_info,
|
|
96
|
+
structlog.processors.JSONRenderer(),
|
|
97
|
+
]
|
|
98
|
+
logging.config.dictConfig(
|
|
99
|
+
{
|
|
100
|
+
"version": 1,
|
|
101
|
+
"disable_existing_loggers": False,
|
|
102
|
+
"formatters": {
|
|
103
|
+
"json-access": {
|
|
104
|
+
"()": structlog.stdlib.ProcessorFormatter,
|
|
105
|
+
"processors": processors,
|
|
106
|
+
"foreign_pre_chain": [
|
|
107
|
+
add_log_severity,
|
|
108
|
+
_process_uvicorn_access_log,
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
"json": {
|
|
112
|
+
"()": structlog.stdlib.ProcessorFormatter,
|
|
113
|
+
"processors": processors,
|
|
114
|
+
"foreign_pre_chain": [add_log_severity],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
"handlers": {
|
|
118
|
+
"uvicorn.access": {
|
|
119
|
+
"level": log_level.value,
|
|
120
|
+
"class": "logging.StreamHandler",
|
|
121
|
+
"formatter": "json-access",
|
|
122
|
+
"stream": "ext://sys.stdout",
|
|
123
|
+
},
|
|
124
|
+
"uvicorn.default": {
|
|
125
|
+
"level": log_level.value,
|
|
126
|
+
"class": "logging.StreamHandler",
|
|
127
|
+
"formatter": "json",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
"loggers": {
|
|
131
|
+
"uvicorn.error": {
|
|
132
|
+
"handlers": ["uvicorn.default"],
|
|
133
|
+
"level": log_level.value,
|
|
134
|
+
"propagate": False,
|
|
135
|
+
},
|
|
136
|
+
"uvicorn.access": {
|
|
137
|
+
"handlers": ["uvicorn.access"],
|
|
138
|
+
"level": log_level.value,
|
|
139
|
+
"propagate": False,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: safir-logging
|
|
3
|
+
Version: 6.3.0
|
|
4
|
+
Summary: Logging for the Rubin Observatory SQuaRE framework, Safir.
|
|
5
|
+
Author-email: "Association of Universities for Research in Astronomy, Inc. (AURA)" <sqre-admin@lists.lsst.org>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2020 Association of Universities for Research in Astronomy, Inc. (AURA)
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://safir.lsst.io
|
|
29
|
+
Project-URL: Source, https://github.com/lsst-sqre/safir
|
|
30
|
+
Project-URL: Change log, https://safir.lsst.io/changelog.html
|
|
31
|
+
Project-URL: Issue tracker, https://github.com/lsst-sqre/safir/issues
|
|
32
|
+
Keywords: rubin,lsst
|
|
33
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Intended Audience :: Developers
|
|
40
|
+
Classifier: Natural Language :: English
|
|
41
|
+
Classifier: Operating System :: POSIX
|
|
42
|
+
Classifier: Typing :: Typed
|
|
43
|
+
Requires-Python: >=3.11
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
License-File: LICENSE
|
|
46
|
+
Requires-Dist: structlog!=23.3.0,>=21.2.0
|
|
47
|
+
|
|
48
|
+
# safir-logging
|
|
49
|
+
|
|
50
|
+
safir-logging is a subpackage of Safir, Rubin Observatory's library for building [FastAPI](https://fastapi.tiangolo.com/) services for the [Rubin Science Platform (Phalanx)](https://github.com/lsst-sqre/phalanx) and [Roundtable](https://github.com/lsst-sqre/roundtable) Kubernetes clusters.
|
|
51
|
+
It is a separate PyPI module so that it can be used as a dependency in contexts where the full Safir dependency is undesirable.
|
|
52
|
+
|
|
53
|
+
safir-logging is available from [PyPI](https://pypi.org/project/safir-logging/):
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
pip install safir-logging
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
safir-logging is developed and tested in conjunction with Safir.
|
|
60
|
+
Read more about Safir at https://safir.lsst.io.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/safir/logging/__init__.py
|
|
5
|
+
src/safir/logging/_models.py
|
|
6
|
+
src/safir/logging/_structlog.py
|
|
7
|
+
src/safir/logging/_uvicorn.py
|
|
8
|
+
src/safir/logging/py.typed
|
|
9
|
+
src/safir_logging.egg-info/PKG-INFO
|
|
10
|
+
src/safir_logging.egg-info/SOURCES.txt
|
|
11
|
+
src/safir_logging.egg-info/dependency_links.txt
|
|
12
|
+
src/safir_logging.egg-info/requires.txt
|
|
13
|
+
src/safir_logging.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
structlog!=23.3.0,>=21.2.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
safir
|