Merge pull request #182 from rai68/log-changes

Log changes
This commit is contained in:
Jayofelony
2024-08-07 19:45:35 +02:00
committed by GitHub
15 changed files with 209 additions and 51 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.pyc

View File

@ -4,7 +4,8 @@ import os
import re import re
import logging import logging
import asyncio import asyncio
import _thread #import _thread
import threading
import pwnagotchi import pwnagotchi
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
@ -304,7 +305,8 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
raise raise
def start_session_fetcher(self): def start_session_fetcher(self):
_thread.start_new_thread(self._fetch_stats, ()) #_thread.start_new_thread(self._fetch_stats, ())
threading.Thread(target=self._fetch_stats, args=(), name="Session Fetcher", daemon=True).start()
def _fetch_stats(self): def _fetch_stats(self):
while True: while True:
@ -387,7 +389,8 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def start_event_polling(self): def start_event_polling(self):
# start a thread and pass in the mainloop # start a thread and pass in the mainloop
_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),)) #_thread.start_new_thread(self._event_poller, (asyncio.get_event_loop(),))
threading.Thread(target=self._event_poller, args=(asyncio.get_event_loop(),), name="Event Polling", daemon=True)
def is_module_running(self, module): def is_module_running(self, module):
s = self.session() s = self.session()

View File

@ -111,7 +111,8 @@ class AsyncTrainer(object):
return self._training_epochs return self._training_epochs
def start_ai(self): def start_ai(self):
_thread.start_new_thread(self._ai_worker, ()) #_thread.start_new_thread(self._ai_worker, ())
threading.Thread(target=self._ai_worker, args=(), name="AI Worker" daemon=True).start()
def _save_ai(self): def _save_ai(self):
logging.info("[AI] saving model to %s ..." % self._nn_path) logging.info("[AI] saving model to %s ..." % self._nn_path)

View File

@ -112,6 +112,7 @@ main.mon_max_blind_epochs = 50
main.no_restart = false main.no_restart = false
main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log" main.log.path = "/etc/pwnagotchi/log/pwnagotchi.log"
main.log.path-debug = "/etc/pwnagotchi/log/pwnagotchi-debug.log"
main.log.rotation.enabled = true main.log.rotation.enabled = true
main.log.rotation.size = "10M" main.log.rotation.size = "10M"

View File

@ -3,7 +3,8 @@ import re
import tempfile import tempfile
import contextlib import contextlib
import shutil import shutil
import _thread #import _thread
import threading
import logging import logging
from time import sleep from time import sleep
@ -85,7 +86,8 @@ def setup_mounts(config):
if interval: if interval:
logging.debug("[FS] Starting thread to sync %s (interval: %d)", logging.debug("[FS] Starting thread to sync %s (interval: %d)",
options['mount'], interval) options['mount'], interval)
_thread.start_new_thread(m.daemonize, (interval,)) threading.Thread(target=m.daemonize, args=(interval,),name="File Sys", daemon=True).start()
#_thread.start_new_thread(m.daemonize, (interval,))
else: else:
logging.debug("[FS] Not syncing %s, because interval is 0", logging.debug("[FS] Not syncing %s, because interval is 0",
options['mount']) options['mount'])

View File

@ -217,24 +217,44 @@ class LastSession(object):
def setup_logging(args, config): def setup_logging(args, config):
cfg = config['main']['log'] cfg = config['main']['log']
filename = cfg['path'] filename = cfg['path']
filenameDebug = cfg['path-debug']
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s") #global formatter
root = logging.getLogger() formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] [%(threadName)s] : %(message)s")
logger = logging.getLogger()
root.setLevel(logging.DEBUG if args.debug else logging.INFO) for handler in logger.handlers:
handler.setLevel(logging.DEBUG if args.debug else logging.INFO)
handler.setFormatter(formatter)
logger.setLevel(logging.DEBUG if args.debug else logging.INFO)
if filename: if filename:
# since python default log rotation might break session data in different files, # since python default log rotation might break session data in different files,
# we need to do log rotation ourselves # we need to do log rotation ourselves
log_rotation(filename, cfg) log_rotation(filename, cfg)
log_rotation(filenameDebug, cfg)
file_handler = logging.FileHandler(filename)
# File handler for logging all normal messages
file_handler = logging.FileHandler(filename) #creates new
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
root.addHandler(file_handler) logger.addHandler(file_handler)
console_handler = logging.StreamHandler() # File handler for logging all debug messages
console_handler.setFormatter(formatter) file_handler = logging.FileHandler(filenameDebug) #creates new
root.addHandler(console_handler) file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# Console handler for logging debug messages if args.debug is true else just log normal
#console_handler = logging.StreamHandler() #creates new
#console_handler.setLevel(logging.DEBUG if args.debug else logging.INFO)
#console_handler.setFormatter(formatter)
#logger.addHandler(console_handler)
if not args.debug: if not args.debug:
# disable scapy and tensorflow logging # disable scapy and tensorflow logging
@ -250,6 +270,8 @@ def setup_logging(args, config):
requests_log.prpagate = False requests_log.prpagate = False
def log_rotation(filename, cfg): def log_rotation(filename, cfg):
rotation = cfg['rotation'] rotation = cfg['rotation']
if not rotation['enabled']: if not rotation['enabled']:

View File

@ -1,4 +1,5 @@
import _thread #import _thread
import threading
import logging import logging
import time import time
@ -41,7 +42,8 @@ class AsyncAdvertiser(object):
def start_advertising(self): def start_advertising(self):
if self._config['personality']['advertise']: if self._config['personality']['advertise']:
_thread.start_new_thread(self._adv_poller, ()) #_thread.start_new_thread(self._adv_poller, ())
threading.Thread(target=self._adv_poller,args=(), name="Grid", daemon=True).start()
grid.set_advertisement_data(self._advertisement) grid.set_advertisement_data(self._advertisement)
grid.advertise(True) grid.advertise(True)

View File

@ -6,6 +6,7 @@ import logging
import os import os
import threading import threading
import pwnagotchi.grid import pwnagotchi.grid
import prctl
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default") default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
loaded = {} loaded = {}
@ -72,6 +73,7 @@ def toggle_plugin(name, enable=True):
def on(event_name, *args, **kwargs): def on(event_name, *args, **kwargs):
for plugin_name in loaded.keys(): for plugin_name in loaded.keys():
one(plugin_name, event_name, *args, **kwargs) one(plugin_name, event_name, *args, **kwargs)
@ -82,7 +84,10 @@ def locked_cb(lock_name, cb, *args, **kwargs):
locks[lock_name] = threading.Lock() locks[lock_name] = threading.Lock()
with locks[lock_name]: with locks[lock_name]:
cb(*args, *kwargs) # Setting the thread name using prctl
plugin_name, plugin_cb = lock_name.split("::")
prctl.set_name(f"{plugin_name}.{plugin_cb}")
cb(*args, **kwargs)
def one(plugin_name, event_name, *args, **kwargs): def one(plugin_name, event_name, *args, **kwargs):
@ -95,8 +100,10 @@ def one(plugin_name, event_name, *args, **kwargs):
if callback is not None and callable(callback): if callback is not None and callable(callback):
try: try:
lock_name = "%s::%s" % (plugin_name, cb_name) lock_name = "%s::%s" % (plugin_name, cb_name)
locked_cb_args = (lock_name, callback, *args, *kwargs) thread_name = f'{plugin_name}.{cb_name}'
_thread.start_new_thread(locked_cb, locked_cb_args) thread = threading.Thread(target=locked_cb, args=(lock_name, callback, *args, *kwargs), name=thread_name, daemon=True)
thread.start()
except Exception as e: except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True) logging.error(e, exc_info=True)

33
pwnagotchi/ui/colors.py Normal file
View File

@ -0,0 +1,33 @@
LOOK_R = '( ⚆_⚆)'
LOOK_L = '(☉_☉ )'
LOOK_R_HAPPY = '( ◕‿◕)'
LOOK_L_HAPPY = '(◕‿◕ )'
SLEEP = '(⇀‿‿↼)'
SLEEP2 = '(≖‿‿≖)'
AWAKE = '(◕‿‿◕)'
BORED = '(-__-)'
INTENSE = '(°▃▃°)'
COOL = '(⌐■_■)'
HAPPY = '(•‿‿•)'
GRATEFUL = '(^‿‿^)'
EXCITED = '(ᵔ◡◡ᵔ)'
MOTIVATED = '(☼‿‿☼)'
DEMOTIVATED = '(≖__≖)'
SMART = '(✜‿‿✜)'
LONELY = '(ب__ب)'
SAD = '(╥☁╥ )'
ANGRY = "(-_-')"
FRIEND = '(♥‿‿♥)'
BROKEN = '(☓‿‿☓)'
DEBUG = '(#__#)'
UPLOAD = '(1__0)'
UPLOAD1 = '(1__1)'
UPLOAD2 = '(0__1)'
PNG = False
POSITION_X = 0
POSITION_Y = 40
def load_from_config(config):
for face_name, face_value in config.items():
globals()[face_name.upper()] = face_value

View File

@ -21,7 +21,8 @@ class Display(View):
self._canvas_next = None self._canvas_next = None
self._render_thread_instance = threading.Thread( self._render_thread_instance = threading.Thread(
target=self._render_thread, target=self._render_thread,
daemon=True daemon=True,
name="Renderer"
) )
self._render_thread_instance.start() self._render_thread_instance.start()

View File

@ -7,6 +7,7 @@ from pwnagotchi.ui.hw.base import DisplayImpl
class DisplayHatMini(DisplayImpl): class DisplayHatMini(DisplayImpl):
def __init__(self, config): def __init__(self, config):
super(DisplayHatMini, self).__init__(config, 'displayhatmini') super(DisplayHatMini, self).__init__(config, 'displayhatmini')
self.mode = "RGB" # its actually BGR;16 5,6,5 bit, but display lib converts it
def layout(self): def layout(self):
fonts.setup(12, 10, 12, 70, 25, 9) fonts.setup(12, 10, 12, 70, 25, 9)

View File

@ -7,6 +7,7 @@ from pwnagotchi.ui.hw.base import DisplayImpl
class Wavesharelcd1in8(DisplayImpl): class Wavesharelcd1in8(DisplayImpl):
def __init__(self, config): def __init__(self, config):
super(Wavesharelcd1in8, self).__init__(config, 'wavesharelcd1in8') super(Wavesharelcd1in8, self).__init__(config, 'wavesharelcd1in8')
self.mode = "RGB"
def layout(self): def layout(self):
fonts.setup(10, 8, 10, 18, 25, 9) fonts.setup(10, 8, 10, 18, 25, 9)

View File

@ -3,7 +3,7 @@ from threading import Lock
class State(object): class State(object):
def __init__(self, state={}): def __init__(self, state={}):
self._state = state self._state = state # all ui elements
self._lock = Lock() self._lock = Lock()
self._listeners = {} self._listeners = {}
self._changes = {} self._changes = {}

View File

@ -5,6 +5,7 @@ import time
from threading import Lock from threading import Lock
from PIL import ImageDraw from PIL import ImageDraw
from PIL import ImageColor as colors
import pwnagotchi import pwnagotchi
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
@ -12,29 +13,106 @@ import pwnagotchi.ui.faces as faces
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
import pwnagotchi.ui.web as web import pwnagotchi.ui.web as web
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
from pwnagotchi.ui.components import * from pwnagotchi.ui.components import *
from pwnagotchi.ui.state import State from pwnagotchi.ui.state import State
from pwnagotchi.voice import Voice from pwnagotchi.voice import Voice
WHITE = 0x00 WHITE = 0x00 # white is actually black on jays image
BLACK = 0xFF BLACK = 0xFF # black is actually white on jays image
BACKGROUND_1 = 0
FOREGROUND_1 = 1
BACKGROUND_L = 0
FOREGROUND_L = 255
BACKGROUND_BGR_16 = (0,0,0)
FOREGROUND_BGR_16 = (31,63,31)
BACKGROUND_RGB = (0,0,0)
FOREGROUND_RGB = (255,255,255)
ROOT = None ROOT = None
#1 (1-bit pixels, black and white, stored with one pixel per byte)
#L (8-bit pixels, grayscale)
#P (8-bit pixels, mapped to any other mode using a color palette)
#BGR;16 (5,6,5 bits, for 65k color)
#RGB (3x8-bit pixels, true color)
#RGBA (4x8-bit pixels, true color with transparency mask)
#CMYK (4x8-bit pixels, color separation)
#YCbCr (3x8-bit pixels, color video format)
#self.FOREGROUND is the main color
#self.BACKGROUNDGROUND is the 2ndary color, used for background
class View(object): class View(object):
def __init__(self, config, impl, state=None): def __init__(self, config, impl, state=None):
global ROOT, BLACK, WHITE global ROOT, BLACK, WHITE
#values/code for display color mode
self.mode = '1' # 1 = (1-bit pixels, black and white, stored with one pixel per byte)
if hasattr(impl, 'mode'):
self.mode = impl.mode
match self.mode:
case '1':
self.BACKGROUND = BACKGROUND_1
self.FOREGROUND = FOREGROUND_1
# do stuff is color mode is 1 when View object is created.
case 'L':
self.BACKGROUND = BACKGROUND_L # black 0 to 255
self.FOREGROUND = FOREGROUND_L
# do stuff is color mode is L when View object is created.
case 'P':
pass
# do stuff is color mode is P when View object is created.
case 'BGR;16':
self.BACKGROUND = BACKGROUND_BGR_16 #black tuple
self.FOREGROUND = FOREGROUND_BGR_16 #white tuple
case 'RGB':
self.BACKGROUND = BACKGROUND_RGB #black tuple
self.FOREGROUND = FOREGROUND_RGB #white tuple
# do stuff is color mode is RGB when View object is created.
case 'RGBA':
# do stuff is color mode is RGBA when View object is created.
pass
case 'CMYK':
# do stuff is color mode is CMYK when View object is created.
pass
case 'YCbCr':
# do stuff is color mode is YCbCr when View object is created.
pass
case _:
# do stuff when color mode doesnt exist for display
self.BACKGROUND = BACKGROUND_1
self.FOREGROUND = FOREGROUND_1
self.invert = 0 self.invert = 0
self._black = 0xFF if 'invert' in config['ui'] and config['ui']['invert'] == True:
self._white = 0x00 logging.debug("INVERT:" + str(config['ui']['invert']))
if 'invert' in config['ui'] and config['ui']['invert']:
logging.debug("INVERT BLACK/WHITES:" + str(config['ui']['invert']))
self.invert = 1 self.invert = 1
BLACK = 0x00 tmp = self.FOREGROUND
WHITE = 0xFF self.FOREGROUND = self.FOREGROUND
self._black = 0x00 self.FOREGROUND = tmp
self._white = 0xFF
# setup faces from the configuration in case the user customized them # setup faces from the configuration in case the user customized them
faces.load_from_config(config['ui']['faces']) faces.load_from_config(config['ui']['faces'])
@ -51,40 +129,40 @@ class View(object):
self._width = self._layout['width'] self._width = self._layout['width']
self._height = self._layout['height'] self._height = self._layout['height']
self._state = State(state={ self._state = State(state={
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'], 'channel': LabeledValue(color=self.FOREGROUND, label='CH', value='00', position=self._layout['channel'],
label_font=fonts.Bold, label_font=fonts.Bold,
text_font=fonts.Medium), text_font=fonts.Medium),
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'], 'aps': LabeledValue(color=self.FOREGROUND, label='APS', value='0 (00)', position=self._layout['aps'],
label_font=fonts.Bold, label_font=fonts.Bold,
text_font=fonts.Medium), text_font=fonts.Medium),
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'], 'uptime': LabeledValue(color=self.FOREGROUND, label='UP', value='00:00:00', position=self._layout['uptime'],
label_font=fonts.Bold, label_font=fonts.Bold,
text_font=fonts.Medium), text_font=fonts.Medium),
'line1': Line(self._layout['line1'], color=BLACK), 'line1': Line(self._layout['line1'], color=self.FOREGROUND),
'line2': Line(self._layout['line2'], color=BLACK), 'line2': Line(self._layout['line2'], color=self.FOREGROUND),
'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=BLACK, font=fonts.Huge, png=config['ui']['faces']['png']), 'face': Text(value=faces.SLEEP, position=(config['ui']['faces']['position_x'], config['ui']['faces']['position_y']), color=self.FOREGROUND, font=fonts.Huge, png=config['ui']['faces']['png']),
# 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK), # 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=self.FOREGROUND),
'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=BLACK), 'friend_name': Text(value=None, position=self._layout['friend_face'], font=fonts.BoldSmall, color=self.FOREGROUND),
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold), 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=self.FOREGROUND, font=fonts.Bold),
'status': Text(value=self._voice.default(), 'status': Text(value=self._voice.default(),
position=self._layout['status']['pos'], position=self._layout['status']['pos'],
color=BLACK, color=self.FOREGROUND,
font=self._layout['status']['font'], font=self._layout['status']['font'],
wrap=True, wrap=True,
# the current maximum number of characters per line, assuming each character is 6 pixels wide # the current maximum number of characters per line, assuming each character is 6 pixels wide
max_length=self._layout['status']['max']), max_length=self._layout['status']['max']),
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=self.FOREGROUND,
position=self._layout['shakes'], label_font=fonts.Bold, position=self._layout['shakes'], label_font=fonts.Bold,
text_font=fonts.Medium), text_font=fonts.Medium),
'mode': Text(value='AUTO', position=self._layout['mode'], 'mode': Text(value='AUTO', position=self._layout['mode'],
font=fonts.Bold, color=BLACK), font=fonts.Bold, color=self.FOREGROUND),
}) })
if state: if state:
@ -387,12 +465,13 @@ class View(object):
state = self._state state = self._state
changes = state.changes(ignore=self._ignore_changes) changes = state.changes(ignore=self._ignore_changes)
if force or len(changes): if force or len(changes):
self._canvas = Image.new('1', (self._width, self._height), self._white) self._canvas = Image.new(self.mode, (self._width, self._height), self.BACKGROUND)
drawer = ImageDraw.Draw(self._canvas) drawer = ImageDraw.Draw(self._canvas, self.mode)
plugins.on('ui_update', self) plugins.on('ui_update', self)
for key, lv in state.items(): for key, lv in state.items():
#lv is a ui element
lv.draw(self._canvas, drawer) lv.draw(self._canvas, drawer)
web.update_frame(self._canvas) web.update_frame(self._canvas)

View File

@ -1,4 +1,5 @@
import _thread #import _thread
import threading
import secrets import secrets
import logging import logging
import os import os
@ -25,7 +26,9 @@ class Server:
self._origin = self._config['origin'] self._origin = self._config['origin']
if self._enabled: if self._enabled:
_thread.start_new_thread(self._http_serve, ()) #_thread.start_new_thread(self._http_serve, ())
logging.info("Starting WebServer thread")
self._thread = threading.Thread(target=self._http_serve, name="WebServer", daemon = True).start()
def _http_serve(self): def _http_serve(self):
if self._address is not None: if self._address is not None: