DEV Community

Cover image for My Mac Remembers Fifteen Days, Only 3.7 Are Documented
Mustafa ERBAY
Mustafa ERBAY

Posted on Originally published at mustafaerbay.com.tr

My Mac Remembers Fifteen Days, Only 3.7 Are Documented

I've stopped counting how many questions I've put to this machine in the past
fortnight — what was blocking sleep, what the download ledger knew, how many
files live in my home directory, how often launchd retried. Every time, the
answer came out of the machine's own records. This time the records themselves
were the question: how far back does that ledger go? It started with one
command:

$ log stats
== archive =============================================================
size:               1.949.318.408 bytes
                    5.756.410.663 bytes (uncompressed)
start:              Sat Sep 26 07:23:41 2026
end:                Sun Oct 11 08:34:48 2026
statedump:          226.689

events:             [       total        log      trace   signpost       loss ]
                    [  36.417.832 31.418.358          0  2.792.628         46 ]
…
Enter fullscreen mode Exit fullscreen mode

Fifteen days. Thirty-six million records. Roughly 1.82 GiB of disk. I had read
lines out of this store before — when I counted launchd's 21,881
retries
I
queried half an hour of it with log show. But I had never looked at the ledger
as a whole: its edges, its limits, who takes up the room.

My first reaction was relief: so I can ask about two weeks ago. Then I asked,
and things got complicated. Because "fifteen days" isn't one memory, it's the
sum of three separate books, and each has its own lifespan. The book that
behaves the way Apple documents reaches back 3.7 days. What keeps the remaining
eleven days alive is a plist key that appears in no documentation at all.

Every measurement was taken on the morning of 11 October 2026, on a single
machine: macOS 26.6.2 (build 25G83), arm64. The numbers are snapshots taken
between 08:34 and 08:43; they move as the machine runs. What matters isn't the
figures themselves but the ratios between them. All commands are read-only —
the single write was the pair of log emit calls that added two lines of my own
for the privacy experiment.

One command, three numbers

The most instructive part of the log stats output is the level breakdown. The
log messages row says: 27,020,277 default, 5,480,967 info, 1,398,267 error,
243,130 fault, 68,345 debug. Add those five and you get 34,210,986 — which
equals neither the 36,417,832 in the events row nor the 31,418,358 log
events inside it. The rows of log stats aren't slices of one another; they're
separate counters over different scopes, with signposts (2,792,628), activities
(1,979,549) and state dumps (226,689) sitting in rows of their own. Worth
keeping in mind while reading. I didn't, and tried to add them up.

There's also a number I'm fond of: the loss column of the event table reads

  1. In fifteen days, 46 events were never written at all. Under pressure the logging system drops records, and it counts them honestly. Forty-six out of thirty-six million is not a meaningful rate, granted. But it is another thing to read the falseness of "everything gets written" off the machine's own counter.

The disk side tells the same story. The /var/db/diagnostics directory holds
1,878 MiB; 1.82 GiB of that is the archive itself, the rest is logd's own logs
and statistics files. Inside are 1,104 .tracev3 files. The log isn't stored as
text: Apple's documentation says the files are binary and compressed and can't
be read directly. The numbers back
that up — 1.82 GiB on disk, 5.36 GiB uncompressed, about a 2.95× ratio.

Three books, three separate memories

log stats --per-book is what untangles the knot. Same archive, three books:

Book On disk Events Oldest record Reach
Persist 507 MiB 17,476,038 7 Oct 14:40 3.7 days
Special 1.23 GiB 16,012,881 26 Sep 07:23 15 days
Signpost 90 MiB 2,544,860 29 Sep 07:58 12 days

Persist is the book described in Apple's documentation. The docs give log levels
in a table: debug is not persisted to disk, info is persisted "only when
collected with the log tool", and notice/error/fault get "yes, up to a
storage limit". Here are all five of Persist's columns: 14,705,232 default,
607,712 error, 184,600 info, 98 debug — and 0 fault.

I read that last number twice. Of the three levels the documentation promises to
disk, one — fault — doesn't appear in the documented book at all; all 243,130
faults in the archive landed in the other book, the tagged one. The deviation
runs the other way too: debug, the level that "isn't written", is sitting there
with 98 records. So even the documented book doesn't behave as documented. The
thesis shows up in the very first table.

And on my machine, "up to a storage limit" means 3.7 days. The same page
explains why: when the data store exceeds a predefined size, the system purges
old messages to make room for new ones. Persist takes about 142 MB a day (I
divided its 531 MB by 3.75 days), hits the ceiling, and gets trimmed from the
front.

So why does the archive start on 26 September? Because the second book, Special,
runs on entirely different rules. Putting the two books' ttl rows side by side
made the difference obvious: Persist's TTL row is all zeros, while Special's TTL
row counts 16,012,448 records. The book holds 16,012,881 events in total — so
apart from 433 records, everything in Special carries a time tag.

Diagram

When I asked about thirteen days ago

I dropped the theory and asked the machine directly. The morning of 28
September, between nine and ten — thirteen days before today. Surviving records:
91,795. Not thin at all; almost generous for a single hour.

Then I pulled the same hour from yesterday, 10 October: 104,635 events — 13,304
of them signposts, so the message side comes to 91,331, almost exactly the same.
At that point I could have said "my log reaches thirteen days back intact" and
walked away. I'm glad I looked at one more column.

The number of records from that hour on 28 September carrying no TTL tag:
zero. There is exactly one untagged line, and it isn't even a log message —
it's a Timesync event noting that the system adjusted its clock. For the same hour
yesterday, counting the TTL column in the log show output gives 1,861 untagged
records.

One note on reading these: the ttl row in log stats collapses tags into five
buckets (1/3/7/14/30). The real value comes from the TTL column of log show,
and there you also find in-between values like 4, 8 and 21 — as we're about to
see.

In other words, everything I have left from thirteen days ago is there because
its author stamped a TTL on it. Every ordinary record is gone. Two separate
methods confirm it — counting log show output line by line, and querying the
same window with log stats — and they agree record for record:
91,795 events, 91,794 tagged, one Timesync.

An unexpected detail surfaced here too. Of the records surviving from 28
September, 17,256 have "1 day" written in their tag. A thirteen-day-old record,
sitting there with a one-day label on it. So this tag is not an expiry date. The
behaviour on disk here is consistent with the tag being a floor: "keep this
record for at least N days", after which it becomes eligible for deletion but
stays put until space runs short. That inference is mine; Apple writes nothing
at all about this key — and in a moment I'll say exactly where I looked.

Who stamps the tag

No system-wide setting decides the tag; whoever writes the message decides. For
the last hour, I counted TTL-tagged records by author:

Process Records Tag
diskarbitrationd 12,085 1 day
WeatherMenu 3,579 4 days
storagekitd 3,136 7 days
runningboardd 2,827 7 days
dasd 1,288 8 days
imagent 1,688 14 days
softwareupdated 19 30 days

My disk mount events are ready for deletion a day later, my messaging service's
records live two weeks, the software update's note stays a month. Learning that
the weather menu has a four-day memory was my unexpected fact of the day.

A caveat: the same process can use different tags. The tag belongs to the
subsystem-and-category pair, not the process — runningboardd was writing with
a 7-day tag during that hour. State dumps are a world of their own: of the 394
dumps produced in the last three hours, every single one carries a 14-day tag,
and they don't come from one process (runningboardd 310, imagent 63,
duetexpertd 10). I couldn't find which policy file hands out that 14;
com.apple.runningboard.plist's ttl category says 7/3/1, so the dump tag
comes from somewhere else.

The 501 files that decide

Where do these tags come from? From
/System/Library/Preferences/Logging/Subsystems. macOS 26.6.2 installs 501
plist files
there, each one a subsystem's logging policy. I opened all of them
with plistlib, walking nested dictionaries recursively: 188 files define a
TTL, for a total of 647 TTL entries. Of those, 638 give a number of days;
the remaining nine say Default or are empty. (My first pass only walked
subsystem → category and reported 185/627; the inner dictionaries under
DEFAULT-OPTIONS escaped that round. Two entries also write the day count as
10d rather than 10 — the key accepts a suffixed form too.) Here is how the
638 break down:

Tag Entries Tag Entries
0 days 116 8 days 6
1 day 84 10 days 5
2 days 42 14 days 72
3 days 75 15–21 days 14
4 days 19 30 days 54
5 days 23 90 days 1
7 days 127

The most popular value is seven days. The 116 entries saying zero days are
interesting too: that's how you say "don't keep this at all". The single 90-day
entry at the top of the list is the iaMigrator category of the
com.apple.internetAccounts subsystem — account migration. My machine's longest
memory is reserved for moving an account from one place to another.

The format of these files is partly documented: Apple spells out the
Level dictionary of logging configuration profiles, the Enable and Persist
keys, the Inherit/Default/Info/Debug values. The os_log(5) manual page
completes the list: Enable-Oversize-Messages, Enable-Private-Data,
Signpost-Enabled, Signpost-Persisted, Signpost-Scope,
Signpost-Backtraces-Enabled.

TTL is in none of them. I checked three separate official surfaces — the
configuration page in the developer documentation, the os_log(5) manual, and
the SystemLogging profile schema in device management. Between them they
explain Level, Persist and Enable-Private-Data; not one mentions the key
that 188 files on my machine use and that determines eleven days of my machine's
memory. The log command does know about it: its default output style prints a
TTL column, and log erase --ttl offers to delete "time-to-live log content".

In fairness, TTL isn't alone. The same 501 files carry other keys I couldn't
find in the documentation: Enable-Fault-Crashlogs (28 entries),
Privacy-Enable-Level (17), Propagate-with-Activity (13),
Install-Log-Persist-Level (9). The format has an undocumented tail, and TTL is
its most consequential member — because it decides how far back my machine
remembers.

Watching an undocumented key run a live system felt familiar: the four-field
format of the quarantine stamp in my download
ledger

wasn't written down anywhere either. The difference is this: there, the
undocumented thing was the shape of a string. Here, it's how far back my machine
remembers.

The documented default doesn't apply on this machine

os_log(5) states the system settings plainly: debug level logging is disabled,
info level is enabled and written to a memory buffer, default level is enabled
and persisted to disk. The developer documentation says info is persisted "only
when collected with the log tool".

My disk holds 5,480,967 info and 68,345 debug records. I collected none of them.

The reason is in those policy files again. In com.apple.runningboard.plist, for
instance, the message category carries Enable: Info, Persist: Default; the
same file's ttl category has Enable: Debug, Persist: Info. Apple's own
shipped policy files override the default Apple documents. The documentation
isn't wrong — it notes that you can change the behaviour "using tools or custom
configuration profiles". It's just that in practice, the scope of that note is
wider than the body of the page.

How much changing power do I get? log config requires root, and the directory
set aside for the user side, /Library/Preferences/Logging/Subsystems, contains
four files: com.apple.WebBookmarks, com.apple.WebInspector,
com.apple.WiFiManager, com.apple.eapol. So even the folder reserved for me
holds four Apple files and nothing of mine. I could technically write there; it
had never crossed my mind.

Half the memory is a single line

The archive's biggest writer by volume is runningboardd: 2,508,357 events and
45.8% of all bytes. The kernel comes second per-process, but the kernel's
four million events take 482 MB — runningboardd's 2.5 million take 2.63 GB.

The gap comes down to one pattern: <state dump>. Special's state-dump counter
reads 226,689; of those, 205,346 come from RunningBoard and take 2,305,448,239
bytes on their own — 56.3% of that book.
Measured against the whole archive the share is 40.1%. So more than half of the
only book that reaches fifteen days back consists of a background service taking
notes to itself about its own internal state.

RunningBoard is a name I already know on this machine: it owns the assertion
mechanism I ran into when I counted what was blocking
sleep
.
Its log patterns speak the same language: 291,660 times "acquiring assertion
targeting this from that originator", 275,822 times "invalidating assertion".
These records can be useful to me, and they have been. But they sit there taking
up 2.3 GB, shortening the lifespan of my own records.

Persist holds an unexpected name too: WhatsApp. In under four days, 2,453,427
records, 102 MB — the same league as the kernel and airportd. (I won't claim a
ranking; half an hour later second place had passed to airportd, a few MB
apart.) Looking at the patterns, most of it is background-task bookkeeping:
"will add backgroundTask", "reusing background assertion". A messaging app
writing on the same order as the kernel in my laptop's persistent log is a good
illustration of how the capacity gets shared.

The record is there, the content isn't

The lifespan of memory is one matter; its content is another. In the last hour I
counted 579,424 event lines; 58,516 of them — 10.1% — contain <private>.
The neighbouring hour gave 8.6% for the same measurement, so the figure moves
between one line in twelve and one in ten. What doesn't move: those lines sit
there with at least one field cut out.

This is deliberate and good design. Apple's documentation puts it this way: the
system doesn't redact integer, floating-point and Boolean values, but it does
redact the contents of dynamic strings and complex dynamic objects. A developer
who wants a value visible has to write the %{public} specifier explicitly.

I tried it myself in two lines:

log emit --subsystem com.mustafaerbay.deney --category gunluk "deney-dizgesi-ALFA"
log emit --public --subsystem com.mustafaerbay.deney --category gunluk "deney-dizgesi-BETA"
log show --last 60s --info --style compact --predicate 'subsystem == "com.mustafaerbay.deney"'
Enter fullscreen mode Exit fullscreen mode

The output:

08:43:45.017 Df log[92558] [com.mustafaerbay.deney:gunluk] <private>
08:43:45.020 Df log[92559] [com.mustafaerbay.deney:gunluk] deney-dizgesi-BETA
Enter fullscreen mode Exit fullscreen mode

Two messages written at the same moment, to the same subsystem. I wrote the
first one on my own machine with my own command, and I can't read it on my own
machine. The message is there; only its content is closed to me. (The log
command's manual states this behaviour openly: by default the message is emitted
as a private string.)

I counted where the redaction bites hardest: locationd 10,375, WhatsApp 8,079,
opendirectoryd 5,759, bluetoothd 3,548, IMDPersistenceAgent 2,864. The
first instinct is to say the system knows where the sensitive data lives. But
this list isn't a judgement call, it's the shadow of a default: because
redaction is opt-out, the ranking only shows who prints the most dynamic
strings. Sure enough, runningboardd (2,491) and watchdogd (2,268) are high
on the same list, and neither carries anything of mine.

There are two documented keys for opening that door, and they have to be counted
separately; on my first pass I counted one of them and drew the wrong
conclusion.

The hard one is Enable-Private-Data: it turns on the capture of private
arguments. Across Apple's 501 shipped files, the number of entries enabling it
is two — both in the asset download service (com.apple.MobileAsset,
com.apple.mobileassetd).

The soft one is Default-Privacy-Setting, which appears with the value Public
in the device management schema's own example profile. That key shows up in 92
files
, across 121 entries: 104 Public, 17 Private. So Apple has loosened
the privacy default by its own hand in roughly a fifth of its subsystems. The
key's exact semantics aren't documented in prose — it most likely makes
unmarked specifiers public, a narrower opening than Enable-Private-Data. But
"barely loosened" would be wrong, and that is what I was about to write before
counting.

Freezing it takes one command; enlarging it takes none

So far this has all been about loss. Is there a way to hold on to any of it? I
found both answers in man log, and they point in opposite directions.

Freezing: log collect exports the archive, or a slice of it, as a
.logarchive — in the manual's words, a bundle that "can be viewed later with
tools such as log or Console". I tried it:

log collect --last 10m --output deneme.logarchive
# Archive successfully written to deneme.logarchive
Enter fullscreen mode Exit fullscreen mode

Five seconds, no root required. The records inside the bundle come to 16.7 MB,
but the folder takes 248 MiB on disk — because the archive carries along the
symbol files that make those records readable. To freeze all fifteen days I'd
pass --start; log repack then filters a bundle down with a predicate. So
there is a documented, one-line way to keep the record myself.

Enlarging: none. The log config --mode list offers level, persist,
stream, the signpost settings and oversize-enabled; there is no mode for the
size ceiling or the TTL. The only knob I'm given for making the ledger last
longer is on the "write less" side.

One warning on the way through that door: when you attach a .logarchive to a
support request, the <private> redaction protects you — but all fifteen tagged
days travel with it. Running log repack's predicate filter first is a good
habit.

Four questions to ask your own machine

Repeating this measurement takes nothing special; the log command is on every
Mac. In your position, I'd ask these four:

  1. How far back does your archive go? The start line in log stats. That number isn't a setting, it's a consequence.
  2. How many days is your documented book? Persist's start line in log stats --per-book. Mine was 3.7 days; install a noisy app and that number shrinks.
  3. What's left from an older day? Ask for one hour with log stats --start "<date> 09:00:00" --end "<date> 10:00:00" and look at the ttl row. If there are no untagged records, all you have about that day is what other people chose to keep.
  4. Who writes the thing you care about? Which process emits the event you care about (sleep, disk, network), and how many days is its tag? If its lifespan is shorter than your question, freeze that slice with log collect — raising the ceiling isn't on the menu.

The fourth question landed on a single sentence for me: if what I want to track
has to outlive the system log, keeping that record is my job. The system log is a
good diagnostic tool; it isn't my archive.

Whose job is remembering

I started out thinking of the log as a diary: a record my machine keeps on my
behalf, one I can look back through. Fifteen days, thirty-six million lines — it
sounds generous.

By the end of the measurement I have a different picture. This isn't a diary,
it's a shared scratchpad: fixed capacity, with the shares left to whoever writes
the record. My disk mount gets a day, the weather menu four days, account
migration ninety. More than half of the only book that reaches fifteen days is
one service's notes to itself. Roughly one line in ten can't be read. And the
documented part — the promised part — collapses in 3.7 days, without a single
one of the faults the documentation promises to put there.

I don't think that's a flaw. The logging system was designed for diagnostics and
it's good at diagnostics: extraordinarily detailed for the first three days, its
privacy default set on the right side. It even counts the records it loses. The
flaw was in the meaning I'd attached to it. When I counted the places my search
box doesn't look
,
I learned the same lesson: a tool's silence doesn't mean there's no answer to
your question; it means that tool wasn't built to ask your question.

Knowing how much your machine remembers, and for how long, also tells you that
what you want remembered is yours to freeze — and that the command for it is one
line. For me that wasn't a reason to
think less of the log; it was a reason to stop handing it a job it never took on.

Official Sources

Top comments (0)