Page MenuHomePhabricator

Implement unencrypted log capture without attachments
ClosedPublic

Authored by marcin on Jan 26 2024, 6:42 AM.
Tags
None
Referenced Files
Unknown Object (File)
Sat, Nov 23, 7:23 AM
Unknown Object (File)
Mon, Nov 11, 2:38 PM
Unknown Object (File)
Sat, Nov 9, 6:51 PM
Unknown Object (File)
Wed, Nov 6, 4:35 PM
Unknown Object (File)
Fri, Nov 1, 8:19 PM
Unknown Object (File)
Oct 15 2024, 12:39 PM
Unknown Object (File)
Oct 15 2024, 12:39 PM
Unknown Object (File)
Oct 15 2024, 12:38 PM
Subscribers

Details

Summary

This differential implements API that enables to monitor and capture backup logs. At this point logs are unencrypted, without attachments. Future differentials will improve those deficiencies. This diff doesn't break the app since log capture is not actually called yet.

How does log capture work?

  1. We attach session to database connection and session starts to accumulate logs.
  2. Each time we call captureLogs we read logID from metadata table in database, store log to file identified by this logID and finally write incremented logID to the database.
Test Plan
  1. Add a call to DatabaseManager::getQueryExecutor().captureBackupLogs() just before DatabaseManager::getQueryExecutor().commitTransaction() in BaseDataStore.h.
  2. Use the app: make drafts, send messages, update threads colours etc..
  3. Periodically download app container from XCode.
  4. Ensure that there are created backup-0-log-xxx files that contain unreadable bytes mixed with readable strings with database table names and database entities fields values.

Diff Detail

Repository
rCOMM Comm
Lint
No Lint Coverage
Unit
No Test Coverage

Event Timeline

marcin edited the test plan for this revision. (Show Details)
Harbormaster returned this revision to the author for changes because remote builds failed.Jan 26 2024, 7:00 AM
Harbormaster failed remote builds in B26182: Diff 36164!
native/cpp/CommonCpp/DatabaseManagers/SQLiteConnectionManager.cpp
12 ↗(On Diff #36164)

I don't want to create new file for each log. I think we should rather accumulate X logs in a single file and the create new file with new log id. This constant is X. We can set it to arbitrary number.

114 ↗(On Diff #36164)

This method handles the case where we were storing logs to file identified by certain logID but the app was closed/killed before logsCount exceeded threshold and/or before it could update logID in the database. In such case we have to increment logID and start new file.

native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
643 ↗(On Diff #36164)

This code is not needed anymore.

native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h
24 ↗(On Diff #36164)

I decided to couple database connection and logs session ina single object to avoid the possibility that connection is closed before the session is deleted. According to SQLite docs deleting a session (or doing anything with it) if the connection it was created for was closed results in undefined behaviour (which possibly means segmentation fault).

native/cpp/CommonCpp/DatabaseManagers/SQLiteConnectionManager.cpp
150 ↗(On Diff #36164)

I plan to run this call in separate thread as a part of ENG-6226. However I will first do some performance tests to measure how much time does it take on average to capture log and see how important it actually is to introduce additional thread.

marcin edited the summary of this revision. (Show Details)

Fix Android compilation

Harbormaster returned this revision to the author for changes because remote builds failed.Jan 26 2024, 8:07 AM
Harbormaster failed remote builds in B26189: Diff 36171!
  1. Use connection manager on both native and web but with different implementations
  2. Change aproach to log capture that will allow us to ensure no log is missed
marcin edited the test plan for this revision. (Show Details)
native/cpp/CommonCpp/DatabaseManagers/NativeConnectionManager.cpp
104–105 ↗(On Diff #36483)

Session object is not "zeroed" after capturing the log (see here name) - reading from the same session multiple times will give us all captured changes from the beginning of session instantiation. Therefore we have to delete and recreate session after capturing logs.

michal added inline comments.
native/cpp/CommonCpp/DatabaseManagers/NativeConnectionManager.cpp
89 ↗(On Diff #36483)

You are calling sqlite3session_patchset but everything is named as changeset which is also a term applicable to the sqlite session api. Could you rename changeset -> patchset?

104–105 ↗(On Diff #36483)

Can you add a code comment with this information? I feel like it's very non-obvious

native/cpp/CommonCpp/DatabaseManagers/NativeConnectionManager.h
6 ↗(On Diff #36483)

Nit: maybe NativeSQliteConnectionManager?

This revision is now accepted and ready to land.Feb 2 2024, 9:50 AM
michal requested changes to this revision.Feb 2 2024, 10:12 AM

Actually one question: logs won't be captured if backupID isn't set. It's set during compaction creation and only primary device will run this code. So the code is correct, but it will currently still create a session on all devices, including secondary devices. Additionally, on the secondary device, the session will never be cleared (detached). Do you think this could have a performance impact/ it's worth only enabling session if we have created a compaction?

This revision now requires changes to proceed.Feb 2 2024, 10:12 AM
native/cpp/CommonCpp/DatabaseManagers/NativeConnectionManager.h
6 ↗(On Diff #36483)

It was actually my initial idea but I was afraid that reviewers will find it too verbose.

native/android/app/CMakeLists.txt
220–221 ↗(On Diff #36483)

shouldn't we add the same flags to run_emscripten.sh? I know it won't be used but just to make sure we use the same engine configs

native/cpp/CommonCpp/DatabaseManagers/NativeConnectionManager.cpp
54 ↗(On Diff #36483)

Wondering if using std::ofstream::write() without std::string and operator<< isn't better when it comes to binary data, also maybe it's faster?
I remember having some issues in the past with it doing something similar in C++, especially with null characters but since you test it it's probably okay.

98–100 ↗(On Diff #36483)

is it possible to have changesetPtr not null but changesetSize == 0? In that case shouldn't we call sqlite3_free(changesetPtr);?

native/android/app/CMakeLists.txt
220–221 ↗(On Diff #36483)

They are added in the diff that introduces restore from logs functionality.

  1. Rename NativeConnectionManager -> NativeSQLiteConnectionManager
  2. Use wrapper method for error handling
  3. Ensure backup logs aren't captured if there is not compaction
  4. Ensure that only tables we have ops for are backed up via logs
  5. Handle edge case when patchset size is sero but pointer is not null
  6. Use std::ofstream::write
  7. Rename changeset to patchset
  8. Add comment explaining why we delete and recreate session on each capture.
This revision is now accepted and ready to land.Feb 7 2024, 3:15 AM

Last minute bug fix: don't create log file if patchset is empty