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.
- s
- e
- d
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.")