Repository navigation
feat!: LoggerHelper v2 - lazy init, catch decorator, Slack alerts, rotation - #1
Merged
Merged
Conversation
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
commented
Aug 10, 2026
Marcus778899
left a comment
Owner
Author
There was a problem hiding this comment.
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:
- 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.
- 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. - 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.
- 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.
- 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
v1 could not be used without wrapping it.
Logger()created directories and four empty files while being constructed, and@log.errorhad to work as a decorator at import time — so every consumer had to build its own lazy holder.Database_connector_mcp/src/core/log.pysays 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, sologs.error("...")raisedAttributeError), and printed a randomuuid4fragment 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 injectableenvmapping so tests need no monkeypatching..envsupport viaconfigure(load_dotenv=True), off by default so the library never editsos.environbehind the application's back.Correctness
error(),warning(),debug(),exception()are ordinary message methods.warn()and@errorremain as deprecated shims, so the downstream wrapper keeps working unchanged.@log.catchnames 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 onasync def, and re-raises by default.DEBUGfinally gets a file.propagate = False— the old random-uuid name leaked a logger per instance and double-logged when constructed twice.str.replaceover 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.catchdefaulting toERRORcorrect 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 tochat.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 forok: false, whichchat.postMessagereturns with a 200; never raises.Housekeeping
Package renamed to lowercase to match the build config (v1 could not build on a case-sensitive filesystem),
hatchlingreplacespoetry-core, the version now lives only in__init__.py, implementation moved under_internal/so onlylogger.pyis public surface, and no type annotations anywhere — the README is the complete reference instead.First tests (139), pre-commit, and CI.
Breaking changes
@log.catchre-raises where@logs.errorswallowed and returnedNone. The deprecated@logs.errorkeeps the old behaviour;@log.catch(reraise=False)asks for it explicitly.assert not path.exists() or not path.read_text().Both old spellings still work, with a
DeprecationWarning.Verification
pre-commit run --all-filesclean;uv buildandpython -m buildboth produce a wheel that imports from a clean environmentDatabase_connector_mcpagainst this wheel: 787 pass, 2 fail — both are the pre-created-empty-file assertion above, intests/core/test_log.py. Their intent still holds; they need the one-line change shown in the README migration notes. Itssrc/core/log.pywas not modified.Follow-up (not in this PR)
Database_connector_mcpcan drop_LazyLoggerandlog_errorsfromsrc/core/log.pyand collapse tofrom loggerhelper import log.🤖 Generated with Claude Code