Logger TXT


One idea, four implementations, one file format, since 2009.

Started 2009 · Active Go · CLI · Homebrew

Logger TXT appends a timestamped line to a text file. You give it a message, optionally tag it with a type and a project, and it writes a line:

1
01/08/26 16:19 -0400 - DEV (SAPCAST) - Demo server runs canned weather through the real scoring code

I started doing this in 2009 because I kept losing track of what I’d worked on. Not in a “where did the day go” way, in a “what exactly did I ship on that Tuesday three weeks ago, and what was the thing that blocked me” way. Filling in hours, reconstructing a decision, working out when a credit card charge with a cryptic company name actually happened: all of it gets easier when there’s a line with a timestamp on it.

My log file is now well past 180,000 lines. It has outlived several laptops and every note-taking app I’ve tried in the meantime, because it’s a text file and text files don’t go out of business.

The format is the product

There have been four implementations of this idea. The command line tool was a shell script from 2009 to 2025, and Go since. The GUI was an Objective-C menu bar app, and is now Swift 6 and SwiftUI. Two languages, two platforms, four rewrites.

The one thing that never changed is the line format. It’s the interface between the tools, which is why the Go CLI and the Swift app share no code at all and still work on the same file. It’s also the interface between me and my own data in ten years, when whatever I’m using to read it hasn’t been written yet.

What it’s actually good for

That shift is bigger than it sounds. Reading the log used to mean scrolling and grepping until I’d found enough fragments to piece a week back together, which is tedious enough that I mostly didn’t bother unless I needed something specific. Now I ask. How did last week go. What was I focused on yesterday. How did that migration turn out in the end. The log is plain text in a consistent format, so an agent can read it straight through and follow the thread without me telling it how.

It gets better with more than one source. Pointing an agent at my log entries and my meeting notes together gives it enough to explain how something went, rather than only telling me when it happened. Terse one-liners going back to 2009 turn out to be a decent corpus once something else can read them for you.

Use case examples

The pitch I’d give someone standing next to me is that a timestamp costs you nothing at the moment you write it, and is worth a surprising amount later. Some of the things it has earned its keep on:

Filling in a timesheet. This is the one that pays for the habit on its own. Instead of trying to remember what you were doing on a Tuesday two weeks ago, you run logger-txt show --start 14/07/26 --end 14/07/26 and read it back.

Prepping for the week. An agent reads last week’s entries alongside my meeting notes, my task manager, and a list of things I deliberately put off, then walks me through what’s still open one item at a time. The log is the part that knows which of those I quietly finished on Thursday and never closed out. The same shape works for a single day when I want yesterday back before standup.

Writing a performance review. Half a year of work is well past what I can honestly recall, and the version you reconstruct from memory is weighted towards the last three weeks. So an agent read the whole period instead and compiled it into a month-by-month source document, and the review got written against that. Nothing goes in unless it’s in the record, which is a much easier rule to hold when the record exists.

Reconstructing a decision. Six months later someone asks why a service is configured the way it is. Searching for the project name gets you the day you were wrestling with it, and usually the reason, because you wrote it down while you were annoyed about it.

Purchases and the credit card bill. A charge shows up under a company name that means nothing to you. logger-txt search for the amount or the shop and there it is.

Anything where when matters. Vehicle maintenance, medication changes, the date you called about a warranty, when the furnace filter was last done. These are the things you’re certain you’ll remember and then don’t.

Progress on a long project. Logging what you finished each day turns into a record of how a project actually went, which is much more honest than the version you reconstruct from memory afterwards.

None of that needs the tool. You could type into a text file yourself. What the tool buys you is that capture takes two seconds and the format stays consistent enough that the entries from 2009 still search exactly like today’s, which is the part that decides whether the habit survives.

Reading it back looks like this:

1
2
3
4
logger-txt show -c 5                       # last five entries
logger-txt show -t dev -p inkwell          # one project's history
logger-txt search --case-sensitive "Vault" # find that thing you know you wrote
logger-txt show --start 01/03/26 --end 14/03/26

Adding needs no quoting ceremony, which matters more than it sounds when you’re doing it thirty times a day:

1
2
logger-txt add Had coffee with the team
logger-txt add -t meeting -p acme Discussed Q3 roadmap

--help has to be enough

The Go rewrite was partly about capabilities. Date ranges, type and project filters, and reverse scanning so a decade-deep file stays fast. Mostly, though, it was a chance to work on something I’d been chewing on: what does a command line tool look like if you treat “a coding agent has to use this” as a design constraint rather than an afterthought?

The constraint I set was that --help has to be enough. No instruction file, no skill to install, no MCP server wrapping the binary, no prose in a repo telling an agent which flag to prefer. Hand an agent the binary and nothing else, and it should reach the right command on the first try.

That turns out to be a concrete loop rather than a philosophy. Watch an agent use the tool, find the spot where it picks something plausible and wrong, then fix the help text until a cold read leads somewhere better. The example I keep coming back to: adding -t and -p filters to both show and search quietly created a trap. search WORK and show -t WORK looked interchangeable but weren’t, since the first also matches “work” anywhere in a message body. An agent reasonably picked search, got results that looked right, and had no way to know from the help text that it wanted the other command. The fix went into the help strings, not into a document telling the agent what to do.

I wrote the long version of this argument up separately, including what changed in the interface and why I think the skill generalizes:

The short version is that almost nothing I did for the agent’s benefit was bad for me. Precise help text, documented defaults, and a non-interactive path for everything are just good CLI design. Agents make the cost of skipping them obvious a lot faster than I would have noticed on my own.

Reading the log backwards

The one piece of the implementation I’m fond of is the reverse scanner. Almost every read is “show me the last N entries” or “find this in the last month”, and the interesting entries are always at the end of the file. Reading 183,000 lines forwards to print the last ten is silly.

So internal/logger/reverse.go reads the file backwards in 8 KiB chunks and mirrors the bufio.Scanner API, and scanRange in internal/logger/logger.go exits as soon as it passes the start of the window. On a normal show, it never touches the older 99% of the file.

The rest of the codebase is deliberately boring. internal/entry, internal/logger and internal/config have no CLI dependencies, internal/cmd holds thin Cobra wrappers, tests are table-driven, and make coverage fails the build under 100%. internal/cmd/help.go is a bigger file than any of the logic.

Getting it

1
brew install grantlucas/tap/logger-txt

Releases are automated the whole way. Merging a PR computes the next version from the commit messages and stages a draft release, and publishing that draft cuts cross-platform binaries and updates the Homebrew formula without me touching anything. That plumbing took an afternoon and has saved me from every “why is the tap out of date” evening since.

It’s a strange thing to have kept a script alive since 2009. The tool has been rewritten out from under the data twice now, and the data doesn’t care. That’s mostly the argument for plain text, and it’s why I’ll probably still be appending lines to this file long after I’ve forgotten which language the current version is written in.