json-logify 0.1.0__py3-none-any.whl
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.
- json_logify-0.1.0.dist-info/METADATA +263 -0
- json_logify-0.1.0.dist-info/RECORD +11 -0
- json_logify-0.1.0.dist-info/WHEEL +5 -0
- json_logify-0.1.0.dist-info/licenses/LICENSE +21 -0
- json_logify-0.1.0.dist-info/top_level.txt +1 -0
- logify/__init__.py +50 -0
- logify/core.py +198 -0
- logify/django.py +88 -0
- logify/fastapi.py +82 -0
- logify/flask.py +88 -0
- logify/version.py +1 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: json-logify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Universal structured logging with exact JSON schema for Python frameworks
|
|
5
|
+
Author-email: Bakdoolot Kulbarakov <kulbarakovbh@gmail.com>
|
|
6
|
+
Maintainer-email: Bakdoolot Kulbarakov <kulbarakovbh@gmail.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 Kulbarakov Bakdoolot
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://github.com/yourusername/json-logify
|
|
30
|
+
Project-URL: Documentation, https://json-logify.readthedocs.io/
|
|
31
|
+
Project-URL: Repository, https://github.com/yourusername/json-logify
|
|
32
|
+
Project-URL: Issues, https://github.com/yourusername/json-logify/issues
|
|
33
|
+
Project-URL: Changelog, https://github.com/yourusername/json-logify/blob/main/CHANGELOG.md
|
|
34
|
+
Keywords: logging,structured,json,django,fastapi,flask,universal,schema,orjson,structlog
|
|
35
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
36
|
+
Classifier: Intended Audience :: Developers
|
|
37
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
38
|
+
Classifier: Operating System :: OS Independent
|
|
39
|
+
Classifier: Programming Language :: Python :: 3
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
45
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
46
|
+
Classifier: Topic :: System :: Logging
|
|
47
|
+
Classifier: Framework :: Django
|
|
48
|
+
Classifier: Framework :: FastAPI
|
|
49
|
+
Classifier: Framework :: Flask
|
|
50
|
+
Requires-Python: >=3.12
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
License-File: LICENSE
|
|
53
|
+
Requires-Dist: structlog>=23.0.0
|
|
54
|
+
Requires-Dist: orjson>=3.8.0
|
|
55
|
+
Provides-Extra: django
|
|
56
|
+
Requires-Dist: django>=5.2.6; extra == "django"
|
|
57
|
+
Provides-Extra: fastapi
|
|
58
|
+
Requires-Dist: fastapi>=0.116.1; extra == "fastapi"
|
|
59
|
+
Requires-Dist: uvicorn>=0.35.0; extra == "fastapi"
|
|
60
|
+
Provides-Extra: flask
|
|
61
|
+
Requires-Dist: flask>=3.1.2; extra == "flask"
|
|
62
|
+
Provides-Extra: dev
|
|
63
|
+
Requires-Dist: pytest>=8.4.2; extra == "dev"
|
|
64
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
65
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
66
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
67
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
68
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
69
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
70
|
+
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
|
|
71
|
+
Requires-Dist: bandit>=1.7.5; extra == "dev"
|
|
72
|
+
Provides-Extra: all
|
|
73
|
+
Requires-Dist: json-logify[django,fastapi,flask]; extra == "all"
|
|
74
|
+
Dynamic: license-file
|
|
75
|
+
|
|
76
|
+
# json-logify
|
|
77
|
+
|
|
78
|
+
Universal structured logging with exact JSON schema for Python frameworks.
|
|
79
|
+
|
|
80
|
+
[](https://badge.fury.io/py/json-logify)
|
|
81
|
+
[](https://pypi.org/project/json-logify/)
|
|
82
|
+
[](https://opensource.org/licenses/MIT)
|
|
83
|
+
|
|
84
|
+
## Features
|
|
85
|
+
|
|
86
|
+
- <� **Exact JSON Schema**: Consistent log format across all frameworks
|
|
87
|
+
- � **High Performance**: Built with structlog and orjson for maximum speed
|
|
88
|
+
- < **Universal**: Works with Django, FastAPI, Flask, and standalone Python
|
|
89
|
+
- =' **Easy Setup**: One-line configuration for most use cases
|
|
90
|
+
- =� **Rich Context**: Request IDs, user tracking, and custom payload support
|
|
91
|
+
- =
|
|
92
|
+
**Modern Python**: Full type hints and async support
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
|
|
96
|
+
### Installation
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Basic installation
|
|
100
|
+
pip install json-logify
|
|
101
|
+
|
|
102
|
+
# For specific frameworks
|
|
103
|
+
pip install json-logify[django]
|
|
104
|
+
pip install json-logify[fastapi]
|
|
105
|
+
pip install json-logify[flask]
|
|
106
|
+
|
|
107
|
+
# Everything
|
|
108
|
+
pip install json-logify[all]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Basic Usage
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from logify import info, error
|
|
115
|
+
|
|
116
|
+
# Simple logging
|
|
117
|
+
info("User logged in", user_id="12345", action="login")
|
|
118
|
+
|
|
119
|
+
# Error logging with exception
|
|
120
|
+
try:
|
|
121
|
+
raise ValueError("Something went wrong")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
error("Operation failed", error=e, operation="data_processing")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Output:
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"timestamp": "2025-01-15T10:30:00.123Z",
|
|
130
|
+
"message": "User logged in",
|
|
131
|
+
"level": "INFO",
|
|
132
|
+
"payload": {
|
|
133
|
+
"user_id": "12345",
|
|
134
|
+
"action": "login"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Django Integration
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# settings.py
|
|
143
|
+
from logify.django import get_logging_config
|
|
144
|
+
|
|
145
|
+
LOGGING = get_logging_config(
|
|
146
|
+
service_name="myapp",
|
|
147
|
+
json_logs=True
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Add middleware (optional for request tracking)
|
|
151
|
+
MIDDLEWARE = [
|
|
152
|
+
'logify.django.LogifyMiddleware',
|
|
153
|
+
# ... other middleware
|
|
154
|
+
]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### FastAPI Integration
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from fastapi import FastAPI
|
|
161
|
+
from logify.fastapi import LogifyMiddleware
|
|
162
|
+
|
|
163
|
+
app = FastAPI()
|
|
164
|
+
app.add_middleware(LogifyMiddleware, service_name="myapi")
|
|
165
|
+
|
|
166
|
+
@app.get("/")
|
|
167
|
+
async def root():
|
|
168
|
+
from logify import info
|
|
169
|
+
info("API endpoint called", endpoint="/")
|
|
170
|
+
return {"message": "Hello World"}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Flask Integration
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from flask import Flask
|
|
177
|
+
from logify.flask import init_logify
|
|
178
|
+
|
|
179
|
+
app = Flask(__name__)
|
|
180
|
+
init_logify(app, service_name="myapp")
|
|
181
|
+
|
|
182
|
+
@app.route("/")
|
|
183
|
+
def hello():
|
|
184
|
+
from logify import info
|
|
185
|
+
info("Flask endpoint called", endpoint="/")
|
|
186
|
+
return "Hello, World!"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Advanced Usage
|
|
190
|
+
|
|
191
|
+
### Context Management
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from logify import bind, set_request_context, clear_request_context
|
|
195
|
+
|
|
196
|
+
# Bind context to a logger
|
|
197
|
+
logger = bind(service="auth", module="login")
|
|
198
|
+
logger.info("Processing login", user_id="123")
|
|
199
|
+
|
|
200
|
+
# Set request-level context (useful in middleware)
|
|
201
|
+
set_request_context(request_id="req-456", user_id="123")
|
|
202
|
+
info("User action", action="view_profile") # Includes request context
|
|
203
|
+
clear_request_context()
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Performance Tracking
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from logify import track_performance
|
|
210
|
+
|
|
211
|
+
@track_performance
|
|
212
|
+
def expensive_operation():
|
|
213
|
+
# Your code here
|
|
214
|
+
return "result"
|
|
215
|
+
|
|
216
|
+
# Automatically logs function start, completion, and duration
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Custom Configuration
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from logify import configure_logging
|
|
223
|
+
|
|
224
|
+
# Configure with custom settings
|
|
225
|
+
configure_logging(
|
|
226
|
+
service_name="myapp",
|
|
227
|
+
level="DEBUG"
|
|
228
|
+
)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Log Schema
|
|
232
|
+
|
|
233
|
+
All logs follow this exact JSON schema:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"timestamp": "2025-01-15T10:30:00.123Z",
|
|
238
|
+
"message": "Log message here",
|
|
239
|
+
"level": "INFO",
|
|
240
|
+
"error": "Error description (optional)",
|
|
241
|
+
"payload": {
|
|
242
|
+
"service": "myapp",
|
|
243
|
+
"request_id": "req-123",
|
|
244
|
+
"custom_field": "custom_value"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The `error` field is optional and will only appear when logging errors or exceptions.
|
|
250
|
+
|
|
251
|
+
## Requirements
|
|
252
|
+
|
|
253
|
+
- Python 3.8+
|
|
254
|
+
- structlog >= 23.0.0
|
|
255
|
+
- orjson >= 3.8.0
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT License - see LICENSE file for details.
|
|
260
|
+
|
|
261
|
+
## Contributing
|
|
262
|
+
|
|
263
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
json_logify-0.1.0.dist-info/licenses/LICENSE,sha256=qRyWd6Y0_db_j_ECsAAEJW8-XMnhYUg_yK3c5UkOfNI,1077
|
|
2
|
+
logify/__init__.py,sha256=t2Jjw7T4jRMIYvXJOTBraaW6MneifmS_gDKDlrRHmeU,1053
|
|
3
|
+
logify/core.py,sha256=kiMraMhpH9GShY0lPhXAnBO0bTMe0a77cIVpWldecZY,5548
|
|
4
|
+
logify/django.py,sha256=i5z9-nzrBF712ba1xkMpAsND-RLFopDLk8M7tsyTPzk,2526
|
|
5
|
+
logify/fastapi.py,sha256=ACQPWGVfTexUUCQX2oh-s5NBT1q0q5UDARbNsKulamU,2567
|
|
6
|
+
logify/flask.py,sha256=YPRsaFiojP-2buZCwNhm5JmpgBAakJAQsKy1hh6r8ek,2645
|
|
7
|
+
logify/version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
8
|
+
json_logify-0.1.0.dist-info/METADATA,sha256=Lu5bw9vf8mz16MTG0jkOXmKltqX1osvMl8E03aXRR30,7659
|
|
9
|
+
json_logify-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
json_logify-0.1.0.dist-info/top_level.txt,sha256=MRERkeaav8J-KtwGpiLDCnSNQ5e2JqKUMAlJnYVntao,7
|
|
11
|
+
json_logify-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kulbarakov Bakdoolot
|
|
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 @@
|
|
|
1
|
+
logify
|
logify/__init__.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
json-logify - Universal structured logging with exact JSON schema for Python frameworks.
|
|
3
|
+
|
|
4
|
+
Simple usage:
|
|
5
|
+
from logify import info, error
|
|
6
|
+
|
|
7
|
+
info("User logged in", user_id="12345")
|
|
8
|
+
error("Database error", error="Connection timeout")
|
|
9
|
+
|
|
10
|
+
For more examples, see: https://github.com/yourusername/json-logify
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .core import ( # Convenience functions; Configuration; Context management; Utilities
|
|
14
|
+
bind,
|
|
15
|
+
clear_request_context,
|
|
16
|
+
configure_logging,
|
|
17
|
+
critical,
|
|
18
|
+
debug,
|
|
19
|
+
error,
|
|
20
|
+
exception,
|
|
21
|
+
generate_request_id,
|
|
22
|
+
get_logger,
|
|
23
|
+
info,
|
|
24
|
+
set_request_context,
|
|
25
|
+
track_performance,
|
|
26
|
+
warning,
|
|
27
|
+
)
|
|
28
|
+
from .version import __version__
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
# Version
|
|
32
|
+
"__version__",
|
|
33
|
+
# Convenience functions
|
|
34
|
+
"debug",
|
|
35
|
+
"info",
|
|
36
|
+
"warning",
|
|
37
|
+
"error",
|
|
38
|
+
"critical",
|
|
39
|
+
"exception",
|
|
40
|
+
# Configuration
|
|
41
|
+
"configure_logging",
|
|
42
|
+
"get_logger",
|
|
43
|
+
# Context management
|
|
44
|
+
"bind",
|
|
45
|
+
"set_request_context",
|
|
46
|
+
"clear_request_context",
|
|
47
|
+
# Utilities
|
|
48
|
+
"generate_request_id",
|
|
49
|
+
"track_performance",
|
|
50
|
+
]
|
logify/core.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core structured logging functionality for json-logify.
|
|
3
|
+
Provides universal structured logging with exact JSON schema using orjson and structlog.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from contextvars import ContextVar
|
|
9
|
+
from datetime import UTC, datetime
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
import orjson
|
|
13
|
+
import structlog
|
|
14
|
+
|
|
15
|
+
# Context variables for request tracking
|
|
16
|
+
_request_context: ContextVar[Dict[str, Any]] = ContextVar("request_context", default={})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def orjson_serializer(_, __, event_dict):
|
|
20
|
+
"""Serialize log entries using orjson for performance."""
|
|
21
|
+
return orjson.dumps(event_dict).decode("utf-8")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def add_timestamp(_, __, event_dict):
|
|
25
|
+
"""Add ISO timestamp to log entries."""
|
|
26
|
+
# Unresolved attribute reference 'UTC' for class 'datetime'
|
|
27
|
+
event_dict["timestamp"] = datetime.now(UTC).isoformat().replace("+00:00", "Z")
|
|
28
|
+
return event_dict
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def format_log_entry(logger, name, event_dict):
|
|
32
|
+
"""Format log entry according to json-logify schema."""
|
|
33
|
+
message = event_dict.pop("event", "")
|
|
34
|
+
|
|
35
|
+
# Get the correct log level from the logger name if available
|
|
36
|
+
# or from the event dict, defaulting to INFO
|
|
37
|
+
level = name or event_dict.get("level", "INFO")
|
|
38
|
+
|
|
39
|
+
# Remove level from event_dict to avoid duplication
|
|
40
|
+
event_dict.pop("level", None)
|
|
41
|
+
|
|
42
|
+
# Get context
|
|
43
|
+
try:
|
|
44
|
+
context = _request_context.get({})
|
|
45
|
+
except LookupError:
|
|
46
|
+
context = {}
|
|
47
|
+
|
|
48
|
+
# Extract error from event_dict if present
|
|
49
|
+
error_field = event_dict.pop("error", None)
|
|
50
|
+
|
|
51
|
+
# Build the standardized log entry
|
|
52
|
+
formatted_entry = {
|
|
53
|
+
"timestamp": event_dict.pop("timestamp", datetime.now(UTC).isoformat().replace("+00:00", "Z")),
|
|
54
|
+
"message": message,
|
|
55
|
+
"level": level.upper(),
|
|
56
|
+
"payload": {**context, **event_dict},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Add optional error field at top level
|
|
60
|
+
if error_field:
|
|
61
|
+
formatted_entry["error"] = str(error_field)
|
|
62
|
+
|
|
63
|
+
return formatted_entry
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Configure structlog with the right processors
|
|
67
|
+
structlog.configure(
|
|
68
|
+
processors=[
|
|
69
|
+
structlog.stdlib.add_log_level,
|
|
70
|
+
add_timestamp,
|
|
71
|
+
format_log_entry,
|
|
72
|
+
orjson_serializer,
|
|
73
|
+
],
|
|
74
|
+
wrapper_class=structlog.BoundLogger,
|
|
75
|
+
logger_factory=structlog.PrintLoggerFactory(),
|
|
76
|
+
cache_logger_on_first_use=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Get the default logger
|
|
81
|
+
logger = structlog.get_logger("json-logify")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Convenience functions
|
|
85
|
+
def debug(message: str, **kwargs):
|
|
86
|
+
"""Log debug message."""
|
|
87
|
+
logger.debug(message, **kwargs)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def info(message: str, **kwargs):
|
|
91
|
+
"""Log info message."""
|
|
92
|
+
logger.info(message, **kwargs)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def warning(message: str, **kwargs):
|
|
96
|
+
"""Log warning message."""
|
|
97
|
+
logger.warning(message, **kwargs)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def warn(message: str, **kwargs):
|
|
101
|
+
"""Alias for warning."""
|
|
102
|
+
logger.warning(message, **kwargs)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def error(message: str, error: Exception = None, **kwargs):
|
|
106
|
+
"""Log error message."""
|
|
107
|
+
if error:
|
|
108
|
+
kwargs["error"] = error # Will be moved to top-level by format_log_entry
|
|
109
|
+
kwargs["error_type"] = type(error).__name__
|
|
110
|
+
logger.error(message, **kwargs)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def critical(message: str, **kwargs):
|
|
114
|
+
"""Log critical message."""
|
|
115
|
+
logger.critical(message, **kwargs)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def exception(message: str, **kwargs):
|
|
119
|
+
"""Log exception with traceback."""
|
|
120
|
+
import traceback
|
|
121
|
+
|
|
122
|
+
kwargs["traceback"] = traceback.format_exc()
|
|
123
|
+
logger.error(message, **kwargs)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_logger(name: str = "json-logify", service: str = "app"):
|
|
127
|
+
"""Get a named logger instance."""
|
|
128
|
+
bound_logger = structlog.get_logger(name)
|
|
129
|
+
return bound_logger.bind(service=service)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def configure_logging(service_name: str = "app", level: str = "INFO"):
|
|
133
|
+
"""Configure logging for the application."""
|
|
134
|
+
import logging
|
|
135
|
+
|
|
136
|
+
# Set logging level on stdlib logger
|
|
137
|
+
logging.basicConfig(level=getattr(logging, level.upper(), logging.INFO))
|
|
138
|
+
|
|
139
|
+
structlog.configure(
|
|
140
|
+
processors=[
|
|
141
|
+
add_timestamp,
|
|
142
|
+
format_log_entry,
|
|
143
|
+
orjson_serializer,
|
|
144
|
+
],
|
|
145
|
+
wrapper_class=structlog.BoundLogger, # type: ignore
|
|
146
|
+
logger_factory=structlog.PrintLoggerFactory(),
|
|
147
|
+
cache_logger_on_first_use=True,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Bind service name to the default logger
|
|
151
|
+
global logger
|
|
152
|
+
logger = structlog.get_logger("json-logify").bind(service=service_name)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def bind(**kwargs):
|
|
156
|
+
"""Create a bound logger with additional context."""
|
|
157
|
+
return logger.bind(**kwargs)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def set_request_context(**kwargs):
|
|
161
|
+
"""Set request-level context variables."""
|
|
162
|
+
try:
|
|
163
|
+
current = _request_context.get({})
|
|
164
|
+
_request_context.set({**current, **kwargs})
|
|
165
|
+
except LookupError:
|
|
166
|
+
_request_context.set(kwargs)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def clear_request_context():
|
|
170
|
+
"""Clear request-level context."""
|
|
171
|
+
_request_context.set({})
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def generate_request_id() -> str:
|
|
175
|
+
"""Generate a unique request ID."""
|
|
176
|
+
return str(uuid.uuid4())
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def track_performance(func):
|
|
180
|
+
"""Decorator to track function performance."""
|
|
181
|
+
|
|
182
|
+
def wrapper(*args, **kwargs):
|
|
183
|
+
start_time = time.time()
|
|
184
|
+
request_id = generate_request_id()
|
|
185
|
+
|
|
186
|
+
info("Function started", function=func.__name__, request_id=request_id)
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
result = func(*args, **kwargs)
|
|
190
|
+
duration = time.time() - start_time
|
|
191
|
+
info("Function completed", function=func.__name__, request_id=request_id, duration_seconds=duration)
|
|
192
|
+
return result
|
|
193
|
+
except Exception as e:
|
|
194
|
+
duration = time.time() - start_time
|
|
195
|
+
error("Function failed", function=func.__name__, request_id=request_id, duration_seconds=duration, error=e)
|
|
196
|
+
raise
|
|
197
|
+
|
|
198
|
+
return wrapper
|
logify/django.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django integration for json-logify structured logging.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .core import clear_request_context, configure_logging, generate_request_id, set_request_context
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_logging_config(service_name: str = "django", level: str = "INFO", json_logs: bool = True):
|
|
9
|
+
"""
|
|
10
|
+
Get Django logging configuration for json-logify.
|
|
11
|
+
|
|
12
|
+
Usage in settings.py:
|
|
13
|
+
from logify.django import get_logging_config
|
|
14
|
+
LOGGING = get_logging_config(service_name="myapp")
|
|
15
|
+
"""
|
|
16
|
+
if json_logs:
|
|
17
|
+
configure_logging(service_name=service_name, level=level)
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
"version": 1,
|
|
21
|
+
"disable_existing_loggers": False,
|
|
22
|
+
"formatters": {
|
|
23
|
+
"json": {"format": "%(message)s"},
|
|
24
|
+
},
|
|
25
|
+
"handlers": {
|
|
26
|
+
"console": {
|
|
27
|
+
"class": "logging.StreamHandler",
|
|
28
|
+
"formatter": "json",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
"root": {
|
|
32
|
+
"handlers": ["console"],
|
|
33
|
+
"level": level,
|
|
34
|
+
},
|
|
35
|
+
"loggers": {
|
|
36
|
+
"django": {
|
|
37
|
+
"handlers": ["console"],
|
|
38
|
+
"level": level,
|
|
39
|
+
"propagate": False,
|
|
40
|
+
},
|
|
41
|
+
service_name: {
|
|
42
|
+
"handlers": ["console"],
|
|
43
|
+
"level": level,
|
|
44
|
+
"propagate": False,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class LogifyMiddleware:
|
|
51
|
+
"""Django middleware for structured logging with request context."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, get_response):
|
|
54
|
+
self.get_response = get_response
|
|
55
|
+
|
|
56
|
+
def __call__(self, request):
|
|
57
|
+
# Generate request ID and set context
|
|
58
|
+
request_id = generate_request_id()
|
|
59
|
+
request.logify_request_id = request_id
|
|
60
|
+
|
|
61
|
+
set_request_context(
|
|
62
|
+
request_id=request_id,
|
|
63
|
+
method=request.method,
|
|
64
|
+
path=request.path,
|
|
65
|
+
user_agent=request.META.get("HTTP_USER_AGENT", ""),
|
|
66
|
+
remote_addr=request.META.get("REMOTE_ADDR", ""),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
response = self.get_response(request)
|
|
71
|
+
|
|
72
|
+
# Add response info to context
|
|
73
|
+
set_request_context(
|
|
74
|
+
status_code=response.status_code,
|
|
75
|
+
content_length=len(response.content) if hasattr(response, "content") else None,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return response
|
|
79
|
+
finally:
|
|
80
|
+
clear_request_context()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def setup_django_logging(service_name: str = "django"):
|
|
84
|
+
"""
|
|
85
|
+
Set up Django logging with json-logify.
|
|
86
|
+
Call this in your Django settings or apps.py
|
|
87
|
+
"""
|
|
88
|
+
configure_logging(service_name=service_name)
|
logify/fastapi.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI integration for json-logify structured logging.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from fastapi import Request
|
|
8
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
9
|
+
|
|
10
|
+
from .core import clear_request_context, configure_logging, error, generate_request_id, info, set_request_context
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LogifyMiddleware(BaseHTTPMiddleware):
|
|
14
|
+
"""FastAPI middleware for structured logging with request context."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, app, service_name: str = "fastapi", log_requests: bool = True):
|
|
17
|
+
super().__init__(app)
|
|
18
|
+
self.service_name = service_name
|
|
19
|
+
self.log_requests = log_requests
|
|
20
|
+
configure_logging(service_name=service_name)
|
|
21
|
+
|
|
22
|
+
async def dispatch(self, request: Request, call_next):
|
|
23
|
+
# Generate request ID and set context
|
|
24
|
+
request_id = generate_request_id()
|
|
25
|
+
start_time = time.time()
|
|
26
|
+
|
|
27
|
+
# Extract client info
|
|
28
|
+
client_host = getattr(request.client, "host", "unknown") if request.client else "unknown"
|
|
29
|
+
|
|
30
|
+
set_request_context(
|
|
31
|
+
request_id=request_id,
|
|
32
|
+
method=request.method,
|
|
33
|
+
path=request.url.path,
|
|
34
|
+
query_params=str(request.query_params) if request.query_params else None,
|
|
35
|
+
client_host=client_host,
|
|
36
|
+
user_agent=request.headers.get("user-agent", ""),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if self.log_requests:
|
|
40
|
+
info("Request started", method=request.method, path=request.url.path, request_id=request_id)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
response = await call_next(request)
|
|
44
|
+
|
|
45
|
+
duration = time.time() - start_time
|
|
46
|
+
|
|
47
|
+
# Add response info to context
|
|
48
|
+
set_request_context(status_code=response.status_code, duration_seconds=duration)
|
|
49
|
+
|
|
50
|
+
if self.log_requests:
|
|
51
|
+
info(
|
|
52
|
+
"Request completed",
|
|
53
|
+
status_code=response.status_code,
|
|
54
|
+
duration_seconds=duration,
|
|
55
|
+
request_id=request_id,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return response
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
duration = time.time() - start_time
|
|
62
|
+
|
|
63
|
+
if self.log_requests:
|
|
64
|
+
error(
|
|
65
|
+
"Request failed",
|
|
66
|
+
error=e,
|
|
67
|
+
duration_seconds=duration,
|
|
68
|
+
request_id=request_id,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
raise
|
|
72
|
+
|
|
73
|
+
finally:
|
|
74
|
+
clear_request_context()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def setup_fastapi_logging(service_name: str = "fastapi"):
|
|
78
|
+
"""
|
|
79
|
+
Set up FastAPI logging with json-logify.
|
|
80
|
+
Call this when creating your FastAPI app.
|
|
81
|
+
"""
|
|
82
|
+
configure_logging(service_name=service_name)
|
logify/flask.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Flask integration for json-logify structured logging.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from flask import Flask, g, request
|
|
8
|
+
|
|
9
|
+
from .core import clear_request_context, configure_logging, error, generate_request_id, info, set_request_context
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def init_logify(app: Flask, service_name: str = "flask", log_requests: bool = True):
|
|
13
|
+
"""
|
|
14
|
+
Initialize json-logify for Flask application.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
from flask import Flask
|
|
18
|
+
from logify.flask import init_logify
|
|
19
|
+
|
|
20
|
+
app = Flask(__name__)
|
|
21
|
+
init_logify(app, service_name="myapp")
|
|
22
|
+
"""
|
|
23
|
+
configure_logging(service_name=service_name)
|
|
24
|
+
|
|
25
|
+
@app.before_request
|
|
26
|
+
def before_request():
|
|
27
|
+
# Generate request ID and set context
|
|
28
|
+
request_id = generate_request_id()
|
|
29
|
+
g.logify_request_id = request_id
|
|
30
|
+
g.logify_start_time = time.time()
|
|
31
|
+
|
|
32
|
+
set_request_context(
|
|
33
|
+
request_id=request_id,
|
|
34
|
+
method=request.method,
|
|
35
|
+
path=request.path,
|
|
36
|
+
query_string=request.query_string.decode() if request.query_string else None,
|
|
37
|
+
remote_addr=request.remote_addr,
|
|
38
|
+
user_agent=request.headers.get("User-Agent", ""),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if log_requests:
|
|
42
|
+
info("Request started", method=request.method, path=request.path, request_id=request_id)
|
|
43
|
+
|
|
44
|
+
@app.after_request
|
|
45
|
+
def after_request(response):
|
|
46
|
+
try:
|
|
47
|
+
# Calculate duration
|
|
48
|
+
duration = time.time() - g.logify_start_time
|
|
49
|
+
|
|
50
|
+
# Add response info to context
|
|
51
|
+
set_request_context(
|
|
52
|
+
status_code=response.status_code, duration_seconds=duration, content_length=response.content_length
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if log_requests:
|
|
56
|
+
info(
|
|
57
|
+
"Request completed",
|
|
58
|
+
status_code=response.status_code,
|
|
59
|
+
duration_seconds=duration,
|
|
60
|
+
request_id=g.logify_request_id,
|
|
61
|
+
)
|
|
62
|
+
except Exception:
|
|
63
|
+
# If we can't access g, just return the response
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
return response
|
|
67
|
+
|
|
68
|
+
def teardown_request(exception):
|
|
69
|
+
# Clear request context
|
|
70
|
+
clear_request_context()
|
|
71
|
+
|
|
72
|
+
if exception and log_requests:
|
|
73
|
+
error(
|
|
74
|
+
"Request failed",
|
|
75
|
+
error=exception,
|
|
76
|
+
request_id=getattr(g, "logify_request_id", "unknown"),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Register teardown function
|
|
80
|
+
app.teardown_appcontext(teardown_request)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def setup_flask_logging(service_name: str = "flask"):
|
|
84
|
+
"""
|
|
85
|
+
Set up Flask logging with json-logify.
|
|
86
|
+
Call this when creating your Flask app.
|
|
87
|
+
"""
|
|
88
|
+
configure_logging(service_name=service_name)
|
logify/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|