Skip to content

Commit

Permalink
Blacken code... add in modifyChildLoggersFunc
Browse files Browse the repository at this point in the history
  • Loading branch information
csm10495 committed Dec 22, 2022
1 parent 8a94334 commit 16e50c8
Show file tree
Hide file tree
Showing 12 changed files with 766 additions and 342 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/create_github_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Create GitHub release
uses: Roang-zero1/github-create-release-action@master
uses: Roang-zero1/github-create-release-action@v3
with:
version_regex: ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+
env:
Expand All @@ -54,7 +54,7 @@ jobs:
- name: Build and publish
working-directory: ${{ github.workspace }}
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ Internally the gspread module is used for Google Sheets communication/authentica

Note that logs may be delayed due to rate limiting, etc. If you are logging *a lot*, it may not be a good idea to enable this feature.


## Customized Child Loggers
`setup()` has an optional parameter: `modifyChildLoggersFunc`. If it is given, it must be a function that would take in each created child logger
and return a child logger. This can be used to add additional things like handlers to each child logger.

## Installation
```
pip install csmlog
Expand Down
20 changes: 11 additions & 9 deletions build_and_upload.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
'''
"""
Brief:
Quick script to build this and upload it to pypi.
Author(s):
Charles Machalow
'''
"""

import os
import shutil
import subprocess
import sys

THIS_FOLDER = os.path.realpath(os.path.dirname(__file__))
DIST_FOLDER = os.path.join(THIS_FOLDER, 'dist')
DIST_FOLDER = os.path.join(THIS_FOLDER, "dist")


def caller(c):
print ('Calling: %s' % c)
print("Calling: %s" % c)
subprocess.call(c, shell=True)


# delete dist folder
try:
shutil.rmtree(DIST_FOLDER)
except:
pass # doesn't exist
pass # doesn't exist


caller('\"%s\" -m pip install twine' % sys.executable)
caller('"%s" -m pip install twine' % sys.executable)

os.chdir(THIS_FOLDER)
caller('\"%s\" setup.py sdist' % sys.executable)
caller('"%s" setup.py sdist' % sys.executable)

for file in os.listdir(DIST_FOLDER):
if file.upper().endswith('.TAR.GZ'):
if file.upper().endswith(".TAR.GZ"):
break

caller('\"%s\" -m twine upload \"dist/%s\" -r pypi' % (sys.executable, file))
caller('"%s" -m twine upload "dist/%s" -r pypi' % (sys.executable, file))
109 changes: 76 additions & 33 deletions csmlog/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'''
"""
This file is part of csmlog. Python logger setup... the way I like it.
MIT License (2021) - Charles Machalow
'''
"""

import logging
import logging.handlers
Expand All @@ -16,18 +16,33 @@
from csmlog.udp_handler import UdpHandler
from csmlog.udp_handler_receiver import UdpHandlerReceiver

__version__ = '0.25.0'
__version__ = "0.26.0"

DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s"

DEFAULT_LOG_FORMAT = '%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s'

class CSMLogger(object):
'''
"""
object to wrap logging logic
'''
def __init__(self, appName, clearLogs=False, udpLogging=True, googleSheetShareEmail=None, formatter=None):
"""

def __init__(
self,
appName,
clearLogs=False,
udpLogging=True,
googleSheetShareEmail=None,
formatter=None,
modifyChildLoggersFunc=None,
):
self.appName = appName
self.udpLogging = udpLogging
self.googleSheetShareEmail = googleSheetShareEmail

# A function to call when creating a child logger. This is useful for adding in
# things like handlers to all child loggers.
self.modifyChildLoggersFunc = modifyChildLoggersFunc

self._loggers = []

# sets self._formatter
Expand All @@ -38,7 +53,7 @@ def __init__(self, appName, clearLogs=False, udpLogging=True, googleSheetShareEm

self.parentLogger = self.__getParentLogger()
self.consoleLoggingStream = None
self._loggers = [self.parentLogger] # keep track of all loggers
self._loggers = [self.parentLogger] # keep track of all loggers

def close(self):
for logger in self._loggers:
Expand All @@ -50,10 +65,17 @@ def close(self):

def getLogger(self, name):
name = os.path.basename(name)
loggerName = '%s.%s' % (self.appName, name) # make this a sublogger of the whole app
loggerName = "%s.%s" % (
self.appName,
name,
) # make this a sublogger of the whole app
logger = self.__getLoggerWithName(loggerName)
self._loggers.append(logger)
logger.sysCall = LoggedSystemCall(logger)

if self.modifyChildLoggersFunc:
logger = self.modifyChildLoggersFunc(logger)

return logger

def __getParentLogger(self):
Expand All @@ -72,25 +94,27 @@ def __getParentLogger(self):

def __getLoggerWithName(self, loggerName):
logger = logging.getLogger(loggerName)
logger.setLevel(1) # log all
logger.setLevel(1) # log all

logFolder = self.getDefaultSaveDirectory()

logFile = os.path.join(logFolder, loggerName + ".txt")

formatter = self.getFormatter()

class RotatingFileHandlerThatWillKeepWorkingOnPermissionErrorDuringRotate(logging.handlers.RotatingFileHandler):
'''
This class is special, it exists because on Windows, file names can't be changed while files are open.
So ultimately we can't rotate if 2 processes are writing to a given log file.
The default RotatingFileHandler will just throw the PermissionError and stop writing
(since it closed the stream already)
By throwing away the PermissionError (since logging it on every log statement would be too much),
and using the delay=True parameter to RotatingFileHandler, we will just continue writing to the
original file without rotating, via reopening the original file. If the other process closes the file,
it will rotate on the next log statement
'''
class RotatingFileHandlerThatWillKeepWorkingOnPermissionErrorDuringRotate(
logging.handlers.RotatingFileHandler
):
"""
This class is special, it exists because on Windows, file names can't be changed while files are open.
So ultimately we can't rotate if 2 processes are writing to a given log file.
The default RotatingFileHandler will just throw the PermissionError and stop writing
(since it closed the stream already)
By throwing away the PermissionError (since logging it on every log statement would be too much),
and using the delay=True parameter to RotatingFileHandler, we will just continue writing to the
original file without rotating, via reopening the original file. If the other process closes the file,
it will rotate on the next log statement
"""

# todo: probably write test cases for rotation with multiple processes writing to one log file.
# ensure some sort of semi-deterministic behavior
Expand All @@ -100,7 +124,9 @@ def rotate(self, source, dest):
except PermissionError:
pass

rfh = RotatingFileHandlerThatWillKeepWorkingOnPermissionErrorDuringRotate(logFile, maxBytes=1024*1024*8, backupCount=10, delay=True)
rfh = RotatingFileHandlerThatWillKeepWorkingOnPermissionErrorDuringRotate(
logFile, maxBytes=1024 * 1024 * 8, backupCount=10, delay=True
)
rfh.setFormatter(formatter)
logger.addHandler(rfh)

Expand Down Expand Up @@ -156,17 +182,17 @@ def setFormatter(self, formatter=None):

@classmethod
def getDefaultSaveDirectoryWithName(cls, appName):
if os.name == 'nt':
if os.name == "nt":
logFolder = os.path.join(os.path.expandvars("%APPDATA%"), appName)
else:
tmpPath = Path(f'/var/log/{uuid.uuid4()}')
tmpPath = Path(f"/var/log/{uuid.uuid4()}")
try:
tmpPath.touch()
tmpPath.unlink()
tmpPath = tmpPath.parent
except PermissionError:
# can't use /var/log... try using ~/log/
tmpPath = Path.home() / 'log'
tmpPath = Path.home() / "log"
tmpPath.mkdir(exist_ok=True)

logFolder = tmpPath / appName
Expand All @@ -182,8 +208,9 @@ def clearLogs(self):
# recreate empty folder
self.getDefaultSaveDirectory()


class _CSMLoggerManager:
''' manages the active instance (and older instances) of CSMLogger '''
"""manages the active instance (and older instances) of CSMLogger"""

def __init__(self):
# loggers that are no longer default (setup() was called again, though may still be in use)
Expand All @@ -194,7 +221,7 @@ def __init__(self):

# publish methods from this guy
for name in dir(self):
if name.startswith('_') or name.endswith('_'):
if name.startswith("_") or name.endswith("_"):
continue

globals()[name] = getattr(self, name)
Expand All @@ -206,7 +233,7 @@ def getLogger(self, *args, **kwargs):
return self._activeCsmLogger.getLogger(*args, **kwargs)

def close(self):
''' will close ALL known CSMLoggers, including active and old '''
"""will close ALL known CSMLoggers, including active and old"""
if not self._activeCsmLogger:
raise RuntimeError("(csmlog) setup() must be called first!")

Expand Down Expand Up @@ -241,18 +268,34 @@ def setFormatter(self, formatter=None):
for logger in self._oldCsmLoggers:
logger.setFormatter(formatter)

def setup(self, appName, clearLogs=False, udpLogging=True, googleSheetShareEmail=None):
''' must be called to setup the logger. Passes args to CSMLogger's constructor '''
def setup(
self,
appName,
clearLogs=False,
udpLogging=True,
googleSheetShareEmail=None,
modifyChildLoggersFunc=None,
):
"""must be called to setup the logger. Passes args to CSMLogger's constructor"""

if self._activeCsmLogger is not None:
self._activeCsmLogger.parentLogger.debug("CSMLogger was already setup. Swapping to appName: %s." % appName)
self._activeCsmLogger.parentLogger.debug(
"CSMLogger was already setup. Swapping to appName: %s." % appName
)
self._oldCsmLoggers.append(self._activeCsmLogger)

self._activeCsmLogger = CSMLogger(appName, clearLogs, udpLogging, googleSheetShareEmail)
self._activeCsmLogger = CSMLogger(
appName,
clearLogs,
udpLogging,
googleSheetShareEmail,
modifyChildLoggersFunc,
)
self._activeCsmLogger.parentLogger.debug("==== %s is starting ====" % appName)


# this will also publish all public methods to globals() for this file.
_csmLoggerManager = _CSMLoggerManager()

# legacy alias for setup()
CSMLogger.setup = setup
CSMLogger.setup = setup
Loading

0 comments on commit 16e50c8

Please sign in to comment.