Logchain
Python logging secured by blockchain 📜⛓️
Logs get chained
The current log line contains the signature of the previous line with your secret.
- detect lines deleted / lost
- detect logs tampering
Philosophy
The package is intended to be a lightweight util for generating incorruptible logs.
For this pupose we rely as much as possible on standard packages: few dependencies, high quality.
The formatters are easily extensible by simply deriving from Basic.
Usage
Install
pip install logchain
Choose your log type
Many types of logs are supported out-of-the-box:
Basicraw text, relying on the standard formatterJsonstructured log lines with static & dynamic fields
You can write a custom formatter in 20-ish lines.
Init once in main
from logchain import LogChainer
# Initialize a default chainer.
chainer = LogChainer()
# Register the formatter to the logger.
chainer.initLogging()
Have a look at the comprehensive guide of constructor parameters.
Use everywhere with python logging module
import logging
logging.debug("My message")
logging.info("Some information")
Check your logs integrity afterwards
from logchain import LogChainer
aLogChain = [
"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)
if not result:
print("Last good line", result.prevLine)
print("First bad line", result.line)
else:
print("All right")
Constructor parameters
They are passed as named arguments.
from logchain import LogChainer
chainer = LogChainer(verbosity = 3, secret = "mySignatureKey")
params = {"verbosity": 3, "secret": "mySignatureKey", "timestamp": {"fmt": "%s"}}
chainer = LogChainer(**params)
| Param Type | Default value | Description |
|---|---|---|
| name string | None | Name of the logger instanciated, defaults to the root logger |
| formatterCls class | formatters.Basic | Type of logging to perform, raw text, json, custom |
| format string | see below | Placeholder string used by raw-text loggers |
| secret string | secrets.token_urlsafe(128) | Signature key to compute the line signature |
| seed string | secrets.token_urlsafe() | Random string to sign into the first log line |
| timestamp dict | see below | Group of properties for the timestamp |
| stream stream | stderr | Where the logs are sent, file/console/custom stream |
| verbosity int | 0 | Number [0..5] mapped to a logging.level |
The 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 of timestamp
| Param Type | Default value | Description |
|---|---|---|
| fmt string | "iso" | iso for 8601 or strftime compatible placeholders (ex: "%F %T.%f" |
| precision string | "milliseconds" | timespec element used by the datetime library |
| utc bool | False | Transform the timestamp to its value in UTC |
Logchain extra logging fields
We enrich the standard logging record with some handy string fields:
| Name | Description |
|---|---|
| fileLine | Widespread filename:lineno |
| levelLetters | 4 first letters of logging level names: short and unambiguous |
| signature | The digital signature of the previous line. Include it in all your lines to benefit from the chaining |
| timestamp | Improved version of asctime, see below |
The timestamp field offers more flexibility than asctime in regards to:
- the precision; can go up to the micro seconds (
msecscannot) - the decimal separator; you choose, '.' by default
- utc or local timezone
- customize the format only in one place:
timestamp.fmt
Dynamic logging fields
The 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.py
class App:
def __init__(self, appName, logger):
self.logger = logger
self.logger.setFields(appName = appName)
logging.info("Creating the app")
def handleTransaction(self, userId, callback):
with self.logger.managedFields(uId = userId, trxId = secrets.token_urlsafe(8)):
callback()
def close(self):
logging.info("Closing the app")
Callbacks.py
# The log chain in transparent for the callbacks
def callback1():
logging.warning("Something happened")
def callback2():
logging.info("Serving a resource")
main.py
def main():
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 loggers
from logchain import LogChainer
chainer = LogChainer()
logger = chainer.initLogging(name = __name__)
logger.info("I am special!")
Verbosity to logging.levels
The default mapping is described by the variable VerbosityToLevel as follows:
| Verbosity | Level |
|---|---|
| 0 | ERROR |
| 1 | WARNING |
| 2 | INFO |
| other | DEBUG |
Contributing
Install
The code is hosted on Gitlab 🦊
Simply clone and submit pull requests.
Testing
The unit tests are located in the test folder and discovered by the module unittest.
# Run all
python -m unittest discover -s test
# Get additional options
python -m unittest --help
Releasing
The process is triggered by a tag added to a commit. The tag must match the pattern release_<VERSION>
and VERSION has to comply to semver.
A CI/CD job handles the new tag event and publishes the package to PYPI using the awesome Poetry tool.
Thanks
Icons made by Freepik from www.flaticon.com
Release files for logchain 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| logchain-1.0.0.tar.gz | 8.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| logchain-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:17.0 kB
Release files / logchain-1.0.0.tar.gz
| Download URL | logchain-1.0.0.tar.gz |
|---|---|
| Size | 8.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
41e60ea1100f4988acab9c93f4c2c94715e763755b38d74f404df020d8d48ebf
|
|
BLAKE2b-256 checksum How to use checksums |
ad4b9b2f26fc2a9d9393fc474056f3a37aa8de9981a1f8aba4e6be289b911fc8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/1.1.5 CPython/3.8.8 Linux/4.19.78-coreos
|
Release files / logchain-1.0.0-py3-none-any.whl
| Download URL | logchain-1.0.0-py3-none-any.whl |
|---|---|
| Size | 8.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ba8b531621ef44a81c90f3c3cc394f7a01b153d09f121ab6333e9beef45cb4d7
|
|
BLAKE2b-256 checksum How to use checksums |
8ea1dffc4ae75e373b576b3ed53e3949a6b65623a6c7485a08fd70964568cfb9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/1.1.5 CPython/3.8.8 Linux/4.19.78-coreos
|