Skip to content

Commit f6df6f4

Browse files
bundler ikke med naisful-app
1 parent 253c42b commit f6df6f4

File tree

4 files changed

+184
-65
lines changed

4 files changed

+184
-65
lines changed

build.gradle.kts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
22

3+
val slf4jVersion = "2.0.16"
4+
val ktorVersion = "3.0.1"
5+
val micrometerRegistryPrometheusVersion = "1.13.6"
36
val junitJupiterVersion = "5.11.3"
47
val logbackClassicVersion = "1.5.12"
58
val logbackEncoderVersion = "8.0"
@@ -17,9 +20,15 @@ plugins {
1720
}
1821

1922
dependencies {
20-
api("com.github.navikt.tbd-libs:naisful-app:$tbdLibsVersion")
23+
api("org.slf4j:slf4j-api:$slf4jVersion")
24+
2125
api("com.github.navikt.tbd-libs:rapids-and-rivers:$tbdLibsVersion")
2226

27+
api("io.ktor:ktor-server-cio:$ktorVersion")
28+
29+
api("io.ktor:ktor-server-metrics-micrometer:$ktorVersion")
30+
api("io.micrometer:micrometer-registry-prometheus:$micrometerRegistryPrometheusVersion")
31+
2332
api("ch.qos.logback:logback-classic:$logbackClassicVersion")
2433
api("net.logstash.logback:logstash-logback-encoder:$logbackEncoderVersion")
2534

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package no.nav.helse.rapids_rivers
2+
3+
import io.ktor.http.*
4+
import io.ktor.server.application.*
5+
import io.ktor.server.cio.*
6+
import io.ktor.server.engine.*
7+
import io.ktor.server.metrics.micrometer.*
8+
import io.ktor.server.response.*
9+
import io.ktor.server.routing.*
10+
import io.micrometer.core.instrument.MeterRegistry
11+
import io.micrometer.core.instrument.binder.jvm.*
12+
import io.micrometer.core.instrument.binder.logging.LogbackMetrics
13+
import io.micrometer.core.instrument.binder.system.ProcessorMetrics
14+
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry
15+
import org.slf4j.Logger
16+
import org.slf4j.LoggerFactory
17+
import java.util.concurrent.atomic.AtomicBoolean
18+
19+
data class NaisEndpoints(
20+
val isaliveEndpoint: String,
21+
val isreadyEndpoint: String,
22+
val metricsEndpoint: String,
23+
val preStopEndpoint: String
24+
) {
25+
companion object {
26+
val Default = NaisEndpoints(
27+
isaliveEndpoint = "/isalive",
28+
isreadyEndpoint = "/isready",
29+
metricsEndpoint = "/metrics",
30+
preStopEndpoint = "/stop"
31+
)
32+
}
33+
}
34+
35+
fun ktorApplication(
36+
naisEndpoints: NaisEndpoints,
37+
meterRegistry: PrometheusMeterRegistry,
38+
preStopHook: suspend () -> Unit,
39+
aliveCheck: () -> Boolean,
40+
readyCheck: () -> Boolean,
41+
cioConfiguration: CIOApplicationEngine.Configuration.() -> Unit,
42+
port: Int,
43+
modules: List<Application.() -> Unit>,
44+
applicationLogger: Logger = LoggerFactory.getLogger("no.nav.helse.rapids_rivers.ktorApplication"),
45+
developmentMode: Boolean = false,
46+
): EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration> {
47+
val config = serverConfig(
48+
environment = applicationEnvironment {
49+
log = applicationLogger
50+
}
51+
) {
52+
this.developmentMode = developmentMode
53+
module {
54+
monitor.subscribe(ApplicationStarting) { it.log.info("Application starting …") }
55+
monitor.subscribe(ApplicationStarted) { it.log.info("Application started …") }
56+
monitor.subscribe(ServerReady) { it.log.info("Application ready …") }
57+
monitor.subscribe(ApplicationStopPreparing) { it.log.info("Application preparing to stop …") }
58+
monitor.subscribe(ApplicationStopping) { it.log.info("Application stopping …") }
59+
monitor.subscribe(ApplicationStopped) { it.log.info("Application stopped …") }
60+
61+
applicationModule(meterRegistry)
62+
modules.forEach { it() }
63+
naisRoutings(naisEndpoints, meterRegistry, preStopHook, aliveCheck, readyCheck)
64+
}
65+
}
66+
val defaultConfig = CIOApplicationEngine.Configuration().apply {
67+
connector {
68+
this.port = port
69+
}
70+
}
71+
val cioConfig = defaultConfig.apply(cioConfiguration)
72+
return EmbeddedServer(config, CIO) {
73+
takeFrom(cioConfig)
74+
}
75+
}
76+
77+
private fun Application.applicationModule(meterRegistry: MeterRegistry) {
78+
install(MicrometerMetrics) {
79+
registry = meterRegistry
80+
val defaultBinders = listOf(
81+
ClassLoaderMetrics(),
82+
JvmInfoMetrics(),
83+
JvmMemoryMetrics(),
84+
JvmThreadMetrics(),
85+
JvmGcMetrics(),
86+
ProcessorMetrics()
87+
)
88+
meterBinders = defaultBinders + buildList {
89+
try {
90+
Class.forName("ch.qos.logback.classic.LoggerContext")
91+
add(LogbackMetrics())
92+
} catch (_: ClassNotFoundException) {}
93+
}
94+
}
95+
}
96+
97+
private fun Application.naisRoutings(
98+
naisEndpoints: NaisEndpoints,
99+
meterRegistry: PrometheusMeterRegistry,
100+
preStopHook: suspend () -> Unit,
101+
aliveCheck: () -> Boolean,
102+
readyCheck: () -> Boolean
103+
) {
104+
val readyToggle = AtomicBoolean(false)
105+
monitor.subscribe(ApplicationStarted) {
106+
readyToggle.set(true)
107+
}
108+
monitor.subscribe(ApplicationStopPreparing) {
109+
readyToggle.set(false)
110+
}
111+
112+
routing {
113+
/*
114+
https://doc.nais.io/workloads/explanations/good-practices/?h=graceful#handles-termination-gracefully
115+
116+
termination lifecycle:
117+
1. Application (pod) gets status TERMINATING, and grace period starts (default 30s)
118+
(simultaneous with 1) If the pod has a preStop hook defined, this is invoked
119+
(simultaneous with 1) The pod is removed from the list of endpoints i.e. taken out of load balancing
120+
(simultaneous with 1, but after preStop if defined) Container receives SIGTERM, and should prepare for shutdown
121+
2. Grace period ends, and container receives SIGKILL
122+
3. Pod disappears from the API, and is no longer visible for the client.
123+
*/
124+
get(naisEndpoints.preStopEndpoint) {
125+
application.log.info("Received shutdown signal via preStopHookPath, calling actual stop hook")
126+
application.monitor.raise(ApplicationStopPreparing, environment)
127+
128+
/**
129+
* fra doccen:
130+
* Be aware that even after your preStop-hook has been triggered,
131+
* your application might still receive new connections for a few seconds.
132+
* This is because step 3 above can take a few seconds to complete.
133+
* Your application should handle those connections before exiting.
134+
*/
135+
preStopHook()
136+
137+
application.log.info("Stop hook returned. Responding to preStopHook request with 200 OK")
138+
call.respond(HttpStatusCode.OK)
139+
}
140+
get(naisEndpoints.isaliveEndpoint) {
141+
if (!aliveCheck()) return@get call.respond(HttpStatusCode.ServiceUnavailable)
142+
call.respondText("ALIVE", ContentType.Text.Plain)
143+
}
144+
get(naisEndpoints.isreadyEndpoint) {
145+
if (!readyToggle.get() || !readyCheck()) return@get call.respond(HttpStatusCode.ServiceUnavailable)
146+
call.respondText("READY", ContentType.Text.Plain)
147+
}
148+
149+
get(naisEndpoints.metricsEndpoint) {
150+
call.respond(meterRegistry.scrape())
151+
}
152+
}
153+
}

src/main/kotlin/no/nav/helse/rapids_rivers/RapidApplication.kt

+20-61
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
package no.nav.helse.rapids_rivers
22

3-
import com.fasterxml.jackson.databind.ObjectMapper
4-
import com.fasterxml.jackson.databind.SerializationFeature
5-
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
6-
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
73
import com.github.navikt.tbd_libs.kafka.AivenConfig
84
import com.github.navikt.tbd_libs.kafka.ConsumerProducerFactory
9-
import com.github.navikt.tbd_libs.naisful.NaisEndpoints
10-
import com.github.navikt.tbd_libs.naisful.defaultStatusPagesConfig
11-
import com.github.navikt.tbd_libs.naisful.naisApp
125
import com.github.navikt.tbd_libs.rapids_and_rivers.JsonMessage
136
import com.github.navikt.tbd_libs.rapids_and_rivers.KafkaRapid
147
import com.github.navikt.tbd_libs.rapids_and_rivers.createDefaultKafkaRapidFromEnv
@@ -18,12 +11,10 @@ import com.github.navikt.tbd_libs.rapids_and_rivers_api.RapidsConnection
1811
import io.ktor.server.application.*
1912
import io.ktor.server.cio.*
2013
import io.ktor.server.engine.*
21-
import io.ktor.server.plugins.statuspages.StatusPagesConfig
2214
import io.micrometer.core.instrument.Clock
2315
import io.micrometer.core.instrument.MeterRegistry
2416
import io.micrometer.core.instrument.MultiGauge
2517
import io.micrometer.core.instrument.Tags
26-
import io.micrometer.core.instrument.Timer
2718
import io.micrometer.prometheusmetrics.PrometheusConfig
2819
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry
2920
import io.prometheus.metrics.model.registry.PrometheusRegistry
@@ -142,9 +133,6 @@ class RapidApplication internal constructor(
142133
env: Map<String, String>,
143134
consumerProducerFactory: ConsumerProducerFactory = ConsumerProducerFactory(AivenConfig.default),
144135
meterRegistry: PrometheusMeterRegistry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT, PrometheusRegistry.defaultRegistry, Clock.SYSTEM),
145-
objectMapper: ObjectMapper = jacksonObjectMapper()
146-
.registerModule(JavaTimeModule())
147-
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS),
148136
builder: Builder.() -> Unit = {},
149137
configure: (EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration>, KafkaRapid) -> Unit = { _, _ -> }
150138
): RapidsConnection {
@@ -157,8 +145,7 @@ class RapidApplication internal constructor(
157145
appName = env["RAPID_APP_NAME"] ?: generateAppName(env),
158146
instanceId = generateInstanceId(env),
159147
rapid = kafkaRapid,
160-
meterRegistry = meterRegistry,
161-
objectMapper = objectMapper
148+
meterRegistry = meterRegistry
162149
)
163150
.apply(builder)
164151
.build(configure)
@@ -181,8 +168,7 @@ class RapidApplication internal constructor(
181168
private val appName: String?,
182169
private val instanceId: String,
183170
private val rapid: KafkaRapid,
184-
private val meterRegistry: PrometheusMeterRegistry,
185-
private val objectMapper: ObjectMapper
171+
private val meterRegistry: PrometheusMeterRegistry
186172
) {
187173

188174
init {
@@ -193,12 +179,9 @@ class RapidApplication internal constructor(
193179
private var ktor: EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration>? = null
194180
private val modules = mutableListOf<Application.() -> Unit>()
195181
private var naisEndpoints = NaisEndpoints.Default
196-
private var statusPagesConfig: StatusPagesConfig.() -> Unit = { defaultStatusPagesConfig() }
197-
private var callIdHeader: String = "callId"
198182
private val isAliveChecks = mutableSetOf<() -> Boolean>(rapid::isRunning)
199183
private val isReadyChecks = mutableSetOf<() -> Boolean>(rapid::isReady)
200-
private var timersConfig: Timer.Builder.(ApplicationCall, Throwable?) -> Unit = { _, _ -> }
201-
private var mdcEntries: Map<String, (ApplicationCall) -> String?> = emptyMap()
184+
private val stopHook = PreStopHook(rapid)
202185

203186
fun withHttpPort(httpPort: Int) = apply {
204187
this.httpPort = httpPort
@@ -208,24 +191,12 @@ class RapidApplication internal constructor(
208191
this.ktor = ktor
209192
}
210193

211-
fun withKtorModule(module: Application.() -> Unit) = apply {
212-
this.modules.add(module)
213-
}
214-
215-
fun withStatusPagesConfig(statusPagesConfig: StatusPagesConfig.() -> Unit) = apply {
216-
this.statusPagesConfig = statusPagesConfig
194+
fun withKtor(ktor: (PreStopHook, KafkaRapid) -> EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration>) = apply {
195+
this.ktor = ktor(stopHook, rapid)
217196
}
218197

219-
fun withTimersConfig(timersConfig: Timer.Builder.(ApplicationCall, Throwable?) -> Unit) = apply {
220-
this.timersConfig = timersConfig
221-
}
222-
223-
fun withMdcEntries(mdcEntries: Map<String, (ApplicationCall) -> String?>) = apply {
224-
this.mdcEntries = mdcEntries
225-
}
226-
227-
fun withCallIdHeader(headerName: String) = apply {
228-
callIdHeader = headerName
198+
fun withKtorModule(module: Application.() -> Unit) = apply {
199+
this.modules.add(module)
229200
}
230201

231202
fun withIsAliveEndpoint(isAliveEndpoint: String) = apply {
@@ -255,42 +226,30 @@ class RapidApplication internal constructor(
255226
fun build(configure: (EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration>, KafkaRapid) -> Unit = { _, _ -> }, cioConfiguration: CIOApplicationEngine.Configuration.() -> Unit = { } ): RapidsConnection {
256227
val app = ktor ?: defaultKtorApp(cioConfiguration)
257228
configure(app, rapid)
229+
with(meterRegistry) {
230+
val pkg = RapidApplication.javaClass.`package`
231+
val title = pkg?.implementationTitle ?: "unknown"
232+
val version = pkg?.implementationVersion ?: "unknown"
233+
MultiGauge.builder("rapids.and.rivers.info")
234+
.description("Rapids and rivers version info")
235+
.tag("title", title)
236+
.tag("version", version)
237+
.register(this)
238+
.register(listOf(MultiGauge.Row.of(Tags.of(emptyList()), 1)))
239+
}
258240
return RapidApplication(app, rapid, appName, instanceId)
259241
}
260242

261243
private fun defaultKtorApp(cioConfiguration: CIOApplicationEngine.Configuration.() -> Unit): EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration> {
262-
val stopHook = PreStopHook(rapid)
263-
val applicationLogger = LoggerFactory.getLogger(RapidApplication::class.java)
264-
return naisApp(
244+
return ktorApplication(
265245
meterRegistry = meterRegistry,
266-
objectMapper = objectMapper,
267-
applicationLogger = applicationLogger,
268-
callLogger = applicationLogger,
269246
naisEndpoints = naisEndpoints,
270-
callIdHeaderName = callIdHeader,
271247
port = httpPort,
272248
aliveCheck = { isAliveChecks.all { it() } },
273249
readyCheck = { isReadyChecks.all { it() } },
274250
preStopHook = stopHook::handlePreStopRequest,
275251
cioConfiguration = cioConfiguration,
276-
statusPagesConfig = statusPagesConfig,
277-
timersConfig = timersConfig,
278-
mdcEntries = mdcEntries,
279-
applicationModule = {
280-
modules.forEach { it() }
281-
282-
with(meterRegistry) {
283-
val pkg = RapidApplication.javaClass.`package`
284-
val title = pkg?.implementationTitle ?: "unknown"
285-
val version = pkg?.implementationVersion ?: "unknown"
286-
MultiGauge.builder("rapids.and.rivers.info")
287-
.description("Rapids and rivers version info")
288-
.tag("title", title)
289-
.tag("version", version)
290-
.register(this)
291-
.register(listOf(MultiGauge.Row.of(Tags.of(emptyList()), 1)))
292-
}
293-
}
252+
modules = modules
294253
)
295254
}
296255

src/test/kotlin/no/nav/helse/rapids_rivers/RapidApplicationComponentTest.kt

+1-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import java.io.InputStreamReader
2727
import java.net.HttpURLConnection
2828
import java.net.ServerSocket
2929
import java.net.URI
30-
import java.net.URL
3130
import java.time.Duration
3231
import java.time.LocalDateTime
3332
import java.util.*
@@ -220,8 +219,7 @@ internal class RapidApplicationComponentTest {
220219
appName = "app-name",
221220
instanceId = "app-name-0",
222221
rapid = KafkaRapid(factory, "component-test", testTopic, metersRegistry),
223-
meterRegistry = metersRegistry,
224-
objectMapper = objectMapper,
222+
meterRegistry = metersRegistry
225223
).withHttpPort(randomPort)
226224
ktor(randomPort)?.let { builder.withKtor(it) }
227225
val rapidsConnection = builder.build()

0 commit comments

Comments
 (0)