pyeztrace 0.0.1__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.
- pyeztrace-0.0.1/LICENSE +21 -0
- pyeztrace-0.0.1/PKG-INFO +248 -0
- pyeztrace-0.0.1/README.md +222 -0
- pyeztrace-0.0.1/pyeztrace/__init__.py +0 -0
- pyeztrace-0.0.1/pyeztrace/cli.py +177 -0
- pyeztrace-0.0.1/pyeztrace/config.py +24 -0
- pyeztrace-0.0.1/pyeztrace/custom_logging.py +308 -0
- pyeztrace-0.0.1/pyeztrace/exceptions.py +11 -0
- pyeztrace-0.0.1/pyeztrace/setup.py +122 -0
- pyeztrace-0.0.1/pyeztrace/tracer.py +261 -0
- pyeztrace-0.0.1/pyeztrace.egg-info/PKG-INFO +248 -0
- pyeztrace-0.0.1/pyeztrace.egg-info/SOURCES.txt +20 -0
- pyeztrace-0.0.1/pyeztrace.egg-info/dependency_links.txt +1 -0
- pyeztrace-0.0.1/pyeztrace.egg-info/requires.txt +2 -0
- pyeztrace-0.0.1/pyeztrace.egg-info/top_level.txt +1 -0
- pyeztrace-0.0.1/pyproject.toml +32 -0
- pyeztrace-0.0.1/setup.cfg +4 -0
- pyeztrace-0.0.1/setup.py +11 -0
- pyeztrace-0.0.1/tests/test_custom_logging.py +168 -0
- pyeztrace-0.0.1/tests/test_exception.py +8 -0
- pyeztrace-0.0.1/tests/test_setup.py +25 -0
- pyeztrace-0.0.1/tests/test_tracer.py +58 -0
pyeztrace-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jefferson Nelsson
|
|
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.
|
pyeztrace-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyeztrace
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A powerful, lightweight Python tracing and logging library
|
|
5
|
+
Author: Jefferson Nelsson
|
|
6
|
+
Author-email: Jefferson Nelsson <jefferson.nelsson@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: logging,tracing,monitoring,debugging,performance
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Requires-Python: >=3.7
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: pydantic-core>=2.33.2
|
|
22
|
+
Requires-Dist: pydantic-settings>=2.9.1
|
|
23
|
+
Dynamic: author
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
|
|
27
|
+
# PyEzTrace
|
|
28
|
+
|
|
29
|
+
A powerful, lightweight Python tracing and logging library with hierarchical logging, context management, and performance metrics.
|
|
30
|
+
|
|
31
|
+
**Note:** This was a fun, 3 hour experiment for one of my local projects. I'm happy to continue building this out if the community finds it useful and thinks it's a good idea!
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- 🌳 **Hierarchical Logging**: Visualize nested operations with tree-style output
|
|
36
|
+
- 🎨 **Multiple Formats**: Support for color, plain text, JSON, CSV, and logfmt outputs
|
|
37
|
+
- 📊 **Performance Metrics**: Built-in timing and tracing capabilities
|
|
38
|
+
- 🔄 **Context Management**: Thread-safe context propagation
|
|
39
|
+
- 🔄 **Log Rotation**: Automatic log file management
|
|
40
|
+
- 🎯 **Decorator-based Tracing**: Easy function and method tracing
|
|
41
|
+
- 💪 **Thread-Safe**: Fully thread-safe implementation
|
|
42
|
+
- 🚀 **High Performance**: Buffered logging and optimized output
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install pyeztrace
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from pyeztrace.setup import Setup
|
|
54
|
+
Setup.initialize("MyApp") # Setup First! Make sure you initialize (optional) with your App Name before importing other modules. Default: EZTRACE
|
|
55
|
+
|
|
56
|
+
from pyeztrace.tracer import trace
|
|
57
|
+
from pyeztrace.custom_logging import Logging
|
|
58
|
+
|
|
59
|
+
# Initialize the logging system
|
|
60
|
+
log = Logging(log_format="color") # or "json", "plain", "csv", "logfmt"
|
|
61
|
+
|
|
62
|
+
# Use the tracer decorator
|
|
63
|
+
@trace()
|
|
64
|
+
def process_order(order_id):
|
|
65
|
+
with log.with_context(order_id=order_id):
|
|
66
|
+
log.log_info("Processing order")
|
|
67
|
+
validate_order(order_id)
|
|
68
|
+
process_payment(order_id)
|
|
69
|
+
log.log_info("Order processed successfully")
|
|
70
|
+
|
|
71
|
+
@trace()
|
|
72
|
+
def validate_order(order_id):
|
|
73
|
+
log.log_info("Validating order")
|
|
74
|
+
# Your validation logic here
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Output example:
|
|
78
|
+
```
|
|
79
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├── process_order called...
|
|
80
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├── Processing order Data: {order_id: "123"}
|
|
81
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├────── validate_order called...
|
|
82
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├────── Validating order
|
|
83
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├────── validate_order Ok. (took 0.50010 seconds)
|
|
84
|
+
2025-05-13T10:00:01 - INFO - [MyApp] ├── Order processed successfully
|
|
85
|
+
2025-05-13T10:00:01 - INFO - [MyApp] ├── process_order Ok. (took 1.23456 seconds)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Features
|
|
89
|
+
|
|
90
|
+
### 1. Tracing with Fine-grained Control
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
@trace(
|
|
94
|
+
message="Custom trace message", # Optional custom message
|
|
95
|
+
stack=True, # Include stack trace on errors
|
|
96
|
+
modules_or_classes=[my_module], # Trace specific modules
|
|
97
|
+
include=["specific_function_*"], # Include only specific functions
|
|
98
|
+
exclude=["ignored_function_*"] # Exclude specific functions
|
|
99
|
+
)
|
|
100
|
+
"""
|
|
101
|
+
Decorator for parent function. Enables tracing for all child functions in the given modules or classes.
|
|
102
|
+
If modules_or_classes is None, it will automatically patch the module where the parent function is defined.
|
|
103
|
+
Accepts a single module/class or a list of modules/classes for cross-module tracing.
|
|
104
|
+
Handles both sync and async parent functions.
|
|
105
|
+
Supports selective tracing via include/exclude patterns (function names).
|
|
106
|
+
"""
|
|
107
|
+
def function():
|
|
108
|
+
# Your code here
|
|
109
|
+
pass
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 2. Context Management
|
|
113
|
+
|
|
114
|
+
Thread-safe context propagation for structured logging:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
with log.with_context(user_id="123", action="login"):
|
|
118
|
+
log.log_info("User logged in") # Will include context automatically
|
|
119
|
+
|
|
120
|
+
with log.with_context(session="abc"):
|
|
121
|
+
# Nested context, inherits parent context
|
|
122
|
+
log.log_info("Session started") # Includes both user_id and session
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 3. Multiple Output Formats
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
# Color-coded console output with hierarchical visualization
|
|
129
|
+
log = Logging(log_format="color")
|
|
130
|
+
|
|
131
|
+
# JSON format for machine processing
|
|
132
|
+
log = Logging(log_format="json")
|
|
133
|
+
# Output: {"timestamp": "2025-05-13T10:00:00", "level": "INFO", "message": "Log message", "data": {"context": "value"}}
|
|
134
|
+
|
|
135
|
+
# Plain text for simple logging
|
|
136
|
+
log = Logging(log_format="plain")
|
|
137
|
+
|
|
138
|
+
# CSV format for spreadsheet analysis
|
|
139
|
+
log = Logging(log_format="csv")
|
|
140
|
+
|
|
141
|
+
# logfmt for system logging
|
|
142
|
+
log = Logging(log_format="logfmt")
|
|
143
|
+
# Output: time=2025-05-13T10:00:00 level=INFO message="Log message" data.context=value
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 4. Async Support
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
@trace()
|
|
150
|
+
async def async_function():
|
|
151
|
+
await some_async_task()
|
|
152
|
+
log.log_info("Async operation completed")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 5. Performance Metrics
|
|
156
|
+
|
|
157
|
+
Automatic performance tracking is enabled with `show_metrics=True`:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
Setup.initialize("MyApp", show_metrics=True)
|
|
161
|
+
|
|
162
|
+
@trace()
|
|
163
|
+
def monitored_function():
|
|
164
|
+
# Function execution time will be automatically logged
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
# At program exit, prints performance summary:
|
|
168
|
+
# === Tracing Performance Metrics Summary ===
|
|
169
|
+
# Function Calls Total(s) Avg(s)
|
|
170
|
+
# --------------------------------------------------------------------
|
|
171
|
+
# my_module.monitored_function 10 1.23456 0.12346
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 6. Log Rotation
|
|
175
|
+
|
|
176
|
+
Configure automatic log rotation based on file size:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from pyeztrace.config import config
|
|
180
|
+
|
|
181
|
+
config.max_size = 10 * 1024 * 1024 # 10MB
|
|
182
|
+
config.backup_count = 5 # Keep 5 backup files
|
|
183
|
+
config.log_dir = "logs" # Custom log directory
|
|
184
|
+
config.log_file = "app.log" # Custom log filename
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 7. Error Handling and Debug Support
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# Different log levels
|
|
191
|
+
log.log_debug("Debug information")
|
|
192
|
+
log.log_info("Normal operation")
|
|
193
|
+
log.log_warning("Warning message")
|
|
194
|
+
log.log_error("Error occurred")
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
# Your code
|
|
198
|
+
except Exception as e:
|
|
199
|
+
# Automatically log exception with stack trace
|
|
200
|
+
log.raise_exception_to_log(e, "Custom error message", stack=True)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 8. Thread-Safe High-Volume Logging
|
|
204
|
+
|
|
205
|
+
The logging system is designed for high-volume scenarios with thread-safe implementation:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
209
|
+
|
|
210
|
+
@trace()
|
|
211
|
+
def concurrent_operation(worker_id):
|
|
212
|
+
with log.with_context(worker_id=worker_id):
|
|
213
|
+
log.log_info("Worker started")
|
|
214
|
+
# ... work ...
|
|
215
|
+
log.log_info("Worker finished")
|
|
216
|
+
|
|
217
|
+
with ThreadPoolExecutor(max_workers=5) as executor:
|
|
218
|
+
executor.map(concurrent_operation, range(5))
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Configuration
|
|
222
|
+
|
|
223
|
+
All configuration options can be set via environment variables or code:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
# Via environment variables
|
|
227
|
+
export EZTRACE_LOG_FORMAT="json"
|
|
228
|
+
export EZTRACE_LOG_LEVEL="DEBUG"
|
|
229
|
+
export EZTRACE_LOG_FILE="custom.log"
|
|
230
|
+
export EZTRACE_MAX_SIZE="10485760" # 10MB
|
|
231
|
+
export EZTRACE_BACKUP_COUNT="5"
|
|
232
|
+
|
|
233
|
+
# Via code
|
|
234
|
+
from pyeztrace.config import config
|
|
235
|
+
config.format = "json"
|
|
236
|
+
config.log_level = "DEBUG"
|
|
237
|
+
config.log_file = "custom.log"
|
|
238
|
+
config.max_size = 10 * 1024 * 1024 # 10MB
|
|
239
|
+
config.backup_count = 5
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Contributing
|
|
243
|
+
|
|
244
|
+
Contributions are welcome! Please read our [Contributing Guidelines](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# PyEzTrace
|
|
2
|
+
|
|
3
|
+
A powerful, lightweight Python tracing and logging library with hierarchical logging, context management, and performance metrics.
|
|
4
|
+
|
|
5
|
+
**Note:** This was a fun, 3 hour experiment for one of my local projects. I'm happy to continue building this out if the community finds it useful and thinks it's a good idea!
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🌳 **Hierarchical Logging**: Visualize nested operations with tree-style output
|
|
10
|
+
- 🎨 **Multiple Formats**: Support for color, plain text, JSON, CSV, and logfmt outputs
|
|
11
|
+
- 📊 **Performance Metrics**: Built-in timing and tracing capabilities
|
|
12
|
+
- 🔄 **Context Management**: Thread-safe context propagation
|
|
13
|
+
- 🔄 **Log Rotation**: Automatic log file management
|
|
14
|
+
- 🎯 **Decorator-based Tracing**: Easy function and method tracing
|
|
15
|
+
- 💪 **Thread-Safe**: Fully thread-safe implementation
|
|
16
|
+
- 🚀 **High Performance**: Buffered logging and optimized output
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install pyeztrace
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from pyeztrace.setup import Setup
|
|
28
|
+
Setup.initialize("MyApp") # Setup First! Make sure you initialize (optional) with your App Name before importing other modules. Default: EZTRACE
|
|
29
|
+
|
|
30
|
+
from pyeztrace.tracer import trace
|
|
31
|
+
from pyeztrace.custom_logging import Logging
|
|
32
|
+
|
|
33
|
+
# Initialize the logging system
|
|
34
|
+
log = Logging(log_format="color") # or "json", "plain", "csv", "logfmt"
|
|
35
|
+
|
|
36
|
+
# Use the tracer decorator
|
|
37
|
+
@trace()
|
|
38
|
+
def process_order(order_id):
|
|
39
|
+
with log.with_context(order_id=order_id):
|
|
40
|
+
log.log_info("Processing order")
|
|
41
|
+
validate_order(order_id)
|
|
42
|
+
process_payment(order_id)
|
|
43
|
+
log.log_info("Order processed successfully")
|
|
44
|
+
|
|
45
|
+
@trace()
|
|
46
|
+
def validate_order(order_id):
|
|
47
|
+
log.log_info("Validating order")
|
|
48
|
+
# Your validation logic here
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Output example:
|
|
52
|
+
```
|
|
53
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├── process_order called...
|
|
54
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├── Processing order Data: {order_id: "123"}
|
|
55
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├────── validate_order called...
|
|
56
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├────── Validating order
|
|
57
|
+
2025-05-13T10:00:00 - INFO - [MyApp] ├────── validate_order Ok. (took 0.50010 seconds)
|
|
58
|
+
2025-05-13T10:00:01 - INFO - [MyApp] ├── Order processed successfully
|
|
59
|
+
2025-05-13T10:00:01 - INFO - [MyApp] ├── process_order Ok. (took 1.23456 seconds)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
### 1. Tracing with Fine-grained Control
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
@trace(
|
|
68
|
+
message="Custom trace message", # Optional custom message
|
|
69
|
+
stack=True, # Include stack trace on errors
|
|
70
|
+
modules_or_classes=[my_module], # Trace specific modules
|
|
71
|
+
include=["specific_function_*"], # Include only specific functions
|
|
72
|
+
exclude=["ignored_function_*"] # Exclude specific functions
|
|
73
|
+
)
|
|
74
|
+
"""
|
|
75
|
+
Decorator for parent function. Enables tracing for all child functions in the given modules or classes.
|
|
76
|
+
If modules_or_classes is None, it will automatically patch the module where the parent function is defined.
|
|
77
|
+
Accepts a single module/class or a list of modules/classes for cross-module tracing.
|
|
78
|
+
Handles both sync and async parent functions.
|
|
79
|
+
Supports selective tracing via include/exclude patterns (function names).
|
|
80
|
+
"""
|
|
81
|
+
def function():
|
|
82
|
+
# Your code here
|
|
83
|
+
pass
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 2. Context Management
|
|
87
|
+
|
|
88
|
+
Thread-safe context propagation for structured logging:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
with log.with_context(user_id="123", action="login"):
|
|
92
|
+
log.log_info("User logged in") # Will include context automatically
|
|
93
|
+
|
|
94
|
+
with log.with_context(session="abc"):
|
|
95
|
+
# Nested context, inherits parent context
|
|
96
|
+
log.log_info("Session started") # Includes both user_id and session
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3. Multiple Output Formats
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# Color-coded console output with hierarchical visualization
|
|
103
|
+
log = Logging(log_format="color")
|
|
104
|
+
|
|
105
|
+
# JSON format for machine processing
|
|
106
|
+
log = Logging(log_format="json")
|
|
107
|
+
# Output: {"timestamp": "2025-05-13T10:00:00", "level": "INFO", "message": "Log message", "data": {"context": "value"}}
|
|
108
|
+
|
|
109
|
+
# Plain text for simple logging
|
|
110
|
+
log = Logging(log_format="plain")
|
|
111
|
+
|
|
112
|
+
# CSV format for spreadsheet analysis
|
|
113
|
+
log = Logging(log_format="csv")
|
|
114
|
+
|
|
115
|
+
# logfmt for system logging
|
|
116
|
+
log = Logging(log_format="logfmt")
|
|
117
|
+
# Output: time=2025-05-13T10:00:00 level=INFO message="Log message" data.context=value
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 4. Async Support
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
@trace()
|
|
124
|
+
async def async_function():
|
|
125
|
+
await some_async_task()
|
|
126
|
+
log.log_info("Async operation completed")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 5. Performance Metrics
|
|
130
|
+
|
|
131
|
+
Automatic performance tracking is enabled with `show_metrics=True`:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
Setup.initialize("MyApp", show_metrics=True)
|
|
135
|
+
|
|
136
|
+
@trace()
|
|
137
|
+
def monitored_function():
|
|
138
|
+
# Function execution time will be automatically logged
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
# At program exit, prints performance summary:
|
|
142
|
+
# === Tracing Performance Metrics Summary ===
|
|
143
|
+
# Function Calls Total(s) Avg(s)
|
|
144
|
+
# --------------------------------------------------------------------
|
|
145
|
+
# my_module.monitored_function 10 1.23456 0.12346
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 6. Log Rotation
|
|
149
|
+
|
|
150
|
+
Configure automatic log rotation based on file size:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from pyeztrace.config import config
|
|
154
|
+
|
|
155
|
+
config.max_size = 10 * 1024 * 1024 # 10MB
|
|
156
|
+
config.backup_count = 5 # Keep 5 backup files
|
|
157
|
+
config.log_dir = "logs" # Custom log directory
|
|
158
|
+
config.log_file = "app.log" # Custom log filename
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 7. Error Handling and Debug Support
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# Different log levels
|
|
165
|
+
log.log_debug("Debug information")
|
|
166
|
+
log.log_info("Normal operation")
|
|
167
|
+
log.log_warning("Warning message")
|
|
168
|
+
log.log_error("Error occurred")
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
# Your code
|
|
172
|
+
except Exception as e:
|
|
173
|
+
# Automatically log exception with stack trace
|
|
174
|
+
log.raise_exception_to_log(e, "Custom error message", stack=True)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 8. Thread-Safe High-Volume Logging
|
|
178
|
+
|
|
179
|
+
The logging system is designed for high-volume scenarios with thread-safe implementation:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
183
|
+
|
|
184
|
+
@trace()
|
|
185
|
+
def concurrent_operation(worker_id):
|
|
186
|
+
with log.with_context(worker_id=worker_id):
|
|
187
|
+
log.log_info("Worker started")
|
|
188
|
+
# ... work ...
|
|
189
|
+
log.log_info("Worker finished")
|
|
190
|
+
|
|
191
|
+
with ThreadPoolExecutor(max_workers=5) as executor:
|
|
192
|
+
executor.map(concurrent_operation, range(5))
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Configuration
|
|
196
|
+
|
|
197
|
+
All configuration options can be set via environment variables or code:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
# Via environment variables
|
|
201
|
+
export EZTRACE_LOG_FORMAT="json"
|
|
202
|
+
export EZTRACE_LOG_LEVEL="DEBUG"
|
|
203
|
+
export EZTRACE_LOG_FILE="custom.log"
|
|
204
|
+
export EZTRACE_MAX_SIZE="10485760" # 10MB
|
|
205
|
+
export EZTRACE_BACKUP_COUNT="5"
|
|
206
|
+
|
|
207
|
+
# Via code
|
|
208
|
+
from pyeztrace.config import config
|
|
209
|
+
config.format = "json"
|
|
210
|
+
config.log_level = "DEBUG"
|
|
211
|
+
config.log_file = "custom.log"
|
|
212
|
+
config.max_size = 10 * 1024 * 1024 # 10MB
|
|
213
|
+
config.backup_count = 5
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Contributing
|
|
217
|
+
|
|
218
|
+
Contributions are welcome! Please read our [Contributing Guidelines](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
File without changes
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Command-line interface for PyEzTrace log analysis."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
class LogAnalyzer:
|
|
12
|
+
def __init__(self, log_file: Path):
|
|
13
|
+
self.log_file = log_file
|
|
14
|
+
|
|
15
|
+
def parse_logs(self, filter_level: Optional[str] = None,
|
|
16
|
+
since: Optional[datetime] = None,
|
|
17
|
+
until: Optional[datetime] = None,
|
|
18
|
+
context: Optional[dict] = None) -> List[dict]:
|
|
19
|
+
"""Parse and filter log entries."""
|
|
20
|
+
entries = []
|
|
21
|
+
|
|
22
|
+
with open(self.log_file, 'r') as f:
|
|
23
|
+
for line in f:
|
|
24
|
+
try:
|
|
25
|
+
entry = self._parse_line(line.strip())
|
|
26
|
+
if self._should_include(entry, filter_level, since, until, context):
|
|
27
|
+
entries.append(entry)
|
|
28
|
+
except:
|
|
29
|
+
continue # Skip invalid lines
|
|
30
|
+
|
|
31
|
+
return entries
|
|
32
|
+
|
|
33
|
+
def analyze_performance(self, function_name: Optional[str] = None) -> dict:
|
|
34
|
+
"""Analyze performance metrics from logs."""
|
|
35
|
+
metrics = {}
|
|
36
|
+
entries = self.parse_logs()
|
|
37
|
+
|
|
38
|
+
for entry in entries:
|
|
39
|
+
if 'duration' not in entry:
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
func = entry.get('function', 'unknown')
|
|
43
|
+
if function_name and func != function_name:
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
if func not in metrics:
|
|
47
|
+
metrics[func] = {
|
|
48
|
+
'count': 0,
|
|
49
|
+
'total_time': 0,
|
|
50
|
+
'min_time': float('inf'),
|
|
51
|
+
'max_time': 0,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
m = metrics[func]
|
|
55
|
+
duration = float(entry['duration'])
|
|
56
|
+
m['count'] += 1
|
|
57
|
+
m['total_time'] += duration
|
|
58
|
+
m['min_time'] = min(m['min_time'], duration)
|
|
59
|
+
m['max_time'] = max(m['max_time'], duration)
|
|
60
|
+
|
|
61
|
+
# Calculate averages
|
|
62
|
+
for m in metrics.values():
|
|
63
|
+
m['avg_time'] = m['total_time'] / m['count']
|
|
64
|
+
|
|
65
|
+
return metrics
|
|
66
|
+
|
|
67
|
+
def find_errors(self, since: Optional[datetime] = None) -> List[dict]:
|
|
68
|
+
"""Find error entries in logs."""
|
|
69
|
+
return self.parse_logs(filter_level="ERROR", since=since)
|
|
70
|
+
|
|
71
|
+
def _parse_line(self, line: str) -> dict:
|
|
72
|
+
"""Parse a single log line."""
|
|
73
|
+
try:
|
|
74
|
+
# Try JSON format first
|
|
75
|
+
return json.loads(line)
|
|
76
|
+
except:
|
|
77
|
+
# Fall back to parsing other formats
|
|
78
|
+
return self._parse_plain_format(line)
|
|
79
|
+
|
|
80
|
+
def _parse_plain_format(self, line: str) -> dict:
|
|
81
|
+
"""Parse plain text format."""
|
|
82
|
+
pattern = r'(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}) - (\w+) - \[([^\]]+)\](.*)'
|
|
83
|
+
match = re.match(pattern, line)
|
|
84
|
+
if not match:
|
|
85
|
+
raise ValueError("Invalid log format")
|
|
86
|
+
|
|
87
|
+
timestamp, level, project, rest = match.groups()
|
|
88
|
+
return {
|
|
89
|
+
'timestamp': timestamp,
|
|
90
|
+
'level': level,
|
|
91
|
+
'project': project,
|
|
92
|
+
'message': rest.strip()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
def _should_include(self, entry: dict,
|
|
96
|
+
filter_level: Optional[str] = None,
|
|
97
|
+
since: Optional[datetime] = None,
|
|
98
|
+
until: Optional[datetime] = None,
|
|
99
|
+
context: Optional[dict] = None) -> bool:
|
|
100
|
+
"""Check if log entry matches filters."""
|
|
101
|
+
if filter_level and entry.get('level') != filter_level:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
timestamp = datetime.fromisoformat(entry['timestamp'])
|
|
105
|
+
if since and timestamp < since:
|
|
106
|
+
return False
|
|
107
|
+
if until and timestamp > until:
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
if context:
|
|
111
|
+
entry_context = entry.get('data', {})
|
|
112
|
+
return all(entry_context.get(k) == v for k, v in context.items())
|
|
113
|
+
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
def main():
|
|
117
|
+
parser = argparse.ArgumentParser(description="PyEzTrace Log Analyzer")
|
|
118
|
+
parser.add_argument('log_file', type=Path, help="Path to log file")
|
|
119
|
+
parser.add_argument('--level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
|
|
120
|
+
help="Filter by log level")
|
|
121
|
+
parser.add_argument('--since', type=str, help="Show logs since (YYYY-MM-DD[THH:MM:SS])")
|
|
122
|
+
parser.add_argument('--until', type=str, help="Show logs until (YYYY-MM-DD[THH:MM:SS])")
|
|
123
|
+
parser.add_argument('--context', type=str, help="Filter by context (key=value[,key=value])")
|
|
124
|
+
parser.add_argument('--analyze', action='store_true', help="Show performance metrics")
|
|
125
|
+
parser.add_argument('--function', type=str, help="Analyze specific function")
|
|
126
|
+
parser.add_argument('--errors', action='store_true', help="Show only errors")
|
|
127
|
+
parser.add_argument('--format', choices=['text', 'json'], default='text',
|
|
128
|
+
help="Output format")
|
|
129
|
+
|
|
130
|
+
args = parser.parse_args()
|
|
131
|
+
|
|
132
|
+
# Parse datetime arguments
|
|
133
|
+
since = datetime.fromisoformat(args.since) if args.since else None
|
|
134
|
+
until = datetime.fromisoformat(args.until) if args.until else None
|
|
135
|
+
|
|
136
|
+
# Parse context filters
|
|
137
|
+
context = {}
|
|
138
|
+
if args.context:
|
|
139
|
+
for pair in args.context.split(','):
|
|
140
|
+
key, value = pair.split('=')
|
|
141
|
+
context[key.strip()] = value.strip()
|
|
142
|
+
|
|
143
|
+
analyzer = LogAnalyzer(args.log_file)
|
|
144
|
+
|
|
145
|
+
if args.analyze:
|
|
146
|
+
metrics = analyzer.analyze_performance(args.function)
|
|
147
|
+
if args.format == 'json':
|
|
148
|
+
print(json.dumps(metrics, indent=2))
|
|
149
|
+
else:
|
|
150
|
+
for func, m in metrics.items():
|
|
151
|
+
print(f"\nFunction: {func}")
|
|
152
|
+
print(f" Calls: {m['count']}")
|
|
153
|
+
print(f" Total: {m['total_time']:.3f}s")
|
|
154
|
+
print(f" Average: {m['avg_time']:.3f}s")
|
|
155
|
+
print(f" Min: {m['min_time']:.3f}s")
|
|
156
|
+
print(f" Max: {m['max_time']:.3f}s")
|
|
157
|
+
|
|
158
|
+
elif args.errors:
|
|
159
|
+
errors = analyzer.find_errors(since)
|
|
160
|
+
if args.format == 'json':
|
|
161
|
+
print(json.dumps(errors, indent=2))
|
|
162
|
+
else:
|
|
163
|
+
for error in errors:
|
|
164
|
+
print(f"\n{error['timestamp']} - {error['message']}")
|
|
165
|
+
if 'data' in error:
|
|
166
|
+
print(f"Context: {json.dumps(error['data'], indent=2)}")
|
|
167
|
+
|
|
168
|
+
else:
|
|
169
|
+
entries = analyzer.parse_logs(args.level, since, until, context)
|
|
170
|
+
if args.format == 'json':
|
|
171
|
+
print(json.dumps(entries, indent=2))
|
|
172
|
+
else:
|
|
173
|
+
for entry in entries:
|
|
174
|
+
print(f"{entry['timestamp']} - {entry['level']} - {entry['message']}")
|
|
175
|
+
|
|
176
|
+
if __name__ == '__main__':
|
|
177
|
+
main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pydantic_settings import BaseSettings
|
|
2
|
+
from pydantic import ConfigDict
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
class LogConfig(BaseSettings):
|
|
8
|
+
"""Configuration for the logging system."""
|
|
9
|
+
format: str = os.environ.get("EZTRACE_LOG_FORMAT", "color")
|
|
10
|
+
log_file: str = "app.log"
|
|
11
|
+
max_size: int = 10 * 1024 * 1024 # 10MB
|
|
12
|
+
backup_count: int = 5
|
|
13
|
+
log_dir: str = "logs"
|
|
14
|
+
log_level: str = "DEBUG"
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(env_prefix="EZTRACE_")
|
|
17
|
+
|
|
18
|
+
def get_log_path(self) -> Path:
|
|
19
|
+
"""Get the full path to the log file."""
|
|
20
|
+
if os.path.isabs(self.log_file):
|
|
21
|
+
return Path(self.log_file)
|
|
22
|
+
return Path(self.log_dir) / self.log_file
|
|
23
|
+
|
|
24
|
+
config = LogConfig()
|