3. History

A monitoring tool or a scheduler does not read logs: It reads the exit status of the script. In this step, disk_watch.py returns the exit status expected by monitoring tools, and you will discover the execution history that Scrippy keeps for every script.

The code

#!/usr/bin/env python3
"""
--------------------------------------------------------------------------------
  @author         : Luiggi Vercotti
  @date           : 2026-10-08
  @version        : 0.3.0
  @description    : Monitor filesystem usage

--------------------------------------------------------------------------------
  Update:
  0.1.0  2026-10-08 - Luiggi Vercotti - cre: Script creation
  0.2.0  2026-10-08 - Luiggi Vercotti - evo: Several mount points, log levels
  0.3.0  2026-10-08 - Luiggi Vercotti - evo: Monitoring exit status

--------------------------------------------------------------------------------
  Functioning:
  ---------------
    Checks the usage of each mount point against a warning and a critical
    threshold and exits with a monitoring compatible exit status:
      0: OK, 1: WARNING, 2: CRITICAL
"""
# ------------------------------------------------------------------------------
#  Initialization of the environment
# ------------------------------------------------------------------------------
import sys
import shutil
import scrippy_core
from scrippy_core import logger

MOUNT_POINTS = ["/", "/home", "/var"]
WARNING_THRESHOLD = 80
CRITICAL_THRESHOLD = 90
OK, WARNING, CRITICAL = 0, 1, 2
STATUS = {OK: "OK", WARNING: "WARNING", CRITICAL: "CRITICAL"}

# ------------------------------------------------------------------------------
#  Definition of functions and classes
# ------------------------------------------------------------------------------
def get_usage(mount_point):
  logger.debug(f"[+] Checking {mount_point}")
  usage = shutil.disk_usage(mount_point)
  return round(usage.used * 100 / usage.total, 1)

def check(mount_points, warning, critical):
  worst = OK
  for mount_point in mount_points:
    usage = get_usage(mount_point)
    if usage >= critical:
      logger.error(f" '-> {mount_point}: {usage}% [CRITICAL]")
      status = CRITICAL
    elif usage >= warning:
      logger.warning(f" '-> {mount_point}: {usage}% [WARNING]")
      status = WARNING
    else:
      logger.info(f" '-> {mount_point}: {usage}% [OK]")
      status = OK
    worst = max(worst, status)
  return worst

# ------------------------------------------------------------------------------
#  Main processing
# ------------------------------------------------------------------------------
def main():
  with scrippy_core.ScriptContext(retention=100) as _context:
    logger.log_section("disk_watch", f"Thresholds: warning={WARNING_THRESHOLD}% critical={CRITICAL_THRESHOLD}%")
    status = check(MOUNT_POINTS, WARNING_THRESHOLD, CRITICAL_THRESHOLD)
    logger.info(f"[+] Status: {STATUS[status]}")
    if status != OK:
      sys.exit(status)

# ------------------------------------------------------------------------------
#  Entry point
# ------------------------------------------------------------------------------
if __name__ == '__main__':
  main()

What changed:

  • check() returns the worst status found: OK (0), WARNING (1) or CRITICAL (2).
  • main() exits with this status using sys.exit().
  • The execution context is created with retention=100 (see Retention).

Exit status

When all mount points are below the warning threshold, the script ends normally with the exit status 0:

[...]
[20261008 19:09:52] [INFO    ] [+] Status: OK
[20261008 19:09:52] [INFO    ] [+] Workspace deletion: /home/luiggi/.local/share/scrippy/tmp/disk_watch_1791446992.1042228_28699
[20261008 19:09:52] [INFO    ] [+] End: 0

When a mount point reaches the warning threshold, the script exits with the status 1:

[...]
[20261008 19:09:52] [WARNING ]  '-> /var: 83.7% [WARNING]
[20261008 19:09:52] [INFO    ] [+] Status: WARNING
[20261008 19:09:52] [INFO    ] [+] Workspace deletion: /home/luiggi/.local/share/scrippy/tmp/disk_watch_1791446992.257132_28708
[20261008 19:09:52] [INFO    ] [+] End: 1 (SystemExit)
[20261008 19:09:52] [CRITICAL] [SystemExit]: 1
echo $?
1

sys.exit() raises a SystemExit exception. The execution context catches it, records the exit status in the history (End: 1 (SystemExit)), logs it as a CRITICAL message and lets the script exit with the requested status. sys.exit() and sys.exit(0) are considered as a successful end: Nothing is logged and the exit status is 0.

Note: Any unhandled error also ends the script with a non-zero exit status. This is detailed in the Error handling step.

The execution history

Each execution of a Scrippy script is recorded in its execution history, a SQLite3 database stored in the histdir directory and named after the script:

/home/luiggi/.local/share/scrippy/hist/disk_watch.db

The --hist option

The --hist option displays the last 10 executions, from the most recent to the oldest:

./disk_watch.py --hist
10 last executions of disk_watch.py
+--------------------------+---------------------+---------------------+----------+--------+--------+------+-----------------+------------+
|         Session          |        Start        |         End         | Duration | Origin |  Exec  | Code |      Params     |    Exit    |
+--------------------------+---------------------+---------------------+----------+--------+--------+------+-----------------+------------+
| 1791446992.257132_28708  | 08/10/2026 19:09:52 | 08/10/2026 19:09:52 |  0 sec   | luiggi | luiggi |  1   |        []       | SystemExit |
| 1791446992.1042228_28699 | 08/10/2026 19:09:52 | 08/10/2026 19:09:52 |  0 sec   | luiggi | luiggi |  0   |        []       |     0      |
| 1791446945.3269963_28568 | 08/10/2026 19:09:05 | 08/10/2026 19:09:05 |  0 sec   | luiggi | luiggi |  0   | ['--no-stdout'] |     0      |
| 1791446945.2166913_28565 | 08/10/2026 19:09:05 | 08/10/2026 19:09:05 |  0 sec   | luiggi | luiggi |  0   |   ['--debug']   |     0      |
+--------------------------+---------------------+---------------------+----------+--------+--------+------+-----------------+------------+

The number of executions to display can be passed to the option:

./disk_watch.py --hist 2

The history columns are:

Column Content
Session The session identifier of the execution
Start The start date of the execution
End The end date of the execution (None while the execution is running, or if it has been killed)
Duration The duration of the execution (i.e: 1 min 12 sec)
Origin The user who initiated the execution (the original user in case of sudo)
Exec The user who actually ran the script (i.e: root in case of sudo)
Code The exit status of the execution
Params The options and arguments passed to the script, secret values being redacted
Exit The name of the error if the execution did not end correctly (i.e: SystemExit, FileNotFoundError), 0 otherwise

Note: The executions run with the --no-log-file option are not recorded. Neither are the executions that stop before the history is opened: Invalid header, invalid configuration file, unauthorized user or concurrent execution refused. These errors are only displayed on the terminal.

Sessions

Each execution is identified by a unique session identifier, made of the start time of the execution (a Unix timestamp) and of the process identifier (PID):

1791446992.257132_28708
'---------------'-----'
    timestamp     PID

The session identifier links the history, the log file and the workspace of an execution. To read the log of the execution that ended in WARNING above:

./disk_watch.py --log 1791446992.257132_28708

Retention

By default, the history keeps the 50 most recent executions. The retention parameter of the execution context changes this value:

with scrippy_core.ScriptContext(retention=100) as _context:

The history is purged at the start of each execution: Only the retention most recent previous executions are kept, then the current execution is added.

Note: The retention only applies to the history. Log files are never deleted by Scrippy: Use a tool such as logrotate or a scheduled find -delete to purge the logdir directory.

What's next

The mount points and the thresholds are hard coded: Changing them requires to modify the script. In the next step, they will move to a configuration file.