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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ structlog!=23.3.0,>=21.2.0