Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#115] Improved log file auto rotation; now attaching last 2 files to user reports #116

Merged
merged 13 commits into from
Aug 14, 2023

Conversation

ssawchenko
Copy link
Collaborator

@ssawchenko ssawchenko commented Jul 28, 2023

Related Issue: #115

Summary of Problem:

  • We wanted to add a second log file to our User Reports to cover the case where we have just cycled our logs due to the auto rotate expiry period rolling over.
  • I noticed while testing this that our auto rotate logic wasn't working 100% correctly, such that files were not actually expiring when expected; I decided to fix that here as well.
    • We don't have access to a file's "created time"; because of this we were using lastModified time to determine expiry which was causing us to re-select the most recent log to use instead of rotating to the next log.

Proposed Solution:

  • SteamclogThrowableWrapper now takes a attachLogFiles boolean (instead of the logAttachmentUrl File); if true the ExternalLogFileDestination will determine the URLs for the log files to attach (ie. the most recent 2).
  • getLogFiles now allows us to sort by lastModified time ascending and descending
  • findAvailableLogFile now filters out the current log from being an option when determining the next log to rotate to
  • Updated sample app to make it easier to test out auto rotation

Testing Steps:

  1. Spark up the sample app; the sample app is now set to rotate through logs every 10 seconds
  2. Every 10 seconds or so do the following: press "Add logs" and then "Show file dump"
  3. Make sure that the logs listed appear to cycle every 10 seconds:
    image
  4. Go to the Sentry page for the sample app: https://steamclock-software.sentry.io/issues/?project=5399932
  5. Select all issues, click the triple dot and delete them to make a clean slate
  6. Go back to the sample app, change the log level to Release
  7. Press the "Report Single Error" and "Send User Report" buttons and then press the "Show File Dump" button again
  8. Wait a few seconds for Sentry to update and make sure you see two reports:
    image
  9. Drill into the "This is my user report" and the scroll all the way to the bottom. Make sure the two reports listed match the two most recent logs shown in the dump. They don't need to be in the same "order", but the files included should match.
Sentry Attachments Listed in Sample app
image image

}

logMetaData += "\nFull Content:\n\n" + SteamcLog.getFullLogContents()
binding.demoText.text = logMetaData
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updating sample app to print out the list of log files so I can properly test the auto rotation feature

return externalLogFileTree.getExternalFile()
}

fun deleteLogFile() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't actually support manual delete due to auto rotation; removing to avoid confusion

enum class LogSort {
LastModifiedAsc,
LastModifiedDesc
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We need to sort the logs in different orders in various places, so I made the setting explicit

currentLogFileCachedTime = Date().time
// Only update if we are changing files; this helps us simulate when a file was "created" so
// that we can cycle every 10 mins
if (currentLogFile == file) { return }
Copy link
Collaborator Author

@ssawchenko ssawchenko Jul 28, 2023

Choose a reason for hiding this comment

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

Before we were not checking for this, which was causing the currentLogFileCachedTime value to be incorrectly updated each time, meaning that the expiry calculation couldn't succeed as expected if the app was continually writing logs.

private var currentLogFileCachedTime: Long? = null
// Since we cannot get a file's created time, we make note of the timestamp when the
// logFile was originally marked as being "current".
private var logFileCreatedTimeEstimate: Long? = null
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Renamed to indicate we're trying to spoof the created time

Copy link
Member

@nbrooke nbrooke left a comment

Choose a reason for hiding this comment

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

Logic looks correct / matching iOS to me (minus the known difference in rotation behaviour due to iOS using creation time and Android only having modified time), but @phileo should definitely too a more careful pass on the actual Kotlin / Android code.

@nbrooke nbrooke removed their assignment Aug 1, 2023
return currentFile
// Find new available log file and update cached variables
cachedCurrentLogFile = findAvailableLogFile(expiryMs)
logFileCreatedTimeEstimate = Date().time
Copy link

Choose a reason for hiding this comment

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

what kind of problems would using cachedCurrentLogFile.lastModifiedTime() cause

Copy link

Choose a reason for hiding this comment

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

Also, this seems to imply that all 10 log files will have the same logFileCreatedTimeEstimate even though each logfile could be selected at different times, is that expected behaviour

Copy link
Collaborator Author

@ssawchenko ssawchenko Aug 2, 2023

Choose a reason for hiding this comment

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

what kind of problems would using cachedCurrentLogFile.lastModifiedTime() cause

Honestly I'm using Date().time here because that was what was being set before in the original code implemented by Julie... but you're right - the file returned by findAvailableLogFile will either be within the valid expiry window OR will have just been cleared such that the last modified date essentially is now. I'll play around with this and see if it has any side effects I can't think of.

Edit: I've updated the code to use lastModifiedTime instead and everything seems to function correctly. Since this is a better value to use for an estimated creation date let's go with it!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also, this seems to imply that all 10 log files will have the same logFileCreatedTimeEstimate even though each logfile could be selected at different times, is that expected behaviour

Yes, technically any of the files could be selected to be used as the next available file (although the logic attempts to pick the oldest one), so in theory, yes that was the expected behaviour when the code was first written I believe.

With the changes I have made in your first comment though, this will no longer be the case. Now a selected file should have a logFileCreatedTimeEstimate of the last modified time so they would not all be the same.

val currentLogFileCachedTime = currentLogFileCachedTime
return cachedCurrentLogFile != null &&
val currentLogFileCachedTime = logFileCreatedTimeEstimate
return logFileCreatedTimeEstimate != null &&
currentLogFileCachedTime != null &&
Copy link

Choose a reason for hiding this comment

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

not sure I understand the logic behind this, you just assigned currentLogFileCachedTime = logFileCreatedTimeEstimate, so aren't we checking the same thing twice?

Copy link
Collaborator Author

@ssawchenko ssawchenko Aug 2, 2023

Choose a reason for hiding this comment

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

This was done to avoid the IDE error:

Smart cast to 'Long' is impossible, because 'logFileCreatedTimeEstimate' is a mutable property that could have been changed by this time

I looked around online and it seemed like creating a local variable was a reasonable way to work around this: https://stackoverflow.com/questions/44595529/smart-cast-to-type-is-impossible-because-variable-is-a-mutable-property-tha

The return statement then makes sure that both the file and the estimated start time have been set (ie. aren't null) and then that that the file has not yet expired, so I don't think we're checking anything twice, but let me know if I'm missing something.

Copy link

Choose a reason for hiding this comment

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

Ah, I see.
What about doing it this way:

val currentLogFileCachedTime = logFileCreatedTimeEstimate ?: return false

I think that should ensure both the file and the estimated start time have been set, and then you should be able to remove both null checks

Copy link
Collaborator Author

@ssawchenko ssawchenko Aug 3, 2023

Choose a reason for hiding this comment

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

Whoops I totally see the duplication! Also that's a good idea. Makes it much cleaner! Done :)

@ssawchenko ssawchenko requested a review from phileo August 2, 2023 17:27
@phileo phileo assigned ssawchenko and unassigned phileo Aug 3, 2023
@ssawchenko
Copy link
Collaborator Author

@phileo Fixed up your final comment, and I also fixed an issue I found due to the lastModification value not being set on new files (was causing the first file to be auto cycled too quickly).

Copy link

@phileo phileo left a comment

Choose a reason for hiding this comment

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

looks good!
👍

@ssawchenko ssawchenko merged commit ed0899e into main Aug 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants