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:
@@ -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")
|
||||||
}
|
}
|
||||||
@@ -524,3 +515,47 @@ object LogManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user