Coverage Summary for Class: LogxFileWriter (kr.open.library.simple_ui.core.logcat.internal.writer)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| LogxFileWriter |
83.3%
(5/6)
|
50%
(4/8)
|
75.7%
(28/37)
|
65.3%
(154/236)
|
| LogxFileWriter$1 |
100%
(1/1)
|
83.3%
(5/6)
|
100%
(8/8)
|
100%
(57/57)
|
| LogxFileWriter$requestClose$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/24)
|
| LogxFileWriter$writeLines$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/25)
|
| LogxFileWriter$WriterCommand |
|
| LogxFileWriter$WriterCommand$Close |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(2/2)
|
| LogxFileWriter$WriterCommand$WriteLines |
100%
(1/1)
|
|
100%
(5/5)
|
100%
(22/22)
|
| Total |
72.7%
(8/11)
|
64.3%
(9/14)
|
79.2%
(42/53)
|
64.2%
(235/366)
|
package kr.open.library.simple_ui.core.logcat.internal.writer
import android.content.Context
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kr.open.library.simple_ui.core.logcat.config.LogxConfigSnapshot
import java.io.IOException
/**
* 파일 로그를 비동기로 기록하는 writer입니다.
*
* Asynchronous file log writer backed by a coroutine channel.
* <br><br>
* 코루틴 채널을 통해 파일 로그를 비동기로 기록합니다.
*/
internal class LogxFileWriter {
/**
* 파일 writer 명령을 표현하는 내부 커맨드입니다.
*
* Command types consumed by the writer coroutine.
* <br><br>
* writer 코루틴이 처리하는 명령 타입입니다.
*/
private sealed interface WriterCommand {
/**
* 로그 라인을 기록하라는 명령입니다.
*
* Command to write log lines to file.
* <br><br>
* 파일에 로그 라인 목록을 기록합니다.
*
* @property context 앱 컨텍스트.
* @property config 현재 설정 스냅샷.
* @property lines 기록할 라인 목록.
* @property errorTag 오류 로그 태그.
*/
data class WriteLines(
val context: Context,
val config: LogxConfigSnapshot,
val lines: List<String>,
val errorTag: String,
) : WriterCommand
/**
* writer를 닫으라는 명령입니다.
*
* Command to close the writer session.
* <br><br>
* writer 세션을 닫습니다.
*/
data object Close : WriterCommand
}
/**
* 파일 기록 동기화를 위한 락입니다.
*
* Lock object for synchronizing file writes.
* <br><br>
* 파일 기록 동기화를 위한 락 객체입니다.
*/
private val fileLock = Any()
/**
* 로그 디렉터리 경로 계산기입니다.
*
* Resolves log directory paths.
* <br><br>
* 로그 디렉터리 경로를 계산합니다.
*/
private val pathResolver = LogxFilePathResolver()
/**
* 파일/Writer 세션 관리자입니다.
*
* Manages file session and writer lifecycle.
* <br><br>
* 파일 세션과 writer 생명주기를 관리합니다.
*/
private val fileSession = LogxFileSession()
/**
* 파일 기록 전용 코루틴 스코프입니다.
*
* Coroutine scope used for file writing.
* <br><br>
* 파일 기록 전용 코루틴 스코프입니다.
*/
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
/**
* writer 명령을 전달하는 채널입니다.
*
* Channel used to send writer commands.
* <br><br>
* writer 명령을 전달하는 채널입니다.
*/
private val channel = Channel<WriterCommand>(Channel.BUFFERED)
init {
// writer 전용 코루틴에서 명령을 순차 처리합니다.
scope.launch {
for (command in channel) {
when (command) {
is WriterCommand.WriteLines -> writeLinesInternal(
command.context,
command.config,
command.lines,
command.errorTag,
)
is WriterCommand.Close -> closeWriterInternal()
}
}
}
}
/**
* 파일 로그 라인을 기록 요청합니다.
*
* Requests writing log lines to file.
* <br><br>
* 파일 로그 라인 기록을 요청합니다.
*
* @param context 앱 컨텍스트.
* @param config 현재 설정 스냅샷.
* @param lines 기록할 라인 목록.
* @param errorTag 오류 로그 태그.
*/
fun writeLines(context: Context, config: LogxConfigSnapshot, lines: List<String>, errorTag: String) {
if (lines.isEmpty()) return
val command = WriterCommand.WriteLines(context, config, lines, errorTag)
val result = channel.trySend(command)
if (result.isFailure) {
scope.launch { channel.send(command) }
}
}
/**
* writer 세션을 닫도록 요청합니다.
*
* Requests closing the writer session.
* <br><br>
* writer 세션을 닫도록 요청합니다.
*/
fun requestClose() {
val result = channel.trySend(WriterCommand.Close)
if (result.isFailure) {
scope.launch { channel.send(WriterCommand.Close) }
}
}
/**
* 채널과 코루틴 스코프를 완전히 종료합니다.
*
* Fully shuts down the channel and coroutine scope.
* <br><br>
* 채널을 닫아 writer 루프를 종료하고, 파일 세션을 닫은 뒤 scope를 cancel합니다.
* 테스트 teardown 또는 앱 종료 시 호출합니다.
*/
fun shutdown() {
channel.close()
synchronized(fileLock) { fileSession.close() }
scope.cancel()
}
/**
* 실제 파일에 로그 라인을 기록합니다.
*
* Performs the actual file write for log lines.
* <br><br>
* 실제 파일 기록을 수행합니다.
*
* @param context 앱 컨텍스트.
* @param config 현재 설정 스냅샷.
* @param lines 기록할 라인 목록.
* @param errorTag 오류 로그 태그.
*/
private fun writeLinesInternal(context: Context, config: LogxConfigSnapshot, lines: List<String>, errorTag: String) {
synchronized(fileLock) {
val logDirectory = pathResolver.resolveDirectory(context, config, errorTag) ?: return
try {
val activeWriter = fileSession.getWriter(logDirectory, config.appName, config.storageType)
lines.forEach { line ->
activeWriter.write(line)
activeWriter.newLine()
}
activeWriter.flush()
} catch (e: IOException) {
Log.e(errorTag, "Failed to write log file: ${e.message}")
fileSession.close()
} catch (e: RuntimeException) {
Log.e(errorTag, "Failed to write log file: ${e.message}")
fileSession.close()
}
}
}
/**
* writer 세션을 닫습니다.
*
* Closes the file writer session.
* <br><br>
* 파일 writer 세션을 닫습니다.
*/
private fun closeWriterInternal() {
synchronized(fileLock) {
fileSession.close()
}
}
}