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