Skip to content

Commit 737a304

Browse files
committed
feat: introduce advanced logging and crash reporting
- Add Sentry integration for automated crash reporting - Introduce `FileLogWriter` and `SentryLogWriter` for enhanced diagnostic capabilities - Implement `CustomProperties` utility to manage dynamic app configuration - Initialize Koin with environment-aware logging modules - Add OS detection utility to handle cross-platform pathing and UI logic - Enable toggling for crash reporting and local logs in settings - Configure persistence for window size and position across sessions - Update Gradle build to use dynamic versioning and Java 21 toolchain - Refactor `AppLogger` to wrap Kermit logging consistently - Improve Mac-specific UI with transparent title bars and full-window content - Add functionality to copy latest log for bug reporting to GitHub
1 parent f5e4774 commit 737a304

35 files changed

Lines changed: 785 additions & 293 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ captures
1616
!*.xcodeproj/project.xcworkspace/
1717
!*.xcworkspace/contents.xcworkspacedata
1818
**/xcshareddata/WorkspaceSettings.xcsettings
19+
/composeApp/src/jvmMain/resources/props.properties

composeApp/build.gradle.kts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
2-
import java.util.Properties
32

43
plugins {
54
alias(libs.plugins.kotlinMultiplatform)
@@ -8,10 +7,26 @@ plugins {
87
alias(libs.plugins.composeHotReload)
98
alias(libs.plugins.kotlinSerialization)
109
}
10+
apply(from = "../versioning.gradle.kts")
11+
12+
group = "com.meet"
13+
14+
val appVersionName: () -> String by extra
15+
val appVersionCode: () -> Int by extra
16+
17+
java {
18+
toolchain {
19+
vendor = JvmVendorSpec.JETBRAINS
20+
languageVersion = JavaLanguageVersion.of(21)
21+
}
22+
}
1123

1224
kotlin {
25+
jvmToolchain {
26+
vendor = JvmVendorSpec.JETBRAINS
27+
languageVersion = JavaLanguageVersion.of(21)
28+
}
1329
jvm()
14-
jvmToolchain(17)
1530
sourceSets {
1631
commonMain.dependencies {
1732
// Compose Multiplatform - UI Framework
@@ -67,6 +82,9 @@ kotlin {
6782

6883
// Theme Changer
6984
implementation(libs.jsystemthemedetector)
85+
86+
// Crash Report
87+
api(libs.sentry)
7088
}
7189
commonTest.dependencies {
7290
implementation(libs.kotlin.test)
@@ -100,7 +118,7 @@ compose {
100118
TargetFormat.Deb // Linux
101119
)
102120
packageName = "DevAnalyzer"
103-
packageVersion = customPackageVersion
121+
packageVersion = appVersionName()
104122
includeAllModules = true
105123
description = "Deep insights into your development environment."
106124
vendor = "Meet"
@@ -139,13 +157,4 @@ compose {
139157
}
140158
}
141159
}
142-
}
143-
144-
val versionProps = Properties()
145-
val versionPropertiesFile = rootProject.file("version.properties")
146-
if (versionPropertiesFile.exists()) {
147-
versionPropertiesFile.inputStream().use { versionProps.load(it) }
148-
} else {
149-
throw GradleException("Root project version.properties not found! Please ensure it exists with the version number.")
150-
}
151-
val customPackageVersion: String = versionProps.getProperty("PACKAGE_VERSION")
160+
}

composeApp/compose-desktop.pro

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
-keep class androidx.datastore.*.** {*;}
1010

11+
# Sentry
12+
-keep class io.sentry.** { *; }
13+
1114
# Keep Ktor classes
1215
-keep class io.ktor.** { *; }
1316
-dontnote io.ktor.**

composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/core/utility/AppLinks.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import androidx.compose.material.icons.filled.Work
1111
import androidx.compose.ui.graphics.vector.ImageVector
1212

1313
object AppLinks {
14-
const val VERSION = "1.2.0"
1514
const val WEBSITE = "https://coding-meet.github.io/DevAnalyzer/"
1615
const val GITHUB_PROJECT =
1716
"https://www.github.com/Coding-Meet/DevAnalyzer"
Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,22 @@
11
package com.meet.dev.analyzer.core.utility
22

33
import co.touchlab.kermit.Logger
4-
import co.touchlab.kermit.StaticConfig
5-
import co.touchlab.kermit.platformLogWriter
64

75
object AppLogger {
86

9-
const val isDebugBuild = false
10-
11-
val logger = Logger(
12-
config = StaticConfig(
13-
logWriterList = if (isDebugBuild) {
14-
listOf(platformLogWriter())
15-
} else {
16-
emptyList() // No logging in release
17-
}
18-
),
19-
tag = "Dev-Analyzer"
20-
)
21-
22-
inline fun d(tag: String = "", throwable: Throwable? = null, message: () -> String) {
23-
if (isDebugBuild) {
24-
logger.d(tag = tag, throwable = throwable, message = message)
25-
}
7+
fun d(tag: String = "", throwable: Throwable? = null, message: () -> String) {
8+
Logger.d(tag, throwable = throwable, message = message)
269
}
27-
inline fun i(tag: String = "", throwable: Throwable? = null, message: () -> String) {
28-
if (isDebugBuild) {
29-
logger.i(tag = tag, throwable = throwable, message = message)
30-
}
10+
11+
fun i(tag: String = "", throwable: Throwable? = null, message: () -> String) {
12+
Logger.i(tag, throwable = throwable, message = message)
3113
}
32-
inline fun e(tag: String = "", throwable: Throwable? = null, message: () -> String) {
33-
if (isDebugBuild) {
34-
logger.e(tag = tag, throwable = throwable, message = message)
35-
}
14+
15+
fun e(tag: String = "", throwable: Throwable? = null, message: () -> String) {
16+
Logger.e(tag, throwable = throwable, message = message)
3617
}
37-
inline fun w(tag: String = "", throwable: Throwable? = null, message: () -> String) {
38-
if (isDebugBuild) {
39-
logger.w(tag = tag, throwable = throwable, message = message)
40-
}
18+
19+
fun w(tag: String = "", throwable: Throwable? = null, message: () -> String) {
20+
Logger.w(tag, throwable = throwable, message = message)
4121
}
4222
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.meet.dev.analyzer.core.utility
2+
3+
import java.io.InputStream
4+
import java.util.Properties
5+
6+
object CustomProperties {
7+
fun loadProperties(): Properties {
8+
val properties = Properties()
9+
val propsFile =
10+
CustomProperties::class.java.classLoader?.getResourceAsStream("props.properties")
11+
?: InputStream.nullInputStream()
12+
properties.load(propsFile)
13+
return properties
14+
}
15+
16+
fun createAppConfig(properties: Properties): DesktopConfig {
17+
val sentryDns = properties["sentry_dns"]?.toString()
18+
val version = properties["version"]?.toString()
19+
val isRelease = properties["is_release"]?.toString()?.toBooleanStrictOrNull() ?: false
20+
21+
val appEnvironment = if (isRelease) AppEnvironment.Release else AppEnvironment.Debug
22+
23+
return DesktopConfig(
24+
sentryDns = sentryDns,
25+
version = version,
26+
appEnvironment = appEnvironment,
27+
)
28+
}
29+
30+
fun setupCrashReporting(appConfig: DesktopConfig, isCrashReportEnabled: Boolean) {
31+
if (appConfig.appEnvironment.isRelease() &&
32+
appConfig.sentryDns != null &&
33+
appConfig.version != null &&
34+
isCrashReportEnabled
35+
) {
36+
initSentry(
37+
dns = appConfig.sentryDns,
38+
version = appConfig.version,
39+
)
40+
} else {
41+
disableSentry()
42+
}
43+
}
44+
45+
fun setupLocalLogs(isLocalLogsEnabled: Boolean) {
46+
if (isLocalLogsEnabled) {
47+
enableLocalLogs()
48+
} else {
49+
disableLocalLogs()
50+
}
51+
}
52+
}

composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/core/utility/DefaultPath.kt

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ package com.meet.dev.analyzer.core.utility
33

44
fun getDefaultAndroidSdkPath(): String {
55
val userHome = System.getProperty("user.home")
6-
val os = System.getProperty("os.name").lowercase()
7-
return when {
8-
os.contains("windows") -> System.getenv("ANDROID_HOME")
6+
return when (getDesktopOS()) {
7+
DesktopOS.WINDOWS -> System.getenv("ANDROID_HOME")
98
?: "$userHome\\AppData\\Local\\Android\\Sdk"
109

11-
os.contains("mac") -> System.getenv("ANDROID_HOME")
10+
DesktopOS.MAC -> System.getenv("ANDROID_HOME")
1211
?: "$userHome/Library/Android/sdk"
1312

14-
else -> System.getenv("ANDROID_HOME")
13+
DesktopOS.LINUX -> System.getenv("ANDROID_HOME")
1514
?: "$userHome/Android/Sdk"
1615
}
1716
}
@@ -40,21 +39,20 @@ fun getDefaultKonanFolderPath(): String {
4039

4140
fun getDefaultJetbrainsFolderPaths(): List<String> {
4241
val userHome = System.getProperty("user.home")
43-
val os = System.getProperty("os.name").lowercase()
44-
return when {
45-
os.contains("windows") -> listOf(
42+
return when (getDesktopOS()) {
43+
DesktopOS.WINDOWS -> listOf(
4644
"C:/Program Files/JetBrains",
4745
"$userHome\\AppData\\Local\\JetBrains",
4846
"$userHome\\AppData\\Roaming\\JetBrains"
4947
)
5048

51-
os.contains("mac") -> listOf(
49+
DesktopOS.MAC -> listOf(
5250
"$userHome/Library/Caches/JetBrains",
5351
"$userHome/Library/Logs/JetBrains",
5452
"$userHome/Library/Application Support/JetBrains"
5553
)
5654

57-
else -> listOf( // Linux / Unix
55+
DesktopOS.LINUX -> listOf( // Linux / Unix
5856
"$userHome/.cache/JetBrains",
5957
"$userHome/.cache/JetBrains",
6058
"$userHome/.config/JetBrains"
@@ -64,21 +62,20 @@ fun getDefaultJetbrainsFolderPaths(): List<String> {
6462

6563
fun getDefaultGoogleFolderPaths(): List<String> {
6664
val userHome = System.getProperty("user.home")
67-
val os = System.getProperty("os.name").lowercase()
68-
return when {
69-
os.contains("windows") -> listOf(
65+
return when (getDesktopOS()) {
66+
DesktopOS.WINDOWS -> listOf(
7067
"C:/Program Files/Android",
7168
"$userHome\\AppData\\Local\\Google",
7269
"$userHome\\AppData\\Roaming\\Google"
7370
)
7471

75-
os.contains("mac") -> listOf(
72+
DesktopOS.MAC -> listOf(
7673
"$userHome/Library/Caches/Google",
7774
"$userHome/Library/Logs/Google",
7875
"$userHome/Library/Application Support/Google"
7976
)
8077

81-
else -> listOf( // Linux / Unix
78+
DesktopOS.LINUX -> listOf( // Linux / Unix
8279
"$userHome/.cache/Google",
8380
"$userHome/.cache/Google",
8481
"$userHome/.config/Google"
@@ -88,24 +85,23 @@ fun getDefaultGoogleFolderPaths(): List<String> {
8885

8986
fun getDefaultJdkFolderPaths(): List<String> {
9087
val userHome = System.getProperty("user.home")
91-
val os = System.getProperty("os.name").lowercase()
9288

93-
return when {
94-
os.contains("mac") ->
89+
return when (getDesktopOS()) {
90+
DesktopOS.MAC ->
9591
listOf(
9692
"$userHome/Library/Java/JavaVirtualMachines",
9793
"/Library/Java/JavaVirtualMachines",
9894
"$userHome/.gradle/jdks",
9995
)
10096

101-
os.contains("windows") ->
97+
DesktopOS.WINDOWS ->
10298
listOf(
10399
"C:\\Program Files\\Java",
104100
"C:\\Program Files\\Eclipse Adoptium",
105101
"$userHome\\.gradle\\jdks"
106102
)
107103

108-
else ->
104+
DesktopOS.LINUX ->
109105
listOf(
110106
"/usr/lib/jvm",
111107
"$userHome/.gradle/jdks",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.meet.dev.analyzer.core.utility
2+
3+
data class DesktopConfig(
4+
val sentryDns: String?,
5+
val version: String?,
6+
val appEnvironment: AppEnvironment,
7+
val os: DesktopOS = getDesktopOS(),
8+
)
9+
10+
sealed class AppEnvironment {
11+
data object Debug : AppEnvironment()
12+
data object Release : AppEnvironment()
13+
14+
fun isDebug(): Boolean =
15+
this is Debug
16+
17+
fun isRelease(): Boolean =
18+
this is Release
19+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.meet.dev.analyzer.core.utility
2+
3+
enum class DesktopOS {
4+
WINDOWS,
5+
MAC,
6+
LINUX,
7+
}
8+
9+
fun getDesktopOS(): DesktopOS {
10+
val osName = System.getProperty("os.name").lowercase()
11+
return when {
12+
osName.contains("win") -> DesktopOS.WINDOWS
13+
osName.contains("mac") -> DesktopOS.MAC
14+
else -> DesktopOS.LINUX
15+
}
16+
}
17+
18+
fun DesktopOS.isMacOs() = this == DesktopOS.MAC
19+
fun DesktopOS.isNotMacOs() = !isMacOs()
20+
21+
fun DesktopOS.isLinux() = this == DesktopOS.LINUX
22+
fun DesktopOS.isNotLinux() = !isLinux()
23+
24+
fun DesktopOS.isWindows() = this == DesktopOS.WINDOWS
25+
fun DesktopOS.isNotWindows() = !isWindows()
26+

0 commit comments

Comments
 (0)