Rewrite logging and add plugin loading
This commit is contained in:
parent
299d50d129
commit
6985831b00
16 changed files with 231 additions and 66 deletions
|
@ -1,6 +1,7 @@
|
|||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("kapt")
|
||||
id("com.github.johnrengelman.shadow") version "6.1.0"
|
||||
}
|
||||
|
||||
group = rootProject.group
|
||||
|
@ -15,7 +16,6 @@ val spekVersion = properties["version.spek"].toString()
|
|||
val moshiVersion = properties["version.moshi"].toString()
|
||||
val coroutinesVersion = properties["version.kotlinx-coroutines"].toString()
|
||||
val nettyVersion = properties["version.netty"].toString()
|
||||
val slf4jVersion = properties["version.slf4j"].toString()
|
||||
val junitVersion = properties["version.junit"].toString()
|
||||
val striktVersion = properties["version.strikt"].toString()
|
||||
|
||||
|
@ -28,9 +28,6 @@ dependencies {
|
|||
kapt("com.squareup.moshi:moshi-kotlin-codegen:${moshiVersion}")
|
||||
api("com.squareup.moshi:moshi:${moshiVersion}")
|
||||
|
||||
// Logging
|
||||
api("org.slf4j:slf4j-api:${slf4jVersion}")
|
||||
|
||||
// Netty
|
||||
api("io.netty:netty-buffer:${nettyVersion}")
|
||||
|
||||
|
|
|
@ -1,36 +1,53 @@
|
|||
package space.blokk.logging
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import space.blokk.Blokk
|
||||
|
||||
class Logger(name: String) {
|
||||
private val logger: Logger = LoggerFactory.getLogger(name)
|
||||
class Logger(val name: String) {
|
||||
fun error(msg: String, t: Throwable) {
|
||||
if (Level.ERROR.isEnabled) {
|
||||
error(msg)
|
||||
t.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun error(msg: String, t: Throwable) = logger.error(msg, t)
|
||||
fun log(level: Level, message: String, throwable: Throwable? = null) =
|
||||
Blokk.server.loggingOutputProvider.log(name, level, message, throwable)
|
||||
|
||||
infix fun error(msg: String) = logger.error(msg)
|
||||
infix fun info(msg: String) = logger.info(msg)
|
||||
infix fun warn(msg: String) = logger.warn(msg)
|
||||
infix fun debug(msg: String) = logger.debug(msg)
|
||||
infix fun trace(msg: String) = logger.trace(msg)
|
||||
infix fun error(message: String) = log(Level.ERROR, message, null)
|
||||
infix fun info(message: String) = log(Level.INFO, message, null)
|
||||
infix fun warn(message: String) = log(Level.WARN, message, null)
|
||||
infix fun debug(message: String) = log(Level.DEBUG, message, null)
|
||||
infix fun trace(message: String) = log(Level.TRACE, message, null)
|
||||
|
||||
infix fun error(fn: () -> String) {
|
||||
if (logger.isErrorEnabled) logger.error(fn())
|
||||
if (Level.ERROR.isEnabled) error(fn())
|
||||
}
|
||||
|
||||
infix fun info(fn: () -> String) {
|
||||
if (logger.isInfoEnabled) logger.info(fn())
|
||||
if (Level.INFO.isEnabled) info(fn())
|
||||
}
|
||||
|
||||
infix fun warn(fn: () -> String) {
|
||||
if (logger.isWarnEnabled) logger.warn(fn())
|
||||
if (Level.WARN.isEnabled) warn(fn())
|
||||
}
|
||||
|
||||
infix fun debug(fn: () -> String) {
|
||||
if (logger.isDebugEnabled) logger.debug(fn())
|
||||
if (Level.DEBUG.isEnabled) debug(fn())
|
||||
}
|
||||
|
||||
infix fun trace(fn: () -> String) {
|
||||
if (logger.isTraceEnabled) logger.trace(fn())
|
||||
if (Level.TRACE.isEnabled) trace(fn())
|
||||
}
|
||||
|
||||
enum class Level {
|
||||
TRACE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR;
|
||||
|
||||
fun isGreaterOrEqualThan(level: Level) = ordinal >= level.ordinal
|
||||
|
||||
val isEnabled get() = isGreaterOrEqualThan(Blokk.server.minLogLevel)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package space.blokk.logging
|
||||
|
||||
interface LoggingOutputProvider {
|
||||
fun log(loggerName: String, level: Logger.Level, message: String, throwable: Throwable? = null)
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
package space.blokk.plugin
|
||||
|
||||
interface Plugin {
|
||||
fun onEnable() {}
|
||||
fun onDisable() {}
|
||||
abstract class Plugin(name: String, version: String) {
|
||||
val meta = Meta(name, version)
|
||||
|
||||
open fun onEnable() {}
|
||||
open fun onDisable() {}
|
||||
|
||||
data class Meta(val name: String, val version: String)
|
||||
|
||||
companion object {
|
||||
fun getCalling(): Plugin? {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package space.blokk.plugin
|
||||
|
||||
interface PluginManager {
|
||||
val plugins: List<Plugin>
|
||||
}
|
|
@ -5,9 +5,13 @@ import kotlinx.coroutines.channels.ReceiveChannel
|
|||
import space.blokk.DifficultyOptions
|
||||
import space.blokk.event.EventTarget
|
||||
import space.blokk.event.EventTargetGroup
|
||||
import space.blokk.logging.Logger
|
||||
import space.blokk.logging.LoggingOutputProvider
|
||||
import space.blokk.net.Session
|
||||
import space.blokk.player.Player
|
||||
import space.blokk.plugin.PluginManager
|
||||
import space.blokk.server.event.ServerEvent
|
||||
import java.io.File
|
||||
|
||||
interface Server : EventTarget<ServerEvent> {
|
||||
val scope: CoroutineScope
|
||||
|
@ -27,6 +31,12 @@ interface Server : EventTarget<ServerEvent> {
|
|||
*/
|
||||
val players: EventTargetGroup<Player>
|
||||
|
||||
val pluginManager: PluginManager
|
||||
val serverDirectory: File
|
||||
|
||||
val minLogLevel: Logger.Level
|
||||
val loggingOutputProvider: LoggingOutputProvider
|
||||
|
||||
/**
|
||||
* Creates a [ReceiveChannel] which emits [Unit] every tick.
|
||||
*
|
||||
|
|
|
@ -39,7 +39,7 @@ abstract class BlockRef {
|
|||
/**
|
||||
* Create a new [Block] and assign it to this ref (i.e. place it in the world).
|
||||
*/
|
||||
fun place(material: Material): Block = TODO()
|
||||
abstract fun place(material: Material): Block
|
||||
|
||||
/**
|
||||
* Create a new [Block] and assign it to this ref (i.e. place it in the world).
|
||||
|
|
|
@ -7,9 +7,11 @@ import kotlinx.coroutines.CoroutineName
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import space.blokk.config.BlokkConfig
|
||||
import space.blokk.event.EventBus
|
||||
import space.blokk.logging.BlokkLoggingOutputProvider
|
||||
import space.blokk.logging.Logger
|
||||
import space.blokk.net.BlokkSocketServer
|
||||
import space.blokk.player.PlayerGroup
|
||||
import space.blokk.plugin.BlokkPluginManager
|
||||
import space.blokk.server.Server
|
||||
import space.blokk.server.event.ServerEvent
|
||||
import space.blokk.util.EncryptionUtils
|
||||
|
@ -28,6 +30,7 @@ class BlokkServer internal constructor() : Server {
|
|||
override var displayedDifficultyOptions = DifficultyOptions(Difficulty.NORMAL, true)
|
||||
override val sessions by socketServer::sessions
|
||||
override val players = PlayerGroup()
|
||||
override val pluginManager = BlokkPluginManager(this)
|
||||
|
||||
val keyPair: KeyPair
|
||||
|
||||
|
@ -40,12 +43,11 @@ class BlokkServer internal constructor() : Server {
|
|||
}
|
||||
|
||||
val x509EncodedPublicKey: ByteArray = EncryptionUtils.generateX509Key(keyPair.public).encoded
|
||||
private val serverDirectory: File
|
||||
|
||||
init {
|
||||
override val serverDirectory: File = run {
|
||||
var dir = File(BlokkServer::class.java.protectionDomain.codeSource.location.toURI())
|
||||
if (VERSION == "development") dir = dir.resolve("../../../../../data").normalize().also { it.mkdirs() }
|
||||
serverDirectory = dir
|
||||
dir
|
||||
}
|
||||
|
||||
val config = ConfigLoader.Builder()
|
||||
|
@ -62,6 +64,9 @@ class BlokkServer internal constructor() : Server {
|
|||
)
|
||||
.build().loadConfigOrThrow<BlokkConfig>()
|
||||
|
||||
override val minLogLevel = config.minLogLevel
|
||||
override val loggingOutputProvider = BlokkLoggingOutputProvider
|
||||
|
||||
private val ticker = Ticker()
|
||||
override fun createTickChannel() = ticker.createTickChannel()
|
||||
|
||||
|
@ -82,6 +87,9 @@ class BlokkServer internal constructor() : Server {
|
|||
logger info "Starting BlokkServer ($VERSION_WITH_V)"
|
||||
logger trace "Configuration: $config"
|
||||
|
||||
pluginManager.loadAll()
|
||||
pluginManager.enableAll()
|
||||
|
||||
socketServer.bind()
|
||||
logger info "Listening on ${config.host}:${config.port}"
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package space.blokk.config
|
||||
|
||||
import space.blokk.logging.Logger
|
||||
|
||||
data class BlokkConfig(
|
||||
val port: Int,
|
||||
val host: String,
|
||||
val silentNonServerErrors: Boolean,
|
||||
val authenticateAndEncrypt: Boolean,
|
||||
val developmentMode: Boolean
|
||||
val developmentMode: Boolean,
|
||||
val minLogLevel: Logger.Level
|
||||
)
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package space.blokk.logging
|
||||
|
||||
import de.moritzruth.khalk.Khalk
|
||||
import java.io.PrintStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
object BlokkLoggingOutputProvider : LoggingOutputProvider {
|
||||
private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS")
|
||||
|
||||
override fun log(loggerName: String, level: Logger.Level, message: String, throwable: Throwable?) {
|
||||
val time = Date()
|
||||
|
||||
val stream: PrintStream = if (level.isGreaterOrEqualThan(Logger.Level.ERROR)) System.err else System.out
|
||||
val khalk = Khalk()
|
||||
val color = when (level) {
|
||||
Logger.Level.ERROR -> khalk.redBright
|
||||
Logger.Level.WARN -> khalk.yellowBright
|
||||
Logger.Level.INFO -> khalk.greenBright
|
||||
Logger.Level.DEBUG -> khalk.cyanBright
|
||||
Logger.Level.TRACE -> khalk.gray
|
||||
else -> khalk
|
||||
}
|
||||
|
||||
stream.println(
|
||||
arrayOf(
|
||||
khalk.gray { dateFormat.format(time) },
|
||||
color { level.toString().padEnd(5) },
|
||||
khalk.bold { Thread.currentThread().name },
|
||||
color.inverse { " $loggerName " },
|
||||
color { "❯" },
|
||||
message
|
||||
).joinToString(" ")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package space.blokk.logging
|
||||
|
||||
import ch.qos.logback.classic.Level
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent
|
||||
import ch.qos.logback.core.AppenderBase
|
||||
import de.moritzruth.khalk.Khalk
|
||||
import java.io.PrintStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class ConsoleAppender : AppenderBase<ILoggingEvent>() {
|
||||
private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS")
|
||||
|
||||
override fun append(event: ILoggingEvent) {
|
||||
val stream: PrintStream = if (event.level.isGreaterOrEqual(Level.ERROR)) System.err else System.out
|
||||
val khalk = Khalk()
|
||||
val color = when (event.level) {
|
||||
Level.ERROR -> khalk.redBright
|
||||
Level.WARN -> khalk.yellowBright
|
||||
Level.INFO -> khalk.greenBright
|
||||
Level.DEBUG -> khalk.cyanBright
|
||||
Level.TRACE -> khalk.gray
|
||||
else -> khalk
|
||||
}
|
||||
|
||||
stream.println(
|
||||
listOf(
|
||||
khalk.gray { dateFormat.format(Date(event.timeStamp)) },
|
||||
color { event.level.toString().padEnd(5) },
|
||||
khalk.bold { event.threadName },
|
||||
color.inverse { " ${event.loggerName} " },
|
||||
color { "❯" },
|
||||
event.message
|
||||
).joinToString(" ")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package space.blokk.logging
|
||||
|
||||
import ch.qos.logback.classic.Level
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent
|
||||
import ch.qos.logback.core.AppenderBase
|
||||
import space.blokk.Blokk
|
||||
|
||||
class LogbackAppender : AppenderBase<ILoggingEvent>() {
|
||||
override fun append(event: ILoggingEvent) {
|
||||
Blokk.server.loggingOutputProvider.log(
|
||||
event.loggerName,
|
||||
when (event.level) {
|
||||
Level.TRACE -> Logger.Level.TRACE
|
||||
Level.DEBUG -> Logger.Level.DEBUG
|
||||
Level.INFO -> Logger.Level.INFO
|
||||
Level.WARN -> Logger.Level.WARN
|
||||
Level.ERROR -> Logger.Level.ERROR
|
||||
else -> error("Should never happen")
|
||||
},
|
||||
event.message
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package space.blokk.plugin
|
||||
|
||||
import space.blokk.BlokkServer
|
||||
import space.blokk.logging.Logger
|
||||
import space.blokk.util.pluralize
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
|
||||
class BlokkPluginManager(private val server: BlokkServer) : PluginManager {
|
||||
private val logger = Logger("PluginManager")
|
||||
|
||||
override val plugins = mutableListOf<Plugin>()
|
||||
|
||||
fun loadAll() {
|
||||
val pluginsDir = server.serverDirectory.resolve("plugins")
|
||||
pluginsDir.mkdirs()
|
||||
val files = pluginsDir.listFiles { it ->
|
||||
it.isFile && it.extension == "jar"
|
||||
}!!
|
||||
|
||||
if (files.isEmpty()) logger warn "No plugins found"
|
||||
else logger info "Loading ${files.size} ${pluralize("plugin", files.size)}..."
|
||||
|
||||
plugins.addAll(files.mapNotNull { file ->
|
||||
var error: LoadError?
|
||||
|
||||
try {
|
||||
val loader = URLClassLoader(arrayOf(file.toURI().toURL()))
|
||||
val resource: URL? = loader.findResource(PLUGIN_CLASS_FILENAME)
|
||||
|
||||
if (resource == null) error = LoadError.NoPluginClassFile
|
||||
else {
|
||||
val pluginClassName = resource.readText().trim()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val pluginClass = loader.loadClass(pluginClassName) as Class<Plugin>
|
||||
return@mapNotNull pluginClass.newInstance()
|
||||
}
|
||||
} catch (e: ClassNotFoundException) {
|
||||
error = LoadError.PluginClassNotFound(e.message!!)
|
||||
} catch (e: ClassCastException) {
|
||||
error = LoadError.NotExtending
|
||||
} catch (e: InstantiationException) {
|
||||
error = when (e.cause) {
|
||||
is NoSuchMethodException -> LoadError.InvalidConstructor
|
||||
else -> LoadError.Unknown(e)
|
||||
}
|
||||
} catch (e: IllegalAccessException) {
|
||||
error = LoadError.InvalidConstructor
|
||||
} catch (e: Exception) {
|
||||
error = LoadError.Unknown(e)
|
||||
}
|
||||
|
||||
when {
|
||||
error is LoadError.Unknown -> logger.error("Loading ${file.name} failed", error.exception)
|
||||
error != null -> logger error "Loading ${file.name} failed: " + when (error) {
|
||||
is LoadError.NotExtending -> "The plugin class must extend space.blokk.plugin.Plugin"
|
||||
is LoadError.PluginClassNotFound -> "The plugin class '${error.name}' could not be found"
|
||||
is LoadError.InvalidConstructor -> "The plugin class must have a public parameterless constructor"
|
||||
is LoadError.NoPluginClassFile ->
|
||||
"The plugin does not contain a plugin_class.txt file specifying the plugin class"
|
||||
|
||||
else -> "" // never happens
|
||||
}
|
||||
}
|
||||
|
||||
null
|
||||
})
|
||||
|
||||
logger info "Successfully loaded ${plugins.size}/${files.size} " +
|
||||
"${pluralize("plugin", plugins.size)}: " +
|
||||
plugins.joinToString { "${it.meta.name}@${it.meta.version}" }
|
||||
}
|
||||
|
||||
sealed class LoadError {
|
||||
data class Unknown(val exception: Exception) : LoadError()
|
||||
data class PluginClassNotFound(val name: String) : LoadError()
|
||||
object InvalidConstructor : LoadError()
|
||||
object NotExtending : LoadError()
|
||||
object NoPluginClassFile : LoadError()
|
||||
}
|
||||
|
||||
fun enableAll() {
|
||||
plugins.forEach { it.onEnable() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PLUGIN_CLASS_FILENAME = "plugin_class.txt"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package space.blokk.util
|
||||
|
||||
fun pluralize(word: String, count: Int) = word + (if (count == 1) "" else "s")
|
|
@ -3,3 +3,4 @@ port: 25565
|
|||
silentNonServerErrors: true
|
||||
authenticateAndEncrypt: true
|
||||
developmentMode: false
|
||||
minLogLevel: INFO
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<configuration>
|
||||
<appender name="console" class="space.blokk.logging.ConsoleAppender"/>
|
||||
<appender name="console" class="space.blokk.logging.LogbackAppender"/>
|
||||
<root level="trace">
|
||||
<appender-ref ref="console"/>
|
||||
</root>
|
||||
<logger name="io.netty" level="WARN"/>
|
||||
<logger name="io.netty" level="ERROR"/>
|
||||
<logger name="ch.qos.logback" level="OFF"/>
|
||||
</configuration>
|
||||
|
|
Reference in a new issue