Added logging

This commit is contained in:
CasVT
2023-07-04 13:41:35 +02:00
parent 6105e9754a
commit c9a60f1b48
8 changed files with 116 additions and 28 deletions

16
MIND.py
View File

@@ -18,9 +18,16 @@ from frontend.ui import ui
HOST = '0.0.0.0'
PORT = '8080'
URL_PREFIX = '' # Must either be empty or start with '/' e.g. '/mind'
LOGGING_LEVEL = logging.INFO
THREADS = 10
DB_FILENAME = 'db', 'MIND.db'
logging.basicConfig(
level=LOGGING_LEVEL,
format='[%(asctime)s][%(threadName)s][%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
def _folder_path(*folders) -> str:
"""Turn filepaths relative to the project folder into absolute paths
Returns:
@@ -32,7 +39,7 @@ def _create_app() -> Flask:
"""Create a Flask app instance
Returns:
Flask: The created app instance
"""
"""
app = Flask(
__name__,
template_folder=_folder_path('frontend','templates'),
@@ -79,7 +86,7 @@ def MIND() -> None:
"""
# Check python version
if (version_info.major < 3) or (version_info.major == 3 and version_info.minor < 7):
print('Error: the minimum python version required is python3.7 (currently ' + version_info.major + '.' + version_info.minor + '.' + version_info.micro + ')')
logging.error('Error: the minimum python version required is python3.7 (currently ' + version_info.major + '.' + version_info.minor + '.' + version_info.micro + ')')
exit(1)
# Register web server
@@ -91,8 +98,11 @@ def MIND() -> None:
with app.app_context():
if isfile(_folder_path('db', 'Noted.db')):
move(_folder_path('db', 'Noted.db'), _folder_path(*DB_FILENAME))
db_location = _folder_path(*DB_FILENAME)
logging.debug(f'Database location: {db_location}')
makedirs(dirname(db_location), exist_ok=True)
DBConnection.file = db_location
setup_db()
reminder_handler.find_next_reminder()
@@ -101,7 +111,7 @@ def MIND() -> None:
dispatcher = ThreadedTaskDispatcher()
dispatcher.set_thread_count(THREADS)
server = create_server(app, _dispatcher=dispatcher, host=HOST, port=PORT, threads=THREADS)
print(f'MIND running on http://{HOST}:{PORT}{URL_PREFIX}')
logging.info(f'MIND running on http://{HOST}:{PORT}{URL_PREFIX}')
server.run()
# Stopping thread

View File

@@ -1,33 +1,39 @@
#-*- coding: utf-8 -*-
import logging
from typing import Any, Dict
class CustomException(Exception):
def __init__(self, e=None) -> None:
logging.warning(self.__doc__)
super().__init__(e)
return
class UsernameTaken(Exception):
class UsernameTaken(CustomException):
"""The username is already taken"""
api_response = {'error': 'UsernameTaken', 'result': {}, 'code': 400}
class UsernameInvalid(Exception):
class UsernameInvalid(CustomException):
"""The username contains invalid characters"""
api_response = {'error': 'UsernameInvalid', 'result': {}, 'code': 400}
class UserNotFound(Exception):
class UserNotFound(CustomException):
"""The user requested can not be found"""
api_response = {'error': 'UserNotFound', 'result': {}, 'code': 404}
class AccessUnauthorized(Exception):
class AccessUnauthorized(CustomException):
"""The password given is not correct"""
api_response = {'error': 'AccessUnauthorized', 'result': {}, 'code': 401}
class ReminderNotFound(Exception):
class ReminderNotFound(CustomException):
"""The reminder with the id can not be found"""
api_response = {'error': 'ReminderNotFound', 'result': {}, 'code': 404}
class NotificationServiceNotFound(Exception):
class NotificationServiceNotFound(CustomException):
"""The notification service was not found"""
api_response = {'error': 'NotificationServiceNotFound', 'result': {}, 'code': 404}
class NotificationServiceInUse(Exception):
class NotificationServiceInUse(CustomException):
"""The notification service is wished to be deleted but a reminder is still using it"""
def __init__(self, type: str=''):
self.type = type
@@ -37,11 +43,11 @@ class NotificationServiceInUse(Exception):
def api_response(self) -> Dict[str, Any]:
return {'error': 'NotificationServiceInUse', 'result': {'type': self.type}, 'code': 400}
class InvalidTime(Exception):
class InvalidTime(CustomException):
"""The time given is in the past"""
api_response = {'error': 'InvalidTime', 'result': {}, 'code': 400}
class KeyNotFound(Exception):
class KeyNotFound(CustomException):
"""A key was not found in the input that is required to be given"""
def __init__(self, key: str=''):
self.key = key
@@ -51,7 +57,7 @@ class KeyNotFound(Exception):
def api_response(self) -> Dict[str, Any]:
return {'error': 'KeyNotFound', 'result': {'key': self.key}, 'code': 400}
class InvalidKeyValue(Exception):
class InvalidKeyValue(CustomException):
"""The value of a key is invalid"""
def __init__(self, key: str='', value: str=''):
self.key = key
@@ -62,14 +68,20 @@ class InvalidKeyValue(Exception):
def api_response(self) -> Dict[str, Any]:
return {'error': 'InvalidKeyValue', 'result': {'key': self.key, 'value': self.value}, 'code': 400}
class TemplateNotFound(Exception):
class TemplateNotFound(CustomException):
"""The template was not found"""
api_response = {'error': 'TemplateNotFound', 'result': {}, 'code': 404}
class APIKeyInvalid(Exception):
class APIKeyInvalid(CustomException):
"""The API key is not correct"""
api_response = {'error': 'APIKeyInvalid', 'result': {}, 'code': 401}
def __init__(self, e=None) -> None:
return
class APIKeyExpired(Exception):
class APIKeyExpired(CustomException):
"""The API key has expired"""
api_response = {'error': 'APIKeyExpired', 'result': {}, 'code': 401}
def __init__(self, e=None) -> None:
return

View File

@@ -1,6 +1,7 @@
#-*- coding: utf-8 -*-
from datetime import datetime
import logging
from sqlite3 import Connection, ProgrammingError, Row
from threading import current_thread, main_thread
from time import time
@@ -19,6 +20,7 @@ class Singleton(type):
i = f'{cls}{current_thread()}'
if (i not in cls._instances
or cls._instances[i].closed):
logging.debug(f'Creating singleton instance: {i}')
cls._instances[i] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[i]
@@ -28,10 +30,12 @@ class ThreadedTaskDispatcher(OldThreadedTaskDispatcher):
super().handler_thread(thread_no)
i = f'{DBConnection}{current_thread()}'
if i in Singleton._instances and not Singleton._instances[i].closed:
logging.debug(f'Closing singleton instance: {i}')
Singleton._instances[i].close()
def shutdown(self, cancel_pending: bool = True, timeout: int = 5) -> bool:
print('Shutting down MIND...')
print()
logging.info('Shutting down MIND...')
super().shutdown(cancel_pending, timeout)
DBConnection(20.0).close()
@@ -39,12 +43,14 @@ class DBConnection(Connection, metaclass=Singleton):
file = ''
def __init__(self, timeout: float) -> None:
logging.debug(f'Opening database connection for {current_thread()}')
super().__init__(self.file, timeout=timeout)
super().cursor().execute("PRAGMA foreign_keys = ON;")
self.closed = False
return
def close(self) -> None:
logging.debug(f'Closing database connection for {current_thread()}')
self.closed = True
super().close()
return
@@ -91,7 +97,7 @@ def migrate_db(current_db_version: int) -> None:
Migrate a MIND database from it's current version
to the newest version supported by the MIND version installed.
"""
print('Migrating database to newer version...')
logging.info('Migrating database to newer version...')
cursor = get_db()
if current_db_version == 1:
# V1 -> V2
@@ -289,6 +295,7 @@ def setup_db() -> None:
"SELECT value FROM config WHERE key = 'database_version' LIMIT 1;"
).fetchone()[0])
logging.debug(f'Current database version {current_db_version} and desired database version {__DATABASE_VERSION__}')
if current_db_version < __DATABASE_VERSION__:
migrate_db(current_db_version)
cursor.execute(

View File

@@ -1,5 +1,6 @@
#-*- coding: utf-8 -*-
import logging
from typing import List
from backend.custom_exceptions import (NotificationServiceInUse,
@@ -44,6 +45,7 @@ class NotificationService:
Returns:
dict: The new info about the service
"""
logging.info(f'Updating notification service {self.id}: {title=}, {url=}')
# Get current data and update it with new values
data = self.get()
@@ -75,7 +77,9 @@ class NotificationService:
Raises:
NotificationServiceInUse: The service is still used by a reminder
"""
"""
logging.info(f'Deleting notification service {self.id}')
# Check if no reminders exist with this service
cursor = get_db()
cursor.execute("""
@@ -164,7 +168,8 @@ class NotificationServices:
Returns:
dict: The info about the new service
"""
"""
logging.info(f'Adding notification service with {title=}, {url=}')
new_id = get_db().execute("""
INSERT INTO notification_services(user_id, title, url)

View File

@@ -1,6 +1,7 @@
#-*- coding: utf-8 -*-
from datetime import datetime
import logging
from sqlite3 import IntegrityError
from threading import Timer
from typing import List, Literal
@@ -29,7 +30,11 @@ def _find_next_time(
current_time = datetime.fromtimestamp(datetime.utcnow().timestamp())
while new_time <= current_time:
new_time += td
return int(new_time.timestamp())
result = int(new_time.timestamp())
logging.debug(
f'{original_time=}, {current_time=} and interval of {repeat_interval} {repeat_quantity} leads to {result}'
)
return result
class ReminderHandler:
"""Handle set reminders
@@ -82,6 +87,7 @@ class ReminderHandler:
"DELETE FROM reminders WHERE id = ?;",
(reminder['id'],)
)
logging.info(f'Deleted reminder {reminder["id"]}')
else:
# Set next time
new_time = _find_next_time(
@@ -123,8 +129,14 @@ class ReminderHandler:
or time < self.next_trigger['time']):
if self.next_trigger['thread'] is not None:
self.next_trigger['thread'].cancel()
t = time - datetime.utcnow().timestamp()
self.next_trigger['thread'] = Timer(t, self.__trigger_reminders, (time,))
self.next_trigger['thread'] = Timer(
t,
self.__trigger_reminders,
(time,)
)
self.next_trigger['thread'].name = "ReminderHandler"
self.next_trigger['thread'].start()
self.next_trigger['time'] = time
@@ -209,6 +221,10 @@ class Reminder:
Returns:
dict: The new reminder info
"""
logging.info(
f'Updating notification service {self.id}: '
+ f'{title=}, {time=}, {notification_services=}, {text=}, {repeat_quantity=}, {repeat_interval=}, {color=}'
)
cursor = get_db()
# Validate data
@@ -301,7 +317,8 @@ class Reminder:
def delete(self) -> None:
"""Delete the reminder
"""
"""
logging.info(f'Deleting reminder {self.id}')
get_db().execute("DELETE FROM reminders WHERE id = ?", (self.id,))
reminder_handler.find_next_reminder()
return
@@ -410,6 +427,11 @@ class Reminders:
Returns:
dict: The info about the reminder
"""
logging.info(
f'Adding reminder with {title=}, {time=}, {notification_services=}, '
+ f'{text=}, {repeat_quantity=}, {repeat_interval=}, {color=}'
)
if time < datetime.utcnow().timestamp():
raise InvalidTime
time = round(time)
@@ -466,6 +488,7 @@ class Reminders:
notification_service (int): The id of the notification service to use to send the reminder
text (str, optional): The body of the reminder. Defaults to ''.
"""
logging.info(f'Testing reminder with {title=}, {notification_services=}, {text=}')
a = Apprise()
cursor = get_db(dict)
for service in notification_services:

View File

@@ -1,5 +1,6 @@
#-*- coding: utf-8 -*-
import logging
from sqlite3 import IntegrityError
from typing import List, Literal
@@ -74,7 +75,12 @@ class StaticReminder:
Returns:
dict: The new static reminder info
"""
"""
logging.info(
f'Updating static reminder {self.id}: '
+ f'{title=}, {notification_services=}, {text=}, {color=}'
)
# Get current data and update it with new values
data = self.get()
new_values = {
@@ -119,6 +125,7 @@ class StaticReminder:
def delete(self) -> None:
"""Delete the static reminder
"""
logging.info(f'Deleting static reminder {self.id}')
get_db().execute("DELETE FROM static_reminders WHERE id = ?", (self.id,))
return
@@ -218,6 +225,10 @@ class StaticReminders:
Returns:
StaticReminder: A StaticReminder instance representing the newly created static reminder
"""
logging.info(
f'Adding static reminder with {title=}, {notification_services=}, {text=}, {color=}'
)
cursor = get_db()
id = cursor.execute("""
INSERT INTO static_reminders(user_id, title, text, color)
@@ -245,6 +256,7 @@ class StaticReminders:
Raises:
ReminderNotFound: The static reminder with the given id was not found
"""
logging.info(f'Triggering static reminder {self.id}')
cursor = get_db(dict)
reminder = cursor.execute("""
SELECT title, text

View File

@@ -1,5 +1,6 @@
#-*- coding: utf-8 -*-
import logging
from sqlite3 import IntegrityError
from typing import List, Literal
@@ -69,6 +70,11 @@ class Template:
Returns:
dict: The new template info
"""
logging.info(
f'Updating template {self.id}: '
+ f'{title=}, {notification_services=}, {text=}, {color=}'
)
cursor = get_db()
data = self.get()
@@ -111,6 +117,7 @@ class Template:
def delete(self) -> None:
"""Delete the template
"""
logging.info(f'Deleting template {self.id}')
get_db().execute("DELETE FROM templates WHERE id = ?;", (self.id,))
return
@@ -203,7 +210,11 @@ class Templates:
Returns:
Template: The info about the template
"""
"""
logging.info(
f'Adding template with {title=}, {notification_services=}, {text=}, {color=}'
)
cursor = get_db()
id = cursor.execute("""
INSERT INTO templates(user_id, title, text, color)

View File

@@ -1,5 +1,6 @@
#-*- coding: utf-8 -*-
import logging
from backend.custom_exceptions import (AccessUnauthorized, UsernameInvalid,
UsernameTaken, UserNotFound)
from backend.db import get_db
@@ -27,7 +28,7 @@ class User:
self.salt = result['salt']
self.user_id = result['id']
# check password
# Check password
hash_password = get_hash(result['salt'], password)
if not hash_password == result['hash']:
raise AccessUnauthorized
@@ -90,11 +91,14 @@ class User:
"UPDATE users SET hash = ? WHERE id = ?",
(hash_password, self.user_id)
)
logging.info(f'The user {self.username} ({self.user_id}) changed their password')
return
def delete(self) -> None:
"""Delete the user account
"""
"""
logging.info(f'Deleting the user {self.username} ({self.user_id})')
cursor = get_db()
cursor.execute("DELETE FROM reminders WHERE user_id = ?", (self.user_id,))
cursor.execute("DELETE FROM templates WHERE user_id = ?", (self.user_id,))
@@ -111,7 +115,8 @@ def _check_username(username: str) -> None:
Raises:
UsernameInvalid: The username is not valid
"""
"""
logging.debug(f'Checking the username {username}')
if username in ONEPASS_INVALID_USERNAMES or username.isdigit():
raise UsernameInvalid
if list(filter(lambda c: not c in ONEPASS_USERNAME_CHARACTERS, username)):
@@ -132,6 +137,8 @@ def register_user(username: str, password: str) -> int:
Returns:
user_id (int): The id of the new user. User registered successful
"""
logging.info(f'Registering user with username {username}')
# Check if username is valid
_check_username(username)
@@ -156,4 +163,5 @@ def register_user(username: str, password: str) -> int:
(username, salt, hashed_password)
).lastrowid
logging.debug(f'Newly registered user has id {user_id}')
return user_id