iOS Unified Logs: The Myth of 30 Days Retention - Analysis of TTLs and log stats Command
- Lionel Notari
- il y a 6 heures
- 21 min de lecture
Disclaimer: The information and analyses presented in this article are the result of personal research and testing. They are provided for informational and educational purposes only. Any commercial use of the contents of this article without prior written permission from the author is strictly prohibited. Users are not authorized to copy, reproduce, transmit, or distribute the contents of this article without permission. The author disclaims any responsibility for the consequences of using the information contained in this article. For permission requests, please contact the author directly.
Over the past few months, iOS Unified Logs have received more and more attention, not only because of the articles and research I’ve shared here, but also thanks to the excellent work of Christian Peter and, more recently, Tim Korver. These logs are now seen as a valuable source of forensic information. But despite this growing interest, they still remain poorly documented, and some important parts of how they work are not well understood.
In my previous articles, I mostly focused on identifying and documenting the relevant unified logs in specific situations in order to reconstruct the user activity/interactions or to show, in details, how the device was used. To do this, I mainly use the log show --predicate command, a powerful way to search through Unified Logs by selecting specific events. For example, it allows me to extract logs generated by specific processes or containing certain strings. But today, I’d like to turn the spotlight on another tool that deserves just as much attention: the log stats command.
You may already know log collect, which is used to extract logs, or log show, which prints them in the Terminal. But log stats is probably less known. Still, trust me, this command is incredibly interesting. I even use it directly in my forensic tool to generate statistics about every extracted logarchive.
With log stats, you can get a clear overview of a log extraction, but also learn important details about the logs themselves, how they behave, how long they live, and what types of data they really contain. Thanks to this command, I was able to "reverse engineer" the Time-to-Live (TTL) values used in iOS Unified Logs. And what I discovered may surprise you (or not): the idea that Unified Logs are kept for 20 to 30 days is mostly a myth.
Log stats command's quick presentation
When talking about iOS Unified Logs, most people already know the main commands. log collect is used to retrieve logs from a device, log show and log stream display them in the terminal, and of course, the popular log show --predicate helps filter and extract exactly what you’re looking for. But there is another command, much less famous, but incredibly useful: log stats. Quiet and almost forgotten in official documentation, this command actually offers a unique perspective on logarchives: Its goal is not to list every single event or give you endless pages of raw logs (thanks!).
No, log stats takes a step back and gives you, in just a few seconds, a global summary of your logarchive: the compressed and uncompressed size, how many events were collected, how many are regular logs, traces, or signposts, how many errors or faults were recorded, which processes generated the most activity, and even how long the logs are meant to stay on the device (the well-known TTL, which we’ll talk about soon).
How to use it?
Generating the general overview of a logarchive with the log stats command is very simple. In a macOS Terminal, you just need to type: log stats --archive <path_to_logarchive>. Wait a few seconds for the process to run, and the first statistics will be displayed. This general overview is automatically generated when you use my forensic tool, and it is included in the extraction report:

To get help and see all the available options for this command, the easiest way is to use the help menu by typing log stats help in your Terminal:

The log stats command offersdifferent options and ways to use it, allowing us to dive in a logarchive with a level of detail that can be useful for forensic investigations.
First, the --archive <archive> option i the key if you want to analyze a specific logarchive. Next, the --count <count> option (or all) lets you choose how many results should be displayed in each section of the report generated by log stats. By default, only the top 5 lines are shown, but you can easily increase this number, for example, by using --count 20 to get a more complete view.
We can also customize how the results are sorted using the --sort option, which lets us choose between sorting by number of events or by size of data (bytes). Honestly, sorting by number of events is very useful, for example, to quickly see which processes generated the most events. On the other hand, I almost never sort my results by bytes.
The output of the command can be formatted in two ways: either in human-readable mode (--style human, which is the default and displays the results nicely formatted for the Terminal), or in JSON mode (--style json). For all of my research presented in this article, I kept the default option. Log stats also offers several ways to look at the data :
--overview : Display statistics for the entire logarchive (default)
--per-book : Display statistics per log book in the archive
--per-file : Display statistics per file in the archive
--sender <sender> : Display statistics for a given sender in the archive
--process <process> : Display statistics for a given process in the archive
--predicate <predicate> : Display statistics for all events in the archive that match the given predicate. Probably the most powerful option, allowing us to focus the statistic analysis on a specific subset of events based on advanced criteria.
In this article, we’ll mostly focus on using --archive, --process, and --predicate, because these options show how useful log stats can be when you’re trying to better understand and work with a log extraction.
A first concrete example
Just to make things more concrete, let’s try the log stats command on an actual logarchive extracted from an iPhone. Here’s the result we run the basic command

Let’s take a closer look at the image and the statistics . The stats command provides the following information:
The compressed and uncompressed size
The date of the first and last log in the archive (this may already be relevant for potential backdating analysis)
The number of statedumps (of limited, if any, forensic use)
The total number of events (22'400'693 in this case), along with a breakdown by event type:
Log
Trace
Signpost
Loss
A breakdown of activities
A detailed breakdown of event logs from the logarchive
A breakdown by Time To Live (TTL) - this aspect will be further explored later in the article
A breakdown by process, allowing identification of the processes that generated the most logs
Similarly, a breakdown by sender to identify those that produced the highest volume of logs
The image above shows the default output of the log stats command when we run it on a logarchive. However, this is only a starting point, there is much more to explore. As shown earlier, the command accepts a lot of different parameters that allow us to refine and extend these statistics. We will return to this in the next part of the article. First of all, these statistics were a bit tricky and caused me some trouble. So let’s take a moment to look at a few interesting points.
How is the total of events and unified logs calculated?
At first, maybe in a naive way, I thought the total was simply calculated like this (just as a reminder, the total number of events in the logarchive I’m using here is 22,400,693 events):
Total = Log + Trace + Signpost + Loss
In other words, with the numbers of my logarchive, it would be:
22'400'693 = 21'007'511 + 0 + 326'371 + 2'322
Actually, that’s a big mistake! And if you don’t have a calculator nearby, trust me, this doesn’t work at all. The result of that addition is… 21'336'204, which means there’s a difference of 1'064'489! That’s far from a small gap. But don’t worry, we’ll go over everything step by step. Do you remember the “activity” category? We also have to include it in the total because activities are a specific type of event. Personally, I find it quite odd that the total number of activities isn’t listed under the “Events” line. The total number of logs is there, and its breakdown is shown just below. Apple could have done the same for activities. In fact, the total number of activities isn’t shown at all; we only get a breakdown of this category. Let’s update our calculation, the second version now becomes:
Total = Log + Trace + Signpost + Loss + Activity
That is, 21'336'204 + 1'045'483 = 22'381'687
A quick look and… we’re still not there! The difference is now 19'006. That’s the moment I almost pulled my hair out. Remember the statedumps? Let’s add them too (even if it’s clear this still won’t be enough). Here’s a third version of the calculation:
Total = Log + Trace + Signpost + Loss + Activity + Statedump
-> 22'381'687 + 18'724 = 22'400'411
After trying to recalculate everything, it was when I started looking into the eventType field that I finally understood the issue. In fact, according to the log help predicates command, an event can have one of the following eventType values:

All of these eventType values appear in the general overview of the statistics… except one! Do you have it? That’s right, the timesyncEvent does not appear in the main statistics overview, even though it is included in the total number of events! The fact that timesyncEvent is not shown in the log stats overview, yet is still counted in the total number of events, is not an isolated case, it happens with every logarchive. Once again, I don’t understand Apple’s logic behind this. It’s really unclear.
Here’s another example, this time from an extraction on iOS 18.0:

When calculating the total, we get:
16'563'088 + 0 + 120'825 + 2'016 + 1'137'369 + 0 + 30 + 11'554 = 17'834'882
which gives a difference of 94 compared to the total displayed. Once again, very close but not correct!
Fortunately, the --predicate option allows us to extract events of type timesyncEvent, which I did using the following command: log stats --archive Extraction_iOS18.logarchive --predicate 'eventType="timesyncEvent"'

which allows us to recover the 94 missing events from the total! It’s now a perfect match for the iOS 18 logarchive. I had planned to do the same verification for my first example (with the 282 missing logs), but unfortunately, I accidentally deleted that logarchive while cleaning up my computer (which, to be fair, is overflowing with them) and I wasn’t able to recover it.
So I can only encourage you to try this yourself on your own logarchives.
Other small issues...
Here are a few other small issues with these statistics that I’d like to discuss. We’ve now understood how the total number of logs is calculated, but there are also some inaccuracies related to logEvent and activityEvent. Let’s start with logEvent and, since I accidentally deleted my first logarchive (no comment), we’ll continue with the one named iOS18 (in the end, it doesn’t make a difference).
logEvent Issues
In the general overview, the total number of logEvents is next to the total number of all events. The “log messages” section gives us a quick breakdown of the different types of logs.

However, one “log message” type is missing: the “release” type, which is listed when using the command log help predicates. This is another inconsistency...

Another inconsistency appears when we display only the statistics for the logEvents:

Analyzing the statistics for the logEvents only using the --predicate 'eventType="logEvent"' command does not give us exactly the same results! While the total number of logEvent events is consistent in both cases (16'563'088 events), the number of default log messages differs!
In the first case, 16'563'088 logEvent entries result in 13'583'517 default log messages, whereas in the second case, the same number of logEvent entries produces only 13'462'692 default log messages.
To find out which one is accurate, we simply need to do the math. Adding up all the log message types should give us the total number of logEvent entries.
1st case (general overview): 13'583'517 + 812'935 + 20'594 + 2'260'311 + 6'556 = 16'683'613
2nd case (LogEvent only): 13'462'692 + 812'935 + 20'594 + 2'260'311 + 6'556 = 16'563'088
We therefore notice an issue in the general overview: the breakdown of log messages does not match the total number of logEvent entries. So, if we subtract this total from the actual number of log events, we get the following result:
16'683'613 - 16'563'088 = 120'025
→ Which corresponds exactly to the number of signpostEvent

In other words, signpostEvent entries also have a default log message. Now, this isn’t a major issue in itself, but it does show how tricky it can be to fully understand these statistics, even though the overview table appears quite straightforward at first glance.
Finally, when looking closely at the --predicate command, it shows us the following valid types for signposts:

No “default”… maybe there’s a link between all of this that I haven’t figured out yet.
activtyEvent issues
One last detail I wanted to mention concerns the activityEvents. These are generally not the most interesting events for forensic purposes, but still worth discussing. The activityEvents include the following three types: activityCreateEvent, activityTransitionEvent, and… userActionEvent, not activityActionEvent!

By the way, if we look again at the valid eventType values from the predicate command, activityActionEvent does not exist, unlike userActionEvent. This is just a small detail, but I don’t really know why this specific activity was named with a user prefix instead of activity, like the others.
Let’s dive a little deeper into the details
Overview of the log stats command options
So far, we’ve analyzed the general overview provided by the log stats command, but it’s also possible to get much more detailed statistics!
Time to Live Analysis - Events retention
Another point that really caught my attention while analyzing the log stats command is the mention of “ttl”, with values such as “1day”, “3days”, “7days”, “14days”, and “30days”. This is very interesting, it means our logs don’t all have the same retention period! From a forensic point of view, this is crucial. It’s always important to know how long relevant traces are kept. As a reminder, the stats were as follows:

First disappointment: we can see that only a small number of events are kept for more than 14 days. In my example, out of 22 million events, only a little over 2 million are stored for 14 days or more, that’s approx. 10%! Unfortunately, unified logs are not always kept for 20 to 30 days. The real retention time is much shorter!
This simple chart reminds us, and confirms, how important it is not to wait before extracting logs.
Let’s try to be even more precise. First, just like I did for the other categories, I took the numbers given by the log stats command and added them together. Since all events have a TTL, it was important to understand how this part worked as well. So I started by adding the values for “1day”, “3days”, “7days”, “14days”, and “30days”, which gave me:
202'337 + 2'084'416 + 1'381'578 + 2'602'634 + 23'333 = 6'294'298.
So, there are a little over 6 million events with a retention period between 1 and 30 days. But our archive contains 22 million events… so where is the rest? We saw earlier that it’s possible to use the predicate option to narrow down the scope of events to which the log stats command is applied. The predicate option is very commonly used when extracting logs, I personally use it a lot with the log show command to extract logs of interest from a logarchive. So it’s very interesting to see that predicate also works with log stats, not to extract logs, but to apply statistics!
The predicate command can be applied to a wide range of fields, here they are!

There are many possible fields to filter events. The most popular ones are probably process and composedMessage, which are often used to extract logs from specific processes with the command log show. But in this case, I focused on the timeToLive field to better understand the retention period of each event… and I have a lot to share!
This timeToLive option helps us investigate where the 16 million events are, the ones that are not included in the TTL breakdown between 1 and 30 days. I’ve been studying and documenting unified logs for quite some time now, so I already had a theory: all the other logs must have a TTL equal to 0.
Here’s the command I used in my Mac Terminal: sudo log stats --archive archiveName --predicate 'timeToLive=0'
And here are the results. As expected, a large number of logs have a timeToLive set to 0!

Let’s do a quick calculation (yes, another one, and no, I didn’t like math in school, in case you were wondering…):
We had a total of 22,400,693 events in the logarchive, 16,106,395 of which had a TTL of 0, and 6,294,298 had a different TTL:
16'106'395 + 6'294'298 = 22'400'693
That’s perfect, everything matches! We now reach the conclusion that all events in a logarchive include a TTL, with the majority (approximately 72% in my case) having a TTL of 0. From this, a few important questions come to mind:
Is this 72% of TTL = 0 events a standard value?
Do the TTL statistics in the logarchive, which only mention 1 day, 3 days, 7 days, 14 days, and 30 days, really give us the full picture?
From a forensic point of view, how long can we realistically expect logs with TTL = 0 to remain available?
I suggest that we take some time to look at each of these questions, one by one. And, before we continue exploring the rest of the statistics, I believe it’s necessary to determine whether this 72% of TTL = 0 events can be considered reasonable or not.
More than 70% of TTL = 0… is that expected?
To answer that, I extracted a logarchive from several Apple devices using my forensic tool, and analyzed the statistics of each archive.
To keep things clear and readable, I decided to first create a summary table listing the devices and their logarchives, and a second table showing the detailed statistics for each one.
Logarchive Name | Device | OS | Capacity (GB) | Extraction Date | Compressed SIze (GB) | Uncompressed Size (GB) |
---|---|---|---|---|---|---|
LogCollect.logarchive | iPhone Xʀ | iOS 18.4.1 | 64 | 2025-04-26 | 0.81 | 1.82 |
Extraction2.logarchive | iPhone Xʀ | iOS 18.4.1 | 64 | 2025-04-27 | 0.73 | 1.60 |
Extraction3.logarchive | iPhone Xʀ | iOS 18.4.1 | 64 | 2025-05-03 | 0.83 | 1.75 |
System Logs.logarchive | Mac mini (2023) | macOS 14.6.1 | 256 | 2025-05-03 | 0.98 | 3.00 |
iPhone14.logarchive | iPhone 14 | iOS 18.4.1 | 128 | 2025-05-03 | 1.04 | 2.13 |
Extraction_iOS18.logarchive | iPhone SE (2020) | iOS 18.0 | 64 | 2025-05-04 | 0.55 | 1.26 |
Extraction_iOS15.logarchive | iPhone SE (2016) | iOS 15.5 | 16 | 2025-05-04 | 0.15 | 0.35 |
Table 1 - Devices
As shown in Table 1, I used several different iPhone models with various iOS versions. I also decided to include my Mac Mini to see whether my conclusions apply to devices other than iPhones. However, I should clarify that apart from the iPhone 14 and the Mac mini, the other devices are only test devices and not used on a daily basis.
In a second step, here are the general statistics of each logarchive, generated using the command log stats --archiveName:
Logarchive Name | Total events | TTL = 0 (%) | 1day (%) | 3days (%) | 7days (%) | 14days (%) | 30days (%) |
---|---|---|---|---|---|---|---|
LogCollect.logarchive | 22'400'693 | 16'106'395 (71.88%) | 202'337 (0.90%) | 2'084'416 (9.30%) | 1'381'578 (6.17%) | 2'602'634 (11.62%) | 23'333 (0.10%) |
Extraction2.logarchive | 18'296'546 | 16,063,531 (87.83%) | 110'827 (0.61%) | 867'331 (4.74%) | 451'730 (2.47%) | 769'928 (4.21%) | 33'199 (0.18%) |
Extraction3.logarchive | 23'408'088 | 16,302,888 (69.63%) | 314'085 (1.34%) | 2'499'076 (10.67%) | 888'005 (3.79%) | 3'374'290 (14.41%) | 29'744 (0.13%) |
System_Logs.logarchive | 27'138'225 | 22'397'388 (82.55%) | 53'969 (0.20%) | (840'998) 3.10% | 1'612'803 (5.94%) | 2'080'909 (7.37%) | 152'158 (0.56%) |
iPhone14.logarchive | 26'857'204 | 18'727'462 (69.75%) | 1'405'553(5.23%) | 3'811'096 (14.19%) | 1'519'742 (5.66%) | 1'356'673 (5.05%) | 36'678 (0.14%) |
Extraction_iOS18.logarchive | 17'834'976 | 15'993'417 (89.63%) | 272'472 (1.53%) | 872'696 (4.89%) | 232'529 (1.30%) | 445'039 (2.49%) | 18'823 (0.11%) |
Extraction_iOS15.logarchive | 5'023'300 | 4'187'106 (83.35%) | 118'971 82.37%) | 131'327 (2.61%) | 242'522 (4.83%) | 339'550 (6.76%) | 3'824 (0.1%) |
Table 2 - Statistics per logarchive - Genarated with the command log stats --archiveName
The first thing that clearly stands out in the table above is the low number of events with a timeToLive longer than two weeks (14 days), no matter the device. I find this really interesting. It shows that most logs will only remain available for a few days. While it’s already known that after 30 days almost nothing is left, this data suggests that after just two weeks , or maybe even earlier, most logs are already gone. I’m not trying to sound alarmist, but this table perfectly shows why unified logs need to be preserved and extracted as quickly as possible from the device of interest. Every day counts.

Looking at the chart above, I feel confident saying that getting more than 70% of events with a timeToLive of 0 is completely normal. My Mac mini even shows over 80% of events with a TTL of 0. It seems that, most of the time, Apple events simply don’t have a specific timeToLive. This clearly doesn’t make our work easier, it’s hard to know exactly how long an event with a TTL of 0 will stay available. But to be honest, I doubt they stay in memory for more than a few days…
But this raises one more important question… What about the logs created by the user, which could help us understand the sequence of events, do they have a timeToLive shorter or longer than 14 days? I’ll leave that question open for now.
1day, 3days, 7days, 14day, 30day ... is that accurate ?
Now that we’ve looked closely at TTL = 0, which, by the way, is not even shown in the statistics from the log stats command, can we trust the values for TTL 1day, 3days, and so on? As you might guess, if I chose to explore this question, the answer is no! To show this, I used only three previously extracted logarchives. That will be enough to look at the results. I kept one logarchive from my iPhone Xr (iOS 18.4.1), one from my Mac mini, and one from my (very) old iPhone SE (iOS 15.5), so we can compare different types of devices and OS.
First, a quick reminder: the command log stats is very useful because it gives statistics not only for the whole logarchive but also for a smaller selection of logs, using the “predicate” option. That’s exactly what I used here. Let’s now take another look at the numbers I mentioned earlier:
Logarchive name | TTL = 0 | 1day | 3days | 7days |
---|---|---|---|---|
Extraction3.logarchive | 16,302,888 | 314'085 | 2'499'076 | 888'005 |
System_Logs.logarchive | 22'397'388 | 53'969 | 840'998 | 1'612'803 |
Extraction_iOS15.logarchive | 4'187'106 | 118'971 | 131'327 | 242'552 |
To get the statistics on the number of events with a TTL of one day, I can run the following command: log stats --archiveName --predicate "timeToLive=1". Let’s now take a look at what this gives us for the logarchives mentioned above:



First observation: we get exactly what we expected with TTL 1! The statistics obtained using the command with --predicate "timeToLive=1" match perfectly with the general overview shown under the “1day” section. It’s a match! It’s also interesting to note that this works not only for recent iPhones running iOS 18, but also for macOS and even for an iPhone that’s nearly ten years old. Tat's a great start but it’s time to get serious and move on to “3days”. Just to recap, the general overview of the three extracted logarchives gave us the following results:
Perhaps a bit naively, I expected that the command --predicate "timeToLive=3" would give the same result as the “3days” value shown in the general overview, since it worked perfectly for “1day”… but let me tell you right away, that’s not the case! Here is what we get with the command --predicate "timeToLive=3":



In this case, it doesn’t work at all! For the Extraction3 logarchive, we had close to 2'500'000 events under the “3days” ttl, and about 840'000 for the system_logs logarchive. How can we explain this difference? I’ll come back to that shortly. To check this further, I used the same method for “7days”. Would it work as it did for 1day, or would I see the same mismatch as with 3days? Let’s take a look:



Conclusion: once again, it doesn’t work! Before we dive into an analysis and try to understand why, let’s first summarize the results we got for the different “timeToLive” values:
Results: 1day vs TTL = 1
Logarchive name | Overview: 1day | --predicate TTL = 1 | Difference | Conclusion |
---|---|---|---|---|
Extraction3.logarchive | 314'085 | 314'085 | 0 | It's a match! |
system_logs.logarchive | 53'969 | 53'969 | 0 | It's a match! |
Extraction_iOS15.logarchive | 118'971 | 118'971 | 0 | It's a match! |
Results: 3days vs TTL = 3
Logarchive name | Overview: 3days | --predicate TTL = 3 | Difference | Conclusion |
---|---|---|---|---|
Extraction3.logarchive | 2'499'076 | 1'817'437 | 681'639 | No match! |
system_logs.logarchive | 840'998 | 404'818 | 436'180 | No match! |
Extraction_iOS15.logarchive | 131'327 | 65'412 | 65'915 | No match! |
Results: 7days vs TTL = 7
Logarchive name | Overview: 7days | --predicate TTL = 7 | Difference | Conclusion |
---|---|---|---|---|
Extraction3.logarchive | 888'005 | 740'105 | 147'900 | No match! |
system_logs.logarchive | 1'612'803 | 1'134'838 | 477'965 | No match! |
Extraction_iOS15.logarchive | 242'552 | 222'515 | 20'037 | No match! |
Since the results for TTL = 1 and “1day” matched perfectly, there’s no need to mention them again. What interests me now is understanding the differences we see for the 3days and 7days timeToLive events. One important clarification: to keep my tables and this article from getting too long, I chose to not include the results for the “14days” and “30days” values. That said, I did run the same tests on those TTLs, and the results were the same as with “3days” and “7days”. In other words, the results do not match.
I won’t keep the suspense going any longer, in the end, the explanation is actually quite simple. But let’s be honest, it took me some time to figure it out. Up to this point, I had been running my statistics using predicates like timeToLive=0, timeToLive=1, timeToLive=3, and timeToLive=7. But here’s the real question: could some events have a timeToLive=2, 4, 5, or 6? Absolutely! There’s nothing stopping us from running those queries, and they won’t return any errors either.
--predicate "timeToLive=2"



--predicate "timeToLive=4"



--predicate "timeToLive=5"



--predicate "timeToLive=6"
For the three test archives, the command timeToLive=6 returned 0 events. Therefore, there is no image to show for this case and, to save space, it will not be included in the tables below.
Results analysis
Do you notice something quite interesting at first glance? If not, go back and review the “match / no match” tables, then take another look at the timeToLive values 2, 4, and 5. Here’s the answer! We can quickly notice that the difference between the “3days” result and the result from the command --predicate "timeToLive=3" matches exactly the number of events returned by --predicate "timeToLive=2". Very interesting! It seems that in the main statistics table (what I like to call the general overview), the label “3days” is actually the total number of events with a timeToLive of 2 or 3 days. In other words, the “3days” ttl isn’t really precise!
Logarchive name | Overview: 3days | --prediacte TTL=3 | Difference | --predicate TTL=2 | TTL = 2 + TTL = 3 | Conclusion |
---|---|---|---|---|---|---|
Extraction3.logarchive | 2'499'076 | 1'817'437 | 681'639 | 681'639 | 2'499'076 | Match! |
system_logs.logarchive | 840'998 | 404'818 | 436'180 | 436'180 | 840'998 | Match! |
Extraction_iOS15.logarchive | 131'327 | 65'412 | 65'915 | 65'915 | 131'227 | Match! |
Now that we have shown this for the “3days”, is it the same for “7days”? Yes, we can see the same behavior! The “7days”, in the general overview is actually the result of: timeToLive=4 + timeToLive=5 + timeToLive=6 + timeToLive=7.
Logarchive name | Overview: 7days | --prediacte TTL=7 | Difference | --predicate TTL=4 | --predicate TTL=5 | TTL=4 + TTL=5 + TTL=6 + TTL=7 | Conclusion |
---|---|---|---|---|---|---|---|
Extraction3.logarchive | 888'005 | 740'105 | 147'900 | 19'872 | 128'028 | 888'805 | Match! |
system_logs.logarchive | 1'612'803 | 1'134'838 | 477'965 | 171'348 | 306'617 | 1'612'803 | Match! |
Extraction_iOS15.logarchive | 242'552 | 222'515 | 20'037 | 3'905 | 17'102 | 242'552 | Match! |
And it also works the same way for the next timeToLive values! Yes, believe me, we have logs with a TTL of 8 days or other "random" numbers:

In conclusion, here's how the timeToLive are grouped in the general overview statistics displayed in the Terminal when using the command log stats:
1day = timeToLive=1 (TTL=1)
3days = TTL=2 + TTL=3
7days = TTL=4 + TTL=5 + TTL=6 + TTL=7
14days = TTL=8 + TTL=9 + TTL=10 + … + TTL=14
30days = TTL=15 + TTL=16 + … + TTL=29 + TTL=30
They are not exact values but actually represent a range!
The forensic consequences
We absolutely need to address the consequences of a TTL set to 0. From a forensic point of view, how long can we expect to find these logs in a logarchive, and why is their TTL set to 0?
Unfortunately, as you might expect, there is very little documentation on this topic (to not say "not at all"). Still, I tend to believe that a TTL of 0 means these logs are not kept for very long, probably only a few days max if the device is used daily. But I can’t say if “a few days” means closer to 2 or to 8. Honestly, it’s impossible to know without more testing.
However, one thing we do know is that the Unified Logs system works on a rolling basis: new logs are written over older ones as space is needed. Because of this, it’s likely that logs with TTL = 0 are replaced quickly by newer ones. In practice, they probably overwrite each other in order of creation. This means that if these logs are not collected quickly, they may disappear and be lost forever.
Let’s take a closer look at the SpringBoard process. This process is very important in forensic investigations because it logs many events when the user interacts with their phone. Here is a summary of the statistics I found during my tests:
Logarchive Name | Number of SpringBoard logs | SpringBoard TTL=0 | Percentage of TTL=0 |
---|---|---|---|
Extraction_iOS18.logarchive | 246'554 | 205'222 | 83.2% |
Extraction3.logarchive | 172'797 | 115'570 | 67% |
To find these statistics, I used the following commands:
Number of SpringBoard logs: log stats --archive Extraction_iOS18.logarchive --process "SpringBoard"
Number of SpringBoard logs with TTL=0: log stats --archive Extraction_iOS18.logarchive --predicate 'process=="SpringBoard" && timeToLive=0'
By typing this in the Terminal, we can see that there are Unified Logs with a TTL = 0 for the SpringBoard process that are several days old:

Well, I don’t use this test iPhone every day, quite the opposite, I only use it from time to time. That might explain why I was able to find such old logs. So, it may not be the best example, even though it’s still interesting. As I mentioned earlier, we can go even further in the analysis and get statistics about a single log of interest, for example:

Conclusion
In this article, we saw how the log stats command works and how it can help us to learn more about a logarchive. The command gives useful information statistics and be used with in concerdance with predicate options to better understand a small set of the data.
The key point was how the TTL (Time To Live) of the Unified Logs worked and not only on iOS as we also analyzed example from macOS. "Categories" like 3days, 7days and the other ones are not exact at all but include a range of several TTL values. For example, the "3days" group includes logs with TTL values of 2 and 3 and "7days" logs with TTL values from 4 to 7 days. Unfortunately, the majority of the Unified Logs seems to have a small TTL which does not facilitate our digital investigation work! Many events, over 70%, have a timeToLive of 0 which is not displayed in the main statistics table. This means that these logs may only stay available for a very short time. As we all know .... it is important to preserve and extract them as quickly as possible if they are needed in an investigation.
We also noticed some small problems in how Apple shows the statistics. For example, the log type “release” is listed in the official documentation when you use log help predicates, but it doesn’t appear in the summary given by log stats in the log messages section. Or, the default logEvent type includes the signpostEvent in the general overview which creates some confusion when analyzing them separately . Overall, even if there are some inconsistencies in the numbers, which are not a big problem, I still think this command should be used more often.
Enjoy your Digital Investigations!
Lionel Notari