pypomes-logging 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.
@@ -0,0 +1,16 @@
1
+ .cache
2
+ .env
3
+ .venv
4
+ .idea
5
+ .vscode
6
+ env
7
+ venv
8
+ temp
9
+ tmp
10
+ **/__pycache__
11
+ *.py[cod]
12
+ *.sh
13
+ *.ps1
14
+ /dist
15
+ /env
16
+ deploy.txt
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 GT Nunes
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,18 @@
1
+ Metadata-Version: 2.3
2
+ Name: pypomes_logging
3
+ Version: 0.0.1
4
+ Summary: A collection of Python pomes, pennyeach (logging module)
5
+ Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-Logging
6
+ Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-Logging/issues
7
+ Author-email: GT Nunes <wisecoder01@gmail.com>
8
+ License-File: LICENSE
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: flask>=3.0.2
14
+ Requires-Dist: pip>=24.0
15
+ Requires-Dist: pypomes-core>=0.8.3
16
+ Requires-Dist: python-dateutil>=2.8.2
17
+ Requires-Dist: setuptools>=68.0.0
18
+ Requires-Dist: wheel>=0.42.0
File without changes
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = [
3
+ "hatchling>=1.22.2"
4
+ ]
5
+ build-backend = "hatchling.build"
6
+
7
+ [project]
8
+ name = "pypomes_logging"
9
+ version = "0.0.1"
10
+ authors = [
11
+ { name="GT Nunes", email="wisecoder01@gmail.com" }
12
+ ]
13
+ description = "A collection of Python pomes, pennyeach (logging module)"
14
+ readme = "README.md"
15
+ requires-python = ">=3.10"
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent"
20
+ ]
21
+ dependencies = [
22
+ "Flask>=3.0.2",
23
+ "pip>=24.0",
24
+ "pypomes-core>=0.8.3",
25
+ "python-dateutil>=2.8.2",
26
+ "setuptools>=68.0.0",
27
+ "wheel>=0.42.0"
28
+ ]
29
+
30
+ [project.urls]
31
+ "Homepage" = "https://github.com/TheWiseCoder/PyPomes-Logging"
32
+ "Bug Tracker" = "https://github.com/TheWiseCoder/PyPomes-Logging/issues"
@@ -0,0 +1,20 @@
1
+ from .logging_pomes import (
2
+ LOGGING_ID, LOGGING_LEVEL, LOGGING_FORMAT, LOGGING_STYLE,
3
+ LOGGING_FILE_PATH, LOGGING_FILE_MODE, PYPOMES_LOGGER,
4
+ logging_get_entries, logging_get_entries_from_request,
5
+ logging_log_msgs, logging_log_debug, logging_log_error,
6
+ logging_log_info, logging_log_critical, logging_log_warning,
7
+ )
8
+
9
+ __all__ = [
10
+ # logging_pomes
11
+ "LOGGING_ID", "LOGGING_LEVEL", "LOGGING_FORMAT", "LOGGING_STYLE",
12
+ "LOGGING_FILE_PATH", "LOGGING_FILE_MODE", "PYPOMES_LOGGER",
13
+ "logging_get_entries", "logging_get_entries_from_request",
14
+ "logging_log_msgs", "logging_log_debug", "logging_log_error",
15
+ "logging_log_info", "logging_log_critical", "logging_log_warning",
16
+ ]
17
+
18
+ from importlib.metadata import version
19
+ __version__ = version("pypomes_logging")
20
+ __version_info__ = tuple(int(i) for i in __version__.split(".") if i.isdigit())
@@ -0,0 +1,321 @@
1
+ import json
2
+ import logging
3
+ from datetime import datetime
4
+ from dateutil import parser
5
+ from flask import Request, Response, send_file
6
+ from io import BytesIO
7
+ from pathlib import Path
8
+ from typing import Final, Literal, TextIO
9
+
10
+ from pypomes_core import (
11
+ APP_PREFIX, DATETIME_FORMAT_INV, TEMP_DIR,
12
+ env_get_str, env_get_path
13
+ )
14
+
15
+
16
+ def __get_logging_level(level: Literal["debug", "info", "warning", "error", "critical"]) -> int:
17
+ """
18
+ Translate the log severity string *level* into the logging's internal severity value.
19
+
20
+ :param level: the string log severity
21
+ :return: the internal logging severity value
22
+ """
23
+ result: int | None
24
+ match level:
25
+ case "debug":
26
+ result = logging.DEBUG # 10
27
+ case "info":
28
+ result = logging.INFO # 20
29
+ case "warning":
30
+ result = logging.WARN # 30
31
+ case "error":
32
+ result = logging.ERROR # 40
33
+ case "critical":
34
+ result = logging.CRITICAL # 50
35
+ case _:
36
+ result = logging.NOTSET # 0
37
+
38
+ return result
39
+
40
+
41
+ LOGGING_ID: Final[str] = env_get_str(f"{APP_PREFIX}_LOGGING_ID", f"{APP_PREFIX}")
42
+ LOGGING_FORMAT: Final[str] = env_get_str(f"{APP_PREFIX}_LOGGING_FORMAT",
43
+ "{asctime} {levelname:1.1} {thread:5d} "
44
+ "{module:20.20} {funcName:20.20} {lineno:3d} {message}")
45
+ LOGGING_STYLE: Final[str] = env_get_str(f"{APP_PREFIX}_LOGGING_STYLE", "{")
46
+
47
+ LOGGING_FILE_PATH: Final[Path] = env_get_path(f"{APP_PREFIX}_LOGGING_FILE_PATH",
48
+ TEMP_DIR / f"{APP_PREFIX}.log")
49
+ LOGGING_FILE_MODE: Final[str] = env_get_str(f"{APP_PREFIX}_LOGGING_FILE_MODE", "a")
50
+
51
+ # define and configure the logger
52
+ PYPOMES_LOGGER: Final[logging.Logger] = logging.getLogger(LOGGING_ID)
53
+
54
+ # define the logging severity level
55
+ # noinspection PyTypeChecker
56
+ LOGGING_LEVEL: Final[int] = __get_logging_level(env_get_str(f"{APP_PREFIX}_LOGGING_LEVEL"))
57
+
58
+ # configure the logger
59
+ # noinspection PyTypeChecker
60
+ logging.basicConfig(filename=LOGGING_FILE_PATH,
61
+ filemode=LOGGING_FILE_MODE,
62
+ format=LOGGING_FORMAT,
63
+ datefmt=DATETIME_FORMAT_INV,
64
+ style=LOGGING_STYLE,
65
+ level=LOGGING_LEVEL)
66
+ for _handler in logging.root.handlers:
67
+ _handler.addFilter(logging.Filter(LOGGING_ID))
68
+
69
+
70
+ def logging_get_entries(errors: list[str],
71
+ log_level: Literal["debug", "info", "warning", "error", "critical"] = None,
72
+ log_from: str = None, log_to: str = None,
73
+ log_path: Path | str = LOGGING_FILE_PATH) -> BytesIO:
74
+ """
75
+ Extract and return all entries in the logging file *log_path*.
76
+
77
+ It is expected for this logging file to be compliant with *PYPOMES_LOGGER*'s default format.
78
+ The extraction meets the criteria specified by *log_level*, and by the inclusive interval *[log_from, log_to]*.
79
+
80
+ :param errors: incidental error messages
81
+ :param log_level: the logging level (defaults to all levels)
82
+ :param log_from: the initial timestamp (defaults to unspecified)
83
+ :param log_to: the finaL timestamp (defaults to unspecified)
84
+ :param log_path: the path of the log file
85
+ :return: the logging entries meeting the specified criteria
86
+ """
87
+ # inicializa variável de retorno
88
+ result: BytesIO | None = None
89
+
90
+ # obtain the logging level
91
+ # noinspection PyTypeChecker
92
+ logging_level: int = __get_logging_level(log_level)
93
+
94
+ # obtain the initial timestamp
95
+ from_stamp: datetime | None = None
96
+ if log_from:
97
+ from_stamp = parser.parse(log_from)
98
+ if not from_stamp:
99
+ errors.append(f"Value '{from_stamp}' of 'from' attribute invalid")
100
+
101
+ # obtain the final timestamp
102
+ to_stamp: datetime | None = None
103
+ if log_to:
104
+ to_stamp = parser.parse(log_to)
105
+ if not to_stamp or \
106
+ (from_stamp and from_stamp > to_stamp):
107
+ errors.append(f"Value '{to_stamp}' of 'to' attribute invalid")
108
+
109
+ file_path: Path = Path(log_path)
110
+ # does the log file exist ?
111
+ if not Path.exists(file_path):
112
+ # no, report the error
113
+ errors.append(f"File '{file_path}' not found")
114
+
115
+ # any error ?
116
+ if len(errors) == 0:
117
+ # no, proceed
118
+ result = BytesIO()
119
+ with Path.open(file_path) as f:
120
+ line: str = f.readline()
121
+ while line:
122
+ items: list[str] = line.split(maxsplit=3)
123
+ # noinspection PyTypeChecker
124
+ msg_level: int = __get_logging_level(items[2])
125
+ if msg_level >= logging_level:
126
+ timestamp: datetime = parser.parse(f"{items[0]} {items[1]}")
127
+ if (not from_stamp or timestamp >= from_stamp) and \
128
+ (not to_stamp or timestamp <= to_stamp):
129
+ result.write(line.encode())
130
+ line = f.readline()
131
+
132
+ return result
133
+
134
+
135
+ def logging_get_entries_from_request(request: Request, as_attachment: bool = False) -> Response:
136
+ """
137
+ Retrieve from the log file, and return, the entries matching the criteria specified.
138
+
139
+ These criteria are specified in the query string of the HTTP request, according to the pattern
140
+ *path=<log-path>&level=<log-level>&from=YYYYMMDDhhmmss&to=YYYYMMDDhhmmss>*
141
+
142
+ All criteria are optional:
143
+ - path: the path of the log file
144
+ - level: the logging level of the entries
145
+ - from: the start timestamp
146
+ - to: the finish timestamp
147
+
148
+ :param request: the HTTP request
149
+ :param as_attachment: indicate to browser that it should offer to save the file, or just display it
150
+ :return: file containing the log entries requested on success, or incidental errors on fail
151
+ """
152
+ # declare the return variable
153
+ result: Response
154
+
155
+ # initialize the error messages list
156
+ errors: list[str] = []
157
+
158
+ # obtain the logging level
159
+ log_level: str = request.args.get("level")
160
+
161
+ # obtain the initial and final timestamps
162
+ log_from: str = request.args.get("from")
163
+ log_to: str = request.args.get("to")
164
+
165
+ # obtain the path for the log file
166
+ log_path: str = request.args.get("path") or LOGGING_FILE_PATH
167
+
168
+ # retrieve the log entries
169
+ # noinspection PyTypeChecker
170
+ log_entries: BytesIO = logging_get_entries(errors, log_level, log_from, log_to, log_path)
171
+
172
+ # any error ?
173
+ if len(errors) == 0:
174
+ # no, return the log entries requested as an attached file
175
+ base: str = "entries" if not log_from or not log_to else \
176
+ (
177
+ f"{''.join(ch for ch in log_from if ch.isdigit())}"
178
+ f"{'_'.join(ch for ch in log_to if ch.isdigit())}"
179
+ )
180
+ log_file = f"log_{base}.log"
181
+ log_entries.seek(0)
182
+ result = send_file(path_or_file=log_entries,
183
+ mimetype="text/plain",
184
+ as_attachment=as_attachment,
185
+ download_name=log_file)
186
+ else:
187
+ # yes, report the failure
188
+ result = Response(json.dumps({"errors": errors}), status=400, mimetype="application/json")
189
+
190
+ return result
191
+
192
+
193
+ def logging_log_msgs(msgs: list[str], output_dev: TextIO = None,
194
+ log_level: Literal["debug", "info", "warning", "error", "critical"] = "error",
195
+ logger: logging.Logger = PYPOMES_LOGGER) -> None:
196
+ """
197
+ Write all messages in *msgs* to *logger*'s logging file, and to *output_dev*.
198
+
199
+ The output device is tipically *sys.stdout* or *sys.stderr*.
200
+
201
+ :param msgs: the messages list
202
+ :param output_dev: output device where the message is to be printed (None for no device printing)
203
+ :param log_level: the logging level, defaults to 'error' (None for no logging)
204
+ :param logger: the logger to use
205
+ """
206
+ # define the log writer
207
+ log_writer: callable = None
208
+ match log_level:
209
+ case "debug":
210
+ log_writer = logger.debug
211
+ case "info":
212
+ log_writer = logger.info
213
+ case "warning":
214
+ log_writer = logger.warning
215
+ case "error":
216
+ log_writer = logger.error
217
+ case "critical":
218
+ log_writer = logger.critical
219
+
220
+ # traverse the messages list
221
+ for msg in msgs:
222
+ # has the log writer been defined ?
223
+ if log_writer:
224
+ # yes, log the message
225
+ log_writer(msg)
226
+
227
+ # write to output
228
+ __write_to_output(msg, output_dev)
229
+
230
+
231
+ def logging_log_debug(msg: str, output_dev: TextIO = None,
232
+ logger: logging.Logger = PYPOMES_LOGGER) -> None:
233
+ """
234
+ Write debug-level message *msg* to *logger*'s logging file, and to *output_dev*.
235
+
236
+ The output device is tipically *sys.stdout* or *sys.stderr*.
237
+
238
+ :param msg: the message to log
239
+ :param output_dev: output device where the message is to be printed (None for no device printing)
240
+ :param logger: the logger to use
241
+ """
242
+ # log the message
243
+ logger.debug(msg)
244
+ __write_to_output(msg, output_dev)
245
+
246
+
247
+ def logging_log_info(msg: str, output_dev: TextIO = None,
248
+ logger: logging.Logger = PYPOMES_LOGGER) -> None:
249
+ """
250
+ Write info-level message *msg* to *logger*'s logging file, and to *output_dev*.
251
+
252
+ The output device is tipically *sys.stdout* or *sys.stderr*.
253
+
254
+ :param msg: the message to log
255
+ :param output_dev: output device where the message is to be printed (None for no device printing)
256
+ :param logger: the logger to use
257
+ """
258
+ # log the message
259
+ logger.info(msg)
260
+ __write_to_output(msg, output_dev)
261
+
262
+
263
+ def logging_log_warning(msg: str, output_dev: TextIO = None,
264
+ logger: logging.Logger = PYPOMES_LOGGER) -> None:
265
+ """
266
+ Write warning-level message *msg* to *logger*'s logging file, and to *output_dev*.
267
+
268
+ The output device is tipically *sys.stdout* or *sys.stderr*.
269
+
270
+ :param msg: the message to log
271
+ :param output_dev: output device where the message is to be printed (None for no device printing)
272
+ :param logger: the logger to use
273
+ """
274
+ # log the message
275
+ logger.warning(msg)
276
+ __write_to_output(msg, output_dev)
277
+
278
+
279
+ def logging_log_error(msg: str, output_dev: TextIO = None,
280
+ logger: logging.Logger = PYPOMES_LOGGER) -> None:
281
+ """
282
+ Write error-level message *msg* to *logger*'s logging file, and to *output_dev*.
283
+
284
+ The output device is tipically *sys.stdout* or *sys.stderr*.
285
+
286
+ :param msg: the message to log
287
+ :param output_dev: output device where the message is to be printed (None for no device printing)
288
+ :param logger: the logger to use
289
+ """
290
+ # log the message
291
+ logger.error(msg)
292
+ __write_to_output(msg, output_dev)
293
+
294
+
295
+ def logging_log_critical(msg: str, output_dev: TextIO = None,
296
+ logger: logging.Logger = PYPOMES_LOGGER) -> None:
297
+ """
298
+ Write critical-level message *msg* to *logger*'s logging file, and to *output_dev*.
299
+
300
+ The output device is tipically *sys.stdout* or *sys.stderr*.
301
+
302
+ :param msg: the message to log
303
+ :param output_dev: output device where the message is to be printed (None for no device printing)
304
+ :param logger: the logger to use
305
+ """
306
+ # log the message
307
+ logger.critical(msg)
308
+ __write_to_output(msg, output_dev)
309
+
310
+
311
+ def __write_to_output(msg: str, output_dev: TextIO) -> None:
312
+
313
+ # has the output device been defined ?
314
+ if output_dev:
315
+ # yes, write the message to it
316
+ output_dev.write(msg)
317
+
318
+ # is the output device 'stderr' ou 'stdout' ?
319
+ if output_dev.name.startswith("<std"):
320
+ # yes, skip to the next line
321
+ output_dev.write("\n")