logging-module 0.2.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nathan T Nguyen
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,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: logging-module
3
+ Version: 0.2.0
4
+ Summary: A comprehensive logging and file utility module for Python applications
5
+ Author-email: Nathan T Nguyen <nhatnguyen2807@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: CreatorGithub, https://github.com/NhatNguyen5
8
+ Project-URL: Homepage, https://test.pypi.org/project/logging-module/
9
+ Project-URL: Documentation, https://github.com/NhatNguyen5/logging_module/wiki/Instructions
10
+ Project-URL: Repository, https://github.com/NhatNguyen5/logging-module.git
11
+ Project-URL: Issues, https://github.com/NhatNguyen5/logging_module/issues
12
+ Keywords: logging,file-utilities,tracing,debugging
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: bump2version; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # Logging Module
29
+
30
+ A comprehensive Python logging and file utility module designed for easy integration into FastAPI projects and other Python applications.
31
+
32
+ ## Features
33
+
34
+ - **Flexible Logging**: Timestamp-based logging with caller information
35
+ - **File Utilities**: Safe file operations with error handling
36
+ - **Configuration**: Easy-to-configure logging and tracing options
37
+ - **Error Management**: Comprehensive error tracking and reporting
38
+
39
+ ## Installation
40
+
41
+ ### From PyPI (when published)
42
+ ```bash
43
+ pip install logging-module
44
+ ```
45
+
46
+ ### From Local Directory
47
+ ```bash
48
+ pip install ./logging_module
49
+ # or in editable mode for development
50
+ pip install -e ./logging_module
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ from logging_module import Logger
57
+
58
+ # Create a logger instance
59
+ app_logger = Logger()
60
+
61
+ # Basic logging
62
+ app_logger.log("Application started")
63
+
64
+ # Log with console output
65
+ app_logger.log("Important message", print_to_console=True)
66
+
67
+ # Log with custom console message
68
+ app_logger.log("Debug info", print_to_console=True, console_message="DEBUG: Debug info")
69
+
70
+ # Use as a callable (shortcut for log())
71
+ app_logger("Quick log message")
72
+
73
+ # Control logging behavior
74
+ app_logger.enable_logging(False) # Disable this logger instance
75
+ app_logger.enable_log_tracing(True) # Enable detailed tracing for this instance
76
+
77
+ # Configure custom log directory
78
+ custom_logger = Logger(log_dir="my_logs")
79
+
80
+ # Configure project directory for better frame filtering
81
+ project_logger = Logger(project_dir="/path/to/project")
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ ### Logging Config (`config.py`)
87
+ - `GLOBAL_LOGGING_ENABLED`: Enable/disable logging (default: `True`)
88
+ - `GLOBAL_LOG_TRACING_ENABLED`: Enable/disable detailed tracing (default: `True`)
89
+
90
+ ```python
91
+ from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
92
+ ```
93
+
94
+ ## Modules
95
+
96
+ ### Logger (`my_logging.py`)
97
+ Main logging class providing timestamped, structured logging with caller information.
98
+
99
+ **Key Features:**
100
+ - Automatic timestamping and file logging
101
+ - Caller information tracking with configurable frame filtering
102
+ - Configurable log directory and project directory
103
+ - Thread-safe file operations
104
+ - Configurable tracing depth
105
+ - Console output options
106
+ - Instance-level enable/disable controls
107
+
108
+ ### File Utilities (`file_utils.py`)
109
+ Safe file operations with comprehensive error handling:
110
+ - Path verification with security checks
111
+ - File verification for different operations (read/write/execute)
112
+ - Directory operations with working directory constraints
113
+
114
+ ### Configuration (`config.py`)
115
+ Global configuration settings for logging behavior:
116
+ - `GLOBAL_LOGGING_ENABLED`: Master switch for all logging
117
+ - `GLOBAL_LOG_TRACING_ENABLED`: Master switch for tracing features
118
+
119
+ ## Development
120
+
121
+ ### Prerequisites
122
+ - Python 3.10+
123
+
124
+ ### Installing in Development Mode
125
+ ```bash
126
+ cd logging_module
127
+ pip install -e .
128
+ ```
129
+
130
+ ### Running Tests
131
+ ```bash
132
+ # Install test dependencies
133
+ pip install -e ".[dev]"
134
+
135
+ # Run all tests
136
+ pytest
137
+
138
+ # Run with verbose output
139
+ pytest -v
140
+
141
+ # Run specific test file
142
+ pytest tests/test_my_logging.py
143
+ ```
144
+
145
+ ## License
146
+
147
+ MIT
148
+
149
+ ## Author
150
+
151
+ Nathan T Nguyen
@@ -0,0 +1,124 @@
1
+ # Logging Module
2
+
3
+ A comprehensive Python logging and file utility module designed for easy integration into FastAPI projects and other Python applications.
4
+
5
+ ## Features
6
+
7
+ - **Flexible Logging**: Timestamp-based logging with caller information
8
+ - **File Utilities**: Safe file operations with error handling
9
+ - **Configuration**: Easy-to-configure logging and tracing options
10
+ - **Error Management**: Comprehensive error tracking and reporting
11
+
12
+ ## Installation
13
+
14
+ ### From PyPI (when published)
15
+ ```bash
16
+ pip install logging-module
17
+ ```
18
+
19
+ ### From Local Directory
20
+ ```bash
21
+ pip install ./logging_module
22
+ # or in editable mode for development
23
+ pip install -e ./logging_module
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```python
29
+ from logging_module import Logger
30
+
31
+ # Create a logger instance
32
+ app_logger = Logger()
33
+
34
+ # Basic logging
35
+ app_logger.log("Application started")
36
+
37
+ # Log with console output
38
+ app_logger.log("Important message", print_to_console=True)
39
+
40
+ # Log with custom console message
41
+ app_logger.log("Debug info", print_to_console=True, console_message="DEBUG: Debug info")
42
+
43
+ # Use as a callable (shortcut for log())
44
+ app_logger("Quick log message")
45
+
46
+ # Control logging behavior
47
+ app_logger.enable_logging(False) # Disable this logger instance
48
+ app_logger.enable_log_tracing(True) # Enable detailed tracing for this instance
49
+
50
+ # Configure custom log directory
51
+ custom_logger = Logger(log_dir="my_logs")
52
+
53
+ # Configure project directory for better frame filtering
54
+ project_logger = Logger(project_dir="/path/to/project")
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ ### Logging Config (`config.py`)
60
+ - `GLOBAL_LOGGING_ENABLED`: Enable/disable logging (default: `True`)
61
+ - `GLOBAL_LOG_TRACING_ENABLED`: Enable/disable detailed tracing (default: `True`)
62
+
63
+ ```python
64
+ from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
65
+ ```
66
+
67
+ ## Modules
68
+
69
+ ### Logger (`my_logging.py`)
70
+ Main logging class providing timestamped, structured logging with caller information.
71
+
72
+ **Key Features:**
73
+ - Automatic timestamping and file logging
74
+ - Caller information tracking with configurable frame filtering
75
+ - Configurable log directory and project directory
76
+ - Thread-safe file operations
77
+ - Configurable tracing depth
78
+ - Console output options
79
+ - Instance-level enable/disable controls
80
+
81
+ ### File Utilities (`file_utils.py`)
82
+ Safe file operations with comprehensive error handling:
83
+ - Path verification with security checks
84
+ - File verification for different operations (read/write/execute)
85
+ - Directory operations with working directory constraints
86
+
87
+ ### Configuration (`config.py`)
88
+ Global configuration settings for logging behavior:
89
+ - `GLOBAL_LOGGING_ENABLED`: Master switch for all logging
90
+ - `GLOBAL_LOG_TRACING_ENABLED`: Master switch for tracing features
91
+
92
+ ## Development
93
+
94
+ ### Prerequisites
95
+ - Python 3.10+
96
+
97
+ ### Installing in Development Mode
98
+ ```bash
99
+ cd logging_module
100
+ pip install -e .
101
+ ```
102
+
103
+ ### Running Tests
104
+ ```bash
105
+ # Install test dependencies
106
+ pip install -e ".[dev]"
107
+
108
+ # Run all tests
109
+ pytest
110
+
111
+ # Run with verbose output
112
+ pytest -v
113
+
114
+ # Run specific test file
115
+ pytest tests/test_my_logging.py
116
+ ```
117
+
118
+ ## License
119
+
120
+ MIT
121
+
122
+ ## Author
123
+
124
+ Nathan T Nguyen
@@ -0,0 +1,32 @@
1
+ """
2
+ Logging Module - A comprehensive logging and file utility package.
3
+
4
+ This module provides:
5
+ - Logging configuration and management
6
+ - File utilities for logging operations
7
+ - Tracing and debug capabilities
8
+ """
9
+
10
+ from .my_logging import Logger
11
+ from .config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
12
+ from .file_utils import (
13
+ FileUtilsError,
14
+ FileExecutionError,
15
+ FileOperator,
16
+ file_verify_path,
17
+ file_verify_file,
18
+ )
19
+
20
+ __version__ = "0.2.0"
21
+ __author__ = "Nathan T Nguyen"
22
+
23
+ __all__ = [
24
+ "Logger",
25
+ "GLOBAL_LOGGING_ENABLED",
26
+ "GLOBAL_LOG_TRACING_ENABLED",
27
+ "FileUtilsError",
28
+ "FileExecutionError",
29
+ "FileOperator",
30
+ "file_verify_path",
31
+ "file_verify_file",
32
+ ]
@@ -0,0 +1,10 @@
1
+ #######################################################################
2
+ #
3
+ # Logging Module Configuration
4
+ #
5
+ # Configuration for logging and tracing
6
+ #
7
+ #######################################################################
8
+
9
+ GLOBAL_LOGGING_ENABLED = True
10
+ GLOBAL_LOG_TRACING_ENABLED = True
@@ -0,0 +1,108 @@
1
+ #######################################################################
2
+ #
3
+ # File Utils
4
+ #
5
+ # Utility functions for file and directory operations
6
+ # Handles path verification and error management
7
+ #
8
+ #######################################################################
9
+
10
+ import os
11
+ from enum import Enum
12
+
13
+
14
+ class FileUtilsError(str, Enum):
15
+ # Directory related errors
16
+ OUTSIDE_WORKING_DIR = "Error: Cannot list {directory} as it is outside the permitted working directory"
17
+ NOT_A_DIRECTORY = "Error: {directory} is not a directory"
18
+ # Read file errors
19
+ FILE_READ_OUTSIDE_WORKING_DIR = "Error: Cannot read {file_path} as it is outside the permitted working directory"
20
+ FILE_READ_NOT_FOUND_OR_NOT_REGULAR = 'Error: File not found or is not a regular file: "{file_path}"'
21
+ # Write file errors
22
+ FILE_WRITE_OUTSIDE_WORKING_DIR = 'Error: Cannot write to "{file_path}" as it is outside the permitted working directory'
23
+ FILE_WRITE_NOT_FOUND_OR_NOT_REGULAR = 'Error: File not found or is not a regular file: "{file_path}"\n Create the file before writing to it.'
24
+ # Executable file errors
25
+ FILE_EXECUTE_OUTSIDE_WORKING_DIR = 'Error: Cannot execute "{file_path}" as it is outside the permitted working directory'
26
+ FILE_EXECUTE_NOT_FOUND_OR_NOT_REGULAR = 'Error: File "{file_path}" not found.'
27
+ FILE_EXECUTE_NOT_PYTHON = 'Error: File "{file_path}" is not a Python file.'
28
+
29
+
30
+ class FileExecutionError(str, Enum):
31
+ EXECUTION_FAILED = "Error: executing Python file: {error_message}"
32
+ EXECUTION_TIMEOUT = 'Error: Execution of file "{file_path}" timed out after {timeout} seconds.'
33
+
34
+
35
+ class FileOperator(str, Enum):
36
+ READ_FILE = "read_file"
37
+ WRITE_FILE = "write_file"
38
+ EXECUTE_FILE = "execute_file"
39
+
40
+
41
+ def file_verify_path(working_directory: str, directory: str) -> str:
42
+ """
43
+ Verify that *directory* (relative to *working_directory*) exists and is
44
+ inside the working directory.
45
+
46
+ Returns the resolved path on success, or a ``FileUtilsError`` string on
47
+ failure.
48
+ """
49
+ path_to_directory = os.path.join(working_directory, directory)
50
+ if (
51
+ not path_to_directory.startswith(working_directory)
52
+ or ".." in os.path.relpath(path_to_directory, working_directory)
53
+ ):
54
+ return FileUtilsError.OUTSIDE_WORKING_DIR.value.format(directory=directory)
55
+
56
+ if not os.path.isdir(path_to_directory):
57
+ return FileUtilsError.NOT_A_DIRECTORY.value.format(directory=directory)
58
+
59
+ return path_to_directory
60
+
61
+
62
+ def file_verify_file(working_directory: str, file: str, options: FileOperator | None = None) -> str:
63
+ """
64
+ Verify that *file* (relative to *working_directory*) is valid for the
65
+ requested *options* operation.
66
+
67
+ Returns the resolved path on success, or a ``FileUtilsError`` string on
68
+ failure.
69
+
70
+ Raises
71
+ ------
72
+ ValueError
73
+ If *options* is not a recognised ``FileOperator`` value.
74
+ """
75
+ path_to_file = os.path.join(working_directory, file)
76
+
77
+ def _is_outside() -> bool:
78
+ return (
79
+ not path_to_file.startswith(working_directory)
80
+ or ".." in os.path.relpath(path_to_file, working_directory)
81
+ )
82
+
83
+ match options:
84
+ case FileOperator.READ_FILE:
85
+ if _is_outside():
86
+ return FileUtilsError.FILE_READ_OUTSIDE_WORKING_DIR.value.format(file_path=path_to_file)
87
+ if not os.path.isfile(path_to_file):
88
+ return FileUtilsError.FILE_READ_NOT_FOUND_OR_NOT_REGULAR.value.format(file_path=path_to_file)
89
+
90
+ case FileOperator.WRITE_FILE:
91
+ if _is_outside():
92
+ return FileUtilsError.FILE_WRITE_OUTSIDE_WORKING_DIR.value.format(file_path=path_to_file)
93
+ # Write operations may target a not-yet-existing file; the caller is
94
+ # responsible for creating any missing parent directories. We only
95
+ # validate the path is inside the working directory and return it.
96
+
97
+ case FileOperator.EXECUTE_FILE:
98
+ if _is_outside():
99
+ return FileUtilsError.FILE_EXECUTE_OUTSIDE_WORKING_DIR.value.format(file_path=file)
100
+ if not os.path.isfile(path_to_file):
101
+ return FileUtilsError.FILE_EXECUTE_NOT_FOUND_OR_NOT_REGULAR.value.format(file_path=file)
102
+ if not path_to_file.endswith(".py"):
103
+ return FileUtilsError.FILE_EXECUTE_NOT_PYTHON.value.format(file_path=file)
104
+
105
+ case _:
106
+ raise ValueError(f"Invalid file operation option provided: {options!r}")
107
+
108
+ return path_to_file # reached by all arms that don't return early
@@ -0,0 +1,207 @@
1
+ #######################################################################
2
+ #
3
+ # My Logging Module
4
+ #
5
+ # Provides logging functionality with timestamping and caller info
6
+ # Supports configurable logging and tracing options
7
+ #
8
+ #######################################################################
9
+
10
+ import os
11
+ import threading
12
+ from datetime import datetime
13
+ from logging_module.file_utils import FileUtilsError, file_verify_path
14
+ from inspect import getframeinfo, stack
15
+ from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
16
+
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Frame-filtering constants
20
+ # ---------------------------------------------------------------------------
21
+
22
+ # Prefixes of filenames/paths to always skip (logger internals, frozen frames)
23
+ _IGNORED_PREFIXES = (
24
+ "<frozen",
25
+ "<string>",
26
+ )
27
+
28
+ # Basenames of framework/infra files to skip (blocklist fallback mode)
29
+ _IGNORED_FILES = {
30
+ "spawn.py",
31
+ "process.py",
32
+ "_subprocess.py",
33
+ "server.py",
34
+ "runners.py",
35
+ "config.py",
36
+ "importer.py",
37
+ "__init__.py",
38
+ "cli.py",
39
+ "discover.py",
40
+ "my_logging.py", # skip this file itself
41
+ }
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Logger
45
+ # ---------------------------------------------------------------------------
46
+
47
+ class Logger:
48
+ """
49
+ Lightweight file logger with optional call-stack tracing.
50
+
51
+ Usage
52
+ -----
53
+ log = Logger()
54
+ log("Something happened")
55
+ log.log("Something happened", print_to_console=True, console_message="Hey!")
56
+
57
+ Parameters
58
+ ----------
59
+ log_dir : str
60
+ Directory (relative or absolute) where log files are written.
61
+ Defaults to ``./logs``.
62
+ project_dir : str | None
63
+ Root of your project source tree. When supplied, only frames whose
64
+ absolute path starts with this directory (and are not inside
65
+ ``site-packages``) are considered "your code". When ``None`` the
66
+ module falls back to the blocklist defined by ``_IGNORED_FILES`` /
67
+ ``_IGNORED_PREFIXES``.
68
+ """
69
+
70
+ DEFAULT_LOG_DIR = "logs"
71
+
72
+ def __init__(
73
+ self,
74
+ log_dir: str = DEFAULT_LOG_DIR,
75
+ project_dir: str | None = None,
76
+ ):
77
+ self._enable: bool = True
78
+ self._enable_log_tracing: bool = False
79
+ self._log_dir: str = log_dir
80
+ self._project_dir: str | None = (
81
+ os.path.abspath(project_dir) if project_dir is not None else None
82
+ )
83
+ self._lock = threading.Lock()
84
+
85
+ # ------------------------------------------------------------------
86
+ # Public interface
87
+ # ------------------------------------------------------------------
88
+
89
+ def __call__(
90
+ self,
91
+ message: str,
92
+ print_to_console: bool = False,
93
+ console_message: str = "",
94
+ ) -> None:
95
+ self.log(message, print_to_console=print_to_console, console_message=console_message)
96
+
97
+ def log(
98
+ self,
99
+ message: str,
100
+ print_to_console: bool = False,
101
+ console_message: str = "",
102
+ ) -> None:
103
+ if not GLOBAL_LOGGING_ENABLED or not self._enable:
104
+ return
105
+
106
+ now = datetime.now()
107
+ date = now.strftime("%Y-%m-%d")
108
+ time_str = now.strftime("%H:%M:%S")
109
+ timestamp = f"{date} {time_str}"
110
+
111
+ logistic_data = f"[{timestamp}][{self.get_caller_info()}]"
112
+ padded_space = "".rjust(len(logistic_data) + 1)
113
+ formatted_message = str(message).replace("\n", f"\n{padded_space}")
114
+ log_entry = f"{logistic_data} {formatted_message}\n"
115
+
116
+ self._write_to_file(log_entry, date, time_str)
117
+
118
+ if print_to_console:
119
+ print(message if console_message == "" else console_message)
120
+
121
+ def enable_logging(self, enable_logging: bool) -> None:
122
+ self._enable = enable_logging
123
+
124
+ def enable_log_tracing(self, enable_log_tracing: bool) -> None:
125
+ self._enable_log_tracing = enable_log_tracing
126
+
127
+ # ------------------------------------------------------------------
128
+ # Caller info
129
+ # ------------------------------------------------------------------
130
+
131
+ def get_caller_info(self) -> str:
132
+ tracing_active = GLOBAL_LOG_TRACING_ENABLED and self._enable_log_tracing
133
+ frames = self._collect_project_frames()
134
+
135
+ if not frames:
136
+ return "unknown:0"
137
+
138
+ if not tracing_active:
139
+ return frames[0]
140
+
141
+ return ">".join(frames)
142
+
143
+ # ------------------------------------------------------------------
144
+ # Private helpers
145
+ # ------------------------------------------------------------------
146
+
147
+ def _is_project_frame(self, filename: str) -> bool:
148
+ """
149
+ Return True when *filename* belongs to project code.
150
+
151
+ Allowlist mode (project_dir set):
152
+ Frame must live inside the project directory and not inside
153
+ a ``site-packages`` folder.
154
+
155
+ Blocklist mode (project_dir is None):
156
+ Frame must not match any entry in ``_IGNORED_FILES`` or
157
+ ``_IGNORED_PREFIXES``.
158
+ """
159
+ # Always skip frozen / string pseudo-files regardless of mode
160
+ if any(filename.startswith(prefix) for prefix in _IGNORED_PREFIXES):
161
+ return False
162
+
163
+ if self._project_dir is not None:
164
+ abs_filename = os.path.abspath(filename)
165
+ return (
166
+ abs_filename.startswith(self._project_dir)
167
+ and "site-packages" not in abs_filename
168
+ )
169
+
170
+ # Blocklist fallback
171
+ return os.path.basename(filename) not in _IGNORED_FILES
172
+
173
+ def _collect_project_frames(self) -> list[str]:
174
+ """Walk the call stack and return frame strings for project code only."""
175
+ frames: list[str] = []
176
+ for frame_record in stack():
177
+ try:
178
+ info = getframeinfo(frame_record[0])
179
+ if self._is_project_frame(info.filename):
180
+ frames.append(f"{os.path.basename(info.filename)}:{info.lineno}")
181
+ except (IndexError, TypeError):
182
+ # IndexError: stack shorter than expected
183
+ # TypeError: frame_record is not subscriptable (e.g. test mocks using None)
184
+ continue
185
+ return frames
186
+
187
+ def _write_to_file(self, log_entry: str, date: str, time_str: str) -> None:
188
+ """Create the log directory/file if needed, then append *log_entry*."""
189
+ log_dir_path = os.path.join("./", self._log_dir)
190
+
191
+ # Ensure the log directory exists
192
+ file_path = file_verify_path("./", self._log_dir)
193
+ if file_path == FileUtilsError.NOT_A_DIRECTORY.value.format(directory=self._log_dir):
194
+ os.makedirs(log_dir_path, exist_ok=True)
195
+ file_path = file_verify_path("./", self._log_dir) # re-verify after creation
196
+
197
+ # Bail out if the path is still an error (e.g. outside working dir)
198
+ if not isinstance(file_path, str) or file_path.startswith("Error"):
199
+ return
200
+
201
+ file_name = os.path.join(file_path, f"{date}_logging.txt")
202
+ with self._lock:
203
+ with open(file_name, "a") as log_file:
204
+ # Write a header only on the very first entry (empty file)
205
+ if log_file.tell() == 0:
206
+ log_file.write(f"Log file created on {date} at {time_str}\n")
207
+ log_file.write(log_entry)
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: logging-module
3
+ Version: 0.2.0
4
+ Summary: A comprehensive logging and file utility module for Python applications
5
+ Author-email: Nathan T Nguyen <nhatnguyen2807@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: CreatorGithub, https://github.com/NhatNguyen5
8
+ Project-URL: Homepage, https://test.pypi.org/project/logging-module/
9
+ Project-URL: Documentation, https://github.com/NhatNguyen5/logging_module/wiki/Instructions
10
+ Project-URL: Repository, https://github.com/NhatNguyen5/logging-module.git
11
+ Project-URL: Issues, https://github.com/NhatNguyen5/logging_module/issues
12
+ Keywords: logging,file-utilities,tracing,debugging
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: bump2version; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # Logging Module
29
+
30
+ A comprehensive Python logging and file utility module designed for easy integration into FastAPI projects and other Python applications.
31
+
32
+ ## Features
33
+
34
+ - **Flexible Logging**: Timestamp-based logging with caller information
35
+ - **File Utilities**: Safe file operations with error handling
36
+ - **Configuration**: Easy-to-configure logging and tracing options
37
+ - **Error Management**: Comprehensive error tracking and reporting
38
+
39
+ ## Installation
40
+
41
+ ### From PyPI (when published)
42
+ ```bash
43
+ pip install logging-module
44
+ ```
45
+
46
+ ### From Local Directory
47
+ ```bash
48
+ pip install ./logging_module
49
+ # or in editable mode for development
50
+ pip install -e ./logging_module
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ from logging_module import Logger
57
+
58
+ # Create a logger instance
59
+ app_logger = Logger()
60
+
61
+ # Basic logging
62
+ app_logger.log("Application started")
63
+
64
+ # Log with console output
65
+ app_logger.log("Important message", print_to_console=True)
66
+
67
+ # Log with custom console message
68
+ app_logger.log("Debug info", print_to_console=True, console_message="DEBUG: Debug info")
69
+
70
+ # Use as a callable (shortcut for log())
71
+ app_logger("Quick log message")
72
+
73
+ # Control logging behavior
74
+ app_logger.enable_logging(False) # Disable this logger instance
75
+ app_logger.enable_log_tracing(True) # Enable detailed tracing for this instance
76
+
77
+ # Configure custom log directory
78
+ custom_logger = Logger(log_dir="my_logs")
79
+
80
+ # Configure project directory for better frame filtering
81
+ project_logger = Logger(project_dir="/path/to/project")
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ ### Logging Config (`config.py`)
87
+ - `GLOBAL_LOGGING_ENABLED`: Enable/disable logging (default: `True`)
88
+ - `GLOBAL_LOG_TRACING_ENABLED`: Enable/disable detailed tracing (default: `True`)
89
+
90
+ ```python
91
+ from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
92
+ ```
93
+
94
+ ## Modules
95
+
96
+ ### Logger (`my_logging.py`)
97
+ Main logging class providing timestamped, structured logging with caller information.
98
+
99
+ **Key Features:**
100
+ - Automatic timestamping and file logging
101
+ - Caller information tracking with configurable frame filtering
102
+ - Configurable log directory and project directory
103
+ - Thread-safe file operations
104
+ - Configurable tracing depth
105
+ - Console output options
106
+ - Instance-level enable/disable controls
107
+
108
+ ### File Utilities (`file_utils.py`)
109
+ Safe file operations with comprehensive error handling:
110
+ - Path verification with security checks
111
+ - File verification for different operations (read/write/execute)
112
+ - Directory operations with working directory constraints
113
+
114
+ ### Configuration (`config.py`)
115
+ Global configuration settings for logging behavior:
116
+ - `GLOBAL_LOGGING_ENABLED`: Master switch for all logging
117
+ - `GLOBAL_LOG_TRACING_ENABLED`: Master switch for tracing features
118
+
119
+ ## Development
120
+
121
+ ### Prerequisites
122
+ - Python 3.10+
123
+
124
+ ### Installing in Development Mode
125
+ ```bash
126
+ cd logging_module
127
+ pip install -e .
128
+ ```
129
+
130
+ ### Running Tests
131
+ ```bash
132
+ # Install test dependencies
133
+ pip install -e ".[dev]"
134
+
135
+ # Run all tests
136
+ pytest
137
+
138
+ # Run with verbose output
139
+ pytest -v
140
+
141
+ # Run specific test file
142
+ pytest tests/test_my_logging.py
143
+ ```
144
+
145
+ ## License
146
+
147
+ MIT
148
+
149
+ ## Author
150
+
151
+ Nathan T Nguyen
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ logging_module/__init__.py
5
+ logging_module/config.py
6
+ logging_module/file_utils.py
7
+ logging_module/my_logging.py
8
+ logging_module.egg-info/PKG-INFO
9
+ logging_module.egg-info/SOURCES.txt
10
+ logging_module.egg-info/dependency_links.txt
11
+ logging_module.egg-info/requires.txt
12
+ logging_module.egg-info/top_level.txt
13
+ tests/test_config.py
14
+ tests/test_file_utils.py
15
+ tests/test_my_logging.py
@@ -0,0 +1,4 @@
1
+
2
+ [dev]
3
+ pytest
4
+ bump2version
@@ -0,0 +1 @@
1
+ logging_module
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools.packages.find]
6
+ # This tells build exactly which folders to treat as packages
7
+ include = ["logging_module*"]
8
+ # This ensures the 'logs' folder is ignored
9
+ exclude = ["logs*"]
10
+
11
+ [project]
12
+ name = "logging-module"
13
+ version = "0.2.0"
14
+ description = "A comprehensive logging and file utility module for Python applications"
15
+ readme = "README.md"
16
+ requires-python = ">=3.10"
17
+ license = "MIT"
18
+ authors = [
19
+ {name = "Nathan T Nguyen", email = "nhatnguyen2807@gmail.com"}
20
+ ]
21
+ keywords = ["logging", "file-utilities", "tracing", "debugging"]
22
+ classifiers = [
23
+ "Development Status :: 3 - Alpha",
24
+ "Intended Audience :: Developers",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ dev = ["pytest", "bump2version"]
34
+
35
+ [tool.pytest.ini_options]
36
+ testpaths = ["tests"]
37
+ python_files = ["test_*.py"]
38
+ python_classes = ["Test*"]
39
+ python_functions = ["test_*"]
40
+
41
+ [project.urls]
42
+ CreatorGithub = "https://github.com/NhatNguyen5"
43
+ Homepage = "https://test.pypi.org/project/logging-module/"
44
+ Documentation = "https://github.com/NhatNguyen5/logging_module/wiki/Instructions"
45
+ Repository = "https://github.com/NhatNguyen5/logging-module.git"
46
+ Issues = "https://github.com/NhatNguyen5/logging_module/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,14 @@
1
+ import pytest
2
+ from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
3
+
4
+
5
+ def test_global_logging_enabled():
6
+ """Test that GLOBAL_LOGGING_ENABLED is a boolean."""
7
+ assert isinstance(GLOBAL_LOGGING_ENABLED, bool)
8
+ assert GLOBAL_LOGGING_ENABLED is True # Default value
9
+
10
+
11
+ def test_global_log_tracing_enabled():
12
+ """Test that GLOBAL_LOG_TRACING_ENABLED is a boolean."""
13
+ assert isinstance(GLOBAL_LOG_TRACING_ENABLED, bool)
14
+ assert GLOBAL_LOG_TRACING_ENABLED is True # Default value
@@ -0,0 +1,93 @@
1
+ import os
2
+ import tempfile
3
+ import pytest
4
+ from logging_module.file_utils import (
5
+ FileUtilsError,
6
+ FileExecutionError,
7
+ FileOperator,
8
+ file_verify_path,
9
+ file_verify_file,
10
+ )
11
+
12
+
13
+ class TestFileVerifyPath:
14
+ def test_valid_directory(self):
15
+ """Test verifying a valid directory."""
16
+ with tempfile.TemporaryDirectory() as temp_dir:
17
+ sub_dir = os.path.join(temp_dir, "test_dir")
18
+ os.makedirs(sub_dir)
19
+ result = file_verify_path(temp_dir, "test_dir")
20
+ assert result == sub_dir
21
+
22
+ def test_invalid_directory_not_exists(self):
23
+ """Test verifying a non-existent directory."""
24
+ with tempfile.TemporaryDirectory() as temp_dir:
25
+ result = file_verify_path(temp_dir, "nonexistent")
26
+ assert result == FileUtilsError.NOT_A_DIRECTORY.value.format(directory="nonexistent")
27
+
28
+ def test_outside_working_directory(self):
29
+ """Test verifying a directory outside working directory."""
30
+ with tempfile.TemporaryDirectory() as temp_dir:
31
+ result = file_verify_path(temp_dir, "../outside")
32
+ assert result == FileUtilsError.OUTSIDE_WORKING_DIR.value.format(directory="../outside")
33
+
34
+
35
+ class TestFileVerifyFile:
36
+ def test_valid_read_file(self):
37
+ """Test verifying a valid file for reading."""
38
+ with tempfile.TemporaryDirectory() as temp_dir:
39
+ file_path = os.path.join(temp_dir, "test.txt")
40
+ with open(file_path, "w") as f:
41
+ f.write("test")
42
+ result = file_verify_file(temp_dir, "test.txt", FileOperator.READ_FILE)
43
+ assert result == file_path
44
+
45
+ def test_invalid_read_file_not_exists(self):
46
+ """Test verifying a non-existent file for reading."""
47
+ with tempfile.TemporaryDirectory() as temp_dir:
48
+ result = file_verify_file(temp_dir, "nonexistent.txt", FileOperator.READ_FILE)
49
+ assert result == FileUtilsError.FILE_READ_NOT_FOUND_OR_NOT_REGULAR.value.format(file_path=os.path.join(temp_dir, "nonexistent.txt"))
50
+
51
+ def test_outside_working_directory_read(self):
52
+ """Test verifying a file outside working directory for reading."""
53
+ with tempfile.TemporaryDirectory() as temp_dir:
54
+ result = file_verify_file(temp_dir, "../outside.txt", FileOperator.READ_FILE)
55
+ expected = FileUtilsError.FILE_READ_OUTSIDE_WORKING_DIR.value.format(file_path=os.path.join(temp_dir, "../outside.txt"))
56
+ assert result == expected
57
+
58
+ def test_valid_write_file_new(self):
59
+ """Test verifying a new file for writing."""
60
+ with tempfile.TemporaryDirectory() as temp_dir:
61
+ result = file_verify_file(temp_dir, "new.txt", FileOperator.WRITE_FILE)
62
+ assert result == os.path.join(temp_dir, "new.txt")
63
+
64
+ def test_outside_working_directory_write(self):
65
+ """Test verifying a file outside working directory for writing."""
66
+ with tempfile.TemporaryDirectory() as temp_dir:
67
+ result = file_verify_file(temp_dir, "../outside.txt", FileOperator.WRITE_FILE)
68
+ expected = FileUtilsError.FILE_WRITE_OUTSIDE_WORKING_DIR.value.format(file_path=os.path.join(temp_dir, "../outside.txt"))
69
+ assert result == expected
70
+
71
+ def test_valid_execute_file(self):
72
+ """Test verifying a valid Python file for execution."""
73
+ with tempfile.TemporaryDirectory() as temp_dir:
74
+ file_path = os.path.join(temp_dir, "test.py")
75
+ with open(file_path, "w") as f:
76
+ f.write("print('test')")
77
+ result = file_verify_file(temp_dir, "test.py", FileOperator.EXECUTE_FILE)
78
+ assert result == file_path
79
+
80
+ def test_invalid_execute_file_not_python(self):
81
+ """Test verifying a non-Python file for execution."""
82
+ with tempfile.TemporaryDirectory() as temp_dir:
83
+ file_path = os.path.join(temp_dir, "test.txt")
84
+ with open(file_path, "w") as f:
85
+ f.write("test")
86
+ result = file_verify_file(temp_dir, "test.txt", FileOperator.EXECUTE_FILE)
87
+ assert result == FileUtilsError.FILE_EXECUTE_NOT_PYTHON.value.format(file_path="test.txt")
88
+
89
+ def test_invalid_option(self):
90
+ """Test invalid file operation option."""
91
+ with tempfile.TemporaryDirectory() as temp_dir:
92
+ with pytest.raises(ValueError, match="Invalid file operation option provided"):
93
+ file_verify_file(temp_dir, "test.txt", "invalid")
@@ -0,0 +1,152 @@
1
+ import os
2
+ import tempfile
3
+ import pytest
4
+ from unittest.mock import patch, MagicMock
5
+ from datetime import datetime
6
+ from logging_module.my_logging import Logger
7
+ from logging_module.config import GLOBAL_LOGGING_ENABLED, GLOBAL_LOG_TRACING_ENABLED
8
+
9
+
10
+ class TestLogger:
11
+ def setup_method(self):
12
+ """Setup before each test."""
13
+ self.Logger = Logger()
14
+
15
+ def test_init(self):
16
+ """Test Logger initialization."""
17
+ assert self.Logger._enable is True
18
+ assert self.Logger._enable_log_tracing is False
19
+
20
+ @patch('logging_module.my_logging.datetime')
21
+ @patch('logging_module.my_logging.file_verify_path')
22
+ @patch('builtins.open', new_callable=MagicMock)
23
+ def test_log_basic(self, mock_open, mock_verify_path, mock_datetime):
24
+ """Test basic logging functionality."""
25
+ # Setup mocks
26
+ mock_datetime.now.return_value = datetime(2023, 5, 15, 10, 30, 45)
27
+ mock_verify_path.return_value = "/tmp/logs"
28
+
29
+ # Create a temporary directory for logs
30
+ with tempfile.TemporaryDirectory() as temp_dir:
31
+ with patch('os.path.join', return_value=os.path.join(temp_dir, "logs", "2023-05-15_logging.txt")):
32
+ with patch('os.path.isfile', return_value=False):
33
+ with patch('os.makedirs'):
34
+ self.Logger.log("Test message")
35
+
36
+ # Check that open was called for writing
37
+ assert mock_open.call_count >= 1 # Single open in append mode
38
+
39
+ @patch('logging_module.my_logging.datetime')
40
+ @patch('logging_module.my_logging.file_verify_path')
41
+ @patch('builtins.open', new_callable=MagicMock)
42
+ @patch('builtins.print')
43
+ def test_log_with_console_output(self, mock_print, mock_open, mock_verify_path, mock_datetime):
44
+ """Test logging with console output."""
45
+ mock_datetime.now.return_value = datetime(2023, 5, 15, 10, 30, 45)
46
+ mock_verify_path.return_value = "/tmp/logs"
47
+
48
+ with tempfile.TemporaryDirectory() as temp_dir:
49
+ with patch('os.path.join', return_value=os.path.join(temp_dir, "logs", "2023-05-15_logging.txt")):
50
+ with patch('os.path.isfile', return_value=True):
51
+ self.Logger.log("Test message", print_to_console=True)
52
+
53
+ # Check that print was called
54
+ mock_print.assert_called_once_with("Test message")
55
+
56
+ @patch('logging_module.my_logging.datetime')
57
+ @patch('logging_module.my_logging.file_verify_path')
58
+ @patch('builtins.open', new_callable=MagicMock)
59
+ @patch('builtins.print')
60
+ def test_log_with_custom_console_message(self, mock_print, mock_open, mock_verify_path, mock_datetime):
61
+ """Test logging with custom console message."""
62
+ mock_datetime.now.return_value = datetime(2023, 5, 15, 10, 30, 45)
63
+ mock_verify_path.return_value = "/tmp/logs"
64
+
65
+ with tempfile.TemporaryDirectory() as temp_dir:
66
+ with patch('os.path.join', return_value=os.path.join(temp_dir, "logs", "2023-05-15_logging.txt")):
67
+ with patch('os.path.isfile', return_value=True):
68
+ self.Logger.log("Test message", print_to_console=True, console_message="Custom message")
69
+
70
+ # Check that print was called with custom message
71
+ mock_print.assert_called_once_with("Custom message")
72
+
73
+ def test_log_disabled_globally(self):
74
+ """Test logging when globally disabled."""
75
+ with patch('logging_module.my_logging.GLOBAL_LOGGING_ENABLED', False):
76
+ with patch('builtins.open') as mock_open:
77
+ self.Logger.log("Test message")
78
+ mock_open.assert_not_called()
79
+
80
+ def test_log_disabled_locally(self):
81
+ """Test logging when locally disabled."""
82
+ self.Logger.enable_logging(False)
83
+ with patch('builtins.open') as mock_open:
84
+ self.Logger.log("Test message")
85
+ mock_open.assert_not_called()
86
+
87
+ @patch('logging_module.my_logging.getframeinfo')
88
+ @patch('logging_module.my_logging.stack')
89
+ def test_get_caller_info_basic(self, mock_stack, mock_getframeinfo):
90
+ """Test getting basic caller info."""
91
+ mock_frame = MagicMock()
92
+ mock_frame.filename = "/path/to/file.py"
93
+ mock_frame.lineno = 42
94
+ mock_getframeinfo.return_value = mock_frame
95
+ mock_stack.return_value = [None, None, None, mock_frame] # calldepth = 3
96
+
97
+ result = self.Logger.get_caller_info()
98
+ assert result == "file.py:42"
99
+
100
+ @patch('logging_module.my_logging.getframeinfo')
101
+ @patch('logging_module.my_logging.stack')
102
+ def test_get_caller_info_tracing_disabled(self, mock_stack, mock_getframeinfo):
103
+ """Test caller info when tracing is disabled."""
104
+ self.Logger.enable_log_tracing(False)
105
+ mock_frame = MagicMock()
106
+ mock_frame.filename = "/path/to/file.py"
107
+ mock_frame.lineno = 42
108
+ mock_getframeinfo.return_value = mock_frame
109
+ mock_stack.return_value = [None, None, None, mock_frame]
110
+
111
+ result = self.Logger.get_caller_info()
112
+ assert result == "file.py:42"
113
+
114
+ @patch('logging_module.my_logging.getframeinfo')
115
+ @patch('logging_module.my_logging.stack')
116
+ def test_get_caller_info_tracing_enabled(self, mock_stack, mock_getframeinfo):
117
+ """Test caller info when tracing is enabled."""
118
+ self.Logger.enable_log_tracing(True)
119
+ with patch('logging_module.my_logging.GLOBAL_LOG_TRACING_ENABLED', True):
120
+ mock_frame1 = MagicMock()
121
+ mock_frame1.filename = "/path/to/file1.py"
122
+ mock_frame1.lineno = 10
123
+ mock_frame2 = MagicMock()
124
+ mock_frame2.filename = "/path/to/file2.py"
125
+ mock_frame2.lineno = 20
126
+ mock_getframeinfo.side_effect = [mock_frame1, mock_frame2]
127
+ mock_stack.return_value = [None, None, None, mock_frame1, mock_frame2]
128
+
129
+ result = self.Logger.get_caller_info()
130
+ assert "file1.py:10>file2.py:20" in result
131
+
132
+ def test_enable_logging(self):
133
+ """Test enabling/disabling logging."""
134
+ self.Logger.enable_logging(False)
135
+ assert self.Logger._enable is False
136
+
137
+ self.Logger.enable_logging(True)
138
+ assert self.Logger._enable is True
139
+
140
+ def test_enable_log_tracing(self):
141
+ """Test enabling/disabling log tracing."""
142
+ self.Logger.enable_log_tracing(True)
143
+ assert self.Logger._enable_log_tracing is True
144
+
145
+ self.Logger.enable_log_tracing(False)
146
+ assert self.Logger._enable_log_tracing is False
147
+
148
+ def test_call_method(self):
149
+ """Test using Logger as callable."""
150
+ with patch.object(self.Logger, 'log') as mock_log:
151
+ self.Logger("Test message", print_to_console=True)
152
+ mock_log.assert_called_once_with("Test message", print_to_console=True, console_message="")