Skip to main content

Logchain

Pipeline status Coverage report

Dependencies License Package

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:

  • Basic raw text, relying on the standard formatter
  • Json structured 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 (msecs cannot)
  • 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 to None.
  • managedFields: set some temporary fields for the scope of the context 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)

Source distribution for logchain 1.0.0
File Size Uploaded
logchain-1.0.0.tar.gz 8.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for logchain 1.0.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.1.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page