Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """
- 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)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement