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.
@@ -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
+ [![PyPI version](https://badge.fury.io/py/json-logify.svg)](https://badge.fury.io/py/json-logify)
81
+ [![Python Support](https://img.shields.io/pypi/pyversions/json-logify.svg)](https://pypi.org/project/json-logify/)
82
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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"