Metadata-Version: 2.4
Name: sqlite-logbook
Version: 0.1.0
Summary: SQLite-backed logging utilities for Python applications
Author: Wei Zheng
License: MIT License
        
        Copyright (c) 2026 Wei Zheng
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/IshootLaser/sqlite-logbook
Project-URL: Repository, https://github.com/IshootLaser/sqlite-logbook
Project-URL: Issues, https://github.com/IshootLaser/sqlite-logbook/issues
Keywords: logging,sqlite,sqlalchemy
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Logging
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: SQLAlchemy<2.1,>=2.0.36
Requires-Dist: PyMySQL<2.0,>=1.1
Requires-Dist: psycopg2-binary<3.0,>=2.9.10
Dynamic: license-file

# sqlite-logbook

[![CI](https://github.com/IshootLaser/sqlite-logbook/actions/workflows/ci.yml/badge.svg)](https://github.com/IshootLaser/sqlite-logbook/actions/workflows/ci.yml)
[![Publish](https://github.com/IshootLaser/sqlite-logbook/actions/workflows/publish.yml/badge.svg)](https://github.com/IshootLaser/sqlite-logbook/actions/workflows/publish.yml)
[![PyPI version](https://img.shields.io/pypi/v/sqlite-logbook.svg)](https://pypi.org/project/sqlite-logbook/)
[![Python versions](https://img.shields.io/pypi/pyversions/sqlite-logbook.svg)](https://pypi.org/project/sqlite-logbook/)

SQLAlchemy-backed logging for Python applications. Every log record is written
to stdout and, when available, to a SQLite database or a MySQL, MariaDB, or
PostgreSQL database.

## Installation

```bash
python -m pip install sqlite-logbook
```

## Usage

Initialize the database before creating a logger. The parent directory for a
SQLite database must already exist.

```python
import logging
from pathlib import Path

from sqlite_logbook import get_logger, init_db

database_path = Path("logs") / "application.sqlite3"
database_path.parent.mkdir(exist_ok=True)

init_db(database_path)
logger = get_logger("my_application", level=logging.INFO)

logger.info("Application started")
try:
	raise ValueError("An example error")
except ValueError as error:
	logger.error("Request failed", exc=error)
```

Each persisted record includes the logger name, timestamp, level, and formatted
message. Passing `exc` to `error` or `critical` also appends the exception
source location to the message. Stdout logging defaults to `INFO` and can be
changed with the numeric `LOGGING_STDOUT_LVL` environment variable.

## Design Decisions

- **Stdout is the primary output.** After `init_db` has been called,
	`get_logger` attaches a shared stdout handler to the named logger. Records at
	or above that handler's configured level are printed to stdout whether or not
	database persistence is active.
- **The database is optional.** If initialization fails, `init_db` leaves the
	package in stdout-only mode. A database handler is attached only after a
	session factory has been created successfully.
- **Write failures are best-effort.** Each record is added and committed in its
	own session. Exceptions raised while adding or committing are rolled back and
	suppressed, so a failed database write does not raise from that handler.
- **Initialization state is module-level and process-local.** The first
	successful `init_db` call stores an SQLAlchemy engine and session factory for
	the current Python process. Later calls return those same objects and warn
	when a different connection string is supplied. SQLAlchemy manages the
	underlying physical connections; the package does not keep one connection
	permanently open.
- **SQLite enables WAL on connect.** Local SQLite engines use a five-second
	connection timeout and execute `PRAGMA journal_mode=WAL` for each new DB-API
	connection.
- **The database handler derives timestamps in UTC.** It converts the logging
	record's creation time with `datetime.fromtimestamp(..., tz=timezone.utc)`.

## Database Backends

Use a SQLAlchemy connection URL and set `local=False` for a remote database.

```python
from sqlite_logbook import init_db

init_db(
		"postgresql+psycopg2://logbook:password@db.example.com:5432/logbook",
		local=False,
)
```

The installed drivers support these URL forms:

- MySQL: `mysql+pymysql://user:password@host:3306/database`
- MariaDB: `mariadb+pymysql://user:password@host:3306/database`
- PostgreSQL: `postgresql+psycopg2://user:password@host:5432/database`

## API

- `init_db(connection_str, table_name=None, local=True)` initializes the
	process-wide SQLAlchemy session factory and creates the log table. Set
	`table_name` to use a table other than `logs`.
- `get_logger(name="app", level=logging.INFO)` returns a standard-library
	logger that writes to stdout and, after successful initialization, the
	configured database.

Initialization is process-wide. A later `init_db` call with a different
connection string leaves the original database in use and emits a warning.
When database initialization fails, logging falls back to stdout-only output.

## Development

The integration tests require MySQL, PostgreSQL, and MariaDB. Start the local
services, run the full test suite, then tear them down.

```bash
docker compose up -d --wait
python -m pip install --upgrade pip build twine
python -m pip install -e .
python -m unittest discover -s unit_tests -v
python -m build
python -m twine check dist/*
docker compose down -v
```

The CI workflow runs the tests on Python 3.9 through 3.13.

## Release Checklist

1. Update `__version__` in `sqlite_logbook/__init__.py`.
2. Run the development test and build commands.
3. Commit the release and create a tag matching the version, for example
	 `v0.1.0`.
4. Push the tag. The publish workflow creates the GitHub release and publishes
	 to PyPI through trusted publishing.
