Skip to content

Commit 58c83c7

Browse files
committed
add zip and email feature
1 parent 0fe1462 commit 58c83c7

File tree

9 files changed

+245
-9
lines changed

9 files changed

+245
-9
lines changed

README.md

+45-6
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@ The FileLogger is a library for saving logs on Files with custom-formatter on ba
1010
- Working on I/O thread
1111
- Using java FastDateTime
1212
- Support INFO, ERROR, DEBUG, WARNING logging level
13+
- Compress and send logs(Email and messengers)
1314

1415
## TODO
1516
1. Add C++ NDK support
16-
2. Mail logs
17+
2. Mail logs
1718
3. Upload on http server
1819
4. Encrypt important logs
20+
5. Retrofit/OkHttp Interceptor
21+
6. Startup log
1922

2023
## Usage
2124

22-
Init:
25+
**Init:**
2326
```kotlin
2427
val config = Config.Builder(it.path)
2528
.setDefaultTag("TAG")
@@ -29,7 +32,7 @@ val config = Config.Builder(it.path)
2932

3033
FileLogger.init(config)
3134
```
32-
Log:
35+
**Log:**
3336
```kotlin
3437
FileLogger.i("TAG", "This is normal Log with custom TAG")
3538
FileLogger.i(msg = "This is normal Info Log")
@@ -38,7 +41,7 @@ FileLogger.w(msg = "This is normal Warning Log")
3841
FileLogger.e(msg = "This is normal Error Log")
3942
```
4043

41-
Exception:
44+
**Exception:**
4245
```kotlin
4346
try {
4447
//...
@@ -47,12 +50,48 @@ try {
4750
}
4851
```
4952

50-
Delete log files:
53+
**Compress to Zip file and Email logs:**
54+
```kotlin
55+
FileLogger.compressLogsInZipFile("my_files") { zipFile ->
56+
zipFile?.let {
57+
FileIntent.fromFile(this@MainActivity, zipFile, BuildConfig.APPLICATION_ID)?.let { intent ->
58+
intent.putExtra(Intent.EXTRA_SUBJECT, "Email Subject")
59+
try {
60+
startActivity(Intent.createChooser(intent, "Email App..."))
61+
} catch (e: java.lang.Exception) {
62+
FileLogger.e(throwable = e)
63+
}
64+
}
65+
}
66+
}
67+
```
68+
for share file with email or etc add this provider in the AndroidManifest.xml file:
69+
```xml
70+
<provider
71+
android:name="androidx.core.content.FileProvider"
72+
android:authorities="${applicationId}.provider"
73+
android:exported="false"
74+
android:grantUriPermissions="true">
75+
<meta-data
76+
android:name="android.support.FILE_PROVIDER_PATHS"
77+
android:resource="@xml/provider_paths"/>
78+
</provider>
79+
```
80+
And this one in resource/xml/provider_paths:
81+
```xml
82+
<?xml version="1.0" encoding="utf-8"?>
83+
<paths>
84+
<external-path name="media" path="."/>
85+
<root-path name="external_files" path="/storage/" />
86+
</paths>
87+
```
88+
89+
**Delete log files:**
5190
```kotlin
5291
FileLogger.deleteFiles()
5392
```
5493

55-
Enable and disable logging:
94+
**Enable and disable logging:**
5695
```kotlin
5796
FileLogger.setEnable(boolean)
5897
```

app/src/main/AndroidManifest.xml

+9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@
2121
<category android:name="android.intent.category.LAUNCHER" />
2222
</intent-filter>
2323
</activity>
24+
<provider
25+
android:name="androidx.core.content.FileProvider"
26+
android:authorities="${applicationId}.provider"
27+
android:exported="false"
28+
android:grantUriPermissions="true">
29+
<meta-data
30+
android:name="android.support.FILE_PROVIDER_PATHS"
31+
android:resource="@xml/provider_paths"/>
32+
</provider>
2433
</application>
2534

2635
</manifest>

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

+31
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ package abbasi.android.filelogger.sample
22

33
import abbasi.android.filelogger.FileLogger
44
import abbasi.android.filelogger.config.Config
5+
import abbasi.android.filelogger.util.FileIntent
6+
import android.content.Intent
57
import android.os.Bundle
68
import android.widget.Button
79
import android.widget.ImageView
10+
import android.widget.Toast
811
import androidx.appcompat.app.AppCompatActivity
12+
import java.io.File
913

1014
class MainActivity : AppCompatActivity() {
15+
private var zipFile: File? = null
16+
1117
override fun onCreate(savedInstanceState: Bundle?) {
1218
super.onCreate(savedInstanceState)
1319
setContentView(R.layout.activity_main)
@@ -43,5 +49,30 @@ class MainActivity : AppCompatActivity() {
4349
FileLogger.deleteFiles()
4450
}
4551

52+
findViewById<Button>(R.id.zipLogs).setOnClickListener {
53+
FileLogger.compressLogsInZipFile {
54+
zipFile = it
55+
Toast.makeText(
56+
this,
57+
"Zip Log file created ${if (zipFile?.exists() == true) "successfully" else "with error"}",
58+
Toast.LENGTH_SHORT
59+
).show()
60+
}
61+
}
62+
63+
findViewById<Button>(R.id.emailLogs).setOnClickListener {
64+
if (zipFile != null) {
65+
FileIntent.fromFile(this, zipFile!!, BuildConfig.APPLICATION_ID)?.let { intent ->
66+
intent.putExtra(Intent.EXTRA_SUBJECT, "Email Subject")
67+
try {
68+
startActivity(Intent.createChooser(intent, "Email App..."))
69+
} catch (e: java.lang.Exception) {
70+
FileLogger.e(throwable = e)
71+
}
72+
}
73+
} else {
74+
Toast.makeText(this, "Make zip file first.", Toast.LENGTH_SHORT).show()
75+
}
76+
}
4677
}
4778
}

app/src/main/res/layout/activity_main.xml

+31-1
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,39 @@
6464
android:layout_marginRight="8dp"
6565
android:text="Delete Logs"
6666
android:textAllCaps="false"
67-
app:layout_constraintBottom_toBottomOf="parent"
67+
app:layout_constraintBottom_toBottomOf="@+id/zipLogs"
6868
app:layout_constraintEnd_toEndOf="parent"
6969
app:layout_constraintStart_toStartOf="parent"
7070
app:layout_constraintTop_toBottomOf="@+id/writeExceptionLog" />
7171

72+
<Button
73+
android:id="@+id/zipLogs"
74+
android:layout_width="0dp"
75+
android:layout_height="wrap_content"
76+
android:layout_marginStart="8dp"
77+
android:layout_marginLeft="8dp"
78+
android:layout_marginEnd="8dp"
79+
android:layout_marginRight="8dp"
80+
android:text="Zip Logs"
81+
android:textAllCaps="false"
82+
app:layout_constraintBottom_toBottomOf="@+id/emailLogs"
83+
app:layout_constraintEnd_toEndOf="parent"
84+
app:layout_constraintStart_toStartOf="parent"
85+
app:layout_constraintTop_toBottomOf="@+id/deleteLogs" />
86+
87+
<Button
88+
android:id="@+id/emailLogs"
89+
android:layout_width="0dp"
90+
android:layout_height="wrap_content"
91+
android:layout_marginStart="8dp"
92+
android:layout_marginLeft="8dp"
93+
android:layout_marginEnd="8dp"
94+
android:layout_marginRight="8dp"
95+
android:text="Email Logs"
96+
android:textAllCaps="false"
97+
app:layout_constraintBottom_toBottomOf="parent"
98+
app:layout_constraintEnd_toEndOf="parent"
99+
app:layout_constraintStart_toStartOf="parent"
100+
app:layout_constraintTop_toBottomOf="@+id/zipLogs" />
101+
72102
</androidx.constraintlayout.widget.ConstraintLayout>
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<paths>
3+
<external-path name="media" path="."/>
4+
<root-path name="external_files" path="/storage/" />
5+
</paths>

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import abbasi.android.filelogger.config.Config
1010
import abbasi.android.filelogger.file.FileWriter
1111
import abbasi.android.filelogger.file.LogLevel
1212
import abbasi.android.filelogger.threading.ThreadQueue
13+
import abbasi.android.filelogger.util.FileZipper
1314
import android.util.Log
15+
import java.io.File
1416

1517
object FileLogger {
1618

@@ -19,7 +21,11 @@ object FileLogger {
1921

2022
private var config: Config? = null
2123
private var fileWriter: FileWriter? = null
22-
private var logQueue: ThreadQueue = ThreadQueue()
24+
private val fileZipper: FileZipper by lazy {
25+
FileZipper()
26+
}
27+
28+
private var logQueue: ThreadQueue = ThreadQueue("RunnableQueue")
2329

2430
fun init(config: Config) {
2531
if (initialized) {
@@ -112,6 +118,15 @@ object FileLogger {
112118
fileWriter?.deleteLogsDir()
113119
}
114120

121+
fun compressLogsInZipFile(
122+
zipFileName: String? = null,
123+
callback: ((zipFile: File?) -> Unit),
124+
) = checkBlock {
125+
config?.let {
126+
fileZipper.compressFiles(it, zipFileName, callback)
127+
}
128+
}
129+
115130
private fun checkBlock(block: () -> Unit) {
116131
if (initialized && isEnable) {
117132
block()

filelogger/src/main/java/abbasi/android/filelogger/threading/ThreadQueue.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import android.os.Looper
1212
import android.os.Message
1313
import java.util.concurrent.CountDownLatch
1414

15-
internal class ThreadQueue : Thread("RunnableQueue") {
15+
internal class ThreadQueue constructor(name: String) : Thread(name) {
1616
private var handler: Handler? = null
1717
private val latch = CountDownLatch(1)
1818

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package abbasi.android.filelogger.util
2+
3+
import abbasi.android.filelogger.FileLogger
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.net.Uri
7+
import android.os.Build
8+
import androidx.core.content.FileProvider
9+
import java.io.File
10+
11+
class FileIntent private constructor() {
12+
companion object {
13+
@JvmStatic
14+
fun uriFromFile(context: Context, file: File, appId: String): Uri? = try {
15+
if (Build.VERSION.SDK_INT >= 24) {
16+
FileProvider.getUriForFile(
17+
context,
18+
"${appId}.provider",
19+
file
20+
)
21+
} else {
22+
Uri.fromFile(file)
23+
}
24+
} catch (e: Exception) {
25+
FileLogger.e(throwable = e)
26+
null
27+
}
28+
29+
@JvmStatic
30+
fun fromUri(uri: Uri): Intent {
31+
val intent = Intent(Intent.ACTION_SEND)
32+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
33+
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
34+
}
35+
intent.type = "message/rfc822"
36+
intent.putExtra(Intent.EXTRA_STREAM, uri)
37+
return intent
38+
}
39+
40+
@JvmStatic
41+
fun fromFile(context: Context, file: File, appId: String): Intent? {
42+
return uriFromFile(context, file, appId)?.let { return@let fromUri(it) }
43+
}
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package abbasi.android.filelogger.util
2+
3+
import abbasi.android.filelogger.FileLogger
4+
import abbasi.android.filelogger.config.Config
5+
import abbasi.android.filelogger.config.Constance
6+
import abbasi.android.filelogger.threading.ThreadQueue
7+
import java.io.*
8+
import java.util.zip.ZipEntry
9+
import java.util.zip.ZipOutputStream
10+
11+
internal class FileZipper {
12+
private var logQueue: ThreadQueue = ThreadQueue("CompressThread")
13+
14+
fun compressFiles(
15+
config: Config,
16+
zipFileName: String? = null,
17+
callback: ((file: File?) -> Unit),
18+
) = logQueue.postRunnable {
19+
try {
20+
val fileName = zipFileName?.replace(".", "_")?.replace("/", "_")
21+
val logFileDirectory = File(config.directory)
22+
val logDirectory = File(logFileDirectory.absolutePath + Constance.DIRECTORY)
23+
val zipFile = File(logDirectory, "${fileName ?: "Logs"}.zip")
24+
if (zipFile.exists()) {
25+
zipFile.delete()
26+
}
27+
val logFiles = logDirectory.listFiles()
28+
var inputStream: BufferedInputStream? = null
29+
var zipOutputStream: ZipOutputStream? = null
30+
try {
31+
val zipFileStream = FileOutputStream(zipFile)
32+
zipOutputStream = ZipOutputStream(BufferedOutputStream(zipFileStream))
33+
val data = ByteArray(1024 * 64)
34+
logFiles?.forEach { currentFile ->
35+
val fileInputStream = FileInputStream(currentFile)
36+
var count: Int
37+
38+
inputStream = BufferedInputStream(fileInputStream, data.size).also { stream ->
39+
val entry = ZipEntry(currentFile.name)
40+
zipOutputStream.putNextEntry(entry)
41+
while (stream.read(data, 0, data.size).also { count = it } != -1) {
42+
zipOutputStream.write(data, 0, count)
43+
}
44+
}
45+
46+
inputStream?.close()
47+
inputStream = null
48+
}
49+
} catch (e: Exception) {
50+
FileLogger.e(throwable = e)
51+
callback(null)
52+
} finally {
53+
inputStream?.close()
54+
zipOutputStream?.close()
55+
}
56+
callback(zipFile)
57+
} catch (e: Exception) {
58+
FileLogger.e(throwable = e)
59+
callback(null)
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)