Now that ZRAM support has been implemented, I wanted to further improve memory management by implementing a logging strategy that logs all activity in daily logs. As a bonus, the approach depicted below will also reduce the amount of writes to the SD flash memory card.
Context
Since I’m running on a memory-constrained machine (Raspberry Pi 3B+ with 1GB or memory) and so far solely relying on an over-dimensioned SD card (32 GB flash memory card), I wanted to keep the live log on a ZRAM filesystem and store the compressed daily logs to an archive folder outside ZRAM.
Here’s what I did:
- Configure the log4j2 logging strategy (basically adding the daily log rotation and compression)
- Create the archive folder in a folder that is readable and writable for the
openhabian
user - Create a
cron
job to move the daily archives from ZRAM to storage.
So let’s go step-by-step in the approach.
1. Configure the log4j2 logger to rotate the log files on a daily basis (this happens at midnight).
1.1. Edit the log4j2 logger configuration file
Edit the file located at /var/lib/openhab2/etc/org.ops4j.pax.logging.cfg
and scroll to the file appender definitions. The first file appender reports to /var/log/openhab2/openhab.log
:
# Rolling file appender
log4j2.appender.out.type = RollingRandomAccessFile
log4j2.appender.out.name = LOGFILE
log4j2.appender.out.fileName = ${openhab.logdir}/openhab.log
# Edit 1 (comment out 1 line): comment out the default logger strategy (add a '%i" suffix to each log):
#log4j2.appender.out.filePattern = ${openhab.logdir}/openhab.log.%i
# Edit 2 (add 1 line): store the archived log files in ${openhab.logdir}/archive/ in a date-based folder structure, and use gzip compression:
log4j2.appender.out.filePattern = ${openhab.logdir}/archive/%d{yyyy/MM}/openhab_%d{yyyy-MM-dd}_%i.log.gz
log4j2.appender.out.immediateFlush = true
log4j2.appender.out.append = true
log4j2.appender.out.layout.type = PatternLayout
log4j2.appender.out.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5.5p] [%-36.36c] - %m%n
log4j2.appender.out.policies.type = Policies
log4j2.appender.out.policies.size.type = SizeBasedTriggeringPolicy
log4j2.appender.out.policies.size.size = 16MB
# Edit 3 (add 3 lines): define the time-based log rotation policy:
log4j2.appender.out.policies.time.type = TimeBasedTriggeringPolicy
log4j2.appender.out.policies.time.interval = 1
log4j2.appender.out.policies.time.modulate = true
Likewise, update the event log appender and the audit log appender (and any other nonstandard appender you defined in your custom setup, e.g. Z-Wave log apppender, HomeKit log appender…):
# Event log appender
log4j2.appender.event.type = RollingRandomAccessFile
log4j2.appender.event.name = EVENT
log4j2.appender.event.fileName = ${openhab.logdir}/events.log
# Edit 1 (comment out 1 line): comment out the default logger strategy (add a '%i" suffix to each log):
#log4j2.appender.event.filePattern = ${openhab.logdir}/events.log.%i
# Edit 2 (add 1 line): store the archived log files in ${openhab.logdir}/archive/ in a date-based folder structure, and use gzip compression:
log4j2.appender.event.filePattern = ${openhab.logdir}/archive/%d{yyyy/MM}/events_%d{yyyy-MM-dd}_%i.log.gz
log4j2.appender.event.immediateFlush = true
log4j2.appender.event.append = true
log4j2.appender.event.layout.type = PatternLayout
log4j2.appender.event.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%-26.26c] - %m%n
log4j2.appender.event.policies.type = Policies
log4j2.appender.event.policies.size.type = SizeBasedTriggeringPolicy
log4j2.appender.event.policies.size.size = 16MB
# Edit 3 (add 3 lines): define the time-based log rotation policy:
log4j2.appender.event.policies.time.type = TimeBasedTriggeringPolicy
log4j2.appender.event.policies.time.interval = 1
log4j2.appender.event.policies.time.modulate = true
# Audit file appender
log4j2.appender.audit.type = RollingRandomAccessFile
log4j2.appender.audit.name = AUDIT
log4j2.appender.audit.fileName = ${openhab.logdir}/audit.log
# Edit 1 (comment out 1 line): comment out the default logger strategy (add a '%i" suffix to each log):
#log4j2.appender.audit.filePattern = ${openhab.logdir}/audit.log.%i
# Edit 2 (add 1 line): store the archived log files in ${openhab.logdir}/archive/ in a date-based folder structure, and use gzip compression:
log4j2.appender.audit.filePattern = ${openhab.logdir}/archive/%d{yyyy/MM}/audit_%d{yyyy-MM}_%i.log.gz
log4j2.appender.audit.append = true
log4j2.appender.audit.layout.type = PatternLayout
log4j2.appender.audit.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5.5p] [%-36.36c] - %m%n
log4j2.appender.audit.policies.type = Policies
log4j2.appender.audit.policies.size.type = SizeBasedTriggeringPolicy
log4j2.appender.audit.policies.size.size = 8MB
# Edit 3 (add 3 lines): define the time-based log rotation policy:
log4j2.appender.audit.policies.time.type = TimeBasedTriggeringPolicy
log4j2.appender.audit.policies.time.interval = 1
log4j2.appender.audit.policies.time.modulate = true
2.2. Restart openHAB
Now restart openHAB to ensure the new log policy is active. Log in as openhabian
user and type the following command:
$ sudo service openhab2 restart
2.3 Notes
- Since the audit log remains empty on my setup, I rotate it on a monthly basis (see the
%d{yyyy/MM}
and%d{yyyy-MM}
patterns) whereas other log files are rotated daily (see the%d{yyyy-MM-dd}
pattern). - The
archive
subfolder and its year/month structure are automatically created when the daily (or monthly) log is rotated. You don’t have to create this folder structure!
2. Create the archive folder in a folder that is readable and writable for the openhabian
user
One important limitation with the log4j2 logger is that it cannot compress archived logs unless they are stored on the same filesystem mount point. With the current ZRAM configuration, the entire /var/log/
folder is now managed by ZRAM. This leads to 2 implementation options:
- You can live with uncompressed logs which can eat up a lot of space after a certain amount of time, and while doing so, your SD card will wear out faster.
- You let the log4j2 logger create compressed log archives in a sub folder in
/var/log/openhab2/
and rely on other tools to move these to another location
In the current tutorial, we opt for option 2. The location where we will store the archived logs is a folder in the home directory of the openhabian
user: /home/openhabian/openhab-log-archive
. It is created by logging in as openhabian
user and typing the following command:
$ mkdir ~/openhab-log-archive
In the next step we will relocate the daily log archives to this folder.
3. Create a cron
job to move the daily archives from ZRAM to storage.
In this step we will schedule 2 jobs for the openhabian
user:
- Move the compressed archives from the ZRAM-controlled folder to the log archive folder from the
openhabian
user - Prune empty folders from the ZRAM-controlled archive folder
Both jobs will be run at a different point in time. We will now add 2 cron jobs for the openhabian
user. To edit the cron job, log in as the openhabian
user and type:
$ crontab -e
NOTE: By default the nano
editor will be used. If you want to use a different editor, then you should first define the VISUAL
shell variable. I’m a vi
user so I would type:
$ VISUAL=vim crontab -e
At the bottom of the cron file, add the following 4 lines:
# Move openHAB backup archives to an archive folder of the openhabian user. Check hourly at 4 minutes past the hour.
4 * * * * /usr/bin/rsync -a --whole-file --remove-source-files /var/log/openhab2/archive/ /home/openhabian/openhab-log-archive
# Prune empty folders after having moved log archives. Check hourly on the 1st of the month at 6 minutes past the hour.
6 * 1 * * /usr/bin/find /var/log/openhab2/archive/ -depth -empty -exec rmdir "{}" ';'
Save the cron job, and you’re done.
Benefits
This approach has the following benefits:
- Reduce ZRAM usage by relocating archive log files to flash storage (or any other storage
rsync
can access, e.g. on a NAS or on cloud storage). - Increase the lifespan of your SD memory card by reducing the number of writes.
Suggestions
A similar approach can also be used for relocating e.g. openHAB backups etc.
Final words
Have fun!