Skip to content

Commit

Permalink
basic impl
Browse files Browse the repository at this point in the history
  • Loading branch information
davin111 committed Jan 22, 2023
1 parent 695ee99 commit 0f13e61
Show file tree
Hide file tree
Showing 17 changed files with 281 additions and 9 deletions.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ allprojects {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactive")

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-webflux")
}

tasks.withType<KotlinCompile> {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
group=io.wafflestudio.truffle.sdk
group=com.wafflestudio.truffle.sdk
version=1.0.0-SNAPSHOT
kotlin.code.style=official
3 changes: 3 additions & 0 deletions truffle-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies {
compileOnly("org.springframework.boot:spring-boot-starter-webflux")
}
70 changes: 70 additions & 0 deletions truffle-core/src/main/kotlin/TruffleClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.wafflestudio.truffle.sdk.core

import com.wafflestudio.truffle.sdk.core.protocol.TruffleApp
import com.wafflestudio.truffle.sdk.core.protocol.TruffleEvent
import com.wafflestudio.truffle.sdk.core.protocol.TruffleException
import com.wafflestudio.truffle.sdk.core.protocol.TruffleRuntime
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.reactor.awaitSingle
import org.slf4j.LoggerFactory
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.bodyToMono
import java.time.Duration
import java.util.concurrent.Executors

interface TruffleClient {
fun sendEvent(ex: Throwable)
}

class DefaultTruffleClient(
name: String,
phase: String,
apiKey: String,
webClientBuilder: WebClient.Builder,
) : TruffleClient {
private val events = MutableSharedFlow<TruffleEvent>(extraBufferCapacity = 10)

private val logger = LoggerFactory.getLogger(javaClass)

private val truffleApp = TruffleApp(name, phase)
private val truffleRuntime = TruffleRuntime(name = "Java", version = System.getProperty("java.version"))

init {
val coroutineScope = CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher())
val webClient = webClientBuilder
.baseUrl("https://truffle-api.wafflestudio.com")
.defaultHeader("x-api-key", apiKey)
.build()

coroutineScope.launch(SupervisorJob()) {
events.collect {
runCatching {
webClient
.post()
.uri("/events")
.bodyValue(it)
.retrieve()
.bodyToMono<Unit>()
.timeout(Duration.ofSeconds(1))
.awaitSingle()
}.getOrElse {
logger.warn("Failed to request to truffle server", it)
}
}
}
}

override fun sendEvent(ex: Throwable) {
events.tryEmit(
TruffleEvent(
app = truffleApp,
runtime = truffleRuntime,
exception = TruffleException(ex),
)
)
}
}
6 changes: 6 additions & 0 deletions truffle-core/src/main/kotlin/protocol/TruffleApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wafflestudio.truffle.sdk.core.protocol

data class TruffleApp(
val name: String,
val phase: String? = null,
)
8 changes: 8 additions & 0 deletions truffle-core/src/main/kotlin/protocol/TruffleEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wafflestudio.truffle.sdk.core.protocol

data class TruffleEvent(
val version: String = TruffleVersion.V1,
val app: TruffleApp,
val runtime: TruffleRuntime,
val exception: TruffleException,
)
29 changes: 29 additions & 0 deletions truffle-core/src/main/kotlin/protocol/TruffleException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.wafflestudio.truffle.sdk.core.protocol

data class TruffleException(
val className: String,
val message: String?,
val elements: List<Element>,
) {
data class Element(
val className: String,
val methodName: String,
val lineNumber: Int,
val fileName: String,
val isInAppInclude: Boolean,
)
}

fun TruffleException(e: Throwable): TruffleException = TruffleException(
className = e.javaClass.name,
message = e.message,
elements = e.stackTrace.map {
TruffleException.Element(
className = it.className,
methodName = it.methodName,
lineNumber = it.lineNumber,
fileName = it.fileName ?: "",
isInAppInclude = true, // FIXME
)
}
)
6 changes: 6 additions & 0 deletions truffle-core/src/main/kotlin/protocol/TruffleRuntime.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wafflestudio.truffle.sdk.core.protocol

data class TruffleRuntime(
val name: String,
val version: String,
)
7 changes: 7 additions & 0 deletions truffle-core/src/main/kotlin/protocol/TruffleVersion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.wafflestudio.truffle.sdk.core.protocol

interface TruffleVersion {
companion object {
const val V1 = "v1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.wafflestudio.truffle.sdk

import com.wafflestudio.truffle.sdk.core.DefaultTruffleClient
import com.wafflestudio.truffle.sdk.core.TruffleClient
import com.wafflestudio.truffle.sdk.reactive.TruffleWebExceptionHandler
import com.wafflestudio.truffle.sdk.servlet.TruffleHandlerExceptionResolver
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.server.WebExceptionHandler
import org.springframework.web.servlet.HandlerExceptionResolver

@EnableConfigurationProperties(TruffleProperties::class)
@Configuration
class TruffleAutoConfiguration {
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Configuration
class TruffleServletConfiguration {
@Bean
fun truffleHandlerExceptionResolver(truffleClient: TruffleClient): HandlerExceptionResolver {
return TruffleHandlerExceptionResolver(truffleClient)
}
}

@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@Configuration
class TruffleReactiveConfiguration {
@Bean
fun truffleWebExceptionHandler(truffleClient: TruffleClient): WebExceptionHandler {
return TruffleWebExceptionHandler(truffleClient)
}
}

@Bean
fun truffleClient(properties: TruffleProperties, webClientBuilder: WebClient.Builder): TruffleClient =
DefaultTruffleClient(
name = properties.name,
phase = properties.phase,
apiKey = properties.apiKey,
webClientBuilder = webClientBuilder,
)
}
10 changes: 10 additions & 0 deletions truffle-spring-boot-starter/src/main/kotlin/TruffleProperties.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.wafflestudio.truffle.sdk

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("truffle.client")
data class TruffleProperties(
val name: String,
val phase: String,
val apiKey: String,
)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.wafflestudio.truffle.sdk.reactive

import com.wafflestudio.truffle.sdk.core.TruffleClient
import org.springframework.core.annotation.Order
import org.springframework.web.server.ResponseStatusException
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebExceptionHandler
import reactor.core.publisher.Mono

@Order(-2)
class TruffleWebExceptionHandler(
private val truffleClient: TruffleClient,
) : WebExceptionHandler {
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
if (ex !is ResponseStatusException) {
truffleClient.sendEvent(ex)
}

return Mono.error(ex)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.wafflestudio.truffle.sdk.servlet

import com.wafflestudio.truffle.sdk.core.TruffleClient
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.core.annotation.Order
import org.springframework.web.server.ResponseStatusException
import org.springframework.web.servlet.HandlerExceptionResolver
import org.springframework.web.servlet.ModelAndView
import java.lang.Exception

@Order(-2)
class TruffleHandlerExceptionResolver(
private val truffleClient: TruffleClient,
) : HandlerExceptionResolver {
override fun resolveException(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any?,
ex: Exception,
): ModelAndView? {
if (ex !is ResponseStatusException) {
truffleClient.sendEvent(ex)
}

return null
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.wafflestudio.truffle.sdk.config.TruffleAutoConfiguration
com.wafflestudio.truffle.sdk.TruffleAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.wafflestudio.truffle.sdk

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@SpringBootApplication
class TestTruffleApplication {
@RestController
class TestController {
@GetMapping("/test")
fun test(): String {
throw RuntimeException("test")
}
}
}

@AutoConfigureWebTestClient
@SpringBootTest(properties = ["spring.main.web-application-type=reactive"])
class TruffleReactiveTest(
@Autowired private val webTestClient: WebTestClient,
) {
@Test
fun truffleTest() {
webTestClient.get()
.uri("/test")
.exchange()
.expectStatus()
.is5xxServerError

Thread.sleep(3000)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
truffle:
client:
name: snutt
phase: dev
api-key: abcdef

0 comments on commit 0f13e61

Please sign in to comment.