ansilogs 1.0.0__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.
- ansilogs/__init__.py +173 -0
- ansilogs/_version.py +24 -0
- ansilogs/py.typed +0 -0
- ansilogs-1.0.0.dist-info/METADATA +96 -0
- ansilogs-1.0.0.dist-info/RECORD +8 -0
- ansilogs-1.0.0.dist-info/WHEEL +5 -0
- ansilogs-1.0.0.dist-info/licenses/LICENSE +21 -0
- ansilogs-1.0.0.dist-info/top_level.txt +1 -0
ansilogs/__init__.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Colored, single-line log output for command-line tools.
|
|
3
|
+
|
|
4
|
+
A small, dependency-free replacement for ``coloredlogs.install()``::
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import ansilogs
|
|
8
|
+
|
|
9
|
+
ansilogs.install(logging.DEBUG)
|
|
10
|
+
|
|
11
|
+
Each record is one line, ``<time> <host> <name>[<pid>] <LEVEL> <message>``, colored with ANSI
|
|
12
|
+
escape codes when stderr is a terminal (honoring ``NO_COLOR`` and ``FORCE_COLOR``, and switching
|
|
13
|
+
Windows consoles to ANSI processing). The handler writes to whatever ``sys.stderr`` is at the
|
|
14
|
+
time of each record, so redirections (test capture, live progress displays) are respected.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import socket
|
|
20
|
+
import sys
|
|
21
|
+
from typing import IO, Optional, Union
|
|
22
|
+
|
|
23
|
+
__all__ = ["DATE_FORMAT", "ColoredFormatter", "StderrHandler", "install", "set_level", "supports_color"]
|
|
24
|
+
|
|
25
|
+
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
26
|
+
|
|
27
|
+
_RESET = "\x1b[0m"
|
|
28
|
+
_TIME = "32" # green
|
|
29
|
+
_HOST = "35" # magenta
|
|
30
|
+
_NAME = "34" # blue
|
|
31
|
+
_LEVEL_NAME = "1;30" # bold black
|
|
32
|
+
_MESSAGE_BY_LEVEL = {
|
|
33
|
+
logging.DEBUG: "32", # green
|
|
34
|
+
logging.WARNING: "33", # yellow
|
|
35
|
+
logging.ERROR: "31", # red
|
|
36
|
+
logging.CRITICAL: "1;31", # bold red
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# SetConsoleMode flag that makes a Windows console interpret ANSI escape sequences.
|
|
40
|
+
_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
|
41
|
+
_STD_ERROR_HANDLE = -12
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _sgr(code: str, text: str) -> str:
|
|
45
|
+
return f"\x1b[{code}m{text}{_RESET}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ColoredFormatter(logging.Formatter):
|
|
49
|
+
"""
|
|
50
|
+
``<time> <host> <name>[<pid>] <LEVEL> <message>``, with ANSI colors when ``colors`` is set.
|
|
51
|
+
|
|
52
|
+
The colors are baked into the format string once; per record only the message is wrapped in
|
|
53
|
+
its level's color. Tracebacks and stack info are appended uncolored.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, colors: bool = True, hostname: Optional[str] = None) -> None:
|
|
57
|
+
host = (hostname or socket.gethostname()).replace("%", "%%")
|
|
58
|
+
fields = [
|
|
59
|
+
("%(asctime)s", _TIME),
|
|
60
|
+
(host, _HOST),
|
|
61
|
+
("%(name)s[%(process)d]", _NAME),
|
|
62
|
+
("%(levelname)s", _LEVEL_NAME),
|
|
63
|
+
]
|
|
64
|
+
prefix = " ".join(_sgr(code, text) if colors else text for text, code in fields)
|
|
65
|
+
super().__init__(f"{prefix} %(message)s", datefmt=DATE_FORMAT)
|
|
66
|
+
self.colors = colors
|
|
67
|
+
|
|
68
|
+
def formatMessage(self, record: logging.LogRecord) -> str:
|
|
69
|
+
code = _MESSAGE_BY_LEVEL.get(record.levelno) if self.colors else None
|
|
70
|
+
if code is None:
|
|
71
|
+
return super().formatMessage(record)
|
|
72
|
+
message = record.message
|
|
73
|
+
record.message = _sgr(code, message)
|
|
74
|
+
try:
|
|
75
|
+
return super().formatMessage(record)
|
|
76
|
+
finally:
|
|
77
|
+
record.message = message
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class StderrHandler(logging.Handler):
|
|
81
|
+
"""Writes each record to the current ``sys.stderr`` (looked up per record, never cached)."""
|
|
82
|
+
|
|
83
|
+
terminator = "\n"
|
|
84
|
+
|
|
85
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
86
|
+
try:
|
|
87
|
+
line = self.format(record)
|
|
88
|
+
stream = sys.stderr
|
|
89
|
+
if stream is None:
|
|
90
|
+
return
|
|
91
|
+
stream.write(line + self.terminator)
|
|
92
|
+
stream.flush()
|
|
93
|
+
except RecursionError:
|
|
94
|
+
raise
|
|
95
|
+
except Exception:
|
|
96
|
+
self.handleError(record)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _enable_windows_ansi(stream: IO[str]) -> bool:
|
|
100
|
+
"""Switch the Windows console behind ``stream`` to ANSI processing; False if it isn't a console."""
|
|
101
|
+
if sys.platform != "win32":
|
|
102
|
+
return True
|
|
103
|
+
import ctypes
|
|
104
|
+
from ctypes import wintypes
|
|
105
|
+
|
|
106
|
+
if stream is not sys.__stderr__:
|
|
107
|
+
# Only the process' own stderr maps to the standard error console handle.
|
|
108
|
+
return False
|
|
109
|
+
kernel32 = ctypes.windll.kernel32
|
|
110
|
+
handle = kernel32.GetStdHandle(_STD_ERROR_HANDLE)
|
|
111
|
+
mode = wintypes.DWORD()
|
|
112
|
+
if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
|
|
113
|
+
return False
|
|
114
|
+
if mode.value & _ENABLE_VIRTUAL_TERMINAL_PROCESSING:
|
|
115
|
+
return True
|
|
116
|
+
return bool(kernel32.SetConsoleMode(handle, mode.value | _ENABLE_VIRTUAL_TERMINAL_PROCESSING))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def supports_color(stream: Optional[IO[str]] = None) -> bool:
|
|
120
|
+
"""
|
|
121
|
+
Whether to color output written to ``stream`` (default: ``sys.stderr``).
|
|
122
|
+
|
|
123
|
+
``NO_COLOR`` (non-empty) disables colors and ``FORCE_COLOR`` (non-empty) forces them;
|
|
124
|
+
otherwise the stream must be a terminal, and on Windows a console that accepts ANSI codes.
|
|
125
|
+
"""
|
|
126
|
+
if os.environ.get("NO_COLOR"):
|
|
127
|
+
return False
|
|
128
|
+
if os.environ.get("FORCE_COLOR"):
|
|
129
|
+
return True
|
|
130
|
+
stream = sys.stderr if stream is None else stream
|
|
131
|
+
if stream is None:
|
|
132
|
+
return False
|
|
133
|
+
isatty = getattr(stream, "isatty", None)
|
|
134
|
+
try:
|
|
135
|
+
if isatty is None or not isatty():
|
|
136
|
+
return False
|
|
137
|
+
except ValueError: # closed stream
|
|
138
|
+
return False
|
|
139
|
+
return _enable_windows_ansi(stream)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _installed_handler() -> Optional[StderrHandler]:
|
|
143
|
+
return next((h for h in logging.getLogger().handlers if isinstance(h, StderrHandler)), None)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def install(level: Union[int, str] = logging.INFO, colors: Optional[bool] = None) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Attach a colored stderr handler to the root logger and show records from ``level`` up.
|
|
149
|
+
|
|
150
|
+
Calling it again reconfigures the existing handler instead of adding another. ``colors``
|
|
151
|
+
defaults to :func:`supports_color` for the current ``sys.stderr``.
|
|
152
|
+
"""
|
|
153
|
+
handler = _installed_handler()
|
|
154
|
+
if handler is None:
|
|
155
|
+
handler = StderrHandler()
|
|
156
|
+
logging.getLogger().addHandler(handler)
|
|
157
|
+
handler.setFormatter(ColoredFormatter(colors=supports_color() if colors is None else colors))
|
|
158
|
+
set_level(level)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def set_level(level: Union[int, str]) -> None:
|
|
162
|
+
"""
|
|
163
|
+
Show records from ``level`` up: sets the handler's level (installing it if needed) and relaxes
|
|
164
|
+
the root logger's level if it would filter those records out.
|
|
165
|
+
"""
|
|
166
|
+
handler = _installed_handler()
|
|
167
|
+
if handler is None:
|
|
168
|
+
install(level)
|
|
169
|
+
return
|
|
170
|
+
handler.setLevel(level)
|
|
171
|
+
root = logging.getLogger()
|
|
172
|
+
if root.getEffectiveLevel() > handler.level:
|
|
173
|
+
root.setLevel(handler.level)
|
ansilogs/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '1.0.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'gaa8e027c2'
|
ansilogs/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ansilogs
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Colored, single-line log output for command-line tools: a dependency-free coloredlogs.install() replacement
|
|
5
|
+
Author-email: doronz88 <doron88@gmail.com>
|
|
6
|
+
Maintainer-email: doronz88 <doron88@gmail.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 doronz88
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://github.com/doronz88/ansilogs
|
|
30
|
+
Project-URL: Bug Reports, https://github.com/doronz88/ansilogs/issues
|
|
31
|
+
Keywords: logging,color,ansi,terminal,cli
|
|
32
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
42
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
43
|
+
Classifier: Topic :: System :: Logging
|
|
44
|
+
Classifier: Typing :: Typed
|
|
45
|
+
Requires-Python: >=3.9
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
License-File: LICENSE
|
|
48
|
+
Provides-Extra: test
|
|
49
|
+
Requires-Dist: pytest; extra == "test"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
# ansilogs
|
|
53
|
+
|
|
54
|
+
Colored, single-line log output for command-line tools. A small, dependency-free replacement
|
|
55
|
+
for `coloredlogs.install()` with the same default look.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import logging
|
|
59
|
+
|
|
60
|
+
import ansilogs
|
|
61
|
+
|
|
62
|
+
ansilogs.install(logging.DEBUG)
|
|
63
|
+
logging.getLogger(__name__).warning("careful")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
2026-10-19 09:35:12 my-host my_tool.cli[4242] WARNING careful
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- One line per record: `<time> <host> <name>[<pid>] <LEVEL> <message>`; tracebacks follow
|
|
71
|
+
uncolored.
|
|
72
|
+
- Colors only when stderr is a terminal. `NO_COLOR` (non-empty) disables them, `FORCE_COLOR`
|
|
73
|
+
(non-empty) forces them. On Windows the console is switched to ANSI processing; if that
|
|
74
|
+
fails, output is plain.
|
|
75
|
+
- Records go to whatever `sys.stderr` is at the time (test capture and live progress displays
|
|
76
|
+
that redirect stderr keep working).
|
|
77
|
+
|
|
78
|
+
## API
|
|
79
|
+
|
|
80
|
+
- `install(level=logging.INFO, colors=None)`: attach the handler to the root logger (once) and
|
|
81
|
+
show records from `level` up. `colors=None` means `supports_color()`.
|
|
82
|
+
- `set_level(level)`: change the shown level (installs the handler if needed).
|
|
83
|
+
- `supports_color(stream=None)`: the color decision used by `install()`.
|
|
84
|
+
- `ColoredFormatter(colors=True, hostname=None)` and `StderrHandler` for custom setups.
|
|
85
|
+
|
|
86
|
+
## Migrating from coloredlogs
|
|
87
|
+
|
|
88
|
+
```diff
|
|
89
|
+
-import coloredlogs
|
|
90
|
+
-coloredlogs.install(level=logging.DEBUG)
|
|
91
|
+
+import ansilogs
|
|
92
|
+
+ansilogs.install(logging.DEBUG)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`coloredlogs.set_level(level)` becomes `ansilogs.set_level(level)`. `COLOREDLOGS_*` environment
|
|
96
|
+
variables and custom field/level styles are not supported.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ansilogs/__init__.py,sha256=nBjnREOzvlPmODpcCawH07WIFg_YB3LytcPaQSSiKQc,5959
|
|
2
|
+
ansilogs/_version.py,sha256=AJPm44ajQjO1SQUg8c6rfc3xjhbaff-iRq7PTfDEQd8,528
|
|
3
|
+
ansilogs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
ansilogs-1.0.0.dist-info/licenses/LICENSE,sha256=yLCk8rOuuGsdobWBq_7cZBbX5LgGqK7cwF081kL9AzU,1065
|
|
5
|
+
ansilogs-1.0.0.dist-info/METADATA,sha256=D92rIu5-iRhYyDcza_oUz3zfJeEPme839TMXpR6k60g,4008
|
|
6
|
+
ansilogs-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
7
|
+
ansilogs-1.0.0.dist-info/top_level.txt,sha256=9HngRK4yiRWsJDSHFiaOx-h61aDiR6Mm1PfDBQ11MIo,9
|
|
8
|
+
ansilogs-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 doronz88
|
|
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
|
+
ansilogs
|