This is an automated email from the ASF dual-hosted git repository.

fanningpj pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-pekko-management.git


The following commit(s) were added to refs/heads/main by this push:
     new fa34b901 new slf4j2 modules (#137)
fa34b901 is described below

commit fa34b90139bff61216a495520e812088f62f4f9f
Author: PJ Fanning <[email protected]>
AuthorDate: Mon Nov 13 10:04:43 2023 +0100

    new slf4j2 modules (#137)
    
    * new slf4j2 modues
    
    * refactor
    
    * Update unit-tests.yml
---
 .github/workflows/unit-tests.yml                   |  27 ++++
 build.sbt                                          |  10 ++
 .../src/main/resources/reference.conf              |   5 +
 .../loglevels/log4j2slf4j2/LogLevelRoutes.scala    | 142 +++++++++++++++++++++
 .../log4j2slf4j2/LogLevelRoutesSpec.scala          | 108 ++++++++++++++++
 project/Dependencies.scala                         |  25 ++++
 6 files changed, 317 insertions(+)

diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index b810367a..1b8e6d40 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -61,6 +61,33 @@ jobs:
       - name: Print logs on failure
         if: ${{ failure() }}
         run: find . -name "*.log" -exec ./scripts/cat-log.sh {} \;
+
+  slf4j2-test:
+    name: Test Logback with slf4J2
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Checkout GitHub merge
+        if: github.event.pull_request
+        run: |-
+          git fetch origin pull/${{ github.event.pull_request.number 
}}/merge:scratch
+          git checkout scratch
+      - name: Setup Java 8
+        uses: actions/setup-java@v3
+        with:
+          distribution: temurin
+          java-version: 8
+
+      - name: Cache Coursier cache
+        uses: coursier/[email protected]
+
+      - name: Create all API docs for artifacts/website and all reference docs
+        run: sbt -Dpekko.test.slf4j2=true management-loglevels-logback/test
+
   docs:
     name: Docs compile
     runs-on: ubuntu-20.04
diff --git a/build.sbt b/build.sbt
index 2be63b8d..bccf0002 100644
--- a/build.sbt
+++ b/build.sbt
@@ -51,6 +51,7 @@ lazy val root = project
     managementClusterBootstrap,
     managementLoglevelsLogback,
     managementLoglevelsLog4j2,
+    managementLoglevelsLog4j2Slf4j2,
     integrationTestAwsApiEc2TagBased,
     integrationTestLocal,
     integrationTestAwsApiEcs,
@@ -126,6 +127,7 @@ lazy val managementLoglevelsLogback = 
pekkoModule("management-loglevels-logback"
   .settings(
     name := "pekko-management-loglevels-logback",
     libraryDependencies := Dependencies.managementLoglevelsLogback,
+    dependencyOverrides := 
Dependencies.managementLoglevelsLogbackSlf4j2Overrides,
     mimaPreviousArtifactsSet)
   .dependsOn(management)
 
@@ -137,6 +139,14 @@ lazy val managementLoglevelsLog4j2 = 
pekkoModule("management-loglevels-log4j2")
     mimaPreviousArtifactsSet)
   .dependsOn(management)
 
+lazy val managementLoglevelsLog4j2Slf4j2 = 
pekkoModule("management-loglevels-log4j2-slf4j2")
+  .enablePlugins(AutomateHeaderPlugin, ReproducibleBuildsPlugin)
+  .disablePlugins(MimaPlugin)
+  .settings(
+    name := "pekko-management-loglevels-log4j2-slf4j2",
+    libraryDependencies := Dependencies.managementLoglevelsLog4j2Slf4j2)
+  .dependsOn(management)
+
 lazy val managementClusterHttp = pekkoModule("management-cluster-http")
   .enablePlugins(AutomateHeaderPlugin, ReproducibleBuildsPlugin)
   .settings(
diff --git 
a/management-loglevels-log4j2-slf4j2/src/main/resources/reference.conf 
b/management-loglevels-log4j2-slf4j2/src/main/resources/reference.conf
new file mode 100644
index 00000000..26e48228
--- /dev/null
+++ b/management-loglevels-log4j2-slf4j2/src/main/resources/reference.conf
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: Apache-2.0
+
+pekko.management.http.routes {
+  loglevels-log4j2 = 
"org.apache.pekko.management.loglevels.log4j2slf4j2.LogLevelRoutes"
+}
diff --git 
a/management-loglevels-log4j2-slf4j2/src/main/scala/org/apache/pekko/management/loglevels/log4j2slf4j2/LogLevelRoutes.scala
 
b/management-loglevels-log4j2-slf4j2/src/main/scala/org/apache/pekko/management/loglevels/log4j2slf4j2/LogLevelRoutes.scala
new file mode 100644
index 00000000..95934cfe
--- /dev/null
+++ 
b/management-loglevels-log4j2-slf4j2/src/main/scala/org/apache/pekko/management/loglevels/log4j2slf4j2/LogLevelRoutes.scala
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * license agreements; and to You under the Apache License, version 2.0:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * This file is part of the Apache Pekko project, which was derived from Akka.
+ */
+
+/*
+ * Copyright (C) 2017-2021 Lightbend Inc. <https://www.lightbend.com>
+ */
+
+package org.apache.pekko.management.loglevels.log4j2slf4j2
+
+import org.apache.logging.log4j.core.LoggerContext
+import org.apache.logging.log4j.{ Level, LogManager }
+import org.apache.pekko
+import pekko.actor.{ ExtendedActorSystem, Extension, ExtensionId }
+import pekko.annotation.InternalApi
+import pekko.event.{ Logging => ClassicLogging }
+import pekko.http.scaladsl.model.StatusCodes
+import pekko.http.scaladsl.server.Directives._
+import pekko.http.scaladsl.server.Route
+import pekko.http.scaladsl.unmarshalling.Unmarshaller
+import pekko.management.scaladsl.{ ManagementRouteProvider, 
ManagementRouteProviderSettings }
+import org.slf4j.LoggerFactory
+
+object LogLevelRoutes extends ExtensionId[LogLevelRoutes] {
+
+  override def createExtension(system: ExtendedActorSystem): LogLevelRoutes =
+    new LogLevelRoutes(system)
+
+}
+
+/**
+ * Provides the path loglevel/logger which can be used to dynamically change 
log levels
+ *
+ * INTERNAL API
+ */
+@InternalApi
+final class LogLevelRoutes private (system: ExtendedActorSystem) extends 
Extension with ManagementRouteProvider {
+
+  private val log = LoggerFactory.getLogger(classOf[LogLevelRoutes])
+
+  import LoggingUnmarshallers._
+
+  override def routes(settings: ManagementRouteProviderSettings): Route = {
+    pathPrefix("loglevel") {
+      extractClientIP { clientIp =>
+        path("log4j2") {
+          pathEndOrSingleSlash {
+            put {
+              parameters(
+                "level".as[Level].withDefault(Level.INFO),
+                "logger" ? LogManager.ROOT_LOGGER_NAME) { (level, logger) =>
+                if (settings.readOnly) {
+                  complete(StatusCodes.Forbidden)
+                } else {
+                  log.info(
+                    s"Log level for [${if 
(logger.equals(LogManager.ROOT_LOGGER_NAME)) "Root"
+                      else logger}] set to [$level] through Pekko Management 
loglevel endpoint from [$clientIp]")
+                  val context = 
LogManager.getContext(false).asInstanceOf[LoggerContext]
+                  val config = context.getConfiguration
+                  val loggerConfig = config.getLoggerConfig(logger)
+                  loggerConfig.setLevel(level)
+                  context.updateLoggers()
+                  complete(StatusCodes.OK)
+                }
+              }
+            } ~
+            get {
+              parameters(
+                "logger" ? LogManager.ROOT_LOGGER_NAME) { logger =>
+                val context = 
LogManager.getContext(false).asInstanceOf[LoggerContext]
+                val config = context.getConfiguration
+                val loggerConfig = config.getLoggerConfig(logger)
+
+                complete(loggerConfig.getLevel.toString)
+              }
+            }
+          }
+        } ~
+        path("pekko") {
+          get {
+            complete(classicLogLevelName(system.eventStream.logLevel))
+          } ~
+          put {
+            if (settings.readOnly)
+              complete(StatusCodes.Forbidden)
+            else {
+              parameter("level".as[ClassicLogging.LogLevel]) { level =>
+                log.info(
+                  "Pekko loglevel set to [{}] through Pekko Management 
loglevel endpoint from [{}]",
+                  Array[Object](classicLogLevelName(level), clientIp): _*)
+                system.eventStream.setLogLevel(level)
+                complete(StatusCodes.OK)
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+}
+
+/**
+ * INTERNAL API
+ */
+@InternalApi
+private[pekko] object LoggingUnmarshallers {
+
+  private val validLevels =
+    Set(Level.ALL, Level.DEBUG, Level.ERROR, Level.INFO, Level.OFF, 
Level.TRACE, Level.WARN).map(_.toString)
+
+  implicit val levelFromStringUnmarshaller: Unmarshaller[String, Level] =
+    Unmarshaller.strict { string =>
+      if (!validLevels(string.toUpperCase))
+        throw new IllegalArgumentException(s"Unknown logger level $string, 
allowed are [${validLevels.mkString(",")}]")
+      Level.valueOf(string)
+    }
+
+  implicit val classicLevelFromStringUnmarshaller: Unmarshaller[String, 
ClassicLogging.LogLevel] =
+    Unmarshaller.strict { string =>
+      ClassicLogging
+        .levelFor(string)
+        .getOrElse(
+          throw new IllegalArgumentException(
+            s"Unknown logger level $string, allowed are 
[${ClassicLogging.AllLogLevels.map(_.toString).mkString(",")}]"))
+    }
+
+  def classicLogLevelName(level: ClassicLogging.LogLevel): String = level 
match {
+    case ClassicLogging.OffLevel     => "OFF"
+    case ClassicLogging.DebugLevel   => "DEBUG"
+    case ClassicLogging.InfoLevel    => "INFO"
+    case ClassicLogging.WarningLevel => "WARNING"
+    case ClassicLogging.ErrorLevel   => "ERROR"
+    case _                           => s"Unknown loglevel: $level"
+  }
+
+}
diff --git 
a/management-loglevels-log4j2-slf4j2/src/test/scala/org/apache/pekko/management/loglevels/log4j2slf4j2/LogLevelRoutesSpec.scala
 
b/management-loglevels-log4j2-slf4j2/src/test/scala/org/apache/pekko/management/loglevels/log4j2slf4j2/LogLevelRoutesSpec.scala
new file mode 100644
index 00000000..82aa1023
--- /dev/null
+++ 
b/management-loglevels-log4j2-slf4j2/src/test/scala/org/apache/pekko/management/loglevels/log4j2slf4j2/LogLevelRoutesSpec.scala
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * license agreements; and to You under the Apache License, version 2.0:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * This file is part of the Apache Pekko project, which was derived from Akka.
+ */
+
+/*
+ * Copyright (C) 2017-2021 Lightbend Inc. <https://www.lightbend.com>
+ */
+
+package org.apache.pekko.management.loglevels.log4j2slf4j2
+
+import org.apache.pekko
+import pekko.actor.ExtendedActorSystem
+import pekko.event.{ Logging => ClassicLogging }
+import pekko.http.javadsl.server.MalformedQueryParamRejection
+import pekko.http.scaladsl.model.{ StatusCodes, Uri }
+import pekko.http.scaladsl.testkit.ScalatestRouteTest
+import pekko.management.scaladsl.ManagementRouteProviderSettings
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+import org.slf4j.LoggerFactory
+
+class LogLevelRoutesSpec extends AnyWordSpec with Matchers with 
ScalatestRouteTest {
+
+  override def testConfigSource: String =
+    """
+      pekko.loglevel = INFO
+      """
+
+  val routes = LogLevelRoutes
+    .createExtension(system.asInstanceOf[ExtendedActorSystem])
+    .routes(ManagementRouteProviderSettings(Uri("https://example.com";), 
readOnly = false))
+
+  "The logback (with slf4j2) log level routes" must {
+
+    "show log level of a Logger" in {
+      Get("/loglevel/log4j2?logger=LogLevelRoutesSpec") ~> routes ~> check {
+        responseAs[String]
+      }
+    }
+
+    "change log level of a Logger to ERROR" in {
+      Put("/loglevel/log4j2?logger=LogLevelRoutesSpec&level=ERROR") ~> routes 
~> check {
+        response.status should ===(StatusCodes.OK)
+        println(response)
+        LoggerFactory.getLogger("LogLevelRoutesSpec").isErrorEnabled should 
===(true)
+      }
+    }
+
+    "change log level of a Logger to DEBUG" in {
+      Put("/loglevel/log4j2?logger=LogLevelRoutesSpec&level=DEBUG") ~> routes 
~> check {
+        response.status should ===(StatusCodes.OK)
+        println(response)
+        LoggerFactory.getLogger("LogLevelRoutesSpec").isDebugEnabled() should 
===(true)
+      }
+    }
+
+    "change log level of a Logger to INFO" in {
+      Put("/loglevel/log4j2?logger=LogLevelRoutesSpec&level=INFO") ~> routes 
~> check {
+        response.status should ===(StatusCodes.OK)
+        println(response)
+        LoggerFactory.getLogger("LogLevelRoutesSpec").isInfoEnabled should 
===(true)
+      }
+    }
+
+    "change log level of a Logger to WARN" in {
+      Put("/loglevel/log4j2?logger=LogLevelRoutesSpec&level=WARN") ~> routes 
~> check {
+        response.status should ===(StatusCodes.OK)
+        println(response)
+        LoggerFactory.getLogger("LogLevelRoutesSpec").isWarnEnabled should 
===(true)
+      }
+    }
+
+    "fail for unknown log level" in {
+      Put("/loglevel/log4j2?logger=LogLevelRoutesSpec&level=MONKEY") ~> routes 
~> check {
+        rejection shouldBe an[MalformedQueryParamRejection]
+      }
+    }
+
+    "not change loglevel if read only" in {
+      val readOnlyRoutes = LogLevelRoutes
+        .createExtension(system.asInstanceOf[ExtendedActorSystem])
+        .routes(ManagementRouteProviderSettings(Uri("https://example.com";), 
readOnly = true))
+      Put("/loglevel/log4j2?logger=LogLevelRoutesSpec&level=DEBUG") ~> 
readOnlyRoutes ~> check {
+        response.status should ===(StatusCodes.Forbidden)
+      }
+    }
+
+    "allow inspecting classic Pekko loglevel" in {
+      Get("/loglevel/pekko") ~> routes ~> check {
+        response.status should ===(StatusCodes.OK)
+        responseAs[String] should ===("INFO")
+      }
+    }
+
+    "allow changing classic Pekko loglevel" in {
+      Put("/loglevel/pekko?level=DEBUG") ~> routes ~> check {
+        response.status should ===(StatusCodes.OK)
+        system.eventStream.logLevel should ===(ClassicLogging.DebugLevel)
+      }
+    }
+  }
+
+}
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 89f9f45a..e01291e3 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -26,7 +26,10 @@ object Dependencies {
   val jacksonVersion = "2.14.3"
 
   val log4j2Version = "2.17.2"
+  val log4j2Slf4j2Version = "2.21.1"
   val logbackVersion = "1.2.11"
+  val logbackSlf4j2Version = "1.3.11"
+  val slf4j2Version = "2.0.9"
 
   // often called-in transitively with insecure versions of databind / core
   private val jacksonDatabind = Seq(
@@ -107,6 +110,14 @@ object Dependencies {
     "org.apache.pekko" %% "pekko-testkit" % pekkoVersion % Test,
     "org.apache.pekko" %% "pekko-http-testkit" % pekkoHttpVersion % Test)
 
+  val managementLoglevelsLogbackSlf4j2Overrides = if 
(java.lang.Boolean.getBoolean("pekko.test.slf4j2")) {
+    Seq(
+      "org.slf4j" % "slf4j-api" % "2.0.9",
+      "ch.qos.logback" % "logback-classic" % "1.3.11" % Test)
+  } else {
+    Seq.empty
+  }
+
   val managementLoglevelsLog4j2 = Seq(
     "org.apache.pekko" %% "pekko-actor" % pekkoVersion,
     "org.apache.pekko" %% "pekko-slf4j" % pekkoVersion,
@@ -120,6 +131,20 @@ object Dependencies {
     "org.apache.pekko" %% "pekko-testkit" % pekkoVersion % Test,
     "org.apache.pekko" %% "pekko-http-testkit" % pekkoHttpVersion % Test)
 
+  val managementLoglevelsLog4j2Slf4j2 = Seq(
+    "org.apache.pekko" %% "pekko-actor" % pekkoVersion,
+    "org.apache.pekko" %% "pekko-slf4j" % pekkoVersion,
+    "org.apache.pekko" %% "pekko-stream" % pekkoVersion,
+    "org.slf4j" % "slf4j-api" % slf4j2Version,
+    "org.apache.logging.log4j" % "log4j-core" % log4j2Slf4j2Version,
+    "org.apache.logging.log4j" % "log4j-api" % log4j2Slf4j2Version,
+    "org.apache.logging.log4j" % "log4j-slf4j2-impl" % log4j2Slf4j2Version,
+    "org.apache.pekko" %% "pekko-http" % pekkoHttpVersion,
+    "org.apache.pekko" %% "pekko-http-spray-json" % pekkoHttpVersion,
+    "org.scalatest" %% "scalatest" % scalaTestVersion % Test,
+    "org.apache.pekko" %% "pekko-testkit" % pekkoVersion % Test,
+    "org.apache.pekko" %% "pekko-http-testkit" % pekkoHttpVersion % Test)
+
   val managementClusterHttp = Seq(
     "org.apache.pekko" %% "pekko-cluster" % pekkoVersion,
     "org.apache.pekko" %% "pekko-cluster-sharding" % pekkoVersion,


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to