-
Notifications
You must be signed in to change notification settings - Fork 0
[#117] Send Sentry report if file path given for logs is not valid #122
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
e80145e
b4aa890
9dc80ed
bc76b68
3213bfd
5fdd885
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package com.steamclock.steamclog | ||
|
|
||
| import android.content.Context | ||
| import androidx.datastore.core.DataStore | ||
| import androidx.datastore.preferences.preferencesDataStore | ||
| import androidx.datastore.preferences.core.Preferences | ||
| import androidx.datastore.preferences.core.booleanPreferencesKey | ||
| import androidx.datastore.preferences.core.edit | ||
| import kotlinx.coroutines.flow.Flow | ||
| import kotlinx.coroutines.flow.map | ||
|
|
||
| /** | ||
| * https://developer.android.com/topic/libraries/architecture/datastore | ||
| */ | ||
| class SClogDataStore(private val context: Context) { | ||
| companion object { | ||
| private val Context.SClogDataStore: DataStore<Preferences> by preferencesDataStore(name = "SClogDataStore") | ||
| private val hasReportedFilepathErrorKey = booleanPreferencesKey("has_logged_file_creation_failure") | ||
| } | ||
|
|
||
| /** | ||
| * hasReportedFilepathError indicates if Steamclog has reported a Sentry error regarding | ||
| * it's inability to use the given filePath to store logs. | ||
| */ | ||
| val getHasReportedFilepathError: Flow<Boolean> | ||
| get() = context.SClogDataStore.data.map { | ||
| it[hasReportedFilepathErrorKey] ?: false | ||
| } | ||
| suspend fun setHasReportedFilepathError(value: Boolean) { | ||
| context.SClogDataStore.edit { it[hasReportedFilepathErrorKey] = value } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,11 @@ | |
|
|
||
| package com.steamclock.steamclog | ||
|
|
||
| import android.content.Context | ||
| import io.sentry.Sentry | ||
| import io.sentry.protocol.User | ||
| import kotlinx.coroutines.flow.first | ||
| import kotlinx.coroutines.runBlocking | ||
| import org.jetbrains.annotations.NonNls | ||
| import timber.log.Timber | ||
| import java.io.File | ||
|
|
@@ -48,13 +51,41 @@ object SteamcLog { | |
| // Don't plant yet; fileWritePath required before we can start writing to ExternalLogFileDestination | ||
| } | ||
|
|
||
| fun initWith(config: Config) { | ||
| fun initWith(context: Context, config: Config) { | ||
| this.config = config | ||
| this.config.fileWritePath?.let { | ||
|
|
||
| // Setup ExternalLogFileDestination | ||
| if (this.config.fileWritePath != null && this.config.fileWritePath!!.exists()) { | ||
| updateTree(externalLogFileTree, true) | ||
| } ?: run { | ||
| logInternal(LogLevel.Warn, "fileWritePath given was null; cannot log to external file") | ||
| } else { | ||
| // We have seen issues where some devices are failing to support some of the default file | ||
| // paths (ie. externalCacheDir); the code below attempts to catch that case and report it | ||
| // proactively to Sentry as an error once per app install. | ||
| val sentryErrorTitle = "Steamclog could not create external logs" | ||
| logInternal(LogLevel.Info, "File path ${this.config.fileWritePath} is invalid") | ||
|
|
||
| try { | ||
| // Attempt to log error only once to avoid overwhelming Sentry. | ||
| // runBlocking usage was recommended in the google docs as the way to synchronously | ||
| // call the datastore methods; we need to use this with caution, but | ||
| // https://developer.android.com/topic/libraries/architecture/datastore#synchronous | ||
| val dataStore = SClogDataStore(context) | ||
| val alreadyReportedFailure = runBlocking { dataStore.getHasReportedFilepathError.first() } | ||
|
||
| val isSentryEnabled = clog.config.logLevel.remote != LogLevel.None | ||
|
|
||
| if (isSentryEnabled && !alreadyReportedFailure) { | ||
| logInternal(LogLevel.Error, sentryErrorTitle) | ||
| runBlocking { dataStore.setHasReportedFilepathError(true) } | ||
| } else { | ||
| logInternal(LogLevel.Warn, sentryErrorTitle) | ||
| } | ||
| } catch (e: Exception) { | ||
| logInternal(LogLevel.Info, "Could not read local DataStore") | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Safeguarding against any error that might occur with DataStore; since this is the first I've worked with it I want to make sure that failures with it do cause the app to crash. |
||
| logInternal(LogLevel.Info, e.message ?: "No error message given") | ||
| logInternal(LogLevel.Error, sentryErrorTitle) | ||
| } | ||
| } | ||
|
|
||
| logInternal(LogLevel.Info, "Steamclog initialized:\n$this") | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious to know why
baseContextis passed in here here, butapplicationContextis passed intoSClogDataStoreUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrm. Honestly I think that was a discrepancy I missed. Because we're using the context to lookup the datastore instance I don't think it matters which we use (although I could be wrong), but you're right, to be safe we should probably ensure we are using the same context. The examples I have found all use
Context, but seem to be passing in the application context. To make this completely explicit I have changed my code to request theApplicationinstead of theContext(Application is a ContextWrapper); I don't know if there's ramifications to this, but it seems to behave the same.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
passing in Application is the right call. Activity Context is tied to the Activity lifecycle, so we don't want any potential issues from carrying out operations tied to an Activity lifecycle.