Skip to content

Conversation

@ParaskP7
Copy link
Contributor

@ParaskP7 ParaskP7 commented Sep 9, 2025

Fixes: #16
Closes: AINFRA-1217
(Internal) Discussions: p1754667446732699-slack-C030U03RC8Y + p1757585328963649-slack-C030U03RC8Y


Description

This PR resolves a long standing OutOfMemory (OOM) error when using this library alongside logs that are of large size.

FYI: Actually, this change resolves a couple more (potential) issues like:

Overall, this change:

  1. Introduces a MAX_LOG_FILE_SIZE of 5MB, just because the resulting encrypted text is about 1.4 larger per MB of a log file, resulting in a final max of about 7MB that is sent to the API. We could have used 6MB as the max log file size, which would result in 8.4MB of encrypted text sent, but it is better to keep enough buffer just in case, and this way, to safeguard ourselves from any InvalidRequest responses from the API.
  2. Based on this MAX_LOG_FILE_SIZE of 5MB, if a log's file size exceeds this max, it is truncated to that 5MB of size, skipping the beginning of the log and only keep the more recent logs.

Testing Steps

Using the DOAndroid project, update automattic-encryptedlogging to 28-f19c0d92473042e5976c92cd7b88cf641456691a (version which is based on this PR's latest commit) and make sure that encrypted logging could work with logs of large size.

Note

I would first advise you to replicate an OOM using your physical/virtual device of choice. To do so, update automattic-encryptedlogging to 28-a16584d1c883c758c29e8e40f8bba0c1320ff567 instead. This commit skips the uploading of the encrypted log to the API, and just handles the encryption part.

Tip

FYI: You could use the below DOAndroid patch, which add the Fill The Log development option into the Developer screen of the app and on click adds 10000 log lines (or else 4MB worth of logs). Adding another 0 to that numberOfLogs val will give you 40MB instead, use what you need:

patch
Subject: [PATCH] Tmp: Assemble release on ci for pr
---
Index: app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperAction.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperAction.kt b/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperAction.kt
--- a/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperAction.kt	(revision f2622054d961b58bf298d4ab91049be79c19a87b)
+++ b/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperAction.kt	(date 1757609295757)
@@ -14,6 +14,8 @@
 
     data object CrashApp : DeveloperAction()
 
+    data object FillLog : DeveloperAction()
+
     data object DeleteAllJournals : DeveloperAction()
 
     data object ReloadCurrentMasterKeyStorage : DeveloperAction()
Index: app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperFragment.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperFragment.kt b/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperFragment.kt
--- a/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperFragment.kt	(revision f2622054d961b58bf298d4ab91049be79c19a87b)
+++ b/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperFragment.kt	(date 1757609295757)
@@ -141,6 +141,20 @@
                 }
             }
             DeveloperAction.CrashApp -> throw Exception("Developer caused a crash!")
+            DeveloperAction.FillLog -> {
+                val numberOfLogs = 10000 // 4MB
+                (0..numberOfLogs).forEachIndexed { index, _ ->
+                    DOLogger.d(TAG, "Filling log line $index " +
+                            "[FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST " +
+                            "FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST " +
+                            "FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST " +
+                            "FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST FILL LOG TEST]"
+                    )
+                    if (index % (numberOfLogs/5) == 0) {
+                        Toast.makeText(context, "Filled $index log lines", Toast.LENGTH_SHORT).show()
+                    }
+                }
+            }
             DeveloperAction.DeleteAllJournals -> deleteAllJournals()
             DeveloperAction.ReloadCurrentMasterKeyStorage -> viewModel.reloadMasterKeyStorage()
             DeveloperAction.EnterMasterKey -> enterMasterKey()
Index: app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperComponentList.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperComponentList.kt b/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperComponentList.kt
--- a/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperComponentList.kt	(revision f2622054d961b58bf298d4ab91049be79c19a87b)
+++ b/app/src/main/java/com/dayoneapp/dayone/main/settings/DeveloperComponentList.kt	(date 1757609295757)
@@ -413,6 +413,10 @@
         title = { Text(text = stringResource(R.string.crash_app)) },
         onClick = { onClick(DeveloperAction.CrashApp) },
     )
+    SettingItem(
+        title = { Text(text = stringResource(R.string.fill_log)) },
+        onClick = { onClick(DeveloperAction.FillLog) },
+    )
     if (BuildConfig.DEBUG) {
         SettingItem(
             title = { Text(text = stringResource(R.string.purge_welcome_entries)) },
Index: app/src/main/res/values/strings.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
--- a/app/src/main/res/values/strings.xml	(revision f2622054d961b58bf298d4ab91049be79c19a87b)
+++ b/app/src/main/res/values/strings.xml	(date 1757609295758)
@@ -881,6 +881,7 @@
     <string name="reset_passive_message_settings">Reset Passive Messages Settings</string>
     <string name="reset_today_tooltip_message_settings">Reset Today Tooltip Message Settings</string>
     <string name="crash_app">Crash The App</string>
+    <string name="fill_log">Fill The Log</string>
     <string name="enable_premium_dev">Enable Premium Dev</string>
     <string name="should_loggedin_premium_dev">You Should be Logged In To Enable Premium Dev</string>
     <string name="purge_welcome_entries">Purge possible welcome entries</string>

PS: Don't forget to use the Device Explorer to check on the actual size of the log right before you cause a crash (using another Crash The App development option from the Developer screen of the app. File log path: /data/data/com.dayoneapp.dayone.debug/files/dayonelogs/com.dayoneapp.dayone.debug YYYY-MM-DD.log

Follow the below generic steps with a log of 2MB, 10MB, 50MB and 200MB file size:

  1. Using App Inspection from AS find the encrypted-logging.db and its sole EncryptedLogModel table. Notice that the table is empty, there are no log to be sent.
  2. Make the app crash. Using DOAndroid for example, you could navigate to Settings -> Developer and then click Crash The App.
  3. You might now notice a new db entry within App Inspection from AS on that DETACHED app (or not, it depends). Now, switch to the ATTACHED app and notice the db empty. This is because this db entry/log has been already sent to Sentry already, on app start and after the app crashed. To really notice the entry, trying crashing the app again, this time put the device on offline mode first. Doing that you'll be able to see the entry on the db, even when switching to the newly created ATTACHED app.
  4. Open Sentry and navigate to Issues. Find the project you're testing for (for example day-one-android or/and pocket-casts-android) and check the latest debug exceptions. Using DOAndroid for example, you could search for Developer caused a crash! and check the latest crash. Now navigate at the bottom and check the uuid. Verify it is the same UUID that you saw on that db entry before.
  5. Open Read Encrypted Logs, copy-paste that UUID into Log UUID, click View and make sure you can read the newly uploaded and now decrypted log.

For a log of specific size (see above) expect to get the following results, all without any OOM, or ANR, or an InvalidRequest response from the API, and the app should launch instantly, that is, after the crash, without any delay:

  1. 2MB:
    • ℹ️ Enqueueing sending encrypted logs. Log file: com.dayoneapp.dayone.debug YYYY-MM-DD.log, 2.00mb
    • ℹ️ The size of encrypted text with uuid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is: 2.80mb
    • ℹ️ Successfully uploaded encrypted log with uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  2. 10MB:
    • ℹ️ Enqueueing sending encrypted logs. Log file: com.dayoneapp.dayone.debug YYYY-MM-DD.log, 10.00mb
    • ⚠️ Log file with uuid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is too big (10.00mb), max allowed log file size is 5.00mb; log file got truncated.
    • ℹ️ The size of encrypted text with uuid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is: 7.00mb
    • ℹ️ Successfully uploaded encrypted log with uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  3. 50MB:
    • ℹ️ Enqueueing sending encrypted logs. Log file: com.dayoneapp.dayone.debug YYYY-MM-DD.log, 50.00mb
    • ⚠️ Log file with uuid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is too big (50.00mb), max allowed log file size is 5.00mb; log file got truncated.
    • ℹ️ The size of encrypted text with uuid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is: 7.00mb
    • ℹ️ Successfully uploaded encrypted log with uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  4. 200MB:
    • ℹ️ Enqueueing sending encrypted logs. Log file: com.dayoneapp.dayone.debug YYYY-MM-DD.log, 200.00mb
    • ⚠️ Log file with uuid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is too big (200.00mb), max allowed log file size is 5.00mb; log file got truncated.
    • ℹ️ The size of encrypted text with uuid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX is: 7.00mb
    • ℹ️ Successfully uploaded encrypted log with uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

@ParaskP7 ParaskP7 requested a review from Copilot September 9, 2025 17:13
@ParaskP7 ParaskP7 added the bug Something isn't working label Sep 9, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes an OutOfMemoryError occurring in the LogEncrypt.encrypt(...) method by temporarily disabling the actual log upload functionality while preserving the encryption logging for debugging purposes.

  • Commented out the actual REST client upload call to prevent OOM issues
  • Added debug logging to track successful encryption operations
  • Modified success message to reflect that uploads are being handled rather than actually uploaded

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

FYI: This change is for debugging purposes so that to avoid uploading
log files that are too big, and until we figure out how big a log file
should be to cause this OOM problem. Then, to try to fix the problem and
see if 'logEncrypter.encrypt(...)' could now handle such big log files.
@ParaskP7 ParaskP7 force-pushed the fix/oom-at-log-encrypter-encrypt branch 6 times, most recently from 8c6bb2f to 5e85f84 Compare September 11, 2025 13:10
@ParaskP7 ParaskP7 requested a review from Copilot September 11, 2025 13:45
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

FYI: This 5MB max logs size limit is both arbitrary and empirical
because it seems that for (at least) some standard devices, large logs
files of 100MB+ can easily cause OOM. Also, this 5MB max logs size limit
is chosen because the API accepts logs up to 10MB, but we leave enough
headroom for encoding overhead.

PS: It is worth to note that we could apply a different kind of a
solution (better one), like streaming encryption or chunked processing.
But, I don't think it is worth it, nor I think it is worth sending such
large log files to the API.
FYI: Even after fixing the logs size problem during encryption, if the
file is too big (200MB+), just because of the 'file.readText()', which
tries to read the entire file into memory as string, another OOM problem
occurs, this time before the encryption logic even runs.

By file size checking and truncating the file to the last 10MB (if too
big), we are avoiding this OOM issue. The current approach:
- Creates a temp file with only the last 10MB of data.
- Reads the temp file content and returns it.
- Cleans up the temp file in the finally block.
- Leaves the original file completely untouched.
@ParaskP7 ParaskP7 force-pushed the fix/oom-at-log-encrypter-encrypt branch from 5e85f84 to 7ae6f7d Compare September 11, 2025 13:49
@ParaskP7 ParaskP7 force-pushed the fix/oom-at-log-encrypter-encrypt branch 4 times, most recently from 16c06ca to 4a7c828 Compare September 11, 2025 15:03
@ParaskP7 ParaskP7 requested a review from Copilot September 11, 2025 15:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Now that the initial fix (6990d7c) got
reverted (b007944) there is no point
referring to a 'MAX_IN_MEMORY_SIZE' constant, but rather a
'MAX_LOG_FILE_SIZE' constant which also adheres to what the API accepts,
which is logs up to 10MB.

FYI: Although the API accepts logs up to 10MB, we use 5MB to leave
enough room for the encryption overhead (and in order to avoid 400
failure with upload turning into invalid requests).

PS: Actually, while testing this solution I also tried working with 9MB,
then 8MB, then 7MB, all resulting in 400 ('InvalidRequest'). Only when I
tried using 6MB the API started accepting the truncated encrypted log.
As such, an in order to give enough buffer, I set the final value to
5MB, just in case 6MB isn't enough for every case.
@ParaskP7 ParaskP7 force-pushed the fix/oom-at-log-encrypter-encrypt branch from 4a7c828 to a999320 Compare September 11, 2025 15:27
@ParaskP7 ParaskP7 force-pushed the fix/oom-at-log-encrypter-encrypt branch from 9c1878e to f19c0d9 Compare September 11, 2025 15:45
@ParaskP7 ParaskP7 marked this pull request as ready for review September 11, 2025 16:51
@ParaskP7 ParaskP7 requested a review from wzieba September 11, 2025 16:51
val encryptedText = logEncrypter.encrypt(text = encryptedLog.file.readText(), uuid = encryptedLog.uuid)
val encryptedText = if (encryptedLog.file.length() <= MAX_LOG_FILE_SIZE) {
logEncrypter.encrypt(
text = encryptedLog.file.readText(),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cap the file at 5 MB but still load it into memory with readText(). This inflates size (UTF-16 + base64) and may still cause OOM on low-RAM devices. Could LogEncrypter.encrypt support ByteArray or InputStream for streaming/chunked encryption to avoid building a full String?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cap the file at 5 MB but still load it into memory with readText(). This inflates size (UTF-16 + base64) and may still cause OOM on low-RAM devices.

That's true @harrytmthy and thanks for the note here! 💯

Could LogEncrypter.encrypt support ByteArray or InputStream for streaming/chunked encryption to avoid building a full String?

We could, we'll try this first, since the API anyway doesn't accept encrypted log files larger than 10MB and take it from there. 👍

@ParaskP7
Copy link
Contributor Author

👋 @wzieba just a friendly reminder about this! 🙏

Copy link
Member

@wzieba wzieba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, leaving one minor nitpick

PR Comment: #28
#discussion_r2355022277

Co-authored-by: Wojciech Zięba <wojtek.zieba@automattic.com>
@ParaskP7
Copy link
Contributor Author

Thanks for reviewing/testing and leaving your suggestions on this @wzieba , much appreciated! 🙇 ❤️ 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Out Of Memory at com.automattic.encryptedlogging.model.encryptedlogging.LogEncrypter.encrypt

4 participants