mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 08:52:58 +03:00
Finish Logging Service Integration (#1346)
This commit is contained in:
parent
c1369ad044
commit
de817af655
24
build.sbt
24
build.sbt
@ -621,26 +621,6 @@ lazy val `logging-service` = project
|
||||
)
|
||||
.dependsOn(`akka-native`)
|
||||
|
||||
ThisBuild / testOptions += Tests.Setup(_ =>
|
||||
// Note [Logging Service in Tests]
|
||||
sys.props("org.enso.loggingservice.test-log-level") = "2"
|
||||
)
|
||||
|
||||
/* Note [Logging Service in Tests]
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* As migrating the runner to our new logging service has forced us to migrate
|
||||
* other components that are related to it too, some tests that used to be
|
||||
* configured by logback are not properly configured anymore and log a lot of
|
||||
* debug information. This is a temporary fix to make sure that there are not
|
||||
* too much logs in the CI - it sets the log level for all tests to be at most
|
||||
* info by default. It can be overridden by particular tests if they set up a
|
||||
* logging service.
|
||||
*
|
||||
* This is a temporary solution and it will be obsoleted once all of our
|
||||
* components do a complete migration to the new logging service, which is
|
||||
* planned in tasks #1144 and #1151.
|
||||
*/
|
||||
|
||||
lazy val cli = project
|
||||
.in(file("lib/scala/cli"))
|
||||
.configs(Test)
|
||||
@ -1170,7 +1150,6 @@ lazy val launcher = project
|
||||
"nl.gn0s1s" %% "bump" % bumpVersion,
|
||||
"org.apache.commons" % "commons-compress" % commonsCompressVersion,
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
|
||||
akkaHttp,
|
||||
akkaSLF4J
|
||||
)
|
||||
)
|
||||
@ -1225,8 +1204,7 @@ lazy val `runtime-version-manager` = project
|
||||
"nl.gn0s1s" %% "bump" % bumpVersion,
|
||||
"org.apache.commons" % "commons-compress" % commonsCompressVersion,
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
|
||||
akkaHttp,
|
||||
akkaSLF4J
|
||||
akkaHttp
|
||||
)
|
||||
)
|
||||
.dependsOn(pkg)
|
||||
|
@ -3,4 +3,4 @@ files-to-copy:
|
||||
- NOTICE
|
||||
- README.md
|
||||
directories-to-copy:
|
||||
- components-licences
|
||||
- THIRD-PARTY
|
||||
|
@ -79,8 +79,7 @@ extraction-location
|
||||
│ └── lts-2.0.8.yaml
|
||||
├── README.md # Information on layout and usage of the Enso distribution.
|
||||
├── .enso.portable # A file that allows the universal launcher to detect that if it is run from this directory, it should run in portable distribution mode.
|
||||
├── NOTICE # A copyright notice regarding components that are included in the distribution of the universal launcher.
|
||||
└── components-licences # Contains licences of distributed components, as described in the NOTICE.
|
||||
└── THIRD-PARTY # Contains licences of distributed components, including the NOTICE file.
|
||||
```
|
||||
|
||||
### Installed Enso Distribution Layout
|
||||
|
@ -251,3 +251,23 @@ In a rare situation where the service would not be initialized at all, a
|
||||
shutdown hook is added that will print the pending log messages before exiting.
|
||||
Some of the messages may be dropped, however, if more messages are buffered than
|
||||
the buffer can hold.
|
||||
|
||||
### Logging in Tests
|
||||
|
||||
The Logging Service provides several utilities for managing logs inside of
|
||||
tests.
|
||||
|
||||
The primary method for setting log-level for all tests in a project is by
|
||||
creating a `logging.properties` file in `resources` of the `test` target.
|
||||
Currently only one property is supported - `test-log-level` which should be set
|
||||
to a log level name (possible values are: `off`, `error`, `warning`, `info`,
|
||||
`debug`, `trace`). If this property is set to any value, the default logging
|
||||
queue is replaced with a special test queue which handles the log messages
|
||||
depending on status of the service. If a service has been set up, it just
|
||||
forwards them (so tests can easily override the log handling). However if it has
|
||||
not been set up, the enabled log messages are printed to STDERR and the rest is
|
||||
dropped.
|
||||
|
||||
Another useful tool is `TestLogger.gatherLogs` - a function that wraps an action
|
||||
and will return a sequence of logs reported when performing that action. It can
|
||||
be used to verify logs of an action inside of a test.
|
||||
|
@ -0,0 +1 @@
|
||||
test-log-level=info
|
@ -3,7 +3,8 @@ package org.enso.launcher.cli
|
||||
import akka.http.scaladsl.model.Uri
|
||||
import org.enso.cli.arguments.{Argument, OptsParseError}
|
||||
import org.enso.launcher.cli.GlobalCLIOptions.InternalOptions
|
||||
import org.enso.loggingservice.LogLevel
|
||||
import org.enso.loggingservice.ColorMode.{Always, Auto, Never}
|
||||
import org.enso.loggingservice.{ColorMode, LogLevel}
|
||||
|
||||
/** Gathers settings set by the global CLI options.
|
||||
*
|
||||
@ -45,7 +46,7 @@ object GlobalCLIOptions {
|
||||
*/
|
||||
def toOptions: Seq[String] = {
|
||||
val level = launcherLogLevel
|
||||
.map(level => Seq(s"--$LOG_LEVEL", level.toString))
|
||||
.map(level => Seq(s"--$LOG_LEVEL", level.name))
|
||||
.getOrElse(Seq())
|
||||
val uri = loggerConnectUri
|
||||
.map(uri => Seq(s"--$CONNECT_LOGGER", uri.toString))
|
||||
@ -66,30 +67,13 @@ object GlobalCLIOptions {
|
||||
if (config.hideProgress) Seq(s"--$HIDE_PROGRESS") else Seq()
|
||||
val useJSON = if (config.useJSON) Seq(s"--$USE_JSON") else Seq()
|
||||
autoConfirm ++ hideProgress ++ useJSON ++
|
||||
ColorMode.toOptions(config.colorMode) ++ config.internalOptions.toOptions
|
||||
LauncherColorMode.toOptions(
|
||||
config.colorMode
|
||||
) ++ config.internalOptions.toOptions
|
||||
}
|
||||
}
|
||||
|
||||
/** Describes possible modes of color display in console output.
|
||||
*/
|
||||
sealed trait ColorMode
|
||||
object ColorMode {
|
||||
|
||||
/** Never use color escape sequences in the output.
|
||||
*/
|
||||
case object Never extends ColorMode
|
||||
|
||||
/** Enable color output if it seems to be supported.
|
||||
*/
|
||||
case object Auto extends ColorMode
|
||||
|
||||
/** Always use escape sequences in the output, even if the program thinks they
|
||||
* are unsupported.
|
||||
*
|
||||
* May be useful if output is piped to other programs that know how to handle
|
||||
* the escape sequences.
|
||||
*/
|
||||
case object Always extends ColorMode
|
||||
object LauncherColorMode {
|
||||
|
||||
/** [[Argument]] instance used to parse [[ColorMode]] from CLI.
|
||||
*/
|
||||
|
@ -10,6 +10,7 @@ import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.cli._
|
||||
import org.enso.cli.arguments.Opts.implicits._
|
||||
import org.enso.cli.arguments._
|
||||
import org.enso.launcher.cli.LauncherColorMode.argument
|
||||
import org.enso.runtimeversionmanager.cli.Arguments._
|
||||
import org.enso.runtimeversionmanager.config.DefaultVersion
|
||||
import org.enso.runtimeversionmanager.runner.LanguageServerOptions
|
||||
@ -18,7 +19,7 @@ import org.enso.launcher.installation.DistributionInstaller
|
||||
import org.enso.launcher.installation.DistributionInstaller.BundleAction
|
||||
import org.enso.launcher.upgrade.LauncherUpgrader
|
||||
import org.enso.launcher.{cli, Launcher}
|
||||
import org.enso.loggingservice.LogLevel
|
||||
import org.enso.loggingservice.{ColorMode, LogLevel}
|
||||
|
||||
/** Defines the CLI commands and options for the program.
|
||||
*
|
||||
@ -558,7 +559,11 @@ object LauncherApplication {
|
||||
|
||||
internalOptsCallback(globalCLIOptions)
|
||||
LauncherUpgrader.setCLIOptions(globalCLIOptions)
|
||||
LauncherLogging.setup(logLevel, connectLogger, globalCLIOptions)
|
||||
LauncherLogging.setup(
|
||||
logLevel,
|
||||
connectLogger,
|
||||
globalCLIOptions.colorMode
|
||||
)
|
||||
initializeApp()
|
||||
|
||||
if (version) {
|
||||
|
@ -1,219 +1,30 @@
|
||||
package org.enso.launcher.cli
|
||||
|
||||
import akka.http.scaladsl.model.Uri
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import java.nio.file.Path
|
||||
|
||||
import org.enso.launcher.distribution.DefaultManagers
|
||||
import org.enso.loggingservice.printers.{
|
||||
FileOutputPrinter,
|
||||
Printer,
|
||||
StderrPrinter,
|
||||
StderrPrinterWithColors
|
||||
import org.enso.loggingservice.{
|
||||
ColorMode,
|
||||
LogLevel,
|
||||
LoggingServiceManager,
|
||||
LoggingServiceSetupHelper
|
||||
}
|
||||
import org.enso.loggingservice.{LogLevel, LoggerMode, LoggingServiceManager}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration.DurationInt
|
||||
import scala.concurrent.{Await, Future, Promise}
|
||||
import scala.util.control.NonFatal
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/** Manages setting up the logging service within the launcher.
|
||||
*/
|
||||
object LauncherLogging {
|
||||
private val logger = Logger[LauncherLogging.type]
|
||||
object LauncherLogging extends LoggingServiceSetupHelper {
|
||||
|
||||
/** Default logl level to use if none is provided.
|
||||
*/
|
||||
val defaultLogLevel: LogLevel = LogLevel.Warning
|
||||
/** @inheritdoc */
|
||||
override val defaultLogLevel: LogLevel = LogLevel.Warning
|
||||
|
||||
/** Sets up launcher's logging service as either a server that gathers other
|
||||
* component's logs or a client that forwards them further.
|
||||
*
|
||||
* Forwarding logs to another server in the launcher is an internal,
|
||||
* development-mode feature that is not designed to be used by end-users
|
||||
* unless they specifically know what they are doing. Redirecting logs to an
|
||||
* external server may result in some important information not being printed
|
||||
* by the launcher, being forwarded instead.
|
||||
*
|
||||
* @param logLevel the log level to use for launcher's logs; does not affect
|
||||
* other component's log level, which has to be set
|
||||
* separately
|
||||
* @param connectToExternalLogger specifies an Uri of an external logging
|
||||
* service that the launcher should forward
|
||||
* its logs to; advanced feature, use with
|
||||
* caution
|
||||
*/
|
||||
def setup(
|
||||
logLevel: Option[LogLevel],
|
||||
connectToExternalLogger: Option[Uri],
|
||||
globalCLIOptions: GlobalCLIOptions
|
||||
): Unit = {
|
||||
val actualLogLevel = logLevel.getOrElse(defaultLogLevel)
|
||||
connectToExternalLogger match {
|
||||
case Some(uri) =>
|
||||
setupLoggingConnection(uri, actualLogLevel)
|
||||
case None =>
|
||||
setupLoggingServer(actualLogLevel, globalCLIOptions)
|
||||
}
|
||||
}
|
||||
/** @inheritdoc */
|
||||
override val logFileSuffix: String = "enso-launcher"
|
||||
|
||||
/** Sets up a fallback logger that just logs to stderr.
|
||||
*
|
||||
* It can be used when the application has failed to parse the CLI options
|
||||
* and does not know which logger to set up.
|
||||
*/
|
||||
def setupFallback(): Unit = {
|
||||
LoggingServiceManager
|
||||
.setup(
|
||||
LoggerMode.Local(Seq(fallbackPrinter)),
|
||||
defaultLogLevel
|
||||
)
|
||||
.onComplete { _ =>
|
||||
loggingServiceEndpointPromise.trySuccess(None)
|
||||
}
|
||||
}
|
||||
|
||||
private def fallbackPrinter = StderrPrinter.create(printExceptions = true)
|
||||
|
||||
private val loggingServiceEndpointPromise = Promise[Option[Uri]]()
|
||||
|
||||
/** Returns a [[Uri]] of the logging service that launched components can
|
||||
* connect to.
|
||||
*
|
||||
* Points to the local server if it has been set up, or to the endpoint that
|
||||
* the launcher was told to connect to. May be empty if the initialization
|
||||
* failed and local logging is used as a fallback.
|
||||
*
|
||||
* The future is completed once the
|
||||
*/
|
||||
def loggingServiceEndpoint(): Future[Option[Uri]] =
|
||||
loggingServiceEndpointPromise.future
|
||||
|
||||
/** Returns a printer for outputting the logs to the standard error.
|
||||
*/
|
||||
private def stderrPrinter(
|
||||
globalCLIOptions: GlobalCLIOptions,
|
||||
printExceptions: Boolean
|
||||
): Printer =
|
||||
globalCLIOptions.colorMode match {
|
||||
case ColorMode.Never =>
|
||||
StderrPrinter.create(printExceptions)
|
||||
case ColorMode.Auto =>
|
||||
StderrPrinterWithColors.colorPrinterIfAvailable(printExceptions)
|
||||
case ColorMode.Always =>
|
||||
StderrPrinterWithColors.forceCreate(printExceptions)
|
||||
}
|
||||
|
||||
private def setupLoggingServer(
|
||||
logLevel: LogLevel,
|
||||
globalCLIOptions: GlobalCLIOptions
|
||||
): Unit = {
|
||||
val printExceptionsInStderr =
|
||||
implicitly[Ordering[LogLevel]].compare(logLevel, LogLevel.Debug) >= 0
|
||||
|
||||
/** Creates a stderr printer and a file printer if a log file can be opened.
|
||||
*
|
||||
* This is a `def` on purpose, as even if the service fails, the printers
|
||||
* are shut down, so the fallback must create new instances.
|
||||
*/
|
||||
def createPrinters() =
|
||||
try {
|
||||
val filePrinter =
|
||||
FileOutputPrinter.create(
|
||||
DefaultManagers.distributionManager.paths.logs
|
||||
)
|
||||
Seq(
|
||||
stderrPrinter(globalCLIOptions, printExceptionsInStderr),
|
||||
filePrinter
|
||||
)
|
||||
} catch {
|
||||
case NonFatal(error) =>
|
||||
logger.error(
|
||||
"Failed to initialize the write-to-file logger, " +
|
||||
"falling back to stderr only.",
|
||||
error
|
||||
)
|
||||
Seq(stderrPrinter(globalCLIOptions, printExceptions = true))
|
||||
}
|
||||
|
||||
LoggingServiceManager
|
||||
.setup(LoggerMode.Server(createPrinters()), logLevel)
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
logger.error(
|
||||
s"Failed to initialize the logging service server: $exception",
|
||||
exception
|
||||
)
|
||||
logger.warn("Falling back to local-only logger.")
|
||||
loggingServiceEndpointPromise.trySuccess(None)
|
||||
LoggingServiceManager
|
||||
.setup(
|
||||
LoggerMode.Local(createPrinters()),
|
||||
logLevel
|
||||
)
|
||||
.onComplete {
|
||||
case Failure(fallbackException) =>
|
||||
System.err.println(
|
||||
s"Failed to initialize the fallback logger: " +
|
||||
s"$fallbackException"
|
||||
)
|
||||
fallbackException.printStackTrace()
|
||||
case Success(_) =>
|
||||
}
|
||||
case Success(serverBinding) =>
|
||||
val uri = serverBinding.toUri()
|
||||
loggingServiceEndpointPromise.success(Some(uri))
|
||||
logger.trace(
|
||||
s"Logging service has been set-up and is listening at `$uri`."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Connects this launcher to an external logging service.
|
||||
*
|
||||
* Currently, this is an internal function used mostly for testing purposes.
|
||||
* It is not a user-facing API.
|
||||
*/
|
||||
private def setupLoggingConnection(uri: Uri, logLevel: LogLevel): Unit = {
|
||||
LoggingServiceManager
|
||||
.setup(
|
||||
LoggerMode.Client(uri),
|
||||
logLevel
|
||||
)
|
||||
.map(_ => true)
|
||||
.recoverWith { _ =>
|
||||
LoggingServiceManager
|
||||
.setup(
|
||||
LoggerMode.Local(Seq(fallbackPrinter)),
|
||||
logLevel
|
||||
)
|
||||
.map(_ => false)
|
||||
}
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
System.err.println(s"Failed to initialize the logger: $exception")
|
||||
exception.printStackTrace()
|
||||
loggingServiceEndpointPromise.trySuccess(None)
|
||||
case Success(connected) =>
|
||||
if (connected) {
|
||||
loggingServiceEndpointPromise.success(Some(uri))
|
||||
System.err.println(
|
||||
s"Log messages from this launcher are forwarded to `$uri`."
|
||||
)
|
||||
} else {
|
||||
loggingServiceEndpointPromise.trySuccess(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Waits until the logging service has been set-up.
|
||||
*
|
||||
* Due to limitations of how the logging service is implemented, it can only
|
||||
* be terminated after it has been set up.
|
||||
*/
|
||||
def waitForSetup(): Unit = {
|
||||
Await.ready(loggingServiceEndpointPromise.future, 5.seconds)
|
||||
}
|
||||
/** @inheritdoc */
|
||||
override lazy val logPath: Path =
|
||||
DefaultManagers.distributionManager.paths.logs
|
||||
|
||||
/** Turns off the main logging service, falling back to just a stderr backend.
|
||||
*
|
||||
@ -224,15 +35,10 @@ object LauncherLogging {
|
||||
* This is necessary on Windows to ensure that the logs file is closed, so
|
||||
* that the log directory can be removed.
|
||||
*/
|
||||
def prepareForUninstall(globalCLIOptions: GlobalCLIOptions): Unit = {
|
||||
def prepareForUninstall(colorMode: ColorMode): Unit = {
|
||||
waitForSetup()
|
||||
LoggingServiceManager.replaceWithFallback(printers =
|
||||
Seq(stderrPrinter(globalCLIOptions, printExceptions = true))
|
||||
Seq(stderrPrinter(colorMode, printExceptions = true))
|
||||
)
|
||||
}
|
||||
|
||||
/** Shuts down the logging service gracefully.
|
||||
*/
|
||||
def tearDown(): Unit =
|
||||
LoggingServiceManager.tearDown()
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ class LauncherRunner(
|
||||
}
|
||||
RunSettings(
|
||||
version,
|
||||
arguments ++ Seq("--log-level", logLevel.toString)
|
||||
arguments ++ setLogLevelArgs(logLevel)
|
||||
++ additionalArguments,
|
||||
connectLoggerIfAvailable = true
|
||||
)
|
||||
@ -128,12 +128,15 @@ class LauncherRunner(
|
||||
}
|
||||
RunSettings(
|
||||
version,
|
||||
arguments ++ Seq("--log-level", logLevel.toString)
|
||||
arguments ++ setLogLevelArgs(logLevel)
|
||||
++ additionalArguments,
|
||||
connectLoggerIfAvailable = true
|
||||
)
|
||||
}
|
||||
|
||||
private def setLogLevelArgs(level: LogLevel): Seq[String] =
|
||||
Seq("--log-level", level.name)
|
||||
|
||||
/** Creates [[RunSettings]] for launching the Language Server.
|
||||
*
|
||||
* See [[org.enso.launcher.Launcher.runLanguageServer]] for more details.
|
||||
|
@ -50,8 +50,8 @@ class DistributionInstaller(
|
||||
*
|
||||
* These files are assumed to be located at the data root.
|
||||
*/
|
||||
private val nonEssentialFiles = Seq("README.md", "NOTICE")
|
||||
private val nonEssentialDirectories = Seq("components-licences")
|
||||
private val nonEssentialFiles = Seq("README.md")
|
||||
private val nonEssentialDirectories = Seq("THIRD-PARTY")
|
||||
|
||||
private val enginesDirectory =
|
||||
installed.dataDirectory / manager.ENGINES_DIRECTORY
|
||||
|
@ -198,7 +198,7 @@ class DistributionUninstaller(
|
||||
dataRoot.toAbsolutePath.normalize
|
||||
)
|
||||
if (logsInsideData) {
|
||||
LauncherLogging.prepareForUninstall(globalCLIOptions)
|
||||
LauncherLogging.prepareForUninstall(globalCLIOptions.colorMode)
|
||||
}
|
||||
|
||||
for (dirName <- knownDataDirectories) {
|
||||
|
1
engine/launcher/src/test/resources/logging.properties
Normal file
1
engine/launcher/src/test/resources/logging.properties
Normal file
@ -0,0 +1 @@
|
||||
test-log-level=warning
|
@ -2,4 +2,4 @@ minimum-version-for-upgrade: 0.0.0
|
||||
files-to-copy:
|
||||
- README.md
|
||||
directories-to-copy:
|
||||
- components-licences
|
||||
- THIRD-PARTY
|
||||
|
@ -94,7 +94,7 @@ class UpgradeSpec
|
||||
val root = launcherPath.getParent.getParent
|
||||
FileSystem.writeTextFile(root / ".enso.portable", "mark")
|
||||
}
|
||||
Thread.sleep(250)
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
|
||||
/** Path to the launcher executable in the temporary distribution.
|
||||
@ -190,7 +190,7 @@ class UpgradeSpec
|
||||
checkVersion() shouldEqual SemVer(0, 0, 1)
|
||||
TestHelpers.readFileContent(root / "README.md").trim shouldEqual "Content"
|
||||
TestHelpers
|
||||
.readFileContent(root / "components-licences" / "test-license.txt")
|
||||
.readFileContent(root / "THIRD-PARTY" / "test-license.txt")
|
||||
.trim shouldEqual "Test license"
|
||||
}
|
||||
|
||||
@ -214,7 +214,7 @@ class UpgradeSpec
|
||||
.readFileContent(dataRoot / "README.md")
|
||||
.trim shouldEqual "Content"
|
||||
TestHelpers
|
||||
.readFileContent(dataRoot / "components-licences" / "test-license.txt")
|
||||
.readFileContent(dataRoot / "THIRD-PARTY" / "test-license.txt")
|
||||
.trim shouldEqual "Test license"
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
package org.enso.loggingservice
|
||||
|
||||
/** Describes possible modes of color display in console output. */
|
||||
sealed trait ColorMode
|
||||
object ColorMode {
|
||||
|
||||
/** Never use color escape sequences in the output. */
|
||||
case object Never extends ColorMode
|
||||
|
||||
/** Enable color output if it seems to be supported. */
|
||||
case object Auto extends ColorMode
|
||||
|
||||
/** Always use escape sequences in the output, even if the program thinks they
|
||||
* are unsupported.
|
||||
*
|
||||
* May be useful if output is piped to other programs that know how to handle
|
||||
* the escape sequences.
|
||||
*/
|
||||
case object Always extends ColorMode
|
||||
}
|
@ -5,7 +5,7 @@ import io.circe.{Decoder, DecodingFailure, Encoder}
|
||||
|
||||
/** Defines a log level for log messages.
|
||||
*/
|
||||
sealed abstract class LogLevel(final val level: Int) {
|
||||
sealed abstract class LogLevel(final val name: String, final val level: Int) {
|
||||
|
||||
/** Determines if a component running on `this` log level should log the
|
||||
* `other`.
|
||||
@ -14,6 +14,9 @@ sealed abstract class LogLevel(final val level: Int) {
|
||||
*/
|
||||
def shouldLog(other: LogLevel): Boolean =
|
||||
other.level <= level
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toString: String = name
|
||||
}
|
||||
|
||||
object LogLevel {
|
||||
@ -21,46 +24,34 @@ object LogLevel {
|
||||
/** This log level should not be used by messages, instead it can be set as
|
||||
* component's log level to completely disable logging for it.
|
||||
*/
|
||||
case object Off extends LogLevel(-1) {
|
||||
override def toString: String = "off"
|
||||
}
|
||||
case object Off extends LogLevel("off", -1)
|
||||
|
||||
/** Log level corresponding to severe errors, should be understandable to the
|
||||
* end-user.
|
||||
*/
|
||||
case object Error extends LogLevel(0) {
|
||||
override def toString: String = "error"
|
||||
}
|
||||
case object Error extends LogLevel("error", 0)
|
||||
|
||||
/** Log level corresponding to important notices or issues that are not
|
||||
* severe.
|
||||
*/
|
||||
case object Warning extends LogLevel(1) {
|
||||
override def toString: String = "warning"
|
||||
}
|
||||
case object Warning extends LogLevel("warning", 1)
|
||||
|
||||
/** Log level corresponding to usual information of what the application is
|
||||
* doing.
|
||||
*/
|
||||
case object Info extends LogLevel(2) {
|
||||
override def toString: String = "info"
|
||||
}
|
||||
case object Info extends LogLevel("info", 2)
|
||||
|
||||
/** Log level used for debugging the application.
|
||||
*
|
||||
* The messages can be more complex and targeted at developers diagnosing the
|
||||
* application.
|
||||
*/
|
||||
case object Debug extends LogLevel(3) {
|
||||
override def toString: String = "debug"
|
||||
}
|
||||
case object Debug extends LogLevel("debug", 3)
|
||||
|
||||
/** Log level used for advanced debugging, may be used for more throughout
|
||||
* diagnostics.
|
||||
*/
|
||||
case object Trace extends LogLevel(4) {
|
||||
override def toString: String = "trace"
|
||||
}
|
||||
case object Trace extends LogLevel("trace", 4)
|
||||
|
||||
/** Lists all available log levels.
|
||||
*
|
||||
@ -99,6 +90,7 @@ object LogLevel {
|
||||
* Returns None if the number does not represent a valid log level.
|
||||
*/
|
||||
def fromInteger(level: Int): Option[LogLevel] = level match {
|
||||
case Off.level => Some(Off)
|
||||
case Error.level => Some(Error)
|
||||
case Warning.level => Some(Warning)
|
||||
case Info.level => Some(Info)
|
||||
@ -107,6 +99,20 @@ object LogLevel {
|
||||
case _ => None
|
||||
}
|
||||
|
||||
/** Creates a [[LogLevel]] from its string representation.
|
||||
*
|
||||
* Returns None if the value does not represent a valid log level.
|
||||
*/
|
||||
def fromString(level: String): Option[LogLevel] = level match {
|
||||
case Off.name => Some(Off)
|
||||
case Error.name => Some(Error)
|
||||
case Warning.name => Some(Warning)
|
||||
case Info.name => Some(Info)
|
||||
case Debug.name => Some(Debug)
|
||||
case Trace.name => Some(Trace)
|
||||
case _ => None
|
||||
}
|
||||
|
||||
/** [[Decoder]] instance for [[LogLevel]].
|
||||
*/
|
||||
implicit val decoder: Decoder[LogLevel] = { json =>
|
||||
|
@ -9,31 +9,29 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||
/** Manages the logging service.
|
||||
*/
|
||||
object LoggingServiceManager {
|
||||
private val testLoggingPropertyKey = "org.enso.loggingservice.test-log-level"
|
||||
|
||||
private var currentService: Option[Service] = None
|
||||
private var currentLevel: LogLevel = LogLevel.Trace
|
||||
|
||||
/** Returns the log level that is currently set up for the application.
|
||||
*
|
||||
* Its result can change depending on initialization state.
|
||||
*/
|
||||
def currentLogLevelForThisApplication(): LogLevel = currentLevel
|
||||
|
||||
/** Creates an instance for the [[messageQueue]].
|
||||
*
|
||||
* Runs special workaround logic if test mode is detected.
|
||||
*/
|
||||
private def initializeMessageQueue(): BlockingConsumerMessageQueue = {
|
||||
sys.props.get(testLoggingPropertyKey) match {
|
||||
case Some(value) =>
|
||||
val logLevel =
|
||||
value.toIntOption.flatMap(LogLevel.fromInteger).getOrElse {
|
||||
System.err.println(
|
||||
s"Invalid log level for $testLoggingPropertyKey, " +
|
||||
s"falling back to info."
|
||||
)
|
||||
LogLevel.Info
|
||||
}
|
||||
|
||||
LoggingSettings.testLogLevel match {
|
||||
case Some(logLevel) =>
|
||||
val shouldOverride = () => currentService.isEmpty
|
||||
|
||||
System.err.println(
|
||||
s"[Logging Service] Using test-mode logger at level $logLevel."
|
||||
)
|
||||
new TestMessageQueue(logLevel, shouldOverride)
|
||||
case None => productionMessageQueue()
|
||||
case None =>
|
||||
productionMessageQueue()
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +92,7 @@ object LoggingServiceManager {
|
||||
* times.
|
||||
*/
|
||||
def tearDown(): Unit = {
|
||||
val service = currentService.synchronized {
|
||||
val service = this.synchronized {
|
||||
val service = currentService
|
||||
currentService = None
|
||||
service
|
||||
@ -108,6 +106,11 @@ object LoggingServiceManager {
|
||||
handlePendingMessages()
|
||||
}
|
||||
|
||||
/** Checks if the logging service has been set up. */
|
||||
def isSetUp(): Boolean = this.synchronized {
|
||||
currentService.isDefined
|
||||
}
|
||||
|
||||
Runtime.getRuntime.addShutdownHook(new Thread(() => tearDown()))
|
||||
|
||||
/** Terminates the currently running logging service (if any) and replaces it
|
||||
@ -121,7 +124,7 @@ object LoggingServiceManager {
|
||||
): Unit = {
|
||||
val fallback =
|
||||
Local.setup(currentLevel, messageQueue, printers)
|
||||
val previousService = currentService.synchronized {
|
||||
val previousService = this.synchronized {
|
||||
val previous = currentService
|
||||
currentService = Some(fallback)
|
||||
previous
|
||||
@ -163,7 +166,7 @@ object LoggingServiceManager {
|
||||
mode: LoggerMode[InitializationResult],
|
||||
logLevel: LogLevel
|
||||
): InitializationResult = {
|
||||
currentService.synchronized {
|
||||
this.synchronized {
|
||||
if (currentService.isDefined) {
|
||||
throw new IllegalStateException(
|
||||
"The logging service has already been set up."
|
||||
|
@ -0,0 +1,227 @@
|
||||
package org.enso.loggingservice
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
import akka.http.scaladsl.model.Uri
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import org.enso.loggingservice.printers.{
|
||||
FileOutputPrinter,
|
||||
Printer,
|
||||
StderrPrinter,
|
||||
StderrPrinterWithColors
|
||||
}
|
||||
|
||||
import scala.concurrent.{Await, ExecutionContext, Future, Promise}
|
||||
import scala.concurrent.duration.DurationInt
|
||||
import scala.util.control.NonFatal
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
abstract class LoggingServiceSetupHelper(implicit
|
||||
executionContext: ExecutionContext
|
||||
) {
|
||||
private val logger = Logger[this.type]
|
||||
|
||||
/** Default log level to use if none is provided. */
|
||||
val defaultLogLevel: LogLevel
|
||||
|
||||
/** The location for storing the log files. */
|
||||
def logPath: Path
|
||||
|
||||
/** A suffix added to created log files. */
|
||||
val logFileSuffix: String
|
||||
|
||||
/** Sets up the logging service as either a server that gathers other
|
||||
* component's logs or a client that forwards them further.
|
||||
*
|
||||
* Forwarding logs to another server is currently an internal,
|
||||
* development-mode feature that is not designed to be used by end-users
|
||||
* unless they specifically know what they are doing. Redirecting logs to an
|
||||
* external server may result in some important information not being printed
|
||||
* by the application, being forwarded instead.
|
||||
*
|
||||
* @param logLevel the log level to use for this application's logs; does not
|
||||
* affect other component's log level, which has to be set
|
||||
* separately
|
||||
* @param connectToExternalLogger specifies an Uri of an external logging
|
||||
* service that the application should forward
|
||||
* its logs to; advanced feature, use with
|
||||
* caution
|
||||
* @param colorMode specifies how to handle colors in console output
|
||||
*/
|
||||
def setup(
|
||||
logLevel: Option[LogLevel],
|
||||
connectToExternalLogger: Option[Uri],
|
||||
colorMode: ColorMode
|
||||
): Unit = {
|
||||
val actualLogLevel = logLevel.getOrElse(defaultLogLevel)
|
||||
connectToExternalLogger match {
|
||||
case Some(uri) =>
|
||||
setupLoggingConnection(uri, actualLogLevel)
|
||||
case None =>
|
||||
setupLoggingServer(actualLogLevel, colorMode)
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets up a fallback logger that just logs to stderr.
|
||||
*
|
||||
* It can be used when the application has failed to parse the CLI options
|
||||
* and does not know which logger to set up.
|
||||
*/
|
||||
def setupFallback(): Unit = {
|
||||
LoggingServiceManager
|
||||
.setup(
|
||||
LoggerMode.Local(Seq(fallbackPrinter)),
|
||||
defaultLogLevel
|
||||
)
|
||||
.onComplete { _ =>
|
||||
loggingServiceEndpointPromise.trySuccess(None)
|
||||
}
|
||||
}
|
||||
|
||||
def fallbackPrinter: Printer = StderrPrinter.create(printExceptions = true)
|
||||
|
||||
private val loggingServiceEndpointPromise = Promise[Option[Uri]]()
|
||||
|
||||
/** Returns a [[Uri]] of the logging service that launched components can
|
||||
* connect to.
|
||||
*
|
||||
* Points to the local server if it has been set up, or to the endpoint that
|
||||
* the launcher was told to connect to. May be empty if the initialization
|
||||
* failed and local logging is used as a fallback.
|
||||
*
|
||||
* The future is completed once the
|
||||
*/
|
||||
def loggingServiceEndpoint(): Future[Option[Uri]] =
|
||||
loggingServiceEndpointPromise.future
|
||||
|
||||
/** Returns a printer for outputting the logs to the standard error. */
|
||||
def stderrPrinter(
|
||||
colorMode: ColorMode,
|
||||
printExceptions: Boolean
|
||||
): Printer =
|
||||
colorMode match {
|
||||
case ColorMode.Never =>
|
||||
StderrPrinter.create(printExceptions)
|
||||
case ColorMode.Auto =>
|
||||
StderrPrinterWithColors.colorPrinterIfAvailable(printExceptions)
|
||||
case ColorMode.Always =>
|
||||
StderrPrinterWithColors.forceCreate(printExceptions)
|
||||
}
|
||||
|
||||
private def setupLoggingServer(
|
||||
logLevel: LogLevel,
|
||||
colorMode: ColorMode
|
||||
): Unit = {
|
||||
val printExceptionsInStderr =
|
||||
implicitly[Ordering[LogLevel]].compare(logLevel, LogLevel.Debug) >= 0
|
||||
|
||||
/** Creates a stderr printer and a file printer if a log file can be opened.
|
||||
*
|
||||
* This is a `def` on purpose, as even if the service fails, the printers
|
||||
* are shut down, so the fallback must create new instances.
|
||||
*/
|
||||
def createPrinters() =
|
||||
try {
|
||||
val filePrinter =
|
||||
FileOutputPrinter.create(
|
||||
logDirectory = logPath,
|
||||
suffix = logFileSuffix,
|
||||
printExceptions = true
|
||||
)
|
||||
Seq(
|
||||
stderrPrinter(colorMode, printExceptionsInStderr),
|
||||
filePrinter
|
||||
)
|
||||
} catch {
|
||||
case NonFatal(error) =>
|
||||
logger.error(
|
||||
"Failed to initialize the write-to-file logger, " +
|
||||
"falling back to stderr only.",
|
||||
error
|
||||
)
|
||||
Seq(stderrPrinter(colorMode, printExceptions = true))
|
||||
}
|
||||
|
||||
LoggingServiceManager
|
||||
.setup(LoggerMode.Server(createPrinters()), logLevel)
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
logger.error(
|
||||
s"Failed to initialize the logging service server: $exception",
|
||||
exception
|
||||
)
|
||||
logger.warn("Falling back to local-only logger.")
|
||||
loggingServiceEndpointPromise.trySuccess(None)
|
||||
LoggingServiceManager
|
||||
.setup(
|
||||
LoggerMode.Local(createPrinters()),
|
||||
logLevel
|
||||
)
|
||||
.onComplete {
|
||||
case Failure(fallbackException) =>
|
||||
System.err.println(
|
||||
s"Failed to initialize the fallback logger: " +
|
||||
s"$fallbackException"
|
||||
)
|
||||
fallbackException.printStackTrace()
|
||||
case Success(_) =>
|
||||
}
|
||||
case Success(serverBinding) =>
|
||||
val uri = serverBinding.toUri()
|
||||
loggingServiceEndpointPromise.success(Some(uri))
|
||||
logger.trace(
|
||||
s"Logging service has been set-up and is listening at `$uri`."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Connects this application to an external logging service.
|
||||
*
|
||||
* Currently, this is an internal function used mostly for testing purposes.
|
||||
* It is not a user-facing API.
|
||||
*/
|
||||
private def setupLoggingConnection(uri: Uri, logLevel: LogLevel): Unit = {
|
||||
LoggingServiceManager
|
||||
.setup(
|
||||
LoggerMode.Client(uri),
|
||||
logLevel
|
||||
)
|
||||
.map(_ => true)
|
||||
.recoverWith { _ =>
|
||||
LoggingServiceManager
|
||||
.setup(
|
||||
LoggerMode.Local(Seq(fallbackPrinter)),
|
||||
logLevel
|
||||
)
|
||||
.map(_ => false)
|
||||
}
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
System.err.println(s"Failed to initialize the logger: $exception")
|
||||
exception.printStackTrace()
|
||||
loggingServiceEndpointPromise.trySuccess(None)
|
||||
case Success(connected) =>
|
||||
if (connected) {
|
||||
loggingServiceEndpointPromise.success(Some(uri))
|
||||
System.err.println(
|
||||
s"Log messages are forwarded to `$uri`."
|
||||
)
|
||||
} else {
|
||||
loggingServiceEndpointPromise.trySuccess(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Waits until the logging service has been set-up.
|
||||
*
|
||||
* Due to limitations of how the logging service is implemented, it can only
|
||||
* be terminated after it has been set up.
|
||||
*/
|
||||
def waitForSetup(): Unit = {
|
||||
Await.ready(loggingServiceEndpointPromise.future, 5.seconds)
|
||||
}
|
||||
|
||||
/** Shuts down the logging service gracefully.
|
||||
*/
|
||||
def tearDown(): Unit = LoggingServiceManager.tearDown()
|
||||
}
|
@ -2,6 +2,9 @@ package org.enso.loggingservice
|
||||
|
||||
import org.enso.loggingservice.printers.TestPrinter
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration.DurationInt
|
||||
|
||||
/** A helper object for handling logs in tests.
|
||||
*/
|
||||
object TestLogger {
|
||||
@ -20,12 +23,18 @@ object TestLogger {
|
||||
*/
|
||||
def gatherLogs(action: => Unit): Seq[TestLogMessage] = {
|
||||
LoggingServiceManager.dropPendingLogs()
|
||||
LoggingServiceManager.tearDown()
|
||||
if (LoggingServiceManager.isSetUp()) {
|
||||
throw new IllegalStateException(
|
||||
"gatherLogs called but another logging service has been already set " +
|
||||
"up, this would lead to conflicts"
|
||||
)
|
||||
}
|
||||
val printer = new TestPrinter
|
||||
LoggingServiceManager.setup(
|
||||
val future = LoggingServiceManager.setup(
|
||||
LoggerMode.Local(Seq(printer)),
|
||||
LogLevel.Debug
|
||||
)
|
||||
Await.ready(future, 1.second)
|
||||
action
|
||||
LoggingServiceManager.tearDown()
|
||||
printer.getLoggedMessages
|
||||
|
@ -0,0 +1,42 @@
|
||||
package org.enso.loggingservice.internal
|
||||
|
||||
import java.util.Properties
|
||||
|
||||
import org.enso.loggingservice.LogLevel
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
/** Reads logger settings from the resources.
|
||||
*
|
||||
* Currently these settings are used to configure logging inside of tests.
|
||||
*/
|
||||
object LoggingSettings {
|
||||
private val propertiesFilename = "logging.properties"
|
||||
private val testLoggingPropertyKey = "test-log-level"
|
||||
private val loggingProperties: Properties = {
|
||||
val props = new Properties
|
||||
Option(this.getClass.getClassLoader.getResourceAsStream(propertiesFilename))
|
||||
.foreach { stream =>
|
||||
val _ = Using(stream) { stream =>
|
||||
props.load(stream)
|
||||
}
|
||||
}
|
||||
props
|
||||
}
|
||||
|
||||
/** Indicates the log level to be used in test mode.
|
||||
*
|
||||
* If set to None, production logging should be used.
|
||||
*/
|
||||
val testLogLevel: Option[LogLevel] = Option(
|
||||
loggingProperties.getProperty(testLoggingPropertyKey)
|
||||
).map { string =>
|
||||
LogLevel.fromString(string).getOrElse {
|
||||
System.err.println(
|
||||
s"Invalid log level for $testLoggingPropertyKey set in " +
|
||||
s"$propertiesFilename, falling back to info."
|
||||
)
|
||||
LogLevel.Info
|
||||
}
|
||||
}
|
||||
}
|
@ -7,10 +7,12 @@ import org.enso.loggingservice.printers.StderrPrinter
|
||||
*
|
||||
* It has a smaller buffer and ignores messages from a certain log level.
|
||||
*
|
||||
* @param logLevel
|
||||
* @param shouldOverride
|
||||
* @param logLevel specifies which messages will be printed to stderr if no
|
||||
* service is set-up
|
||||
* @param isLoggingServiceSetUp a function used to check if a logging service
|
||||
* is set up
|
||||
*/
|
||||
class TestMessageQueue(logLevel: LogLevel, shouldOverride: () => Boolean)
|
||||
class TestMessageQueue(logLevel: LogLevel, isLoggingServiceSetUp: () => Boolean)
|
||||
extends BlockingConsumerMessageQueue(bufferSize = 100) {
|
||||
|
||||
private def shouldKeepMessage(
|
||||
@ -24,10 +26,10 @@ class TestMessageQueue(logLevel: LogLevel, shouldOverride: () => Boolean)
|
||||
|
||||
/** @inheritdoc */
|
||||
override def send(message: Either[InternalLogMessage, WSLogMessage]): Unit =
|
||||
if (shouldKeepMessage(message)) {
|
||||
if (shouldOverride())
|
||||
if (isLoggingServiceSetUp()) {
|
||||
if (shouldKeepMessage(message))
|
||||
overridePrinter.print(message.fold(_.toLogMessage, identity))
|
||||
else
|
||||
super.send(message)
|
||||
} else {
|
||||
super.send(message)
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,10 @@ trait ServiceWithActorSystem extends Service {
|
||||
ConfigValueFactory.fromAnyRef("akka.event.DefaultLoggingFilter")
|
||||
)
|
||||
.withValue("akka.loglevel", ConfigValueFactory.fromAnyRef("WARNING"))
|
||||
.withValue(
|
||||
"akka.coordinated-shutdown.run-by-actor-system-terminate",
|
||||
ConfigValueFactory.fromAnyRef("off")
|
||||
)
|
||||
ActorSystem(
|
||||
name,
|
||||
config,
|
||||
|
@ -73,8 +73,7 @@ trait ThreadProcessingService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
/** @inheritdoc */
|
||||
abstract override def terminate(): Unit = {
|
||||
super.terminate()
|
||||
queueThread match {
|
||||
|
@ -12,31 +12,32 @@ import org.enso.loggingservice.internal.protocol.WSLogMessage
|
||||
* this file.
|
||||
*
|
||||
* @param logDirectory the directory to create the logfile in
|
||||
* @param suffix a suffix to be added to the filename
|
||||
* @param printExceptions whether to print exceptions attached to the log
|
||||
* messages
|
||||
*/
|
||||
class FileOutputPrinter(logDirectory: Path, printExceptions: Boolean)
|
||||
extends Printer {
|
||||
class FileOutputPrinter(
|
||||
logDirectory: Path,
|
||||
suffix: String,
|
||||
printExceptions: Boolean
|
||||
) extends Printer {
|
||||
|
||||
private val renderer = new DefaultLogMessageRenderer(printExceptions)
|
||||
private val writer = initializeWriter()
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
/** @inheritdoc */
|
||||
override def print(message: WSLogMessage): Unit = {
|
||||
val lines = renderer.render(message)
|
||||
writer.println(lines)
|
||||
}
|
||||
|
||||
/** @inheritdoc
|
||||
*/
|
||||
/** @inheritdoc */
|
||||
override def shutdown(): Unit = {
|
||||
writer.flush()
|
||||
writer.close()
|
||||
}
|
||||
|
||||
/** Opens the log file for writing.
|
||||
*/
|
||||
/** Opens the log file for writing. */
|
||||
private def initializeWriter(): PrintWriter = {
|
||||
val logPath = logDirectory.resolve(makeLogFilename())
|
||||
Files.createDirectories(logDirectory)
|
||||
@ -49,24 +50,23 @@ class FileOutputPrinter(logDirectory: Path, printExceptions: Boolean)
|
||||
)
|
||||
}
|
||||
|
||||
/** Creates a log filename that is created based on the current timestamp.
|
||||
*/
|
||||
/** Creates a log filename that is created based on the current timestamp. */
|
||||
private def makeLogFilename(): String = {
|
||||
val timestampZone = ZoneId.of("UTC")
|
||||
val timestamp = LocalDateTime
|
||||
.ofInstant(Instant.now(), timestampZone)
|
||||
.format(DateTimeFormatter.ofPattern("YYYYMMdd-HHmmss-SSS"))
|
||||
s"$timestamp-enso.log"
|
||||
s"$timestamp-$suffix.log"
|
||||
}
|
||||
}
|
||||
|
||||
object FileOutputPrinter {
|
||||
|
||||
/** Creates a new [[FileOutputPrinter]].
|
||||
*/
|
||||
/** Creates a new [[FileOutputPrinter]]. */
|
||||
def create(
|
||||
logDirectory: Path,
|
||||
suffix: String,
|
||||
printExceptions: Boolean = true
|
||||
): FileOutputPrinter =
|
||||
new FileOutputPrinter(logDirectory, printExceptions)
|
||||
new FileOutputPrinter(logDirectory, suffix, printExceptions)
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Args=--initialize-at-run-time=com.typesafe.config.impl.ConfigImpl$EnvVariablesHolder,com.typesafe.config.impl.ConfigImpl$SystemPropertiesHolder
|
||||
Args=--initialize-at-run-time=com.typesafe.config.impl.ConfigImpl$EnvVariablesHolder,com.typesafe.config.impl.ConfigImpl$SystemPropertiesHolder,org.enso.loggingservice.LoggingServiceManager$
|
||||
|
@ -1,22 +1,31 @@
|
||||
package org.enso.projectmanager.boot
|
||||
|
||||
import org.enso.loggingservice.printers.StderrPrinterWithColors
|
||||
import org.enso.loggingservice.{LogLevel, LoggerMode, LoggingServiceManager}
|
||||
import java.nio.file.Path
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import akka.http.scaladsl.model.Uri
|
||||
import org.enso.loggingservice.{LogLevel, LoggingServiceSetupHelper}
|
||||
import org.enso.projectmanager.service.LoggingServiceDescriptor
|
||||
import org.enso.projectmanager.versionmanagement.DefaultDistributionConfiguration
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
||||
/** A helper for setting up the logging service in the Project Manager. */
|
||||
object Logging {
|
||||
object Logging extends LoggingServiceSetupHelper {
|
||||
|
||||
/** Sets up the logging service for local logging. */
|
||||
def setup(
|
||||
logLevel: LogLevel,
|
||||
executionContext: ExecutionContext
|
||||
): Future[Unit] = {
|
||||
// TODO [RW] setting up the logging server will be added in #1151
|
||||
val printer = StderrPrinterWithColors.colorPrinterIfAvailable(false)
|
||||
LoggingServiceManager.setup(LoggerMode.Local(Seq(printer)), logLevel)(
|
||||
executionContext
|
||||
)
|
||||
/** @inheritdoc */
|
||||
override val defaultLogLevel: LogLevel = LogLevel.Info
|
||||
|
||||
/** @inheritdoc */
|
||||
override lazy val logPath: Path =
|
||||
DefaultDistributionConfiguration.distributionManager.paths.logs
|
||||
|
||||
/** @inheritdoc */
|
||||
override val logFileSuffix: String = "enso-project-manager"
|
||||
|
||||
object GlobalLoggingService extends LoggingServiceDescriptor {
|
||||
|
||||
/** @inheritdoc */
|
||||
override def getEndpoint: Future[Option[Uri]] = loggingServiceEndpoint()
|
||||
}
|
||||
}
|
||||
|
@ -24,13 +24,7 @@ import org.enso.projectmanager.protocol.{
|
||||
}
|
||||
import org.enso.projectmanager.service.config.GlobalConfigService
|
||||
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagementService
|
||||
import org.enso.projectmanager.service.{
|
||||
MonadicProjectValidator,
|
||||
ProjectCreationService,
|
||||
ProjectService,
|
||||
ProjectServiceFailure,
|
||||
ValidationFailure
|
||||
}
|
||||
import org.enso.projectmanager.service._
|
||||
import org.enso.projectmanager.versionmanagement.DefaultDistributionConfiguration
|
||||
|
||||
import scala.concurrent.ExecutionContext
|
||||
@ -71,6 +65,9 @@ class MainModule[
|
||||
gen
|
||||
)
|
||||
|
||||
val distributionConfiguration = DefaultDistributionConfiguration
|
||||
val loggingService = Logging.GlobalLoggingService
|
||||
|
||||
lazy val languageServerRegistry =
|
||||
system.actorOf(
|
||||
LanguageServerRegistry
|
||||
@ -79,7 +76,8 @@ class MainModule[
|
||||
config.bootloader,
|
||||
config.supervision,
|
||||
config.timeout,
|
||||
DefaultDistributionConfiguration,
|
||||
distributionConfiguration,
|
||||
loggingService,
|
||||
ExecutorWithUnlimitedPool
|
||||
),
|
||||
"language-server-registry"
|
||||
@ -99,10 +97,13 @@ class MainModule[
|
||||
)
|
||||
|
||||
lazy val projectCreationService =
|
||||
new ProjectCreationService[F](DefaultDistributionConfiguration)
|
||||
new ProjectCreationService[F](
|
||||
distributionConfiguration,
|
||||
loggingService
|
||||
)
|
||||
|
||||
lazy val globalConfigService =
|
||||
new GlobalConfigService[F](DefaultDistributionConfiguration)
|
||||
new GlobalConfigService[F](distributionConfiguration)
|
||||
|
||||
lazy val projectService =
|
||||
new ProjectService[F](
|
||||
@ -114,11 +115,11 @@ class MainModule[
|
||||
clock,
|
||||
gen,
|
||||
languageServerGateway,
|
||||
DefaultDistributionConfiguration
|
||||
distributionConfiguration
|
||||
)
|
||||
|
||||
lazy val runtimeVersionManagementService =
|
||||
new RuntimeVersionManagementService[F](DefaultDistributionConfiguration)
|
||||
new RuntimeVersionManagementService[F](distributionConfiguration)
|
||||
|
||||
lazy val clientControllerFactory =
|
||||
new ManagerClientControllerFactory[F](
|
||||
@ -126,6 +127,7 @@ class MainModule[
|
||||
projectService = projectService,
|
||||
globalConfigService = globalConfigService,
|
||||
runtimeVersionManagementService = runtimeVersionManagementService,
|
||||
loggingServiceDescriptor = loggingService,
|
||||
timeoutConfig = config.timeout
|
||||
)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||
import akka.http.scaladsl.Http
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.apache.commons.cli.CommandLine
|
||||
import org.enso.loggingservice.LogLevel
|
||||
import org.enso.loggingservice.{ColorMode, LogLevel}
|
||||
import org.enso.projectmanager.boot.Globals.{
|
||||
ConfigFilename,
|
||||
ConfigNamespace,
|
||||
@ -132,8 +132,15 @@ object ProjectManager extends App with LazyLogging {
|
||||
case 1 => LogLevel.Debug
|
||||
case _ => LogLevel.Trace
|
||||
}
|
||||
|
||||
// TODO [RW] at some point we may want to allow customization of color
|
||||
// output in CLI flags
|
||||
val colorMode = ColorMode.Auto
|
||||
|
||||
ZIO
|
||||
.fromFuture(executionContext => Logging.setup(level, executionContext))
|
||||
.effect {
|
||||
Logging.setup(Some(level), None, colorMode)
|
||||
}
|
||||
.catchAll { exception =>
|
||||
putStrLnErr(s"Failed to setup the logger: $exception")
|
||||
}
|
||||
@ -144,7 +151,8 @@ object ProjectManager extends App with LazyLogging {
|
||||
): ZIO[Console, Nothing, ExitCode] = {
|
||||
val versionDescription = VersionDescription.make(
|
||||
"Enso Project Manager",
|
||||
includeRuntimeJVMInfo = true
|
||||
includeRuntimeJVMInfo = false,
|
||||
enableNativeImageOSWorkaround = true
|
||||
)
|
||||
putStrLn(versionDescription.asString(useJson)) *>
|
||||
ZIO.succeed(SuccessExitCode)
|
||||
@ -153,7 +161,8 @@ object ProjectManager extends App with LazyLogging {
|
||||
private def logServerStartup(): UIO[Unit] =
|
||||
effectTotal {
|
||||
logger.info(
|
||||
s"Started server at ${config.server.host}:${config.server.port}, press enter to kill server"
|
||||
s"Started server at ${config.server.host}:${config.server.port}, " +
|
||||
s"press enter to kill server"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,10 @@ import java.util.concurrent.Executors
|
||||
import akka.actor.ActorRef
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory
|
||||
import org.enso.loggingservice.LogLevel
|
||||
import org.enso.loggingservice.LoggingServiceManager
|
||||
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory
|
||||
import org.enso.runtimeversionmanager.runner.{LanguageServerOptions, Runner}
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.util.Using
|
||||
|
||||
object ExecutorWithUnlimitedPool extends LanguageServerExecutor {
|
||||
@ -72,9 +71,8 @@ object ExecutorWithUnlimitedPool extends LanguageServerExecutor {
|
||||
val versionManager = RuntimeVersionManagerFactory(distributionConfiguration)
|
||||
.makeRuntimeVersionManager(progressTracker)
|
||||
|
||||
// TODO [RW] logging #1151
|
||||
val loggerConnection = Future.successful(None)
|
||||
val logLevel = LogLevel.Info
|
||||
val inheritedLogLevel =
|
||||
LoggingServiceManager.currentLogLevelForThisApplication()
|
||||
val options = LanguageServerOptions(
|
||||
rootId = descriptor.rootId,
|
||||
interface = descriptor.networkConfig.interface,
|
||||
@ -85,14 +83,14 @@ object ExecutorWithUnlimitedPool extends LanguageServerExecutor {
|
||||
val runner = new Runner(
|
||||
versionManager,
|
||||
distributionConfiguration.environment,
|
||||
loggerConnection
|
||||
descriptor.deferredLoggingServiceEndpoint
|
||||
)
|
||||
val runSettings = runner
|
||||
.startLanguageServer(
|
||||
options = options,
|
||||
projectPath = descriptor.rootPath,
|
||||
version = descriptor.engineVersion,
|
||||
logLevel = logLevel,
|
||||
logLevel = inheritedLogLevel,
|
||||
additionalArguments = Seq()
|
||||
)
|
||||
.get
|
||||
|
@ -32,6 +32,7 @@ import org.enso.projectmanager.infrastructure.languageserver.LanguageServerContr
|
||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProtocol._
|
||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerRegistry.ServerShutDown
|
||||
import org.enso.projectmanager.model.Project
|
||||
import org.enso.projectmanager.service.LoggingServiceDescriptor
|
||||
import org.enso.projectmanager.util.UnhandledLogging
|
||||
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||
|
||||
@ -47,6 +48,7 @@ import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||
* @param supervisionConfig a supervision config
|
||||
* @param timeoutConfig a timeout config
|
||||
* @param distributionConfiguration configuration of the distribution
|
||||
* @param loggingServiceDescriptor a logging service configuration descriptor
|
||||
* @param executor an executor service used to start the language server
|
||||
* process
|
||||
*/
|
||||
@ -59,6 +61,7 @@ class LanguageServerController(
|
||||
supervisionConfig: SupervisionConfig,
|
||||
timeoutConfig: TimeoutConfig,
|
||||
distributionConfiguration: DistributionConfiguration,
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor,
|
||||
executor: LanguageServerExecutor
|
||||
) extends Actor
|
||||
with ActorLogging
|
||||
@ -69,14 +72,15 @@ class LanguageServerController(
|
||||
|
||||
private val descriptor =
|
||||
LanguageServerDescriptor(
|
||||
name = s"language-server-${project.id}",
|
||||
rootId = UUID.randomUUID(),
|
||||
rootPath = project.path.get,
|
||||
networkConfig = networkConfig,
|
||||
distributionConfiguration = distributionConfiguration,
|
||||
engineVersion = engineVersion,
|
||||
jvmSettings = distributionConfiguration.defaultJVMSettings,
|
||||
discardOutput = distributionConfiguration.shouldDiscardChildOutput
|
||||
name = s"language-server-${project.id}",
|
||||
rootId = UUID.randomUUID(),
|
||||
rootPath = project.path.get,
|
||||
networkConfig = networkConfig,
|
||||
distributionConfiguration = distributionConfiguration,
|
||||
engineVersion = engineVersion,
|
||||
jvmSettings = distributionConfiguration.defaultJVMSettings,
|
||||
discardOutput = distributionConfiguration.shouldDiscardChildOutput,
|
||||
deferredLoggingServiceEndpoint = loggingServiceDescriptor.getEndpoint
|
||||
)
|
||||
|
||||
override def supervisorStrategy: SupervisorStrategy =
|
||||
@ -313,6 +317,7 @@ object LanguageServerController {
|
||||
* @param distributionConfiguration configuration of the distribution
|
||||
* @param executor an executor service used to start the language server
|
||||
* process
|
||||
* @param loggingServiceDescriptor a logging service configuration descriptor
|
||||
* @return a configuration object
|
||||
*/
|
||||
def props(
|
||||
@ -324,6 +329,7 @@ object LanguageServerController {
|
||||
supervisionConfig: SupervisionConfig,
|
||||
timeoutConfig: TimeoutConfig,
|
||||
distributionConfiguration: DistributionConfiguration,
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor,
|
||||
executor: LanguageServerExecutor
|
||||
): Props =
|
||||
Props(
|
||||
@ -336,6 +342,7 @@ object LanguageServerController {
|
||||
supervisionConfig,
|
||||
timeoutConfig,
|
||||
distributionConfiguration,
|
||||
loggingServiceDescriptor,
|
||||
executor
|
||||
)
|
||||
)
|
||||
|
@ -2,11 +2,14 @@ package org.enso.projectmanager.infrastructure.languageserver
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import akka.http.scaladsl.model.Uri
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.projectmanager.boot.configuration.NetworkConfig
|
||||
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||
import org.enso.runtimeversionmanager.runner.JVMSettings
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
/** A descriptor specifying options related to starting a Language Server.
|
||||
*
|
||||
* @param name a name of the LS
|
||||
@ -20,6 +23,11 @@ import org.enso.runtimeversionmanager.runner.JVMSettings
|
||||
* @param jvmSettings settings to use for the JVM that will host the engine
|
||||
* @param discardOutput specifies if the process output should be discarded or
|
||||
* printed to parent's streams
|
||||
* @param deferredLoggingServiceEndpoint a future that is completed once the
|
||||
* logging service has been fully set-up;
|
||||
* if the child component should connect
|
||||
* to the logging service, it should
|
||||
* contain the Uri to connect to
|
||||
*/
|
||||
case class LanguageServerDescriptor(
|
||||
name: String,
|
||||
@ -29,5 +37,6 @@ case class LanguageServerDescriptor(
|
||||
distributionConfiguration: DistributionConfiguration,
|
||||
engineVersion: SemVer,
|
||||
jvmSettings: JVMSettings,
|
||||
discardOutput: Boolean
|
||||
discardOutput: Boolean,
|
||||
deferredLoggingServiceEndpoint: Future[Option[Uri]]
|
||||
)
|
||||
|
@ -19,6 +19,7 @@ import org.enso.projectmanager.infrastructure.languageserver.LanguageServerProto
|
||||
StopServer
|
||||
}
|
||||
import org.enso.projectmanager.infrastructure.languageserver.LanguageServerRegistry.ServerShutDown
|
||||
import org.enso.projectmanager.service.LoggingServiceDescriptor
|
||||
import org.enso.projectmanager.util.UnhandledLogging
|
||||
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||
|
||||
@ -31,6 +32,7 @@ import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||
* @param supervisionConfig a supervision config
|
||||
* @param timeoutConfig a timeout config
|
||||
* @param distributionConfiguration configuration of the distribution
|
||||
* @param loggingServiceDescriptor a logging service configuration descriptor
|
||||
* @param executor an executor service used to start the language server
|
||||
* process
|
||||
*/
|
||||
@ -40,6 +42,7 @@ class LanguageServerRegistry(
|
||||
supervisionConfig: SupervisionConfig,
|
||||
timeoutConfig: TimeoutConfig,
|
||||
distributionConfiguration: DistributionConfiguration,
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor,
|
||||
executor: LanguageServerExecutor
|
||||
) extends Actor
|
||||
with ActorLogging
|
||||
@ -65,6 +68,7 @@ class LanguageServerRegistry(
|
||||
supervisionConfig,
|
||||
timeoutConfig,
|
||||
distributionConfiguration,
|
||||
loggingServiceDescriptor,
|
||||
executor
|
||||
),
|
||||
s"language-server-controller-${project.id}"
|
||||
@ -129,6 +133,7 @@ object LanguageServerRegistry {
|
||||
* @param distributionConfiguration configuration of the distribution
|
||||
* @param executor an executor service used to start the language server
|
||||
* process
|
||||
* @param loggingServiceDescriptor a logging service configuration descriptor
|
||||
* @return a configuration object
|
||||
*/
|
||||
def props(
|
||||
@ -137,6 +142,7 @@ object LanguageServerRegistry {
|
||||
supervisionConfig: SupervisionConfig,
|
||||
timeoutConfig: TimeoutConfig,
|
||||
distributionConfiguration: DistributionConfiguration,
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor,
|
||||
executor: LanguageServerExecutor
|
||||
): Props =
|
||||
Props(
|
||||
@ -146,6 +152,7 @@ object LanguageServerRegistry {
|
||||
supervisionConfig,
|
||||
timeoutConfig,
|
||||
distributionConfiguration,
|
||||
loggingServiceDescriptor,
|
||||
executor
|
||||
)
|
||||
)
|
||||
|
@ -13,9 +13,12 @@ import org.enso.projectmanager.event.ClientEvent.{
|
||||
}
|
||||
import org.enso.projectmanager.protocol.ProjectManagementApi._
|
||||
import org.enso.projectmanager.requesthandler._
|
||||
import org.enso.projectmanager.service.ProjectServiceApi
|
||||
import org.enso.projectmanager.service.config.GlobalConfigServiceApi
|
||||
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagementServiceApi
|
||||
import org.enso.projectmanager.service.{
|
||||
LoggingServiceDescriptor,
|
||||
ProjectServiceApi
|
||||
}
|
||||
import org.enso.projectmanager.util.UnhandledLogging
|
||||
|
||||
import scala.annotation.unused
|
||||
@ -28,6 +31,7 @@ import scala.concurrent.duration._
|
||||
* @param projectService a project service
|
||||
* @param globalConfigService global configuration service
|
||||
* @param runtimeVersionManagementService version management service
|
||||
* @param loggingServiceDescriptor a logging service configuration descriptor
|
||||
* @param timeoutConfig a request timeout config
|
||||
*/
|
||||
class ClientController[F[+_, +_]: Exec: CovariantFlatMap: ErrorChannel](
|
||||
@ -35,6 +39,7 @@ class ClientController[F[+_, +_]: Exec: CovariantFlatMap: ErrorChannel](
|
||||
projectService: ProjectServiceApi[F],
|
||||
globalConfigService: GlobalConfigServiceApi[F],
|
||||
runtimeVersionManagementService: RuntimeVersionManagementServiceApi[F],
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor,
|
||||
timeoutConfig: TimeoutConfig
|
||||
) extends Actor
|
||||
with ActorLogging
|
||||
@ -87,7 +92,10 @@ class ClientController[F[+_, +_]: Exec: CovariantFlatMap: ErrorChannel](
|
||||
.props(globalConfigService, timeoutConfig.requestTimeout),
|
||||
ConfigDelete -> ConfigDeleteHandler
|
||||
.props(globalConfigService, timeoutConfig.requestTimeout),
|
||||
LoggingServiceGetEndpoint -> NotImplementedHandler.props
|
||||
LoggingServiceGetEndpoint -> LoggingServiceEndpointRequestHandler.props(
|
||||
loggingServiceDescriptor,
|
||||
timeoutConfig.requestTimeout
|
||||
)
|
||||
)
|
||||
|
||||
override def receive: Receive = {
|
||||
@ -124,6 +132,7 @@ object ClientController {
|
||||
* @param globalConfigService global configuration service
|
||||
* @param runtimeVersionManagementService version management service
|
||||
* @param timeoutConfig a request timeout config
|
||||
* @param loggingServiceDescriptor a logging service configuration descriptor
|
||||
* @return a configuration object
|
||||
*/
|
||||
def props[F[+_, +_]: Exec: CovariantFlatMap: ErrorChannel](
|
||||
@ -131,6 +140,7 @@ object ClientController {
|
||||
projectService: ProjectServiceApi[F],
|
||||
globalConfigService: GlobalConfigServiceApi[F],
|
||||
runtimeVersionManagementService: RuntimeVersionManagementServiceApi[F],
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor,
|
||||
timeoutConfig: TimeoutConfig
|
||||
): Props =
|
||||
Props(
|
||||
@ -139,6 +149,7 @@ object ClientController {
|
||||
projectService = projectService,
|
||||
globalConfigService = globalConfigService,
|
||||
runtimeVersionManagementService = runtimeVersionManagementService,
|
||||
loggingServiceDescriptor = loggingServiceDescriptor,
|
||||
timeoutConfig = timeoutConfig
|
||||
)
|
||||
)
|
||||
|
@ -7,9 +7,12 @@ import org.enso.jsonrpc.ClientControllerFactory
|
||||
import org.enso.projectmanager.boot.configuration.TimeoutConfig
|
||||
import org.enso.projectmanager.control.core.CovariantFlatMap
|
||||
import org.enso.projectmanager.control.effect.{ErrorChannel, Exec}
|
||||
import org.enso.projectmanager.service.ProjectServiceApi
|
||||
import org.enso.projectmanager.service.config.GlobalConfigServiceApi
|
||||
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagementServiceApi
|
||||
import org.enso.projectmanager.service.{
|
||||
LoggingServiceDescriptor,
|
||||
ProjectServiceApi
|
||||
}
|
||||
|
||||
/** Project manager client controller factory.
|
||||
*
|
||||
@ -17,6 +20,7 @@ import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagemen
|
||||
* @param projectService a project service
|
||||
* @param globalConfigService global configuration service
|
||||
* @param runtimeVersionManagementService version management service
|
||||
* @param loggingServiceDescriptor a logging service configuration descriptor
|
||||
* @param timeoutConfig a request timeout config
|
||||
*/
|
||||
class ManagerClientControllerFactory[
|
||||
@ -26,6 +30,7 @@ class ManagerClientControllerFactory[
|
||||
projectService: ProjectServiceApi[F],
|
||||
globalConfigService: GlobalConfigServiceApi[F],
|
||||
runtimeVersionManagementService: RuntimeVersionManagementServiceApi[F],
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor,
|
||||
timeoutConfig: TimeoutConfig
|
||||
) extends ClientControllerFactory {
|
||||
|
||||
@ -42,6 +47,7 @@ class ManagerClientControllerFactory[
|
||||
projectService,
|
||||
globalConfigService,
|
||||
runtimeVersionManagementService,
|
||||
loggingServiceDescriptor,
|
||||
timeoutConfig
|
||||
),
|
||||
s"jsonrpc-connection-controller-$clientId"
|
||||
|
@ -0,0 +1,111 @@
|
||||
package org.enso.projectmanager.requesthandler
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props, Status}
|
||||
import akka.http.scaladsl.model.Uri
|
||||
import akka.pattern.pipe
|
||||
import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult}
|
||||
import org.enso.projectmanager.protocol.ProjectManagementApi.{
|
||||
LoggingServiceGetEndpoint,
|
||||
LoggingServiceUnavailable
|
||||
}
|
||||
import org.enso.projectmanager.service.LoggingServiceDescriptor
|
||||
import org.enso.projectmanager.util.UnhandledLogging
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
/** A request handler for `logging-service/get-endpoint` commands.
|
||||
*
|
||||
* @param loggingServiceDescriptor a logging service configuration descriptor
|
||||
* @param requestTimeout timeout for the request
|
||||
*/
|
||||
class LoggingServiceEndpointRequestHandler(
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor,
|
||||
requestTimeout: FiniteDuration
|
||||
) extends Actor
|
||||
with ActorLogging
|
||||
with UnhandledLogging {
|
||||
|
||||
private def method = LoggingServiceGetEndpoint
|
||||
|
||||
import context.dispatcher
|
||||
|
||||
override def receive: Receive = requestStage
|
||||
|
||||
private def requestStage: Receive = {
|
||||
case Request(LoggingServiceGetEndpoint, id, _) =>
|
||||
loggingServiceDescriptor.getEndpoint
|
||||
.map(LoggingServiceInitialized)
|
||||
.pipeTo(self)
|
||||
|
||||
val timeoutCancellable = context.system.scheduler.scheduleOnce(
|
||||
requestTimeout,
|
||||
self,
|
||||
RequestTimeout
|
||||
)
|
||||
context.become(responseStage(id, sender(), timeoutCancellable))
|
||||
}
|
||||
|
||||
private def responseStage(
|
||||
id: Id,
|
||||
replyTo: ActorRef,
|
||||
timeoutCancellable: Cancellable
|
||||
): Receive = {
|
||||
case Status.Failure(ex) =>
|
||||
log.error(ex, s"Failure during $method operation:")
|
||||
replyTo ! ResponseError(
|
||||
Some(id),
|
||||
LoggingServiceUnavailable(s"Logging service failed to set up: $ex")
|
||||
)
|
||||
timeoutCancellable.cancel()
|
||||
context.stop(self)
|
||||
|
||||
case RequestTimeout =>
|
||||
log.error(s"Request $method with $id timed out")
|
||||
replyTo ! ResponseError(
|
||||
Some(id),
|
||||
LoggingServiceUnavailable(
|
||||
"Logging service has not been set up within the timeout."
|
||||
)
|
||||
)
|
||||
context.stop(self)
|
||||
|
||||
case LoggingServiceInitialized(maybeUri) =>
|
||||
maybeUri match {
|
||||
case Some(uri) =>
|
||||
replyTo ! ResponseResult(
|
||||
LoggingServiceGetEndpoint,
|
||||
id,
|
||||
LoggingServiceGetEndpoint.Result(uri.toString)
|
||||
)
|
||||
case None =>
|
||||
replyTo ! ResponseError(
|
||||
Some(id),
|
||||
LoggingServiceUnavailable("Logging service is not available.")
|
||||
)
|
||||
}
|
||||
timeoutCancellable.cancel()
|
||||
context.stop(self)
|
||||
}
|
||||
|
||||
private case class LoggingServiceInitialized(endpoint: Option[Uri])
|
||||
}
|
||||
|
||||
object LoggingServiceEndpointRequestHandler {
|
||||
|
||||
/** Creates a configuration object used to create a
|
||||
* [[LoggingServiceEndpointRequestHandler]].
|
||||
*
|
||||
* @param loggingServiceDescriptor a logging service configuration descriptor
|
||||
* @param requestTimeout timeout for the request
|
||||
* @return a configuration object
|
||||
*/
|
||||
def props(
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor,
|
||||
requestTimeout: FiniteDuration
|
||||
): Props = Props(
|
||||
new LoggingServiceEndpointRequestHandler(
|
||||
loggingServiceDescriptor,
|
||||
requestTimeout
|
||||
)
|
||||
)
|
||||
}
|
@ -2,6 +2,7 @@ package org.enso.projectmanager.requesthandler
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Stash, Status}
|
||||
import akka.pattern.pipe
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import org.enso.jsonrpc.Errors.ServiceError
|
||||
import org.enso.jsonrpc.{
|
||||
HasParams,
|
||||
@ -100,7 +101,7 @@ abstract class RequestHandler[
|
||||
context.stop(self)
|
||||
|
||||
case Left(failure: FailureType) =>
|
||||
log.error(s"Request $id failed due to $failure")
|
||||
log.error(s"Request $method with $id failed due to $failure")
|
||||
val error = implicitly[FailureMapper[FailureType]].mapFailure(failure)
|
||||
replyTo ! ResponseError(Some(id), error)
|
||||
timeoutCancellable.foreach(_.cancel())
|
||||
@ -130,7 +131,13 @@ abstract class RequestHandler[
|
||||
replyTo: ActorRef,
|
||||
timeoutCancellable: Option[Cancellable]
|
||||
): Unit = {
|
||||
timeoutCancellable.foreach(_.cancel())
|
||||
timeoutCancellable.foreach { cancellable =>
|
||||
cancellable.cancel()
|
||||
Logger[this.type].trace(
|
||||
s"The operation $method ($id) reported starting a long-running task, " +
|
||||
s"its request-timeout has been cancelled."
|
||||
)
|
||||
}
|
||||
context.become(responseStage(id, replyTo, None))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package org.enso.projectmanager.service
|
||||
|
||||
import akka.http.scaladsl.model.Uri
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
/** A service descriptor that provides information on the logging service setup.
|
||||
*/
|
||||
trait LoggingServiceDescriptor {
|
||||
|
||||
/** Returns a future that will yield the logging service endpoint once it is
|
||||
* initialized or None if the logging service does not expect incoming
|
||||
* connections.
|
||||
*/
|
||||
def getEndpoint: Future[Option[Uri]]
|
||||
}
|
@ -14,15 +14,14 @@ import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFa
|
||||
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||
import org.enso.runtimeversionmanager.runner.Runner
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
/** A service for creating new project structures using the runner of the
|
||||
* specific engine version selected for the project.
|
||||
*/
|
||||
class ProjectCreationService[
|
||||
F[+_, +_]: Sync: ErrorChannel: CovariantFlatMap
|
||||
](
|
||||
distributionConfiguration: DistributionConfiguration
|
||||
distributionConfiguration: DistributionConfiguration,
|
||||
loggingServiceDescriptor: LoggingServiceDescriptor
|
||||
) extends ProjectCreationServiceApi[F] {
|
||||
|
||||
/** @inheritdoc */
|
||||
@ -41,7 +40,7 @@ class ProjectCreationService[
|
||||
new Runner(
|
||||
versionManager,
|
||||
distributionConfiguration.environment,
|
||||
Future.successful(None)
|
||||
loggingServiceDescriptor.getEndpoint
|
||||
)
|
||||
|
||||
val settings =
|
||||
|
@ -55,8 +55,6 @@ trait DistributionConfiguration {
|
||||
* or piped to parent's streams.
|
||||
*
|
||||
* This option is used to easily turn off logging in tests.
|
||||
*
|
||||
* TODO [RW] It will likely become obsolete once #1151 (or #1144) is done.
|
||||
*/
|
||||
def shouldDiscardChildOutput: Boolean
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
test-log-level=warning
|
@ -40,7 +40,7 @@ import org.enso.projectmanager.service.{
|
||||
}
|
||||
import org.enso.projectmanager.test.{ObservableGenerator, ProgrammableClock}
|
||||
import org.enso.runtimeversionmanager.OS
|
||||
import org.enso.runtimeversionmanager.test.{DropLogs, FakeReleases}
|
||||
import org.enso.runtimeversionmanager.test.FakeReleases
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import pureconfig.ConfigSource
|
||||
import pureconfig.generic.auto._
|
||||
@ -50,10 +50,7 @@ import zio.{Runtime, Semaphore, ZEnv, ZIO}
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{Await, Future}
|
||||
|
||||
class BaseServerSpec
|
||||
extends JsonRpcServerTestKit
|
||||
with DropLogs
|
||||
with BeforeAndAfterAll {
|
||||
class BaseServerSpec extends JsonRpcServerTestKit with BeforeAndAfterAll {
|
||||
|
||||
override def protocol: Protocol = JsonRpc.protocol
|
||||
|
||||
@ -121,6 +118,8 @@ class BaseServerSpec
|
||||
discardChildOutput = !debugChildLogs
|
||||
)
|
||||
|
||||
val loggingService = new TestLoggingService
|
||||
|
||||
lazy val languageServerRegistry =
|
||||
system.actorOf(
|
||||
LanguageServerRegistry
|
||||
@ -130,6 +129,7 @@ class BaseServerSpec
|
||||
supervisionConfig,
|
||||
timeoutConfig,
|
||||
distributionConfiguration,
|
||||
loggingService,
|
||||
ExecutorWithUnlimitedPool
|
||||
)
|
||||
)
|
||||
@ -146,7 +146,10 @@ class BaseServerSpec
|
||||
)
|
||||
|
||||
lazy val projectCreationService =
|
||||
new ProjectCreationService[ZIO[ZEnv, +*, +*]](distributionConfiguration)
|
||||
new ProjectCreationService[ZIO[ZEnv, +*, +*]](
|
||||
distributionConfiguration,
|
||||
loggingService
|
||||
)
|
||||
|
||||
lazy val globalConfigService = new GlobalConfigService[ZIO[ZEnv, +*, +*]](
|
||||
distributionConfiguration
|
||||
@ -176,6 +179,7 @@ class BaseServerSpec
|
||||
projectService = projectService,
|
||||
globalConfigService = globalConfigService,
|
||||
runtimeVersionManagementService = runtimeVersionManagementService,
|
||||
loggingServiceDescriptor = loggingService,
|
||||
timeoutConfig = timeoutConfig
|
||||
)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ trait ProjectManagementOps { this: BaseServerSpec =>
|
||||
}
|
||||
}
|
||||
""")
|
||||
val Right(openReply) = parse(client.expectMessage(10.seconds.dilated))
|
||||
val Right(openReply) = parse(client.expectMessage(20.seconds.dilated))
|
||||
val socket = for {
|
||||
result <- openReply.hcursor.downExpectedField("result")
|
||||
addr <- result.downExpectedField("languageServerJsonAddress")
|
||||
|
@ -0,0 +1,24 @@
|
||||
package org.enso.projectmanager
|
||||
|
||||
import akka.http.scaladsl.model.Uri
|
||||
import org.enso.projectmanager.service.LoggingServiceDescriptor
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
class TestLoggingService extends LoggingServiceDescriptor {
|
||||
private var currentFuture: Future[Option[Uri]] = Future.successful(None)
|
||||
|
||||
override def getEndpoint: Future[Option[Uri]] = currentFuture
|
||||
|
||||
def withOverriddenEndpoint[R](
|
||||
future: Future[Option[Uri]]
|
||||
)(action: => R): Unit = {
|
||||
val oldValue = currentFuture
|
||||
currentFuture = future
|
||||
try {
|
||||
action
|
||||
} finally {
|
||||
currentFuture = oldValue
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package org.enso.projectmanager.protocol
|
||||
|
||||
import akka.http.scaladsl.model.Uri
|
||||
import io.circe.literal.JsonStringContext
|
||||
import org.enso.projectmanager.BaseServerSpec
|
||||
import org.enso.testkit.FlakySpec
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
class LoggingServiceEndpointSpec extends BaseServerSpec with FlakySpec {
|
||||
|
||||
class TestException extends RuntimeException {
|
||||
override def toString: String = "test-exception"
|
||||
}
|
||||
|
||||
"logging-service/get-endpoint" should {
|
||||
"fail if endpoint is not setup" in {
|
||||
implicit val client = new WsTestClient(address)
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "logging-service/get-endpoint",
|
||||
"id": 0
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{
|
||||
"jsonrpc":"2.0",
|
||||
"id":0,
|
||||
"error": { "code": 4013, "message": "Logging service is not available." }
|
||||
}
|
||||
""")
|
||||
}
|
||||
|
||||
"return the endpoint if it has been set-up" in {
|
||||
implicit val client = new WsTestClient(address)
|
||||
loggingService.withOverriddenEndpoint(
|
||||
Future.successful(Some(Uri("ws://test-uri/")))
|
||||
) {
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "logging-service/get-endpoint",
|
||||
"id": 0
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{
|
||||
"jsonrpc":"2.0",
|
||||
"id":0,
|
||||
"result" : {
|
||||
"uri": "ws://test-uri/"
|
||||
}
|
||||
}
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
"report logging service setup failures" in {
|
||||
implicit val client = new WsTestClient(address)
|
||||
loggingService.withOverriddenEndpoint(Future.failed(new TestException)) {
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "logging-service/get-endpoint",
|
||||
"id": 0
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{
|
||||
"jsonrpc":"2.0",
|
||||
"id":0,
|
||||
"error": { "code": 4013, "message": "Logging service failed to set up: test-exception" }
|
||||
}
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package org.enso.runtimeversionmanager.test
|
||||
|
||||
import org.enso.loggingservice.TestLogger
|
||||
import org.scalatest.{BeforeAndAfterAll, Suite}
|
||||
|
||||
/** Ensures that any pending logs that were not processed when executing tests
|
||||
* are ignored instead of being dumped to stderr.
|
||||
*
|
||||
* This does not affect tests that set up the loggings service themselves or
|
||||
* capture the logs. It only affects logs that were not handled at all.
|
||||
*/
|
||||
trait DropLogs extends BeforeAndAfterAll { self: Suite =>
|
||||
override def afterAll(): Unit = {
|
||||
super.afterAll()
|
||||
TestLogger.dropLogs()
|
||||
}
|
||||
}
|
@ -24,8 +24,7 @@ class RuntimeVersionManagerTest
|
||||
with Matchers
|
||||
with OptionValues
|
||||
with WithTemporaryDirectory
|
||||
with FakeEnvironment
|
||||
with DropLogs {
|
||||
with FakeEnvironment {
|
||||
|
||||
/** Creates the [[DistributionManager]], [[RuntimeVersionManager]] and an
|
||||
* [[Environment]] for use in the tests.
|
||||
|
@ -0,0 +1 @@
|
||||
test-log-level=error
|
@ -4,7 +4,6 @@ import io.circe.Json
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.runtimeversionmanager.distribution.DistributionManager
|
||||
import org.enso.runtimeversionmanager.test.{
|
||||
DropLogs,
|
||||
FakeEnvironment,
|
||||
WithTemporaryDirectory
|
||||
}
|
||||
@ -17,8 +16,7 @@ class GlobalConfigurationManagerSpec
|
||||
with Matchers
|
||||
with WithTemporaryDirectory
|
||||
with FakeEnvironment
|
||||
with OptionValues
|
||||
with DropLogs {
|
||||
with OptionValues {
|
||||
def makeConfigManager(): GlobalConfigurationManager = {
|
||||
val env = fakeInstalledEnvironment()
|
||||
val distributionManager = new DistributionManager(env)
|
||||
|
@ -9,7 +9,6 @@ import org.enso.runtimeversionmanager.distribution.{
|
||||
PortableDistributionManager
|
||||
}
|
||||
import org.enso.runtimeversionmanager.test.{
|
||||
DropLogs,
|
||||
FakeEnvironment,
|
||||
WithTemporaryDirectory
|
||||
}
|
||||
@ -20,8 +19,7 @@ class DistributionManagerSpec
|
||||
extends AnyWordSpec
|
||||
with Matchers
|
||||
with WithTemporaryDirectory
|
||||
with FakeEnvironment
|
||||
with DropLogs {
|
||||
with FakeEnvironment {
|
||||
|
||||
"DistributionManager" should {
|
||||
"detect portable distribution" in {
|
||||
|
@ -22,7 +22,7 @@ import org.enso.runtimeversionmanager.releases.engine.{
|
||||
import org.enso.runtimeversionmanager.releases.graalvm.GraalCEReleaseProvider
|
||||
import org.enso.runtimeversionmanager.releases.testing.FakeReleaseProvider
|
||||
import org.enso.runtimeversionmanager.test._
|
||||
import org.enso.testkit.RetrySpec
|
||||
import org.enso.testkit.{FlakySpec, RetrySpec}
|
||||
import org.scalatest.BeforeAndAfterEach
|
||||
import org.scalatest.concurrent.TimeLimitedTests
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
@ -38,9 +38,9 @@ class ConcurrencyTest
|
||||
with WithTemporaryDirectory
|
||||
with FakeEnvironment
|
||||
with BeforeAndAfterEach
|
||||
with DropLogs
|
||||
with TimeLimitedTests
|
||||
with RetrySpec {
|
||||
with RetrySpec
|
||||
with FlakySpec {
|
||||
|
||||
/** This is an upper bound to avoid stalling the tests forever, but particular
|
||||
* operations have smaller timeouts usually.
|
||||
@ -170,7 +170,8 @@ class ConcurrencyTest
|
||||
)._2
|
||||
|
||||
"locks" should {
|
||||
"synchronize parallel installations with the same runtime" taggedAs Retry in {
|
||||
"synchronize parallel installations " +
|
||||
"with the same runtime".taggedAs(Flaky, Retry) in {
|
||||
|
||||
/** Two threads start installing different engines in parallel, but these
|
||||
* engines use the same runtime. The second thread is stalled on
|
||||
@ -246,7 +247,7 @@ class ConcurrencyTest
|
||||
)
|
||||
}
|
||||
|
||||
"synchronize installation and usage" taggedAs Retry in {
|
||||
"synchronize installation and usage".taggedAs(Flaky, Retry) in {
|
||||
|
||||
/** The first thread starts installing the engine, but is suspended when
|
||||
* downloading the package. The second thread then tries to use it, but
|
||||
@ -304,7 +305,7 @@ class ConcurrencyTest
|
||||
)
|
||||
}
|
||||
|
||||
"synchronize uninstallation and usage" taggedAs Retry in {
|
||||
"synchronize uninstallation and usage".taggedAs(Flaky, Retry) in {
|
||||
|
||||
/** The first thread starts using the engine, while in the meantime
|
||||
* another thread starts uninstalling it. The second thread has to wait
|
||||
|
@ -271,6 +271,7 @@ class RuntimeVersionManager(
|
||||
def listInstalledGraalRuntimes(): Seq[GraalRuntime] =
|
||||
FileSystem
|
||||
.listDirectory(distributionManager.paths.runtimes)
|
||||
.filter(isNotIgnoredDirectory)
|
||||
.map(path => (path, loadGraalRuntime(path)))
|
||||
.flatMap(handleErrorsAsWarnings[GraalRuntime]("A runtime"))
|
||||
|
||||
@ -278,10 +279,18 @@ class RuntimeVersionManager(
|
||||
def listInstalledEngines(): Seq[Engine] = {
|
||||
FileSystem
|
||||
.listDirectory(distributionManager.paths.engines)
|
||||
.filter(isNotIgnoredDirectory)
|
||||
.map(path => (path, loadEngine(path)))
|
||||
.flatMap(handleErrorsAsWarnings[Engine]("An engine"))
|
||||
}
|
||||
|
||||
private def isNotIgnoredDirectory(path: Path): Boolean = {
|
||||
val ignoreList = Seq(".DS_Store")
|
||||
val fileName = path.getFileName.toString
|
||||
val isIgnored = ignoreList.contains(fileName)
|
||||
!isIgnored
|
||||
}
|
||||
|
||||
/** A helper function that is used when listing components.
|
||||
*
|
||||
* A component error is non-fatal in context of listing, so it is issued as a
|
||||
|
@ -157,7 +157,8 @@ class DistributionManager(val env: Environment) {
|
||||
TMP_DIRECTORY,
|
||||
LOG_DIRECTORY,
|
||||
LOCK_DIRECTORY,
|
||||
"components-licences"
|
||||
"THIRD-PARTY",
|
||||
".DS_Store"
|
||||
)
|
||||
|
||||
/** Config directory for an installed distribution.
|
||||
|
@ -99,14 +99,12 @@ class Runner(
|
||||
"--data-port",
|
||||
options.dataPort.toString,
|
||||
"--log-level",
|
||||
logLevel.toString
|
||||
logLevel.name
|
||||
)
|
||||
RunSettings(
|
||||
version,
|
||||
arguments ++ additionalArguments,
|
||||
// TODO [RW] set to true when language server gets logging support
|
||||
// (#1144)
|
||||
connectLoggerIfAvailable = false
|
||||
connectLoggerIfAvailable = true
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user