Skip to main content

Universal logger with custom levels for ETL and automation processes.

Project description

handsome_log

handsome_log is a Python logger designed for ETL pipelines, automation tasks, and web scraping scripts.

Built on top of Python's standard logging module and the colorlog package, it adds custom log levels, colored output, secret masking, loop spinners, and flexible file rotation — all with minimal setup.

The main motivation: stop copying logging snippets from old projects. One import, one line, and you're done.


Installing

pip install handsome-log

Quick Start

from handsome_log import get_logger

logger = get_logger(__name__)

logger.startup("Pipeline started")
logger.info("Processing data")
logger.success("Step completed successfully")
logger.finished("Pipeline done")

alt text


Log Levels

handsome_log includes all standard Python levels plus custom ones designed for data pipelines:

Level Method When to Use Default Color
STARTUP logger.startup() Initializing or starting a process Bold Blue
DEBUG logger.debug() Detailed technical or debugging messages Cyan
DRY_RUN logger.dry_run() Simulation runs that don't commit any changes Purple
VALIDATION logger.validation() Validating data, schemas, or credentials Blue
INFO logger.info() General runtime information Green
SUCCESS logger.success() A process or step completed successfully Bold Green
WARNING logger.warning() Something unexpected happened, but the process continues Yellow
ERROR logger.error() A failure occurred but the system is still running Red
CRITICAL logger.critical() A critical failure that halts the process Bold Red
FINISHED logger.finished() Final message marking the end of a process Bold Green

Features

Startup and Finished Borders

startup() and finished() automatically wrap the message with ########## borders to make pipeline boundaries easy to spot in long logs.

logger.startup("Pipeline started")

logger.finished("Pipeline done")

To disable the border, pass border=False:

logger.startup("Starting without border", border=False)

alt text

Hints

Every log method accepts an optional hint keyword argument. The hint is printed on the line below the log message, indented and in the same color, prefixed with └─.

logger.warning("Minor delay in response", hint="Consider optimizing the database query")


logger.error("Failed to write file", hint="Check if the directory exists and has write permissions")
logger.success("Step completed", hint="Results saved to output/result.json")

alt text

Custom HEX Colors

Override the default color for any log level using HEX codes or named colors from colorlog:

logger = get_logger("MY_PIPELINE", custom_colors={
    "DEBUG":      "#00BFFF",
    "INFO":       "#00FF7F",
    "WARNING":    "#FFA500",
    "ERROR":      "#FF5733",
    "CRITICAL":   "#FF0000",
    "SUCCESS":    "#FFD700",
    "STARTUP":    "#DA70D6",
    "VALIDATION": "#40E0D0",
    "DRY_RUN":    "#FF69B4",
})

logger.startup("HEX colors active")
logger.success("Success — gold")

Only the specified levels are overridden; all others use the defaults.

alt text

Loop Status Spinner

loop_status() wraps any iterable and displays a live terminal spinner while the loop runs. When the loop finishes (or fails), it prints the elapsed time.

import time

for item in logger.loop_status(range(100), message="Processing items", show_percent=True):
    time.sleep(0.05)

# [myapp] Processing items  57%  /
# → [myapp] Processing items done (Elapsed time: 5.23s)

Parameters:

Parameter Default Description
message "Processing" Text displayed next to the spinner
delay 0.1 Delay between spinner frames (seconds)
show_spinner True Toggle the spinning character
show_percent False Show progress as a percentage (requires __len__)
end_message "done" Message printed after the loop completes

Secret Masking

When you log messages that involve API keys, passwords, tokens, or any other sensitive value, there's always a risk of accidentally writing the raw secret to the terminal or to a log file.

mask() solves this by intercepting the secret before it ever reaches the logger. It works inside an f-string, so the raw value is evaluated in Python memory, converted to a safe placeholder, and only the placeholder is passed to the log call. The actual secret is never stored, never written to any file, and never appears in any log output.

from handsome_log import get_logger, mask

logger = get_logger("myapp")

api_key = "sk-abc123def456xyz789"

# The raw api_key value never touches the logger — only "<masked>" does
logger.info(f"API key loaded: {mask(api_key)}")
# [12:00:00] [myapp] [INFO] : API key loaded: <masked>

alt text You don't need to sanitize your variables beforehand or remember to strip secrets from messages. Just wrap the value with mask() wherever you reference it in a log call and you're safe.

Masking styles:

Style Example output Description
full <masked> Hides everything, including length (default)
stars ******** 8 fixed asterisks — also hides length
partial sk-a***xyz7 Reveals the first and last show characters

Use partial when you need to confirm which key or token is being used (e.g. to match it against a list) without leaking the full value:

logger.info(f"API key: {mask(api_key)}")                             # <masked>
logger.info(f"API key: {mask(api_key, style='stars')}")              # ********
logger.info(f"API key: {mask(api_key, style='partial', show=4)}")    # sk-a***xyz7
logger.info(f"API key: {mask(api_key, style='partial', show=6)}")    # sk-abc***xyz789

mask() also works inside hint, so even contextual debugging info stays safe:

logger.success(
    f"Connected to DB — password {mask(db_password)} accepted",
    hint=f"Using key {mask(api_key, style='partial', show=4)} — rotate every 30 days",
)
logger.error(
    f"Auth failed for token {mask(jwt_token, style='partial', show=8)}",
    hint="Check token expiration and issuer claim",
)

alt text

Log Files

Basic File Logging

Pass log_to_file=True and a log_file_path to write logs to a file simultaneously with the console output:

logger = get_logger(
    "MY_PIPELINE",
    log_to_file=True,
    log_file_path="logs/pipeline.log",
)

The directory is created automatically if it doesn't exist.


File Rotation

By Size

Rotate when the file exceeds a size limit (in bytes):

logger = get_logger(
    "MY_PIPELINE",
    log_to_file=True,
    log_file_path="logs/pipeline.log",
    max_file_size=5_000_000,  # 5 MB
    file_retention=5,         # keep up to 5 backup files
)

By Time

Use file_rotation to rotate on a schedule. file_retention sets how many days of logs to keep.

file_rotation Rotates... file_retention interpreted as
"daily" Every 24 h at midnight Days (= number of backups)
"weekly" Every 7 days from creation Days (// 7 = weekly backups)
"monthly" At the start of each calendar month Days (// 30 = monthly backups)
"custom" Every custom_retention days Days (// custom_retention)
# Daily — keep 7 days
logger = get_logger("MY_PIPELINE", log_to_file=True, log_file_path="logs/daily.log",
                    file_rotation="daily", file_retention=7)

# Weekly — keep 4 weeks
logger = get_logger("MY_PIPELINE", log_to_file=True, log_file_path="logs/weekly.log",
                    file_rotation="weekly", file_retention=28)

# Monthly — keep 3 months
logger = get_logger("MY_PIPELINE", log_to_file=True, log_file_path="logs/monthly.log",
                    file_rotation="monthly", file_retention=90)

# Custom — rotate every 3 days, keep 30 days total
logger = get_logger("MY_PIPELINE", log_to_file=True, log_file_path="logs/custom.log",
                    file_rotation="custom", custom_retention=3, file_retention=30)

API Reference

get_logger()

from handsome_log import get_logger

logger = get_logger(name, **options)
Parameter Type Default Description
name str required Logger name (typically __name__)
level int logging.DEBUG Minimum log level to capture
log_to_file bool False Write logs to a file
log_file_path str | None None Path to the .log file. Required when log_to_file=True
use_colors bool True Enable ANSI color output (terminal only)
overwrite_handlers bool False Clear existing handlers before adding new ones
show_seconds bool True Include seconds in the log timestamp
custom_colors dict | None None Override colors per level. Accepts named colors or HEX codes (#FF5733)
file_rotation str | None None Time-based rotation: "daily", "weekly", "monthly", "custom"
file_retention int 30 Days of rotated files to keep (or backup count for size rotation)
max_file_size int 0 Rotate by size (bytes). Only active when file_rotation=None. 0 = disabled
custom_retention int 1 Rotation interval in days when file_rotation="custom"

mask()

from handsome_log import mask

mask(value, style="full", show=4)
Parameter Type Default Description
value str required The secret value to mask
style str "full" "full", "stars", or "partial"
show int 4 Characters to reveal at each end when style="partial"

Credits

Built on top of Python's standard logging module and the excellent colorlog package. Full credit and gratitude to the developers of those foundational libraries.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

handsome_log-1.0.0.tar.gz (15.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

handsome_log-1.0.0-py3-none-any.whl (13.6 kB view details)

Uploaded Python 3

File details

Details for the file handsome_log-1.0.0.tar.gz.

File metadata

  • Download URL: handsome_log-1.0.0.tar.gz
  • Upload date:
  • Size: 15.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for handsome_log-1.0.0.tar.gz
Algorithm Hash digest
SHA256 a9a8546bcb7e51ff7ba89923840e9439b16b4d473bdf088bb1cd4f30beeec595
MD5 da1a4da9625b3b266665b4a04a16e892
BLAKE2b-256 e194df8dcaff5f31084c8629fcd1200a07c6123957d87748495f6bcafc1bbb3c

See more details on using hashes here.

File details

Details for the file handsome_log-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: handsome_log-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 13.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for handsome_log-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3c835f019d23e093a832379ba910258238fb66bc2ffbee89f175c8302391a3dc
MD5 d7f8c8aaa35f1471bf898307eaf591b5
BLAKE2b-256 59c73e3145c7504c590a484a028e6cc1ed233e637a877bbc9d2d2810648aa6ed

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page