diff --git a/build.sbt b/build.sbt index acba5a4619..b343a6bd12 100644 --- a/build.sbt +++ b/build.sbt @@ -232,7 +232,7 @@ lazy val webapi: Project = Project(id = "webapi", base = file("webapi")) dockerUpdateLatest := true, dockerBaseImage := "eclipse-temurin:17-jre-focal", Docker / maintainer := "support@dasch.swiss", - Docker / dockerExposedPorts ++= Seq(3333), + Docker / dockerExposedPorts ++= Seq(3333, 3339), Docker / defaultLinuxInstallLocation := "/opt/docker", // use filterNot to return all items that do NOT meet the criteria dockerCommands := dockerCommands.value.filterNot { diff --git a/docker-compose.yml b/docker-compose.yml index c318b6ccb8..5e8a531651 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,6 +53,7 @@ services: image: daschswiss/knora-api:latest ports: - "3333:3333" + - "3339:3339" volumes: - /tmp:/tmp networks: diff --git a/docs/03-endpoints/api-util/health.md b/docs/03-endpoints/instrumentation/health.md similarity index 100% rename from docs/03-endpoints/api-util/health.md rename to docs/03-endpoints/instrumentation/health.md diff --git a/docs/03-endpoints/instrumentation/introduction.md b/docs/03-endpoints/instrumentation/introduction.md new file mode 100644 index 0000000000..67fa5958a5 --- /dev/null +++ b/docs/03-endpoints/instrumentation/introduction.md @@ -0,0 +1,14 @@ + + +# Instrumentation + +The instrumentation endpoints are running on a separate port (default `3339`) +defined in `application.conf` under the key: `app.instrumentaion-server-config.port` +and can also be set through the environment variable: `KNORA_INSTRUMENTATION_SERVER_PORT`. + +The exposed endpoints are: + - `/metrics` - a metrics endpoint, backed by the ZIO metrics backend exposing metrics in the prometheus format + - `/health` - provides information about the health state, see [Health Endpoint](./health.md) diff --git a/docs/03-endpoints/instrumentation/metrics.md b/docs/03-endpoints/instrumentation/metrics.md new file mode 100644 index 0000000000..d91d048875 --- /dev/null +++ b/docs/03-endpoints/instrumentation/metrics.md @@ -0,0 +1,39 @@ + + +# Metrics Endpoint + +The metrics endpoint exposes metrics gathered through the ZIO metrics frontend in the Prometheus +format. Additionally, ZIO runtime and JVM metrics are also exposed. + +## Configuration + +The refresh interval is configured in `application.conf` under the key: `app.instrumentaion-server-config.interval` +which es per default set to `5 seconds`. + + +## Example request + +`GET /metrics` + + +## Example response + +```text +# TYPE jvm_memory_pool_allocated_bytes_total counter +# HELP jvm_memory_pool_allocated_bytes_total Some help +jvm_memory_pool_allocated_bytes_total{pool="G1 Survivor Space"} 4828024.0 1671021037947 +# TYPE jvm_memory_pool_allocated_bytes_total counter +# HELP jvm_memory_pool_allocated_bytes_total Some help +jvm_memory_pool_allocated_bytes_total{pool="G1 Eden Space"} 3.3554432E7 1671021037947 +# TYPE zio_fiber_successes counter +# HELP zio_fiber_successes Some help +zio_fiber_successes 17.0 1671021037947 +# TYPE zio_fiber_lifetimes histogram +# HELP zio_fiber_lifetimes Some help +zio_fiber_lifetimes_bucket{le="1.0"} 17.0 1671021037947 +zio_fiber_lifetimes_bucket{le="2.0"} 17.0 1671021037947 +... +``` diff --git a/docs/05-internals/development/monitoring.md b/docs/05-internals/development/monitoring.md deleted file mode 100644 index 247c20ae2b..0000000000 --- a/docs/05-internals/development/monitoring.md +++ /dev/null @@ -1,29 +0,0 @@ - - -# Monitoring Knora - -Monitoring is implemented by using the Prometheus / Grafana stack. - -## Usage: - -1) Start webapi with the necessary -p option (e.g., from inside sbt: - run -p or reStart -p -2) Start the monitoring stack by executing the following line inside - the monitoring - folder: - - - - $ WEBAPIHOST= ADMIN_USER=admin ADMIN_PASSWORD=admin docker-compose up -d - -3) Head over to localhost:3000, log in using the admin username and - password, and open the "Webapi Akka Actor System" dashboard. -4) To shut down the monitoring stack, run the following line inside the - monitoring folder: - - - - $ docker-compose down diff --git a/mkdocs.yml b/mkdocs.yml index 417d122fe9..1ebf77e5bf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,8 +51,13 @@ nav: - Permissions Endpoint: 03-endpoints/api-admin/permissions.md - Stores Endpoint: 03-endpoints/api-admin/stores.md - Util API: - - Health: 03-endpoints/api-util/health.md + - Version: 03-endpoints/api-util/version.md + - Instrumentation API: + - Introduction: 03-endpoints/instrumentation/introduction.md + - Health Endpoint: 03-endpoints/instrumentation/health.md + - Metrics Endpoint: 03-endpoints/instrumentation/metrics.md + - Publishing and Deployment: - Publishing: 04-publishing-deployment/publishing.md - Configuration: 04-publishing-deployment/configuration.md @@ -98,7 +103,6 @@ nav: - Setup Visual Studio Code for development of DSP-API: 05-internals/development/vscode-config.md - Testing: 05-internals/development/testing.md - Docker Cheat Sheet: 05-internals/development/docker-cheat-sheet.md - - Monitoring DSP-API: 05-internals/development/monitoring.md - Starting the DSP-Stack inside Docker Container: 05-internals/development/docker-compose.md - Updating Repositories: 05-internals/development/updating-repositories.md - Generating Client Test Data: 05-internals/development/generating-client-test-data.md diff --git a/webapi/src/it/scala/org/knora/webapi/CoreSpec.scala b/webapi/src/it/scala/org/knora/webapi/CoreSpec.scala index a24652835a..d83f943cdf 100644 --- a/webapi/src/it/scala/org/knora/webapi/CoreSpec.scala +++ b/webapi/src/it/scala/org/knora/webapi/CoreSpec.scala @@ -9,21 +9,21 @@ import akka.actor import akka.testkit.ImplicitSender import akka.testkit.TestKitBase import com.typesafe.scalalogging.Logger -import org.scalatest.BeforeAndAfterAll -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AnyWordSpec -import zio._ -import zio.logging.backend.SLF4J - -import scala.concurrent.ExecutionContext - import org.knora.webapi.config.AppConfig +import org.knora.webapi.core.AppRouter import org.knora.webapi.core.AppServer import org.knora.webapi.core.TestStartupUtils import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject import org.knora.webapi.messages.util.ResponderData import org.knora.webapi.store.cache.settings.CacheServiceSettings import org.knora.webapi.util.LogAspect +import org.scalatest.BeforeAndAfterAll +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import zio._ +import zio.logging.backend.SLF4J + +import scala.concurrent.ExecutionContext abstract class CoreSpec extends AnyWordSpec @@ -71,7 +71,7 @@ abstract class CoreSpec /** * Create router and config by unsafe running them. */ - private val (router, config) = + private val (router: AppRouter, config: AppConfig) = Unsafe.unsafe { implicit u => runtime.unsafe .run( @@ -97,8 +97,9 @@ abstract class CoreSpec runtime.unsafe .run( (for { + _ <- AppServer.testWithoutSipi _ <- prepareRepository(rdfDataObjects) @@ LogAspect.logSpan("prepare-repo") - } yield ()).provideSomeLayer(AppServer.testWithoutSipi) + } yield ()) ) .getOrThrow() diff --git a/webapi/src/it/scala/org/knora/webapi/E2ESpec.scala b/webapi/src/it/scala/org/knora/webapi/E2ESpec.scala index b6f6691004..2d7e38398e 100644 --- a/webapi/src/it/scala/org/knora/webapi/E2ESpec.scala +++ b/webapi/src/it/scala/org/knora/webapi/E2ESpec.scala @@ -28,9 +28,9 @@ import scala.concurrent.Await import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.concurrent.duration.FiniteDuration - import dsp.errors.FileWriteException import org.knora.webapi.config.AppConfig +import org.knora.webapi.core.AppRouter import org.knora.webapi.core.AppServer import org.knora.webapi.core.TestStartupUtils import org.knora.webapi.messages.store.sipimessages.SipiUploadResponse @@ -95,7 +95,7 @@ abstract class E2ESpec /** * Create router and config by unsafe running them. */ - val (router, config) = + val (router: AppRouter, config: AppConfig) = Unsafe.unsafe { implicit u => runtime.unsafe .run( @@ -120,9 +120,10 @@ abstract class E2ESpec Unsafe.unsafe { implicit u => runtime.unsafe .run( - (for { + for { + _ <- AppServer.testWithoutSipi _ <- prepareRepository(rdfDataObjects) @@ LogAspect.logSpan("prepare-repo") - } yield ()).provideSomeLayer(AppServer.testWithoutSipi) + } yield () ) .getOrThrow() } diff --git a/webapi/src/it/scala/org/knora/webapi/ITKnoraLiveSpec.scala b/webapi/src/it/scala/org/knora/webapi/ITKnoraLiveSpec.scala index 58859618cb..9b0055f466 100644 --- a/webapi/src/it/scala/org/knora/webapi/ITKnoraLiveSpec.scala +++ b/webapi/src/it/scala/org/knora/webapi/ITKnoraLiveSpec.scala @@ -22,8 +22,8 @@ import scala.concurrent.Await import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.concurrent.duration.FiniteDuration - import org.knora.webapi.config.AppConfig +import org.knora.webapi.core.AppRouter import org.knora.webapi.core.AppServer import org.knora.webapi.core.TestStartupUtils import org.knora.webapi.messages.store.sipimessages._ @@ -87,7 +87,7 @@ abstract class ITKnoraLiveSpec /** * Create router and config by unsafe running them. */ - val (router, config) = + val (router: AppRouter, config: AppConfig) = Unsafe.unsafe { implicit u => runtime.unsafe .run( @@ -112,9 +112,10 @@ abstract class ITKnoraLiveSpec Unsafe.unsafe { implicit u => runtime.unsafe .run( - (for { + for { + _ <- AppServer.testWithSipi _ <- prepareRepository(rdfDataObjects) @@ LogAspect.logSpan("prepare-repo") - } yield ()).provideSomeLayer(AppServer.testWithSipi) + } yield () ) .getOrThrow() } diff --git a/webapi/src/it/scala/org/knora/webapi/R2RSpec.scala b/webapi/src/it/scala/org/knora/webapi/R2RSpec.scala index 018dcdba11..b24687e37f 100644 --- a/webapi/src/it/scala/org/knora/webapi/R2RSpec.scala +++ b/webapi/src/it/scala/org/knora/webapi/R2RSpec.scala @@ -20,8 +20,8 @@ import java.nio.file.Paths import java.util.concurrent.TimeUnit import scala.concurrent.Await import scala.concurrent.Future - import org.knora.webapi.config.AppConfig +import org.knora.webapi.core.AppRouter import org.knora.webapi.core.AppServer import org.knora.webapi.core.TestStartupUtils import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject @@ -78,7 +78,7 @@ abstract class R2RSpec /** * Create router and config by unsafe running them. */ - private val (router, config) = + private val (router: AppRouter, config: AppConfig) = Unsafe.unsafe { implicit u => runtime.unsafe .run( @@ -101,9 +101,10 @@ abstract class R2RSpec Unsafe.unsafe { implicit u => runtime.unsafe .run( - (for { + for { + _ <- AppServer.testWithoutSipi _ <- prepareRepository(rdfDataObjects) @@ LogAspect.logSpan("prepare-repo") - } yield ()).provideSomeLayer(AppServer.testWithoutSipi) + } yield () ) .getOrThrow() } diff --git a/webapi/src/it/scala/org/knora/webapi/core/LayersTest.scala b/webapi/src/it/scala/org/knora/webapi/core/LayersTest.scala index 3e98425ef1..1cef56af8d 100644 --- a/webapi/src/it/scala/org/knora/webapi/core/LayersTest.scala +++ b/webapi/src/it/scala/org/knora/webapi/core/LayersTest.scala @@ -1,7 +1,6 @@ package org.knora.webapi.core import zio.ZLayer - import org.knora.webapi.auth.JWTService import org.knora.webapi.config.AppConfigForTestContainers import org.knora.webapi.routing.ApiRoutes diff --git a/webapi/src/main/resources/application.conf b/webapi/src/main/resources/application.conf index 74c24a1305..912c919f19 100644 --- a/webapi/src/main/resources/application.conf +++ b/webapi/src/main/resources/application.conf @@ -501,18 +501,9 @@ app { collect-client-test-data = false } - monitoring { - prometheus-endpoint: false - prometheus-endpoint: ${?KNORA_WEBAPI_PROMETHEUS_ENDPOINT} + instrumentation-server-config { + port = 3339 + port = ${?KNORA_INSTRUMENTATION_SERVER_PORT} + interval = 5 seconds } } - -kamon.prometheus.embedded-server { - - # Hostname and port used by the embedded web server to publish the - # prometheus scraping enpoint. - hostname = 0.0.0.0 - hostname = ${?KNORA_WEBAPI_PROMETHEUS_HOST} - port = 9095 - port = ${?KNORA_WEBAPI_PROMETHEUS_PORT} -} diff --git a/webapi/src/main/scala/org/knora/webapi/Main.scala b/webapi/src/main/scala/org/knora/webapi/Main.scala index a69d02220c..664b649929 100644 --- a/webapi/src/main/scala/org/knora/webapi/Main.scala +++ b/webapi/src/main/scala/org/knora/webapi/Main.scala @@ -30,6 +30,10 @@ object Main extends ZIOApp { ] = ZLayer.empty ++ Runtime.removeDefaultLoggers ++ SLF4J.slf4j ++ LayersLive.dspLayersLive /* Here we start our Application */ - override def run = AppServer.live.launch - + override def run = for { + f <- ZIO.never.forkDaemon + _ <- InstrumentationHttpServer.make + _ <- AppServer.live + _ <- f.join + } yield () } diff --git a/webapi/src/main/scala/org/knora/webapi/config/AppConfig.scala b/webapi/src/main/scala/org/knora/webapi/config/AppConfig.scala index 10d9bfc56d..b2e5b8902c 100644 --- a/webapi/src/main/scala/org/knora/webapi/config/AppConfig.scala +++ b/webapi/src/main/scala/org/knora/webapi/config/AppConfig.scala @@ -12,6 +12,7 @@ import zio.config._ import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths +import java.time.Duration import scala.concurrent.duration import scala.util.Failure import scala.util.Success @@ -55,7 +56,8 @@ final case class AppConfig( triplestore: Triplestore, shacl: Shacl, cacheService: CacheService, - clientTestDataService: ClientTestDataService + clientTestDataService: ClientTestDataService, + instrumentationServerConfig: InstrumentationServerConfig ) { val jwtLongevityAsDuration = scala.concurrent.duration.Duration(jwtLongevity) val defaultTimeoutAsDuration = @@ -237,6 +239,11 @@ final case class ClientTestDataService( collectClientTestData: Boolean ) +final case class InstrumentationServerConfig( + port: Int, + interval: Duration +) + /** * Loads the applicaton configuration using ZIO-Config. ZIO-Config is capable of loading * the Typesafe-Config format. diff --git a/webapi/src/main/scala/org/knora/webapi/core/AppServer.scala b/webapi/src/main/scala/org/knora/webapi/core/AppServer.scala index f9030d936e..e37613c74e 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/AppServer.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/AppServer.scala @@ -179,37 +179,31 @@ object AppServer { } yield appServer /** - * The AppServer live layer + * The live AppServer */ - val live: ZLayer[AppServerEnvironment, Nothing, Unit] = - ZLayer { - for { - appServer <- AppServer.init() - _ <- appServer.start(requiresAdditionalRepositoryChecks = true, requiresIIIFService = true) - } yield () - } + val live: ZIO[AppServerEnvironment, Nothing, Unit] = + for { + appServer <- AppServer.init() + _ <- appServer.start(requiresAdditionalRepositoryChecks = true, requiresIIIFService = true) + } yield () /** - * The AppServer test layer with Sipi, which initiates the startup checks. Before this layer does what it does, + * The test AppServer with Sipi, which initiates the startup checks. Before this effect does what it does, * the complete server should have already been started. */ - val testWithSipi: ZLayer[AppServerEnvironment, Nothing, Unit] = - ZLayer { - for { - appServer <- AppServer.init() - _ <- appServer.start(requiresAdditionalRepositoryChecks = false, requiresIIIFService = true) - } yield () - } + val testWithSipi: ZIO[AppServerEnvironment, Nothing, Unit] = + for { + appServer <- AppServer.init() + _ <- appServer.start(requiresAdditionalRepositoryChecks = false, requiresIIIFService = true) + } yield () /** - * The AppServer test layer without Sipi, which initiates the startup checks. Before this layer does what it does, + * The test AppServer without Sipi, which initiates the startup checks. Before this effect does what it does, * the complete server should have already been started. */ - val testWithoutSipi: ZLayer[AppServerEnvironment, Nothing, Unit] = - ZLayer { - for { - appServer <- AppServer.init() - _ <- appServer.start(requiresAdditionalRepositoryChecks = false, requiresIIIFService = false) - } yield () - } + val testWithoutSipi: ZIO[AppServerEnvironment, Nothing, Unit] = + for { + appServer <- AppServer.init() + _ <- appServer.start(requiresAdditionalRepositoryChecks = false, requiresIIIFService = false) + } yield () } diff --git a/webapi/src/main/scala/org/knora/webapi/core/HttpServerWithZIOHttp.scala b/webapi/src/main/scala/org/knora/webapi/core/HttpServerWithZIOHttp.scala index 3cf16f33e5..022b11d6ef 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/HttpServerWithZIOHttp.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/HttpServerWithZIOHttp.scala @@ -10,11 +10,11 @@ import zio.ZLayer import zio._ import org.knora.webapi.config.AppConfig -import org.knora.webapi.routing.HealthRouteWithZIOHttp +import org.knora.webapi.routing.IndexApp object HttpServerWithZIOHttp { - val routes = HealthRouteWithZIOHttp() + val routes = IndexApp() val layer: ZLayer[AppConfig & State, Nothing, Unit] = ZLayer { diff --git a/webapi/src/main/scala/org/knora/webapi/core/InstrumentationHttpServer.scala b/webapi/src/main/scala/org/knora/webapi/core/InstrumentationHttpServer.scala new file mode 100644 index 0000000000..b8ca62820e --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/core/InstrumentationHttpServer.scala @@ -0,0 +1,59 @@ +package org.knora.webapi.core + +import zhttp.service.Server +import zio.Runtime +import zio.ZIO +import zio.ZLayer +import zio.metrics.connectors.MetricsConfig +import zio.metrics.connectors.prometheus +import zio.metrics.jvm.DefaultJvmMetrics + +import org.knora.webapi.config.AppConfig +import org.knora.webapi.instrumentation.health.HealthApp +import org.knora.webapi.instrumentation.index.IndexApp +import org.knora.webapi.instrumentation.prometheus.PrometheusApp + +object InstrumentationHttpServer { + + private val routes = + for { + index <- ZIO.service[IndexApp].map(_.route) + health <- ZIO.service[HealthApp].map(_.route) + prometheus <- ZIO.service[PrometheusApp].map(_.route) + } yield index ++ health ++ prometheus + + private val run = + for { + config <- ZIO.service[AppConfig] + r <- routes + _ <- Server.start(config.instrumentationServerConfig.port, r).forkDaemon + _ <- ZIO.logInfo(s"Starting instrumentation http server on port: ${config.instrumentationServerConfig.port}") + } yield () + + val make: ZIO[AppConfig with State, Throwable, Unit] = + ZIO + .service[AppConfig] + .flatMap(config => + run + .provideSome[AppConfig with State]( + // HttpApp implementation layers + IndexApp.layer, + HealthApp.layer, + PrometheusApp.layer, + + // Metrics config + ZLayer.succeed(MetricsConfig(config.instrumentationServerConfig.interval)), + + // The prometheus reporting layer + prometheus.publisherLayer, + prometheus.prometheusLayer, + + // Enable the ZIO internal metrics and the default JVM metricsConfig + // Do NOT forget the .unit for the JVM metrics layer + Runtime.enableRuntimeMetrics, + Runtime.enableFiberRoots, + DefaultJvmMetrics.live.unit + ) + ) + +} diff --git a/webapi/src/main/scala/org/knora/webapi/core/domain/AppState.scala b/webapi/src/main/scala/org/knora/webapi/core/domain/AppState.scala index 0916fb7bdd..67db2a0226 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/domain/AppState.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/domain/AppState.scala @@ -58,16 +58,6 @@ case class SetAllowReloadOverHTTPState(value: Boolean) extends ApplicationReques */ case class GetAllowReloadOverHTTPState() extends ApplicationRequest -/** - * Setter message for storing the rometheusReporter flag. - */ -case class SetPrometheusReporterState(value: Boolean) extends ApplicationRequest - -/** - * Getter message for retrieving the rometheusReporter flag value. - */ -case class GetPrometheusReporterState() extends ApplicationRequest - /** * Setter message for storing the ZipkinReporter flag. */ diff --git a/webapi/src/main/scala/org/knora/webapi/routing/HealthRouteWithZIOHttp.scala b/webapi/src/main/scala/org/knora/webapi/instrumentation/health/HealthApp.scala similarity index 93% rename from webapi/src/main/scala/org/knora/webapi/routing/HealthRouteWithZIOHttp.scala rename to webapi/src/main/scala/org/knora/webapi/instrumentation/health/HealthApp.scala index aa7ac232e4..6bccee9363 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/HealthRouteWithZIOHttp.scala +++ b/webapi/src/main/scala/org/knora/webapi/instrumentation/health/HealthApp.scala @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.knora.webapi.routing +package org.knora.webapi.instrumentation.health import spray.json.JsObject import spray.json.JsString @@ -14,12 +14,12 @@ import org.knora.webapi.core.State import org.knora.webapi.core.domain.AppState /** - * Provides the '/healthZ' endpoint serving the health status. + * Provides the '/health' endpoint serving the health status. */ -object HealthRouteWithZIOHttp { +final case class HealthApp() { - def apply(): HttpApp[State, Nothing] = - Http.collectZIO[Request] { case Method.GET -> !! / "healthZ" => + val route: HttpApp[State, Nothing] = + Http.collectZIO[Request] { case Method.GET -> !! / "health" => State.getAppState.map(toHealthCheckResult).flatMap(createResponse) } @@ -113,3 +113,7 @@ object HealthRouteWithZIOHttp { message = "Application is healthy" ) } +object HealthApp { + val layer = + ZLayer.succeed(HealthApp()) +} diff --git a/webapi/src/main/scala/org/knora/webapi/instrumentation/index/IndexApp.scala b/webapi/src/main/scala/org/knora/webapi/instrumentation/index/IndexApp.scala new file mode 100644 index 0000000000..0b66f8ff6f --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/instrumentation/index/IndexApp.scala @@ -0,0 +1,27 @@ +package org.knora.webapi.instrumentation.index + +import zhttp.html.Html +import zhttp.http._ +import zio.ULayer +import zio.ZLayer + +/** + * Provides the '/' endpoint serving a small index page. + */ +final case class IndexApp() { + + val route: HttpApp[Any, Nothing] = + Http.collect[Request] { case Method.GET -> !! => Response.html(Html.fromString(indexPage)) } + + private val indexPage = + """ + |Simple Server + | + |

Prometheus Metrics

+ |""".stripMargin +} +object IndexApp { + val layer: ULayer[IndexApp] = + ZLayer.succeed(IndexApp()) +} diff --git a/webapi/src/main/scala/org/knora/webapi/instrumentation/prometheus/PrometheusApp.scala b/webapi/src/main/scala/org/knora/webapi/instrumentation/prometheus/PrometheusApp.scala new file mode 100644 index 0000000000..ecfcaba405 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/instrumentation/prometheus/PrometheusApp.scala @@ -0,0 +1,22 @@ +package org.knora.webapi.instrumentation.prometheus + +import zhttp.http._ +import zio.ZIO +import zio.ZLayer +import zio.metrics.connectors.prometheus.PrometheusPublisher + +/** + * Provides the '/metrics' endpoint serving the metrics in prometheus format. + */ +final case class PrometheusApp() { + + val route: HttpApp[PrometheusPublisher, Nothing] = + Http + .collectZIO[Request] { case Method.GET -> !! / "metrics" => + ZIO.serviceWithZIO[PrometheusPublisher](_.get.map(Response.text)) + } +} +object PrometheusApp { + val layer = + ZLayer.succeed(PrometheusApp()) +} diff --git a/webapi/src/main/scala/org/knora/webapi/routing/IndexApp.scala b/webapi/src/main/scala/org/knora/webapi/routing/IndexApp.scala new file mode 100644 index 0000000000..06b54cbc52 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/routing/IndexApp.scala @@ -0,0 +1,22 @@ +package org.knora.webapi.routing + +import zhttp.html._ +import zhttp.http._ + +/** + * Provides the '/' endpoint serving a small index page. + */ +object IndexApp { + + def apply(): HttpApp[Any, Nothing] = + Http.collect[Request] { case Method.GET -> !! => Response.html(Html.fromString(indexPage)) } + + private val indexPage = + """ + |DSP-API public routes + | + |

nothing yet

+ |""".stripMargin + +} diff --git a/webapi/src/test/scala/org/knora/webapi/config/AppConfigZSpec.scala b/webapi/src/test/scala/org/knora/webapi/config/AppConfigZSpec.scala index 80eb6edc38..50c9b923a1 100644 --- a/webapi/src/test/scala/org/knora/webapi/config/AppConfigZSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/config/AppConfigZSpec.scala @@ -24,7 +24,10 @@ object AppConfigZSpec extends ZIOSpecDefault { assertTrue(appConfig.printExtendedConfig == false) && assertTrue(appConfig.jwtLongevityAsDuration == FiniteDuration(30L, TimeUnit.DAYS)) && assertTrue(appConfig.sipi.timeoutInSeconds == FiniteDuration(120L, TimeUnit.SECONDS)) && - assertTrue(appConfig.bcryptPasswordStrength == User.PasswordStrength(12)) + assertTrue(appConfig.bcryptPasswordStrength == User.PasswordStrength(12)) && + assertTrue( + appConfig.instrumentationServerConfig.interval == java.time.Duration.ofSeconds(5) + ) } }.provideLayer(AppConfig.live) )