Package: logchain
LogchainPython logging secured by blockchain 📜⛓️Logs get chainedThe current log line contains the signature of the previous line with your secret.detect lines deleted / lostdetect logs tamperingPhilosophyThe package is intended to be alightweightutil for generatingincorruptiblelogs.For this pupose we rely as much as possible on standard packages: few dependencies, high quality.The formatters are easilyextensibleby simply deriving fromBasic.UsageInstallpipinstalllogchainChoose your log typeMany types of logs are supported out-of-the-box:Basicraw text, relying on the standard formatterJsonstructured log lines with static & dynamic fieldsYou can write a custom formatter in 20-ish lines.Init once in mainfromlogchainimportLogChainer# Initialize a default chainer.chainer=LogChainer()# Register the formatter to the logger.chainer.initLogging()Have a look atthe comprehensive guide of constructor parameters.Use everywhere with python logging moduleimportlogginglogging.debug("My message")logging.info("Some information")Check your logs integrity afterwardsfromlogchainimportLogChaineraLogChain=["2020-04-25 00:21:36.266 TestChaining.py:33 test_logging_happy_case hello gg |862101dead44e3fb","2020-04-25 00:21:36.266 TestChaining.py:34 test_logging_happy_case voila1 |87f4b398040df0b0","2020-04-25 00:21:36.266 TestChaining.py:35 test_logging_happy_case voila2 |98e33f86376f5858","2020-04-25 00:21:36.267 TestChaining.py:36 test_logging_happy_case voila3 |0757cf00b412a767","2020-04-25 00:21:36.267 TestChaining.py:37 test_logging_happy_case voila4 |95ea9b92cc3e1bc7"]chainer=LogChainer(secret="Et2FwQefvb7HfCb7tATguSicVj_7TVlM")result=chainer.verify(aLogChain)ifnotresult:print("Last good line",result.prevLine)print("First bad line",result.line)else:print("All right")Constructor parametersThey are passed as named arguments.fromlogchainimportLogChainerchainer=LogChainer(verbosity=3,secret="mySignatureKey")params={"verbosity":3,"secret":"mySignatureKey","timestamp":{"fmt":"%s"}}chainer=LogChainer(**params)ParamTypeDefault valueDescriptionnamestringNoneName of the logger instanciated, defaults to the root loggerformatterClsclassformatters.BasicType of logging to perform, raw text, json, customformatstringsee belowPlaceholder string used by raw-text loggerssecretstringsecrets.token_urlsafe(128)Signature key to compute the line signatureseedstringsecrets.token_urlsafe()Random string to sign into the first log linetimestampdictsee belowGroup of properties for the timestampstreamstreamstderrWhere the logs are sent, file/console/custom streamverbosityint0Number [0..5] mapped to a logging.levelThe default format is%(timestamp)s %(levelLetters)s %(fileLine)-15s %(funcName)-15s %(message)-60s |%(signature)s. It relies on some extra fields like the signature at its end.Settings oftimestampParamTypeDefault valueDescriptionfmtstring"iso"iso for 8601 orstrftimecompatible placeholders (ex: "%F %T.%f"precisionstring"milliseconds"timespecelement used bythe datetime libraryutcboolFalseTransform the timestamp to its value in UTCLogchain extra logging fieldsWe enrich the standard logging record with some handy string fields:NameDescriptionfileLineWidespreadfilename:linenolevelLetters4 first letters of logging level names: short and unambiguoussignatureThe digital signature of the previous line. Include it in all your lines to benefit from the chainingtimestampImproved version ofasctime, see belowThetimestampfield offers more flexibility thanasctimein regards to:the precision; can go up to the micro seconds (msecscannot)the decimal separator; you choose, '.' by defaultutc or local timezonecustomize the format only in one place:timestamp.fmtDynamic logging fieldsThe package is suitable for server/app logging which context changes from one transaction to another.
Here is an example of setting contextual information throughout the lifecycle of an app:App.pyclassApp:def__init__(self,appName,logger):self.logger=loggerself.logger.setFields(appName=appName)logging.info("Creating the app")defhandleTransaction(self,userId,callback):withself.logger.managedFields(uId=userId,trxId=secrets.token_urlsafe(8)):callback()defclose(self):logging.info("Closing the app")Callbacks.py# The log chain in transparent for the callbacksdefcallback1():logging.warning("Something happened")defcallback2():logging.info("Serving a resource")main.pydefmain():chainer=logchain.LogChainer(formatterCls=logchain.formatters.Json)chainer.initLogging()app=App("MyApp",chainer)app.handleTransaction("user1",callback1)app.handleTransaction("user1",callback2)app.close()You can either use:setFields: set some permanant fields, remove one by setting it toNone.managedFields: set some temporary fields for the scope of thecontext manager.Using named loggersfromlogchainimportLogChainerchainer=LogChainer()logger=chainer.initLogging(name=__name__)logger.info("I am special!")Verbosity to logging.levelsThe default mapping is described by the variableVerbosityToLevelas follows:VerbosityLevel0ERROR1WARNING2INFOotherDEBUGContributingInstallThe code is hosted on Gitlab 🦊Simply clone and submit pull requests.TestingThe unit tests are located in thetest folderand discovered by the moduleunittest.# Run allpython-munittestdiscover-stest# Get additional optionspython-munittest--helpReleasingThe process is triggered by a tag added to a commit. The tag must match the patternrelease_<VERSION>andVERSIONhas to comply tosemver.A CI/CD jobhandles the new tag event and publishes the package to PYPI using the awesomePoetry tool.ThanksIcons made byFreepikfromwww.flaticon.com