Loggers


Downloads

The loggers on this page are in Python which you may need to install.


My Scripts

e I have 3 log scripts all which I run from the commandline.

Note this is the format s & e produce

logbook.csv

Start Time,End Time,Duration
2026-01-11 15:33:45,2026-01-11 17:20:07,01:46:22

s and e as can be seen above put start and end times in logbook.csv which lives in your home directory.

d is used to put a simple one line description of what you did in activity.txt which lives also in your home directory.

Note that to work the scripts need to be on your path.

Note this system is incredibly minimal.

It doesn’t even log how long tasks take.

This choice for me produces emotional safety for my demand resistance.

The system is designed to be a gentle witness, not something that feels more like surveillance.


Appendix - The Python code

A programming note

I have very poor programming skills. Hence these were vibe-coded. My dream is to work through these slowly one day and understand them fully.


s

#!/usr/bin/env python3
import csv
import os
import datetime
import tempfile

log_file = os.path.expanduser("~/logbook.csv")
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"

def atomic_append(row, write_header=False):
    directory = os.path.dirname(log_file) or "."
    fd, temp_path = tempfile.mkstemp(dir=directory)

    try:
        # Write the row (and header if needed) to a temp file
        with os.fdopen(fd, "w", newline="") as tmp:
            writer = csv.writer(tmp)
            if write_header:
                writer.writerow(["Start Time", "End Time", "Duration"])
            writer.writerow(row)
            tmp.flush()
            os.fsync(tmp.fileno())

        # Append the temp file atomically to the real log
        with open(log_file, "a", newline="") as f:
            with open(temp_path, "r") as tmp:
                f.write(tmp.read())
                f.flush()
                os.fsync(f.fileno())

    finally:
        os.remove(temp_path)

def log_start():
    now = datetime.datetime.now().strftime(TIME_FORMAT)

    file_exists = os.path.exists(log_file)
    write_header = not file_exists or os.stat(log_file).st_size == 0

    atomic_append([now, "", ""], write_header=write_header)

if __name__ == "__main__":
    log_start()

e

#!/usr/bin/env python3

import csv
import os
import datetime
import tempfile

log_file = os.path.expanduser("~/logbook.csv")
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"

def atomic_write_all_rows(rows):
    directory = os.path.dirname(log_file) or "."
    fd, temp_path = tempfile.mkstemp(dir=directory)

    try:
        # Write full CSV to a temp file
        with os.fdopen(fd, "w", newline="") as tmp:
            writer = csv.DictWriter(tmp, fieldnames=["Start Time", "End Time", "Duration"])
            writer.writeheader()
            writer.writerows(rows)
            tmp.flush()
            os.fsync(tmp.fileno())

        # Atomically replace the original file
        os.replace(temp_path, log_file)

    except Exception:
        # Ensure temp file is removed on failure
        if os.path.exists(temp_path):
            os.remove(temp_path)
        raise

def log_end():
    now = datetime.datetime.now().strftime(TIME_FORMAT)

    # Load existing rows
    with open(log_file, newline="") as f:
        rows = list(csv.DictReader(f))

    # Find the last open session
    for row in reversed(rows):
        if row["End Time"] == "":
            row["End Time"] = now

            start_time = datetime.datetime.strptime(row["Start Time"], TIME_FORMAT)
            end_time = datetime.datetime.strptime(now, TIME_FORMAT)
            duration = end_time - start_time

            total_seconds = int(duration.total_seconds())
            hours, remainder = divmod(total_seconds, 3600)
            minutes, seconds = divmod(remainder, 60)

            row["Duration"] = f"{hours:02}:{minutes:02}:{seconds:02}"
            break

    # Write the entire CSV back atomically
    atomic_write_all_rows(rows)

if __name__ == "__main__":
    log_end()

d

#!/usr/bin/env python3

import os
import tempfile

activity_file = os.path.expanduser("~/activity.txt")

activity = input("Enter activity: ").strip()
if activity:
    directory = os.path.dirname(activity_file) or "."

    # Write to a temporary file first
    fd, temp_path = tempfile.mkstemp(dir=directory)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as tmp:
            tmp.write(activity + "\n")
            tmp.flush()
            os.fsync(tmp.fileno())

        # Append atomically to the real file
        with open(activity_file, "a", encoding="utf-8") as f:
            with open(temp_path, "r", encoding="utf-8") as tmp:
                f.write(tmp.read())
                f.flush()
                os.fsync(f.fileno())

    finally:
        if os.path.exists(temp_path):
            os.remove(temp_path)

    print("Activity saved!")
else:
    print("Nothing entered, nothing saved.")