Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

lager

Package Overview
Dependencies
Maintainers
3
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lager - npm Package Compare versions

Comparing version
0.18.2
to
0.19.0
+146
.gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*.doctest.txt
*_tests/
*.bak/
*.bak
f.py
file.py
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.nox_win/
.nox_wsl/
.nox_lin/
.nox_win.bak/
.nox_wsl.bak/
.nox_lin.bak/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
.ipynb_checkpoints/
# pyenv
.python-version
# Environments
.env
.env.json
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# node
node_modules/
########
# MISC #
########
.DS_Store
*~
.*.sw[po]
scratch/
.pytype/
mypy_reports/
pythonenv3.8/
.idea/
monkeytype.sqlite3
.parcel-cache/
package-lock.json
.pdm.toml
__pypackages__
pdm.lock
.vscode/
dgpy-packages/
temp/
thingy.py
herm.py
yarn.lock
.pdm-python
# used for merging multiple branches at once
branches.txt
# -*- coding: utf-8 -*-
"""Package metadata/info"""
from __future__ import annotations
__all__ = ("__description__", "__pkgroot__", "__title__", "__version__")
__title__ = "lager"
__description__ = "EZ-PZ logging based on loguru"
__pkgroot__ = __file__.replace("__about__.py", "").rstrip("/\\")
__version__ = "0.19.0"
# -*- coding: utf-8 -*-
"""Python lager brewed by a loguru"""
from __future__ import annotations
from lager import logging
from lager.__about__ import __version__
from lager.const import LAGER_PORT, LOGURU_DEFAULT_FMT, TORNADO_FMT
from lager.core import (
LAGER,
LN,
LOG,
_change_activation,
_core,
_find_iter,
_log,
_options,
add,
bind,
catch,
complete,
configure,
contextualize,
critical,
debug,
disable,
enable,
error,
exception,
flog,
handlers,
info,
lager,
level,
ln,
log,
logger,
loglevel,
opt,
parse,
patch,
remove,
reset,
start,
stop,
success,
trace,
warning,
)
__all__ = (
"LAGER",
"LAGER_PORT",
"LN",
"LOG",
"LOGURU_DEFAULT_FMT",
"TORNADO_FMT",
"__version__",
#############
## HOISTED ##
#############
"_change_activation",
"_core",
"_find_iter",
"_log",
"_options",
"add",
"bind",
"catch",
"complete",
"configure",
"contextualize",
"critical",
"debug",
"disable",
"enable",
"error",
"exception",
"flog",
"handlers",
"info",
"lager",
"level",
"ln",
"log",
"logger",
"logging",
"loglevel",
"opt",
"parse",
"patch",
"remove",
"reset",
"start",
"stop",
"success",
"trace",
"warning",
)
# -*- coding: utf-8 -*-
"""pkg entry ~ `python -m lager`"""
from __future__ import annotations
import sys
from lager.__about__ import __pkgroot__, __title__, __version__
def main() -> None:
"""Print package metadata"""
import json
sys.stdout.write(
json.dumps(
{
"package": __title__,
"version": __version__,
"pkgroot": __pkgroot__,
},
indent=2,
)
)
if __name__ == "__main__":
main()
# -*- coding: utf-8 -*-
"""Package metadata/info"""
from __future__ import annotations
import warnings
from lager.__about__ import __description__, __pkgroot__, __title__, __version__
warnings.warn(
"lager._meta is deprecated, use lager.__about__ instead",
DeprecationWarning,
stacklevel=2,
)
__all__ = ("__description__", "__pkgroot__", "__title__", "__version__")
# -*- coding: utf-8 -*-
"""Constants go here!"""
from __future__ import annotations
from typing import Dict
LAGER_PORT = 52437
TORNADO_FMT = "".join(
[
"<level>",
"[{level.name[0]} ",
"{time:YYMMDDTHH:mm:ss} ",
"{name}:{module}:{line}]",
"</level> ",
"{message}",
]
)
LOGURU_DEFAULT_FMT = "".join(
[
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>",
" | ",
"<level>{level: <8}</level>",
" | ",
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan>",
" - ",
"<level>{message}</level>",
]
)
LOG_LEVELS: Dict[str, str] = {
"notset": "NOTSET",
"n": "NOTSET",
"debug": "DEBUG",
"d": "DEBUG",
"info": "INFO",
"i": "INFO",
"s": "SUCCESS",
"success": "SUCCESS",
"warning": "WARNING",
"warn": "WARNING",
"w": "WARNING",
"error": "ERROR",
"e": "ERROR",
"critical": "CRITICAL",
"fatal": "CRITICAL",
"c": "CRITICAL",
# enum/enum-strings
"0": "NOTSET",
"10": "DEBUG",
"20": "INFO",
"25": "SUCCESS",
"30": "WARNING",
"40": "ERROR",
"50": "CRITICAL",
}
# -*- coding: utf-8 -*-
"""Python lager brewed by a loguru"""
from __future__ import annotations
import asyncio
import sys as _sys
from functools import wraps
from time import time
from typing import Any, Callable, Dict, Optional, TypeVar, Union
from loguru import logger as logger
from loguru._handler import Handler
from lager.const import LOG_LEVELS
T = TypeVar("T")
try:
import orjson
def _stringify_new_line(serializable: Any) -> str:
return orjson.dumps(serializable, option=orjson.OPT_APPEND_NEWLINE).decode(
"utf-8"
)
def _stringify_no_new_line(serializable: Any) -> str:
return f"{orjson.dumps(serializable).decode('utf-8')}\n"
_stringify = (
_stringify_new_line
if hasattr(orjson, "OPT_APPEND_NEWLINE")
else _stringify_no_new_line
)
def _serialize_record(text: str, record: Dict[str, Any]) -> str:
exception = record["exception"]
if exception is not None:
exception = {
"type": None if exception.type is None else exception.type.__name__,
"value": exception.value,
"traceback": bool(record["exception"].traceback),
}
serializable = {
"text": text,
"record": {
"elapsed": {
"repr": record["elapsed"],
"seconds": record["elapsed"].total_seconds(),
},
"exception": exception,
"extra": record["extra"],
"file": {
"name": record["file"].name,
"path": record["file"].path,
},
"function": record["function"],
"level": {
"icon": record["level"].icon,
"name": record["level"].name,
"no": record["level"].no,
},
"line": record["line"],
"message": record["message"],
"module": record["module"],
"name": record["name"],
"process": {
"id": record["process"].id,
"name": record["process"].name,
},
"thread": {
"id": record["thread"].id,
"name": record["thread"].name,
},
"time": {
"repr": record["time"],
"timestamp": record["time"].timestamp(),
},
},
}
return _stringify(serializable)
Handler._serialize_record = staticmethod(_serialize_record)
except ModuleNotFoundError:
pass
# lager/logger aliases
log = LOG = logger
lager = LAGER = logger
ln = LN = logger # ln => natural log
def loglevel(level: Union[str, int]) -> str:
"""Convert log-level abrev to a valid loguru log level"""
return str(LOG_LEVELS[str(level).strip("'").strip('"').lower()])
def flog(
funk: Optional[Callable[..., T]] = None,
level: Union[str, int] = "debug",
enter: bool = True,
exit: bool = True,
) -> T:
"""Log function (sync/async) enter and exit using this decorator
Args:
funk (Callable): Function to decorate
level (Union[int, str]): Log level
enter (bool): Log function entry if True
exit (bool): Log function exit if False
Returns:
A wrapped function that now has logging!
Usage:
# SYNC
@flog
def add(a, b):
return a + b
add(1, 4)
# ASYNC
@flog
async def add_async(a, b):
return a + b
import asyncio
asyncio.run(add_async(1, 4))
"""
def _flog(funk: Callable[..., T]) -> Callable[..., T]:
name = funk.__name__
@wraps(funk)
def _flog_decorator(*args: Any, **kwargs: Any) -> T:
logger_ = logger.opt(depth=1)
if enter:
logger_.log(
loglevel(level),
"FLOG-ENTER > '{}' (args={}, kwargs={})",
name,
args,
kwargs,
)
ti = time()
result = funk(*args, **kwargs)
tf = time()
if exit:
logger_.log(
loglevel(level),
"FLOG-EXIT < '{}' (return={}, dt_sec={})",
name,
result,
tf - ti,
)
return result
@wraps(funk)
async def _flog_decorator_async(*args: Any, **kwargs: Any) -> T:
logger_ = logger.opt(depth=7)
if enter:
logger_.log(
loglevel(level),
"FLOG-ENTER > '{}' (args={}, kwargs={})",
name,
args,
kwargs,
)
ti = time()
result: T = await funk(*args, **kwargs) # type: ignore[misc]
tf = time()
if exit:
logger_.log(
loglevel(level),
"FLOG-EXIT < '{}' (return={}, dt_sec={})",
name,
result,
tf - ti,
)
return result
if asyncio.iscoroutinefunction(funk) or asyncio.iscoroutine(funk):
return _flog_decorator_async # type: ignore[return-value]
return _flog_decorator
return _flog(funk) if funk else _flog # type: ignore[return-value]
def handlers() -> Dict[int, Handler]:
"""Return all handlers"""
return logger._core.handlers # type: ignore[no-any-return]
def reset(level: Optional[Union[str, int]] = None) -> None:
logger.remove()
logger.add(_sys.stderr, level=loglevel(level or "debug"))
__hoisted__ = (
"_change_activation",
"_core",
"_dynamic_level",
"_find_iter",
"_log",
"_options",
"add",
"bind",
"catch",
"complete",
"configure",
"contextualize",
"critical",
"debug",
"disable",
"enable",
"error",
"exception",
"info",
"level",
"opt",
"parse",
"patch",
"remove",
"start",
"stop",
"success",
"trace",
"warning",
)
_change_activation = LAGER._change_activation
_core = LAGER._core
_find_iter = LAGER._find_iter
_log = LAGER._log
_options = LAGER._options
add = LAGER.add
bind = LAGER.bind
catch = LAGER.catch
complete = LAGER.complete
configure = LAGER.configure
contextualize = LAGER.contextualize
critical = LAGER.critical
debug = LAGER.debug
disable = LAGER.disable
enable = LAGER.enable
error = LAGER.error
exception = LAGER.exception
info = LAGER.info
level = LAGER.level
opt = LAGER.opt
parse = LAGER.parse
patch = LAGER.patch
remove = LAGER.remove
start = LAGER.start
stop = LAGER.stop
success = LAGER.success
trace = LAGER.trace
warning = LAGER.warning
__all__ = (
"LAGER",
"LOG",
"_change_activation",
"_core",
"_find_iter",
"_log",
"_options",
"add",
"bind",
"catch",
"complete",
"configure",
"contextualize",
"critical",
"debug",
"disable",
"enable",
"error",
"exception",
"flog",
"handlers",
"info",
"lager",
"level",
"ln",
"log",
"logger",
"loglevel",
"opt",
"parse",
"patch",
"remove",
"start",
"stop",
"success",
"trace",
"warning",
)
# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import Any
try:
import httpx
_SINKS = []
class HttpxSink:
def __init__(self, url: str, *args: Any, **kwargs: Any) -> None:
self.url = url
self.client = httpx.AsyncClient(*args, **kwargs)
_SINKS.append(self)
async def __call__(self, msg: Any) -> None:
httpx.post(self.url, data={"msg": msg})
async def handle(self, message: Any) -> None:
await self.client.post(url=self.url, data={"msg": message})
async def await_delete_channels(self) -> None:
await self.client.aclose()
except ImportError:
pass
# -*- coding: utf-8 -*-
"""FastAPI logging"""
from __future__ import annotations
from typing import List, Optional, Set, Tuple, Union
from lager.logging import intercept
__all__ = ("FASTAPI_LOGGERS", "fastapi_intercept")
FASTAPI_LOGGERS = [
"gunicorn",
"gunicorn.errors",
"uvicorn",
"uvicorn.error",
"fastapi",
"sqlalchemy",
]
def fastapi_intercept(
loggers: Optional[Union[List[str], Set[str], Tuple[str, ...]]] = None,
) -> None:
_loggers2intercept = (
FASTAPI_LOGGERS if not loggers else sorted(set(*(*FASTAPI_LOGGERS, loggers)))
)
intercept(_loggers2intercept)
# -*- coding: utf-8 -*-
"""Lager/Loguru + std logging"""
from __future__ import annotations
import logging
from logging import (
BASIC_FORMAT as BASIC_FORMAT,
CRITICAL as CRITICAL,
DEBUG as DEBUG,
ERROR as ERROR,
FATAL as FATAL,
INFO as INFO,
NOTSET as NOTSET,
WARN as WARN,
WARNING as WARNING,
BufferingFormatter as BufferingFormatter,
FileHandler as FileHandler,
Filter as Filter,
Formatter as Formatter,
Handler as _Handler,
Logger as _Logger,
LoggerAdapter as LoggerAdapter,
LogRecord as LogRecord,
NullHandler as NullHandler,
StreamHandler as StreamHandler,
addLevelName as addLevelName,
addLevelName as add_level_name,
basicConfig as basicConfig,
basicConfig as basic_config,
captureWarnings as captureWarnings,
captureWarnings as capture_warnings,
critical as critical,
debug as debug,
disable as disable,
error as error,
exception as exception,
fatal as fatal,
getLevelName as getLevelName,
getLevelName as get_level_name,
getLogger as getLogger,
getLogger as get_logger,
getLoggerClass as getLoggerClass,
getLoggerClass as get_logger_class,
getLogRecordFactory as getLogRecordFactory,
getLogRecordFactory as get_log_record_factory,
info as info,
lastResort as lastResort,
lastResort as last_resort,
log as log,
makeLogRecord as makeLogRecord,
makeLogRecord as make_log_record,
raiseExceptions as raiseExceptions,
raiseExceptions as raise_exceptions,
setLoggerClass as setLoggerClass,
setLoggerClass as set_logger_class,
setLogRecordFactory as setLogRecordFactory,
setLogRecordFactory as set_log_record_factory,
shutdown as shutdown,
warn as warn,
warning as warning,
)
from types import TracebackType
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union
from typing_extensions import Literal, Self, TypeAlias
from lager.core import LOG, loglevel
__all__ = (
"BASIC_FORMAT",
"CRITICAL",
"DEBUG",
"ERROR",
"FATAL",
"INFO",
"NOTSET",
"WARN",
"WARNING",
"BufferingFormatter",
"FileHandler",
"Filter",
"Formatter",
"Handler",
"LogRecord",
"Logger",
"LoggerAdapter",
"NullHandler",
"StdLoggingHandler",
"StreamHandler",
# lager.logging members
"__aliases__",
"addLevelName",
"add_level_name",
"basicConfig",
"basic_config",
"captureWarnings",
"capture_warnings",
"critical",
"debug",
"disable",
"error",
"exception",
"fatal",
"getLevelName",
"getLogRecordFactory",
"getLogger",
"getLoggerClass",
"get_level_name",
"get_log_record_factory",
"get_logger",
"get_logger_class",
"info",
"intercept",
"intercept_all",
"lastResort",
"last_resort",
"log",
"loggers_dict",
"makeLogRecord",
"make_log_record",
"patch_logging",
"raiseExceptions",
"raise_exceptions",
"setLogRecordFactory",
"setLoggerClass",
"set_log_record_factory",
"set_logger_class",
"shutdown",
"warn",
"warning",
)
# logging snake_case aliases bc I cannot stand camelCase
__aliases__ = {
"add_level_name": "addLevelName",
"basic_config": "basicConfig",
"capture_warnings": "captureWarnings",
"get_level_name": "getLevelName",
"get_log_record_factory": "getLogRecordFactory",
"get_logger": "getLogger",
"get_logger_class": "getLoggerClass",
"last_resort": "lastResort",
"make_log_record": "makeLogRecord",
"raise_exceptions": "raiseExceptions",
"set_log_record_factory": "setLogRecordFactory",
"set_logger_class": "setLoggerClass",
}
_SysExcInfoType: TypeAlias = Union[
Tuple[Type[BaseException], BaseException, Union[TracebackType, None]],
Tuple[None, None, None],
]
_ExcInfoType: TypeAlias = Union[None, bool, _SysExcInfoType, BaseException]
_ArgsType: TypeAlias = Union[Tuple[object, ...], Mapping[str, object]]
_FilterType: TypeAlias = Union[Filter, Callable[[LogRecord], bool]]
_Level: TypeAlias = Union[int, str]
_FormatStyle: TypeAlias = Literal["%", "{", "$"]
class Logger(_Logger):
def __init__(self, name: str, level: _Level = 0) -> None:
super().__init__(name, level)
def set_level(self, level: _Level) -> None:
"""snake_case alias for setLevel"""
self.setLevel(level)
def is_enabled_for(self, level: int) -> bool:
"""snake_case alias for isEnabledFor"""
return self.isEnabledFor(level)
def get_effective_level(self) -> int:
"""snake_case alias for getEffectiveLevel"""
return self.getEffectiveLevel()
def get_child(self, suffix: str) -> Self:
"""snake_case alias for getChild"""
return self.getChild(suffix)
def find_caller(
self, stack_info: bool = False, stacklevel: int = 1
) -> Tuple[str, int, str, Union[str, None]]:
"""snake_case alias for findCaller"""
return self.findCaller(stack_info, stacklevel)
def add_handler(self, hdlr: Handler) -> None:
"""snake_case alias for addHandler"""
return self.addHandler(hdlr)
def remove_handler(self, hdlr: Handler) -> None:
"""snake_case alias for removeHandler"""
return self.removeHandler(hdlr)
def make_record(
self,
name: str,
level: int,
fn: str,
lno: int,
msg: object,
args: _ArgsType,
exc_info: Union[_SysExcInfoType, None],
func: Optional[str] = None,
extra: Union[Mapping[str, object], None] = None,
sinfo: Optional[str] = None,
) -> LogRecord:
return self.makeRecord(
name, level, fn, lno, msg, args, exc_info, func, extra, sinfo
)
def has_handlers(self) -> bool:
"""snake_case alias for hasHandlers"""
return self.hasHandlers()
def call_handlers(self, record: LogRecord) -> None:
"""snake_case alias for callHandlers"""
return self.callHandlers(record)
class Handler(_Handler):
def __init__(self, level: _Level = 0) -> None:
super().__init__(level)
def create_lock(self) -> None:
"""snake_case alias for createLock"""
return self.createLock()
def set_level(self, level: _Level) -> None:
"""snake_case alias for setLevel"""
return self.setLevel(level)
def set_formatter(self, fmt: Union[Formatter, None]) -> None:
"""snake_case alias for setFormatter"""
return self.setFormatter(fmt)
def handle_error(self, record: LogRecord) -> None:
"""snake_case alias for handleError"""
return self.handleError(record)
# =====================================================================================
def patch_logging() -> None:
for k, v in __aliases__.items():
setattr(logging, k, getattr(logging, v))
class StdLoggingHandler(logging.Handler):
"""Logging intercept handler"""
def emit(self, record: logging.LogRecord) -> None:
# Get corresponding Loguru level if it exists
try:
level = LOG.level(record.levelname).name
except AttributeError:
level = loglevel(record.levelno)
# Find caller from where originated the logging call
frame = logging.currentframe()
depth = 2
while (
frame.f_code.co_filename # pyright: ignore[reportOptionalMemberAccess]
== logging.__file__
):
frame = frame.f_back # type: ignore[assignment]
depth += 1
LOG.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
def _logger_dict() -> Any:
return logging.root.manager.loggerDict
def loggers_dict() -> Dict[str, logging.Logger]:
return {name: logging.getLogger(name) for name in _logger_dict()}
def intercept(loggers: List[str]) -> None:
for logger in loggers:
std_logger = logging.getLogger(logger)
std_logger.handlers = [StdLoggingHandler()]
def intercept_all() -> None:
intercept(list(loggers_dict().keys()))
# -*- coding: utf-8 -*-
"""Lager & pydantic"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Type, Union
from jsonbourne.pydantic import JsonBaseModel
if TYPE_CHECKING:
from datetime import datetime, timedelta
from types import TracebackType
class RecordFile(JsonBaseModel):
name: str
path: str
class RecordLevel(JsonBaseModel):
name: str
no: int
icon: str
class RecordThread(JsonBaseModel):
id: int
name: str
class RecordProcess(JsonBaseModel):
id: int
name: str
class RecordException(JsonBaseModel):
type: Optional[Type[BaseException]]
value: Optional[BaseException]
traceback: Optional[TracebackType]
class Record(JsonBaseModel):
elapsed: timedelta
exception: Optional[RecordException]
extra: Dict[Any, Any]
file: RecordFile
function: str
level: RecordLevel
line: int
message: str
module: str
name: Union[str, None]
process: RecordProcess
thread: RecordThread
time: datetime
class Message(JsonBaseModel):
record: Record
+8
-6

@@ -1,5 +0,7 @@

MIT License
dgpy-libs
Copyright (c) 2022
The MIT License (MIT)
Copyright (c) 2019-2025 Dynamic Graphics Inc (dgi)
Permission is hereby granted, free of charge, to any person obtaining a copy

@@ -12,4 +14,4 @@ of this software and associated documentation files (the "Software"), to deal

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

@@ -21,3 +23,3 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+11
-16

@@ -1,11 +0,8 @@

Metadata-Version: 2.1
Metadata-Version: 2.4
Name: lager
Version: 0.18.2
Version: 0.19.0
Summary: EZ-PZ logging based on loguru
Home-page: https://github.com/dynamic-graphics-inc/dgpy-libs/tree/main/libs/lager
License: MIT
Keywords: logging,dgpy,dgi,loguru,beer
Author: jesse rubin
Author-email: jesse@dgi.com
Requires-Python: >=3.8,<4.0
Author-email: jesse rubin <jesse@dgi.com>
License-File: LICENSE
Keywords: beer,dgi,dgpy,logging,loguru
Classifier: Development Status :: 5 - Production/Stable

@@ -15,4 +12,3 @@ Classifier: Intended Audience :: Developers

Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9

@@ -22,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.10

Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Dist: loguru (>=0.7.0)
Requires-Dist: typing-extensions (>=4.5.0,<5.0.0)
Project-URL: Repository, https://github.com/dynamic-graphics-inc/dgpy-libs
Requires-Python: <4.0,>=3.9
Requires-Dist: loguru>=0.7
Requires-Dist: typing-extensions<5,>=4.5
Description-Content-Type: text/markdown

@@ -34,3 +30,3 @@

# Lager :beer:
# lager :beer:

@@ -62,2 +58,1 @@ [![Wheel](https://img.shields.io/pypi/wheel/lager.svg)](https://img.shields.io/pypi/wheel/lager.svg)

2022-07-21 08:38:20.263 | INFO | __main__:<cell line: 3>:3 - info

@@ -1,13 +0,15 @@

[tool.poetry]
[build-system]
build-backend = "hatchling.build"
requires = [ "hatchling" ]
[project]
name = "lager"
version = "0.18.2"
version = "0.19.0"
description = "EZ-PZ logging based on loguru"
authors = ["jesse rubin <jesse@dgi.com>"]
license = "MIT"
repository = "https://github.com/dynamic-graphics-inc/dgpy-libs"
homepage = "https://github.com/dynamic-graphics-inc/dgpy-libs/tree/main/libs/lager"
readme = 'README.md'
packages = [
{ include = "lager", from = "." },
]
readme = "README.md"
keywords = [ "beer", "dgi", "dgpy", "logging", "loguru" ]
authors = [ { name = "jesse rubin", email = "jesse@dgi.com" } ]
requires-python = ">=3.9,<4.0"
classifiers = [

@@ -18,38 +20,35 @@ "Development Status :: 5 - Production/Stable",

"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Typing :: Typed",
]
keywords = [
"logging",
"dgpy",
"dgi",
"loguru",
"beer",
]
dependencies = [ "loguru>=0.7", "typing-extensions>=4.5,<5" ]
[tool.poetry.dependencies]
python = "^3.8"
loguru = ">=0.7.0"
typing-extensions = "^4.5.0"
[tool.hatch]
[tool.hatch.metadata]
allow-direct-references = true
[tool.poetry.dev-dependencies]
pytest = "^8.0.0"
pytest-cov = "^4.1.0"
[tool.hatch.build.targets.wheel]
packages = [ "src/lager" ]
[tool.poetry.group.dev.dependencies]
typing-extensions = "^4.5.0"
[tool.hatch.build.targets.sdist]
include = [
"src",
"pyproject.toml",
"README.md",
"LICENSE",
]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
extend = "../../pyproject.toml"
[tool.coverage.run]
source = ['lager']
source = [ 'lager' ]
branch = true
context = '${CONTEXT}'
omit = ["**/__main__.py"]
omit = [ "**/__main__.py" ]

@@ -70,3 +69,3 @@ [tool.coverage.report]

[tool.ruff]
extend = "../../pyproject.toml"
[tool]
dev-dependencies = [ "typing-extensions>=4.5.0,<5.0.0" ]

@@ -5,3 +5,3 @@ <a href="https://github.com/dynamic-graphics-inc/dgpy-libs">

# Lager :beer:
# lager :beer:

@@ -8,0 +8,0 @@ [![Wheel](https://img.shields.io/pypi/wheel/lager.svg)](https://img.shields.io/pypi/wheel/lager.svg)

# -*- coding: utf-8 -*-
"""Package metadata/info"""
from __future__ import annotations
__all__ = ("__title__", "__description__", "__pkgroot__", "__version__")
__title__ = "lager"
__description__ = "EZ-PZ logging based on loguru"
__pkgroot__ = __file__.replace("__about__.py", "").rstrip("/\\")
__version__ = "0.18.2"
# -*- coding: utf-8 -*-
"""Python lager brewed by a loguru"""
from __future__ import annotations
from lager import logging
from lager.__about__ import __version__
from lager.const import LAGER_PORT, LOGURU_DEFAULT_FMT, TORNADO_FMT
from lager.core import (
LAGER,
LN,
LOG,
_change_activation,
_core,
_find_iter,
_log,
_options,
add,
bind,
catch,
complete,
configure,
contextualize,
critical,
debug,
disable,
enable,
error,
exception,
flog,
handlers,
info,
lager,
level,
ln,
log,
logger,
loglevel,
opt,
parse,
patch,
remove,
reset,
start,
stop,
success,
trace,
warning,
)
__all__ = (
"__version__",
"LAGER_PORT",
"LOGURU_DEFAULT_FMT",
"TORNADO_FMT",
"loglevel",
"handlers",
"flog",
"logger",
"logging",
"lager",
"LAGER",
"LOG",
"log",
"ln",
"LN",
"reset",
#############
## HOISTED ##
#############
"_change_activation",
"_core",
"_find_iter",
"_log",
"_options",
"add",
"bind",
"catch",
"complete",
"configure",
"contextualize",
"critical",
"debug",
"disable",
"enable",
"error",
"exception",
"info",
"level",
"opt",
"parse",
"patch",
"remove",
"start",
"stop",
"success",
"trace",
"warning",
)
# -*- coding: utf-8 -*-
"""pkg entry ~ `python -m lager`"""
from __future__ import annotations
import sys
from lager.__about__ import __pkgroot__, __title__, __version__
def main() -> None:
"""Print package metadata"""
import json
sys.stdout.write(
json.dumps(
{
"package": __title__,
"version": __version__,
"pkgroot": __pkgroot__,
},
indent=2,
)
)
if __name__ == "__main__":
main()
# -*- coding: utf-8 -*-
"""Package metadata/info"""
from __future__ import annotations
import warnings
from lager.__about__ import __description__, __pkgroot__, __title__, __version__
warnings.warn(
"lager._meta is deprecated, use lager.__about__ instead",
DeprecationWarning,
stacklevel=2,
)
__all__ = ("__title__", "__description__", "__pkgroot__", "__version__")
# -*- coding: utf-8 -*-
"""Constants go here!"""
from __future__ import annotations
from typing import Dict
LAGER_PORT = 52437
TORNADO_FMT = "".join(
[
"<level>",
"[{level.name[0]} ",
"{time:YYMMDDTHH:mm:ss} ",
"{name}:{module}:{line}]",
"</level> ",
"{message}",
]
)
LOGURU_DEFAULT_FMT = "".join(
[
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>",
" | ",
"<level>{level: <8}</level>",
" | ",
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan>",
" - ",
"<level>{message}</level>",
]
)
LOG_LEVELS: Dict[str, str] = {
"notset": "NOTSET",
"n": "NOTSET",
"debug": "DEBUG",
"d": "DEBUG",
"info": "INFO",
"i": "INFO",
"s": "SUCCESS",
"success": "SUCCESS",
"warning": "WARNING",
"warn": "WARNING",
"w": "WARNING",
"error": "ERROR",
"e": "ERROR",
"critical": "CRITICAL",
"fatal": "CRITICAL",
"c": "CRITICAL",
# enum/enum-strings
"0": "NOTSET",
"10": "DEBUG",
"20": "INFO",
"25": "SUCCESS",
"30": "WARNING",
"40": "ERROR",
"50": "CRITICAL",
}
# -*- coding: utf-8 -*-
"""Python lager brewed by a loguru"""
from __future__ import annotations
import asyncio
import sys as _sys
from functools import wraps
from time import time
from typing import Any, Callable, Dict, Optional, TypeVar, Union
from loguru import logger as logger
from loguru._handler import Handler
from lager.const import LOG_LEVELS
T = TypeVar("T")
try:
import orjson
def _stringify_new_line(serializable: Any) -> str:
return orjson.dumps(serializable, option=orjson.OPT_APPEND_NEWLINE).decode(
"utf-8"
)
def _stringify_no_new_line(serializable: Any) -> str:
return f"{orjson.dumps(serializable).decode('utf-8')}\n"
_stringify = (
_stringify_new_line
if hasattr(orjson, "OPT_APPEND_NEWLINE")
else _stringify_no_new_line
)
def _serialize_record(text: str, record: Dict[str, Any]) -> str:
exception = record["exception"]
if exception is not None:
exception = {
"type": None if exception.type is None else exception.type.__name__,
"value": exception.value,
"traceback": bool(record["exception"].traceback),
}
serializable = {
"text": text,
"record": {
"elapsed": {
"repr": record["elapsed"],
"seconds": record["elapsed"].total_seconds(),
},
"exception": exception,
"extra": record["extra"],
"file": {
"name": record["file"].name,
"path": record["file"].path,
},
"function": record["function"],
"level": {
"icon": record["level"].icon,
"name": record["level"].name,
"no": record["level"].no,
},
"line": record["line"],
"message": record["message"],
"module": record["module"],
"name": record["name"],
"process": {
"id": record["process"].id,
"name": record["process"].name,
},
"thread": {
"id": record["thread"].id,
"name": record["thread"].name,
},
"time": {
"repr": record["time"],
"timestamp": record["time"].timestamp(),
},
},
}
return _stringify(serializable)
Handler._serialize_record = staticmethod(_serialize_record)
except ModuleNotFoundError:
pass
# lager/logger aliases
log = LOG = logger
lager = LAGER = logger
ln = LN = logger # ln => natural log
def loglevel(level: Union[str, int]) -> str:
"""Convert log-level abrev to a valid loguru log level"""
return str(LOG_LEVELS[str(level).strip("'").strip('"').lower()])
def flog(
funk: Optional[Callable[..., T]] = None,
level: Union[str, int] = "debug",
enter: bool = True,
exit: bool = True,
) -> T:
"""Log function (sync/async) enter and exit using this decorator
Args:
funk (Callable): Function to decorate
level (Union[int, str]): Log level
enter (bool): Log function entry if True
exit (bool): Log function exit if False
Returns:
A wrapped function that now has logging!
Usage:
# SYNC
@flog
def add(a, b):
return a + b
add(1, 4)
# ASYNC
@flog
async def add_async(a, b):
return a + b
import asyncio
asyncio.run(add_async(1, 4))
"""
def _flog(funk: Callable[..., T]) -> Callable[..., T]:
name = funk.__name__
@wraps(funk)
def _flog_decorator(*args: Any, **kwargs: Any) -> T:
logger_ = logger.opt(depth=1)
if enter:
logger_.log(
loglevel(level),
"FLOG-ENTER > '{}' (args={}, kwargs={})",
name,
args,
kwargs,
)
ti = time()
result = funk(*args, **kwargs)
tf = time()
if exit:
logger_.log(
loglevel(level),
"FLOG-EXIT < '{}' (return={}, dt_sec={})",
name,
result,
tf - ti,
)
return result
@wraps(funk)
async def _flog_decorator_async(*args: Any, **kwargs: Any) -> T:
logger_ = logger.opt(depth=7)
if enter:
logger_.log(
loglevel(level),
"FLOG-ENTER > '{}' (args={}, kwargs={})",
name,
args,
kwargs,
)
ti = time()
result: T = await funk(*args, **kwargs) # type: ignore[misc]
tf = time()
if exit:
logger_.log(
loglevel(level),
"FLOG-EXIT < '{}' (return={}, dt_sec={})",
name,
result,
tf - ti,
)
return result
if asyncio.iscoroutinefunction(funk) or asyncio.iscoroutine(funk):
return _flog_decorator_async # type: ignore[return-value]
return _flog_decorator
return _flog(funk) if funk else _flog # type: ignore[return-value]
def handlers() -> Dict[int, Handler]:
"""Return all handlers"""
return logger._core.handlers # type: ignore[no-any-return]
def reset(level: Optional[Union[str, int]] = None) -> None:
logger.remove()
logger.add(_sys.stderr, level=loglevel(level or "debug"))
__hoisted__ = (
"_change_activation",
"_core",
"_dynamic_level",
"_find_iter",
"_log",
"_options",
"add",
"bind",
"catch",
"complete",
"configure",
"contextualize",
"critical",
"debug",
"disable",
"enable",
"error",
"exception",
"info",
"level",
"opt",
"parse",
"patch",
"remove",
"start",
"stop",
"success",
"trace",
"warning",
)
_change_activation = LAGER._change_activation
_core = LAGER._core
_find_iter = LAGER._find_iter
_log = LAGER._log
_options = LAGER._options
add = LAGER.add
bind = LAGER.bind
catch = LAGER.catch
complete = LAGER.complete
configure = LAGER.configure
contextualize = LAGER.contextualize
critical = LAGER.critical
debug = LAGER.debug
disable = LAGER.disable
enable = LAGER.enable
error = LAGER.error
exception = LAGER.exception
info = LAGER.info
level = LAGER.level
opt = LAGER.opt
parse = LAGER.parse
patch = LAGER.patch
remove = LAGER.remove
start = LAGER.start
stop = LAGER.stop
success = LAGER.success
trace = LAGER.trace
warning = LAGER.warning
__all__ = (
"LAGER",
"LOG",
"_change_activation",
"_core",
"_find_iter",
"_log",
"_options",
"add",
"bind",
"catch",
"complete",
"configure",
"contextualize",
"critical",
"debug",
"disable",
"enable",
"error",
"exception",
"flog",
"handlers",
"info",
"lager",
"level",
"ln",
"log",
"logger",
"loglevel",
"opt",
"parse",
"patch",
"remove",
"start",
"stop",
"success",
"trace",
"warning",
)
# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import Any
try:
import httpx
_SINKS = []
class HttpxSink:
def __init__(self, url: str, *args: Any, **kwargs: Any) -> None:
self.url = url
self.client = httpx.AsyncClient(*args, **kwargs)
_SINKS.append(self)
async def __call__(self, msg: Any) -> None:
httpx.post(self.url, data={"msg": msg})
async def handle(self, message: Any) -> None:
await self.client.post(url=self.url, data={"msg": message})
async def await_delete_channels(self) -> None:
await self.client.aclose()
except ImportError:
pass
# -*- coding: utf-8 -*-
"""FastAPI logging"""
from __future__ import annotations
from typing import List, Optional, Set, Tuple, Union
from lager.logging import intercept
__all__ = ("FASTAPI_LOGGERS", "fastapi_intercept")
FASTAPI_LOGGERS = [
"gunicorn",
"gunicorn.errors",
"uvicorn",
"uvicorn.error",
"fastapi",
"sqlalchemy",
]
def fastapi_intercept(
loggers: Optional[Union[List[str], Set[str], Tuple[str, ...]]] = None
) -> None:
_loggers2intercept = (
FASTAPI_LOGGERS if not loggers else sorted(set(*(*FASTAPI_LOGGERS, loggers)))
)
intercept(_loggers2intercept)
# -*- coding: utf-8 -*-
"""Lager/Loguru + std logging"""
from __future__ import annotations
import logging
from logging import (
BASIC_FORMAT as BASIC_FORMAT,
CRITICAL as CRITICAL,
DEBUG as DEBUG,
ERROR as ERROR,
FATAL as FATAL,
INFO as INFO,
NOTSET as NOTSET,
WARN as WARN,
WARNING as WARNING,
BufferingFormatter as BufferingFormatter,
FileHandler as FileHandler,
Filter as Filter,
Formatter as Formatter,
Handler as _Handler,
Logger as _Logger,
LoggerAdapter as LoggerAdapter,
LogRecord as LogRecord,
NullHandler as NullHandler,
StreamHandler as StreamHandler,
addLevelName as addLevelName,
addLevelName as add_level_name,
basicConfig as basicConfig,
basicConfig as basic_config,
captureWarnings as captureWarnings,
captureWarnings as capture_warnings,
critical as critical,
debug as debug,
disable as disable,
error as error,
exception as exception,
fatal as fatal,
getLevelName as getLevelName,
getLevelName as get_level_name,
getLogger as getLogger,
getLogger as get_logger,
getLoggerClass as getLoggerClass,
getLoggerClass as get_logger_class,
getLogRecordFactory as getLogRecordFactory,
getLogRecordFactory as get_log_record_factory,
info as info,
lastResort as lastResort,
lastResort as last_resort,
log as log,
makeLogRecord as makeLogRecord,
makeLogRecord as make_log_record,
raiseExceptions as raiseExceptions,
raiseExceptions as raise_exceptions,
setLoggerClass as setLoggerClass,
setLoggerClass as set_logger_class,
setLogRecordFactory as setLogRecordFactory,
setLogRecordFactory as set_log_record_factory,
shutdown as shutdown,
warn as warn,
warning as warning,
)
from types import TracebackType
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, Union
from typing_extensions import Literal, Self, TypeAlias
from lager.core import LOG, loglevel
__all__ = (
"BASIC_FORMAT",
"BufferingFormatter",
"CRITICAL",
"DEBUG",
"ERROR",
"FATAL",
"FileHandler",
"Filter",
"Formatter",
"Handler",
"INFO",
"LogRecord",
"Logger",
"LoggerAdapter",
"NOTSET",
"NullHandler",
"StreamHandler",
"WARN",
"WARNING",
"addLevelName",
"add_level_name",
"basicConfig",
"basic_config",
"captureWarnings",
"capture_warnings",
"critical",
"debug",
"disable",
"error",
"exception",
"fatal",
"getLevelName",
"getLogRecordFactory",
"getLogger",
"getLoggerClass",
"get_level_name",
"get_log_record_factory",
"get_logger",
"get_logger_class",
"info",
"lastResort",
"last_resort",
"log",
"makeLogRecord",
"make_log_record",
"raiseExceptions",
"raise_exceptions",
"setLogRecordFactory",
"setLoggerClass",
"set_log_record_factory",
"set_logger_class",
"shutdown",
"warn",
"warning",
# lager.logging members
"__aliases__",
"StdLoggingHandler",
"loggers_dict",
"intercept",
"intercept_all",
"patch_logging",
)
# logging snake_case aliases bc I cannot stand camelCase
__aliases__ = {
"add_level_name": "addLevelName",
"basic_config": "basicConfig",
"capture_warnings": "captureWarnings",
"get_level_name": "getLevelName",
"get_log_record_factory": "getLogRecordFactory",
"get_logger": "getLogger",
"get_logger_class": "getLoggerClass",
"last_resort": "lastResort",
"make_log_record": "makeLogRecord",
"raise_exceptions": "raiseExceptions",
"set_log_record_factory": "setLogRecordFactory",
"set_logger_class": "setLoggerClass",
}
_SysExcInfoType: TypeAlias = Union[
Tuple[Type[BaseException], BaseException, Union[TracebackType, None]],
Tuple[None, None, None],
]
_ExcInfoType: TypeAlias = Union[None, bool, _SysExcInfoType, BaseException]
_ArgsType: TypeAlias = Union[Tuple[object, ...], Mapping[str, object]]
_FilterType: TypeAlias = Union[Filter, Callable[[LogRecord], bool]]
_Level: TypeAlias = Union[int, str]
_FormatStyle: TypeAlias = Literal["%", "{", "$"]
class Logger(_Logger):
def __init__(self, name: str, level: _Level = 0) -> None:
super().__init__(name, level)
def set_level(self, level: _Level) -> None:
"""snake_case alias for setLevel"""
self.setLevel(level)
def is_enabled_for(self, level: int) -> bool:
"""snake_case alias for isEnabledFor"""
return self.isEnabledFor(level)
def get_effective_level(self) -> int:
"""snake_case alias for getEffectiveLevel"""
return self.getEffectiveLevel()
def get_child(self, suffix: str) -> Self:
"""snake_case alias for getChild"""
return self.getChild(suffix)
def find_caller(
self, stack_info: bool = False, stacklevel: int = 1
) -> Tuple[str, int, str, Union[str, None]]:
"""snake_case alias for findCaller"""
return self.findCaller(stack_info, stacklevel)
def add_handler(self, hdlr: Handler) -> None:
"""snake_case alias for addHandler"""
return self.addHandler(hdlr)
def remove_handler(self, hdlr: Handler) -> None:
"""snake_case alias for removeHandler"""
return self.removeHandler(hdlr)
def make_record(
self,
name: str,
level: int,
fn: str,
lno: int,
msg: object,
args: _ArgsType,
exc_info: Union[_SysExcInfoType, None],
func: Optional[str] = None,
extra: Union[Mapping[str, object], None] = None,
sinfo: Optional[str] = None,
) -> LogRecord:
return self.makeRecord(
name, level, fn, lno, msg, args, exc_info, func, extra, sinfo
)
def has_handlers(self) -> bool:
"""snake_case alias for hasHandlers"""
return self.hasHandlers()
def call_handlers(self, record: LogRecord) -> None:
"""snake_case alias for callHandlers"""
return self.callHandlers(record)
class Handler(_Handler):
def __init__(self, level: _Level = 0) -> None:
super().__init__(level)
def create_lock(self) -> None:
"""snake_case alias for createLock"""
return self.createLock()
def set_level(self, level: _Level) -> None:
"""snake_case alias for setLevel"""
return self.setLevel(level)
def set_formatter(self, fmt: Union[Formatter, None]) -> None:
"""snake_case alias for setFormatter"""
return self.setFormatter(fmt)
def handle_error(self, record: LogRecord) -> None:
"""snake_case alias for handleError"""
return self.handleError(record)
# =====================================================================================
def patch_logging() -> None:
for k, v in __aliases__.items():
setattr(logging, k, getattr(logging, v))
class StdLoggingHandler(logging.Handler):
"""Logging intercept handler"""
def emit(self, record: logging.LogRecord) -> None:
# Get corresponding Loguru level if it exists
try:
level = LOG.level(record.levelname).name
except AttributeError:
level = loglevel(record.levelno)
# Find caller from where originated the logging call
frame = logging.currentframe()
depth = 2
while (
frame.f_code.co_filename # pyright: ignore[reportOptionalMemberAccess]
== logging.__file__
):
frame = frame.f_back # type: ignore[assignment]
depth += 1
LOG.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
def _logger_dict() -> Any:
return logging.root.manager.loggerDict
def loggers_dict() -> Dict[str, logging.Logger]:
return {name: logging.getLogger(name) for name in _logger_dict()}
def intercept(loggers: List[str]) -> None:
for logger in loggers:
std_logger = logging.getLogger(logger)
std_logger.handlers = [StdLoggingHandler()]
def intercept_all() -> None:
intercept(list(loggers_dict().keys()))
# -*- coding: utf-8 -*-
"""Lager & pydantic"""
from __future__ import annotations
from datetime import datetime, timedelta
from types import TracebackType
from typing import Any, Dict, Optional, Type, Union
from jsonbourne.pydantic import JsonBaseModel
class RecordFile(JsonBaseModel):
name: str
path: str
class RecordLevel(JsonBaseModel):
name: str
no: int
icon: str
class RecordThread(JsonBaseModel):
id: int
name: str
class RecordProcess(JsonBaseModel):
id: int
name: str
class RecordException(JsonBaseModel):
type: Optional[Type[BaseException]]
value: Optional[BaseException]
traceback: Optional[TracebackType]
class Record(JsonBaseModel):
elapsed: timedelta
exception: Optional[RecordException]
extra: Dict[Any, Any]
file: RecordFile
function: str
level: RecordLevel
line: int
message: str
module: str
name: Union[str, None]
process: RecordProcess
thread: RecordThread
time: datetime
class Message(JsonBaseModel):
record: Record