Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ jobs:
- run: python -m build
- run: twine check dist/*
- name: Check the wheel ships the py.typed marker
run: unzip -l dist/*.whl | grep -q "pylogkit/py.typed"
run: unzip -l dist/*.whl | grep -q "ctxlogkit/py.typed"
- name: Install the wheel into a clean environment and import it
run: |
python -m venv "$RUNNER_TEMP/wheel-venv"
"$RUNNER_TEMP/wheel-venv/bin/pip" install dist/*.whl
cd "$RUNNER_TEMP"
"$RUNNER_TEMP/wheel-venv/bin/python" -c "import pylogkit; print(pylogkit.__all__)"
"$RUNNER_TEMP/wheel-venv/bin/python" -c "import ctxlogkit; print(ctxlogkit.__all__)"
- uses: actions/upload-artifact@v4
with:
name: dist
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
python-version: "3.12"
- name: Check that the tag matches the package version
run: |
version=$(python -c "import re, pathlib; print(re.search(r'__version__ = \"(.+?)\"', pathlib.Path('pylogkit/_version.py').read_text()).group(1))")
version=$(python -c "import re, pathlib; print(re.search(r'__version__ = \"(.+?)\"', pathlib.Path('ctxlogkit/_version.py').read_text()).group(1))")
if [ "v$version" != "$GITHUB_REF_NAME" ]; then
echo "Tag $GITHUB_REF_NAME does not match version $version" >&2
exit 1
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## Unreleased

### Changed

- **Breaking:** the project is renamed from `pylogkit` to `ctxlogkit`, for both the distribution
and the import package. The old name on PyPI belongs to an unrelated project. Replace
`from pylogkit import ...` with `from ctxlogkit import ...` (#44).

### Added

- `setup_logging()` accepts `syslog_address` (a `(host, port)` tuple or a Unix
Expand Down
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pylogkit
# ctxlogkit

[![CI](https://github.com/markbac/py-logkit/actions/workflows/ci.yml/badge.svg)](https://github.com/markbac/py-logkit/actions/workflows/ci.yml)

Expand All @@ -9,7 +9,7 @@ carrying the same request context (user, session, request, host, environment and
It builds on the standard `logging` module and [`colorlog`](https://pypi.org/project/colorlog/),
so everything it returns is an ordinary `logging.Logger`.

## Why pylogkit
## Why ctxlogkit

- **One call, sensible defaults.** Console, file, JSON and syslog handlers with their own
levels, formats and rotation, configured by keyword arguments.
Expand All @@ -24,12 +24,13 @@ so everything it returns is an ordinary `logging.Logger`.

Requires Python 3.10 or later. The only mandatory dependency is `colorlog`.

> **Warning:** The name `pylogkit` on PyPI belongs to a different, unrelated project, so
> `pip install pylogkit` does **not** install this library. Install it from this repository
> instead (see the naming discussion in [#44](https://github.com/markbac/py-logkit/issues/44)).
> **Note:** This project was previously called `pylogkit`. That name on PyPI belongs to a
> different, unrelated project, so the distribution and the import package are now `ctxlogkit`
> (see [#44](https://github.com/markbac/py-logkit/issues/44)). It is not on PyPI yet, so install it
> from this repository:

```bash
pip install "pylogkit[all] @ git+https://github.com/markbac/py-logkit"
pip install "ctxlogkit[all] @ git+https://github.com/markbac/py-logkit"
```

or from a clone:
Expand All @@ -44,7 +45,7 @@ pip install ".[all]" # everything optional
## Quick start

```python
from pylogkit import ContextualLoggerAdapter, set_log_context, setup_logging
from ctxlogkit import ContextualLoggerAdapter, set_log_context, setup_logging

logger = setup_logging(
name="shop.checkout",
Expand Down Expand Up @@ -109,7 +110,7 @@ exit, even when the block raises. The context is held in a `contextvars.ContextV
`asyncio` tasks and threads do not see each other's values:

```python
from pylogkit import log_context
from ctxlogkit import log_context

with log_context(request_id="abc"):
log.info("handled") # carries request_id=abc
Expand Down Expand Up @@ -168,7 +169,7 @@ Pass `redact_keys` to mask the value of any field whose name contains one of the
syslog, and are off unless you ask for them.

```python
from pylogkit import DEFAULT_REDACT_KEYS, setup_logging
from ctxlogkit import DEFAULT_REDACT_KEYS, setup_logging

logger = setup_logging(
"app",
Expand All @@ -192,7 +193,7 @@ only puts the record on an in-memory queue, and a background thread owns the rea
writes it. The context fields are captured on the calling thread, so they are still correct.

```python
from pylogkit import setup_logging, shutdown_logging
from ctxlogkit import setup_logging, shutdown_logging

logger = setup_logging("app", to_file=True, use_queue=True)
logger.info("returns without waiting for the file")
Expand Down Expand Up @@ -234,7 +235,7 @@ Each JSON record starts with `timestamp` (ISO 8601 with UTC offset), `level`, `l
## Helpers

```python
from pylogkit import log_duration, log_exception, tqdm_logging
from ctxlogkit import log_duration, log_exception, tqdm_logging

@log_duration(logger, level="debug")
def import_orders():
Expand Down
10 changes: 5 additions & 5 deletions pylogkit/__init__.py → ctxlogkit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""pylogkit: a structured and colourised logging toolkit for Python applications.
"""ctxlogkit: a structured and colourised logging toolkit for Python applications.

The public API lives in :mod:`pylogkit.log_setup` and is re-exported here, so
The public API lives in :mod:`ctxlogkit.log_setup` and is re-exported here, so
applications can write::

from pylogkit import setup_logging, ContextualLoggerAdapter
from ctxlogkit import setup_logging, ContextualLoggerAdapter

logger = ContextualLoggerAdapter(setup_logging(name=__name__, level="DEBUG"))
logger.info("Hello, logging!")
"""

from pylogkit._version import __version__
from pylogkit.log_setup import (
from ctxlogkit._version import __version__
from ctxlogkit.log_setup import (
DEFAULT_REDACT_KEYS,
ContextualLoggerAdapter,
RedactionFilter,
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions pylogkit/log_setup.py → ctxlogkit/log_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

Example::

from pylogkit import ContextualLoggerAdapter, setup_logging
from ctxlogkit import ContextualLoggerAdapter, setup_logging

logger = setup_logging(name=__name__, to_file=True, file_path="app.log", level="DEBUG")
logger = ContextualLoggerAdapter(logger)
Expand Down Expand Up @@ -123,7 +123,7 @@
except ImportError:
tqdm = None

_log_context: ContextVar[dict[str, Any] | None] = ContextVar("pylogkit_log_context", default=None)
_log_context: ContextVar[dict[str, Any] | None] = ContextVar("ctxlogkit_log_context", default=None)


def set_log_context(**kwargs: Any) -> None:
Expand Down
File renamed without changes.
14 changes: 7 additions & 7 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Architecture

This document explains how `pylogkit` is put together and why. It is written for contributors and for
This document explains how `ctxlogkit` is put together and why. It is written for contributors and for
users who want to know exactly what happens between a logging call and the bytes on disk. For usage,
see the [README](../README.md).

## Overview

`pylogkit` is a thin layer over the standard library's `logging` package. It does not replace
`ctxlogkit` is a thin layer over the standard library's `logging` package. It does not replace
loggers, levels or handlers. It configures them consistently and adds three things: context fields on
every record, a stable set of output formats, and a few opt-in behaviours (queueing, redaction and
environment configuration).

The whole implementation is one module, `pylogkit/log_setup.py`, re-exported from the package root.
The whole implementation is one module, `ctxlogkit/log_setup.py`, re-exported from the package root.

```mermaid
flowchart TB
subgraph pkg["pylogkit package"]
subgraph pkg["ctxlogkit package"]
init["__init__.py<br/>public API"]
ver["_version.py<br/>single-sourced version"]
core["log_setup.py<br/>implementation"]
Expand All @@ -32,7 +32,7 @@ flowchart TB

## Public API

Everything in `pylogkit.__all__` is public. The rest of `log_setup.py` is private, marked by a leading
Everything in `ctxlogkit.__all__` is public. The rest of `log_setup.py` is private, marked by a leading
underscore.

| Area | Names |
Expand Down Expand Up @@ -171,7 +171,7 @@ handler. Standard `LogRecord` attributes are never masked. Tracebacks are not se
## Optional dependencies

`colorlog` is required. `python-json-logger` and `tqdm` are optional extras, imported inside
`try`/`except ImportError` blocks so that importing `pylogkit` never fails because of them. Asking for
`try`/`except ImportError` blocks so that importing `ctxlogkit` never fails because of them. Asking for
JSON output without the library raises an `ImportError` that names the install command.

`python-json-logger` changed between versions, and `_build_json_formatter()` hides the differences.
Expand All @@ -192,7 +192,7 @@ CI runs the tests against the latest release, against 2.0.7, and with neither ex
- `from __future__ import annotations` keeps annotations lazy so the code runs on Python 3.10 and later.
`LoggerAdapter` is only subscriptable at runtime from Python 3.11, so a type-checking-only alias
is used as the adapter's base class.
- The version is defined once, in `pylogkit/_version.py`, and `pyproject.toml` reads it without
- The version is defined once, in `ctxlogkit/_version.py`, and `pyproject.toml` reads it without
importing the package. See [releasing](releasing.md).

## Testing
Expand Down
8 changes: 4 additions & 4 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Releasing

Releases follow [Semantic Versioning](https://semver.org/). The version lives in one place,
`pylogkit/_version.py`, and the build reads it from there.
`ctxlogkit/_version.py`, and the build reads it from there.

## Cutting a release

1. Move the entries under `## Unreleased` in `CHANGELOG.md` into a new `## X.Y.Z - YYYY-MM-DD`
heading, and leave an empty `## Unreleased` section above it.
1. Set `__version__` in `pylogkit/_version.py` to `X.Y.Z`.
1. Set `__version__` in `ctxlogkit/_version.py` to `X.Y.Z`.
1. Open a pull request with those two changes and merge it once CI is green.
1. Tag the merge commit and push the tag:

Expand All @@ -22,8 +22,8 @@ whose notes are generated from the merged pull requests.

## Publishing to PyPI

The workflow does not publish to PyPI yet. The name `pylogkit` is already used there by an
unrelated project (see issue #44), so a distribution name has to be chosen first. Once it is,
The workflow does not publish to PyPI yet. The name `ctxlogkit` was checked and was free when the project
was renamed from `pylogkit` (issue #44). To publish, reserve it on PyPI and then
add a publish job that uses
[trusted publishing](https://docs.pypi.org/trusted-publishers/) with the `id-token: write`
permission, so that no API token is stored in the repository.
14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"

[project]
name = "pylogkit"
name = "ctxlogkit"
dynamic = ["version"]
description = "A structured and colourised logging toolkit for Python applications"
readme = "README.md"
Expand All @@ -30,7 +30,7 @@ dependencies = ["colorlog>=6.0.0"]
[project.optional-dependencies]
json = ["python-json-logger>=2.0.7"]
progress = ["tqdm"]
all = ["pylogkit[json,progress]"]
all = ["ctxlogkit[json,progress]"]
dev = ["pytest>=7", "pytest-cov", "ruff", "mypy", "build"]

[project.urls]
Expand All @@ -39,13 +39,13 @@ Issues = "https://github.com/markbac/py-logkit/issues"
Changelog = "https://github.com/markbac/py-logkit/blob/main/CHANGELOG.md"

[tool.setuptools.dynamic]
version = { attr = "pylogkit._version.__version__" }
version = { attr = "ctxlogkit._version.__version__" }

[tool.setuptools.package-data]
pylogkit = ["py.typed"]
ctxlogkit = ["py.typed"]

[tool.setuptools.packages.find]
include = ["pylogkit*"]
include = ["ctxlogkit*"]

[tool.pytest.ini_options]
testpaths = ["tests"]
Expand All @@ -55,7 +55,7 @@ markers = [
]

[tool.coverage.run]
source = ["pylogkit"]
source = ["ctxlogkit"]
branch = true

[tool.coverage.report]
Expand Down Expand Up @@ -83,7 +83,7 @@ convention = "google"
[tool.mypy]
strict = true
python_version = "3.10"
files = ["pylogkit"]
files = ["ctxlogkit"]
warn_unused_ignores = true
warn_redundant_casts = true

Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from pylogkit import clear_log_context
from ctxlogkit import clear_log_context

_HAS_JSON_LOGGER = find_spec("pythonjsonlogger") is not None
_HAS_TQDM = find_spec("tqdm") is not None
Expand Down
2 changes: 1 addition & 1 deletion tests/test_child_loggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from pylogkit import set_log_context, setup_logging
from ctxlogkit import set_log_context, setup_logging


def test_child_logger_records_are_formatted(capsys):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_console_colour.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from pylogkit import setup_logging
from ctxlogkit import setup_logging


class FakeTTY(io.StringIO):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_contextual_adapter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for :class:`pylogkit.log_setup.ContextualLoggerAdapter`."""
"""Tests for :class:`ctxlogkit.log_setup.ContextualLoggerAdapter`."""

from pylogkit import (
from ctxlogkit import (
ContextualLoggerAdapter,
get_log_context,
set_log_context,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_demo.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Smoke test for the example in ``python -m pylogkit.log_setup``."""
"""Smoke test for the example in ``python -m ctxlogkit.log_setup``."""

import runpy
import time

import pytest

from pylogkit import log_setup
from ctxlogkit import log_setup

pytestmark = pytest.mark.json_logger

Expand Down
8 changes: 4 additions & 4 deletions tests/test_dependencies.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests that declared dependencies are real, installable distributions.

These tests read the metadata of the installed ``pylogkit`` distribution, so
These tests read the metadata of the installed ``ctxlogkit`` distribution, so
run them in an environment created with ``pip install -e ".[all,dev]"``.
"""

Expand All @@ -14,20 +14,20 @@

def _requirement_names():
"""Return the distribution names of every declared requirement."""
requires = importlib.metadata.requires("pylogkit") or []
requires = importlib.metadata.requires("ctxlogkit") or []
return {NAME.match(requirement).group(0) for requirement in requires}


def test_core_dependency_is_colorlog():
"""The only mandatory dependency is ``colorlog`` (not the misspelt name)."""
core = [r for r in importlib.metadata.requires("pylogkit") if "extra ==" not in r]
core = [r for r in importlib.metadata.requires("ctxlogkit") if "extra ==" not in r]

assert [NAME.match(r).group(0) for r in core] == ["colorlog"]


def test_optional_features_are_extras():
"""JSON output and progress bars are optional extras."""
extras = set(importlib.metadata.metadata("pylogkit").get_all("Provides-Extra"))
extras = set(importlib.metadata.metadata("ctxlogkit").get_all("Provides-Extra"))

assert {"json", "progress", "all", "dev"} <= extras

Expand Down
4 changes: 2 additions & 2 deletions tests/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ def test_mermaid_diagrams_are_present_and_avoid_semicolons():

def test_architecture_names_the_public_api():
"""Every exported name is mentioned, so new exports force a documentation update."""
import pylogkit
import ctxlogkit

text = ARCHITECTURE.read_text(encoding="utf-8")

missing = [name for name in pylogkit.__all__ if name not in text]
missing = [name for name in ctxlogkit.__all__ if name not in text]
assert not missing, missing


Expand Down
2 changes: 1 addition & 1 deletion tests/test_emoji.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging

from pylogkit import log_setup, setup_logging
from ctxlogkit import log_setup, setup_logging

STANDARD_LEVELS = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}

Expand Down
Loading
Loading