Skip to content

Commit

Permalink
add log backup params as server properties + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek authored and tgodzik committed Jul 11, 2023
1 parent 3342ff1 commit 8feeabb
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import scala.meta.pc.PresentationCompilerConfig.OverrideDefFormat
* supports a small subset of this, so it may be problematic
* for certain clients.
* @param macOsMaxWatchRoots The maximum number of root directories to watch on MacOS.
* @param maxLogFileSize The maximum size of the log file before it gets backed up and truncated.
* @param maxLogBackups The maximum number of backup log files.
*/
final case class MetalsServerConfig(
globSyntax: GlobSyntaxConfig = GlobSyntaxConfig.default,
Expand Down Expand Up @@ -92,6 +94,14 @@ final case class MetalsServerConfig(
.getOrElse(32),
loglevel: String =
sys.props.get("metals.loglevel").map(_.toLowerCase()).getOrElse("info"),
maxLogFileSize: Long = Option(System.getProperty("metals.max-logfile-size"))
.withFilter(_.forall(Character.isDigit(_)))
.map(_.toLong)
.getOrElse(3 << 20),
maxLogBackups: Int = Option(System.getProperty("metals.max-log-backups"))
.withFilter(_.forall(Character.isDigit(_)))
.map(_.toInt)
.getOrElse(10),
) {
override def toString: String =
List[String](
Expand All @@ -110,6 +120,8 @@ final case class MetalsServerConfig(
s"bloop-port=${bloopPort.map(_.toString()).getOrElse("default")}",
s"macos-max-watch-roots=${macOsMaxWatchRoots}",
s"loglevel=${loglevel}",
s"max-logfile-size=${maxLogFileSize}",
s"max-log-backup=${maxLogBackups}",
).mkString("MetalsServerConfig(\n ", ",\n ", "\n)")
}
object MetalsServerConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class WorkspaceFolders(
onInitialize: MetalsLspService => Future[Unit],
shoutdownMetals: () => Future[Unit],
redirectSystemOut: Boolean,
initialServerConfig: MetalsServerConfig,
)(implicit ec: ExecutionContext) {

private val folderServices: AtomicReference[List[MetalsLspService]] =
Expand Down Expand Up @@ -50,6 +51,7 @@ class WorkspaceFolders(
MetalsLogger.setupLspLogger(
folderServices.get().map(_.folder),
redirectSystemOut,
initialServerConfig,
)
for {
_ <- Future.sequence(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class WorkspaceLspService(
_.initialized(),
() => shutdown().asScala,
redirectSystemOut,
serverInputs.initialServerConfig,
)

def folderServices = workspaceFolders.getFolderServices
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,9 @@ object MetalsLogger {
def setupLspLogger(
folders: List[AbsolutePath],
redirectSystemStreams: Boolean,
config: MetalsServerConfig,
): Unit = {
val newLogFiles = folders.map(backUpOldLogFileIfTooBig)
val newLogFiles = folders.map(backUpOldLogFileIfTooBig(_, config))
scribe.info(s"logging to files ${newLogFiles.mkString(",")}")
if (redirectSystemStreams) {
redirectSystemOut(newLogFiles)
Expand All @@ -127,38 +128,41 @@ object MetalsLogger {
private def backupLogsDir(workspaceFolder: AbsolutePath) =
workspaceFolder.resolve(".metals").resolve(".backup_logs")

private def backUpOldLogFileIfTooBig(
workspaceFolder: AbsolutePath
def backUpOldLogFileIfTooBig(
workspaceFolder: AbsolutePath,
config: MetalsServerConfig,
): AbsolutePath = {
val logFilePath = workspaceFolder.resolve(workspaceLogPath)
val MAX_SIZE = 3 << 20
if (logFilePath.isFile && Files.size(logFilePath.toNIO) > MAX_SIZE) {
val backedUpLogFile = backupLogsDir(workspaceFolder).resolve(
s"log_${System.currentTimeMillis()}"
)
backedUpLogFile.parent.createDirectories()
try {
try {
if (
logFilePath.isFile && Files.size(
logFilePath.toNIO
) > config.maxLogFileSize
) {
val backedUpLogFile = backupLogsDir(workspaceFolder).resolve(
s"log_${System.currentTimeMillis()}"
)
backedUpLogFile.parent.createDirectories()
Files.move(
logFilePath.toNIO,
backedUpLogFile.toNIO,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.ATOMIC_MOVE,
)
limitNumberOfKeptBackupLogs(workspaceFolder)
} catch {
case NonFatal(t) =>
scribe.warn(s"""|error while moving file: $logFilePath
|to: $backedUpLogFile
|$t
|""".stripMargin)
limitKeptBackupLogs(workspaceFolder, config.maxLogBackups)
}
} catch {
case NonFatal(t) =>
scribe.warn(s"""|error while creating a backup for log file
|$t
|""".stripMargin)
}
logFilePath
}

private def limitNumberOfKeptBackupLogs(workspaceFolder: AbsolutePath) = {
private def limitKeptBackupLogs(workspaceFolder: AbsolutePath, limit: Int) = {
val backupDir = backupLogsDir(workspaceFolder)
new LimitedFilesManager(backupDir.toNIO, fileLimit = 10, "log_").deleteOld()
new LimitedFilesManager(backupDir.toNIO, limit, "log_").deleteOld()
}

def newFileWriter(logfile: AbsolutePath): FileWriter =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,11 @@ class MetalsLanguageServer(
val folderPaths = folders.map(_.path)

setupJna()
MetalsLogger.setupLspLogger(folderPaths, redirectSystemOut)
MetalsLogger.setupLspLogger(
folderPaths,
redirectSystemOut,
serverInputs.initialServerConfig,
)

val clientInfo = Option(params.getClientInfo()).fold("") { info =>
s"for client ${info.getName()} ${Option(info.getVersion).getOrElse("")}"
Expand Down
93 changes: 93 additions & 0 deletions tests/unit/src/test/scala/tests/LogBackupSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package tests

import java.nio.file.Files

import scala.util.control.NonFatal

import scala.meta.internal.io.PathIO
import scala.meta.internal.metals.Directories
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.MetalsServerConfig
import scala.meta.internal.metals.RecursivelyDelete
import scala.meta.internal.metals.logging.MetalsLogger
import scala.meta.internal.metals.utils.LimitedFilesManager
import scala.meta.io.AbsolutePath

class LogBackupSuite extends BaseSuite {
val maxLogBackups = 2
val serverConfig: MetalsServerConfig =
MetalsServerConfig.default.copy(maxLogFileSize = 100, maxLogBackups = 2)
var workspace: AbsolutePath = _
def log: AbsolutePath = workspace.resolve(Directories.log)
def backupDir: AbsolutePath =
workspace.resolve(".metals").resolve(".backup_logs")
def limitedFilesManager = new LimitedFilesManager(
backupDir.toNIO,
maxLogBackups,
"log_",
)

override def beforeEach(context: BeforeEach): Unit = {
workspace = createWorkspace(context.test.name)
cleanWorkspace()
}

test("too-small-for-backup") {
log.writeText(".")
MetalsLogger.backUpOldLogFileIfTooBig(workspace, serverConfig)
assert(!backupDir.exists)
}

test("backup") {
log.writeText(List.range(1, 100).mkString)
MetalsLogger.backUpOldLogFileIfTooBig(workspace, serverConfig)
assert(backupDir.exists)
assert(limitedFilesManager.getAllFiles().size == 1)
assert(!log.exists)
}

test("backup-and-delete-overflow") {
def createBackupLog(text: String) =
backupDir.resolve(s"log_${System.currentTimeMillis()}").writeText(text)
val logdata = List.range(1, 100).mkString
log.writeText(logdata)
createBackupLog("1")
Thread.sleep(2)
createBackupLog("2")
Thread.sleep(2)
MetalsLogger.backUpOldLogFileIfTooBig(workspace, serverConfig)
assertNoDiff(
limitedFilesManager
.getAllFiles()
.sortBy(_.timestamp)
.flatMap(file => Files.readAllLines(file.toPath).asScala)
.mkString("\n"),
s"""|2
|${logdata}""".stripMargin,
)
assert(!log.exists)
}

def cleanWorkspace(): Unit =
if (workspace.isDirectory) {
try {
RecursivelyDelete(workspace)
Files.createDirectories(workspace.toNIO)
} catch {
case NonFatal(_) =>
scribe.warn(s"Unable to delete workspace $workspace")
}
}

protected def createWorkspace(name: String): AbsolutePath = {
val pathToSuite = PathIO.workingDirectory
.resolve("target")
.resolve("e2e")
.resolve("log-backup")

val path = pathToSuite.resolve(name)

Files.createDirectories(path.toNIO)
path
}
}

0 comments on commit 8feeabb

Please sign in to comment.