Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3a1d9b5
fix: render Canvas results with correct colour scheme
PaperMtn Apr 28, 2026
93a3c7c
fix: avoid AttributeError when file results have no user
PaperMtn Apr 28, 2026
0968c7a
Merge branch 'develop' into feature/bug-fixes
PaperMtn Apr 28, 2026
e9429d9
docs: changelog entry for retry fix from PR #93
PaperMtn Apr 28, 2026
4b4de8d
refactor: collapse notify_type cascade into elif chain
PaperMtn Apr 28, 2026
624634e
fix: do not sys.exit on log_to_stdout formatting error in debug mode
PaperMtn Apr 28, 2026
aa8cdf9
perf: hoist log_to_stdout regexes to module-level constants
PaperMtn Apr 28, 2026
d0a2162
refactor: replace log_to_stdout colour cascade with LEVEL_STYLES dict
PaperMtn Apr 28, 2026
1230db6
fix: drop unused Logger base class and prevent handler duplication in…
PaperMtn Apr 28, 2026
bd79ba8
fix: route WORKSPACE_PROBE to its dedicated JSON formatter
PaperMtn Apr 28, 2026
8a0e41c
fix: route WARNING/ERROR/CRITICAL to their matching logger methods
PaperMtn Apr 28, 2026
b31ca39
refactor: install single JSONFormatter once instead of swapping per call
PaperMtn Apr 28, 2026
163af28
fix: surface CSV export failures instead of swallowing them silently
PaperMtn Apr 28, 2026
0b55a38
fix: guard export_csv against empty input
PaperMtn Apr 28, 2026
8b2185a
chore: drop redundant f.close() after with block in export_csv
PaperMtn Apr 28, 2026
a937889
docs: add docstring to JSONLogger.log
PaperMtn Apr 28, 2026
9ca7625
test: tighten loggers.py coverage to 100% and improve test isolation
PaperMtn Apr 28, 2026
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ venv.bak/
.dmypy.json
dmypy.json

# Claude
.mcp.json
CLAUDE.md
LOGGING_ISSUES.md

# App specific
*.csv
logging_test.py
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Fixed
- Canvas results in `StdoutLogger` were rendered with the red `USER` colour scheme because `msg_level` was set to `'USER'` instead of `'CANVAS'`. Added a dedicated `CANVAS` style branch in `log_to_stdout`. Fixes [#90](https://github.com/PaperMtn/slack-watchman/issues/90)
- Fixed `AttributeError` crash in `StdoutLogger` when logging file results with no resolved user. The user dict is now coerced via `(message.get('user') or {})` before reading `display_name`/`email`. Fixes [#91](https://github.com/PaperMtn/slack-watchman/issues/91)
- Removed pointless retry on `log_to_stdout` exception in `StdoutLogger.log`. The previous handler retried with identical arguments and could only fail the same way; replaced with a single contextual error message. Fixes [#92](https://github.com/PaperMtn/slack-watchman/issues/92) (thanks @SAY-5)
- A formatting failure inside `StdoutLogger.log_to_stdout` no longer terminates the process when debug mode is enabled. The `sys.exit(1)` call has been removed; the traceback is still printed in debug mode and the scan continues. Fixes [#95](https://github.com/PaperMtn/slack-watchman/issues/95)
- `JSONLogger` no longer subclasses `logging.Logger` (the inheritance was unused — every emission already went through `self.logger`) and no longer stacks duplicate handlers when instantiated more than once. `addHandler` is now guarded so the process-wide singleton from `logging.getLogger('Slack Watchman')` keeps a single handler. Fixes [#98](https://github.com/PaperMtn/slack-watchman/issues/98)
- `JSONLogger.log` now matches `WORKSPACE_PROBE` (the level the caller actually passes) instead of `WORKSPACE_PROBE_INFORMATION`. Probe results were silently falling through to the catch-all `else` branch and being emitted as `CRITICAL` with the wrong formatter. Fixes [#99](https://github.com/PaperMtn/slack-watchman/issues/99)
- `JSONLogger.log` now has explicit `WARNING`, `ERROR`, and `CRITICAL` branches that call the matching `self.logger.warning/error/critical` method. Previously all three (and any unknown level) hit the catch-all `else` and were emitted as `CRITICAL`, so JSON output diverged from `StdoutLogger` and lost severity information. Fixes [#100](https://github.com/PaperMtn/slack-watchman/issues/100)
- `export_csv` now returns a `bool` indicating whether the CSV was written, and its callers in `__init__.py` log a `SUCCESS` only on success and an `ERROR` on failure. Previously a write error was swallowed and the user saw a `Users output to CSV file: ...` success line for a file that was never written. Also corrected the channels CSV success message (it was incorrectly labelled `Users output to CSV file`). Fixes [#102](https://github.com/PaperMtn/slack-watchman/issues/102)
- `export_csv` now guards against empty input. The previous `dataclasses.asdict(export_data[0])` would raise `IndexError`, which was hidden by the bare `except` and reported only as a stray `print` line. Empty input now returns `False` without opening a file. Fixes [#103](https://github.com/PaperMtn/slack-watchman/issues/103)

### Removed
- Redundant `f.close()` call in `export_csv` after the `with open(...) as f:` block (the context manager already closes the file). Fixes [#104](https://github.com/PaperMtn/slack-watchman/issues/104)

### Changed
- Converted the `notify_type` cascade in `StdoutLogger.log` from a series of independent `if`s to an `elif` chain, since the branches are mutually exclusive. Avoids unnecessary string comparisons on every log call. Fixes [#94](https://github.com/PaperMtn/slack-watchman/issues/94)
- Hoisted the colourising regexes (`_TYPE_COLORER`, `_HEADER_WORDS`) in `loggers.py` to module-level constants instead of recompiling them on every `log_to_stdout` call. Fixes [#96](https://github.com/PaperMtn/slack-watchman/issues/96)
- Replaced the 13 near-identical `elif` colour branches in `StdoutLogger.log_to_stdout` with a `_LEVEL_STYLES` dict lookup. Behaviour is unchanged for every existing level. Fixes [#97](https://github.com/PaperMtn/slack-watchman/issues/97)
- Replaced the per-call `handler.setFormatter` mutation in `JSONLogger.log` with a single `_JSONFormatter` that builds the JSON envelope from `record.msg` and an `extra`-supplied `log_level`. Eliminates the eight `*_format` attributes, removes the not-thread-safe handler state swap, and keeps the JSON schema unchanged for every existing level. Fixes [#101](https://github.com/PaperMtn/slack-watchman/issues/101)

## [4.4.5] - 2026-04-27
### Changed
- Updated dependabot.yml to created PRs against `develop` branch instead of `maaster`
Expand Down
18 changes: 10 additions & 8 deletions src/slack_watchman/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,20 +320,22 @@ def main():
user_list = watchman_processor.get_users(slack_con, verbose)
OUTPUT_LOGGER.log('SUCCESS', f'{len(user_list)} users discovered')
OUTPUT_LOGGER.log('INFO', 'Writing to csv')
export_csv('slack_users', user_list)
OUTPUT_LOGGER.log(
'SUCCESS',
f'Users output to CSV file: {os.path.join(os.getcwd(), "slack_users.csv")}')
users_csv_path = os.path.join(os.getcwd(), "slack_users.csv")
if export_csv('slack_users', user_list):
OUTPUT_LOGGER.log('SUCCESS', f'Users output to CSV file: {users_csv_path}')
else:
OUTPUT_LOGGER.log('ERROR', f'Failed to write users CSV: {users_csv_path}')

if channels:
OUTPUT_LOGGER.log('INFO', 'Enumerating channels...')
channel_list = watchman_processor.get_channels(slack_con, verbose)
OUTPUT_LOGGER.log('SUCCESS', f'{len(channel_list)} channels discovered')
OUTPUT_LOGGER.log('INFO', 'Writing to csv')
export_csv('slack_channels', channel_list)
OUTPUT_LOGGER.log(
'SUCCESS',
f'Users output to CSV file: {os.path.join(os.getcwd(), "slack_channels.csv")}')
channels_csv_path = os.path.join(os.getcwd(), "slack_channels.csv")
if export_csv('slack_channels', channel_list):
OUTPUT_LOGGER.log('SUCCESS', f'Channels output to CSV file: {channels_csv_path}')
else:
OUTPUT_LOGGER.log('ERROR', f'Failed to write channels CSV: {channels_csv_path}')
OUTPUT_LOGGER.log('INFO', 'Finding public Canvases')
for channel in channel_list:
if not channel.canvas_empty and channel.canvas_id:
Expand Down
Loading