veronix 1.0.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.
- veronix-1.0.0/CHANGELOG.md +7 -0
- veronix-1.0.0/LICENSE +24 -0
- veronix-1.0.0/MANIFEST.in +2 -0
- veronix-1.0.0/PKG-INFO +171 -0
- veronix-1.0.0/README.md +113 -0
- veronix-1.0.0/pyproject.toml +55 -0
- veronix-1.0.0/setup.cfg +4 -0
- veronix-1.0.0/src/veronix/__about__.py +15 -0
- veronix-1.0.0/src/veronix/__init__.py +29 -0
- veronix-1.0.0/src/veronix/__main__.py +6 -0
- veronix-1.0.0/src/veronix/cli.py +80 -0
- veronix-1.0.0/src/veronix/colors.py +64 -0
- veronix-1.0.0/src/veronix/formatter.py +61 -0
- veronix-1.0.0/src/veronix/handlers.py +39 -0
- veronix-1.0.0/src/veronix/levels.py +41 -0
- veronix-1.0.0/src/veronix/logger.py +192 -0
- veronix-1.0.0/src/veronix/py.typed +0 -0
- veronix-1.0.0/src/veronix.egg-info/PKG-INFO +171 -0
- veronix-1.0.0/src/veronix.egg-info/SOURCES.txt +21 -0
- veronix-1.0.0/src/veronix.egg-info/dependency_links.txt +1 -0
- veronix-1.0.0/src/veronix.egg-info/entry_points.txt +2 -0
- veronix-1.0.0/src/veronix.egg-info/top_level.txt +1 -0
- veronix-1.0.0/tests/test_veronix.py +82 -0
veronix-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
VERONIX PROPRIETARY LICENSE - ALL RIGHTS RESERVED
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 1tz.raj (GitHub: 7rzraj). All rights reserved.
|
|
4
|
+
|
|
5
|
+
1. GRANT. You may install Veronix with "pip install veronix" and use it, free
|
|
6
|
+
of charge, in personal, educational and commercial projects, including as a
|
|
7
|
+
dependency of your own software.
|
|
8
|
+
|
|
9
|
+
2. RESTRICTIONS. Without prior written permission from the author you may NOT:
|
|
10
|
+
a) republish, re-upload or redistribute Veronix (original or modified, in
|
|
11
|
+
source or binary form) on PyPI, GitHub, any package index, website,
|
|
12
|
+
marketplace or any other platform;
|
|
13
|
+
b) sell, sublicense, rent or claim Veronix as your own work;
|
|
14
|
+
c) rename, rebrand or fork it into a competing or look-alike package;
|
|
15
|
+
d) remove or alter copyright notices, author credits or this license.
|
|
16
|
+
|
|
17
|
+
3. ENFORCEMENT. Any republication or unauthorised copying is copyright
|
|
18
|
+
infringement. The author may file takedown notices (e.g. DMCA) with PyPI,
|
|
19
|
+
GitHub and hosting providers and pursue legal remedies.
|
|
20
|
+
|
|
21
|
+
4. NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
22
|
+
THE AUTHOR IS NOT LIABLE FOR ANY CLAIM OR DAMAGES ARISING FROM ITS USE.
|
|
23
|
+
|
|
24
|
+
Contact: Instagram @1tz.raj_ | GitHub @7rzraj | Discord 1tz.raj
|
veronix-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: veronix
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Clean, professional and colourful console logging for Python - by 1tz.raj
|
|
5
|
+
Author: 1tz.raj
|
|
6
|
+
Maintainer: 1tz.raj
|
|
7
|
+
License: VERONIX PROPRIETARY LICENSE - ALL RIGHTS RESERVED
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 1tz.raj (GitHub: 7rzraj). All rights reserved.
|
|
10
|
+
|
|
11
|
+
1. GRANT. You may install Veronix with "pip install veronix" and use it, free
|
|
12
|
+
of charge, in personal, educational and commercial projects, including as a
|
|
13
|
+
dependency of your own software.
|
|
14
|
+
|
|
15
|
+
2. RESTRICTIONS. Without prior written permission from the author you may NOT:
|
|
16
|
+
a) republish, re-upload or redistribute Veronix (original or modified, in
|
|
17
|
+
source or binary form) on PyPI, GitHub, any package index, website,
|
|
18
|
+
marketplace or any other platform;
|
|
19
|
+
b) sell, sublicense, rent or claim Veronix as your own work;
|
|
20
|
+
c) rename, rebrand or fork it into a competing or look-alike package;
|
|
21
|
+
d) remove or alter copyright notices, author credits or this license.
|
|
22
|
+
|
|
23
|
+
3. ENFORCEMENT. Any republication or unauthorised copying is copyright
|
|
24
|
+
infringement. The author may file takedown notices (e.g. DMCA) with PyPI,
|
|
25
|
+
GitHub and hosting providers and pursue legal remedies.
|
|
26
|
+
|
|
27
|
+
4. NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
28
|
+
THE AUTHOR IS NOT LIABLE FOR ANY CLAIM OR DAMAGES ARISING FROM ITS USE.
|
|
29
|
+
|
|
30
|
+
Contact: Instagram @1tz.raj_ | GitHub @7rzraj | Discord 1tz.raj
|
|
31
|
+
|
|
32
|
+
Project-URL: Homepage, https://github.com/7rzraj/veronix
|
|
33
|
+
Project-URL: Documentation, https://github.com/7rzraj/veronix#readme
|
|
34
|
+
Project-URL: Source, https://github.com/7rzraj/veronix
|
|
35
|
+
Project-URL: Issues, https://github.com/7rzraj/veronix/issues
|
|
36
|
+
Project-URL: Changelog, https://github.com/7rzraj/veronix/blob/main/CHANGELOG.md
|
|
37
|
+
Project-URL: Developer, https://github.com/7rzraj
|
|
38
|
+
Project-URL: Instagram, https://instagram.com/1tz.raj_
|
|
39
|
+
Keywords: logging,logger,colorful logs,console logging,python logger,colored terminal,ansi colors,veronix,1tz.raj,7rzraj,cli logging
|
|
40
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
41
|
+
Classifier: Intended Audience :: Developers
|
|
42
|
+
Classifier: License :: Other/Proprietary License
|
|
43
|
+
Classifier: Operating System :: OS Independent
|
|
44
|
+
Classifier: Programming Language :: Python :: 3
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
47
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
48
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
49
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
50
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
51
|
+
Classifier: Topic :: System :: Logging
|
|
52
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
53
|
+
Classifier: Typing :: Typed
|
|
54
|
+
Requires-Python: >=3.8
|
|
55
|
+
Description-Content-Type: text/markdown
|
|
56
|
+
License-File: LICENSE
|
|
57
|
+
Dynamic: license-file
|
|
58
|
+
|
|
59
|
+
<div align="center">
|
|
60
|
+
|
|
61
|
+
<img src="https://raw.githubusercontent.com/7rzraj/veronix/main/assets/banner.png" alt="Veronix - colourful Python logging by 1tz.raj" width="100%">
|
|
62
|
+
|
|
63
|
+
[](https://pypi.org/project/veronix/)
|
|
64
|
+
[](https://pypi.org/project/veronix/)
|
|
65
|
+
[](https://github.com/7rzraj/veronix/actions)
|
|
66
|
+
[](LICENSE)
|
|
67
|
+
[](https://pypi.org/project/veronix/)
|
|
68
|
+
|
|
69
|
+
Made with ❤️ by **[1tz.raj](https://github.com/7rzraj)**
|
|
70
|
+
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div align="center">
|
|
74
|
+
<img src="https://raw.githubusercontent.com/7rzraj/veronix/main/assets/demo-screenshot.png" alt="Veronix demo output in terminal" width="85%">
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 📦 Installation
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install veronix
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 🚀 Usage
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import veronix
|
|
89
|
+
|
|
90
|
+
veronix.info("Server started", port=8000)
|
|
91
|
+
veronix.success("Database connected")
|
|
92
|
+
veronix.warning("Disk almost full")
|
|
93
|
+
veronix.error("Payment failed", order_id=1042)
|
|
94
|
+
veronix.critical("System down!")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Levels
|
|
98
|
+
`TRACE` · `DEBUG` · `INFO` · `SUCCESS` · `WARNING` · `ERROR` · `CRITICAL`
|
|
99
|
+
|
|
100
|
+
### Configure
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
veronix.configure(level="DEBUG", file="logs/app.log") # save to file (auto-rotating)
|
|
104
|
+
veronix.configure(json_mode=True) # JSON logs
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Named loggers
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
api = veronix.get_logger("api")
|
|
111
|
+
api.info("GET /users 200")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Timer, decorator, exceptions
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
with veronix.timer("db query"):
|
|
118
|
+
run_query()
|
|
119
|
+
|
|
120
|
+
@veronix.log_calls
|
|
121
|
+
def work(): ...
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
1 / 0
|
|
125
|
+
except ZeroDivisionError:
|
|
126
|
+
veronix.exception("Something broke") # prints full traceback
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Banner & divider
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
veronix.banner("My App v1.0")
|
|
133
|
+
veronix.rule("Startup")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 💻 Command line
|
|
137
|
+
|
|
138
|
+
| Command | What it does |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `veronix demo` | See all colours and levels |
|
|
141
|
+
| `veronix info` | Developer and package details |
|
|
142
|
+
| `veronix levels` | List log levels |
|
|
143
|
+
| `veronix log "text" -l ERROR` | Print one log line |
|
|
144
|
+
| `veronix --version` | Show version |
|
|
145
|
+
|
|
146
|
+
## ✨ Features
|
|
147
|
+
- Auto colour detection (respects `NO_COLOR` / `FORCE_COLOR`), works on Windows, Linux, macOS
|
|
148
|
+
- Zero dependencies
|
|
149
|
+
- File logging with rotation, JSON output, thread-safe
|
|
150
|
+
- Fully typed (`py.typed`)
|
|
151
|
+
|
|
152
|
+
## 👨💻 Developer
|
|
153
|
+
|
|
154
|
+
<img src="https://raw.githubusercontent.com/7rzraj/veronix/main/assets/avatar.png" width="110" align="right" alt="1tz.raj">
|
|
155
|
+
|
|
156
|
+
| | |
|
|
157
|
+
|---|---|
|
|
158
|
+
| **Name** | 1tz.raj |
|
|
159
|
+
| **GitHub** | [@7rzraj](https://github.com/7rzraj) |
|
|
160
|
+
| **Instagram** | [@1tz.raj_](https://instagram.com/1tz.raj_) |
|
|
161
|
+
| **Discord** | `1tz.raj` |
|
|
162
|
+
| **Project** | https://github.com/7rzraj/veronix |
|
|
163
|
+
|
|
164
|
+
## ⚠️ Copyright Notice
|
|
165
|
+
|
|
166
|
+
© 2026 **1tz.raj**. All rights reserved.
|
|
167
|
+
You may **use** Veronix freely, but you may **not republish, re-upload, rebrand or redistribute** it
|
|
168
|
+
(on PyPI, GitHub or anywhere else). Violations will result in a copyright takedown (DMCA).
|
|
169
|
+
See [LICENSE](LICENSE).
|
|
170
|
+
|
|
171
|
+
<sub>Keywords: veronix, 1tz.raj, 7rzraj, python logging, colorful logger, console logger, pip install veronix</sub>
|
veronix-1.0.0/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://raw.githubusercontent.com/7rzraj/veronix/main/assets/banner.png" alt="Veronix - colourful Python logging by 1tz.raj" width="100%">
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/veronix/)
|
|
6
|
+
[](https://pypi.org/project/veronix/)
|
|
7
|
+
[](https://github.com/7rzraj/veronix/actions)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://pypi.org/project/veronix/)
|
|
10
|
+
|
|
11
|
+
Made with ❤️ by **[1tz.raj](https://github.com/7rzraj)**
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div align="center">
|
|
16
|
+
<img src="https://raw.githubusercontent.com/7rzraj/veronix/main/assets/demo-screenshot.png" alt="Veronix demo output in terminal" width="85%">
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📦 Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install veronix
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 🚀 Usage
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import veronix
|
|
31
|
+
|
|
32
|
+
veronix.info("Server started", port=8000)
|
|
33
|
+
veronix.success("Database connected")
|
|
34
|
+
veronix.warning("Disk almost full")
|
|
35
|
+
veronix.error("Payment failed", order_id=1042)
|
|
36
|
+
veronix.critical("System down!")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Levels
|
|
40
|
+
`TRACE` · `DEBUG` · `INFO` · `SUCCESS` · `WARNING` · `ERROR` · `CRITICAL`
|
|
41
|
+
|
|
42
|
+
### Configure
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
veronix.configure(level="DEBUG", file="logs/app.log") # save to file (auto-rotating)
|
|
46
|
+
veronix.configure(json_mode=True) # JSON logs
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Named loggers
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
api = veronix.get_logger("api")
|
|
53
|
+
api.info("GET /users 200")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Timer, decorator, exceptions
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
with veronix.timer("db query"):
|
|
60
|
+
run_query()
|
|
61
|
+
|
|
62
|
+
@veronix.log_calls
|
|
63
|
+
def work(): ...
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
1 / 0
|
|
67
|
+
except ZeroDivisionError:
|
|
68
|
+
veronix.exception("Something broke") # prints full traceback
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Banner & divider
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
veronix.banner("My App v1.0")
|
|
75
|
+
veronix.rule("Startup")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 💻 Command line
|
|
79
|
+
|
|
80
|
+
| Command | What it does |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `veronix demo` | See all colours and levels |
|
|
83
|
+
| `veronix info` | Developer and package details |
|
|
84
|
+
| `veronix levels` | List log levels |
|
|
85
|
+
| `veronix log "text" -l ERROR` | Print one log line |
|
|
86
|
+
| `veronix --version` | Show version |
|
|
87
|
+
|
|
88
|
+
## ✨ Features
|
|
89
|
+
- Auto colour detection (respects `NO_COLOR` / `FORCE_COLOR`), works on Windows, Linux, macOS
|
|
90
|
+
- Zero dependencies
|
|
91
|
+
- File logging with rotation, JSON output, thread-safe
|
|
92
|
+
- Fully typed (`py.typed`)
|
|
93
|
+
|
|
94
|
+
## 👨💻 Developer
|
|
95
|
+
|
|
96
|
+
<img src="https://raw.githubusercontent.com/7rzraj/veronix/main/assets/avatar.png" width="110" align="right" alt="1tz.raj">
|
|
97
|
+
|
|
98
|
+
| | |
|
|
99
|
+
|---|---|
|
|
100
|
+
| **Name** | 1tz.raj |
|
|
101
|
+
| **GitHub** | [@7rzraj](https://github.com/7rzraj) |
|
|
102
|
+
| **Instagram** | [@1tz.raj_](https://instagram.com/1tz.raj_) |
|
|
103
|
+
| **Discord** | `1tz.raj` |
|
|
104
|
+
| **Project** | https://github.com/7rzraj/veronix |
|
|
105
|
+
|
|
106
|
+
## ⚠️ Copyright Notice
|
|
107
|
+
|
|
108
|
+
© 2026 **1tz.raj**. All rights reserved.
|
|
109
|
+
You may **use** Veronix freely, but you may **not republish, re-upload, rebrand or redistribute** it
|
|
110
|
+
(on PyPI, GitHub or anywhere else). Violations will result in a copyright takedown (DMCA).
|
|
111
|
+
See [LICENSE](LICENSE).
|
|
112
|
+
|
|
113
|
+
<sub>Keywords: veronix, 1tz.raj, 7rzraj, python logging, colorful logger, console logger, pip install veronix</sub>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "veronix"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Clean, professional and colourful console logging for Python - by 1tz.raj"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "1tz.raj" }]
|
|
13
|
+
maintainers = [{ name = "1tz.raj" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"logging", "logger", "colorful logs", "console logging", "python logger",
|
|
16
|
+
"colored terminal", "ansi colors", "veronix", "1tz.raj", "7rzraj", "cli logging",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 5 - Production/Stable",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: Other/Proprietary License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.8",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: System :: Logging",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
"Typing :: Typed",
|
|
33
|
+
]
|
|
34
|
+
dependencies = []
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/7rzraj/veronix"
|
|
38
|
+
Documentation = "https://github.com/7rzraj/veronix#readme"
|
|
39
|
+
Source = "https://github.com/7rzraj/veronix"
|
|
40
|
+
Issues = "https://github.com/7rzraj/veronix/issues"
|
|
41
|
+
Changelog = "https://github.com/7rzraj/veronix/blob/main/CHANGELOG.md"
|
|
42
|
+
Developer = "https://github.com/7rzraj"
|
|
43
|
+
Instagram = "https://instagram.com/1tz.raj_"
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
veronix = "veronix.cli:main"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.dynamic]
|
|
49
|
+
version = { attr = "veronix.__about__.__version__" }
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
where = ["src"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.package-data]
|
|
55
|
+
veronix = ["py.typed"]
|
veronix-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright (c) 2026 1tz.raj. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Package metadata for Veronix (single source of truth)."""
|
|
4
|
+
|
|
5
|
+
__title__ = "veronix"
|
|
6
|
+
__version__ = "1.0.0"
|
|
7
|
+
__description__ = "Clean, professional and colourful console logging for Python."
|
|
8
|
+
__author__ = "1tz.raj"
|
|
9
|
+
__license__ = "Veronix Proprietary License (All Rights Reserved)"
|
|
10
|
+
__copyright__ = "Copyright (c) 2026 1tz.raj. All rights reserved."
|
|
11
|
+
__url__ = "https://github.com/7rzraj/veronix"
|
|
12
|
+
|
|
13
|
+
__instagram__ = "https://instagram.com/1tz.raj_"
|
|
14
|
+
__github__ = "https://github.com/7rzraj"
|
|
15
|
+
__discord__ = "1tz.raj"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright (c) 2026 1tz.raj. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Veronix - clean, professional and colourful console logging for Python.
|
|
5
|
+
|
|
6
|
+
pip install veronix
|
|
7
|
+
|
|
8
|
+
import veronix
|
|
9
|
+
veronix.info("Hello from Veronix")
|
|
10
|
+
|
|
11
|
+
Developer : 1tz.raj
|
|
12
|
+
GitHub : https://github.com/7rzraj
|
|
13
|
+
Instagram : https://instagram.com/1tz.raj_
|
|
14
|
+
Discord : 1tz.raj
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .__about__ import (__author__, __copyright__, __description__, __license__,
|
|
18
|
+
__url__, __version__)
|
|
19
|
+
from .levels import (CRITICAL, DEBUG, ERROR, INFO, SUCCESS, TRACE, WARNING)
|
|
20
|
+
from .logger import (Logger, banner, configure, critical, debug, error, exception,
|
|
21
|
+
get_logger, info, log_calls, rule, success, timer, trace,
|
|
22
|
+
warn, warning)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"Logger", "get_logger", "configure", "trace", "debug", "info", "success",
|
|
26
|
+
"warning", "warn", "error", "critical", "exception", "banner", "rule",
|
|
27
|
+
"timer", "log_calls", "TRACE", "DEBUG", "INFO", "SUCCESS", "WARNING",
|
|
28
|
+
"ERROR", "CRITICAL", "__version__", "__author__",
|
|
29
|
+
]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Copyright (c) 2026 1tz.raj. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Command line interface: ``veronix --help``."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from . import __about__ as about
|
|
11
|
+
from .levels import LEVEL_NAMES, resolve_level
|
|
12
|
+
from .logger import Logger
|
|
13
|
+
from .colors import style, supports_color
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _demo() -> None:
|
|
17
|
+
log = Logger("demo", level="TRACE", color=None)
|
|
18
|
+
log.banner(f"VERONIX v{about.__version__}")
|
|
19
|
+
log.trace("Trace: very detailed information")
|
|
20
|
+
log.debug("Debug: variable x = 42")
|
|
21
|
+
log.info("Info: server started", port=8000, env="prod")
|
|
22
|
+
log.success("Success: database connected")
|
|
23
|
+
log.warning("Warning: disk usage at 91%")
|
|
24
|
+
log.error("Error: could not reach payment API")
|
|
25
|
+
log.critical("Critical: system is going down!")
|
|
26
|
+
try:
|
|
27
|
+
1 / 0
|
|
28
|
+
except ZeroDivisionError:
|
|
29
|
+
log.exception("Exception caught with traceback")
|
|
30
|
+
log.rule("done")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _info() -> None:
|
|
34
|
+
c = supports_color()
|
|
35
|
+
rows = [("Package", about.__title__), ("Version", about.__version__),
|
|
36
|
+
("Developer", about.__author__), ("Install", "pip install veronix"),
|
|
37
|
+
("GitHub", about.__github__), ("Instagram", about.__instagram__),
|
|
38
|
+
("Discord", about.__discord__), ("License", about.__license__)]
|
|
39
|
+
print(style(f"\n {about.__title__.upper()} - {about.__description__}\n", "bright_cyan", bold=True, enabled=c))
|
|
40
|
+
for k, v in rows:
|
|
41
|
+
print(f" {style((k + ':').ljust(12), 'yellow', bold=True, enabled=c)} {v}")
|
|
42
|
+
print()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main(argv=None) -> int:
|
|
46
|
+
p = argparse.ArgumentParser(prog="veronix", description=about.__description__)
|
|
47
|
+
p.add_argument("-V", "--version", action="version", version=f"veronix {about.__version__}")
|
|
48
|
+
sub = p.add_subparsers(dest="cmd")
|
|
49
|
+
sub.add_parser("demo", help="show every log level in colour")
|
|
50
|
+
sub.add_parser("info", help="show developer and package details")
|
|
51
|
+
sub.add_parser("levels", help="list available log levels")
|
|
52
|
+
lg = sub.add_parser("log", help="print a single log line")
|
|
53
|
+
lg.add_argument("message")
|
|
54
|
+
lg.add_argument("-l", "--level", default="INFO")
|
|
55
|
+
lg.add_argument("-n", "--name", default="cli")
|
|
56
|
+
lg.add_argument("-f", "--file", help="also write the log to this file")
|
|
57
|
+
lg.add_argument("--json", action="store_true", help="JSON output")
|
|
58
|
+
args = p.parse_args(argv)
|
|
59
|
+
|
|
60
|
+
if args.cmd == "demo":
|
|
61
|
+
_demo()
|
|
62
|
+
elif args.cmd == "info":
|
|
63
|
+
_info()
|
|
64
|
+
elif args.cmd == "levels":
|
|
65
|
+
for num, name in sorted(LEVEL_NAMES.items()):
|
|
66
|
+
Logger("levels", level=0, show_time=False, show_name=False).log(num, f"{name} ({num})")
|
|
67
|
+
elif args.cmd == "log":
|
|
68
|
+
try:
|
|
69
|
+
lvl = resolve_level(args.level)
|
|
70
|
+
except ValueError as exc:
|
|
71
|
+
print(exc, file=sys.stderr)
|
|
72
|
+
return 2
|
|
73
|
+
Logger(args.name, level=0, file=args.file, json_mode=args.json).log(lvl, args.message)
|
|
74
|
+
else:
|
|
75
|
+
p.print_help()
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
sys.exit(main())
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Copyright (c) 2026 1tz.raj. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""ANSI colour helpers with automatic terminal detection."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
RESET = "\033[0m"
|
|
11
|
+
BOLD = "\033[1m"
|
|
12
|
+
DIM = "\033[2m"
|
|
13
|
+
ITALIC = "\033[3m"
|
|
14
|
+
|
|
15
|
+
FG = {
|
|
16
|
+
"black": 30, "red": 31, "green": 32, "yellow": 33,
|
|
17
|
+
"blue": 34, "magenta": 35, "cyan": 36, "white": 37,
|
|
18
|
+
"gray": 90, "bright_red": 91, "bright_green": 92, "bright_yellow": 93,
|
|
19
|
+
"bright_blue": 94, "bright_magenta": 95, "bright_cyan": 96, "bright_white": 97,
|
|
20
|
+
}
|
|
21
|
+
BG = {
|
|
22
|
+
"red": 41, "green": 42, "yellow": 43, "blue": 44,
|
|
23
|
+
"magenta": 45, "cyan": 46, "white": 47, "gray": 100,
|
|
24
|
+
"bright_red": 101, "bright_green": 102, "bright_blue": 104,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Enable ANSI escape processing on modern Windows consoles.
|
|
28
|
+
if os.name == "nt": # pragma: no cover
|
|
29
|
+
os.system("")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def supports_color(stream=None) -> bool:
|
|
33
|
+
"""Return True if ``stream`` (default: stdout) should receive colours.
|
|
34
|
+
|
|
35
|
+
Respects the ``NO_COLOR`` and ``FORCE_COLOR`` environment variables.
|
|
36
|
+
"""
|
|
37
|
+
if os.environ.get("NO_COLOR"):
|
|
38
|
+
return False
|
|
39
|
+
if os.environ.get("FORCE_COLOR"):
|
|
40
|
+
return True
|
|
41
|
+
stream = stream or sys.stdout
|
|
42
|
+
if os.environ.get("TERM") == "dumb":
|
|
43
|
+
return False
|
|
44
|
+
isatty = getattr(stream, "isatty", None)
|
|
45
|
+
return bool(isatty and isatty())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def style(text: str, fg: str | None = None, bg: str | None = None,
|
|
49
|
+
bold: bool = False, dim: bool = False, enabled: bool = True) -> str:
|
|
50
|
+
"""Wrap ``text`` in ANSI codes. Returns plain text if ``enabled`` is False."""
|
|
51
|
+
if not enabled:
|
|
52
|
+
return text
|
|
53
|
+
codes = []
|
|
54
|
+
if bold:
|
|
55
|
+
codes.append("1")
|
|
56
|
+
if dim:
|
|
57
|
+
codes.append("2")
|
|
58
|
+
if fg in FG:
|
|
59
|
+
codes.append(str(FG[fg]))
|
|
60
|
+
if bg in BG:
|
|
61
|
+
codes.append(str(BG[bg]))
|
|
62
|
+
if not codes:
|
|
63
|
+
return text
|
|
64
|
+
return f"\033[{';'.join(codes)}m{text}{RESET}"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Copyright (c) 2026 1tz.raj. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Console, plain-text and JSON formatters."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from .colors import style
|
|
11
|
+
from .levels import LEVEL_NAMES, LEVEL_STYLES
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _extras(extra: dict) -> str:
|
|
15
|
+
return " ".join(f"{k}={v!r}" if isinstance(v, str) else f"{k}={v}" for k, v in extra.items())
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def format_console(rec: dict, color: bool, show_time: bool, show_name: bool, time_format: str) -> str:
|
|
19
|
+
lv = rec["levelno"]
|
|
20
|
+
badge_fg, badge_bg, msg_fg = LEVEL_STYLES.get(lv, ("white", "gray", "white"))
|
|
21
|
+
parts = []
|
|
22
|
+
if show_time:
|
|
23
|
+
ts = datetime.fromtimestamp(rec["time"]).strftime(time_format)
|
|
24
|
+
parts.append(style(ts, "gray", enabled=color))
|
|
25
|
+
parts.append(style(f" {rec['level']:<8} ", badge_fg, badge_bg, bold=True, enabled=color))
|
|
26
|
+
if show_name:
|
|
27
|
+
parts.append(style(rec["name"], "magenta", enabled=color))
|
|
28
|
+
msg = style(rec["message"], msg_fg, bold=lv >= 40, enabled=color)
|
|
29
|
+
if rec["extra"]:
|
|
30
|
+
msg += " " + style(_extras(rec["extra"]), "gray", enabled=color)
|
|
31
|
+
parts.append(msg)
|
|
32
|
+
line = style(" | ", "gray", enabled=color).join(parts)
|
|
33
|
+
if rec.get("traceback"):
|
|
34
|
+
line += "\n" + style(rec["traceback"].rstrip(), "red", dim=True, enabled=color)
|
|
35
|
+
return line
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def format_plain(rec: dict, time_format: str) -> str:
|
|
39
|
+
ts = datetime.fromtimestamp(rec["time"]).strftime(time_format)
|
|
40
|
+
line = f"{ts} | {rec['level']:<8} | {rec['name']} | {rec['message']}"
|
|
41
|
+
if rec["extra"]:
|
|
42
|
+
line += " " + _extras(rec["extra"])
|
|
43
|
+
if rec.get("traceback"):
|
|
44
|
+
line += "\n" + rec["traceback"].rstrip()
|
|
45
|
+
return line
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def format_json(rec: dict) -> str:
|
|
49
|
+
data = {
|
|
50
|
+
"time": datetime.fromtimestamp(rec["time"]).isoformat(),
|
|
51
|
+
"level": rec["level"],
|
|
52
|
+
"name": rec["name"],
|
|
53
|
+
"message": rec["message"],
|
|
54
|
+
**rec["extra"],
|
|
55
|
+
}
|
|
56
|
+
if rec.get("traceback"):
|
|
57
|
+
data["traceback"] = rec["traceback"]
|
|
58
|
+
return json.dumps(data, default=str, ensure_ascii=False)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
__all__ = ["format_console", "format_plain", "format_json", "LEVEL_NAMES"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright (c) 2026 1tz.raj. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Thread-safe rotating file writer."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import threading
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RotatingFileWriter:
|
|
12
|
+
"""Append lines to a file and rotate it when it grows too large."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, path: str, max_bytes: int = 5_000_000, backups: int = 3):
|
|
15
|
+
self.path = path
|
|
16
|
+
self.max_bytes = max_bytes
|
|
17
|
+
self.backups = backups
|
|
18
|
+
self._lock = threading.Lock()
|
|
19
|
+
folder = os.path.dirname(os.path.abspath(path))
|
|
20
|
+
os.makedirs(folder, exist_ok=True)
|
|
21
|
+
|
|
22
|
+
def _rotate(self) -> None:
|
|
23
|
+
for i in range(self.backups - 1, 0, -1):
|
|
24
|
+
src, dst = f"{self.path}.{i}", f"{self.path}.{i + 1}"
|
|
25
|
+
if os.path.exists(src):
|
|
26
|
+
os.replace(src, dst)
|
|
27
|
+
if self.backups > 0 and os.path.exists(self.path):
|
|
28
|
+
os.replace(self.path, f"{self.path}.1")
|
|
29
|
+
|
|
30
|
+
def write(self, line: str) -> None:
|
|
31
|
+
with self._lock:
|
|
32
|
+
try:
|
|
33
|
+
if self.max_bytes and os.path.exists(self.path) \
|
|
34
|
+
and os.path.getsize(self.path) >= self.max_bytes:
|
|
35
|
+
self._rotate()
|
|
36
|
+
with open(self.path, "a", encoding="utf-8") as fh:
|
|
37
|
+
fh.write(line + "\n")
|
|
38
|
+
except OSError:
|
|
39
|
+
pass # logging must never crash the application
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Copyright (c) 2026 1tz.raj. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Log level definitions."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
TRACE = 5
|
|
8
|
+
DEBUG = 10
|
|
9
|
+
INFO = 20
|
|
10
|
+
SUCCESS = 25
|
|
11
|
+
WARNING = 30
|
|
12
|
+
ERROR = 40
|
|
13
|
+
CRITICAL = 50
|
|
14
|
+
|
|
15
|
+
LEVEL_NAMES = {
|
|
16
|
+
TRACE: "TRACE", DEBUG: "DEBUG", INFO: "INFO", SUCCESS: "SUCCESS",
|
|
17
|
+
WARNING: "WARNING", ERROR: "ERROR", CRITICAL: "CRITICAL",
|
|
18
|
+
}
|
|
19
|
+
NAME_TO_LEVEL = {v: k for k, v in LEVEL_NAMES.items()}
|
|
20
|
+
NAME_TO_LEVEL["WARN"] = WARNING
|
|
21
|
+
|
|
22
|
+
# level -> (badge foreground, badge background, message foreground)
|
|
23
|
+
LEVEL_STYLES = {
|
|
24
|
+
TRACE: ("white", "gray", "gray"),
|
|
25
|
+
DEBUG: ("black", "cyan", "cyan"),
|
|
26
|
+
INFO: ("white", "blue", "bright_blue"),
|
|
27
|
+
SUCCESS: ("black", "green", "bright_green"),
|
|
28
|
+
WARNING: ("black", "yellow", "bright_yellow"),
|
|
29
|
+
ERROR: ("white", "red", "bright_red"),
|
|
30
|
+
CRITICAL: ("bright_white", "bright_red", "bright_red"),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def resolve_level(level) -> int:
|
|
35
|
+
"""Convert a level name (``"info"``) or number into an int."""
|
|
36
|
+
if isinstance(level, int):
|
|
37
|
+
return level
|
|
38
|
+
key = str(level).strip().upper()
|
|
39
|
+
if key not in NAME_TO_LEVEL:
|
|
40
|
+
raise ValueError(f"Unknown log level: {level!r}")
|
|
41
|
+
return NAME_TO_LEVEL[key]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Copyright (c) 2026 1tz.raj. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Core Logger class and module-level convenience API."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
import sys
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
import traceback
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
|
|
14
|
+
from .colors import supports_color, style
|
|
15
|
+
from .formatter import format_console, format_json, format_plain
|
|
16
|
+
from .handlers import RotatingFileWriter
|
|
17
|
+
from .levels import (CRITICAL, DEBUG, ERROR, INFO, SUCCESS, TRACE, WARNING,
|
|
18
|
+
LEVEL_NAMES, resolve_level)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Logger:
|
|
22
|
+
"""A lightweight, colourful logger.
|
|
23
|
+
|
|
24
|
+
>>> log = Logger("app", level="DEBUG")
|
|
25
|
+
>>> log.info("Server started", port=8000)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, name: str = "veronix", level="INFO", *, color=None,
|
|
29
|
+
show_time: bool = True, show_name: bool = True, file: str | None = None,
|
|
30
|
+
json_mode: bool = False, stream=None, max_bytes: int = 5_000_000,
|
|
31
|
+
backups: int = 3, time_format: str = "%Y-%m-%d %H:%M:%S"):
|
|
32
|
+
self.name = name
|
|
33
|
+
self.level = resolve_level(level)
|
|
34
|
+
self.color = color # None = auto-detect
|
|
35
|
+
self.show_time = show_time
|
|
36
|
+
self.show_name = show_name
|
|
37
|
+
self.json_mode = json_mode
|
|
38
|
+
self.stream = stream # None = sys.stdout at call time
|
|
39
|
+
self.time_format = time_format
|
|
40
|
+
self._lock = threading.Lock()
|
|
41
|
+
self._file = RotatingFileWriter(file, max_bytes, backups) if file else None
|
|
42
|
+
|
|
43
|
+
# ------------------------------------------------------------ config
|
|
44
|
+
def configure(self, **kw) -> "Logger":
|
|
45
|
+
"""Change settings at runtime (level, color, file, json_mode, ...)."""
|
|
46
|
+
if "level" in kw:
|
|
47
|
+
self.level = resolve_level(kw.pop("level"))
|
|
48
|
+
if "file" in kw:
|
|
49
|
+
path = kw.pop("file")
|
|
50
|
+
self._file = RotatingFileWriter(
|
|
51
|
+
path, kw.pop("max_bytes", 5_000_000), kw.pop("backups", 3)) if path else None
|
|
52
|
+
for key, value in kw.items():
|
|
53
|
+
if not hasattr(self, key):
|
|
54
|
+
raise TypeError(f"Unknown option: {key}")
|
|
55
|
+
setattr(self, key, value)
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def set_level(self, level) -> None:
|
|
59
|
+
self.level = resolve_level(level)
|
|
60
|
+
|
|
61
|
+
def child(self, name: str) -> "Logger":
|
|
62
|
+
"""Create a logger that shares this logger's settings."""
|
|
63
|
+
new = Logger(f"{self.name}.{name}", self.level, color=self.color,
|
|
64
|
+
show_time=self.show_time, show_name=self.show_name,
|
|
65
|
+
json_mode=self.json_mode, stream=self.stream,
|
|
66
|
+
time_format=self.time_format)
|
|
67
|
+
new._file = self._file
|
|
68
|
+
return new
|
|
69
|
+
|
|
70
|
+
# ------------------------------------------------------------ core
|
|
71
|
+
def log(self, level, message, *args, exc_info: bool = False, **extra) -> None:
|
|
72
|
+
levelno = resolve_level(level)
|
|
73
|
+
if levelno < self.level:
|
|
74
|
+
return
|
|
75
|
+
message = str(message)
|
|
76
|
+
if args:
|
|
77
|
+
try:
|
|
78
|
+
message = message % args
|
|
79
|
+
except (TypeError, ValueError):
|
|
80
|
+
message = " ".join([message, *map(str, args)])
|
|
81
|
+
tb = traceback.format_exc() if exc_info and sys.exc_info()[0] else None
|
|
82
|
+
rec = {"time": time.time(), "levelno": levelno,
|
|
83
|
+
"level": LEVEL_NAMES.get(levelno, f"L{levelno}"),
|
|
84
|
+
"name": self.name, "message": message, "extra": extra, "traceback": tb}
|
|
85
|
+
|
|
86
|
+
stream = self.stream or sys.stdout
|
|
87
|
+
use_color = supports_color(stream) if self.color is None else self.color
|
|
88
|
+
with self._lock:
|
|
89
|
+
try:
|
|
90
|
+
if self.json_mode:
|
|
91
|
+
stream.write(format_json(rec) + "\n")
|
|
92
|
+
else:
|
|
93
|
+
stream.write(format_console(rec, use_color, self.show_time,
|
|
94
|
+
self.show_name, self.time_format) + "\n")
|
|
95
|
+
stream.flush()
|
|
96
|
+
except (OSError, ValueError):
|
|
97
|
+
pass
|
|
98
|
+
if self._file:
|
|
99
|
+
self._file.write(format_json(rec) if self.json_mode
|
|
100
|
+
else format_plain(rec, self.time_format))
|
|
101
|
+
|
|
102
|
+
def trace(self, msg, *a, **k): self.log(TRACE, msg, *a, **k)
|
|
103
|
+
def debug(self, msg, *a, **k): self.log(DEBUG, msg, *a, **k)
|
|
104
|
+
def info(self, msg, *a, **k): self.log(INFO, msg, *a, **k)
|
|
105
|
+
def success(self, msg, *a, **k): self.log(SUCCESS, msg, *a, **k)
|
|
106
|
+
def warning(self, msg, *a, **k): self.log(WARNING, msg, *a, **k)
|
|
107
|
+
warn = warning
|
|
108
|
+
def error(self, msg, *a, **k): self.log(ERROR, msg, *a, **k)
|
|
109
|
+
def critical(self, msg, *a, **k): self.log(CRITICAL, msg, *a, **k)
|
|
110
|
+
|
|
111
|
+
def exception(self, msg, *a, **k):
|
|
112
|
+
"""Log an ERROR with the current traceback (use inside ``except``)."""
|
|
113
|
+
k["exc_info"] = True
|
|
114
|
+
self.log(ERROR, msg, *a, **k)
|
|
115
|
+
|
|
116
|
+
# ------------------------------------------------------------ extras
|
|
117
|
+
def banner(self, text: str, fg: str = "bright_cyan", width: int = 0) -> None:
|
|
118
|
+
"""Print a framed banner (console only)."""
|
|
119
|
+
stream = self.stream or sys.stdout
|
|
120
|
+
c = supports_color(stream) if self.color is None else self.color
|
|
121
|
+
width = max(width, len(text) + 4)
|
|
122
|
+
bar = "=" * width
|
|
123
|
+
stream.write(style(bar, fg, enabled=c) + "\n"
|
|
124
|
+
+ style(text.center(width), fg, bold=True, enabled=c) + "\n"
|
|
125
|
+
+ style(bar, fg, enabled=c) + "\n")
|
|
126
|
+
stream.flush()
|
|
127
|
+
|
|
128
|
+
def rule(self, title: str = "", width: int = 60) -> None:
|
|
129
|
+
"""Print a horizontal divider line."""
|
|
130
|
+
stream = self.stream or sys.stdout
|
|
131
|
+
c = supports_color(stream) if self.color is None else self.color
|
|
132
|
+
line = f" {title} ".center(width, "-") if title else "-" * width
|
|
133
|
+
stream.write(style(line, "gray", enabled=c) + "\n")
|
|
134
|
+
stream.flush()
|
|
135
|
+
|
|
136
|
+
@contextmanager
|
|
137
|
+
def timer(self, label: str = "task", level=SUCCESS):
|
|
138
|
+
"""Time a block of code: ``with log.timer("db query"): ...``"""
|
|
139
|
+
start = time.perf_counter()
|
|
140
|
+
try:
|
|
141
|
+
yield
|
|
142
|
+
finally:
|
|
143
|
+
self.log(level, f"{label} finished in {time.perf_counter() - start:.3f}s")
|
|
144
|
+
|
|
145
|
+
def log_calls(self, func=None, *, level=DEBUG):
|
|
146
|
+
"""Decorator that logs calls, results and exceptions of a function."""
|
|
147
|
+
def decorate(fn):
|
|
148
|
+
@functools.wraps(fn)
|
|
149
|
+
def wrapper(*args, **kwargs):
|
|
150
|
+
self.log(level, f"calling {fn.__name__}()")
|
|
151
|
+
start = time.perf_counter()
|
|
152
|
+
try:
|
|
153
|
+
result = fn(*args, **kwargs)
|
|
154
|
+
except Exception:
|
|
155
|
+
self.exception(f"{fn.__name__}() raised an exception")
|
|
156
|
+
raise
|
|
157
|
+
self.log(level, f"{fn.__name__}() returned in {time.perf_counter() - start:.4f}s")
|
|
158
|
+
return result
|
|
159
|
+
return wrapper
|
|
160
|
+
return decorate(func) if func else decorate
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------- default API
|
|
164
|
+
_default = Logger("veronix")
|
|
165
|
+
_loggers: dict = {}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_logger(name: str = "veronix", **kwargs) -> Logger:
|
|
169
|
+
"""Return a named logger (cached). Extra kwargs apply on first creation."""
|
|
170
|
+
if name not in _loggers:
|
|
171
|
+
_loggers[name] = Logger(name, **kwargs)
|
|
172
|
+
return _loggers[name]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def configure(**kwargs) -> Logger:
|
|
176
|
+
"""Configure the default logger used by ``veronix.info()`` etc."""
|
|
177
|
+
return _default.configure(**kwargs)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def trace(msg, *a, **k): _default.trace(msg, *a, **k)
|
|
181
|
+
def debug(msg, *a, **k): _default.debug(msg, *a, **k)
|
|
182
|
+
def info(msg, *a, **k): _default.info(msg, *a, **k)
|
|
183
|
+
def success(msg, *a, **k): _default.success(msg, *a, **k)
|
|
184
|
+
def warning(msg, *a, **k): _default.warning(msg, *a, **k)
|
|
185
|
+
warn = warning
|
|
186
|
+
def error(msg, *a, **k): _default.error(msg, *a, **k)
|
|
187
|
+
def critical(msg, *a, **k): _default.critical(msg, *a, **k)
|
|
188
|
+
def exception(msg, *a, **k): _default.exception(msg, *a, **k)
|
|
189
|
+
def banner(text, **k): _default.banner(text, **k)
|
|
190
|
+
def rule(title="", **k): _default.rule(title, **k)
|
|
191
|
+
def timer(label="task", **k): return _default.timer(label, **k)
|
|
192
|
+
def log_calls(func=None, **k): return _default.log_calls(func, **k)
|
|
File without changes
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: veronix
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Clean, professional and colourful console logging for Python - by 1tz.raj
|
|
5
|
+
Author: 1tz.raj
|
|
6
|
+
Maintainer: 1tz.raj
|
|
7
|
+
License: VERONIX PROPRIETARY LICENSE - ALL RIGHTS RESERVED
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 1tz.raj (GitHub: 7rzraj). All rights reserved.
|
|
10
|
+
|
|
11
|
+
1. GRANT. You may install Veronix with "pip install veronix" and use it, free
|
|
12
|
+
of charge, in personal, educational and commercial projects, including as a
|
|
13
|
+
dependency of your own software.
|
|
14
|
+
|
|
15
|
+
2. RESTRICTIONS. Without prior written permission from the author you may NOT:
|
|
16
|
+
a) republish, re-upload or redistribute Veronix (original or modified, in
|
|
17
|
+
source or binary form) on PyPI, GitHub, any package index, website,
|
|
18
|
+
marketplace or any other platform;
|
|
19
|
+
b) sell, sublicense, rent or claim Veronix as your own work;
|
|
20
|
+
c) rename, rebrand or fork it into a competing or look-alike package;
|
|
21
|
+
d) remove or alter copyright notices, author credits or this license.
|
|
22
|
+
|
|
23
|
+
3. ENFORCEMENT. Any republication or unauthorised copying is copyright
|
|
24
|
+
infringement. The author may file takedown notices (e.g. DMCA) with PyPI,
|
|
25
|
+
GitHub and hosting providers and pursue legal remedies.
|
|
26
|
+
|
|
27
|
+
4. NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
28
|
+
THE AUTHOR IS NOT LIABLE FOR ANY CLAIM OR DAMAGES ARISING FROM ITS USE.
|
|
29
|
+
|
|
30
|
+
Contact: Instagram @1tz.raj_ | GitHub @7rzraj | Discord 1tz.raj
|
|
31
|
+
|
|
32
|
+
Project-URL: Homepage, https://github.com/7rzraj/veronix
|
|
33
|
+
Project-URL: Documentation, https://github.com/7rzraj/veronix#readme
|
|
34
|
+
Project-URL: Source, https://github.com/7rzraj/veronix
|
|
35
|
+
Project-URL: Issues, https://github.com/7rzraj/veronix/issues
|
|
36
|
+
Project-URL: Changelog, https://github.com/7rzraj/veronix/blob/main/CHANGELOG.md
|
|
37
|
+
Project-URL: Developer, https://github.com/7rzraj
|
|
38
|
+
Project-URL: Instagram, https://instagram.com/1tz.raj_
|
|
39
|
+
Keywords: logging,logger,colorful logs,console logging,python logger,colored terminal,ansi colors,veronix,1tz.raj,7rzraj,cli logging
|
|
40
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
41
|
+
Classifier: Intended Audience :: Developers
|
|
42
|
+
Classifier: License :: Other/Proprietary License
|
|
43
|
+
Classifier: Operating System :: OS Independent
|
|
44
|
+
Classifier: Programming Language :: Python :: 3
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
47
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
48
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
49
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
50
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
51
|
+
Classifier: Topic :: System :: Logging
|
|
52
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
53
|
+
Classifier: Typing :: Typed
|
|
54
|
+
Requires-Python: >=3.8
|
|
55
|
+
Description-Content-Type: text/markdown
|
|
56
|
+
License-File: LICENSE
|
|
57
|
+
Dynamic: license-file
|
|
58
|
+
|
|
59
|
+
<div align="center">
|
|
60
|
+
|
|
61
|
+
<img src="https://raw.githubusercontent.com/7rzraj/veronix/main/assets/banner.png" alt="Veronix - colourful Python logging by 1tz.raj" width="100%">
|
|
62
|
+
|
|
63
|
+
[](https://pypi.org/project/veronix/)
|
|
64
|
+
[](https://pypi.org/project/veronix/)
|
|
65
|
+
[](https://github.com/7rzraj/veronix/actions)
|
|
66
|
+
[](LICENSE)
|
|
67
|
+
[](https://pypi.org/project/veronix/)
|
|
68
|
+
|
|
69
|
+
Made with ❤️ by **[1tz.raj](https://github.com/7rzraj)**
|
|
70
|
+
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div align="center">
|
|
74
|
+
<img src="https://raw.githubusercontent.com/7rzraj/veronix/main/assets/demo-screenshot.png" alt="Veronix demo output in terminal" width="85%">
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 📦 Installation
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install veronix
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 🚀 Usage
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import veronix
|
|
89
|
+
|
|
90
|
+
veronix.info("Server started", port=8000)
|
|
91
|
+
veronix.success("Database connected")
|
|
92
|
+
veronix.warning("Disk almost full")
|
|
93
|
+
veronix.error("Payment failed", order_id=1042)
|
|
94
|
+
veronix.critical("System down!")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Levels
|
|
98
|
+
`TRACE` · `DEBUG` · `INFO` · `SUCCESS` · `WARNING` · `ERROR` · `CRITICAL`
|
|
99
|
+
|
|
100
|
+
### Configure
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
veronix.configure(level="DEBUG", file="logs/app.log") # save to file (auto-rotating)
|
|
104
|
+
veronix.configure(json_mode=True) # JSON logs
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Named loggers
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
api = veronix.get_logger("api")
|
|
111
|
+
api.info("GET /users 200")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Timer, decorator, exceptions
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
with veronix.timer("db query"):
|
|
118
|
+
run_query()
|
|
119
|
+
|
|
120
|
+
@veronix.log_calls
|
|
121
|
+
def work(): ...
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
1 / 0
|
|
125
|
+
except ZeroDivisionError:
|
|
126
|
+
veronix.exception("Something broke") # prints full traceback
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Banner & divider
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
veronix.banner("My App v1.0")
|
|
133
|
+
veronix.rule("Startup")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 💻 Command line
|
|
137
|
+
|
|
138
|
+
| Command | What it does |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `veronix demo` | See all colours and levels |
|
|
141
|
+
| `veronix info` | Developer and package details |
|
|
142
|
+
| `veronix levels` | List log levels |
|
|
143
|
+
| `veronix log "text" -l ERROR` | Print one log line |
|
|
144
|
+
| `veronix --version` | Show version |
|
|
145
|
+
|
|
146
|
+
## ✨ Features
|
|
147
|
+
- Auto colour detection (respects `NO_COLOR` / `FORCE_COLOR`), works on Windows, Linux, macOS
|
|
148
|
+
- Zero dependencies
|
|
149
|
+
- File logging with rotation, JSON output, thread-safe
|
|
150
|
+
- Fully typed (`py.typed`)
|
|
151
|
+
|
|
152
|
+
## 👨💻 Developer
|
|
153
|
+
|
|
154
|
+
<img src="https://raw.githubusercontent.com/7rzraj/veronix/main/assets/avatar.png" width="110" align="right" alt="1tz.raj">
|
|
155
|
+
|
|
156
|
+
| | |
|
|
157
|
+
|---|---|
|
|
158
|
+
| **Name** | 1tz.raj |
|
|
159
|
+
| **GitHub** | [@7rzraj](https://github.com/7rzraj) |
|
|
160
|
+
| **Instagram** | [@1tz.raj_](https://instagram.com/1tz.raj_) |
|
|
161
|
+
| **Discord** | `1tz.raj` |
|
|
162
|
+
| **Project** | https://github.com/7rzraj/veronix |
|
|
163
|
+
|
|
164
|
+
## ⚠️ Copyright Notice
|
|
165
|
+
|
|
166
|
+
© 2026 **1tz.raj**. All rights reserved.
|
|
167
|
+
You may **use** Veronix freely, but you may **not republish, re-upload, rebrand or redistribute** it
|
|
168
|
+
(on PyPI, GitHub or anywhere else). Violations will result in a copyright takedown (DMCA).
|
|
169
|
+
See [LICENSE](LICENSE).
|
|
170
|
+
|
|
171
|
+
<sub>Keywords: veronix, 1tz.raj, 7rzraj, python logging, colorful logger, console logger, pip install veronix</sub>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
CHANGELOG.md
|
|
2
|
+
LICENSE
|
|
3
|
+
MANIFEST.in
|
|
4
|
+
README.md
|
|
5
|
+
pyproject.toml
|
|
6
|
+
src/veronix/__about__.py
|
|
7
|
+
src/veronix/__init__.py
|
|
8
|
+
src/veronix/__main__.py
|
|
9
|
+
src/veronix/cli.py
|
|
10
|
+
src/veronix/colors.py
|
|
11
|
+
src/veronix/formatter.py
|
|
12
|
+
src/veronix/handlers.py
|
|
13
|
+
src/veronix/levels.py
|
|
14
|
+
src/veronix/logger.py
|
|
15
|
+
src/veronix/py.typed
|
|
16
|
+
src/veronix.egg-info/PKG-INFO
|
|
17
|
+
src/veronix.egg-info/SOURCES.txt
|
|
18
|
+
src/veronix.egg-info/dependency_links.txt
|
|
19
|
+
src/veronix.egg-info/entry_points.txt
|
|
20
|
+
src/veronix.egg-info/top_level.txt
|
|
21
|
+
tests/test_veronix.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
veronix
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Copyright (c) 2026 1tz.raj. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
|
|
9
|
+
import veronix
|
|
10
|
+
from veronix import Logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestVeronix(unittest.TestCase):
|
|
14
|
+
def make(self, **kw):
|
|
15
|
+
buf = io.StringIO()
|
|
16
|
+
return Logger("t", stream=buf, color=False, **kw), buf
|
|
17
|
+
|
|
18
|
+
def test_levels_filter(self):
|
|
19
|
+
log, buf = self.make(level="WARNING")
|
|
20
|
+
log.info("hidden")
|
|
21
|
+
log.error("shown")
|
|
22
|
+
out = buf.getvalue()
|
|
23
|
+
self.assertNotIn("hidden", out)
|
|
24
|
+
self.assertIn("shown", out)
|
|
25
|
+
|
|
26
|
+
def test_color_codes(self):
|
|
27
|
+
buf = io.StringIO()
|
|
28
|
+
Logger("t", stream=buf, color=True).success("ok")
|
|
29
|
+
self.assertIn("\033[", buf.getvalue())
|
|
30
|
+
|
|
31
|
+
def test_no_color(self):
|
|
32
|
+
log, buf = self.make()
|
|
33
|
+
log.info("plain")
|
|
34
|
+
self.assertNotIn("\033[", buf.getvalue())
|
|
35
|
+
|
|
36
|
+
def test_extra_and_format_args(self):
|
|
37
|
+
log, buf = self.make()
|
|
38
|
+
log.info("user %s", "raj", id=7)
|
|
39
|
+
self.assertIn("user raj", buf.getvalue())
|
|
40
|
+
self.assertIn("id=7", buf.getvalue())
|
|
41
|
+
|
|
42
|
+
def test_json_and_file(self):
|
|
43
|
+
with tempfile.TemporaryDirectory() as d:
|
|
44
|
+
path = os.path.join(d, "app.log")
|
|
45
|
+
log, buf = self.make(file=path, json_mode=True)
|
|
46
|
+
log.info("hello", a=1)
|
|
47
|
+
data = json.loads(open(path, encoding="utf-8").read().strip())
|
|
48
|
+
self.assertEqual(data["message"], "hello")
|
|
49
|
+
self.assertEqual(data["a"], 1)
|
|
50
|
+
|
|
51
|
+
def test_exception(self):
|
|
52
|
+
log, buf = self.make()
|
|
53
|
+
try:
|
|
54
|
+
1 / 0
|
|
55
|
+
except ZeroDivisionError:
|
|
56
|
+
log.exception("boom")
|
|
57
|
+
self.assertIn("ZeroDivisionError", buf.getvalue())
|
|
58
|
+
|
|
59
|
+
def test_timer_and_decorator(self):
|
|
60
|
+
log, buf = self.make(level="DEBUG")
|
|
61
|
+
with log.timer("job"):
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@log.log_calls
|
|
65
|
+
def f():
|
|
66
|
+
return 1
|
|
67
|
+
|
|
68
|
+
self.assertEqual(f(), 1)
|
|
69
|
+
self.assertIn("job finished", buf.getvalue())
|
|
70
|
+
self.assertIn("calling f()", buf.getvalue())
|
|
71
|
+
|
|
72
|
+
def test_bad_level(self):
|
|
73
|
+
with self.assertRaises(ValueError):
|
|
74
|
+
Logger(level="nope")
|
|
75
|
+
|
|
76
|
+
def test_public_api(self):
|
|
77
|
+
self.assertTrue(callable(veronix.info))
|
|
78
|
+
self.assertEqual(veronix.__author__, "1tz.raj")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
unittest.main()
|