Coverage Summary for Class: LogxPipeline (kr.open.library.simple_ui.core.logcat.internal.pipeline)

Class Class, % Method, % Branch, % Line, % Instruction, %
LogxPipeline 100% (1/1) 50% (6/12) 18.2% (8/44) 30.5% (25/82) 27.5% (120/436)


 package kr.open.library.simple_ui.core.logcat.internal.pipeline
 
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import android.util.Log
 import kr.open.library.simple_ui.core.BuildConfig
 import kr.open.library.simple_ui.core.logcat.config.LogType
 import kr.open.library.simple_ui.core.logcat.config.LogxConfigSnapshot
 import kr.open.library.simple_ui.core.logcat.config.LogxConfigStore
 import kr.open.library.simple_ui.core.logcat.internal.common.LogxTagHelper
 import kr.open.library.simple_ui.core.logcat.internal.extractor.LogStackTraceExtractor
 import kr.open.library.simple_ui.core.logcat.internal.filter.LogxFilter
 import kr.open.library.simple_ui.core.logcat.internal.formatter.FormattedJson
 import kr.open.library.simple_ui.core.logcat.internal.formatter.LogxFileLineBuilder
 import kr.open.library.simple_ui.core.logcat.internal.formatter.LogxFormatter
 import kr.open.library.simple_ui.core.logcat.internal.writer.LogxConsoleWriter
 import kr.open.library.simple_ui.core.logcat.internal.writer.LogxFileWriter
 import java.util.concurrent.atomic.AtomicBoolean
 
 /**
  * 로그 처리 파이프라인의 중심 오케스트레이터입니다.
  *
  * Orchestrates the logging pipeline: filtering, formatting, and writing.
  * <br><br>
  * 로그 필터링/포맷팅/출력을 조합해 실행합니다.
  *
  * @param contextProvider 파일 저장에 필요한 컨텍스트 제공자.
  * @param fileWriter 파일 로그 작성자.
  * @param configStore 설정 스토어.
  * @param filter 로그 허용 여부 필터.
  * @param formatter 로그 포맷터.
  * @param consoleWriter Logcat 출력기.
  * @param fileLineBuilder 파일 라인 빌더.
  */
 internal open class LogxPipeline(
     private val contextProvider: () -> Context?,
     private val fileWriter: LogxFileWriter,
     private val configStore: LogxConfigStore = LogxConfigStore,
     private val filter: LogxFilter = LogxFilter,
     private val formatter: LogxFormatter = LogxFormatter,
     private val consoleWriter: LogxConsoleWriter = LogxConsoleWriter,
     private val fileLineBuilder: LogxFileLineBuilder = LogxFileLineBuilder(),
 ) {
     /**
      * Development mode cache flag.<br><br>
      * 개발 모드 여부 캐시 플래그.<br>
      */
     @Volatile
     private var developmentMode: Boolean = BuildConfig.DEBUG
 
     /**
      * 컨텍스트 미설정 경고를 1회만 출력하기 위한 플래그입니다.
      *
      * Ensures the missing-context warning is logged only once.
      * <br><br>
      * 컨텍스트 미설정 경고를 한 번만 출력하도록 제어합니다.
      */
     private val warnedNoContext = AtomicBoolean(false)
 
     public fun setDevelopmentMode(context: Context) {
         developmentMode = ((context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0)
     }
 
     public fun isDevelopmentMode(): Boolean = developmentMode
 
     /**
      * 일반 로그(d/i/w/e/v 등)를 처리합니다.
      *
      * Handles standard log types (d/i/w/e/v).
      * <br><br>
      * 표준 로그 타입을 필터링/포맷팅/출력합니다.
      *
      * @param type 로그 타입.
      * @param inputTag 사용자 입력 태그.
      * @param msg 출력 메시지.
      * @param hasMessage 메시지 포함 여부.
      * @param tagProvided 태그가 제공되었는지 여부.
      */
     open fun logStandard(type: LogType, inputTag: String?, msg: Any?, hasMessage: Boolean, tagProvided: Boolean) {
         val config = configStore.snapshot()
         if (!config.isLogging) return
         if (!config.logTypes.contains(type)) return
 
         val tag = resolveTag(inputTag, tagProvided)
         if (!filter.isAllowed(type, tag, config)) return
 
         val frame = LogStackTraceExtractor.extract(config.skipPackages).current
         val prefix = LogxTagHelper.buildPrefix(config.appName, tag)
         val payload = formatter.formatBasic(frame, msg?.toString(), hasMessage)
 
         consoleWriter.write(type, prefix, payload)
         writeFileLinesIfEnabled(config, inputTag) {
             fileLineBuilder.buildLines(type, prefix, listOf(payload))
         }
     }
 
     /**
      * PARENT 로그를 처리합니다.
      *
      * Handles the PARENT log type.
      * <br><br>
      * 부모/현재 프레임을 포함하는 PARENT 로그를 처리합니다.
      *
      * @param inputTag 사용자 입력 태그.
      * @param msg 출력 메시지.
      * @param hasMessage 메시지 포함 여부.
      * @param tagProvided 태그가 제공되었는지 여부.
      */
     open fun logParent(inputTag: String?, msg: Any?, hasMessage: Boolean, tagProvided: Boolean) {
         val config = configStore.snapshot()
         if (!config.isLogging) return
         if (!config.logTypes.contains(LogType.PARENT)) return
 
         val tag = resolveTag(inputTag, tagProvided)
         if (!filter.isAllowed(LogType.PARENT, tag, config)) return
 
         val frames = LogStackTraceExtractor.extract(config.skipPackages)
         val prefix = LogxTagHelper.buildPrefix(config.appName, tag)
         val payloadLines = formatter.formatParent(frames, msg?.toString(), hasMessage)
 
         consoleWriter.writeLines(LogType.PARENT, prefix, payloadLines)
         writeFileLinesIfEnabled(config, inputTag) {
             fileLineBuilder.buildLines(LogType.PARENT, prefix, payloadLines)
         }
     }
 
     /**
      * THREAD 로그를 처리합니다.
      *
      * Handles the THREAD log type.
      * <br><br>
      * 스레드 ID를 포함한 로그를 처리합니다.
      *
      * @param inputTag 사용자 입력 태그.
      * @param msg 출력 메시지.
      * @param hasMessage 메시지 포함 여부.
      * @param tagProvided 태그가 제공되었는지 여부.
      * @param threadId 출력할 스레드 ID.
      */
     open fun logThread(inputTag: String?, msg: Any?, hasMessage: Boolean, tagProvided: Boolean, threadId: Long) {
         val config = configStore.snapshot()
         if (!config.isLogging) return
         if (!config.logTypes.contains(LogType.THREAD)) return
 
         val tag = resolveTag(inputTag, tagProvided)
         if (!filter.isAllowed(LogType.THREAD, tag, config)) return
 
         val frame = LogStackTraceExtractor.extract(config.skipPackages).current
         val prefix = LogxTagHelper.buildPrefix(config.appName, tag)
         val payload = formatter.formatThread(frame, threadId, msg?.toString(), hasMessage)
 
         consoleWriter.write(LogType.THREAD, prefix, payload)
         writeFileLinesIfEnabled(config, inputTag) {
             fileLineBuilder.buildLines(LogType.THREAD, prefix, listOf(payload))
         }
     }
 
     /**
      * JSON 로그를 처리합니다.
      *
      * Handles JSON log output.
      * <br><br>
      * JSON 로그를 포맷팅해 출력합니다.
      *
      * @param inputTag 사용자 입력 태그.
      * @param json JSON 원문 문자열.
      * @param tagProvided 태그가 제공되었는지 여부.
      */
     open fun logJson(inputTag: String?, json: String, tagProvided: Boolean) {
         val config = configStore.snapshot()
         if (!config.isLogging) return
         if (!config.logTypes.contains(LogType.JSON)) return
 
         val tag = resolveTag(inputTag, tagProvided)
         if (!filter.isAllowed(LogType.JSON, tag, config)) return
 
         val frame = LogStackTraceExtractor.extract(config.skipPackages).current
         val prefix = LogxTagHelper.buildPrefix(config.appName, tag)
         val formattedJson = formatter.formatJson(frame, json)
 
         val consoleMessage = buildConsoleJsonMessage(formattedJson)
         consoleWriter.write(LogType.JSON, prefix, consoleMessage)
 
         writeFileLinesIfEnabled(config, inputTag) {
             fileLineBuilder.buildJsonLines(prefix, formattedJson)
         }
     }
 
     /**
      * 입력 태그를 검증하고 유효한 태그만 반환합니다.
      *
      * Validates the input tag and returns a usable tag or null.
      * <br><br>
      * 유효한 태그만 반환하며 잘못된 태그는 무시합니다.
      *
      * @param inputTag 사용자 입력 태그.
      * @param tagProvided 태그가 제공되었는지 여부.
      */
     private fun resolveTag(inputTag: String?, tagProvided: Boolean): String? {
         if (LogxTagHelper.isValidTag(inputTag)) return inputTag
         if (tagProvided) {
             Log.e(LogxTagHelper.errorTag(inputTag), "Invalid tag input. Tag will be ignored.")
         }
         return null
     }
 
     /**
      * 파일 저장이 활성화된 경우 파일 로그를 작성합니다.
      *
      * Writes file log lines if file logging is enabled.
      * <br><br>
      * 파일 저장이 활성화되었을 때만 파일에 기록합니다.
      *
      * @param config 현재 설정 스냅샷.
      * @param inputTag 사용자 입력 태그.
      * @param lines 기록할 라인 목록.
      */
     private fun writeFileLines(config: LogxConfigSnapshot, inputTag: String?, lines: List<String>) {
         if (!config.isSaveEnabled) return
 
         val context = contextProvider()
         if (context == null) {
             warnNoContextOnce(inputTag)
             return
         }
 
         val errorTag = LogxTagHelper.errorTag(inputTag)
         fileWriter.writeLines(context, config, lines, errorTag)
     }
 
     /**
      * 파일 저장이 활성화된 경우 라인 생성 후 기록합니다.
      *
      * Builds and writes file log lines only when enabled.
      * <br><br>
      * 파일 저장이 활성화된 경우에만 라인을 생성해 기록합니다.
      *
      * @param config 현재 설정 스냅샷.
      * @param inputTag 사용자 입력 태그.
      * @param buildLines 라인 목록 생성 함수.
      */
     private inline fun writeFileLinesIfEnabled(config: LogxConfigSnapshot, inputTag: String?, buildLines: () -> List<String>) {
         if (!config.isSaveEnabled) return
         writeFileLines(config, inputTag, buildLines())
     }
 
     /**
      * 컨텍스트 미설정 경고를 한 번만 출력합니다.
      *
      * Logs the missing-context warning only once.
      * <br><br>
      * 컨텍스트 미설정 경고를 1회만 로그로 남깁니다.
      *
      * @param inputTag 사용자 입력 태그.
      */
     private fun warnNoContextOnce(inputTag: String?) {
         if (!warnedNoContext.compareAndSet(false, true)) return
         if (isDevelopmentMode()) {
             Log.e(
                 LogxTagHelper.errorTag(inputTag),
                 "Context is not initialized. Call Logx.initialize(applicationContext) before enabling file logging."
             )
         }
     }
 
     /**
      * 콘솔 출력용 JSON 메시지를 조합합니다.
      *
      * Builds a console message from formatted JSON parts.
      * <br><br>
      * JSON 헤더/본문/종료 라인을 결합합니다.
      *
      * @param formattedJson JSON 출력 구성 요소.
      */
     private fun buildConsoleJsonMessage(formattedJson: FormattedJson): String =
         buildString {
             append(formattedJson.header)
             formattedJson.bodyLines.forEach { line ->
                 append('\n')
                 append(line)
             }
             append('\n')
             append(formattedJson.endLine)
         }
 }