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]