Implemented file logging settings

Implemented log rotation, log deletion
Minor fixes
This commit is contained in:
Nite 2020-10-02 18:47:21 +02:00
parent 5de20861ca
commit 6e7ebeabef
No known key found for this signature in database
GPG Key ID: 1D1AD59B1C6386C1
18 changed files with 278 additions and 19 deletions

View File

@ -22,6 +22,7 @@ import org.moire.ultrasonic.featureflags.Feature;
import org.moire.ultrasonic.featureflags.FeatureStorage;
import org.moire.ultrasonic.filepicker.FilePickerDialog;
import org.moire.ultrasonic.filepicker.OnFileSelectedListener;
import org.moire.ultrasonic.log.FileLoggerTree;
import org.moire.ultrasonic.provider.SearchSuggestionProvider;
import org.moire.ultrasonic.service.Consumer;
import org.moire.ultrasonic.service.MediaPlayerController;
@ -70,6 +71,7 @@ public class SettingsFragment extends PreferenceFragment
private PreferenceCategory serversCategory;
private Preference resumeOnBluetoothDevice;
private Preference pauseOnBluetoothDevice;
private CheckBoxPreference debugLogToFile;
private SharedPreferences settings;
@ -118,6 +120,7 @@ public class SettingsFragment extends PreferenceFragment
serversCategory = (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_SERVERS_KEY);
resumeOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE);
pauseOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE);
debugLogToFile = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE);
sharingDefaultGreeting.setText(Util.getShareGreeting(getActivity()));
setupClearSearchPreference();
@ -171,6 +174,8 @@ public class SettingsFragment extends PreferenceFragment
setBluetoothPreferences(sharedPreferences.getBoolean(key, true));
} else if (Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY.equals(key)) {
setImageLoaderConcurrency(Integer.parseInt(sharedPreferences.getString(key, "5")));
} else if (Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE.equals(key)) {
setDebugLogToFile(sharedPreferences.getBoolean(key, false));
}
}
@ -415,6 +420,13 @@ public class SettingsFragment extends PreferenceFragment
sendBluetoothAlbumArt.setChecked(false);
sendBluetoothAlbumArt.setEnabled(false);
}
if (debugLogToFile.isChecked()) {
debugLogToFile.setSummary(getString(R.string.settings_debug_log_path,
FileUtil.getUltrasonicDirectory(getActivity()), FileLoggerTree.FILENAME));
} else {
debugLogToFile.setSummary("");
}
}
private static void setImageLoaderConcurrency(int concurrency) {
@ -482,4 +494,47 @@ public class SettingsFragment extends PreferenceFragment
// Clear download queue.
mediaPlayerControllerLazy.getValue().clear();
}
private void setDebugLogToFile(boolean writeLog) {
if (writeLog) {
FileLoggerTree.Companion.plantToTimberForest(getActivity().getApplicationContext());
Timber.i("Enabled debug logging to file");
} else {
FileLoggerTree.Companion.uprootFromTimberForest();
Timber.i("Disabled debug logging to file");
int fileNum = FileLoggerTree.Companion.getLogFileNumber(getActivity());
long fileSize = FileLoggerTree.Companion.getLogFileSizes(getActivity());
String message = getString(R.string.settings_debug_log_summary,
String.valueOf(fileNum),
String.valueOf(Math.ceil(fileSize / 1000000d)),
FileUtil.getUltrasonicDirectory(getActivity()));
new AlertDialog.Builder(getActivity())
.setMessage(message)
.setIcon(android.R.drawable.ic_dialog_info)
.setNegativeButton(R.string.settings_debug_log_keep, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
}
})
.setPositiveButton(R.string.settings_debug_log_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FileLoggerTree.Companion.deleteLogFiles(getActivity());
Timber.i("Deleted debug log files");
dialogInterface.dismiss();
new AlertDialog.Builder(getActivity()).setMessage(R.string.settings_debug_log_deleted)
.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
}).create().show();
}
})
.create().show();
}
}
}

View File

@ -134,6 +134,7 @@ public final class Constants
public static final String PREFERENCES_KEY_FIRST_RUN_EXECUTED = "firstRunExecuted";
public static final String PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE = "resumeOnBluetoothDevice";
public static final String PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE = "pauseOnBluetoothDevice";
public static final String PREFERENCES_KEY_DEBUG_LOG_TO_FILE = "debugLogToFile";
public static final int PREFERENCE_VALUE_ALL = 0;
public static final int PREFERENCE_VALUE_A2DP = 1;

View File

@ -1457,4 +1457,11 @@ public class Util
SharedPreferences preferences = getPreferences(context);
return preferences.getInt(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE, Constants.PREFERENCE_VALUE_A2DP);
}
public static boolean getDebugLogToFile(Context context)
{
SharedPreferences preferences = getPreferences(context);
return preferences.getBoolean(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE, false);
}
}

View File

@ -2,7 +2,6 @@ package org.moire.ultrasonic.app
import androidx.multidex.MultiDexApplication
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.koin.core.logger.Level
import org.moire.ultrasonic.BuildConfig
@ -13,8 +12,8 @@ import org.moire.ultrasonic.di.featureFlagsModule
import org.moire.ultrasonic.di.mediaPlayerModule
import org.moire.ultrasonic.di.musicServiceModule
import org.moire.ultrasonic.log.FileLoggerTree
import org.moire.ultrasonic.log.TimberKoinLogger
import org.moire.ultrasonic.log.timberLogger
import org.moire.ultrasonic.util.Util
import timber.log.Timber
import timber.log.Timber.DebugTree
@ -24,7 +23,9 @@ class UApp : MultiDexApplication() {
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
Timber.plant(FileLoggerTree(this))
}
if (Util.getDebugLogToFile(this)) {
FileLoggerTree.plantToTimberForest(this)
}
startKoin {

View File

@ -1,8 +1,8 @@
@file:JvmName("MusicServiceModule")
package org.moire.ultrasonic.di
import okhttp3.logging.HttpLoggingInterceptor
import kotlin.math.abs
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.android.ext.koin.androidContext
import org.koin.core.qualifier.named
import org.koin.dsl.module

View File

@ -1,37 +1,106 @@
package org.moire.ultrasonic.log
import android.content.Context
import java.io.File
import java.io.FileWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.Util
import timber.log.Timber
import java.io.File
import java.io.FileWriter
/**
* A Timber Tree which can be used to log to a file
* Subclass of the DebugTree so it inherits the Tag handling
*/
class FileLoggerTree(val context: Context) : Timber.DebugTree() {
private val filename = "ultrasonic.log"
private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
/**
* Writes a log entry to file
*/
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
var file: File? = null
var writer: FileWriter? = null
callNum++
try {
file = File(FileUtil.getUltrasonicDirectory(context), filename)
getNextLogFile()
writer = FileWriter(file, true)
val exceptionString = t?.toString() ?: "";
writer.write("${logLevelToString(priority)} $tag $message $exceptionString\n")
writer.flush()
val exceptionString = t?.toString() ?: ""
val time: String = dateFormat.format(Date())
synchronized(file!!) {
writer.write(
"$time: ${logLevelToString(priority)} $tag $message $exceptionString\n"
)
writer.flush()
}
} catch (x: Throwable) {
super.e(x, "Failed to write log to %s", file)
// Using base class DebugTree here, we don't want to try to log this into file
super.log(6, TAG, String.format("Failed to write log to %s", file), x)
} finally {
if (writer != null) Util.close(writer)
}
}
private fun logLevelToString(logLevel: Int) : String {
return when(logLevel) {
/**
* Sets the file to log into
* This function also rotates the log files periodically, when they reach the predefined size
*/
private fun getNextLogFile() {
if (file == null) {
synchronized(this) {
if (file != null) return
getNumberedFile(false)
// Using base class DebugTree here, we don't want to try to log this into file
super.log(4, TAG,String.format("Logging into file %s", file?.name), null)
return
}
}
if (callNum % 100 == 0) {
// Gain some performance by only executing this rarely
if (file!!.length() > MAX_LOGFILE_LENGTH) {
synchronized(this) {
if (file!!.length() <= MAX_LOGFILE_LENGTH) return
getNumberedFile(true)
// Using base class DebugTree here, we don't want to try to log this into file
super.log(
4,
TAG,
String.format("Log file rotated, logging into file %s", file?.name),
null
)
}
}
}
}
/**
* Checks the number of log files
* @param next: if false, sets the current log file with the greatest number
* if true, sets a new file for logging with the next number
*/
private fun getNumberedFile(next: Boolean) {
var fileNum = 1
val fileList = getLogFileList(context)
if (!fileList.isNullOrEmpty()) {
fileList.sortByDescending { t -> t.name }
val lastFile = fileList[0]
val number = fileNumberRegex.find(lastFile.name)?.groups?.get(1)?.value
if (number != null) {
fileNum = number.toInt()
}
}
if (next) fileNum++
file = File(
FileUtil.getUltrasonicDirectory(context),
FILENAME.replace("*", fileNum.toString())
)
}
private fun logLevelToString(logLevel: Int): String {
return when (logLevel) {
2 -> "V"
3 -> "D"
4 -> "I"
@ -41,4 +110,60 @@ class FileLoggerTree(val context: Context) : Timber.DebugTree() {
else -> "U"
}
}
}
companion object {
val TAG = FileLoggerTree::class.simpleName
@Volatile private var file: File? = null
const val FILENAME = "ultrasonic.*.log"
private val fileNameRegex = Regex(
FILENAME.replace(".", "\\.").replace("*", "\\d*")
)
private val fileNumberRegex = Regex(
FILENAME.replace(".", "\\.").replace("*", "(\\d*)")
)
const val MAX_LOGFILE_LENGTH = 10000000
var callNum = 0
fun plantToTimberForest(context: Context) {
if (!Timber.forest().any { t -> t is FileLoggerTree }) {
Timber.plant(FileLoggerTree(context))
}
}
fun uprootFromTimberForest() {
val fileLoggerTree = Timber.forest().singleOrNull { t -> t is FileLoggerTree }
?: return
Timber.uproot(fileLoggerTree)
file = null
}
fun getLogFileNumber(context: Context): Int {
val fileList = getLogFileList(context)
if (!fileList.isNullOrEmpty()) return fileList.size
return 0
}
fun getLogFileSizes(context: Context): Long {
var sizeSum: Long = 0
val fileList = getLogFileList(context)
if (fileList.isNullOrEmpty()) return sizeSum
for (file in fileList) {
sizeSum += file.length()
}
return sizeSum
}
fun deleteLogFiles(context: Context) {
val fileList = getLogFileList(context)
if (fileList.isNullOrEmpty()) return
for (file in fileList) {
file.delete()
}
}
private fun getLogFileList(context: Context): Array<File> {
val directory = FileUtil.getUltrasonicDirectory(context)
return directory.listFiles { t -> t.name.matches(fileNameRegex) }
}
}
}

View File

@ -19,7 +19,7 @@ fun KoinApplication.timberLogger(
/**
* Timber Logger implementation for Koin
*/
class TimberKoinLogger (level: Level = Level.INFO) : Logger(level) {
class TimberKoinLogger(level: Level = Level.INFO) : Logger(level) {
override fun log(level: Level, msg: MESSAGE) {
if (this.level <= level) {

View File

@ -10,4 +10,4 @@ class TimberOkHttpLogger : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Timber.d(message)
}
}
}

View File

@ -401,6 +401,13 @@
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="settings.debug.title">Debug options</string>
<string name="settings.debug.log_to_file">Write debug log to file</string>
<string name="settings.debug.log_path">The log files are available at %1$s/%2$s</string>
<string name="settings.debug.log_summary">There are %1$s log files taking up ~%2$s MB space in the %3$s directory. Do you want to keep these?</string>
<string name="settings.debug.log_keep">Keep files</string>
<string name="settings.debug.log_delete">Delete files</string>
<string name="settings.debug.log_deleted">Deleted log files.</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>

View File

@ -402,6 +402,13 @@
<string name="settings.playback.bluetooth_all">Todos los dispositivos Bluetooth</string>
<string name="settings.playback.bluetooth_a2dp">Solo dispositivos de audio (A2DP)</string>
<string name="settings.playback.bluetooth_disabled">Deshabilitado</string>
<string name="settings.debug.title">Debug options</string>
<string name="settings.debug.log_to_file">Write debug log to file</string>
<string name="settings.debug.log_path">The log files are available at %1$s/%2$s</string>
<string name="settings.debug.log_summary">There are %1$s log files taking up ~%2$s MB space in the %3$s directory. Do you want to keep these?</string>
<string name="settings.debug.log_keep">Keep files</string>
<string name="settings.debug.log_delete">Delete files</string>
<string name="settings.debug.log_deleted">Deleted log files.</string>
<string name="permissions.access_error">Ultrasonic no puede acceder a la caché de los ficheros de música. La ubicación de la caché se restableció a la ruta predeterminada.</string>
<string name="permissions.message_box_title">Atención</string>

View File

@ -402,6 +402,13 @@
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="settings.debug.title">Debug options</string>
<string name="settings.debug.log_to_file">Write debug log to file</string>
<string name="settings.debug.log_path">The log files are available at %1$s/%2$s</string>
<string name="settings.debug.log_summary">There are %1$s log files taking up ~%2$s MB space in the %3$s directory. Do you want to keep these?</string>
<string name="settings.debug.log_keep">Keep files</string>
<string name="settings.debug.log_delete">Delete files</string>
<string name="settings.debug.log_deleted">Deleted log files.</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>

View File

@ -402,6 +402,13 @@
<string name="settings.playback.bluetooth_all">Minden Bluetooth eszköz</string>
<string name="settings.playback.bluetooth_a2dp">Csak audio (A2DP) eszközök</string>
<string name="settings.playback.bluetooth_disabled">Kikapcsolva</string>
<string name="settings.debug.title">Hibakeresési lehetőségek</string>
<string name="settings.debug.log_to_file">Hibakeresési napló írása fájlba</string>
<string name="settings.debug.log_path">A naplófájlok elérhetőek a következő helyen: %1$s/%2$s</string>
<string name="settings.debug.log_summary">%1$s napló fájl ~%2$s MB méretben található a %3$s könyvtárban. Meg szeretnéd atrtani ezeket?</string>
<string name="settings.debug.log_keep">Fájlok megtartása</string>
<string name="settings.debug.log_delete">Fájlok törlése</string>
<string name="settings.debug.log_deleted">Naplófájlok törölve.</string>
<string name="permissions.access_error">Az Ultrasonic nem éri el a zenei fájl gyorsítótárat. A gyorsítótár helye visszaállítva az alapbeállításra.</string>
<string name="permissions.message_box_title">Figyelem</string>

View File

@ -402,6 +402,13 @@
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="settings.debug.title">Debug options</string>
<string name="settings.debug.log_to_file">Write debug log to file</string>
<string name="settings.debug.log_path">The log files are available at %1$s/%2$s</string>
<string name="settings.debug.log_summary">There are %1$s log files taking up ~%2$s MB space in the %3$s directory. Do you want to keep these?</string>
<string name="settings.debug.log_keep">Keep files</string>
<string name="settings.debug.log_delete">Delete files</string>
<string name="settings.debug.log_deleted">Deleted log files.</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>

View File

@ -402,6 +402,13 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="settings.debug.title">Debug options</string>
<string name="settings.debug.log_to_file">Write debug log to file</string>
<string name="settings.debug.log_path">The log files are available at %1$s/%2$s</string>
<string name="settings.debug.log_summary">There are %1$s log files taking up ~%2$s MB space in the %3$s directory. Do you want to keep these?</string>
<string name="settings.debug.log_keep">Keep files</string>
<string name="settings.debug.log_delete">Delete files</string>
<string name="settings.debug.log_deleted">Deleted log files.</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>

View File

@ -402,6 +402,13 @@
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="settings.debug.title">Debug options</string>
<string name="settings.debug.log_to_file">Write debug log to file</string>
<string name="settings.debug.log_path">The log files are available at %1$s/%2$s</string>
<string name="settings.debug.log_summary">There are %1$s log files taking up ~%2$s MB space in the %3$s directory. Do you want to keep these?</string>
<string name="settings.debug.log_keep">Keep files</string>
<string name="settings.debug.log_delete">Delete files</string>
<string name="settings.debug.log_deleted">Deleted log files.</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>

View File

@ -402,6 +402,13 @@
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="settings.debug.title">Debug options</string>
<string name="settings.debug.log_to_file">Write debug log to file</string>
<string name="settings.debug.log_path">The log files are available at %1$s/%2$s</string>
<string name="settings.debug.log_summary">There are %1$s log files taking up ~%2$s MB space in the %3$s directory. Do you want to keep these?</string>
<string name="settings.debug.log_keep">Keep files</string>
<string name="settings.debug.log_delete">Delete files</string>
<string name="settings.debug.log_deleted">Deleted log files.</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>

View File

@ -405,6 +405,13 @@
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
<string name="settings.playback.bluetooth_disabled">Disabled</string>
<string name="settings.debug.title">Debug options</string>
<string name="settings.debug.log_to_file">Write debug log to file</string>
<string name="settings.debug.log_path">The log files are available at %1$s/%2$s</string>
<string name="settings.debug.log_summary">There are %1$s log files taking up ~%2$s MB space in the %3$s directory. Do you want to keep these?</string>
<string name="settings.debug.log_keep">Keep files</string>
<string name="settings.debug.log_delete">Delete files</string>
<string name="settings.debug.log_deleted">Deleted log files.</string>
<string name="permissions.access_error">Ultrasonic can\'t access the music file cache. Cache location was reset to the default path.</string>
<string name="permissions.message_box_title">Warning</string>

View File

@ -305,7 +305,14 @@
a:title="@string/feature_flags_five_star_rating_title"
a:summary="@string/feature_flags_five_star_rating_description"
/>
</PreferenceCategory>
<PreferenceCategory a:title="@string/settings.debug.title">
<CheckBoxPreference
a:defaultValue="false"
a:key="debugLogToFile"
a:title="@string/settings.debug.log_to_file"
a:summary=""
/>
</PreferenceCategory>
</PreferenceScreen>