66import argparse
77import json
88import logging
9+ import logging .handlers
910import re
1011import sqlite3
1112import sys
1718
1819import praw
1920
20- # Configure logging
21- logging .basicConfig (
22- level = logging .INFO ,
23- format = '%(asctime)s - %(levelname)s - %(message)s'
24- )
21+ # Global logger setup - will be enhanced with per-subreddit loggers
22+ root_logger = logging .getLogger ()
23+ root_logger .setLevel (logging .INFO )
24+
25+ # Console handler for general output
26+ console_handler = logging .StreamHandler ()
27+ console_handler .setLevel (logging .INFO )
28+ console_formatter = logging .Formatter ('%(asctime)s - %(name)s - %(levelname)s - %(message)s' )
29+ console_handler .setFormatter (console_formatter )
30+ root_logger .addHandler (console_handler )
31+
32+ # Main logger
2533logger = logging .getLogger (__name__ )
2634
2735
@@ -220,6 +228,8 @@ def __init__(self, config_path: str = "config.json", cli_args: Optional[argparse
220228 self .db = ModlogDatabase (retention_days = self .config .get ('retention_days' , 30 ))
221229 self .wiki_char_limit = 524288
222230 self .batch_size = self .config .get ('batch_size' , 100 )
231+ self .subreddit_loggers = {}
232+ self ._setup_subreddit_logging ()
223233
224234 def _load_config (self , config_path : str , cli_args : argparse .Namespace ) -> dict :
225235 """Load JSON config, then override with CLI args"""
@@ -268,6 +278,54 @@ def _validate_config(self, config: dict) -> None:
268278 logger .warning ("Unusual retention_days: %s, using 30" , retention )
269279 config ['retention_days' ] = 30
270280
281+ def _setup_subreddit_logging (self ):
282+ """Setup per-subreddit logging with rotation"""
283+ # Create logs directory if it doesn't exist
284+ log_dir = Path (self .config .get ('log_directory' , 'logs' ))
285+ log_dir .mkdir (exist_ok = True )
286+
287+ # Get subreddits to set up logging for
288+ subreddits = [self .config ['source_subreddit' ]]
289+ if 'target_subreddit' in self .config and self .config ['target_subreddit' ] != self .config ['source_subreddit' ]:
290+ subreddits .append (self .config ['target_subreddit' ])
291+
292+ for subreddit in subreddits :
293+ # Create logger for this subreddit
294+ sub_logger = logging .getLogger (f"modlog.{ subreddit } " )
295+ sub_logger .setLevel (logging .DEBUG ) # Let handlers control level
296+
297+ # Prevent adding handlers multiple times
298+ if sub_logger .handlers :
299+ continue
300+
301+ # Create rotating file handler
302+ log_file = log_dir / f"{ subreddit } _modlog.log"
303+ file_handler = logging .handlers .RotatingFileHandler (
304+ log_file ,
305+ maxBytes = self .config .get ('log_max_bytes' , 10 * 1024 * 1024 ), # 10MB default
306+ backupCount = self .config .get ('log_backup_count' , 5 ), # Keep 5 backups
307+ encoding = 'utf-8'
308+ )
309+
310+ # Create formatter for subreddit logs
311+ file_formatter = logging .Formatter (
312+ '%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s'
313+ )
314+ file_handler .setFormatter (file_formatter )
315+ file_handler .setLevel (logging .DEBUG )
316+
317+ # Add handler to logger
318+ sub_logger .addHandler (file_handler )
319+
320+ # Store reference
321+ self .subreddit_loggers [subreddit ] = sub_logger
322+
323+ logger .info ("Setup logging for subreddit: %s -> %s" , subreddit , log_file )
324+
325+ def get_subreddit_logger (self , subreddit : str ) -> logging .Logger :
326+ """Get logger for specific subreddit"""
327+ return self .subreddit_loggers .get (subreddit , logger )
328+
271329 def _init_reddit (self ) -> praw .Reddit :
272330 """Initialize Reddit API connection"""
273331 reddit_config = self .config ['reddit' ]
@@ -543,15 +601,19 @@ def _process_modlog_entry(self, entry) -> Optional[Dict]:
543601 def fetch_modlog_entries (self , limit : int = 100 ) -> List [Dict ]:
544602 """Fetch and process modlog entries with rate limit handling"""
545603 subreddit = self .reddit .subreddit (self .config ['source_subreddit' ])
604+ sub_logger = self .get_subreddit_logger (self .config ['source_subreddit' ])
546605 entries = []
547606
607+ sub_logger .info ("Starting to fetch modlog entries, limit: %s" , limit )
548608 try :
549609 for entry in subreddit .mod .log (limit = limit ):
550610 try :
551611 processed = self ._process_modlog_entry (entry )
552612 if processed :
553613 processed ['subreddit' ] = subreddit .display_name
554614 entries .append (processed )
615+ sub_logger .debug ("Processed entry: %s [%s] by %s" ,
616+ processed ['id' ], processed ['action_type' ], processed ['moderator' ])
555617 # Mark as processed
556618 self .db .mark_processed (
557619 processed ['id' ],
@@ -565,15 +627,17 @@ def fetch_modlog_entries(self, limit: int = 100) -> List[Dict]:
565627 import re
566628 match = re .search (r'(\d+) minute' , str (e ))
567629 wait_time = int (match .group (1 )) * 60 if match else 60
568- logger .warning ("Rate limited, waiting %s seconds" , wait_time )
630+ sub_logger .warning ("Rate limited, waiting %s seconds" , wait_time )
569631 time .sleep (wait_time )
570632 else :
571633 raise
572634
573635 # Sort by timestamp (newest first)
574636 entries .sort (key = lambda x : x ['timestamp' ], reverse = True )
637+ sub_logger .info ("Successfully fetched %s modlog entries" , len (entries ))
575638
576639 except Exception as e :
640+ sub_logger .error ("Error fetching modlog: %s" , e )
577641 logger .error ("Error fetching modlog: %s" , e )
578642
579643 return entries
@@ -691,20 +755,27 @@ def generate_wiki_content(self, entries: List[Dict]) -> str:
691755
692756 def update_wiki (self , new_entries : List [Dict ]) -> bool :
693757 """Merge with existing wiki content and update"""
758+ target_sub = self .config ['target_subreddit' ]
759+ sub_logger = self .get_subreddit_logger (target_sub )
760+
694761 try :
695- subreddit = self .reddit .subreddit (self . config [ 'target_subreddit' ] )
762+ subreddit = self .reddit .subreddit (target_sub )
696763 wiki_page = self .config .get ('wiki_page' , 'modlog' )
764+
765+ sub_logger .info ("Updating wiki page: /r/%s/wiki/%s" , target_sub , wiki_page )
697766
698767 # Get current wiki content (for logging purposes)
699768 try :
700769 existing_content = subreddit .wiki [wiki_page ].content_md
701- logger .debug ("Existing wiki content size: %s characters" , len (existing_content ))
770+ sub_logger .debug ("Existing wiki content size: %s characters" , len (existing_content ))
702771 except Exception :
703- logger .info ("Wiki page doesn't exist yet, will create new" )
772+ sub_logger .info ("Wiki page doesn't exist yet, will create new" )
704773
705774 # Only use DB entries; wiki parsing no longer needed
706775 cutoff = time .time () - self .config .get ('retention_days' , 30 ) * 86400
707776 retained = self .db .get_recent_entries (cutoff , subreddit = self .config ['source_subreddit' ])
777+
778+ sub_logger .debug ("Retrieved %s entries from database for retention period" , len (retained ))
708779
709780 # Sort newest first
710781 retained .sort (key = lambda x : x ['timestamp' ], reverse = True )
@@ -717,22 +788,29 @@ def update_wiki(self, new_entries: List[Dict]) -> bool:
717788 content = content ,
718789 reason = "Rolling modlog update with retention"
719790 )
791+ sub_logger .info ("Wiki page updated with %s entries, content size: %s chars" , len (retained ), len (content ))
720792 logger .info ("Wiki page updated with %s entries." , len (retained ))
721793 return True
722794
723795 except praw .exceptions .APIException as e :
724796 if e .error_type == "RATELIMIT" :
797+ sub_logger .error ("Rate limited when updating wiki: %s" , e )
725798 logger .error ("Rate limited when updating wiki: %s" , e )
726799 return False
727800 else :
728801 raise
729802 except Exception as e :
803+ sub_logger .error ("Failed to update wiki: %s" , e )
730804 logger .error ("Failed to update wiki: %s" , e )
731805 return False
732806
733807 def run_once (self ):
734808 """Run a single update cycle"""
809+ source_sub = self .config ['source_subreddit' ]
810+ sub_logger = self .get_subreddit_logger (source_sub )
811+
735812 logger .info ("Starting modlog update cycle..." )
813+ sub_logger .info ("=== Starting update cycle for /r/%s ===" , source_sub )
736814
737815 # Cleanup old database entries
738816 self .db .cleanup_old_entries ()
@@ -742,10 +820,14 @@ def run_once(self):
742820
743821 if entries :
744822 logger .info ("Processing %s new modlog entries" , len (entries ))
823+ sub_logger .info ("Processing %s new modlog entries" , len (entries ))
745824 # Update wiki with current database content
746825 self .update_wiki (entries )
747826 else :
748827 logger .info ("No new modlog entries to process" )
828+ sub_logger .info ("No new modlog entries to process" )
829+
830+ sub_logger .info ("=== Completed update cycle for /r/%s ===" , source_sub )
749831
750832 def run_continuous (self ):
751833 """Run continuously with interval"""
@@ -764,6 +846,13 @@ def run_continuous(self):
764846 def cleanup (self ):
765847 """Cleanup resources"""
766848 self .db .close ()
849+
850+ # Close all subreddit loggers
851+ for subreddit , sub_logger in self .subreddit_loggers .items ():
852+ for handler in sub_logger .handlers [:]:
853+ handler .close ()
854+ sub_logger .removeHandler (handler )
855+ logger .debug ("Closed logging for subreddit: %s" , subreddit )
767856
768857
769858def main ():
0 commit comments