diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45e603b..29a9432 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Allow empty log messages if you only want to create a log entry about a function being called.
- Add more Apple ARM64 platforms: macOS, tvOS, watchOS
+- Provide FileLogger in addition to ConsoleLogger
- Dependency update:
- [Kotlin 1.9.23](https://kotlinlang.org/docs/whatsnew19.html)
- [Gradle-8.7](https://docs.gradle.org/8.7/release-notes.html)
diff --git a/log4k/build.gradle.kts b/log4k/build.gradle.kts
index 2c47df6..fa59a1e 100644
--- a/log4k/build.gradle.kts
+++ b/log4k/build.gradle.kts
@@ -18,6 +18,10 @@ kotlin {
applyDefaultHierarchyTemplate()
sourceSets {
+ commonMain.dependencies {
+ implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0-RC.2")
+ implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.3.2")
+ }
commonTest.dependencies {
implementation(kotlin("test"))
}
diff --git a/log4k/src/androidMain/AndroidManifest.xml b/log4k/src/androidMain/AndroidManifest.xml
new file mode 100644
index 0000000..4a37036
--- /dev/null
+++ b/log4k/src/androidMain/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/log4k/src/androidMain/kotlin/saschpe/log4k/FileLogger.android.kt b/log4k/src/androidMain/kotlin/saschpe/log4k/FileLogger.android.kt
new file mode 100644
index 0000000..30be4ed
--- /dev/null
+++ b/log4k/src/androidMain/kotlin/saschpe/log4k/FileLogger.android.kt
@@ -0,0 +1,8 @@
+package saschpe.log4k
+
+import kotlinx.io.files.Path
+import saschpe.log4k.internal.ContextProvider.Companion.applicationContext
+
+internal actual val defaultLogPath: Path
+ get() = Path(applicationContext.cacheDir.path)
+
diff --git a/log4k/src/androidMain/kotlin/saschpe/log4k/internal/ContextProvider.kt b/log4k/src/androidMain/kotlin/saschpe/log4k/internal/ContextProvider.kt
new file mode 100644
index 0000000..8bd6471
--- /dev/null
+++ b/log4k/src/androidMain/kotlin/saschpe/log4k/internal/ContextProvider.kt
@@ -0,0 +1,42 @@
+package saschpe.log4k.internal
+
+import android.annotation.SuppressLint
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+
+/**
+ * Hidden initialization content provider.
+ *
+ * Does not provide real content but hides initialization boilerplate from the library user.
+ *
+ * @link https://firebase.googleblog.com/2016/12/how-does-firebase-initialize-on-android.html
+ */
+internal class ContextProvider : ContentProvider() {
+ /**
+ * Called exactly once before Application.onCreate()
+ */
+ override fun onCreate(): Boolean {
+ applicationContext = context?.applicationContext ?: throw Exception("Need the context")
+ return true
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? = null
+
+ override fun query(
+ uri: Uri, projection: Array?, selection: String?, args: Array?, sortOrder: String?
+ ): Cursor? = null
+
+ override fun update(uri: Uri, values: ContentValues?, selection: String?, args: Array?): Int = 0
+
+ override fun delete(uri: Uri, selection: String?, args: Array?): Int = 0
+
+ override fun getType(uri: Uri): String? = null
+
+ companion object {
+ @SuppressLint("StaticFieldLeak")
+ lateinit var applicationContext: Context
+ }
+}
diff --git a/log4k/src/androidUnitTest/kotlin/saschpe/log4k/ConsoleLoggerTest.kt b/log4k/src/androidUnitTest/kotlin/saschpe/log4k/ConsoleLoggerTest.kt
new file mode 100644
index 0000000..f2dd1d0
--- /dev/null
+++ b/log4k/src/androidUnitTest/kotlin/saschpe/log4k/ConsoleLoggerTest.kt
@@ -0,0 +1,10 @@
+package saschpe.log4k
+
+import org.junit.Test
+
+class ConsoleLoggerTest {
+ @Test
+ fun foo() {
+ Log.debug { "hello" }
+ }
+}
diff --git a/log4k/src/appleMain/kotlin/saschpe/log4k/FileLogger.ios.kt b/log4k/src/appleMain/kotlin/saschpe/log4k/FileLogger.ios.kt
new file mode 100644
index 0000000..916ff79
--- /dev/null
+++ b/log4k/src/appleMain/kotlin/saschpe/log4k/FileLogger.ios.kt
@@ -0,0 +1,7 @@
+package saschpe.log4k
+
+import kotlinx.io.files.Path
+import kotlinx.io.files.SystemTemporaryDirectory
+
+internal actual val defaultLogPath: Path
+ get() = SystemTemporaryDirectory
diff --git a/log4k/src/commonMain/kotlin/saschpe/log4k/FileLogger.kt b/log4k/src/commonMain/kotlin/saschpe/log4k/FileLogger.kt
new file mode 100644
index 0000000..a10dcc7
--- /dev/null
+++ b/log4k/src/commonMain/kotlin/saschpe/log4k/FileLogger.kt
@@ -0,0 +1,72 @@
+package saschpe.log4k
+
+import kotlinx.io.files.Path
+import kotlinx.io.files.SystemFileSystem
+
+internal expect val defaultLogPath: Path
+
+class FileLogger(
+ private val rotation: Rotate = Rotate.Daily,
+ private val logPath: Path = defaultLogPath
+) : Logger() {
+ override fun print(level: Log.Level, tag: String, message: String?, throwable: Throwable?) {
+ var logTag = tag.ifEmpty { getTraceTag() }
+
+ val logFile: Path = rotation.logFile(logPath)
+// logFile.writeText("${level.name.first()}/$logTag: $message")
+// throwable?.let { logFile.writeText("$throwable") }
+ }
+
+ private fun getTraceTag(): String {
+// val trace = Exception().stackTrace[6]
+// val className = trace.className.split(".").last()
+// return "$className.${trace.methodName}"
+ return "foo"
+ }
+
+ sealed class Rotate {
+ internal abstract fun logFile(logPath: Path): Path
+
+ data object Daily : Rotate() {
+ override fun logFile(logPath: Path): Path {
+ val logFile = "daily.txt" // "daily.${DateFormat.format("yyyy-MM-dd", Date().time)}.txt"
+ return Path(logPath, logFile).apply { SystemFileSystem.createDirectories(this) }
+ }
+ }
+
+ class After(private val lines: Int = 10000) : Rotate() {
+ override fun logFile(logPath: Path): Path {
+ val logFile = Path(logPath, "log.txt")
+ val foes = if (SystemFileSystem.exists(logFile)/* && logFile.lines > lines*/) {
+// val now = DateFormat.format("yyyy-MM-dd_HH-mm-ss", Date().time)
+// logFile.renameTo(File(logPath, "log.$now.txt"))
+ Path(logPath, "log.txt")
+ } else {
+ logFile
+ }
+
+ return foes.apply { SystemFileSystem.createDirectories(this) }
+ }
+
+// private val File.lines: Int
+// get() {
+// var lines = 0
+// val fis = FileInputStream(this)
+// val buffer = ByteArray(BUFFER_SIZE)
+// var read: Int
+// while (fis.read(buffer).also { read = it } != -1) {
+// for (i in 0 until read) {
+// if (buffer[i] == '\n'.code.toByte())
+// lines++
+// }
+// }
+// fis.close()
+// return lines
+// }
+
+ companion object {
+ private const val BUFFER_SIZE = 8 * 1024
+ }
+ }
+ }
+}
diff --git a/log4k/src/jsMain/kotlin/saschpe/log4k/FileLogger.js.kt b/log4k/src/jsMain/kotlin/saschpe/log4k/FileLogger.js.kt
new file mode 100644
index 0000000..916ff79
--- /dev/null
+++ b/log4k/src/jsMain/kotlin/saschpe/log4k/FileLogger.js.kt
@@ -0,0 +1,7 @@
+package saschpe.log4k
+
+import kotlinx.io.files.Path
+import kotlinx.io.files.SystemTemporaryDirectory
+
+internal actual val defaultLogPath: Path
+ get() = SystemTemporaryDirectory
diff --git a/log4k/src/jvmMain/kotlin/saschpe/log4k/FileLogger.jvm.kt b/log4k/src/jvmMain/kotlin/saschpe/log4k/FileLogger.jvm.kt
new file mode 100644
index 0000000..916ff79
--- /dev/null
+++ b/log4k/src/jvmMain/kotlin/saschpe/log4k/FileLogger.jvm.kt
@@ -0,0 +1,7 @@
+package saschpe.log4k
+
+import kotlinx.io.files.Path
+import kotlinx.io.files.SystemTemporaryDirectory
+
+internal actual val defaultLogPath: Path
+ get() = SystemTemporaryDirectory