Skip to content

Commit

Permalink
Merge pull request #718 from girardinsamuel/feat/663
Browse files Browse the repository at this point in the history
[5.X] Add `logging` feature
  • Loading branch information
eaguad1337 authored Jul 5, 2024
2 parents cc1411d + 398f977 commit 8e83cf7
Show file tree
Hide file tree
Showing 27 changed files with 1,680 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11"]
name: Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ src/masonite.egg-info/*
.vscode
build/
venv4
.python-version
*.whl
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
init:
cp .env-example .env
pip install -r requirements.txt
pip install --no-cache-dir --upgrade -r requirements.txt
pip install '.[test]'
# Create MySQL Database
# Create Postgres Database
Expand Down
910 changes: 910 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[tool.poetry]
authors = ['Joe Mancuso', 'Eduardo Aguad']
description = "Masonite is a modern and developer centric Python web framework."
license = "MIT"
name = "masonite"
version = "5.0.0"

[tool.poetry.dependencies]
python = "^3.9"
inflection = ">=0.3,<0.4"
exceptionite = ">=2.2,<3.0"
pendulum = ">=2,<3"
jinja2 = "<3.1.0"
cleo = ">=0.8.1,<0.9"
hupper = ">=1.10,<1.11"
bcrypt = ">=3.2,<3.3"
whitenoise = ">=5.2,<5.3"
python-dotenv = ">=0.15,<0.16"
masonite-orm = ">=2.14,<3"
hashids = ">=1.3,<1.4"
cryptography = ">=36,<37"
tldextract = ">=2.2,<2.3"
hfilesize = ">=0.1"
dotty_dict = ">=1.3.0,<1.40"
pyjwt = ">=2.3,<2.5"
pytest = ">=7,<8"
werkzeug = ">=2,<3"
watchdog = ">=2,<3"

[tool.poetry.dev-dependencies]
pytest = "^7.0.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
8 changes: 4 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ argon2-cffi
bcrypt>=3.2,<3.3
black
cleo>=0.8.1,<0.9
cryptography>=3.3.1,<4.0
dotty_dict>=1.3.0<1.40
cryptography>=36,<37
dotty_dict>=1.3.0,<1.40
exceptionite>=2.0,<3
hashids>=1.3,<1.4
hfilesize>=0.1
Expand All @@ -20,7 +20,7 @@ python-dotenv>=0.15,<0.16
responses
slackblocks
tldextract>=2.2,<2.3
werkzeug>=2<3
watchdog>=2<3
werkzeug>=2,<3
watchdog>=2,<3
whitenoise>=5.2,<5.3
pyjwt>=2.3,<2.5
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@
"masonite.helpers",
"masonite.input",
"masonite.loader",
"masonite.logging",
"masonite.logging.drivers",
"masonite.mail.drivers",
"masonite.mail",
"masonite.middleware.route",
Expand Down
5 changes: 5 additions & 0 deletions src/masonite/facades/Log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .Facade import Facade


class Log(metaclass=Facade):
key = "logger"
24 changes: 24 additions & 0 deletions src/masonite/facades/Log.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import TYPE_CHECKING, List

if TYPE_CHECKING:
from ..logging.LoggerFactory import LoggerFactory

class Log:
"""Log facade."""

def log(level: str, message: str) -> None:
"""Log a message with the given level."""
...
def debug(message: str) -> None: ...
def info(message: str) -> None: ...
def notice(message: str) -> None: ...
def warning(message: str) -> None: ...
def error(message: str) -> None: ...
def critical(message: str) -> None: ...
def alert(message: str) -> None: ...
def emergency(message: str) -> None: ...
def stack(*channels: List[str]) -> "LoggerFactory":
"""On-demand stack channels."""
...
def channel(channel: str) -> "LoggerFactory": ...
def build(driver: str, options: dict = {}) -> "LoggerFactory": ...
1 change: 1 addition & 0 deletions src/masonite/facades/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
from .Cache import Cache
from .RateLimiter import RateLimiter
from .Broadcast import Broadcast
from .Log import Log
126 changes: 126 additions & 0 deletions src/masonite/logging/Logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import logging
from typing import TYPE_CHECKING, List

if TYPE_CHECKING:
from ..foundation import Application

from .LoggerFactory import LoggerFactory
from ..exceptions import InvalidConfigurationSetup


class Logger:
# 0 Emergency: system is unusable
# 1 Alert: action must be taken immediately
# 2 Critical: critical conditions
# 3 Error: error conditions
# 4 Warning: warning conditions
# 5 Notice: normal but significant condition
# 6 Informational: informational messages
# 7 Debug: debug-level messages

def __init__(self, application: "Application", options: dict = {}) -> None:
self.application = application
self.drivers = {}
self.options = options

# configure python logging module to add new levels
logging.NOTICE = 25
logging.ALERT = 60
logging.EMERGENCY = 70
new_levels = {
"notice": logging.NOTICE,
"alert": logging.ALERT,
"emergency": logging.EMERGENCY,
}
for name, levelno in new_levels.items():
logging.addLevelName(levelno, name.upper())

self.levels = {
"debug": logging.DEBUG,
"info": logging.INFO,
"notice": logging.NOTICE,
"warning": logging.WARNING,
"error": logging.ERROR,
"critical": logging.CRITICAL,
"alert": logging.ALERT,
"emergency": logging.EMERGENCY,
}

def get_default_level(self) -> str:
return self.options.get("channels.default.level")

def get_default_timezone(self) -> str:
return self.options.get("channels.default.timezone")

def get_default_format(self) -> str:
return self.options.get("channels.default.format")

def get_default_date_format(self) -> str:
return self.options.get("channels.default.date_format")

def add_driver(self, name: str, driver):
self.drivers.update({name: driver})

def set_options(self, options: dict) -> "Logger":
self.options = options
return self

def get_driver_from_channel(self, channel: str = None, options: dict = {}):
if channel is None:
channel = self.options.get("channels.default.driver")

# get driver for channel
driver_name = self.options.get(f"channels.{channel}.driver")
if not driver_name:
raise InvalidConfigurationSetup(
f"No config for channel '{channel}' in config/logging.py !"
)
return self.get_driver(
driver_name, channel, options or self.options.get(f"channels.{channel}")
)

def get_driver(self, driver: str, name: str = None, options: dict = {}):
return self.drivers[driver](self.application, name or driver, options)

def get_level_name(self, levelno: int) -> str:
for name, no in self.levels.items():
if no == levelno:
return name

def log(self, level: str, message: str) -> None:
"""Log a message with the given level."""
return LoggerFactory(self).log(level, message)

def debug(self, message: str) -> None:
return LoggerFactory(self).debug(message)

def info(self, message: str) -> None:
return LoggerFactory(self).info(message)

def notice(self, message: str) -> None:
return LoggerFactory(self).notice(message)

def warning(self, message: str) -> None:
return LoggerFactory(self).warning(message)

def error(self, message: str) -> None:
return LoggerFactory(self).error(message)

def critical(self, message: str) -> None:
return LoggerFactory(self).critical(message)

def alert(self, message: str) -> None:
return LoggerFactory(self).alert(message)

def emergency(self, message: str) -> None:
return LoggerFactory(self).emergency(message)

def stack(self, *channels: List[str]) -> "LoggerFactory":
"""On-demand stack channels."""
return LoggerFactory(self, driver="stack", options={"channels": channels})

def channel(self, channel: str) -> "LoggerFactory":
return LoggerFactory(self, channel=channel)

def build(self, driver: str, options: dict = {}) -> "LoggerFactory":
return LoggerFactory(self, driver, options=options)
6 changes: 6 additions & 0 deletions src/masonite/logging/LoggerExceptionsListener.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from ..facades import Log


class LoggerExceptionsListener:
def handle(self, exception_type: str, exception: Exception):
Log.error(f"{exception_type}: {exception}")
51 changes: 51 additions & 0 deletions src/masonite/logging/LoggerFactory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from .Logger import Logger


class LoggerFactory:
def __init__(
self,
logger: "Logger",
driver: str = None,
channel: str = None,
options: dict = {},
) -> None:
self.logger = logger
self.driver = driver
self.channel = channel
self.options = options

if driver and not channel:
self.selected_driver = self.logger.get_driver(driver, options=options)
# log to default configured channel or given channel
else:
self.selected_driver = self.logger.get_driver_from_channel(channel, options)

def log(self, level: str, message: str) -> None:
self.selected_driver.log(level, message)

def debug(self, message: str) -> None:
self.selected_driver.debug(message)

def info(self, message: str) -> None:
self.selected_driver.info(message)

def notice(self, message: str) -> None:
self.selected_driver.notice(message)

def warning(self, message: str) -> None:
self.selected_driver.warning(message)

def error(self, message: str) -> None:
self.selected_driver.error(message)

def critical(self, message: str) -> None:
self.selected_driver.critical(message)

def alert(self, message: str) -> None:
self.selected_driver.alert(message)

def emergency(self, message: str) -> None:
self.selected_driver.emergency(message)
34 changes: 34 additions & 0 deletions src/masonite/logging/LoggingProvider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from ..providers import Provider
from ..facades import Config
from .Logger import Logger
from .drivers import (
TerminalDriver,
DailyFileDriver,
SingleFileDriver,
StackDriver,
SysLogDriver,
SlackDriver,
)
from .LoggerExceptionsListener import LoggerExceptionsListener


class LoggingProvider(Provider):
def __init__(self, application):
self.application = application

def register(self):
logger = Logger(self.application).set_options(Config.get("logging"))
logger.add_driver("terminal", TerminalDriver)
logger.add_driver("daily", DailyFileDriver)
logger.add_driver("single", SingleFileDriver)
logger.add_driver("stack", StackDriver)
logger.add_driver("syslog", SysLogDriver)
logger.add_driver("slack", SlackDriver)
self.application.bind("logger", logger)

self.application.make("event").listen(
"masonite.exception.*", [LoggerExceptionsListener]
)

def boot(self):
pass
1 change: 1 addition & 0 deletions src/masonite/logging/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .LoggingProvider import LoggingProvider
Loading

0 comments on commit 8e83cf7

Please sign in to comment.