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
Metadata
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 | Provenance | Actions |
|---|---|---|---|---|
| logchain-1.0.0.tar.gz | 8.6 kB | Without provenance | Details for logchain-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| logchain-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for logchain-1.0.0-py3-none-any.whl |
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
|
Security for logchain 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.