Skip to content

Commit c85b0b3

Browse files
author
abolfazlabbasi
committed
add Retention Policy
1 parent 9552471 commit c85b0b3

File tree

8 files changed

+167
-36
lines changed

8 files changed

+167
-36
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The FileLogger is a library for saving logs on Files with custom-formatter on ba
1212
- Support INFO, ERROR, DEBUG, WARNING logging level
1313
- Compress and send logs(Email and messengers)
1414
- Startup logs
15+
- Retention Policy (Size, Count, Time to Live)
1516

1617
## TODO
1718
1. Add C++ NDK support
@@ -29,6 +30,7 @@ val config = Config.Builder(it.path)
2930
.setDefaultTag("TAG")
3031
.setLogcatEnable(true)
3132
.setDataFormatterPattern("dd-MM-yyyy-HH:mm:ss")
33+
.setRetentionPolicy(RetentionPolicy.TimeToLive(durationInMillis = 1000 * 60 * 10)) // 10 min
3234
.setStartupData(
3335
mapOf(
3436
"App Version" to "${BuildConfig.VERSION}",

app/src/main/java/abbasi/android/filelogger/sample/MainActivity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package abbasi.android.filelogger.sample
22

33
import abbasi.android.filelogger.FileLogger
44
import abbasi.android.filelogger.config.Config
5+
import abbasi.android.filelogger.config.RetentionPolicy
56
import abbasi.android.filelogger.util.FileIntent
67
import android.content.Intent
78
import android.os.Build
@@ -24,6 +25,7 @@ class MainActivity : AppCompatActivity() {
2425
val config = Config.Builder(it.path)
2526
.setDefaultTag("TAG")
2627
.setLogcatEnable(true)
28+
.setRetentionPolicy(RetentionPolicy.TimeToLive(durationInMillis = 1000 * 60 * 1))
2729
.setStartupData(
2830
mapOf(
2931
"App Version" to "${System.currentTimeMillis()}",
@@ -37,7 +39,7 @@ class MainActivity : AppCompatActivity() {
3739
)
3840
).build()
3941

40-
FileLogger.init(config)
42+
FileLogger.init(this, config)
4143
}
4244
}
4345

filelogger/src/main/java/abbasi/android/filelogger/FileLogger.kt

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ package abbasi.android.filelogger
88

99
import abbasi.android.filelogger.config.Config
1010
import abbasi.android.filelogger.file.FileWriter
11+
import abbasi.android.filelogger.file.LogFileManager
1112
import abbasi.android.filelogger.file.LogLevel
13+
import abbasi.android.filelogger.file.RetentionPolicyChecker
1214
import abbasi.android.filelogger.threading.ThreadQueue
1315
import abbasi.android.filelogger.util.FileZipper
16+
import android.content.Context
1417
import android.util.Log
1518
import java.io.File
1619

@@ -20,28 +23,41 @@ object FileLogger {
2023
private var isEnable: Boolean = true
2124

2225
private var config: Config? = null
23-
private val fileWriter: FileWriter? by lazy {
24-
return@lazy config?.let {
25-
return@let FileWriter(
26-
it.directory,
27-
it.dataFormatterPattern,
28-
it.startupData
29-
)
30-
}
31-
}
26+
private lateinit var retentionChecker: RetentionPolicyChecker
27+
private lateinit var logFileManager: LogFileManager
28+
private lateinit var fileWriter: FileWriter
29+
3230
private val fileZipper: FileZipper by lazy {
3331
FileZipper()
3432
}
3533

3634
private var logQueue: ThreadQueue = ThreadQueue("LogQueue")
3735

38-
fun init(config: Config) {
36+
fun init(context: Context, config: Config) {
3937
if (initialized) {
4038
return
4139
}
4240

4341
this.config = config
42+
43+
logFileManager = LogFileManager(
44+
context = context.applicationContext,
45+
rootDir = config.directory
46+
)
47+
48+
retentionChecker = RetentionPolicyChecker(logFileManager)
49+
50+
fileWriter = FileWriter(
51+
logFileManager = logFileManager,
52+
dataFormatterPattern = config.dataFormatterPattern,
53+
startLogs = config.startupData
54+
)
55+
4456
initialized = true
57+
58+
config.retentionPolicy?.let {
59+
retentionChecker(policy = it)
60+
}
4561
}
4662

4763
fun i(tag: String? = config?.defaultTag, msg: String) = checkBlock {
@@ -102,9 +118,9 @@ object FileLogger {
102118
tag: String? = config?.defaultTag,
103119
msg: String? = "",
104120
throwable: Throwable? = null
105-
) = fileWriter?.let { writer ->
121+
) = fileWriter.let { writer ->
106122
logQueue.postRunnable {
107-
val stringBuilder = StringBuilder("$logLevel/$tag: $msg \n")
123+
val stringBuilder = StringBuilder("$logLevel/$tag: $msg\n")
108124

109125
throwable?.let { exception ->
110126
exception.stackTrace.forEach { element ->
@@ -122,7 +138,7 @@ object FileLogger {
122138

123139
fun deleteFiles() = checkBlock {
124140
i(msg = "FileLogger delete files called")
125-
fileWriter?.deleteLogsDir()
141+
logFileManager.deleteLogsDir(fileWriter.openedFilePath)
126142
}
127143

128144
fun compressLogsInZipFile(

filelogger/src/main/java/abbasi/android/filelogger/config/Config.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,20 @@ class Config private constructor(
1616
val logcatEnable: Boolean,
1717
val dataFormatterPattern: String,
1818
val startupData: Map<String, String>?,
19+
val retentionPolicy: RetentionPolicy?,
1920
) {
2021

2122
class Builder(private val directory: String) {
2223
private var defaultTag: String = DEFAULT_TAG
2324
private var logcatEnable: Boolean = LOGCAT_ENABLE
2425
private var dataFormatterPattern: String = DEFAULT_PATTERN
2526
private var startupData: Map<String, String>? = null
27+
private var retentionPolicy: RetentionPolicy? = null
2628

2729
fun setDefaultTag(defaultTag: String) = apply { this.defaultTag = defaultTag }
2830
fun setLogcatEnable(logcatEnable: Boolean) = apply { this.logcatEnable = logcatEnable }
2931
fun setStartupData(startupData: Map<String, String>?) = apply { this.startupData = startupData }
32+
fun setRetentionPolicy(retentionPolicy: RetentionPolicy) = apply { this.retentionPolicy = retentionPolicy }
3033

3134
fun setDataFormatterPattern(pattern: String) = apply {
3235
this.dataFormatterPattern = pattern.replace("/", "-")
@@ -44,6 +47,7 @@ class Config private constructor(
4447
logcatEnable,
4548
dataFormatterPattern,
4649
startupData,
50+
retentionPolicy,
4751
)
4852
}
4953
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
*
3+
* Copyright (c) 2024 Abolfazl Abbasi
4+
*
5+
* */
6+
7+
package abbasi.android.filelogger.config
8+
9+
sealed interface RetentionPolicy {
10+
data class FileCountLimit(val count: Int) : RetentionPolicy
11+
data class FileSizeLimit(val sizeInBytes: Long) : RetentionPolicy
12+
data class TimeToLive(val durationInMillis: Long) : RetentionPolicy
13+
}

filelogger/src/main/java/abbasi/android/filelogger/file/FileWriter.kt

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,30 @@
66

77
package abbasi.android.filelogger.file
88

9-
import abbasi.android.filelogger.config.Constance.Companion.DIRECTORY
109
import abbasi.android.filelogger.time.FastDateFormat
1110
import android.util.Log
1211
import java.io.File
1312
import java.io.FileOutputStream
1413
import java.io.OutputStreamWriter
15-
import java.util.*
14+
import java.util.Locale
1615

1716
internal class FileWriter(
18-
private val directory: String,
17+
logFileManager: LogFileManager,
1918
dataFormatterPattern: String,
2019
startLogs: Map<String, String>?,
2120
) {
2221
private var streamWriter: OutputStreamWriter? = null
2322
private var dateFormat: FastDateFormat? = null
2423
private var logFile: File? = null
2524

25+
val openedFilePath
26+
get() = logFile?.absolutePath
27+
2628
init {
2729
dateFormat = FastDateFormat.getInstance(dataFormatterPattern, Locale.US)
2830
try {
29-
val file = File(directory)
30-
val logDir = File(file.absolutePath + DIRECTORY)
31-
if (logDir.exists().not()) {
32-
logDir.mkdirs()
33-
}
34-
35-
logFile = File(logDir, "${dateFormat?.format(System.currentTimeMillis())}.txt")
31+
val fileName = "${dateFormat?.format(System.currentTimeMillis())}.txt"
32+
logFile = logFileManager.createLogFile(fileName)
3633
} catch (e: Exception) {
3734
e.printStackTrace()
3835
}
@@ -43,7 +40,7 @@ internal class FileWriter(
4340
streamWriter = OutputStreamWriter(stream).apply {
4441
write("File logger started at ${dateFormat?.format(System.currentTimeMillis())}\n")
4542
startLogs?.forEach {
46-
write("${it.key}: ${it.value} \n")
43+
write("${it.key}: ${it.value}\n")
4744
}
4845
write("\n\n")
4946
flush()
@@ -63,15 +60,4 @@ internal class FileWriter(
6360
}
6461
}
6562
}
66-
67-
fun deleteLogsDir() {
68-
val currentFile = File(directory)
69-
val logDir = File(currentFile.absolutePath + DIRECTORY)
70-
71-
logDir.listFiles()?.filter {
72-
it.absolutePath != logFile?.absolutePath
73-
}?.forEach {
74-
it.delete()
75-
}
76-
}
7763
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package abbasi.android.filelogger.file
2+
3+
import abbasi.android.filelogger.config.Constance.Companion.DIRECTORY
4+
import android.content.Context
5+
import java.io.File
6+
7+
internal class LogFileManager(
8+
context: Context,
9+
rootDir: String
10+
) {
11+
12+
private val prefs = context.getSharedPreferences("log_metadata", Context.MODE_PRIVATE)
13+
private val logsDirectory: String = rootDir + DIRECTORY
14+
val logFilesList
15+
get() = File(logsDirectory).listFiles()?.filter { it.isFile }
16+
17+
init {
18+
val logDir = File(logsDirectory)
19+
if (logDir.exists().not()) {
20+
logDir.mkdirs()
21+
}
22+
}
23+
24+
fun createLogFile(fileName: String): File {
25+
val logFile = File(logsDirectory, fileName)
26+
if (logFile.exists()) {
27+
return logFile
28+
}
29+
30+
logFile.createNewFile()
31+
prefs.edit().putLong(fileName, System.currentTimeMillis()).apply()
32+
33+
return logFile
34+
}
35+
36+
fun getCreationTime(fileName: String): Long {
37+
return prefs.getLong(fileName, 0L)
38+
}
39+
40+
fun deleteLogFile(fileName: String) {
41+
try {
42+
val file = File(logsDirectory, fileName)
43+
if (file.exists()) {
44+
file.delete()
45+
prefs.edit().remove(fileName).apply()
46+
}
47+
} catch (e: Exception) {
48+
e.printStackTrace()
49+
}
50+
}
51+
52+
fun deleteLogsDir(currentLogPath: String?) {
53+
File(logsDirectory).listFiles()?.filter {
54+
it.absolutePath != currentLogPath
55+
}?.forEach {
56+
it.delete()
57+
}
58+
}
59+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package abbasi.android.filelogger.file
2+
3+
import abbasi.android.filelogger.config.RetentionPolicy
4+
5+
internal class RetentionPolicyChecker(
6+
private val fileManager: LogFileManager,
7+
) {
8+
operator fun invoke(policy: RetentionPolicy) {
9+
val logsList = fileManager.logFilesList ?: return
10+
when (policy) {
11+
is RetentionPolicy.FileCountLimit -> {
12+
val logFiles = logsList.sortedBy { it.lastModified() }
13+
14+
if (logFiles.size > policy.count) {
15+
logFiles.take(logFiles.size - policy.count).forEach { file ->
16+
fileManager.deleteLogFile(file.name)
17+
}
18+
}
19+
}
20+
21+
is RetentionPolicy.FileSizeLimit -> {
22+
val logFiles = logsList.sortedBy { it.lastModified() }
23+
24+
var totalSize = logFiles.sumOf { it.length() }
25+
if (totalSize > policy.sizeInBytes) {
26+
logFiles.forEach { file ->
27+
if (totalSize <= policy.sizeInBytes) {
28+
return
29+
}
30+
totalSize -= file.length()
31+
fileManager.deleteLogFile(file.name)
32+
}
33+
}
34+
}
35+
36+
is RetentionPolicy.TimeToLive -> {
37+
val now = System.currentTimeMillis()
38+
39+
logsList.forEach { file ->
40+
val creationTime = fileManager.getCreationTime(file.name)
41+
42+
if (now - creationTime > policy.durationInMillis) {
43+
fileManager.deleteLogFile(file.name)
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)