fsvlog 0.3.2__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.
fsvlog/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ __version__ = "0.4.0"
2
+
3
+ from typing import Tuple
4
+ from .fsvlog import FSVLog, Colors, create_logger
5
+
6
+ __all__: Tuple[str, ...] = (
7
+ "FSVLog",
8
+ "Colors",
9
+ "create_logger",
10
+ )
11
+
12
+ def __dir__() -> Tuple[str, ...]:
13
+ return __all__ + ("__version__", "__doc__")
14
+
15
+ def __getattr__(name: str) -> object:
16
+ if name not in __all__:
17
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
18
+ return globals()[name]
fsvlog/fsvlog.py ADDED
@@ -0,0 +1,381 @@
1
+ import os
2
+ import json
3
+ import inspect
4
+ import sys
5
+ import traceback
6
+ from datetime import datetime
7
+ from typing import Optional, Dict, Any, List
8
+ from collections import defaultdict
9
+
10
+
11
+ class Colors:
12
+ @staticmethod
13
+ def colorize(text: str, rgb: tuple) -> str:
14
+ r, g, b = rgb
15
+ r = max(0, min(255, r))
16
+ g = max(0, min(255, g))
17
+ b = max(0, min(255, b))
18
+ return f"\033[38;2;{r};{g};{b}m{text}\033[0m"
19
+
20
+ GREEN = (0, 255, 0)
21
+ RED = (255, 0, 0)
22
+ BLUE = (0, 0, 255)
23
+ YELLOW = (255, 255, 0)
24
+ CYAN = (0, 255, 255)
25
+ MAGENTA = (255, 0, 255)
26
+ WHITE = (255, 255, 255)
27
+ GRAY = (128, 128, 128)
28
+
29
+
30
+ LOG_LEVELS = {
31
+ 'DEBUG': 10,
32
+ 'INFO': 20,
33
+ 'WARNING': 30,
34
+ 'ERROR': 40,
35
+ 'FATAL': 50
36
+ }
37
+
38
+
39
+ class FSVLog:
40
+ def __init__(self, log_folder_path: str = "Logs", max_logs: int = 10,
41
+ max_file_size_mb: int = 100, log_format: str = None,
42
+ time_format: str = None, filename_time_format: str = None,
43
+ file_only: bool = False, console_only: bool = False,
44
+ use_json: bool = False, log_level: str = "DEBUG",
45
+ enable_metrics: bool = False,
46
+ enable_global_exception_logging: bool = True,
47
+ exception_format: str = "compact"):
48
+
49
+ if not isinstance(log_folder_path, str) or log_folder_path == "":
50
+ raise ValueError("log_folder_path must be a non-empty string")
51
+ if not isinstance(max_logs, int) or max_logs <= 0:
52
+ raise ValueError("max_logs must be a positive integer")
53
+ if not isinstance(max_file_size_mb, int) or max_file_size_mb <= 0:
54
+ raise ValueError("max_file_size_mb must be a positive integer")
55
+ if not isinstance(file_only, bool):
56
+ raise ValueError("file_only must be a boolean")
57
+ if not isinstance(console_only, bool):
58
+ raise ValueError("console_only must be a boolean")
59
+ if not isinstance(use_json, bool):
60
+ raise ValueError("use_json must be a boolean")
61
+ if not isinstance(exception_format, str):
62
+ raise ValueError("exception_format must be a string")
63
+ if exception_format not in ["full", "compact", "minimal"]:
64
+ raise ValueError("exception_format must be 'full', 'compact', or 'minimal'")
65
+ if file_only and console_only:
66
+ raise ValueError("file_only and console_only cannot both be True")
67
+
68
+ self.log_folder_path = log_folder_path
69
+ self.max_logs = max_logs
70
+ self.max_file_size = max_file_size_mb * 1024 * 1024
71
+ self.log_format = log_format or "{timestamp} [{level}] {message}"
72
+ self.time_format = time_format or "%Y-%m-%dT%H:%M:%S.%f"
73
+ self.filename_time_format = filename_time_format or "%Y%m%d_%H%M%S_%f"
74
+ self.file_only = file_only
75
+ self.console_only = console_only
76
+ self.use_json = use_json
77
+ self.log_level = LOG_LEVELS.get(log_level.upper(), 20)
78
+ self.enable_metrics = enable_metrics
79
+ self.exception_format = exception_format
80
+
81
+ self._needs_caller_info = any(x in self.log_format for x in ['filename', 'lineno', 'funcname'])
82
+
83
+ self.colors = Colors()
84
+ self._current_log_file = None
85
+ self._bound_context = {}
86
+ self._original_excepthook = None
87
+
88
+ if enable_metrics:
89
+ self.metrics = defaultdict(int)
90
+
91
+ if not self.console_only:
92
+ os.makedirs(self.log_folder_path, exist_ok=True)
93
+ self._open_log_file()
94
+ self._manage_log_files()
95
+
96
+ if enable_global_exception_logging:
97
+ self.enable_global_exception_logging()
98
+
99
+ def _should_log(self, level: str) -> bool:
100
+ return LOG_LEVELS.get(level, 20) >= self.log_level
101
+
102
+ def _update_metrics(self, level: str):
103
+ if self.enable_metrics:
104
+ self.metrics[level] += 1
105
+
106
+ def _get_timestamp(self) -> str:
107
+ now = datetime.now()
108
+ if self.time_format == "%Y-%m-%dT%H:%M:%S.%f":
109
+ result = now.strftime(self.time_format)[:-3]
110
+ return result + "Z"
111
+ elif self.time_format.endswith(".%f"):
112
+ result = now.strftime(self.time_format)[:-3]
113
+ return result
114
+ else:
115
+ return now.strftime(self.time_format)
116
+
117
+ def _get_timestamp_for_filename(self) -> str:
118
+ now = datetime.now()
119
+ result = now.strftime(self.filename_time_format)
120
+ if "%f" in self.filename_time_format:
121
+ result = result[:-3]
122
+ return result
123
+
124
+ def _get_caller_info(self) -> Dict[str, Any]:
125
+ if not self._needs_caller_info:
126
+ return {}
127
+ try:
128
+ frame = inspect.currentframe()
129
+ for _ in range(3):
130
+ if frame:
131
+ frame = frame.f_back
132
+ if frame and frame.f_code.co_name in ['_log', 'info', 'error', 'warning', 'debug', 'fatal', 'custom',
133
+ 'exception']:
134
+ frame = frame.f_back
135
+ if frame:
136
+ return {
137
+ 'filename': os.path.basename(frame.f_code.co_filename),
138
+ 'lineno': frame.f_lineno,
139
+ 'funcname': frame.f_code.co_name
140
+ }
141
+ except Exception:
142
+ pass
143
+ return {'filename': 'unknown', 'lineno': 0, 'funcname': 'unknown'}
144
+
145
+ def _open_log_file(self):
146
+ filename = f"log_{self._get_timestamp_for_filename()}.log"
147
+ self.log_file_path = os.path.join(self.log_folder_path, filename)
148
+ self._current_log_file = open(self.log_file_path, "a", encoding="utf-8")
149
+
150
+ def _rotate_log_file(self):
151
+ if self._current_log_file:
152
+ self._current_log_file.close()
153
+
154
+ timestamp = self._get_timestamp_for_filename()
155
+ rotated_name = os.path.join(self.log_folder_path, f"log_{timestamp}.log")
156
+ os.rename(self.log_file_path, rotated_name)
157
+ self._open_log_file()
158
+
159
+ def _manage_log_files(self):
160
+ log_files = [
161
+ os.path.join(self.log_folder_path, f)
162
+ for f in os.listdir(self.log_folder_path)
163
+ if os.path.isfile(os.path.join(self.log_folder_path, f)) and f.endswith('.log')
164
+ ]
165
+ log_files.sort(key=os.path.getctime)
166
+ while len(log_files) > self.max_logs:
167
+ try:
168
+ os.remove(log_files.pop(0))
169
+ except Exception as e:
170
+ print(f"Failed to remove old log file: {e}")
171
+
172
+ def _format_log_entry(self, message: str, level: str, extra: Dict[str, Any] = None) -> str:
173
+ timestamp = self._get_timestamp()
174
+ caller = self._get_caller_info()
175
+ context = {**self._bound_context}
176
+ if extra:
177
+ context.update(extra)
178
+ log_type = context.pop('type', '')
179
+ if self.use_json:
180
+ log_data = {
181
+ "timestamp": timestamp,
182
+ "level": level,
183
+ "message": message,
184
+ "pid": os.getpid(),
185
+ **context,
186
+ **caller
187
+ }
188
+ if log_type:
189
+ log_data['type'] = log_type
190
+ return json.dumps(log_data)
191
+
192
+ context_str = json.dumps(context, ensure_ascii=False) if context else ""
193
+ format_vars = {
194
+ 'timestamp': timestamp,
195
+ 'level': level,
196
+ 'message': message,
197
+ 'context': context_str,
198
+ 'type': log_type,
199
+ **caller,
200
+ **context
201
+ }
202
+ try:
203
+ result = self.log_format.format(**format_vars)
204
+ if not log_type:
205
+ result = result.replace("[{type}]", "").replace("{type}", "").replace(" ", " ").replace("[]", "")
206
+ while " " in result:
207
+ result = result.replace(" ", " ")
208
+ if not context_str:
209
+ while " " in result:
210
+ result = result.replace(" ", " ")
211
+
212
+ return result.strip()
213
+ except KeyError as e:
214
+ return f"{timestamp} [{level}] ERROR: Invalid format key {e} - {message}"
215
+
216
+ def _write_to_file(self, log_entry: str):
217
+ if self.console_only:
218
+ return
219
+ try:
220
+ if os.path.getsize(self.log_file_path) > self.max_file_size:
221
+ self._rotate_log_file()
222
+ self._current_log_file.write(log_entry + "\n")
223
+ self._current_log_file.flush()
224
+ except Exception as e:
225
+ if not self.file_only:
226
+ error_msg = self._format_log_entry(f"Failed to write to log file: {e}", "ERROR")
227
+ print(self.colors.colorize(error_msg, Colors.RED))
228
+
229
+ def _log(self, message: str, level: str, color: tuple = None, extra: Dict[str, Any] = None):
230
+ if not self._should_log(level):
231
+ return None
232
+ self._update_metrics(level)
233
+ log_entry = self._format_log_entry(message, level, extra)
234
+ if not self.file_only:
235
+ if color:
236
+ print(self.colors.colorize(log_entry, color))
237
+ else:
238
+ print(log_entry)
239
+ if not self.console_only:
240
+ self._write_to_file(log_entry)
241
+ return log_entry
242
+
243
+ def _global_exception_handler(self, exc_type, exc_value, exc_traceback):
244
+ if exc_type is not KeyboardInterrupt:
245
+ filename = "unknown"
246
+ lineno = 0
247
+ funcname = "unknown"
248
+ if exc_traceback:
249
+ tb = exc_traceback
250
+ while tb.tb_next:
251
+ tb = tb.tb_next
252
+ frame = tb.tb_frame
253
+ filename = os.path.basename(frame.f_code.co_filename)
254
+ lineno = tb.tb_lineno
255
+ funcname = frame.f_code.co_name
256
+
257
+ if self.exception_format == "compact":
258
+ error_msg = f"Exception in {filename}:{lineno} ({funcname}): {exc_type.__name__}: {exc_value}"
259
+ elif self.exception_format == "minimal":
260
+ error_msg = f"{exc_type.__name__}: {exc_value} [{filename}:{lineno}]"
261
+ else:
262
+ tb_text = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
263
+ error_msg = f"Unhandled exception in [{filename}:{lineno}] ({funcname}):\n{tb_text}"
264
+
265
+ old_needs_caller = self._needs_caller_info
266
+ old_format = self.log_format
267
+ self._needs_caller_info = False
268
+ self.log_format = "{timestamp} [{level}] {message}"
269
+ self._log(error_msg, "FATAL", Colors.MAGENTA)
270
+ self._needs_caller_info = old_needs_caller
271
+ self.log_format = old_format
272
+
273
+ def enable_global_exception_logging(self):
274
+ self._original_excepthook = sys.excepthook
275
+ sys.excepthook = lambda *args: self._global_exception_handler(*args)
276
+
277
+ def set_exception_format(self, format: str):
278
+ if format not in ["full", "compact", "minimal"]:
279
+ raise ValueError("format must be 'full', 'compact', or 'minimal'")
280
+ self.exception_format = format
281
+
282
+ def bind(self, **kwargs) -> 'FSVLog':
283
+ new_logger = FSVLog(
284
+ log_folder_path=self.log_folder_path,
285
+ max_logs=self.max_logs,
286
+ max_file_size_mb=self.max_file_size // (1024 * 1024),
287
+ log_format=self.log_format,
288
+ time_format=self.time_format,
289
+ filename_time_format=self.filename_time_format,
290
+ file_only=self.file_only,
291
+ console_only=self.console_only,
292
+ use_json=self.use_json,
293
+ log_level=self._get_log_level_name(),
294
+ enable_metrics=self.enable_metrics,
295
+ exception_format=self.exception_format
296
+ )
297
+ new_logger._bound_context = {**self._bound_context, **kwargs}
298
+ return new_logger
299
+
300
+ def type(self, log_type: str) -> 'FSVLog':
301
+ return self.bind(type=log_type)
302
+
303
+ def _get_log_level_name(self) -> str:
304
+ for name, value in LOG_LEVELS.items():
305
+ if value == self.log_level:
306
+ return name
307
+ return "INFO"
308
+
309
+ def set_log_level(self, level: str):
310
+ self.log_level = LOG_LEVELS.get(level.upper(), 20)
311
+
312
+ def set_time_format(self, time_format: str):
313
+ self.time_format = time_format
314
+
315
+ def set_log_format(self, log_format: str):
316
+ self.log_format = log_format
317
+ self._needs_caller_info = any(x in log_format for x in ['filename', 'lineno', 'funcname'])
318
+
319
+ def exception(self, message: str, extra: Dict[str, Any] = None):
320
+ tb = traceback.format_exc()
321
+ if tb and tb != "NoneType: None\n":
322
+ exc_type, exc_value, exc_traceback = sys.exc_info()
323
+ filename = "unknown"
324
+ lineno = 0
325
+ funcname = "unknown"
326
+ if exc_traceback:
327
+ tb_obj = exc_traceback
328
+ while tb_obj.tb_next:
329
+ tb_obj = tb_obj.tb_next
330
+ frame = tb_obj.tb_frame
331
+ filename = os.path.basename(frame.f_code.co_filename)
332
+ lineno = tb_obj.tb_lineno
333
+ funcname = frame.f_code.co_name
334
+ if self.exception_format == "compact":
335
+ error_msg = f"{message}: {exc_type.__name__}: {exc_value} [{filename}:{lineno}]"
336
+ elif self.exception_format == "minimal":
337
+ error_msg = f"{message}: {exc_type.__name__}: {exc_value}"
338
+ else:
339
+ error_msg = f"{message}\n{tb}"
340
+ else:
341
+ error_msg = message
342
+
343
+ return self._log(error_msg, "ERROR", Colors.RED, extra)
344
+
345
+ def fatal(self, message: str, extra: Dict[str, Any] = None):
346
+ return self._log(message, "FATAL", Colors.MAGENTA, extra)
347
+
348
+ def error(self, message: str, extra: Dict[str, Any] = None):
349
+ return self._log(message, "ERROR", Colors.RED, extra)
350
+
351
+ def warning(self, message: str, extra: Dict[str, Any] = None):
352
+ return self._log(message, "WARNING", Colors.YELLOW, extra)
353
+
354
+ def info(self, message: str, extra: Dict[str, Any] = None):
355
+ return self._log(message, "INFO", Colors.GREEN, extra)
356
+
357
+ def debug(self, message: str, extra: Dict[str, Any] = None):
358
+ return self._log(message, "DEBUG", Colors.CYAN, extra)
359
+
360
+ def custom(self, message: str, level: str, color: tuple = None, extra: Dict[str, Any] = None):
361
+ return self._log(message, level.upper(), color, extra)
362
+
363
+ def get_current_log_file(self) -> str:
364
+ return self.log_file_path
365
+
366
+ def get_metrics(self) -> Dict[str, int]:
367
+ if not self.enable_metrics:
368
+ return {}
369
+ return dict(self.metrics)
370
+
371
+ def reset_metrics(self):
372
+ if self.enable_metrics:
373
+ self.metrics.clear()
374
+
375
+ def close(self):
376
+ if hasattr(self, '_current_log_file') and self._current_log_file:
377
+ self._current_log_file.close()
378
+
379
+
380
+ def create_logger(log_folder_path: str = "Logs", max_logs: int = 10, **kwargs):
381
+ return FSVLog(log_folder_path, max_logs, **kwargs)
@@ -0,0 +1,290 @@
1
+ Metadata-Version: 2.4
2
+ Name: fsvlog
3
+ Version: 0.3.2
4
+ Summary: Advanced logging library with colored output, file rotation, and flexible configuration
5
+ Author-email: Fsainv028 <fsainv028@gmail.com>
6
+ Project-URL: Homepage, https://github.com/Fsainv028/FSVLog
7
+ Project-URL: Repository, https://github.com/Fsainv028/FSVLog.git
8
+ Project-URL: Release Notes, https://github.com/Fsainv028/FSVLog/releases
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Topic :: System :: Logging
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE.txt
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=6.0; extra == "dev"
26
+ Requires-Dist: black>=22.0; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # FSVLog - Advanced Python Logging Library
30
+ > A powerful and flexible logging library with colored console output, file rotation, JSON support, metrics, and global exception handling.
31
+
32
+ ---
33
+ [![PyPI version](https://badge.fury.io/py/fsvlog.svg)](https://badge.fury.io/py/fsvlog)
34
+ [![Python versions](https://img.shields.io/pypi/pyversions/fsvlog.svg)](https://pypi.org/project/fsvlog/)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
36
+
37
+
38
+ ## Features
39
+ - 🎨 Colored output with RGB color support for console logging
40
+ - 📁 File logging with automatic rotation and cleanup
41
+ - 🔄 Flexible modes - File-only, console-only, or both
42
+ - 📏 Size-based rotation to prevent log files from growing too large
43
+ - 🗑️ Automatic cleanup to keep only the newest log files
44
+ - ✨ Custom formatting with flexible log message templates
45
+ - 📊 Metrics tracking to count logs by level
46
+ - 🛡️ Global exception handling to catch and log unhandled exceptions
47
+ - 📋 JSON format support for machine-readable logs
48
+ - 🔗 Context binding to add persistent context to log entries
49
+ - 🎯 Multiple log levels: DEBUG, INFO, WARNING, ERROR, FATAL
50
+ - ⚡ Zero dependencies - pure Python, no external packages
51
+
52
+
53
+ ## Installation
54
+ ```bash
55
+ pip install fsvlog
56
+ ```
57
+ ---
58
+ ## Quick Start
59
+ ```python
60
+ from fsvlog import FSVLog
61
+
62
+ # Create logger with default settings
63
+ logger = FSVLog()
64
+
65
+ # Log messages with different levels
66
+ logger.debug("Debug message")
67
+ logger.info("Info message")
68
+ logger.warning("Warning message")
69
+ logger.error("Error message")
70
+ logger.fatal("Fatal message")
71
+ ```
72
+ ## Advanced Usage
73
+ ### Basic Configuration
74
+ ```python
75
+ logger = FSVLog(
76
+ log_folder_path="logs", # Log directory (default: "Logs")
77
+ max_logs=10, # Max log files to keep (default: 10)
78
+ max_file_size_mb=100, # Max file size before rotation (default: 100MB)
79
+ log_level="INFO", # Minimum log level (default: "DEBUG")
80
+ file_only=False, # Log only to file (default: False)
81
+ console_only=False, # Log only to console (default: False)
82
+ use_json=False, # Use JSON format (default: False)
83
+ enable_metrics=False, # Enable metrics tracking (default: False)
84
+ enable_global_exception_logging=True, # Catch unhandled exceptions
85
+ exception_format="compact" # "full", "compact", or "minimal"
86
+ )
87
+ ```
88
+ ### Custom Formatting
89
+ ```python
90
+ # Custom log format
91
+ logger = FSVLog(
92
+ log_format="{timestamp} [{level}] [{filename}:{lineno}] {message}",
93
+ time_format="%Y-%m-%d %H:%M:%S"
94
+ )
95
+
96
+ logger.info("Custom format example")
97
+ # Output: 2026-07-29 14:30:00 [INFO] [app.py:42] Custom format example
98
+ ```
99
+ ### Context Binding
100
+ ```python
101
+ # Bind context to all subsequent logs
102
+ logger = FSVLog()
103
+ user_logger = logger.bind(user_id=123, session="abc123")
104
+ user_logger.info("User logged in")
105
+ # Output includes user_id and session
106
+
107
+ # Type-specific logging
108
+ auth_logger = logger.type("AUTH")
109
+ auth_logger.info("Authentication successful")
110
+ # Output: [AUTH] Authentication successful
111
+ ```
112
+ ### JSON Logging
113
+ ```python
114
+ # Enable JSON format for structured logging
115
+ logger = FSVLog(use_json=True)
116
+ logger.info("Structured log", extra={"user": "john", "action": "login"})
117
+ # Output: {"timestamp": "2026-07-29T14:30:00.123Z", "level": "INFO", "message": "Structured log", "user": "john", "action": "login"}
118
+ ```
119
+ ### Metrics
120
+ ```python
121
+ # Enable metrics tracking
122
+ logger = FSVLog(enable_metrics=True)
123
+ logger.info("Message 1")
124
+ logger.error("Error 1")
125
+ logger.info("Message 2")
126
+
127
+ metrics = logger.get_metrics()
128
+ print(metrics) # {'INFO': 2, 'ERROR': 1}
129
+
130
+ logger.reset_metrics()
131
+ ```
132
+ ### Exception Handling
133
+ ```python
134
+ logger = FSVLog(exception_format="compact")
135
+
136
+ try:
137
+ result = 1 / 0
138
+ except Exception:
139
+ logger.exception("Division by zero occurred")
140
+ # Output: Division by zero occurred: ZeroDivisionError: division by zero [app.py:42]
141
+ ```
142
+ ### Global Exception Logging
143
+ ```python
144
+ # All unhandled exceptions will be automatically logged
145
+ logger = FSVLog(enable_global_exception_logging=True)
146
+
147
+ # This will be caught and logged
148
+ def buggy_function():
149
+ raise RuntimeError("Something went wrong!")
150
+
151
+ buggy_function() # Automatically logged as FATAL
152
+ ```
153
+ ### Custom Colors
154
+ ```python
155
+ # Use predefined colors
156
+ logger.info("Success!", color=Colors.GREEN)
157
+ logger.error("Danger!", color=Colors.RED)
158
+
159
+ # Custom RGB colors
160
+ logger.custom("Custom colored message", "CUSTOM", (255, 165, 0)) # Orange
161
+ ```
162
+ ### Log Levels
163
+ | Level | Method | Color | Use Case |
164
+ |----------|----------|----------|----------|
165
+ | DEBUG | .debug() | Cyan | Detailed debugging info |
166
+ | INFO | .info() | Green | General information |
167
+ | WARNING | .warning() | Yellow | Warning messages |
168
+ | ERROR | .error() | Red | Error conditions |
169
+ | FATAL | .fatal() | Magenta | Critical errors |
170
+
171
+ ---
172
+ ## API Reference
173
+ ### Class: FSVLog
174
+ Parameters:
175
+
176
+ - log_folder_path (str): Path to log directory. Default: "Logs"
177
+ - max_logs (int): Maximum number of log files to keep. Default: 10
178
+ - max_file_size_mb (int): Maximum file size in MB before rotation. Default: 100
179
+ - log_format (str): Custom log format template. Default: "{timestamp} [{level}] {message}"
180
+ - time_format (str): Time format string. Default: "%Y-%m-%dT%H:%M:%S.%f"
181
+ - filename_time_format (str): Filename time format. Default: "%Y%m%d_%H%M%S_%f"
182
+ - file_only (bool): Log only to file. Default: False
183
+ - console_only (bool): Log only to console. Default: False
184
+ - use_json (bool): Use JSON format. Default: False
185
+ - log_level (str): Minimum log level. Default: "DEBUG"
186
+ - enable_metrics (bool): Enable metrics tracking. Default: False
187
+ - enable_global_exception_logging (bool): Catch unhandled exceptions. Default: True
188
+ - exception_format (str): Exception format: "full", "compact", or "minimal". Default: "compact"
189
+
190
+ Available format keys:
191
+
192
+ {timestamp} - Current timestamp
193
+ {level} - Log level
194
+ {message} - Log message
195
+ {filename} - Source file name
196
+ {lineno} - Line number
197
+ {funcname} - Function name
198
+ {type} - Custom log type
199
+ {context} - Bound context (JSON)
200
+
201
+ Methods:
202
+
203
+ debug(msg, extra=None) - Log debug message
204
+ info(msg, extra=None) - Log info message
205
+ warning(msg, extra=None) - Log warning message
206
+ error(msg, extra=None) - Log error message
207
+ fatal(msg, extra=None) - Log fatal message
208
+ exception(msg, extra=None) - Log exception with traceback
209
+ custom(msg, level, color=None, extra=None) - Log with custom level and color
210
+ bind(**kwargs) - Create logger with bound context
211
+ type(log_type) - Create logger with specific log type
212
+ set_log_level(level) - Change minimum log level
213
+ set_log_format(format) - Change log format
214
+ set_time_format(format) - Change time format
215
+ get_current_log_file() - Get current log file path
216
+ get_metrics() - Get metrics dictionary
217
+ reset_metrics() - Reset metrics
218
+ close() - Close log file
219
+ enable_global_exception_logging() - Enable global exception handler
220
+ set_exception_format(format) - Set exception format
221
+
222
+ ### Helper Function: create_logger()
223
+ ```python
224
+ from fsvlog import create_logger
225
+
226
+ logger = create_logger("logs", max_logs=5, log_level="INFO")
227
+ ```
228
+ ## Examples
229
+ ### Web Application Logging
230
+ ```python
231
+ from fsvlog import FSVLog
232
+
233
+ logger = FSVLog(log_folder_path="web_logs", enable_metrics=True)
234
+
235
+ # Bind request context
236
+ req_logger = logger.bind(request_id="req-123", user="alice")
237
+ req_logger.info("Request received")
238
+ req_logger.error("Database connection failed")
239
+
240
+ # Type-based logging
241
+ access_logger = logger.type("ACCESS")
242
+ access_logger.info("GET /api/users 200")
243
+ ```
244
+ ### Microservice Logging
245
+ ```python
246
+ logger = FSVLog(
247
+ log_folder_path="/var/log/myapp",
248
+ use_json=True,
249
+ enable_global_exception_logging=True,
250
+ exception_format="full"
251
+ )
252
+
253
+ # Add service context
254
+ svc_logger = logger.bind(service="auth-service", version="1.2.3")
255
+ svc_logger.info("Service started")
256
+ ```
257
+ ### Development Debugging
258
+ ```python
259
+ import time
260
+ from fsvlog import FSVLog
261
+
262
+ logger = FSVLog(
263
+ console_only=True, # Console only for development
264
+ log_level="DEBUG",
265
+ log_format="{timestamp} [{level}] [{funcname}()] {message}"
266
+ )
267
+
268
+ def process_data():
269
+ logger.debug("Processing data...")
270
+ # ... processing
271
+ logger.info("Data processed successfully")
272
+
273
+ process_data()
274
+ ```
275
+ ---
276
+ # License
277
+ **MIT License - see LICENSE file for details**
278
+
279
+ ---
280
+ # Contributing
281
+ **Contributions are welcome! Please feel free to submit a Pull Request.**
282
+
283
+ ---
284
+ ## Support
285
+ - **GitHub**: [Fsainv028/FSVLog](https://github.com/Fsainv028/FSVLog)
286
+ - **Issues**: [Report a bug](https://github.com/Fsainv028/FSVLog/issues)
287
+ - **Email**: fsainv028@gmail.com
288
+
289
+ ---
290
+ Made with ❤️ by Fsainv028
@@ -0,0 +1,7 @@
1
+ fsvlog/__init__.py,sha256=iTEqkpC7PSDQe7Au48e1kvEmJky-5Ka-siIWGWAcEyY,434
2
+ fsvlog/fsvlog.py,sha256=isA5EH2Z7mps29B_QvnaO2pUb8AYpKCIHUdMFUSWkto,14802
3
+ fsvlog-0.3.2.dist-info/licenses/LICENSE.txt,sha256=IB9EP7RwSoUPAMI3OLuqpdMA2qfXXwvSMg1ddLJFoMU,1091
4
+ fsvlog-0.3.2.dist-info/METADATA,sha256=VxpIQzp63w5J6UtccXPrcSko4gpJwt9tvl9RlQMUXVA,10073
5
+ fsvlog-0.3.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
6
+ fsvlog-0.3.2.dist-info/top_level.txt,sha256=ZcNecbCB2QfYomNWys6x8m7L4d7-nCGXHTl72cxs6Rs,7
7
+ fsvlog-0.3.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2026] [Fsainv028]
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
+ fsvlog