logmachine 2.1.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {logmachine-2.1.0 → logmachine-2.2.0}/LICENSE +0 -0
- {logmachine-2.1.0/logmachine.egg-info → logmachine-2.2.0}/PKG-INFO +26 -11
- logmachine-2.1.0/PKG-INFO → logmachine-2.2.0/README.md +22 -25
- logmachine-2.2.0/logmachine/__init__.py +1 -0
- {logmachine-2.1.0 → logmachine-2.2.0}/logmachine/main.py +100 -70
- logmachine-2.1.0/README.md → logmachine-2.2.0/logmachine.egg-info/PKG-INFO +40 -7
- {logmachine-2.1.0 → logmachine-2.2.0}/logmachine.egg-info/SOURCES.txt +0 -0
- {logmachine-2.1.0 → logmachine-2.2.0}/logmachine.egg-info/dependency_links.txt +0 -0
- {logmachine-2.1.0 → logmachine-2.2.0}/logmachine.egg-info/top_level.txt +0 -0
- {logmachine-2.1.0 → logmachine-2.2.0}/pyproject.toml +4 -4
- {logmachine-2.1.0 → logmachine-2.2.0}/tests/test_core.py +0 -0
- logmachine-2.1.0/logmachine/__init__.py +0 -1
- {logmachine-2.1.0 → logmachine-2.2.0}/setup.cfg +0 -0
|
File without changes
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: logmachine
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Collaborative, beautiful logging system for distributed developers
|
|
5
5
|
Author-email: Mugabo Gusenga <mugabo@bufferpunk.com>
|
|
6
6
|
Project-URL: Homepage, https://logmachine.bufferpunk.com
|
|
7
|
-
Project-URL: Documentation, https://github.com/
|
|
8
|
-
Project-URL: Source, https://github.com/
|
|
9
|
-
Project-URL: Tracker, https://github.com/
|
|
7
|
+
Project-URL: Documentation, https://github.com/logmachine/python
|
|
8
|
+
Project-URL: Source, https://github.com/logmachine/python
|
|
9
|
+
Project-URL: Tracker, https://github.com/logmachine/python/issues
|
|
10
10
|
Keywords: logging,devtools,collaborative,open source,cli,json,ansi
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -16,7 +16,7 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Dynamic: license-file
|
|
18
18
|
|
|
19
|
-
# 🧠
|
|
19
|
+
# 🧠 LogMachine
|
|
20
20
|
|
|
21
21
|
> Collaborative, beautiful logging system for distributed developers
|
|
22
22
|
|
|
@@ -50,11 +50,11 @@ pip install logmachine
|
|
|
50
50
|
### Basic Setup
|
|
51
51
|
|
|
52
52
|
```python
|
|
53
|
-
from logmachine import
|
|
53
|
+
from logmachine import LogMachine
|
|
54
54
|
|
|
55
55
|
# Create a simple logger without central logging
|
|
56
|
-
# Providing a
|
|
57
|
-
logger =
|
|
56
|
+
# Providing a non-empty string initializes the logger with that name, else the root logger is used to collect every single log in the python process.
|
|
57
|
+
logger = LogMachine("myapp", debug_level=1)
|
|
58
58
|
|
|
59
59
|
logger.info("Hello, world!")
|
|
60
60
|
logger.error("An error occurred!")
|
|
@@ -65,6 +65,17 @@ logger.warning("This is a warning message.")
|
|
|
65
65
|
|
|
66
66
|
### With Central Logging (HTTP or Socket.IO)
|
|
67
67
|
|
|
68
|
+
You can use the default logger with central logging pointing to "https://logmachine.bufferpunk.com"
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from logmachine import default_logger
|
|
72
|
+
logger = default_logger()
|
|
73
|
+
logger.info("This log is sent to the LogMachine default central server!")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This is the default central logging server for logmachine, and you can create your own room there for free.
|
|
77
|
+
To use your own central logging server, provide the configuration as shown below:
|
|
78
|
+
|
|
68
79
|
```python
|
|
69
80
|
logger_config = {
|
|
70
81
|
"url": "https://logmachine.bufferpunk.com", # Base server URL
|
|
@@ -74,7 +85,7 @@ logger_config = {
|
|
|
74
85
|
"socketio": True, # Set False to use HTTP
|
|
75
86
|
"socketio_path": "/api/socket.io/" # Optional
|
|
76
87
|
}
|
|
77
|
-
logger =
|
|
88
|
+
logger = LogMachine("with_central", debug_level=0, central=logger_config, socketio=True)
|
|
78
89
|
logger.success("Central logging is working!")
|
|
79
90
|
```
|
|
80
91
|
|
|
@@ -105,8 +116,9 @@ Sample (terminal):
|
|
|
105
116
|
### Add Your Own Log Level
|
|
106
117
|
|
|
107
118
|
```python
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
logger.new_level("CRITICAL_HACK", 60)
|
|
120
|
+
logger.new_level("CRITICAL_HACK", 60, color="\033[38;5;13m") # Optional color... does your girlfriend love pink? Maybe you should be in a relationship with your terminal.
|
|
121
|
+
logger.critical_hack("Zero day found!")
|
|
110
122
|
```
|
|
111
123
|
|
|
112
124
|
---
|
|
@@ -115,6 +127,9 @@ log.critical_hack("Zero day found!")
|
|
|
115
127
|
|
|
116
128
|
### Convert Logs to JSON
|
|
117
129
|
|
|
130
|
+
This is useful for sending logs to web dashboards or log collectors that expect JSON.
|
|
131
|
+
It reads the your log files, parses the log entries, and outputs them as JSON objects.
|
|
132
|
+
|
|
118
133
|
```python
|
|
119
134
|
json_logs = log.jsonifier()
|
|
120
135
|
for entry in json_logs:
|
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Name: logmachine
|
|
3
|
-
Version: 2.1.0
|
|
4
|
-
Summary: Collaborative, beautiful logging system for distributed developers
|
|
5
|
-
Author-email: Mugabo Gusenga <mugabo@bufferpunk.com>
|
|
6
|
-
Project-URL: Homepage, https://logmachine.bufferpunk.com
|
|
7
|
-
Project-URL: Documentation, https://github.com/Scion-Kin/logmachine
|
|
8
|
-
Project-URL: Source, https://github.com/Scion-Kin/logmachine
|
|
9
|
-
Project-URL: Tracker, https://github.com/Scion-Kin/logmachine/issues
|
|
10
|
-
Keywords: logging,devtools,collaborative,open source,cli,json,ansi
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Operating System :: OS Independent
|
|
14
|
-
Requires-Python: >=3.7
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE
|
|
17
|
-
Dynamic: license-file
|
|
18
|
-
|
|
19
|
-
# 🧠 logmachine 2.1.0
|
|
1
|
+
# 🧠 LogMachine
|
|
20
2
|
|
|
21
3
|
> Collaborative, beautiful logging system for distributed developers
|
|
22
4
|
|
|
@@ -50,11 +32,11 @@ pip install logmachine
|
|
|
50
32
|
### Basic Setup
|
|
51
33
|
|
|
52
34
|
```python
|
|
53
|
-
from logmachine import
|
|
35
|
+
from logmachine import LogMachine
|
|
54
36
|
|
|
55
37
|
# Create a simple logger without central logging
|
|
56
|
-
# Providing a
|
|
57
|
-
logger =
|
|
38
|
+
# Providing a non-empty string initializes the logger with that name, else the root logger is used to collect every single log in the python process.
|
|
39
|
+
logger = LogMachine("myapp", debug_level=1)
|
|
58
40
|
|
|
59
41
|
logger.info("Hello, world!")
|
|
60
42
|
logger.error("An error occurred!")
|
|
@@ -65,6 +47,17 @@ logger.warning("This is a warning message.")
|
|
|
65
47
|
|
|
66
48
|
### With Central Logging (HTTP or Socket.IO)
|
|
67
49
|
|
|
50
|
+
You can use the default logger with central logging pointing to "https://logmachine.bufferpunk.com"
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from logmachine import default_logger
|
|
54
|
+
logger = default_logger()
|
|
55
|
+
logger.info("This log is sent to the LogMachine default central server!")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This is the default central logging server for logmachine, and you can create your own room there for free.
|
|
59
|
+
To use your own central logging server, provide the configuration as shown below:
|
|
60
|
+
|
|
68
61
|
```python
|
|
69
62
|
logger_config = {
|
|
70
63
|
"url": "https://logmachine.bufferpunk.com", # Base server URL
|
|
@@ -74,7 +67,7 @@ logger_config = {
|
|
|
74
67
|
"socketio": True, # Set False to use HTTP
|
|
75
68
|
"socketio_path": "/api/socket.io/" # Optional
|
|
76
69
|
}
|
|
77
|
-
logger =
|
|
70
|
+
logger = LogMachine("with_central", debug_level=0, central=logger_config, socketio=True)
|
|
78
71
|
logger.success("Central logging is working!")
|
|
79
72
|
```
|
|
80
73
|
|
|
@@ -105,8 +98,9 @@ Sample (terminal):
|
|
|
105
98
|
### Add Your Own Log Level
|
|
106
99
|
|
|
107
100
|
```python
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
logger.new_level("CRITICAL_HACK", 60)
|
|
102
|
+
logger.new_level("CRITICAL_HACK", 60, color="\033[38;5;13m") # Optional color... does your girlfriend love pink? Maybe you should be in a relationship with your terminal.
|
|
103
|
+
logger.critical_hack("Zero day found!")
|
|
110
104
|
```
|
|
111
105
|
|
|
112
106
|
---
|
|
@@ -115,6 +109,9 @@ log.critical_hack("Zero day found!")
|
|
|
115
109
|
|
|
116
110
|
### Convert Logs to JSON
|
|
117
111
|
|
|
112
|
+
This is useful for sending logs to web dashboards or log collectors that expect JSON.
|
|
113
|
+
It reads the your log files, parses the log entries, and outputs them as JSON objects.
|
|
114
|
+
|
|
118
115
|
```python
|
|
119
116
|
json_logs = log.jsonifier()
|
|
120
117
|
for entry in json_logs:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import LogMachine, default_logger
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import atexit
|
|
1
2
|
import os
|
|
2
3
|
import re
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
5
6
|
import requests
|
|
6
7
|
import socketio
|
|
8
|
+
import queue
|
|
9
|
+
from logging.handlers import QueueHandler, QueueListener
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
def get_login():
|
|
9
13
|
"""
|
|
@@ -11,12 +15,17 @@ def get_login():
|
|
|
11
15
|
:return: The login name of the current user.
|
|
12
16
|
"""
|
|
13
17
|
try:
|
|
14
|
-
|
|
18
|
+
if not os.path.exists(os.path.expanduser("~/.cl_username")):
|
|
19
|
+
return os.getlogin()
|
|
20
|
+
else:
|
|
21
|
+
with open(os.path.expanduser("~/.cl_username"), 'r') as f:
|
|
22
|
+
os.environ['CL_USERNAME'] = f.read().strip()
|
|
23
|
+
return os.environ['CL_USERNAME']
|
|
15
24
|
except Exception:
|
|
16
25
|
return os.environ.get('USER', 'unknown')
|
|
17
26
|
|
|
18
27
|
|
|
19
|
-
class
|
|
28
|
+
class HTTPTransporter(logging.StreamHandler):
|
|
20
29
|
"""
|
|
21
30
|
A class to handle the transport of log messages using HTTP requests.
|
|
22
31
|
This class sends log messages to a central server via HTTP POST requests.
|
|
@@ -36,22 +45,19 @@ class RequestsTransporter(logging.StreamHandler):
|
|
|
36
45
|
try:
|
|
37
46
|
msg = self.format(record)
|
|
38
47
|
super().emit(record)
|
|
39
|
-
if self.central:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
if response.status_code != 200:
|
|
54
|
-
raise Exception(f"Failed to send log to central: {response.text}")
|
|
48
|
+
if not self.central or not self.central.get('room'):
|
|
49
|
+
raise ValueError("""Central configuration must include 'room' for log transport.
|
|
50
|
+
Example: {'url': 'http://central-server/api/logs', 'room': 'my_organization_name'}""")
|
|
51
|
+
|
|
52
|
+
log_data = self.parse_log(msg)
|
|
53
|
+
if log_data:
|
|
54
|
+
response = requests.post(
|
|
55
|
+
f"{self.central.get('url', '') + self.central.get('endpoint', '/api/logs')}?room={self.central.get('room', '')}",
|
|
56
|
+
json=log_data,
|
|
57
|
+
headers={"Content-Type": "application/json", **self.central.get('headers', {})}
|
|
58
|
+
)
|
|
59
|
+
if response.status_code != 200:
|
|
60
|
+
raise Exception(f"Failed to send log to central: {response.text}")
|
|
55
61
|
|
|
56
62
|
except Exception:
|
|
57
63
|
self.handleError(record)
|
|
@@ -90,57 +96,67 @@ class SocketIOTransporter(logging.StreamHandler):
|
|
|
90
96
|
|
|
91
97
|
|
|
92
98
|
class CustomFormatter(logging.Formatter):
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
def __init__(self, *args, **kwargs):
|
|
100
|
+
super().__init__(*args, **kwargs)
|
|
101
|
+
self.colors = {
|
|
102
|
+
'DEBUG': '\x1b[36m',
|
|
103
|
+
'INFO': '\x1b[34m',
|
|
104
|
+
'WARNING': '\x1b[33m',
|
|
105
|
+
'ERROR': '\x1b[31m',
|
|
106
|
+
'SUCCESS': '\x1b[32m'
|
|
107
|
+
}
|
|
108
|
+
self.reset = '\x1b[0m'
|
|
109
|
+
self.bold = '\x1b[1m'
|
|
110
|
+
self.level_formats = {
|
|
111
|
+
'DEBUG': self.bold + '[ DEBUG ]' + self.reset,
|
|
112
|
+
'INFO': self.bold + '[ INFO ]' + self.reset,
|
|
113
|
+
'WARNING': self.bold + '[ WARNING ]' + self.reset,
|
|
114
|
+
'ERROR': self.bold + '[ ERROR ]' + self.reset,
|
|
115
|
+
'SUCCESS': self.bold + '[ SUCCESS ]' + self.reset
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def set_color(self, levelname: str, color_code: str):
|
|
119
|
+
"""
|
|
120
|
+
Set a custom color for a specific log level.
|
|
121
|
+
:param levelname: The name of the log level (e.g., 'DEBUG', 'INFO').
|
|
122
|
+
:param color_code: The ANSI color code to use for the specified log level.
|
|
123
|
+
"""
|
|
124
|
+
self.colors[levelname] = color_code
|
|
125
|
+
self.level_formats[levelname] = f"{self.bold}[ {levelname} ]{self.reset}"
|
|
109
126
|
|
|
110
127
|
def format(self, record) -> str:
|
|
111
128
|
username = os.environ.get('CL_USERNAME') or get_login()
|
|
112
129
|
|
|
113
130
|
levelname = record.levelname
|
|
114
|
-
color = self.
|
|
115
|
-
level_fmt = self.
|
|
116
|
-
level_fmt = f"{color}{level_fmt}{self.
|
|
131
|
+
color = self.colors.get(levelname, '')
|
|
132
|
+
level_fmt = self.level_formats.get(levelname, f'{levelname}')
|
|
133
|
+
level_fmt = f"{color}{level_fmt}{self.reset}"
|
|
117
134
|
record.asctime = self.formatTime(record, self.datefmt)
|
|
118
135
|
module_file = record.pathname
|
|
119
136
|
parent_dir = os.path.basename(os.path.dirname(record.pathname)) if module_file != '<stdin>' else 'stdin'
|
|
120
137
|
|
|
121
|
-
return f"""{self.
|
|
138
|
+
return f"""{self.colors.get('DEBUG')}({username}{self.reset} @ {self.colors.get('WARNING') + parent_dir + self.reset}) 🤌 CL Timing: {color}[ {record.asctime} ]{self.reset}
|
|
122
139
|
{level_fmt} {record.getMessage()}
|
|
123
140
|
🏁"""
|
|
124
141
|
|
|
125
|
-
class
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
super().__init__(*args, level=logging.DEBUG)
|
|
130
|
-
logging.addLevelName(self.SUCCESS, "SUCCESS")
|
|
142
|
+
class LogMachine(logging.Logger):
|
|
143
|
+
def __init__(self, name="", *args, **kwargs) -> None:
|
|
144
|
+
super().__init__(name, *args, level=int(kwargs.get('debug_level', 0)))
|
|
145
|
+
logging.addLevelName(25, "SUCCESS")
|
|
131
146
|
self.log_file = kwargs.get('log_file', 'logs.log')
|
|
132
147
|
self.error_file = kwargs.get('error_file', 'errors.log')
|
|
133
148
|
self.debug_level = int(kwargs.get('debug_level', 0))
|
|
134
149
|
self.verbose = kwargs.get('verbose', False)
|
|
135
150
|
self.central = kwargs.get('central', None)
|
|
136
|
-
self.
|
|
151
|
+
self.queue = queue.Queue()
|
|
137
152
|
|
|
138
153
|
# Remove existing handlers
|
|
139
|
-
self.handlers
|
|
154
|
+
for h in self.handlers[:]:
|
|
155
|
+
self.removeHandler(h)
|
|
140
156
|
|
|
141
157
|
# File handlers
|
|
142
158
|
fh = logging.FileHandler(self.log_file)
|
|
143
|
-
fh.setLevel(
|
|
159
|
+
fh.setLevel(self.debug_level)
|
|
144
160
|
eh = logging.FileHandler(self.error_file)
|
|
145
161
|
eh.setLevel(logging.ERROR)
|
|
146
162
|
|
|
@@ -148,7 +164,7 @@ class ContribLog(logging.Logger):
|
|
|
148
164
|
if self.central:
|
|
149
165
|
"""
|
|
150
166
|
The central uses usernames to group logs.
|
|
151
|
-
OS usernames are used to identify the
|
|
167
|
+
OS usernames are used to identify the user, meaning names can clash.
|
|
152
168
|
Therefore, we avoid a user having to define a username, rather, ask the central server to provide it.
|
|
153
169
|
After getting the username, we store it in the user's home directory in a file named `.cl_username`.
|
|
154
170
|
This way, the user can change it at any time, and it will be used in all future logs without needing to request it again.
|
|
@@ -158,42 +174,37 @@ class ContribLog(logging.Logger):
|
|
|
158
174
|
login = get_login()
|
|
159
175
|
response = requests.get(f"{self.central.get('url', '')}/api/get_username?base={login}")
|
|
160
176
|
if response.status_code == 200:
|
|
161
|
-
os.environ['CL_USERNAME'] = response.json().get('username'
|
|
177
|
+
os.environ['CL_USERNAME'] = response.json().get('username') or 'unknown' # Unknown will probably never be reached, but it's a fallback.
|
|
162
178
|
if os.environ.get('CL_USERNAME') != 'unknown':
|
|
163
179
|
with open(os.path.expanduser("~/.cl_username"), 'w') as f:
|
|
164
180
|
f.write(os.environ['CL_USERNAME'])
|
|
165
181
|
else:
|
|
166
182
|
os.environ['CL_USERNAME'] = 'unknown'
|
|
167
|
-
except Exception
|
|
183
|
+
except Exception:
|
|
168
184
|
os.environ['CL_USERNAME'] = 'unknown'
|
|
169
185
|
else:
|
|
170
|
-
|
|
171
|
-
os.environ['CL_USERNAME'] = f.read().strip()
|
|
186
|
+
get_login()
|
|
172
187
|
|
|
173
188
|
if not kwargs.get('attached', False) and not self.central.get('socketio', False):
|
|
174
|
-
ch =
|
|
189
|
+
ch = HTTPTransporter(log_parser=self.parse_log, central=self.central)
|
|
175
190
|
else:
|
|
176
191
|
ch = SocketIOTransporter(log_parser=self.parse_log, central=self.central)
|
|
177
|
-
|
|
178
192
|
else:
|
|
179
193
|
ch = logging.StreamHandler()
|
|
180
194
|
|
|
181
195
|
ch.setLevel(logging.DEBUG)
|
|
182
196
|
|
|
183
|
-
formatter = CustomFormatter('%(asctime)s %(levelname)s %(message)s', datefmt='%Y-%m-%dT%H:%M:%S%z')
|
|
184
|
-
fh.setFormatter(formatter)
|
|
185
|
-
eh.setFormatter(formatter)
|
|
186
|
-
ch.setFormatter(formatter)
|
|
187
|
-
|
|
188
|
-
self.addHandler(fh)
|
|
189
|
-
self.addHandler(eh)
|
|
190
|
-
self.addHandler(ch)
|
|
197
|
+
self.formatter = CustomFormatter('%(asctime)s %(levelname)s %(message)s', datefmt='%Y-%m-%dT%H:%M:%S%z')
|
|
198
|
+
fh.setFormatter(self.formatter)
|
|
199
|
+
eh.setFormatter(self.formatter)
|
|
200
|
+
ch.setFormatter(self.formatter)
|
|
201
|
+
self.addHandler(QueueHandler(self.queue))
|
|
191
202
|
|
|
192
203
|
# Filter console output based on debug_level
|
|
193
204
|
class DebugLevelFilter(logging.Filter):
|
|
194
205
|
def __init__(self, debug_level):
|
|
195
206
|
super().__init__()
|
|
196
|
-
self.debug_level =
|
|
207
|
+
self.debug_level = debug_level
|
|
197
208
|
|
|
198
209
|
def filter(self, record):
|
|
199
210
|
if self.debug_level == 0:
|
|
@@ -212,12 +223,22 @@ class ContribLog(logging.Logger):
|
|
|
212
223
|
return record.levelname in allowed
|
|
213
224
|
|
|
214
225
|
ch.addFilter(DebugLevelFilter(self.debug_level if not self.verbose else 0))
|
|
226
|
+
self.listener = QueueListener(self.queue, fh, eh, ch)
|
|
227
|
+
self.listener.start()
|
|
228
|
+
atexit.register(self.listener.stop)
|
|
229
|
+
self.info("LogMachine initialized with debug level {} with{}".format(
|
|
230
|
+
self.debug_level,
|
|
231
|
+
self.central and
|
|
232
|
+
f" central logging to {self.central.get('url', '')}" or
|
|
233
|
+
"out central logging"
|
|
234
|
+
)
|
|
235
|
+
)
|
|
215
236
|
|
|
216
237
|
def success(self, msg, *args, **kwargs) -> None:
|
|
217
|
-
if self.isEnabledFor(
|
|
218
|
-
self._log(
|
|
238
|
+
if self.isEnabledFor(25):
|
|
239
|
+
self._log(25, msg, args, stacklevel=2, **kwargs)
|
|
219
240
|
|
|
220
|
-
def new_level(self, level_name: str, level_num: int):
|
|
241
|
+
def new_level(self, level_name: str, level_num: int, ansi_color="\x1b[37m") -> None:
|
|
221
242
|
"""
|
|
222
243
|
Dynamically add a new logging level.
|
|
223
244
|
:param level_name: Name of the new logging level.
|
|
@@ -228,8 +249,9 @@ class ContribLog(logging.Logger):
|
|
|
228
249
|
logging.addLevelName(level_num, level_name)
|
|
229
250
|
setattr(self, level_name.lower(), lambda msg, *args, **kwargs: self._log(level_num, msg, args, stacklevel=2, **kwargs))
|
|
230
251
|
self.setLevel(min(self.level, level_num)) # Ensure the logger's level is set appropriately
|
|
252
|
+
self.formatter.set_color(level_name, ansi_color) # Add color formatting for the new level
|
|
231
253
|
|
|
232
|
-
def parse_log(self, log_text) -> dict:
|
|
254
|
+
def parse_log(self, log_text) -> dict | None:
|
|
233
255
|
log_text = log_text.strip()
|
|
234
256
|
ansi_escape = re.compile(r'\x1b\[[0-9;]*m')
|
|
235
257
|
end_escape = re.compile(r'🏁')
|
|
@@ -240,7 +262,7 @@ class ContribLog(logging.Logger):
|
|
|
240
262
|
header_match = re.search(header_pattern, clean)
|
|
241
263
|
|
|
242
264
|
if not header_match:
|
|
243
|
-
return
|
|
265
|
+
return
|
|
244
266
|
|
|
245
267
|
user, module, timestamp = header_match.groups()
|
|
246
268
|
lines = clean.splitlines()
|
|
@@ -257,7 +279,7 @@ class ContribLog(logging.Logger):
|
|
|
257
279
|
"timestamp": timestamp,
|
|
258
280
|
"message": end_escape.sub('', message).strip()
|
|
259
281
|
}
|
|
260
|
-
|
|
282
|
+
|
|
261
283
|
def jsonifier(self) -> list:
|
|
262
284
|
"""
|
|
263
285
|
Reads the log file and returns a list of JSON objects representing each log entry.
|
|
@@ -278,4 +300,12 @@ class ContribLog(logging.Logger):
|
|
|
278
300
|
return log_entries
|
|
279
301
|
|
|
280
302
|
|
|
281
|
-
|
|
303
|
+
def default_logger():
|
|
304
|
+
return LogMachine('default_logger', debug_level=0, verbose=False, central={
|
|
305
|
+
'url': 'https://logmachine.bufferpunk.com',
|
|
306
|
+
'room': f'{get_login()}_logs',
|
|
307
|
+
'headers': {}
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
logging.setLoggerClass(LogMachine)
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: logmachine
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Collaborative, beautiful logging system for distributed developers
|
|
5
|
+
Author-email: Mugabo Gusenga <mugabo@bufferpunk.com>
|
|
6
|
+
Project-URL: Homepage, https://logmachine.bufferpunk.com
|
|
7
|
+
Project-URL: Documentation, https://github.com/logmachine/python
|
|
8
|
+
Project-URL: Source, https://github.com/logmachine/python
|
|
9
|
+
Project-URL: Tracker, https://github.com/logmachine/python/issues
|
|
10
|
+
Keywords: logging,devtools,collaborative,open source,cli,json,ansi
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.7
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# 🧠 LogMachine
|
|
2
20
|
|
|
3
21
|
> Collaborative, beautiful logging system for distributed developers
|
|
4
22
|
|
|
@@ -32,11 +50,11 @@ pip install logmachine
|
|
|
32
50
|
### Basic Setup
|
|
33
51
|
|
|
34
52
|
```python
|
|
35
|
-
from logmachine import
|
|
53
|
+
from logmachine import LogMachine
|
|
36
54
|
|
|
37
55
|
# Create a simple logger without central logging
|
|
38
|
-
# Providing a
|
|
39
|
-
logger =
|
|
56
|
+
# Providing a non-empty string initializes the logger with that name, else the root logger is used to collect every single log in the python process.
|
|
57
|
+
logger = LogMachine("myapp", debug_level=1)
|
|
40
58
|
|
|
41
59
|
logger.info("Hello, world!")
|
|
42
60
|
logger.error("An error occurred!")
|
|
@@ -47,6 +65,17 @@ logger.warning("This is a warning message.")
|
|
|
47
65
|
|
|
48
66
|
### With Central Logging (HTTP or Socket.IO)
|
|
49
67
|
|
|
68
|
+
You can use the default logger with central logging pointing to "https://logmachine.bufferpunk.com"
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from logmachine import default_logger
|
|
72
|
+
logger = default_logger()
|
|
73
|
+
logger.info("This log is sent to the LogMachine default central server!")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This is the default central logging server for logmachine, and you can create your own room there for free.
|
|
77
|
+
To use your own central logging server, provide the configuration as shown below:
|
|
78
|
+
|
|
50
79
|
```python
|
|
51
80
|
logger_config = {
|
|
52
81
|
"url": "https://logmachine.bufferpunk.com", # Base server URL
|
|
@@ -56,7 +85,7 @@ logger_config = {
|
|
|
56
85
|
"socketio": True, # Set False to use HTTP
|
|
57
86
|
"socketio_path": "/api/socket.io/" # Optional
|
|
58
87
|
}
|
|
59
|
-
logger =
|
|
88
|
+
logger = LogMachine("with_central", debug_level=0, central=logger_config, socketio=True)
|
|
60
89
|
logger.success("Central logging is working!")
|
|
61
90
|
```
|
|
62
91
|
|
|
@@ -87,8 +116,9 @@ Sample (terminal):
|
|
|
87
116
|
### Add Your Own Log Level
|
|
88
117
|
|
|
89
118
|
```python
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
logger.new_level("CRITICAL_HACK", 60)
|
|
120
|
+
logger.new_level("CRITICAL_HACK", 60, color="\033[38;5;13m") # Optional color... does your girlfriend love pink? Maybe you should be in a relationship with your terminal.
|
|
121
|
+
logger.critical_hack("Zero day found!")
|
|
92
122
|
```
|
|
93
123
|
|
|
94
124
|
---
|
|
@@ -97,6 +127,9 @@ log.critical_hack("Zero day found!")
|
|
|
97
127
|
|
|
98
128
|
### Convert Logs to JSON
|
|
99
129
|
|
|
130
|
+
This is useful for sending logs to web dashboards or log collectors that expect JSON.
|
|
131
|
+
It reads the your log files, parses the log entries, and outputs them as JSON objects.
|
|
132
|
+
|
|
100
133
|
```python
|
|
101
134
|
json_logs = log.jsonifier()
|
|
102
135
|
for entry in json_logs:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "logmachine"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.2.0"
|
|
4
4
|
description = "Collaborative, beautiful logging system for distributed developers"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.7"
|
|
@@ -17,9 +17,9 @@ classifiers = [
|
|
|
17
17
|
|
|
18
18
|
[project.urls]
|
|
19
19
|
Homepage = "https://logmachine.bufferpunk.com"
|
|
20
|
-
Documentation = "https://github.com/
|
|
21
|
-
Source = "https://github.com/
|
|
22
|
-
Tracker = "https://github.com/
|
|
20
|
+
Documentation = "https://github.com/logmachine/python"
|
|
21
|
+
Source = "https://github.com/logmachine/python"
|
|
22
|
+
Tracker = "https://github.com/logmachine/python/issues"
|
|
23
23
|
|
|
24
24
|
[build-system]
|
|
25
25
|
requires = ["setuptools>=61.0", "websocket-client", "python-socketio", "requests"]
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .main import ContribLog
|
|
File without changes
|