Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions agent360-example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,42 @@
###################### Default sections
# [agent] ; Main thread
#
###################### Log rotation settings
# rotation = none ; Rotation mode: 'size', 'time', or 'none' (default: none)
# ; 'none' = no rotation (recommended for external logrotate)
# ; 'size' = rotate when file reaches max size
# ; 'time' = rotate at specified time intervals
# rotation_compress = False ; Compress rotated logs with gzip (default: False)
# ; Creates .gz files (e.g., agent360.log.1.gz)
#
# Size-based rotation (when rotation = size):
# rotation_max_bytes = 10485760 ; Max file size before rotation (default: 10MB = 10485760 bytes)
# rotation_backup_count = 7 ; Number of backup files to keep (default: 7)
#
# Time-based rotation (when rotation = time):
# rotation_when = midnight ; When to rotate: 'S'=seconds, 'M'=minutes, 'H'=hours,
# ; 'D'=days, 'midnight', 'W0'-'W6'=weekday (0=Monday)
# rotation_interval = 1 ; Interval for rotation (default: 1)
# rotation_utc = False ; Use UTC for time rotation (default: False)
#
# Examples:
# Daily rotation with 30 day retention and compression:
# rotation = time
# rotation_when = midnight
# rotation_interval = 1
# rotation_backup_count = 30
# rotation_compress = True
#
# Rotate every 50MB, keep 10 files, compressed:
# rotation = size
# rotation_max_bytes = 52428800
# rotation_backup_count = 10
# rotation_compress = True
#
# External logrotate (recommended for Linux):
# rotation = none
# # Then configure /etc/logrotate.d/agent360
#
# [execution] ; Plugin execution threads
# ttl = 15 ; Example: plugins will be killed after 15 sec
#
Expand Down
105 changes: 85 additions & 20 deletions agent360/agent360.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import time
import types
from optparse import OptionParser
from logging import Formatter, getLogger, StreamHandler
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler

try:
from urllib.parse import urlparse, urlencode
Expand Down Expand Up @@ -367,35 +369,98 @@ def _config_section_create(self, section):
self.config.add_section(section)

def _logging_init(self):
'''
Initialize logging faculty
'''
"""
Initialize logging with optional in-app log rotation.
Supported rotation modes:
- none (default): plain FileHandler (use with external logrotate)
- size: RotatingFileHandler
- time: TimedRotatingFileHandler
"""
level = self.config.getint('agent', 'logging_level')

# Resolve target log file path
if os.name == 'nt':
log_file = os.path.expandvars(self.config.get('agent', 'log_file'))
else:
log_file = self.config.get('agent', 'log_file')

# Normalize legacy 'log_file_mode' (kept for compatibility)
log_file_mode = self.config.get('agent', 'log_file_mode')
if log_file_mode in ('w', 'a'):
pass
elif log_file_mode == 'truncate':
log_file_mode = 'w'
elif log_file_mode == 'append':
log_file_mode = 'a'
else:
log_file_mode = 'a'
if log_file_mode not in ('w', 'a'):
if log_file_mode == 'truncate':
log_file_mode = 'w'
else:
log_file_mode = 'a'

# Rotation config (all optional)
rotation = self.config.get('agent', 'rotation') if self.config.has_option('agent', 'rotation') else 'none'
rotation_compress = self.config.getboolean('agent', 'rotation_compress') if self.config.has_option('agent', 'rotation_compress') else False
# Size-based defaults (10MB, keep 7 files)
rotation_max_bytes = self.config.getint('agent', 'rotation_max_bytes') if self.config.has_option('agent', 'rotation_max_bytes') else 10 * 1024 * 1024
rotation_backup_count = self.config.getint('agent', 'rotation_backup_count') if self.config.has_option('agent', 'rotation_backup_count') else 7
# Time-based defaults (roll at midnight, keep 7 files)
rotation_when = self.config.get('agent', 'rotation_when') if self.config.has_option('agent', 'rotation_when') else 'midnight'
rotation_interval = self.config.getint('agent', 'rotation_interval') if self.config.has_option('agent', 'rotation_interval') else 1
rotation_utc = self.config.getboolean('agent', 'rotation_utc') if self.config.has_option('agent', 'rotation_utc') else False

# Configure root logger explicitly (avoid basicConfig)
root = getLogger()
root.setLevel(level)
fmt = Formatter("%(asctime)-15s %(levelname)s %(message)s")

if log_file == '-':
logging.basicConfig(level=level) # Log to sys.stderr by default
else:
try:
logging.basicConfig(filename=log_file, filemode=log_file_mode, level=level, format="%(asctime)-15s %(levelname)s %(message)s")
except IOError as e:
logging.basicConfig(level=level)
logging.info('IOError: %s', e)
logging.info('Drop logging to stderr')
try:
if log_file == '-':
# Container/journald-friendly: write to stderr
handler = StreamHandler()
else:
if rotation == 'none':
# No rotation at all
handler = logging.FileHandler(log_file, mode=log_file_mode)
elif rotation == 'time':
# Time-based rotation
handler = TimedRotatingFileHandler(
log_file,
when=rotation_when, # e.g., 'S', 'M', 'H', 'D', 'midnight', 'W0'
interval=rotation_interval,
backupCount=rotation_backup_count,
utc=rotation_utc
)
else:
# Size-based rotation
handler = RotatingFileHandler(
log_file,
mode=log_file_mode,
maxBytes=rotation_max_bytes,
backupCount=rotation_backup_count
)

# Add gzip compression if enabled
if rotation != 'none' and rotation_compress:
import gzip
import shutil

def namer(name):
"""Add .gz suffix to rotated files"""
return name + ".gz"

def rotator(source, dest):
"""Compress rotated log files with gzip"""
with open(source, 'rb') as f_in:
with gzip.open(dest, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
os.remove(source)

handler.namer = namer
handler.rotator = rotator

handler.setFormatter(fmt)
root.handlers = [handler]
except IOError as e:
# Fall back to stderr if file cannot be opened
root.handlers = [StreamHandler()]
root.handlers[0].setFormatter(fmt)
logging.info('IOError: %s', e)
logging.info('Drop logging to stderr')

logging.info('Agent logging_level %i', level)

Expand Down