1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-22 00:06:48 +02:00

- Implemented a fileMutex to synchronize file access operations, preventing potential race conditions when writing to or exporting the log file.

- Increased `MAX_LOG_SIZE_BYTES` from 5 MiB to 10 MiB.
This commit is contained in:
oliexdev
2025-08-19 21:07:26 +02:00
parent 8c7a62c401
commit 01fd3214aa
2 changed files with 108 additions and 86 deletions

View File

@@ -20,6 +20,7 @@ package com.health.openscale.core.utils
import android.app.Activity import android.app.Activity
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
@@ -27,7 +28,10 @@ import com.health.openscale.BuildConfig
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.FileWriter import java.io.FileWriter
@@ -49,7 +53,9 @@ object LogManager {
private const val LOG_SUB_DIRECTORY = "logs" private const val LOG_SUB_DIRECTORY = "logs"
private const val CURRENT_LOG_FILE_NAME_BASE = "openScale_current_log" private const val CURRENT_LOG_FILE_NAME_BASE = "openScale_current_log"
private const val LOG_FILE_EXTENSION = ".txt" private const val LOG_FILE_EXTENSION = ".txt"
private const val MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024 // 5 MB private const val MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024 // 10 MiB
private val fileMutex = Mutex()
private var isInitialized = false private var isInitialized = false
private var logToFileEnabled = false private var logToFileEnabled = false
@@ -249,45 +255,35 @@ object LogManager {
// Log to file if enabled // Log to file if enabled
if (logToFileEnabled) { if (logToFileEnabled) {
val formattedMessageForFile = val formatted = formatMessageForFile(priority, currentTag, message, throwable)
formatMessageForFile(priority, currentTag, message, throwable)
coroutineScope.launch { coroutineScope.launch {
fileMutex.withLock {
try { try {
val currentLogFile = val currentLogFile = File(getLogDirectory(), "$CURRENT_LOG_FILE_NAME_BASE$LOG_FILE_EXTENSION")
File(getLogDirectory(), "$CURRENT_LOG_FILE_NAME_BASE$LOG_FILE_EXTENSION")
// Ensure the log file's parent directory exists.
// This is a safeguard, though getLogDirectory should handle it.
currentLogFile.parentFile?.mkdirs() currentLogFile.parentFile?.mkdirs()
// Check if file is missing or empty, then write headers.
// This can happen if the file was cleared or logging was just enabled.
if (!currentLogFile.exists() || currentLogFile.length() == 0L) { if (!currentLogFile.exists() || currentLogFile.length() == 0L) {
d( writeInitialLogHeaders(currentLogFile)
TAG,
"Log file missing or empty, writing headers: ${currentLogFile.absolutePath}"
)
writeInitialLogHeaders(currentLogFile) // This will create/overwrite with headers
isMarkdownBlockOpen = true isMarkdownBlockOpen = true
} }
checkAndRotateLog(currentLogFile) // Rotate log if it exceeds max size checkAndRotateLog(currentLogFile)
// Append the log message
OutputStreamWriter( OutputStreamWriter(
FileOutputStream(currentLogFile, true), FileOutputStream(currentLogFile, true),
StandardCharsets.UTF_8 StandardCharsets.UTF_8
).use { writer -> ).use { w ->
writer.append(formattedMessageForFile) w.append(formatted)
writer.append("\n") w.append("\n")
} }
} catch (e: IOException) { } catch (e: IOException) {
// Log error related to file writing to Logcat only, to avoid recursive file logging issues.
Log.e(TAG, "Error writing to log file: ${e.message}", e) Log.e(TAG, "Error writing to log file: ${e.message}", e)
} }
} }
} }
} }
}
/** /**
* Writes initial header information to the specified log file. * Writes initial header information to the specified log file.
@@ -377,36 +373,23 @@ object LogManager {
private fun checkAndRotateLog(currentLogFile: File) { private fun checkAndRotateLog(currentLogFile: File) {
if (currentLogFile.exists() && currentLogFile.length() > MAX_LOG_SIZE_BYTES) { if (currentLogFile.exists() && currentLogFile.length() > MAX_LOG_SIZE_BYTES) {
val oldFileSize = currentLogFile.length() val oldFileSize = currentLogFile.length()
i( i(TAG, "Log file '${currentLogFile.name}' (size: $oldFileSize bytes) exceeds limit ($MAX_LOG_SIZE_BYTES bytes). Rotating.")
TAG,
"Log file '${currentLogFile.name}' (size: $oldFileSize bytes) exceeds limit ($MAX_LOG_SIZE_BYTES bytes). Rotating."
)
try { try {
OutputStreamWriter( if (isMarkdownBlockOpen) {
FileOutputStream(currentLogFile, true), OutputStreamWriter(FileOutputStream(currentLogFile, true), StandardCharsets.UTF_8).use {
StandardCharsets.UTF_8
).use {
it.append("\n```").append("\n") it.append("\n```").append("\n")
} }
isMarkdownBlockOpen = false isMarkdownBlockOpen = false
} catch (_: IOException) {
} }
} catch (_: IOException) {}
if (currentLogFile.delete()) { if (currentLogFile.delete()) {
i( i(TAG, "Oversized log file deleted: ${currentLogFile.name}. A new log file will be started with headers.")
TAG,
"Oversized log file deleted: ${currentLogFile.name}. A new log file will be started with headers."
)
// Immediately open a new header and start a fresh ```diff block.
writeInitialLogHeaders(currentLogFile) writeInitialLogHeaders(currentLogFile)
isMarkdownBlockOpen = true isMarkdownBlockOpen = true
} else { } else {
e( e(TAG, "Failed to delete oversized log file '${currentLogFile.name}' for rotation.")
TAG,
"Failed to delete oversized log file '${currentLogFile.name}' for rotation. Current log may continue to grow or writes may fail."
)
} }
} }
} }
@@ -497,6 +480,9 @@ object LogManager {
if (!isInitialized) return if (!isInitialized) return
val logFile = File(getLogDirectory(), "$CURRENT_LOG_FILE_NAME_BASE$LOG_FILE_EXTENSION") val logFile = File(getLogDirectory(), "$CURRENT_LOG_FILE_NAME_BASE$LOG_FILE_EXTENSION")
if (!logFile.exists() || !isMarkdownBlockOpen) return if (!logFile.exists() || !isMarkdownBlockOpen) return
coroutineScope.launch {
fileMutex.withLock {
try { try {
OutputStreamWriter(FileOutputStream(logFile, true), StandardCharsets.UTF_8).use { OutputStreamWriter(FileOutputStream(logFile, true), StandardCharsets.UTF_8).use {
it.append("\n```").append("\n") it.append("\n```").append("\n")
@@ -507,13 +493,18 @@ object LogManager {
Log.e(TAG, "Error closing markdown diff block", e) Log.e(TAG, "Error closing markdown diff block", e)
} }
} }
}
}
@JvmStatic @JvmStatic
fun ensureMarkdownBlockOpen() { fun ensureMarkdownBlockOpen() {
if (!isInitialized || !logToFileEnabled || isMarkdownBlockOpen) return if (!isInitialized || !logToFileEnabled || isMarkdownBlockOpen) return
val logFile = File(getLogDirectory(), "$CURRENT_LOG_FILE_NAME_BASE$LOG_FILE_EXTENSION") val logFile = File(getLogDirectory(), "$CURRENT_LOG_FILE_NAME_BASE$LOG_FILE_EXTENSION")
try {
if (!logFile.exists() || logFile.length() == 0L) return if (!logFile.exists() || logFile.length() == 0L) return
coroutineScope.launch {
fileMutex.withLock {
try {
OutputStreamWriter(FileOutputStream(logFile, true), StandardCharsets.UTF_8).use { OutputStreamWriter(FileOutputStream(logFile, true), StandardCharsets.UTF_8).use {
it.append("```diff\n") it.append("```diff\n")
} }
@@ -523,4 +514,48 @@ object LogManager {
Log.e(TAG, "Error reopening markdown diff block", e) Log.e(TAG, "Error reopening markdown diff block", e)
} }
} }
}
}
@JvmStatic
suspend fun exportLogToUri(context: Context, targetUri: Uri): Boolean {
if (!isInitialized) return false
return try {
fileMutex.withLock {
val logFile = File(getLogDirectory(), "$CURRENT_LOG_FILE_NAME_BASE$LOG_FILE_EXTENSION")
if (!logFile.exists()) return@withLock false
if (isMarkdownBlockOpen) {
try {
OutputStreamWriter(FileOutputStream(logFile, true), StandardCharsets.UTF_8).use {
it.append("\n```").append("\n")
}
isMarkdownBlockOpen = false
} catch (e: IOException) {
Log.e(TAG, "Error closing markdown block before export", e)
}
}
context.contentResolver.openOutputStream(targetUri)?.use { out ->
logFile.inputStream().use { it.copyTo(out) }
} ?: return@withLock false
if (logToFileEnabled) {
try {
OutputStreamWriter(FileOutputStream(logFile, true), StandardCharsets.UTF_8).use {
it.append("```diff\n")
}
isMarkdownBlockOpen = true
} catch (e: IOException) {
Log.e(TAG, "Error reopening markdown block after export", e)
}
}
true
}
} catch (t: Throwable) {
Log.e(TAG, "Error exporting log file", t)
false
}
}
} }

View File

@@ -105,24 +105,11 @@ fun GeneralSettingsScreen(
val logFileToCopy = LogManager.getLogFile() val logFileToCopy = LogManager.getLogFile()
if (logFileToCopy != null && logFileToCopy.exists()) { if (logFileToCopy != null && logFileToCopy.exists()) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
try { val ok = LogManager.exportLogToUri(context, uri)
LogManager.closeMarkdownBlock()
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
logFileToCopy.inputStream().use { inputStream ->
inputStream.copyTo(outputStream)
}
}
LogManager.ensureMarkdownBlockOpen()
scope.launch { scope.launch {
if (ok) {
sharedViewModel.showSnackbar(context.getString(R.string.log_export_success)) sharedViewModel.showSnackbar(context.getString(R.string.log_export_success))
} } else {
} catch (e: Exception) {
LogManager.e("GeneralSettingsScreen", "Error exporting log file", e)
LogManager.ensureMarkdownBlockOpen()
scope.launch {
sharedViewModel.showSnackbar(context.getString(R.string.log_export_error)) sharedViewModel.showSnackbar(context.getString(R.string.log_export_error))
} }
} }