Skip to content

feat!: LoggerHelper v2 - lazy init, catch decorator, Slack alerts, rotation - #1

Merged
Marcus778899 merged 3 commits into
mainfrom
feat/slack_refactor
Aug 10, 2026
Merged

Marcus778899 merged 3 commits into
mainfrom
feat/slack_refactor

Conversation

@Marcus778899

Copy link
Copy Markdown
Owner

Why

v1 could not be used without wrapping it. Logger() created directories and four empty files while being constructed, and @log.error had to work as a decorator at import time — so every consumer had to build its own lazy holder. Database_connector_mcp/src/core/log.py says so in a comment: "Delete this once loggerhelper builds its handlers on first emit". It also read no environment variables, had no way to log an error message (the name belonged to the decorator, so logs.error("...") raised AttributeError), and printed a random uuid4 fragment that told a single-process program nothing.

What changed

Usability

  • from loggerhelper import log — a lazy shared instance. Constructing touches nothing; handlers are built on the first emit and files open on demand, so an unused level leaves no empty file.
  • configure(log_dir="./logs") is the whole setup. Settings resolve explicit argument > environment variable > default, with an injectable env mapping so tests need no monkeypatching.
  • .env support via configure(load_dotenv=True), off by default so the library never edits os.environ behind the application's back.

Correctness

  • error(), warning(), debug(), exception() are ordinary message methods. warn() and @error remain as deprecated shims, so the downstream wrapper keeps working unchanged.
  • @log.catch names the definition and the line that actually raised (ConnectionRefusedError in service.pool.AdapterPool.acquire (pool.py:87): ...), emits one record carrying the traceback rather than two, works on async def, and re-raises by default.
  • Rotation by size and by day; the date was previously frozen at construction, so a long-running process wrote into yesterday's directory forever. Retention cleanup added, and DEBUG finally gets a file.
  • A stable stdlib logger name plus propagate = False — the old random-uuid name leaked a logger per instance and double-logged when constructed twice.
  • Colour is applied to fields instead of by str.replace over the rendered line (which mangled any message containing -INFO-), and switches off when the stream is not a terminal.

Levels

Documented by blast radius rather than by how important the function is: WARNING = degraded but this operation still succeeded, ERROR = this operation failed, CRITICAL = the process cannot continue. That is what makes @log.catch defaulting to ERROR correct without deciding per function.

Slack (new, optional)

Attached only when a credential is set. Supports both ways into Slack — an incoming webhook, or a xoxb- bot token posting to chat.postMessage — because neither credential appears on the App Credentials page, and which one you have depends on how the app was set up. Runs behind a bounded queue on a background thread so it never blocks; collapses repeats so an error loop cannot flood the channel; checks for ok: false, which chat.postMessage returns with a 200; never raises.

Housekeeping

Package renamed to lowercase to match the build config (v1 could not build on a case-sensitive filesystem), hatchling replaces poetry-core, the version now lives only in __init__.py, implementation moved under _internal/ so only logger.py is public surface, and no type annotations anywhere — the README is the complete reference instead.

First tests (139), pre-commit, and CI.

Breaking changes

  • @log.catch re-raises where @logs.error swallowed and returned None. The deprecated @logs.error keeps the old behaviour; @log.catch(reraise=False) asks for it explicitly.
  • Log files are no longer pre-created empty. A level that is never used has no file. Code asserting on an empty file needs assert not path.exists() or not path.read_text().

Both old spellings still work, with a DeprecationWarning.

Verification

  • 139 tests pass on Python 3.8, 3.9 and 3.12
  • pre-commit run --all-files clean; uv build and python -m build both produce a wheel that imports from a clean environment
  • Every code example in the README was executed
  • Downstream Database_connector_mcp against this wheel: 787 pass, 2 fail — both are the pre-created-empty-file assertion above, in tests/core/test_log.py. Their intent still holds; they need the one-line change shown in the README migration notes. Its src/core/log.py was not modified.

Follow-up (not in this PR)

Database_connector_mcp can drop _LazyLogger and log_errors from src/core/log.py and collapse to from loggerhelper import log.

🤖 Generated with Claude Code

Marcus778899 and others added 3 commits August 11, 2026 00:04
The v1 package could not be used without wrapping it. Every consumer had to
build its own lazy holder, because `Logger()` created directories and four
files while being constructed and `@log.error` had to work as a decorator at
import time. It also read no environment variables, had no way to log an error
*message* (the name belonged to the decorator), and printed a random uuid4
fragment that told a single-process program nothing.

- Lazy: constructing a Logger touches nothing; handlers are built on the first
  emit and files open on demand, so an unused level leaves no empty file.
- `from loggerhelper import log` plus `configure(log_dir=...)`; settings resolve
  argument > environment > default, with an injectable env mapping for tests.
- `error()`, `warning()`, `debug()`, `exception()` are ordinary message methods
  now. `warn()` and `@error` remain as deprecated shims.
- `@log.catch` logs one record carrying the traceback, names the definition and
  the line that actually raised, works on `async def`, and re-raises by default.
- Levels are documented by blast radius, which is what makes the decorator's
  ERROR default correct without deciding per function.
- The uuid field becomes `module.function:line` of the call site.
- Rotation by size and by day, retention cleanup, and a fix for the date being
  frozen at construction; DEBUG finally gets a file.
- Optional Slack alerts behind a bounded queue and a background thread, with
  repeat collapsing so an error loop cannot flood the channel.
- Colour is applied to fields rather than by str.replace over the rendered line,
  and is switched off when the stream is not a terminal.
- Package renamed to lowercase to match the build config, hatchling replaces
  poetry-core, implementation moved under `_internal/`.
- First tests (121), pre-commit, and CI across Python 3.9/3.12/3.13.

BREAKING CHANGE: `@log.catch` re-raises where `@logs.error` swallowed, and log
files are no longer pre-created empty. Both old spellings still work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The annotations were not carrying their weight in a package this size, and
without them the README has to be complete -- so it now documents every method,
argument and accepted value in one place.

- No annotations anywhere in src or tests, and no `from __future__` imports.
- LoggerConfig becomes a plain class with an explicit __init__; a dataclass
  cannot exist without annotated fields. SETTINGS replaces dataclasses.fields()
  for the error messages.
- py.typed removed: it advertises inline types the package no longer ships.
- LoggerConfig.replace() added, and a config passed to Logger() is now a base
  rather than the last word -- `Logger(config=shared, name=worker)` silently
  dropped the name before, which the README would have promised worked.
- requires-python stays >=3.9, with the reasoning recorded: the code runs on 3.8
  (verified, 126 tests) and `logging`'s stacklevel= is the hard floor, but 3.8
  went end of life in October 2024.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… from

The webhook URL was the only way in, which assumed the app had Incoming Webhooks
enabled. An app set up any other way has a bot token instead -- and neither
credential appears on the Basic Information page, where you would first look.

- SLACK_BOT_TOKEN + SLACK_CHANNEL post to chat.postMessage, as an alternative to
  SLACK_WEBHOOK_URL. Setting both is refused: they name different destinations
  and picking one silently would be worse than saying so.
- chat.postMessage answers 200 with ok:false for a bad channel or a missing
  scope, so the response body is checked. A webhook signals failure with the
  status code, which urlopen already raises on.
- Config errors point at the page the value is actually on, and repr() masks
  both credentials so a config is safe to log.
- SlackWebhookHandler becomes SlackHandler, since it is no longer webhook-only.

README:

- A Deployment section covering `uv build`, the `python -m build` alternative,
  the clean-environment import check, and consuming the wheel by path. Both
  build commands verified.
- Step-by-step for obtaining each Slack credential, including the /invite that
  is easy to miss.
- A section on loading the environment from a .env file, since `export` does not
  survive a new shell.
- pyproject takes the version from __init__.py rather than repeating it, so the
  README does not have to ask for a two-place bump.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@Marcus778899 Marcus778899 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on this major refactor! The transition to a more standard Python package structure and the introduction of a lazy-initialized logger are fantastic improvements.

Here are some highlights of the changes:

  1. Lazy Initialization: Delaying file creation and handler setup until the first log is a much-needed optimization. It prevents the creation of empty files for unused log levels and reduces import side effects.
  2. Decorator Enhancements: Updating @log.catch to support �sync def coroutines and to re-raise by default (while allowing
    eraise=False) resolves a significant flaw in v1 where errors were silently swallowed.
  3. Robust Configuration: Moving to a configuration model that respects explicit arguments, environment variables, and .env\ files (opt-in) is clean and flexible. The \LoggerConfig\ class serves as a strong typed foundation.
  4. Slack Integration: Implementing this in a background thread with a bounded queue is the correct architectural choice to prevent webhook failures from blocking the main application flow.
  5. Modern Build System: Adopting \hatchling\ (and testing with \uv) and standardizing the package name to lowercase (\loggerhelper) aligns the project with modern Python packaging standards.

The PR description and README updates are thorough, clearly documenting the breaking changes and providing clear migration paths.

The code looks solid, and I'm approving this PR.

@Marcus778899
Marcus778899 merged commit ec94350 into main Aug 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant