Archived
1
0
Fork 0

Rewrite logging and add plugin loading

This commit is contained in:
Moritz Ruth 2020-10-14 19:47:40 +02:00
parent 299d50d129
commit 6985831b00
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
16 changed files with 231 additions and 66 deletions

View file

@ -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}")

View file

@ -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)
}
}

View file

@ -0,0 +1,5 @@
package space.blokk.logging
interface LoggingOutputProvider {
fun log(loggerName: String, level: Logger.Level, message: String, throwable: Throwable? = null)
}

View file

@ -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? {

View file

@ -0,0 +1,5 @@
package space.blokk.plugin
interface PluginManager {
val plugins: List<Plugin>
}

View file

@ -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.
*

View file

@ -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).

View file

@ -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}"

View file

@ -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
)

View file

@ -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(" ")
)
}
}

View file

@ -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(" ")
)
}
}

View file

@ -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
)
}
}

View file

@ -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"
}
}

View file

@ -0,0 +1,3 @@
package space.blokk.util
fun pluralize(word: String, count: Int) = word + (if (count == 1) "" else "s")

View file

@ -3,3 +3,4 @@ port: 25565
silentNonServerErrors: true
authenticateAndEncrypt: true
developmentMode: false
minLogLevel: INFO

View file

@ -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>