Python - Logging Module v1.1.0 (logging.py)
justinooo |
PRO |
01/20/25 11:46:07 PM UTC (Edited) |
0 ⭐ |
1023 👁️ |
Never ⏰ |
[Logging, Betterstack]
"""
File: logging.py
Author: Justin Garofolo
Date: 2024-05-31
Version: 1.1.0
Description:
A flexible logging system for Python applications.
It supports multiple logging levels and asynchronous log handling through threaded workers.
The module can be easily integrated into projects with the capability to log messages both locally and remotely.
Note that remote logging capabilities are implemented via BetterStack's logging service by default: https://betterstack.com/logs
Copyright:
Copyright (C) 2024 SneakSync. All rights reserved.
Documentation:
https://link.sneaksync.com/logging
"""
import requests, os, threading
from typing import Optional
from enum import Enum
from datetime import datetime, timezone
from http import HTTPStatus
from dataclasses import dataclass
from queue import Queue
def create_logger_from_env(
name: str,
env_var: str,
auto_print: bool = True
) -> "Logger":
tk = os.getenv(env_var)
logger = Logger(name, token = tk, auto_print = auto_print)
if not tk:
logger.warn(f"Failed to initialize instance of Logger using create_logger_from_env. Missing environment variable: {env_var}")
return logger
def create_logger(
name: str,
token: Optional[str] = None,
auto_print: bool = True
) -> "Logger":
return Logger(name, token = token, auto_print = auto_print)
class Level(Enum):
TRACE = "trace"
DEBUG = "debug"
INFO = "info"
WARN = "warn"
ERROR = "error"
FATAL = "fatal"
PANIC = "panic"
@dataclass
class Log:
id: int
timestamp: datetime
message: str
level: Level
class Logger:
_inc: int = 0 # auto-increment for unique log ID
def __init__(
self,
name: str,
token: Optional[str] = None,
worker_count: int = 3,
auto_print: bool = True
):
self.name = name
self.token = token
self.worker_count = worker_count
self.auto_print = auto_print
self.queue = Queue()
if token:
mask = ('*' * (len(token) - 4) + token[-4:]) \
if len(token) > 4 else token
self.debug(f"Initialized logging module with token: {mask}")
def log_worker(self, worker_id: int):
self.info(f"Starting log worker (ID: {worker_id})")
while True:
log = self.queue.get()
# thread kill switch
if log is None:
break
if not isinstance(log, Log):
type_str = type(log).__name__
print(f"Item from log queue is not an instance of Log: {log} [{type_str}]")
continue
self.upload_log(log)
self.queue.task_done()
self.info(f"Killed log worker (ID: {worker_id})")
def start_workers(self):
for idx in range(self.worker_count):
worker_id = idx + 1
worker = threading.Thread(target = self.log_worker, args = (worker_id,))
worker.daemon = True
worker.start()
def stop_workers(self):
for _ in range(self.worker_count):
self.queue.put(None)
def log(self, level: Level, message: str):
Logger._inc += 1
log_id = Logger._inc
if self.auto_print:
now = datetime.now()
now_str = now.strftime('%Y-%m-%d %I:%M:%S %p')
print(f"[{self.name} | {now_str} | {level.value}] {message}")
log_obj = Log(
id = log_id,
timestamp = datetime.now(timezone.utc),
message = message,
level = level
)
self.queue.put(log_obj)
def debug(self, message: str):
return self.log(Level.DEBUG, message)
def info(self, message: str):
return self.log(Level.INFO, message)
def warn(self, message: str):
return self.log(Level.WARN, message)
def error(self, message: str):
return self.log(Level.ERROR, message)
def upload_log(self, log: Log) -> bool:
headers = {
'Content-Type': 'application/json',
'Authorization': f"Bearer {self.token}"
}
data = {
"dt": log.timestamp.strftime('%Y-%m-%d %H:%M:%S.%f UTC'),
# "message": f"[#{log.id}] {log.message}",
"message": log.message,
"level": log.level.value
}
url = "https://in.logs.betterstack.com"
response = requests.post(url, json = data, headers = headers)
if response.status_code != HTTPStatus.ACCEPTED.value:
print(f"Failed to send log to logtail, status code: {response.status_code}")
return False
return True
__default_logger: Optional[Logger] = None
def set_default(logger: Logger):
global __default_logger
__default_logger = logger
def debug(message: str):
assert __default_logger is not None, "Failed to invoke 'logging.debug': missing default logger."
return __default_logger.debug(message)
def info(message: str):
assert __default_logger is not None, "Failed to invoke 'logging.info': missing default logger."
return __default_logger.info(message)
def warn(message: str):
assert __default_logger is not None, "Failed to invoke 'logging.warn': missing default logger."
return __default_logger.warn(message)
def error(message: str):
assert __default_logger is not None, "Failed to invoke 'logging.error': missing default logger."
return __default_logger.error(message)
Comments