Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ android {
buildConfigField("String", "NAVER_CLIENT_SECRET", properties["NAVER_CLIENT_SECRET"].toString())
buildConfigField("String", "KAKAO_NATIVE_APP_KEY", properties["kakao.native.app.key"].toString())
manifestPlaceholders["KAKAO_NATIVE_APP_KEY_MANIFEST"] = properties["kakao.native.app.key.manifest"].toString()
buildConfigField("String", "DISCORD_WEBHOOK_URL", properties["DISCORD_WEBHOOK_URL"].toString())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

속성 누락 시 빌드 실패 가능성 확인 필요

local.propertiesDISCORD_WEBHOOK_URL이 없을 경우 빌드가 실패할 수 있습니다. 다른 개발자나 CI/CD 환경에서 문제가 발생할 수 있으니, 속성 존재 여부 확인 후 기본값 제공 또는 명확한 에러 메시지 추가를 고려해보세요.

🔎 제안하는 안전한 처리 방법
-        buildConfigField("String", "DISCORD_WEBHOOK_URL", properties["DISCORD_WEBHOOK_URL"].toString())
+        buildConfigField(
+            "String", 
+            "DISCORD_WEBHOOK_URL", 
+            properties.getProperty("DISCORD_WEBHOOK_URL") 
+                ?: throw GradleException("DISCORD_WEBHOOK_URL must be defined in local.properties")
+        )

또는 개발 환경에서 빈 문자열을 기본값으로 사용:

-        buildConfigField("String", "DISCORD_WEBHOOK_URL", properties["DISCORD_WEBHOOK_URL"].toString())
+        buildConfigField(
+            "String", 
+            "DISCORD_WEBHOOK_URL", 
+            properties.getProperty("DISCORD_WEBHOOK_URL", "\"\"")
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
buildConfigField("String", "DISCORD_WEBHOOK_URL", properties["DISCORD_WEBHOOK_URL"].toString())
buildConfigField(
"String",
"DISCORD_WEBHOOK_URL",
properties.getProperty("DISCORD_WEBHOOK_URL")
?: throw GradleException("DISCORD_WEBHOOK_URL must be defined in local.properties")
)
🤖 Prompt for AI Agents
In app/build.gradle.kts around line 38, the buildConfigField uses
properties["DISCORD_WEBHOOK_URL"] directly which can throw or fail the build
when the property is missing; update the script to first check for the property
key and either supply a safe default (e.g., empty string) or fail fast with a
clear error message indicating the missing property and how to set it
(local.properties or CI secret). Ensure the check happens before calling
toString(), and choose one approach (default empty value for dev builds or an
explicit Gradle exception with instructions for CI) so builds behave
predictably.

}

signingConfigs {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.kuit.findu.data.dataremote.service

import com.kuit.findu.domain.model.DiscordLogBody
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Url

interface WebhookService {
@POST
suspend fun sendLog(
@Url webhookUrl: String,
@Body body: DiscordLogBody
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.kuit.findu.data.dataremote.util

import com.kuit.findu.data.dataremote.service.WebhookService
import com.kuit.findu.domain.model.DiscordLogBody
import com.kuit.findu.domain.repository.UserInfoRepository
import javax.inject.Inject

class DiscordLogger @Inject constructor(
private val webhookService: WebhookService,
private val userInfoRepository: UserInfoRepository,
private val webhookUrl: String,
) {

suspend fun logServerError(
code: Int,
method: String,
url: String,
) {
val deviceId = userInfoRepository.getDeviceId()
val nickname = userInfoRepository.getNickname()

val body = DiscordLogBody(
content = """
🚨 **Server Error 발생**

👤 User
- Nickname: $nickname
- DeviceId: $deviceId

🌐 Request
- Method: $method
- Url: $url
- Code: $code
""".trimIndent()
)

runCatching {
webhookService.sendLog(webhookUrl, body)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,54 @@ package com.kuit.findu.data.dataremote.util

import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.crashlytics.recordException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject

class ErrorTrackingInterceptor @Inject constructor(
private val firebaseCrashlytics: FirebaseCrashlytics,
private val discordLogger: DiscordLogger,
) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response: Response

try {
response = chain.proceed(request)
} catch (e: Exception) {
// 1. 네트워크 자체가 끊긴 경우 등 (IOException)
firebaseCrashlytics.recordException(e)
throw e
}

// 2. 서버에서 응답은 왔으나 4xx, 5xx 에러인 경우
if (!response.isSuccessful) {
val code = response.code

val url = request.url.toString()

// Crashlytics에 상세 정보 기록
val exceptionMessage = when (response.code) {
in (400..499) -> "Client $code Error"
in (500..599) -> "Server $code Error"
val exceptionMessage = when (code) {
in 400..499 -> "Client $code Error"
in 500..599 -> "Server $code Error"
else -> "Unknown $code Error"
}

firebaseCrashlytics.recordException(Exception(exceptionMessage)) {
key("api_method", request.method)
key("api_url", url)
key("api_status", code)
}

if (code in 500..599) {
CoroutineScope(Dispatchers.IO).launch {
discordLogger.logServerError(
code = code,
method = request.method,
url = url
)
}
}
}

return response
Expand Down
27 changes: 27 additions & 0 deletions app/src/main/java/com/kuit/findu/di/LogModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.kuit.findu.di

import com.kuit.findu.BuildConfig
import com.kuit.findu.data.dataremote.service.WebhookService
import com.kuit.findu.data.dataremote.util.DiscordLogger
import com.kuit.findu.domain.repository.UserInfoRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object LogModule {
@Provides
@Singleton
fun provideDiscordLogger(
webhookService: WebhookService,
userInfoRepository: UserInfoRepository,
): DiscordLogger =
DiscordLogger(
webhookService,
userInfoRepository,
BuildConfig.DISCORD_WEBHOOK_URL,
)
}
42 changes: 42 additions & 0 deletions app/src/main/java/com/kuit/findu/di/LogNetworkModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.kuit.findu.di

import com.kuit.findu.data.dataremote.service.WebhookService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Named
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object LogNetworkModule {

@Provides
@Singleton
@Named("webhook")
fun provideWebhookOkHttpClient(): OkHttpClient =
OkHttpClient.Builder().build()

@Provides
@Singleton
@Named("webhook")
fun provideWebhookRetrofit(
@Named("webhook") okHttpClient: OkHttpClient
): Retrofit =
Retrofit.Builder()
.baseUrl("https://discord.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()

@Provides
@Singleton
fun provideWebhookService(
@Named("webhook") retrofit: Retrofit
): WebhookService =
retrofit.create(WebhookService::class.java)
}
7 changes: 6 additions & 1 deletion app/src/main/java/com/kuit/findu/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.kuit.findu.BuildConfig.DEBUG
import com.kuit.findu.data.datalocal.datasource.TokenLocalDataSource
import com.kuit.findu.data.dataremote.util.AuthAuthenticator
import com.kuit.findu.data.dataremote.util.AuthInterceptor
import com.kuit.findu.data.dataremote.util.DiscordLogger
import com.kuit.findu.data.dataremote.util.ErrorTrackingInterceptor
import dagger.Module
import dagger.Provides
Expand Down Expand Up @@ -85,8 +86,12 @@ object NetworkModule {
@Singleton
fun provideErrorTrackingInterceptor(
firebaseCrashlytics: FirebaseCrashlytics,
discordLogger: DiscordLogger
): ErrorTrackingInterceptor {
return ErrorTrackingInterceptor(firebaseCrashlytics)
return ErrorTrackingInterceptor(
firebaseCrashlytics= firebaseCrashlytics,
discordLogger = discordLogger
)
}

@ExperimentalSerializationApi
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.kuit.findu.domain.model

data class DiscordLogBody(
val content: String
)