mtk-logger 0.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,57 @@
1
+ Metadata-Version: 2.3
2
+ Name: mtk_logger
3
+ Version: 0.3.0
4
+ Summary: Logging utilities for Python applications
5
+ Author: Patrick Prasquier
6
+ Author-email: pprasquier@odm-tech.com
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Requires-Dist: fastapi[all] (>=0.115.0,<0.116.0)
12
+ Requires-Dist: prefy (>=1.4.1,<2.0.0)
13
+ Project-URL: Repository, https://github.com/pprasquier/mtk_logger
14
+ Description-Content-Type: text/markdown
15
+
16
+ # MTK Logger
17
+
18
+ Logging utilities for MarkTrack applications.
19
+
20
+ ## Installation
21
+
22
+ To install from GitHub Packages:
23
+
24
+ ```bash
25
+ # Configure pip to use GitHub Packages
26
+ pip config set global.extra-index-url https://YOUR_GITHUB_TOKEN@github.com/pprasquier/mtk_logger/raw/main/dist/
27
+
28
+ # Install the package
29
+ pip install mtk_logger
30
+ ```
31
+
32
+ Or with Poetry:
33
+
34
+ ```bash
35
+ # Add GitHub Packages as a source
36
+ poetry source add --priority=supplemental github https://github.com/pprasquier/mtk_logger/raw/main/dist/
37
+
38
+ # Install the package
39
+ poetry add mtk_logger
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ```python
45
+ from mtk_logger.logger import get_logger
46
+
47
+ logger = get_logger(__name__)
48
+ logger.info("This is a log message")
49
+ ```
50
+
51
+ For FastAPI middleware:
52
+
53
+ ```python
54
+ from mtk_logger.fastapi_middleware import RequestLoggingMiddleware
55
+
56
+ app.add_middleware(RequestLoggingMiddleware)
57
+ ```
@@ -0,0 +1,42 @@
1
+ # MTK Logger
2
+
3
+ Logging utilities for MarkTrack applications.
4
+
5
+ ## Installation
6
+
7
+ To install from GitHub Packages:
8
+
9
+ ```bash
10
+ # Configure pip to use GitHub Packages
11
+ pip config set global.extra-index-url https://YOUR_GITHUB_TOKEN@github.com/pprasquier/mtk_logger/raw/main/dist/
12
+
13
+ # Install the package
14
+ pip install mtk_logger
15
+ ```
16
+
17
+ Or with Poetry:
18
+
19
+ ```bash
20
+ # Add GitHub Packages as a source
21
+ poetry source add --priority=supplemental github https://github.com/pprasquier/mtk_logger/raw/main/dist/
22
+
23
+ # Install the package
24
+ poetry add mtk_logger
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from mtk_logger.logger import get_logger
31
+
32
+ logger = get_logger(__name__)
33
+ logger.info("This is a log message")
34
+ ```
35
+
36
+ For FastAPI middleware:
37
+
38
+ ```python
39
+ from mtk_logger.fastapi_middleware import RequestLoggingMiddleware
40
+
41
+ app.add_middleware(RequestLoggingMiddleware)
42
+ ```
@@ -0,0 +1,17 @@
1
+ [tool.poetry]
2
+ name = "mtk_logger"
3
+ version = "0.3.0"
4
+ description = "Logging utilities for Python applications"
5
+ authors = ["Patrick Prasquier <pprasquier@odm-tech.com>"]
6
+ readme = "README.md"
7
+ packages = [{include = "mtk_logger", from = "src"}]
8
+ repository = "https://github.com/pprasquier/mtk_logger"
9
+
10
+ [tool.poetry.dependencies]
11
+ python = "^3.12"
12
+ fastapi = {extras = ["all"], version = "^0.115.0"}
13
+ prefy = "^1.4.1"
14
+
15
+ [build-system]
16
+ requires = ["poetry-core"]
17
+ build-backend = "poetry.core.masonry.api"
File without changes
@@ -0,0 +1,103 @@
1
+ import uuid
2
+ import json
3
+ import os
4
+ from starlette.middleware.base import BaseHTTPMiddleware
5
+ from typing import Callable
6
+ from starlette.responses import StreamingResponse
7
+ from fastapi import Request
8
+ import time
9
+ from .logger import get_logger
10
+
11
+ class RequestLoggingMiddleware(BaseHTTPMiddleware):
12
+ async def dispatch(self, request: Request, call_next: Callable):
13
+ # Get request details
14
+ request_id = str(uuid.uuid4())
15
+ start_time = time.time()
16
+ logger = get_logger(os.path.basename(__file__))
17
+
18
+ # Extract path and method
19
+ path = request.url.path
20
+ method = request.method
21
+ logger.debug(f'("Request: {request_id} - {method} {path}")')
22
+
23
+ # Try to extract query parameters
24
+ # Convert query_params to dict, preserving lists for repeated keys
25
+ query_params = {}
26
+ for key, value in request.query_params.multi_items():
27
+ if key in query_params:
28
+ logger.debug(f"Duplicate key '{key}' found in query parameters")
29
+ # If the key exists but its value is not a list yet, convert it to a list
30
+ if not isinstance(query_params[key], list):
31
+ query_params[key] = [query_params[key]]
32
+ # Now we can safely append the new value
33
+ query_params[key].append(value)
34
+ else:
35
+ query_params[key] = value
36
+ logger.debug(f"Added key '{key}' to query parameters")
37
+
38
+ # Try to extract request body (this is tricky because we can only read it once)
39
+ body = None
40
+ if method in ["POST", "PUT", "PATCH"]:
41
+ try:
42
+ # We need to read the body and then restore it for the actual endpoint
43
+ body_bytes = await request.body()
44
+ # Restore the request body
45
+ request._body = body_bytes
46
+
47
+ # Try to parse as JSON, but handle non-JSON bodies gracefully
48
+ try:
49
+ body = json.loads(body_bytes.decode())
50
+ # For large payloads, consider truncating or summarizing
51
+ if isinstance(body, dict):
52
+ # For each list in the body, replace with count and sample
53
+ for key, value in body.items():
54
+ if isinstance(value, list) and len(value) > 5:
55
+ sample = value[:3] # Take first 3 items as sample
56
+ body[key] = {
57
+ "count": len(value),
58
+ "sample": sample
59
+ }
60
+ except:
61
+ body = {"raw_size": len(body_bytes), "note": "Non-JSON body"}
62
+ except:
63
+ body = {"note": "Could not read request body"}
64
+
65
+ # Log the request
66
+ log_data = {
67
+ "request_id": request_id,
68
+ "method": method,
69
+ "path": path,
70
+ "query_params": query_params,
71
+ "body": body
72
+ }
73
+
74
+ logger.info(f"API Request: {json.dumps(log_data)}")
75
+
76
+ # Process the request
77
+ response = await call_next(request)
78
+
79
+ # Read and log the response body
80
+ response_body = b""
81
+ async for chunk in response.body_iterator:
82
+ response_body += chunk
83
+
84
+ # Decode the response body
85
+ responses = response_body.decode()
86
+
87
+ # Try to parse the response body as JSON and count the records
88
+ try:
89
+ response_data = json.loads(responses)
90
+ if isinstance(response_data, list):
91
+ record_count = len(response_data)
92
+ else:
93
+ record_count = 1 # If it's not a list, assume it's a single record
94
+ except json.JSONDecodeError:
95
+ record_count = 0 # If the response is not JSON, set record count to 0
96
+
97
+ # Log response details
98
+ process_time = time.time() - start_time
99
+ logger.info(f"Request {request_id} completed in {process_time:.4f}s with status {response.status_code} and {record_count} records")
100
+ logger.debug(f'Request {request_id} response= {responses}')
101
+
102
+ # Return a new StreamingResponse with the original response body
103
+ return StreamingResponse(iter([response_body]), status_code=response.status_code, headers=dict(response.headers))
@@ -0,0 +1,55 @@
1
+ import os
2
+ import sys
3
+ import logging
4
+ from logging.handlers import TimedRotatingFileHandler
5
+ from prefy import Preferences
6
+
7
+ ### BE SURE TO ADD log_file_name and app_name settings. Cf. settings_example.json
8
+
9
+ class MTKLogger(logging.Logger):
10
+ def __init__(self, script_name=None, backup_count=7):
11
+ settings = Preferences()
12
+
13
+ log_file_name=settings.log_file_name
14
+ app_name=settings.app_name
15
+
16
+ # Set up base logger
17
+ super().__init__(app_name)
18
+ preferences_level=settings.log_level
19
+ level = getattr(logging, preferences_level, logging.DEBUG)
20
+ self.setLevel(level)
21
+
22
+ # Common log file for the day
23
+ log_filename = os.path.join(settings.log_dir, f'{log_file_name}.log')
24
+
25
+ # Script name for contextual logging
26
+ script_id = script_name or os.path.basename(sys.argv[0])
27
+
28
+ # Custom formatter including script name
29
+ formatter = logging.Formatter(
30
+ f'%(asctime)s - %(name)s - %(levelname)s - {script_id} - [Line %(lineno)d] - %(message)s'
31
+ )
32
+
33
+ # Timed rotating file handler (daily, retain N days)
34
+ file_handler = TimedRotatingFileHandler(
35
+ filename=log_filename,
36
+ when='midnight',
37
+ interval=1,
38
+ backupCount=backup_count,
39
+ encoding='utf-8',
40
+ utc=False # Use True if you want UTC timestamps
41
+ )
42
+ file_handler.setLevel(logging.DEBUG)
43
+ file_handler.setFormatter(formatter)
44
+ self.addHandler(file_handler)
45
+
46
+ # Console handler
47
+ console_handler = logging.StreamHandler(sys.stdout)
48
+ console_handler.setLevel(logging.DEBUG)
49
+ console_handler.setFormatter(formatter)
50
+ self.addHandler(console_handler)
51
+
52
+ self.debug("Logging initialized for script: %s, daily rotation enabled, logs in: %s", script_id, log_filename)
53
+
54
+ def get_logger(script_name=None, backup_count=7):
55
+ return MTKLogger(script_name, backup_count)