arin-log 0.2.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.
- arin_log/__init__.py +68 -0
- arin_log/console.py +91 -0
- arin_log/demo_log.py +37 -0
- arin_log/folder.py +306 -0
- arin_log/log_cnfg.json +63 -0
- arin_log/logger.py +265 -0
- arin_log/utils.py +61 -0
- arin_log-0.2.0.dist-info/METADATA +185 -0
- arin_log-0.2.0.dist-info/RECORD +12 -0
- arin_log-0.2.0.dist-info/WHEEL +5 -0
- arin_log-0.2.0.dist-info/licenses/LICENSE +21 -0
- arin_log-0.2.0.dist-info/top_level.txt +1 -0
arin_log/__init__.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
arin_log
|
|
3
|
+
--------
|
|
4
|
+
Een lichtgewicht, platformonafhankelijke logging-package voor
|
|
5
|
+
langlopende pipelines.
|
|
6
|
+
|
|
7
|
+
Twee rollen in één package:
|
|
8
|
+
|
|
9
|
+
1. Een virtuele assistent (Arin) die op de console tegen de gebruiker
|
|
10
|
+
praat en hem op de hoogte houdt van wat het script doet.
|
|
11
|
+
2. Een schoon, technisch logbestand dat alleen de bakens bevat:
|
|
12
|
+
start, exception, exit. Bedoeld voor de ontwikkelaar die later
|
|
13
|
+
moet reconstrueren wat er is gebeurd.
|
|
14
|
+
|
|
15
|
+
De gebruiker ziet een verhaal. De ontwikkelaar ziet een spoor.
|
|
16
|
+
|
|
17
|
+
Publieke API
|
|
18
|
+
------------
|
|
19
|
+
Vanuit de logger:
|
|
20
|
+
log_start, log_notes, log_except, log_exit,
|
|
21
|
+
config_logs, file_exist, tijd
|
|
22
|
+
|
|
23
|
+
Vanuit de folder:
|
|
24
|
+
change_folder, get_folder
|
|
25
|
+
|
|
26
|
+
Vanuit de console:
|
|
27
|
+
cons_menu
|
|
28
|
+
|
|
29
|
+
Interne functies blijven bereikbaar via hun eigen module, bijvoorbeeld:
|
|
30
|
+
from arin_log.logger import not_set
|
|
31
|
+
from arin_log.folder import get_config
|
|
32
|
+
from arin_log.utils import load_json, save_json
|
|
33
|
+
from arin_log.console import cls, menu_dict
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
__version__ = "0.2.0"
|
|
37
|
+
|
|
38
|
+
from arin_log.logger import (
|
|
39
|
+
log_start,
|
|
40
|
+
log_notes,
|
|
41
|
+
log_except,
|
|
42
|
+
log_exit,
|
|
43
|
+
config_logs,
|
|
44
|
+
file_exist,
|
|
45
|
+
tijd,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
from arin_log.folder import (
|
|
49
|
+
change_folder,
|
|
50
|
+
get_folder,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
from arin_log.console import (
|
|
54
|
+
cons_menu,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"log_start",
|
|
59
|
+
"log_notes",
|
|
60
|
+
"log_except",
|
|
61
|
+
"log_exit",
|
|
62
|
+
"config_logs",
|
|
63
|
+
"file_exist",
|
|
64
|
+
"tijd",
|
|
65
|
+
"change_folder",
|
|
66
|
+
"get_folder",
|
|
67
|
+
"cons_menu",
|
|
68
|
+
]
|
arin_log/console.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
console.py
|
|
3
|
+
----------
|
|
4
|
+
Console-interactie voor arin_log.
|
|
5
|
+
|
|
6
|
+
Bevat:
|
|
7
|
+
cls() - wist het scherm (Windows: cls, anders clear)
|
|
8
|
+
menu_dict(optlst) - bouwt een dict van genummerde menu-opties
|
|
9
|
+
cons_menu(optlst, ...) - toont een console-menu en retourneert
|
|
10
|
+
de gekozen functie als commando-string.
|
|
11
|
+
|
|
12
|
+
Dit is een aparte module omdat console-interactie (menu, scherm wissen)
|
|
13
|
+
niets met loggen te maken heeft. De logger logt; de console toont.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from os import system, name, path
|
|
17
|
+
from arin_log.logger import log_notes
|
|
18
|
+
|
|
19
|
+
br = chr(10)
|
|
20
|
+
prgnm = path.basename(__file__).split('.')[0]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def cls():
|
|
24
|
+
"""Wist het scherm. Op Windows met 'cls', anders met 'clear'."""
|
|
25
|
+
system('cls' if name == 'nt' else 'clear')
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def menu_dict(optlst):
|
|
30
|
+
"""Bouwt een dict van genummerde menu-opties.
|
|
31
|
+
|
|
32
|
+
De opties krijgen de sleutels '1', '2', '3', ... in volgorde.
|
|
33
|
+
Daarnaast worden de aliassen 'e', 'exit', 'q' en 'quit' toegevoegd,
|
|
34
|
+
die allemaal naar 'exit' wijzen."""
|
|
35
|
+
opties = {}
|
|
36
|
+
idx = 1
|
|
37
|
+
for opt in optlst:
|
|
38
|
+
ids = str(idx)
|
|
39
|
+
opties[ids] = opt
|
|
40
|
+
idx += 1
|
|
41
|
+
opties['e'] = 'exit'
|
|
42
|
+
opties['exit'] = 'exit'
|
|
43
|
+
opties['q'] = 'exit'
|
|
44
|
+
opties['quit'] = 'exit'
|
|
45
|
+
return opties
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def cons_menu(optlst, logloc=None):
|
|
49
|
+
"""Toont een console-menu en retourneert de gekozen functie
|
|
50
|
+
als een commando-string (bijvoorbeeld 'mijn_functie()').
|
|
51
|
+
|
|
52
|
+
optlst - lijst met functienamen (strings, zonder haakjes).
|
|
53
|
+
'exit' wordt automatisch toegevoegd als het ontbreekt.
|
|
54
|
+
logloc - context voor de logger; standaard None.
|
|
55
|
+
|
|
56
|
+
De functie blijft het menu tonen tot de gebruiker een geldige
|
|
57
|
+
keuze maakt. 'exit', 'e', 'q' en 'quit' geven 'exit(0)' terug."""
|
|
58
|
+
|
|
59
|
+
exts = ['e', 'exit', 'q', 'quit']
|
|
60
|
+
tlst = optlst
|
|
61
|
+
if 'exit' not in tlst:
|
|
62
|
+
tlst.append('exit')
|
|
63
|
+
|
|
64
|
+
opties = menu_dict(tlst)
|
|
65
|
+
keys = list(opties.keys())
|
|
66
|
+
lng = len(keys)
|
|
67
|
+
choice = '#'
|
|
68
|
+
com = ''
|
|
69
|
+
while choice not in keys:
|
|
70
|
+
cls()
|
|
71
|
+
log_notes([f'{logloc} ; console ; menu', br])
|
|
72
|
+
for key in keys:
|
|
73
|
+
tmp = opties[key].replace('_', ' ')
|
|
74
|
+
if key not in exts:
|
|
75
|
+
print(f'{key}. {tmp}')
|
|
76
|
+
print()
|
|
77
|
+
choice = input(f'Enter your choice ({keys[0]}-{keys[-5]}): ')
|
|
78
|
+
if choice.lower() in exts:
|
|
79
|
+
choice = choice.lower()
|
|
80
|
+
cls()
|
|
81
|
+
if choice in keys:
|
|
82
|
+
com = opties[choice]
|
|
83
|
+
tmp = opties[choice].replace('_', ' ')
|
|
84
|
+
x = keys.index(choice)
|
|
85
|
+
if x < lng - 5:
|
|
86
|
+
log_notes([br, f'Your ; choice ; {tmp}'], logloc=logloc)
|
|
87
|
+
if com.lower() == 'exit':
|
|
88
|
+
com = 'exit(0)'
|
|
89
|
+
else:
|
|
90
|
+
com = f'{com}()'
|
|
91
|
+
return com
|
arin_log/demo_log.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
demo_log.py
|
|
3
|
+
-----------
|
|
4
|
+
Eerste kennismaking met arin_log.
|
|
5
|
+
|
|
6
|
+
Laat zien:
|
|
7
|
+
- hoe je een hoofdmodule markeert met log_start / log_exit
|
|
8
|
+
- hoe je een exception logt met log_except
|
|
9
|
+
- hoe je console-output en logbestand-uitvoer combineert
|
|
10
|
+
via de logloc-parameter
|
|
11
|
+
|
|
12
|
+
Draai met:
|
|
13
|
+
python -m arin_log.demo_log
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from arin_log.logger import log_start, log_notes, log_except, log_exit
|
|
17
|
+
from sys import _getframe
|
|
18
|
+
from os import path
|
|
19
|
+
|
|
20
|
+
prgnm = path.basename(__file__).split('.')[0]
|
|
21
|
+
|
|
22
|
+
def demo():
|
|
23
|
+
log_start(prgnm, logloc=prgnm)
|
|
24
|
+
|
|
25
|
+
log_notes("demo ; test ; logger")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
1 / 0
|
|
29
|
+
except Exception:
|
|
30
|
+
log_except(_getframe(), "zero ; division ; exception", logloc=prgnm)
|
|
31
|
+
|
|
32
|
+
log_notes("continuing ; logging ; success")
|
|
33
|
+
log_notes("demo ; test ; success", logloc=prgnm)
|
|
34
|
+
log_exit(prgnm, logloc=prgnm)
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
demo()
|
arin_log/folder.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""
|
|
2
|
+
folder.py
|
|
3
|
+
---------
|
|
4
|
+
Beheert de config folder van arin_log.
|
|
5
|
+
|
|
6
|
+
Alle user-specifieke configuratie staat in één bestand:
|
|
7
|
+
|
|
8
|
+
%APPDATA%\\arin_log\\config_log.json
|
|
9
|
+
|
|
10
|
+
Dit bestand bevat:
|
|
11
|
+
- de kleuren (gekopieerd uit log_cnfg.json bij de eerste run)
|
|
12
|
+
- de gekozen log_folder
|
|
13
|
+
|
|
14
|
+
Bij de eerste run wordt het bestand aangemaakt:
|
|
15
|
+
1. De gebruiker kiest een log folder via de dialoog.
|
|
16
|
+
2. De kleuren worden uit log_cnfg.json (package folder) gelezen.
|
|
17
|
+
3. Alles wordt opgeslagen in config_log.json.
|
|
18
|
+
|
|
19
|
+
Bij volgende runs wordt alleen config_log.json gelezen.
|
|
20
|
+
Als het bestand corrupt is, wordt het opnieuw aangemaakt.
|
|
21
|
+
|
|
22
|
+
Publieke functies:
|
|
23
|
+
get_folder() - geeft de huidige log_folder terug
|
|
24
|
+
change_folder(parent=None) - folder-dialoog, update config + log_folder
|
|
25
|
+
get_config() - geeft de volledige config dict terug
|
|
26
|
+
|
|
27
|
+
Interne functie:
|
|
28
|
+
_get_folder() - resolver, eenmalig bij import
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from os import path, makedirs, environ
|
|
32
|
+
from arin_log.utils import load_json, save_json
|
|
33
|
+
br = chr(10)
|
|
34
|
+
|
|
35
|
+
CNFG_FOLDER = "arin_log"
|
|
36
|
+
CNFG_FILE = "config_log.json"
|
|
37
|
+
TEMPLATE_FILE = "log_cnfg.json"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# =====================================================================
|
|
41
|
+
# 1. INTERNE HELPERS — paden
|
|
42
|
+
# =====================================================================
|
|
43
|
+
def _config_folder():
|
|
44
|
+
"""Bepaalt de config folder.
|
|
45
|
+
|
|
46
|
+
Windows: %APPDATA%\\arin_log
|
|
47
|
+
Anders: ~/.arin_log
|
|
48
|
+
Maakt de folder aan als die nog niet bestaat.
|
|
49
|
+
Retourneert het pad, of False bij falen."""
|
|
50
|
+
base = environ.get("APPDATA")
|
|
51
|
+
if base:
|
|
52
|
+
folder = path.join(base, CNFG_FOLDER)
|
|
53
|
+
else:
|
|
54
|
+
folder = path.join(path.expanduser("~"), ".arin_log")
|
|
55
|
+
try:
|
|
56
|
+
makedirs(folder, exist_ok=True)
|
|
57
|
+
except Exception:
|
|
58
|
+
return False
|
|
59
|
+
return folder
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _config_file():
|
|
63
|
+
"""Pad naar config_log.json, of False."""
|
|
64
|
+
folder = _config_folder()
|
|
65
|
+
if not folder:
|
|
66
|
+
return False
|
|
67
|
+
return path.join(folder, CNFG_FILE)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _template_file():
|
|
71
|
+
"""Pad naar log_cnfg.json (in de package folder)."""
|
|
72
|
+
return path.join(path.dirname(__file__), TEMPLATE_FILE)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# =====================================================================
|
|
76
|
+
# 2. INTERNE HELPERS — config lezen en schrijven
|
|
77
|
+
# =====================================================================
|
|
78
|
+
def _read_config():
|
|
79
|
+
"""Leest config_log.json en retourneert de dict.
|
|
80
|
+
Retourneert {} als het bestand ontbreekt."""
|
|
81
|
+
pf = _config_file()
|
|
82
|
+
if pf and path.isfile(pf):
|
|
83
|
+
d = load_json(pf)
|
|
84
|
+
if isinstance(d, dict):
|
|
85
|
+
return d
|
|
86
|
+
return {}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _write_config(data):
|
|
90
|
+
"""Schrijft data naar config_log.json.
|
|
91
|
+
Retourneert True bij succes, anders False."""
|
|
92
|
+
pf = _config_file()
|
|
93
|
+
if not pf:
|
|
94
|
+
return False
|
|
95
|
+
return save_json(pf, data, indent=4)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _read_template():
|
|
99
|
+
"""Leest log_cnfg.json uit de package folder.
|
|
100
|
+
Retourneert de dict, of {} als het bestand ontbreekt."""
|
|
101
|
+
pf = _template_file()
|
|
102
|
+
if path.isfile(pf):
|
|
103
|
+
d = load_json(pf)
|
|
104
|
+
if isinstance(d, dict):
|
|
105
|
+
return d
|
|
106
|
+
return {}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _create_config(log_folder):
|
|
110
|
+
"""Maakt config_log.json aan.
|
|
111
|
+
|
|
112
|
+
Kopieert alle sleutels uit log_cnfg.json (de template) en
|
|
113
|
+
voegt 'log_folder' toe. Retourneert True bij succes."""
|
|
114
|
+
d = _read_template()
|
|
115
|
+
d["log_folder"] = log_folder
|
|
116
|
+
return _write_config(d)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _save_folder(folder):
|
|
120
|
+
"""Update alleen de 'log_folder'-key in config_log.json.
|
|
121
|
+
Andere keys blijven staan. Retourneert True bij succes."""
|
|
122
|
+
d = _read_config()
|
|
123
|
+
if not d:
|
|
124
|
+
return _create_config(folder)
|
|
125
|
+
d["log_folder"] = folder
|
|
126
|
+
return _write_config(d)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _save_failed():
|
|
130
|
+
"""Meldt dat het opslaan is mislukt en stopt met exitcode 1."""
|
|
131
|
+
from sys import stderr, exit
|
|
132
|
+
stderr.write(f"folder ; selection ; not saved{br}administrator ; adjustments ; required")
|
|
133
|
+
exit(1)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# =====================================================================
|
|
137
|
+
# 3. INTERNE HELPERS — folder keuze
|
|
138
|
+
# =====================================================================
|
|
139
|
+
def _norm(p):
|
|
140
|
+
"""Normaliseert een pad naar forward slashes.
|
|
141
|
+
Retourneert het pad als string, of de oorspronkelijke waarde
|
|
142
|
+
als die leeg is."""
|
|
143
|
+
return str(p).replace("\\", "/") if p else p
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def manual_input():
|
|
147
|
+
"""Vraagt de gebruiker om een folder via de console.
|
|
148
|
+
|
|
149
|
+
Blijft vragen tot de gebruiker een niet-lege invoer geeft en
|
|
150
|
+
de folder bestaat (of aangemaakt kan worden). Normaliseert het
|
|
151
|
+
pad naar forward slashes.
|
|
152
|
+
|
|
153
|
+
De hele lus zit in een buitenste try/except. Als er iets
|
|
154
|
+
onverwachts misgaat, geeft de functie False terug in plaats
|
|
155
|
+
van te crashen."""
|
|
156
|
+
try:
|
|
157
|
+
while True:
|
|
158
|
+
gekozen = input("Enter a folder path for your log files: ")
|
|
159
|
+
if not gekozen:
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
gekozen = _norm(gekozen)
|
|
163
|
+
|
|
164
|
+
if not path.isdir(gekozen):
|
|
165
|
+
try:
|
|
166
|
+
makedirs(gekozen, exist_ok=True)
|
|
167
|
+
except Exception:
|
|
168
|
+
print(f"Could not create folder: {gekozen}")
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
return gekozen
|
|
172
|
+
except Exception:
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _folder_dialog(parent=None):
|
|
177
|
+
"""Opent een folder-dialoog. tkinter wordt lokaal geïmporteerd.
|
|
178
|
+
|
|
179
|
+
parent=None -> tijdelijke verborgen root (batch-context).
|
|
180
|
+
parent=<Tk> -> gebruikt de bestaande root (GUI-context).
|
|
181
|
+
Retourneert het gekozen pad, of '' bij annuleren.
|
|
182
|
+
|
|
183
|
+
Als tkinter of de GUI niet beschikbaar is, valt de functie
|
|
184
|
+
terug op manual_input() (console-invoer). Als dat ook faalt,
|
|
185
|
+
stopt het programma met de melding 'platform ; graphic GUI ;
|
|
186
|
+
inexistent' en exitcode 1."""
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
from tkinter import Tk, filedialog
|
|
190
|
+
|
|
191
|
+
root = None
|
|
192
|
+
folder = ""
|
|
193
|
+
eigen_root = False
|
|
194
|
+
if parent is None:
|
|
195
|
+
root = Tk()
|
|
196
|
+
root.withdraw()
|
|
197
|
+
eigen_root = True
|
|
198
|
+
parent = root
|
|
199
|
+
try:
|
|
200
|
+
folder = filedialog.askdirectory(
|
|
201
|
+
parent=parent,
|
|
202
|
+
title="Choose a folder for your log files"
|
|
203
|
+
)
|
|
204
|
+
finally:
|
|
205
|
+
if eigen_root:
|
|
206
|
+
try:
|
|
207
|
+
root.destroy()
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
return folder if folder else ""
|
|
212
|
+
|
|
213
|
+
except Exception:
|
|
214
|
+
gekozen = manual_input()
|
|
215
|
+
if gekozen:
|
|
216
|
+
return gekozen
|
|
217
|
+
from sys import stderr, exit
|
|
218
|
+
stderr.write(f"platform ; graphic GUI ; inexistent{br}administrator ; adjustments ; required")
|
|
219
|
+
exit(1)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# =====================================================================
|
|
223
|
+
# 4. RESOLVER — eenmalig bij import
|
|
224
|
+
# =====================================================================
|
|
225
|
+
def _get_folder():
|
|
226
|
+
"""Resolver: lees config_log.json, of maak het aan.
|
|
227
|
+
|
|
228
|
+
Retourneert altijd een geldig pad (string).
|
|
229
|
+
- Als config_log.json bestaat: log_folder daaruit.
|
|
230
|
+
- Anders: dialoog, dan config_log.json aanmaken.
|
|
231
|
+
- Bij corruptie: opnieuw aanmaken.
|
|
232
|
+
|
|
233
|
+
Wordt eenmalig aangeroepen bij het laden van de module.
|
|
234
|
+
Daarna is log_folder gezet en gebruikt de rest van de code
|
|
235
|
+
die waarde via get_folder() of folder.log_folder."""
|
|
236
|
+
d = _read_config()
|
|
237
|
+
folder = d.get("log_folder", False)
|
|
238
|
+
|
|
239
|
+
if folder:
|
|
240
|
+
return _norm(folder)
|
|
241
|
+
|
|
242
|
+
while True:
|
|
243
|
+
gekozen = _folder_dialog()
|
|
244
|
+
if gekozen:
|
|
245
|
+
gekozen = _norm(gekozen)
|
|
246
|
+
if not _create_config(gekozen):
|
|
247
|
+
_save_failed()
|
|
248
|
+
return gekozen
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# =====================================================================
|
|
252
|
+
# 5. PUBLIEKE FUNCTIES
|
|
253
|
+
# =====================================================================
|
|
254
|
+
def get_folder():
|
|
255
|
+
"""Geeft de huidige log_folder terug.
|
|
256
|
+
|
|
257
|
+
Altijd de actuele waarde. Aanroepen na change_folder() geeft
|
|
258
|
+
de nieuwe folder, niet de oude. Dit is de enige juiste manier
|
|
259
|
+
om de log folder op te vragen vanuit een andere module."""
|
|
260
|
+
return log_folder
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def change_folder(parent=None):
|
|
264
|
+
"""Opent de folder-dialoog, update config_log.json en de
|
|
265
|
+
globale log_folder en retourneert de nieuwe waarde.
|
|
266
|
+
|
|
267
|
+
parent=None -> tijdelijke verborgen root (batch-context).
|
|
268
|
+
parent=<Tk> -> gebruikt de bestaande root (GUI-context).
|
|
269
|
+
|
|
270
|
+
Bij annulering: retourneert False, log_folder blijft ongewijzigd.
|
|
271
|
+
Retourneert het nieuwe pad (string) bij succes.
|
|
272
|
+
|
|
273
|
+
Als de keuze niet opgeslagen kan worden, stopt het programma
|
|
274
|
+
met de melding 'folder ; selection ; not saved' en exitcode 1."""
|
|
275
|
+
global log_folder
|
|
276
|
+
|
|
277
|
+
gekozen = _folder_dialog(parent=parent)
|
|
278
|
+
if not gekozen:
|
|
279
|
+
return False
|
|
280
|
+
|
|
281
|
+
gekozen = _norm(gekozen)
|
|
282
|
+
if not _save_folder(gekozen):
|
|
283
|
+
_save_failed()
|
|
284
|
+
log_folder = gekozen
|
|
285
|
+
return log_folder
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def get_config():
|
|
289
|
+
"""Geeft de volledige config dict terug uit config_log.json.
|
|
290
|
+
|
|
291
|
+
Wordt gebruikt door logger.config_logs() om de kleuren te laden."""
|
|
292
|
+
return _read_config()
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# =====================================================================
|
|
296
|
+
# 6. TOP-LEVEL UITVOERING — log_folder is klaar na import
|
|
297
|
+
# =====================================================================
|
|
298
|
+
log_folder = _get_folder()
|
|
299
|
+
|
|
300
|
+
# =====================================================================
|
|
301
|
+
# 7. TESTBLOK
|
|
302
|
+
# =====================================================================
|
|
303
|
+
if __name__ == "__main__":
|
|
304
|
+
print(f"log_folder: {log_folder}")
|
|
305
|
+
print(f"config: {get_config()}")
|
|
306
|
+
exit(0)
|
arin_log/log_cnfg.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"color_codes": {
|
|
3
|
+
"black": "30m",
|
|
4
|
+
"blue": "34m",
|
|
5
|
+
"cyan": "36m",
|
|
6
|
+
"darkgrey": "90m",
|
|
7
|
+
"green": "32m",
|
|
8
|
+
"lightblue": "94m",
|
|
9
|
+
"lightcyan": "96m",
|
|
10
|
+
"lightgreen": "92m",
|
|
11
|
+
"lightgrey": "37m",
|
|
12
|
+
"lightred": "91m",
|
|
13
|
+
"orange": "33m",
|
|
14
|
+
"pink": "95m",
|
|
15
|
+
"purple": "35m",
|
|
16
|
+
"red": "31m",
|
|
17
|
+
"yellow": "93m"
|
|
18
|
+
},
|
|
19
|
+
"color_words": {
|
|
20
|
+
"Arin": "cyan",
|
|
21
|
+
"completed": "cyan",
|
|
22
|
+
"modification": "cyan",
|
|
23
|
+
"pending": "cyan",
|
|
24
|
+
"terminated": "cyan",
|
|
25
|
+
"satisfied": "cyan",
|
|
26
|
+
"shutdown": "cyan",
|
|
27
|
+
"startup": "cyan",
|
|
28
|
+
"rebooted": "cyan",
|
|
29
|
+
"recovery": "cyan",
|
|
30
|
+
"running": "cyan",
|
|
31
|
+
"updated": "cyan",
|
|
32
|
+
"added": "green",
|
|
33
|
+
"succes": "green",
|
|
34
|
+
"success": "green",
|
|
35
|
+
"present": "green",
|
|
36
|
+
"restored": "green",
|
|
37
|
+
"saved": "green",
|
|
38
|
+
"corrupt": "lightred",
|
|
39
|
+
"error": "lightred",
|
|
40
|
+
"empty": "lightred",
|
|
41
|
+
"fail": "lightred",
|
|
42
|
+
"false": "lightred",
|
|
43
|
+
"inexistent": "lightred",
|
|
44
|
+
"invalid": "lightred",
|
|
45
|
+
"not exist": "lightred",
|
|
46
|
+
"not valid": "lightred",
|
|
47
|
+
"rude": "lightred",
|
|
48
|
+
"broken": "lightred",
|
|
49
|
+
"unrecoverable": "lightred",
|
|
50
|
+
"exception": "pink",
|
|
51
|
+
"changed": "orange",
|
|
52
|
+
"deleted": "orange",
|
|
53
|
+
"interrupted": "orange",
|
|
54
|
+
"interruption": "orange",
|
|
55
|
+
"fault": "orange",
|
|
56
|
+
"fault(s)": "orange",
|
|
57
|
+
"none": "orange",
|
|
58
|
+
"removed": "orange",
|
|
59
|
+
"timeout": "orange",
|
|
60
|
+
"unaltered": "orange",
|
|
61
|
+
"unfit": "orange"
|
|
62
|
+
}
|
|
63
|
+
}
|
arin_log/logger.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""
|
|
2
|
+
logger.py
|
|
3
|
+
---------
|
|
4
|
+
Centrale logging-module van arin_log.
|
|
5
|
+
|
|
6
|
+
Ondersteunende modules:
|
|
7
|
+
folder.py - beheert de config en de log folder.
|
|
8
|
+
utils.py - algemene helpers (load_json, save_json).
|
|
9
|
+
console.py - console-interactie (menu, scherm wissen).
|
|
10
|
+
|
|
11
|
+
Publieke functies:
|
|
12
|
+
log_start, log_notes, log_except, log_exit, config_logs,
|
|
13
|
+
file_exist, tijd, not_set, last_record.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from os import path, makedirs
|
|
17
|
+
from sys import _getframe, stderr, exit
|
|
18
|
+
from gc import collect
|
|
19
|
+
from re import sub
|
|
20
|
+
import time
|
|
21
|
+
|
|
22
|
+
from arin_log import folder
|
|
23
|
+
|
|
24
|
+
color_words = {}
|
|
25
|
+
color_codes = {}
|
|
26
|
+
prgnm = ''
|
|
27
|
+
tas = ''
|
|
28
|
+
tas2 = ''
|
|
29
|
+
br = chr(10)
|
|
30
|
+
recs_dic = {}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def not_set():
|
|
34
|
+
global prgnm
|
|
35
|
+
isns = True if prgnm == '' else False
|
|
36
|
+
return isns
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def tijd():
|
|
40
|
+
return time.strftime("%H:%M:%S")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def last_record(logloc):
|
|
44
|
+
global recs_dic, br
|
|
45
|
+
result = recs_dic.get(logloc, br)
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def to_cons(note):
|
|
50
|
+
"""Print note naar de console, met kleuren volgens color_words
|
|
51
|
+
en color_codes. Woorden worden alleen gekleurd als ze exact
|
|
52
|
+
overeenkomen (inclusief spaties)."""
|
|
53
|
+
global color_words, color_codes
|
|
54
|
+
clrlst = list(color_words.keys())
|
|
55
|
+
if any([x in note for x in clrlst]):
|
|
56
|
+
for w in clrlst:
|
|
57
|
+
if w in note:
|
|
58
|
+
tmp = sub(r'[^\w\s]', '', note)
|
|
59
|
+
tmp = sub(' +', ' ', tmp)
|
|
60
|
+
nwlst = tmp.split(' ')
|
|
61
|
+
twl = w.split(' ')
|
|
62
|
+
if all([x in nwlst for x in twl]):
|
|
63
|
+
clr = color_words.get(w, None)
|
|
64
|
+
clrc = color_codes.get(clr, None)
|
|
65
|
+
if clrc is not None:
|
|
66
|
+
note = note.replace(w, f"\033[{clrc}{w}\033[00m")
|
|
67
|
+
print(note)
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def to_file(data, logloc=None):
|
|
72
|
+
global br, recs_dic
|
|
73
|
+
frm = _getframe()
|
|
74
|
+
logloc = 'error' if logloc is None else logloc
|
|
75
|
+
fnm = '.'.join([tas2, logloc, 'log'])
|
|
76
|
+
pf = path.join(folder.log_folder, fnm)
|
|
77
|
+
act = 'a' if path.isfile(pf) else 'w'
|
|
78
|
+
if isinstance(data, list):
|
|
79
|
+
records = data
|
|
80
|
+
else:
|
|
81
|
+
records = [str(data)]
|
|
82
|
+
ln = len(records)
|
|
83
|
+
if ln > 0:
|
|
84
|
+
try:
|
|
85
|
+
with open(pf, act, encoding="utf-8") as txtFile:
|
|
86
|
+
for rec in records:
|
|
87
|
+
|
|
88
|
+
if (last_record(logloc) == br) and (rec == br):
|
|
89
|
+
continue
|
|
90
|
+
else:
|
|
91
|
+
if rec == br:
|
|
92
|
+
recs_dic[logloc] = br
|
|
93
|
+
rec = ''
|
|
94
|
+
else:
|
|
95
|
+
recs_dic[logloc] = 'ne'
|
|
96
|
+
rec = rec + br
|
|
97
|
+
txtFile.write(rec)
|
|
98
|
+
|
|
99
|
+
if any([x in rec for x in ['completed', 'terminated']]):
|
|
100
|
+
lng = len(rec)
|
|
101
|
+
if 'completed' in rec:
|
|
102
|
+
lijn = '-' * (lng - 1) + br
|
|
103
|
+
elif 'terminated' in rec:
|
|
104
|
+
lijn = '_' * (lng - 1) + br
|
|
105
|
+
txtFile.write(lijn)
|
|
106
|
+
recs_dic[logloc] = 'ne'
|
|
107
|
+
txtFile.close()
|
|
108
|
+
except Exception:
|
|
109
|
+
st = ' ; '.join(['logging', fnm, 'fail'])
|
|
110
|
+
log_except(frm, st, mod=prgnm, logloc=logloc)
|
|
111
|
+
else:
|
|
112
|
+
st = ' ; '.join(['log', fnm, 'empty'])
|
|
113
|
+
log_except(frm, st, mod=prgnm, logloc=logloc)
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def log_notes(notes, cons=None, logloc=None, end=False, ar_on=True):
|
|
118
|
+
"""Schrijft notes naar de console (indien cons=True) en naar het
|
|
119
|
+
logbestand (indien logloc is meegegeven).
|
|
120
|
+
|
|
121
|
+
end=True -> gebruik 'Arin' als prefix (voor afsluitende berichten).
|
|
122
|
+
end=False -> gebruik 'Arin :' als prefix (of geen prefix als ar_on=False).
|
|
123
|
+
"""
|
|
124
|
+
global br, prgnm
|
|
125
|
+
cons = True if cons is None else cons
|
|
126
|
+
st = False if logloc is None else True
|
|
127
|
+
if isinstance(notes, str):
|
|
128
|
+
notes = [notes]
|
|
129
|
+
for el in notes:
|
|
130
|
+
if cons:
|
|
131
|
+
if end:
|
|
132
|
+
if el == br:
|
|
133
|
+
print()
|
|
134
|
+
else:
|
|
135
|
+
to_cons(' '.join(['Arin', el]))
|
|
136
|
+
else:
|
|
137
|
+
if el == br:
|
|
138
|
+
print()
|
|
139
|
+
elif ar_on:
|
|
140
|
+
to_cons(' '.join(['Arin :', el]))
|
|
141
|
+
else:
|
|
142
|
+
to_cons(el)
|
|
143
|
+
if st:
|
|
144
|
+
to_file(el, logloc=logloc)
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def log_start(pnm, imp=None, cons=None, logloc=None):
|
|
149
|
+
"""Markeert het begin van een module-run en ververst de datum
|
|
150
|
+
waarop het logbestand is gebaseerd (tas2). Zo komt een run die
|
|
151
|
+
na middernacht start in het logbestand van de nieuwe dag."""
|
|
152
|
+
global br, tas2
|
|
153
|
+
imp = False if imp is None else imp
|
|
154
|
+
typ = 'pending' if imp else 'running'
|
|
155
|
+
cons = True if cons is None else cons
|
|
156
|
+
tas2 = time.strftime('%y-%m-%d')
|
|
157
|
+
lijn = ' ; '.join([pnm, tijd(), typ])
|
|
158
|
+
if last_record(logloc) != br:
|
|
159
|
+
lijn = [br, lijn]
|
|
160
|
+
if not imp:
|
|
161
|
+
lijn = [lijn, br]
|
|
162
|
+
log_notes(lijn, cons=cons, logloc=logloc)
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def log_except(frm, note, mod=None, cons=None, logloc=None):
|
|
167
|
+
global br
|
|
168
|
+
logloc = 'error' if logloc is None else logloc
|
|
169
|
+
cons = True if cons is None else cons
|
|
170
|
+
if last_record(logloc) == br:
|
|
171
|
+
notes = []
|
|
172
|
+
else:
|
|
173
|
+
notes = [br]
|
|
174
|
+
flpf = frm.f_code.co_filename
|
|
175
|
+
mod = path.basename(flpf).split('.')[0] if mod is None else mod
|
|
176
|
+
fncpos = '.'.join([mod, frm.f_code.co_name, str(frm.f_lineno)])
|
|
177
|
+
lijn = ' ; '.join([fncpos, tijd(), 'exception'])
|
|
178
|
+
notes.append(lijn)
|
|
179
|
+
if isinstance(note, str):
|
|
180
|
+
notes.append(note)
|
|
181
|
+
elif isinstance(note, list):
|
|
182
|
+
notes = notes + note
|
|
183
|
+
log_notes(notes, logloc=logloc, cons=cons)
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def log_exit(prginf, imp=None, cons=None, logloc=None):
|
|
188
|
+
global br
|
|
189
|
+
imp = False if imp is None else imp
|
|
190
|
+
cons = True if cons is None else cons
|
|
191
|
+
notes = [prginf] if isinstance(prginf, str) else prginf
|
|
192
|
+
if imp:
|
|
193
|
+
notes[-1] = ' ; '.join([notes[-1], tijd(), 'completed'])
|
|
194
|
+
else:
|
|
195
|
+
if last_record(logloc) != br:
|
|
196
|
+
notes = [br] + notes
|
|
197
|
+
notes[-1] = ' ; '.join([notes[-1], tijd(), 'terminated'])
|
|
198
|
+
notes.append(br)
|
|
199
|
+
log_notes(notes, logloc=logloc, cons=cons)
|
|
200
|
+
collect()
|
|
201
|
+
if imp:
|
|
202
|
+
return
|
|
203
|
+
else:
|
|
204
|
+
import sys
|
|
205
|
+
sys.tracebacklimit = 0
|
|
206
|
+
sys.exit(0)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def config_logs():
|
|
210
|
+
"""Leest de kleuren uit config_log.json (via folder.get_config)
|
|
211
|
+
en zet ze als globals.
|
|
212
|
+
|
|
213
|
+
De sleutel 'log_folder' wordt overgeslagen; die hoort bij folder.py."""
|
|
214
|
+
jsnd = folder.get_config()
|
|
215
|
+
if len(jsnd) > 0:
|
|
216
|
+
for k in jsnd.keys():
|
|
217
|
+
if k == "log_folder":
|
|
218
|
+
continue
|
|
219
|
+
globals()[k] = jsnd.get(k, False)
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def file_exist(pf, timeout=None):
|
|
224
|
+
from time import sleep
|
|
225
|
+
timeout = 0.24 if timeout is None else timeout
|
|
226
|
+
if pf is None:
|
|
227
|
+
return False
|
|
228
|
+
else:
|
|
229
|
+
st = True
|
|
230
|
+
slt = 0.08
|
|
231
|
+
tt = 0
|
|
232
|
+
while not path.isfile(pf):
|
|
233
|
+
tt = tt + slt
|
|
234
|
+
if tt >= timeout:
|
|
235
|
+
st = False
|
|
236
|
+
break
|
|
237
|
+
sleep(slt)
|
|
238
|
+
return st
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _ensure_log_folder():
|
|
242
|
+
"""Controleert of folder.log_folder bestaat en maakt hem zo nodig
|
|
243
|
+
aan. Lukt dat niet, dan stopt het programma met een duidelijke
|
|
244
|
+
melding op stderr en exitcode 1."""
|
|
245
|
+
try:
|
|
246
|
+
makedirs(folder.log_folder, exist_ok=True)
|
|
247
|
+
except Exception:
|
|
248
|
+
stderr.write(f"log ; folder ; inexistent{br}administrator ; adjustments ; required")
|
|
249
|
+
exit(1)
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
if not_set():
|
|
254
|
+
prgnm = path.basename(__file__).split('.')[0]
|
|
255
|
+
tas2 = time.strftime('%y-%m-%d')
|
|
256
|
+
tas = time.strftime('%Y-%m-%d')
|
|
257
|
+
_ensure_log_folder()
|
|
258
|
+
config_logs()
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == "__main__":
|
|
262
|
+
from arin_log.demo_log import demo
|
|
263
|
+
demo()
|
|
264
|
+
exit(0)
|
|
265
|
+
|
arin_log/utils.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from os import path
|
|
2
|
+
from json import dump, load
|
|
3
|
+
|
|
4
|
+
# Vaste ANSI-kleuren voor _warn.
|
|
5
|
+
# Los van de user-config (log_cnfg.json), omdat utils.py een blad is
|
|
6
|
+
# en geen toegang heeft tot de logger.
|
|
7
|
+
# Kleurcodes uit log_cnfg.json: cyan=36m, pink=95m, lightred=91m.
|
|
8
|
+
_CYAN = "\033[36m"
|
|
9
|
+
_RESET = "\033[00m"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _warn(note, word, color):
|
|
13
|
+
"""Print een waarschuwing op de console.
|
|
14
|
+
|
|
15
|
+
'Arin' wordt altijd cyaan. Het opgegeven woord krijgt de
|
|
16
|
+
opgegeven kleurcode. Wordt alleen gebruikt bij interne fouten
|
|
17
|
+
in utils.py (corrupt of ontbrekend bestand).
|
|
18
|
+
|
|
19
|
+
Deze functie staat los van de logger: utils.py is een blad en
|
|
20
|
+
mag niet van logger afhangen (zou een circulaire import geven)."""
|
|
21
|
+
note = note.replace(word, f"\033[{color}{word}{_RESET}")
|
|
22
|
+
print(f"{_CYAN}Arin{_RESET} : {note}")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_json(pf):
|
|
27
|
+
"""Leest een JSON-bestand en retourneert de inhoud als dict.
|
|
28
|
+
Retourneert {} als het bestand ontbreekt of corrupt is.
|
|
29
|
+
|
|
30
|
+
Bij ontbreken of corruptie wordt een melding op de console
|
|
31
|
+
geprint, met Arin-prefix en vaste kleuren."""
|
|
32
|
+
jsnd = {}
|
|
33
|
+
fnm = path.basename(pf)
|
|
34
|
+
if path.isfile(pf):
|
|
35
|
+
try:
|
|
36
|
+
with open(pf, encoding='utf-8') as fil:
|
|
37
|
+
jsnd = load(fil)
|
|
38
|
+
except Exception:
|
|
39
|
+
_warn(f"data ; {fnm} ; corrupt", "corrupt", "95m")
|
|
40
|
+
else:
|
|
41
|
+
_warn(f"file ; {fnm} ; inexistent", "inexistent", "91m")
|
|
42
|
+
return jsnd
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def save_json(pf, data, indent=None):
|
|
46
|
+
"""Schrijft data als JSON naar pf.
|
|
47
|
+
|
|
48
|
+
indent=None of een negatief getal -> geen indentering.
|
|
49
|
+
indent=<int> >= 0 -> indentering met die breedte.
|
|
50
|
+
|
|
51
|
+
Retourneert True bij succes, anders False.
|
|
52
|
+
Bij een fout wordt een melding op de console geprint."""
|
|
53
|
+
indent = indent if isinstance(indent, int) and indent >= 0 else None
|
|
54
|
+
fnm = path.basename(pf)
|
|
55
|
+
try:
|
|
56
|
+
with open(pf, 'w', encoding='utf-8') as fil:
|
|
57
|
+
dump(data, fil, ensure_ascii=False, indent=indent)
|
|
58
|
+
return True
|
|
59
|
+
except Exception:
|
|
60
|
+
_warn(f"save ; {fnm} ; fail", "fail", "91m")
|
|
61
|
+
return False
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arin-log
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A lightweight logging package for long-running pipelines: a virtual assistant for the user, a clean archive for the developer.
|
|
5
|
+
Author-email: Marcus Aurelius <marcus.aurelius.hu@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/marcus-aurelius-hu/arin_log
|
|
8
|
+
Project-URL: Source, https://github.com/marcus-aurelius-hu/arin_log
|
|
9
|
+
Project-URL: Issues, https://github.com/marcus-aurelius-hu/arin_log/issues
|
|
10
|
+
Keywords: logging,pipeline,console,logger,arin
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: System :: Logging
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# arin-log
|
|
27
|
+
|
|
28
|
+
A lightweight, cross-platform logging package for long-running pipelines.
|
|
29
|
+
A virtual assistant for the user. A clean archive for the developer.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## What is `arin_log`?
|
|
34
|
+
|
|
35
|
+
`arin_log` combines two roles in one package:
|
|
36
|
+
|
|
37
|
+
1. **A virtual assistant (Arin)** who talks to the user on the console
|
|
38
|
+
and keeps them informed about what the script is doing.
|
|
39
|
+
2. **A clean, technical log file** that contains only the beacons:
|
|
40
|
+
start, exception, exit. Meant for the developer who has to
|
|
41
|
+
reconstruct later what happened.
|
|
42
|
+
|
|
43
|
+
The user sees a story. The developer sees a trail. That is the core.
|
|
44
|
+
|
|
45
|
+
**Arin is not an AI.** She is a voice — a smart logger with a
|
|
46
|
+
character. This is a deliberate choice not to abuse the AI hype.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install arin-log
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
On first import, a folder dialog appears asking where you want to
|
|
57
|
+
store your log files. The choice, together with the default colors,
|
|
58
|
+
is saved in:
|
|
59
|
+
|
|
60
|
+
- **Windows:** `%APPDATA%\arin_log\config_log.json`
|
|
61
|
+
- **Linux/macOS:** `~/.arin_log/config_log.json`
|
|
62
|
+
|
|
63
|
+
The dialog appears only once. On every next run, `arin_log` reads
|
|
64
|
+
this file and skips the dialog.
|
|
65
|
+
|
|
66
|
+
If you want to change the log folder later:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from arin_log import change_folder
|
|
70
|
+
change_folder()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Quick example
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from arin_log import log_start, log_notes, log_except, log_exit
|
|
79
|
+
from sys import _getframe
|
|
80
|
+
|
|
81
|
+
log_start("my_pipeline", logloc="pipeline")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
log_notes("startup ; loading data", logloc="pipeline")
|
|
85
|
+
# ... your work ...
|
|
86
|
+
log_notes("success ; data loaded", logloc="pipeline")
|
|
87
|
+
except Exception:
|
|
88
|
+
log_except(_getframe(), "data ; loading ; fail", logloc="pipeline")
|
|
89
|
+
log_exit("my_pipeline", imp=False, logloc="pipeline")
|
|
90
|
+
|
|
91
|
+
log_exit("my_pipeline", imp=True, logloc="pipeline")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**What the user sees on the console:**
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Arin : startup ; loading data
|
|
98
|
+
Arin : success ; data loaded
|
|
99
|
+
Arin my_pipeline ; 14:32:11 ; completed
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**What ends up in the log file:**
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
26-10-04.pipeline.log
|
|
106
|
+
|
|
107
|
+
my_pipeline ; 14:32:11 ; running
|
|
108
|
+
|
|
109
|
+
startup ; loading data
|
|
110
|
+
success ; data loaded
|
|
111
|
+
|
|
112
|
+
my_pipeline ; 14:32:11 ; completed
|
|
113
|
+
------------------------------------------------------------
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## What makes `arin_log` different?
|
|
119
|
+
|
|
120
|
+
| Aspect | `arin_log` | Loguru / stdlib / structlog |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| **Context propagation** | One parameter `logloc` travels through the whole call chain; every submodule becomes part of the same log file | `LoggerAdapter`, `contextvars`, or manual config |
|
|
123
|
+
| **Console vs. log file** | Deliberately separated: console = everything (with Arin as a voice), log file = only the beacons | Everything goes to the same handlers |
|
|
124
|
+
| **Archive** | One log file per main module per day, with automatic section separation per subtask | Scattered files or one big file |
|
|
125
|
+
| **Arin as virtual assistant** | A character who guides the user, not a log-level prefix | No equivalent |
|
|
126
|
+
| **Exception notation** | `module.function.linenumber ; time ; exception` — compact and directly usable | Full traceback or `exc_info=True` |
|
|
127
|
+
|
|
128
|
+
**Core USP:** `logloc` + daily grouping per main module + separation of
|
|
129
|
+
console and archive + Arin.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## How it works
|
|
134
|
+
|
|
135
|
+
The package consists of four modules, each with a clear task:
|
|
136
|
+
|
|
137
|
+
- **`logger.py`** — the heart: `log_start`, `log_notes`, `log_except`,
|
|
138
|
+
`log_exit`, and the helpers.
|
|
139
|
+
- **`folder.py`** — manages the config folder and the log folder.
|
|
140
|
+
- **`console.py`** — console interaction: clear screen, menus.
|
|
141
|
+
- **`utils.py`** — JSON helpers (`load_json`, `save_json`).
|
|
142
|
+
|
|
143
|
+
### Configuration
|
|
144
|
+
|
|
145
|
+
- **`log_cnfg.json`** ships with the package as a template: the default
|
|
146
|
+
color words and color codes for the console.
|
|
147
|
+
- **`config_log.json`** lives in `%APPDATA%\arin_log\` (Windows) or
|
|
148
|
+
`~/.arin_log/` (Linux/macOS). On first run it is created by copying
|
|
149
|
+
the colors from `log_cnfg.json` and adding your chosen log folder.
|
|
150
|
+
|
|
151
|
+
To customize colors, edit `config_log.json` in the config folder
|
|
152
|
+
above. Your changes survive `pip install --upgrade arin-log`, because
|
|
153
|
+
the file is never touched by pip.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## The log folder
|
|
158
|
+
|
|
159
|
+
`arin_log` stores its files in a folder of your choice. On first use,
|
|
160
|
+
a folder dialog appears. The choice is saved in `config_log.json`
|
|
161
|
+
(see Configuration), so it survives across sessions and is never
|
|
162
|
+
overwritten by a `pip install --upgrade`.
|
|
163
|
+
|
|
164
|
+
If `config_log.json` is missing or corrupt, the dialog appears again —
|
|
165
|
+
the package has no fallback folder. The user is expected to choose.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Requirements
|
|
170
|
+
|
|
171
|
+
- Python 3.9 or higher
|
|
172
|
+
|
|
173
|
+
No external dependencies.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT — see [LICENSE](LICENSE).
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Status
|
|
184
|
+
|
|
185
|
+
**Alpha (0.2.0).** The core works. Tests are still in development.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
arin_log/__init__.py,sha256=hP5IEKp9lGNVjzh02xRoFbr4aIv_RP0to764h8FMWDg,1437
|
|
2
|
+
arin_log/console.py,sha256=UlInyJkt0468K0X8r-GfPHu5CZM0TlVtDB1YrZfiKQ4,2868
|
|
3
|
+
arin_log/demo_log.py,sha256=WVwDj_G-c32OEZstqurxtbx0_f_zBU0ECvEfaHKBf3c,873
|
|
4
|
+
arin_log/folder.py,sha256=bttm6LWH7j04dYTA_2JBW6bS_6BIezLWRasW90mvMCw,9709
|
|
5
|
+
arin_log/log_cnfg.json,sha256=-N7A_663Xbc4NQGofNXc99DCpBwUz5wYVWoIzI3SgpQ,1526
|
|
6
|
+
arin_log/logger.py,sha256=3Thbt_iZ54UBsI59lucqOJ2pT3hcfMz6HMEyRsQ40-w,8008
|
|
7
|
+
arin_log/utils.py,sha256=04oJk2328-wxnsDwXWPojcfzu946EpSbRBeoeI2QUx0,2125
|
|
8
|
+
arin_log-0.2.0.dist-info/licenses/LICENSE,sha256=HXmJEUrdgvHJ6S_VAXL9ZWRgKS0KyqzEu0Q_TWxx0nQ,1071
|
|
9
|
+
arin_log-0.2.0.dist-info/METADATA,sha256=RqH2QHQwlzeAUSdz6BBXcWYAHmJYuRnn1zuUU2MWyZU,5936
|
|
10
|
+
arin_log-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
11
|
+
arin_log-0.2.0.dist-info/top_level.txt,sha256=Z70CumXkXyWyXM6YZk86a1GVb8q6hdsLC7lNpbEJQpY,9
|
|
12
|
+
arin_log-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marcus Aurelius
|
|
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
|
+
arin_log
|