diff --git a/.sdkmanrc b/.sdkmanrc index 9584f14f9c..62631dfc04 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=21.0.1-graalce +java=21.0.2-graalce diff --git a/build.gradle.kts b/build.gradle.kts index ae1e46ad15..13e64c90eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,9 +25,9 @@ plugins { id("project-report") id("org.jetbrains.dokka") version("1.9.10") id("com.github.jk1.dependency-license-report") version("2.5") - id("org.jetbrains.kotlinx.binary-compatibility-validator") version("0.13.2") - id("org.graalvm.buildtools.native") version("0.9.28") apply(false) - id("io.gitlab.arturbosch.detekt") version("1.23.4") apply(false) + id("org.jetbrains.kotlinx.binary-compatibility-validator") version("0.14.0") + id("org.graalvm.buildtools.native") version("0.10.1") apply(false) + id("io.gitlab.arturbosch.detekt") version("1.23.5") apply(false) id("me.champeau.jmh") version("0.7.2") apply(false) } @@ -140,7 +140,7 @@ gradle.taskGraph.whenReady(closureOf { }) tasks.wrapper { - gradleVersion = "8.5" + gradleVersion = "8.6" distributionType = ALL } @@ -159,6 +159,8 @@ apiValidation { // Experimental modules "rest", "rest_tools", +// "serverless_http", +// "serverless_http_google", "web", "templates_jte", ) diff --git a/core/README.md b/core/README.md index 68a8951fc6..ba05616d5a 100644 --- a/core/README.md +++ b/core/README.md @@ -42,16 +42,10 @@ The following code block shows the most common use cases for the [Logger] class: @code core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt?logger -By default, Hexagon uses the [Java Util Logging] logging library, you can use any of its -implementations by just adding another logging adapter as a dependency. Below you can see some -alternatives: - -* [Logback](/logging_logback) -* [SLF4J JUL](/logging_slf4j_jul) +By default, Hexagon uses the [System.Logger] class. [Logger]: /api/core/com.hexagonkt.core.logging/-logger -[Java Util Logging]: -https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html +[System.Logger]: https://docs.oracle.com/javase/9/docs/api/java/lang/System.Logger.html # Package com.hexagonkt.core.media Media types definitions and constants for default media types. diff --git a/core/api/core.api b/core/api/core.api index f707a8e429..1529b11899 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -157,10 +157,10 @@ public final class com/hexagonkt/core/Jvm { public final fun getVersion ()Ljava/lang/String; public final fun getZoneId ()Ljava/time/ZoneId; public final fun isConsole ()Z - public final fun loadSystemSettings (Ljava/util/Map;Z)V - public static synthetic fun loadSystemSettings$default (Lcom/hexagonkt/core/Jvm;Ljava/util/Map;ZILjava/lang/Object;)V + public final fun loadSystemSettings (Ljava/util/Map;)V public final fun systemFlag (Ljava/lang/String;)Z public final fun systemSetting (Lkotlin/reflect/KClass;Ljava/lang/String;)Ljava/lang/Object; + public final fun systemSetting (Lkotlin/reflect/KClass;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; public final fun systemSettingOrNull (Lkotlin/reflect/KClass;Ljava/lang/String;)Ljava/lang/Object; public final fun totalMemory ()Ljava/lang/String; public final fun usedMemory ()Ljava/lang/String; @@ -220,7 +220,8 @@ public final class com/hexagonkt/core/UuidsKt { } public final class com/hexagonkt/core/logging/Logger { - public fun (Ljava/lang/String;)V + public fun (Ljava/lang/String;Ljava/lang/System$Logger;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/System$Logger;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lkotlin/reflect/KClass;)V public final fun debug (Lkotlin/jvm/functions/Function0;)V public final fun error (Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V @@ -228,26 +229,15 @@ public final class com/hexagonkt/core/logging/Logger { public static synthetic fun error$default (Lcom/hexagonkt/core/logging/Logger;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun getName ()Ljava/lang/String; public final fun info (Lkotlin/jvm/functions/Function0;)V - public final fun isDebugEnabled ()Z - public final fun isErrorEnabled ()Z - public final fun isInfoEnabled ()Z - public final fun isLoggerLevelEnabled (Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun isTraceEnabled ()Z - public final fun isWarnEnabled ()Z - public final fun log (Lcom/hexagonkt/core/logging/LoggingLevel;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V - public final fun log (Lcom/hexagonkt/core/logging/LoggingLevel;Lkotlin/jvm/functions/Function0;)V - public final fun setLoggerLevel (Lcom/hexagonkt/core/logging/LoggingLevel;)V + public final fun isLoggable (Ljava/lang/System$Logger$Level;)Z + public final fun log (Ljava/lang/System$Logger$Level;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V + public final fun log (Ljava/lang/System$Logger$Level;Lkotlin/jvm/functions/Function0;)V public final fun trace (Lkotlin/jvm/functions/Function0;)V public final fun warn (Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V public final fun warn (Lkotlin/jvm/functions/Function0;)V public static synthetic fun warn$default (Lcom/hexagonkt/core/logging/Logger;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V } -public abstract interface class com/hexagonkt/core/logging/LoggerPort { - public abstract fun log (Lcom/hexagonkt/core/logging/LoggingLevel;Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V - public abstract fun log (Lcom/hexagonkt/core/logging/LoggingLevel;Lkotlin/jvm/functions/Function0;)V -} - public final class com/hexagonkt/core/logging/LoggingKt { public static final fun debug (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; public static synthetic fun debug$default (Ljava/lang/Object;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/Object; @@ -258,50 +248,6 @@ public final class com/hexagonkt/core/logging/LoggingKt { public static synthetic fun trace$default (Ljava/lang/Object;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/Object; } -public final class com/hexagonkt/core/logging/LoggingLevel : java/lang/Enum { - public static final field DEBUG Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field ERROR Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field INFO Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field OFF Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field TRACE Lcom/hexagonkt/core/logging/LoggingLevel; - public static final field WARN Lcom/hexagonkt/core/logging/LoggingLevel; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggingLevel; - public static fun values ()[Lcom/hexagonkt/core/logging/LoggingLevel; -} - -public final class com/hexagonkt/core/logging/LoggingManager { - public static final field INSTANCE Lcom/hexagonkt/core/logging/LoggingManager; - public final fun getAdapter ()Lcom/hexagonkt/core/logging/LoggingPort; - public final fun getDefaultLoggerName ()Ljava/lang/String; - public final fun getUseColor ()Z - public final fun isLoggerLevelEnabled (Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun isLoggerLevelEnabled (Ljava/lang/Object;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun isLoggerLevelEnabled (Lkotlin/reflect/KClass;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public final fun setAdapter (Lcom/hexagonkt/core/logging/LoggingPort;)V - public final fun setDefaultLoggerName (Ljava/lang/String;)V - public final fun setLoggerLevel (Lcom/hexagonkt/core/logging/LoggingLevel;)V - public final fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V - public final fun setLoggerLevel (Lkotlin/reflect/KClass;Lcom/hexagonkt/core/logging/LoggingLevel;)V - public final fun setUseColor (Z)V -} - -public abstract interface class com/hexagonkt/core/logging/LoggingPort { - public abstract fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public abstract fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public abstract fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - -public final class com/hexagonkt/core/logging/SystemLoggingAdapter : com/hexagonkt/core/logging/LoggingPort { - public fun ()V - public fun (Lcom/hexagonkt/core/logging/LoggingLevel;)V - public synthetic fun (Lcom/hexagonkt/core/logging/LoggingLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - public final class com/hexagonkt/core/media/MediaType { public static final field Companion Lcom/hexagonkt/core/media/MediaType$Companion; public fun (Lcom/hexagonkt/core/media/MediaTypeGroup;Ljava/lang/String;)V diff --git a/core/src/main/kotlin/com/hexagonkt/core/ClasspathHandler.kt b/core/src/main/kotlin/com/hexagonkt/core/ClasspathHandler.kt index 4c1e9038b3..7b6ca9c1b6 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/ClasspathHandler.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/ClasspathHandler.kt @@ -1,5 +1,6 @@ package com.hexagonkt.core +import com.hexagonkt.core.logging.logger import java.net.URL import java.net.URLConnection import java.net.URLStreamHandler @@ -21,7 +22,7 @@ object ClasspathHandler : URLStreamHandler() { } } catch (e: Error) { - e.printStackTrace() + logger.error(e) } } diff --git a/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt b/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt index 1ba4aeb608..35a60af29c 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/Jvm.kt @@ -85,24 +85,16 @@ object Jvm { (runtime.totalMemory() - runtime.freeMemory()).let { "%,d".format(it / 1024) } /** - * Add a map to system properties, optionally overriding them. + * Add a map to system properties, overriding entries if already set. * * @param settings Data to be added to system properties. - * @param overwrite If true, overwrite existing entries with supplied data. */ - fun loadSystemSettings(settings: Map, overwrite: Boolean = false) { - settings.keys.forEach { - check(it.matches(systemSettingPattern)) { - "Property name must match $systemSettingPattern ($it)" - } + fun loadSystemSettings(settings: Map) { + settings.entries.forEach { (k, v) -> + val matchPattern = k.matches(systemSettingPattern) + check(matchPattern) { "Property name must match $systemSettingPattern ($k)" } + System.setProperty(k, v) } - - val systemProperties = System.getProperties() - val properties = - if (overwrite) settings.entries - else settings.entries.filter { !systemProperties.containsKey(it.key) } - - properties.forEach { (k, v) -> System.setProperty(k, v) } } /** @@ -122,6 +114,9 @@ object Jvm { systemSettingOrNull(type, name) ?: error("Required '${type.simpleName}' system setting '$name' not found") + fun systemSetting(type: KClass, name: String, defaultValue: T): T = + systemSettingOrNull(type, name) ?: defaultValue + /** * Retrieve a flag (boolean parameter) by name by looking in OS environment variables first and * in the JVM system properties if not found. @@ -147,6 +142,9 @@ object Jvm { inline fun systemSetting(name: String): T = systemSetting(T::class, name) + inline fun systemSetting(name: String, defaultValue: T): T = + systemSetting(T::class, name, defaultValue) + private fun systemSettingRaw(name: String): String? { val correctName = name.matches(systemSettingPattern) require(correctName) { "Setting name must match $systemSettingPattern" } diff --git a/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt b/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt index 9ab99a6f06..a710700532 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/MultipleException.kt @@ -1,7 +1,7 @@ package com.hexagonkt.core /** - * Exception with a list of causes. Cause is `null` as it can't be tell which one of the list is the + * Exception with a list of causes. Cause is `null` as it can't be told which one of the list is the * cause. * * A coded multiple exception should be created this way: diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt index fd3f20c960..eb507deb76 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt @@ -1,43 +1,60 @@ package com.hexagonkt.core.logging +import com.hexagonkt.core.text.stripAnsi +import java.lang.System.Logger.Level +import java.lang.System.Logger.Level.* import kotlin.reflect.KClass -import com.hexagonkt.core.logging.LoggingLevel.* /** - * Logger class with Kotlin improvements like lazy evaluation. It is backed by a logging port. + * Logger class with Kotlin usability improvements. It is backed by a [System.Logger] instance. * * @param name Logger name. It is shown in the logs messages and used for log filtering. * @sample com.hexagonkt.core.logging.LoggerTest.loggerUsage */ -class Logger(val name: String) { +class Logger( + val name: String, + internal val logger: System.Logger = System.getLogger(name) +) { - internal val log: LoggerPort = LoggingManager.adapter.createLogger(name) + /** + * Logger class with Kotlin improvements like lazy evaluation. + * + * @param type Logger type. It is shown in the logs messages and used for log filtering. + */ + constructor(type: KClass<*>): + this(type.qualifiedName ?: error("Cannot get qualified name of type")) /** - * Log a message, with associated exception information. + * Check if this logger is enabled for a given log level. * - * @see LoggerPort.log + * @param level Level to check. + * @return True if this logger is enabled for the supplied level. */ - fun log(level: LoggingLevel, exception: E, message: (E) -> Any?) { - log.log(level, exception, message) - } + fun isLoggable(level: Level): Boolean = + logger.isLoggable(level) /** - * Log a message. + * Log a message, with associated exception information. * - * @see LoggerPort.log + * @param level Level used in the log statement. + * @param exception The exception associated with log message. + * @param message The message supplier to use in the log statement. */ - fun log(level: LoggingLevel, message: () -> Any?) { - log.log(level, message) + fun log(level: Level, exception: E, message: (E) -> Any?) { + val messageSupplier = { stripAnsi(message(exception), useColor) } + logger.log(level, messageSupplier, exception) } /** - * Logger class with Kotlin improvements like lazy evaluation. + * Log a message. * - * @param type Logger type. It is shown in the logs messages and used for log filtering. + * @param level Level used in the log statement. + * @param message The message supplier to use in the log statement. */ - constructor(type: KClass<*>): - this(type.qualifiedName ?: error("Cannot get qualified name of type")) + fun log(level: Level, message: () -> Any?) { + val messageSupplier = { stripAnsi(message(), useColor) } + logger.log(level, messageSupplier) + } /** * Log a message using [TRACE] level. @@ -45,7 +62,7 @@ class Logger(val name: String) { * @param message The required message to log. */ fun trace(message: () -> Any?) { - log.log(TRACE, message) + log(TRACE, message) } /** @@ -54,7 +71,7 @@ class Logger(val name: String) { * @param message The required message to log. */ fun debug(message: () -> Any?) { - log.log(DEBUG, message) + log(DEBUG, message) } /** @@ -63,16 +80,16 @@ class Logger(val name: String) { * @param message The required message to log. */ fun info(message: () -> Any?) { - log.log(INFO, message) + log(INFO, message) } /** - * Log a message using [WARN] level. + * Log a message using [WARNING] level. * * @param message The required message to log. */ fun warn(message: () -> Any?) { - log.log(WARN, message) + log(WARNING, message) } /** @@ -81,86 +98,31 @@ class Logger(val name: String) { * @param message The required message to log. */ fun error(message: () -> Any?) { - log.log(ERROR, message) + log(ERROR, message) } /** - * Log a message using [WARN] level with associated exception information. + * Log a message using [WARNING] level with associated exception information. * * @param exception The exception associated with log message. * @param message The message to log (optional). If not supplied it will be empty. */ fun warn(exception: E?, message: (E?) -> Any? = { "" }) { - if (exception == null) log.log(WARN) { message(null) } - else log.log(WARN, exception, message) + if (exception == null) log(WARNING) { message(null) } + else log(WARNING, exception, message) } /** * Log a message using [ERROR] level with associated exception information. * * @param exception The exception associated with log message. - * @param message The message to log (optional). If not supplied it will be empty. + * @param message The message to log (function to optional). If not supplied it will be empty. */ fun error(exception: E?, message: (E?) -> Any? = { "" }) { - if (exception == null) log.log(ERROR) { message(null) } - else log.log(ERROR, exception, message) - } - - /** - * Set a logging level for this logger. - * - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(level: LoggingLevel) { - LoggingManager.setLoggerLevel(name, level) + if (exception == null) log(ERROR) { message(null) } + else log(ERROR, exception, message) } - /** - * Check if a logging level is enabled for this logger. - * - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for this logger. - */ - fun isLoggerLevelEnabled(level: LoggingLevel): Boolean = - LoggingManager.isLoggerLevelEnabled(name, level) - - /** - * Check if the [TRACE] logging level is enabled for this logger. - * - * @return True if the [TRACE] level is enabled for this logger. - */ - fun isTraceEnabled(): Boolean = - isLoggerLevelEnabled(TRACE) - - /** - * Check if the [DEBUG] logging level is enabled for this logger. - * - * @return True if the [DEBUG] level is enabled for this logger. - */ - fun isDebugEnabled(): Boolean = - isLoggerLevelEnabled(DEBUG) - - /** - * Check if the [INFO] logging level is enabled for this logger. - * - * @return True if the [INFO] level is enabled for this logger. - */ - fun isInfoEnabled(): Boolean = - isLoggerLevelEnabled(INFO) - - /** - * Check if the [WARN] logging level is enabled for this logger. - * - * @return True if the [WARN] level is enabled for this logger. - */ - fun isWarnEnabled(): Boolean = - isLoggerLevelEnabled(WARN) - - /** - * Check if the [ERROR] logging level is enabled for this logger. - * - * @return True if the [ERROR] level is enabled for this logger. - */ - fun isErrorEnabled(): Boolean = - isLoggerLevelEnabled(ERROR) + internal fun stripAnsi(receiver: T?, apply: Boolean): String? = + receiver?.toString()?.let { if (apply) it.stripAnsi() else it } } diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggerPort.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggerPort.kt deleted file mode 100644 index 453b8479fc..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggerPort.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.hexagonkt.core.logging - -/** - * A Logger is used to log messages for a specific system or application component. - */ -interface LoggerPort { - - /** - * Log a message, with associated exception information. - * - * @param level One of the message level identifiers, e.g., TRACE. - * @param exception The exception associated with log message. - * @param message The required message to log. - */ - fun log(level: LoggingLevel, exception: E, message: (E) -> Any?) - - /** - * Log a message. - * - * @param level One of the message level identifiers, e.g., TRACE. - * @param message The required message to log. - */ - fun log(level: LoggingLevel, message: () -> Any?) -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/Logging.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/Logging.kt index 4c26959c84..eb5876a1c9 100644 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/Logging.kt +++ b/core/src/main/kotlin/com/hexagonkt/core/logging/Logging.kt @@ -1,46 +1,51 @@ package com.hexagonkt.core.logging +import java.lang.System.Logger.Level.* +import com.hexagonkt.core.Jvm + +internal val useColor: Boolean by lazy { Jvm.systemSetting("hexagonkt_logging_color", true) } +internal val defaultLoggerName: String by lazy { + Jvm.systemSetting("hexagonkt_logging_logger_name", "com.hexagonkt.core.logging") +} + /** Default logger for when you feel too lazy to declare one. */ -val logger: Logger by lazy { Logger(LoggingManager.defaultLoggerName) } +val logger: Logger by lazy { Logger(defaultLoggerName) } /** - * Uses this [T] to log a message with a prefix using [TRACE][LoggingLevel.TRACE] level. - * - * com.hexagonkt.core.logging.Logger must have TRACE level + * Use this [T] to log a message with a prefix using [TRACE] level. * - * TODO Add use case and example in documentation. + * [com.hexagonkt.core.logging.logger] must have the [TRACE] level enabled. * - * @receiver . - * @param prefix . - * @return . + * @receiver Object which string representation will be logged. + * @param T Type of the logged object. + * @param prefix Prefix for the logging message. + * @return The receiver reference for chaining methods. */ fun T.trace(prefix: String = ""): T = apply { logger.trace { "$prefix$this" } } /** - * Uses this [T] to log a message with a prefix using [DEBUG][LoggingLevel.DEBUG] level. + * Use this [T] to log a message with a prefix using [DEBUG] level. * - * com.hexagonkt.core.logging.Logger must have DEBUG level + * [com.hexagonkt.core.logging.logger] must have the [DEBUG] level enabled. * - * TODO Add use case and example in documentation. - * - * @receiver . - * @param prefix . - * @return . + * @receiver Object which string representation will be logged. + * @param T Type of the logged object. + * @param prefix Prefix for the logging message. + * @return The receiver reference for chaining methods. */ fun T.debug(prefix: String = ""): T = apply { logger.debug { "$prefix$this" } } /** - * Uses this [T] to log a message with a prefix using [INFO][LoggingLevel.INFO] level. - * - * com.hexagonkt.core.logging.Logger must have INFO level + * Use this [T] to log a message with a prefix using [INFO] level. * - * TODO Add use case and example in documentation. + * [com.hexagonkt.core.logging.logger] must have the [INFO] level enabled. * - * @receiver . - * @param prefix . - * @return . + * @receiver Object which string representation will be logged. + * @param T Type of the logged object. + * @param prefix Prefix for the logging message. + * @return The receiver reference for chaining methods. */ fun T.info(prefix: String = ""): T = apply { logger.info { "$prefix$this" } } diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingLevel.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingLevel.kt deleted file mode 100644 index e04bd8a164..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingLevel.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.hexagonkt.core.logging - -/** - * Logger logging level values. - * - * @property TRACE Used for low level details that are logged very often. - * @property DEBUG Useful information to diagnose problems or failures. - * @property INFO Only used for really useful information that is not written very often. - * @property WARN To notify that something failed and was ignored, but it could be an issue later. - * @property ERROR Error that stopped the correct processing of the process. - * @property OFF Disable all logging levels. - */ -enum class LoggingLevel { - TRACE, - DEBUG, - INFO, - WARN, - ERROR, - OFF, -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt deleted file mode 100644 index 9532e6b604..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingManager.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.hexagonkt.core.logging - -import kotlin.reflect.KClass - -/** - * Manages Logs using [SystemLoggingAdapter] - */ -object LoggingManager { - var useColor: Boolean = true - var adapter: LoggingPort = SystemLoggingAdapter() - var defaultLoggerName: String = "com.hexagonkt.core.logging" - set(value) { - require(value.isNotEmpty()) { "Default logger name cannot be empty string" } - field = value - } - - /** - * Set a logger logging level by name. - * - * @param name Logger name. - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(name: String, level: LoggingLevel) { - adapter.setLoggerLevel(name, level) - } - - /** - * Set a logging level for a logger with a class name. - * - * @param type Class type. - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(type: KClass<*>, level: LoggingLevel) { - setLoggerLevel(qualifiedName(type), level) - } - - /** - * Set a logger logging level for a logger with a default name. - * - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(level: LoggingLevel) { - setLoggerLevel("", level) - } - - /** - * Check if a logging level is enabled for a logger. - * - * @param name Logger name. - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean = - adapter.isLoggerLevelEnabled(name, level) - - /** - * Check if a logging level is enabled for a logger with an instance. - * - * @param instance class instance. - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(instance: Any, level: LoggingLevel): Boolean = - isLoggerLevelEnabled(instance::class, level) - - /** - * Check if a logging level is enabled for a logger with a class name. - * - * @param type Class type. - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(type: KClass<*>, level: LoggingLevel): Boolean = - isLoggerLevelEnabled(qualifiedName(type), level) - - /** - * Check if a logging level is enabled for the root logger. - * - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(level: LoggingLevel): Boolean = - isLoggerLevelEnabled("", level) - - private fun qualifiedName(type: KClass<*>): String = - type.qualifiedName ?: error("Cannot get qualified name of type") -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingPort.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingPort.kt deleted file mode 100644 index 177a246c70..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/LoggingPort.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.hexagonkt.core.logging - -/** - * Logging Contract for integrating different logging libraries. - */ -interface LoggingPort { - - /** - * Create [Logger][LoggerPort] with name. - * - * @param name Logger name. - */ - fun createLogger(name: String): LoggerPort - - /** - * Set logging level for a logger. - * - * @param name Logger name. - * @param level One of the logging levels identifiers, e.g., TRACE - */ - fun setLoggerLevel(name: String, level: LoggingLevel) - - /** - * Check if a logging level is enabled for a logger. - * - * @param name Logger name. - * @param level One of the logging levels identifiers, e.g., TRACE - * @return True if the supplied level is enabled for the passed logger name. - */ - fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLogger.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLogger.kt deleted file mode 100644 index 90eabed085..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLogger.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.hexagonkt.core.logging - -import com.hexagonkt.core.logging.LoggingLevel.* - -internal data class SystemLogger(val name: String) : LoggerPort { - - private val logger: System.Logger = System.getLogger(name) - - override fun log(level: LoggingLevel, exception: E, message: (E) -> Any?) { - if (LoggingManager.isLoggerLevelEnabled(name, level)) - logger.log(level(level), message(exception).toString(), exception) - } - - override fun log(level: LoggingLevel, message: () -> Any?) { - if (LoggingManager.isLoggerLevelEnabled(name, level)) - logger.log(level(level), message()) - } - - private fun level(level: LoggingLevel): System.Logger.Level = - when (level) { - TRACE -> System.Logger.Level.TRACE - DEBUG -> System.Logger.Level.DEBUG - INFO -> System.Logger.Level.INFO - WARN -> System.Logger.Level.WARNING - ERROR -> System.Logger.Level.ERROR - OFF -> error("OFF Level not allowed for a logging message") - } -} diff --git a/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLoggingAdapter.kt b/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLoggingAdapter.kt deleted file mode 100644 index ab9ea645ae..0000000000 --- a/core/src/main/kotlin/com/hexagonkt/core/logging/SystemLoggingAdapter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.hexagonkt.core.logging - -import com.hexagonkt.core.logging.LoggingLevel.INFO -import com.hexagonkt.core.require - -class SystemLoggingAdapter(defaultLevel: LoggingLevel = INFO) : LoggingPort { - - private val loggerLevels: MutableMap = mutableMapOf("" to defaultLevel) - - override fun createLogger(name: String): LoggerPort = - SystemLogger(name) - - override fun setLoggerLevel(name: String, level: LoggingLevel) { - loggerLevels[name] = level - } - - override fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean = - findLoggingLevel(name).ordinal <= level.ordinal - - private fun findLoggingLevel(name: String): LoggingLevel { - var path = name - - do { - val loggingLevel = loggerLevels[path] - if (loggingLevel != null) - return loggingLevel - - path = path.substringBeforeLast('.') - } - while (path.contains('.')) - - return loggerLevels.require("") - } -} diff --git a/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt b/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt index 7a96c368e4..75fc882615 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/ClasspathHandlerProviderTest.kt @@ -1,7 +1,5 @@ package com.hexagonkt.core -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingManager import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -13,7 +11,6 @@ import kotlin.test.assertFailsWith internal class ClasspathHandlerProviderTest { @BeforeAll fun registerHandler() { - LoggingManager.setLoggerLevel("com.hexagonkt", LoggingLevel.TRACE) ClasspathHandler.registerHandler() } @@ -46,7 +43,7 @@ internal class ClasspathHandlerProviderTest { @Test fun `Read classpath resource returns resource's text` () { val resourceText = urlOf("classpath:sample.properties").readText() - assert(resourceText.contains("handlers=com.hexagonkt.core.logging.jul.SystemStreamHandler")) + assert(resourceText.contains("handlers=java.util.logging.ConsoleHandler")) } @Test fun `Unknown protocol throws exception`() { diff --git a/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt b/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt index 8c03346695..64c83493e6 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/JvmTest.kt @@ -23,23 +23,14 @@ internal class JvmTest { mapOf("s1" to "v1", "s2" to "v2").forEach { (k, v) -> System.setProperty(k, v) } Jvm.loadSystemSettings(mapOf("s1" to "x1", "s2" to "x2")) - assertEquals("v1", System.getProperty("s1")) - assertEquals("v2", System.getProperty("s2")) + assertEquals("x1", System.getProperty("s1")) + assertEquals("x2", System.getProperty("s2")) - Jvm.loadSystemSettings(mapOf("s1" to "x1", "s2" to "x2", "s3" to "x3")) + Jvm.loadSystemSettings(mapOf("s1" to "v1", "s2" to "v2", "s3" to "x3")) assertEquals("v1", System.getProperty("s1")) assertEquals("v2", System.getProperty("s2")) assertEquals("x3", System.getProperty("s3")) - Jvm.loadSystemSettings(mapOf("s1" to "x1", "s2" to "x2"), true) - assertEquals("x1", System.getProperty("s1")) - assertEquals("x2", System.getProperty("s2")) - - Jvm.loadSystemSettings(mapOf("s1" to "z1", "s2" to "z2", "s3" to "z3"), true) - assertEquals("z1", System.getProperty("s1")) - assertEquals("z2", System.getProperty("s2")) - assertEquals("z3", System.getProperty("s3")) - val e = assertFailsWith { Jvm.loadSystemSettings(mapOf("1" to "v")) } assertEquals("Property name must match [_A-Za-z]+[_A-Za-z0-9]* (1)", e.message) } @@ -214,7 +205,9 @@ internal class JvmTest { assert(Jvm.systemSetting("PATH").isNotEmpty()) assert(Jvm.systemSetting("path").isNotEmpty()) + assertNotEquals("default", Jvm.systemSetting("path", "default")) assertNull(Jvm.systemSettingOrNull("_not_defined_")) + assertEquals("default", Jvm.systemSetting("_not_defined_", "default")) System.setProperty("PATH", "path override") assert(Jvm.systemSetting("PATH") != "path override") diff --git a/core/src/test/kotlin/com/hexagonkt/core/NetworkTest.kt b/core/src/test/kotlin/com/hexagonkt/core/NetworkTest.kt index 73a63c3545..3a58cab177 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/NetworkTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/NetworkTest.kt @@ -76,9 +76,9 @@ internal class NetworkTest { } @Test fun `URL check works properly`() { - assertTrue { urlOf("http://example.com").responseSuccessful() } - assertFalse { urlOf("http://invalid-domain.z").responseSuccessful() } - assertTrue { urlOf("http://example.com").responseFound() } - assertFalse { urlOf("http://example.com/nothing").responseFound() } + assertTrue { urlOf("https://example.com").responseSuccessful() } + assertFalse { urlOf("https://invalid-domain.z").responseSuccessful() } + assertTrue { urlOf("https://example.com").responseFound() } + assertFalse { urlOf("https://example.com/nothing").responseFound() } } } diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt index b1ba8d65a3..a7d18c37ad 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggerTest.kt @@ -1,16 +1,32 @@ package com.hexagonkt.core.logging -import com.hexagonkt.core.logging.LoggingLevel.* +import com.hexagonkt.core.text.Ansi.RESET +import com.hexagonkt.core.text.AnsiColor.BRIGHT_WHITE +import com.hexagonkt.core.text.AnsiColor.RED_BG +import com.hexagonkt.core.text.AnsiEffect.UNDERLINE +import com.hexagonkt.core.urlOf import io.mockk.every import io.mockk.mockk +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.condition.DisabledInNativeImage import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import java.lang.System.Logger.Level.ERROR +import java.lang.System.Logger.Level.TRACE +import java.util.logging.LogManager import kotlin.IllegalStateException import kotlin.reflect.KClass import kotlin.test.* +@TestInstance(PER_CLASS) internal class LoggerTest { + @BeforeAll fun setUp() { + val configuration = urlOf("classpath:sample.properties") + LogManager.getLogManager().readConfiguration(configuration.openStream()) + } + @Suppress("RedundantExplicitType", "UNUSED_VARIABLE") // Ignored for examples generation @Test fun loggerUsage() { // logger @@ -36,11 +52,6 @@ internal class LoggerTest { classLogger.warn(exception) classLogger.error(exception) classLogger.error { "Error without an exception" } - - // Logging level can be changed programmatically - LoggingManager.setLoggerLevel(ERROR) - LoggingManager.setLoggerLevel(classLogger::class, DEBUG) - LoggingManager.setLoggerLevel("com.hexagonkt", INFO) // logger } @@ -72,48 +83,19 @@ internal class LoggerTest { assert(Logger("name"::class).name == "kotlin.String") } - @Test fun `A logger level can be changed`() { - val l = Logger("l") - - l.setLoggerLevel(TRACE) - assert(l.name == "l") - assert(l.isTraceEnabled()) - assert(l.isDebugEnabled()) - assert(l.isInfoEnabled()) - assert(l.isWarnEnabled()) - assert(l.isErrorEnabled()) - - l.setLoggerLevel(DEBUG) - assert(l.name == "l") - assertFalse(l.isTraceEnabled()) - assert(l.isDebugEnabled()) - assert(l.isInfoEnabled()) - assert(l.isWarnEnabled()) - assert(l.isErrorEnabled()) - - l.setLoggerLevel(INFO) - assert(l.name == "l") - assertFalse(l.isTraceEnabled()) - assertFalse(l.isDebugEnabled()) - assert(l.isInfoEnabled()) - assert(l.isWarnEnabled()) - assert(l.isErrorEnabled()) - - l.setLoggerLevel(WARN) - assert(l.name == "l") - assertFalse(l.isTraceEnabled()) - assertFalse(l.isDebugEnabled()) - assertFalse(l.isInfoEnabled()) - assert(l.isWarnEnabled()) - assert(l.isErrorEnabled()) + @Test fun `A logger can be queried for its enabled state on a given level`() { + assert(Logger("name").isLoggable(ERROR)) + assertFalse(Logger("name").isLoggable(TRACE)) + } - l.setLoggerLevel(ERROR) - assert(l.name == "l") - assertFalse(l.isTraceEnabled()) - assertFalse(l.isDebugEnabled()) - assertFalse(l.isInfoEnabled()) - assertFalse(l.isWarnEnabled()) - assert(l.isErrorEnabled()) + @Test fun `ANSI testing`() { + val l = Logger("name") + val message = "$RED_BG$BRIGHT_WHITE${UNDERLINE}ANSI$RESET normal" + val noAnsiMessage = l.stripAnsi(message, true) + val ansiMessage = l.stripAnsi(message, false) + assertEquals(message, ansiMessage) + assertNotEquals(message, noAnsiMessage) + assertContentEquals(noAnsiMessage?.toByteArray(), "ANSI normal".toByteArray()) } @Test diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt deleted file mode 100644 index e789514ee4..0000000000 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingManagerTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.hexagonkt.core.logging - -import com.hexagonkt.core.logging.LoggingLevel.* -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.condition.DisabledInNativeImage -import kotlin.IllegalStateException -import kotlin.reflect.KClass -import org.junit.jupiter.api.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -internal class LoggingManagerTest { - - // TODO Repeat this test on other logging adapters - @Test fun `Loggers are enabled and disabled at runtime`() { - - LoggingManager.adapter = SystemLoggingAdapter() - - val ch = Logger("com.hx") - val chc = Logger("com.hx.core") - val chl = Logger("com.hx.logging") - - LoggingManager.setLoggerLevel("com.hx", TRACE) - assertTrue(Logger("z").isLoggerLevelEnabled(INFO)) - assertFalse(Logger("z").isLoggerLevelEnabled(DEBUG)) - assertTrue( - entries.all { - ch.isLoggerLevelEnabled(it) - && chc.isLoggerLevelEnabled(it) - && chl.isLoggerLevelEnabled(it) - } - ) - - LoggingManager.setLoggerLevel("com.hx.core", WARN) - assertTrue(chc.isLoggerLevelEnabled(ERROR) && chc.isErrorEnabled()) - assertTrue(chc.isLoggerLevelEnabled(WARN) && chc.isWarnEnabled()) - assertFalse(chc.isLoggerLevelEnabled(INFO) || chc.isInfoEnabled()) - assertFalse(chc.isLoggerLevelEnabled(DEBUG) || chc.isDebugEnabled()) - assertFalse(chc.isLoggerLevelEnabled(TRACE) || chc.isTraceEnabled()) - assertTrue(entries.all { ch.isLoggerLevelEnabled(it) && chl.isLoggerLevelEnabled(it) }) - - // TODO Check if parent level changes gets reflected on created loggers (com.hx -> TRACE) - LoggingManager.setLoggerLevel("com.hx.core", TRACE) - assertTrue(LoggingManager.isLoggerLevelEnabled("com.hx.core", INFO)) - assertTrue(chc.isLoggerLevelEnabled(ERROR) && chc.isErrorEnabled()) - assertTrue(chc.isLoggerLevelEnabled(WARN) && chc.isWarnEnabled()) - assertTrue(chc.isLoggerLevelEnabled(INFO) && chc.isInfoEnabled()) - assertTrue(chc.isLoggerLevelEnabled(DEBUG) && chc.isDebugEnabled()) - assertTrue(chc.isLoggerLevelEnabled(TRACE) && chc.isTraceEnabled()) - } - - @Test fun `'defaultLoggerName' can be changed`() { - val dln = LoggingManager.defaultLoggerName - - LoggingManager.defaultLoggerName = "com.example" - assertEquals("com.example", LoggingManager.defaultLoggerName) - - LoggingManager.defaultLoggerName = dln - } - - @Test fun `'defaultLoggerName' cannot be set to empty string`() { - val e = assertFailsWith { - LoggingManager.defaultLoggerName = "" - } - - assertEquals("Default logger name cannot be empty string", e.message) - } - - @Test - @DisabledInNativeImage - fun `Problem reading class name raises error`() { - val kc = mockk>() - every { kc.qualifiedName } returns null - - assertFailsWith { LoggingManager.isLoggerLevelEnabled(kc, INFO) } - .apply { assertEquals("Cannot get qualified name of type", message) } - - assertFailsWith { LoggingManager.setLoggerLevel(kc, INFO) } - .apply { assertEquals("Cannot get qualified name of type", message) } - } -} diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt index ff03e9a6c3..720d677b66 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/logging/LoggingTest.kt @@ -1,34 +1,23 @@ package com.hexagonkt.core.logging -import com.hexagonkt.core.logging.LoggingLevel.* +import com.hexagonkt.core.urlOf +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test -import java.time.LocalDate +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import java.util.logging.LogManager import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue +@TestInstance(PER_CLASS) internal class LoggingTest { - @Test fun `Log helpers`() { - LoggingManager.useColor = false - assertEquals(LoggingManager.defaultLoggerName, logger.name) - - assertEquals("foo", "foo".trace(">>> ")) - assertEquals("foo", "foo".trace()) - assertEquals(null, null.trace()) - assertEquals("text", "text".trace()) - - assertEquals("foo", "foo".debug(">>> ")) - assertEquals("foo", "foo".debug()) - assertEquals(null, null.debug()) - assertEquals("text", "text".debug()) - - assertEquals("foo", "foo".info(">>> ")) - assertEquals("foo", "foo".info()) - assertEquals(null, null.info()) - assertEquals("text", "text".info()) + @BeforeAll fun setUp() { + val configuration = urlOf("classpath:sample.properties") + LogManager.getLogManager().readConfiguration(configuration.openStream()) + } - LoggingManager.useColor = true + @Test fun `Log helpers`() { + assertEquals(defaultLoggerName, logger.name) assertEquals("foo", "foo".trace(">>> ")) assertEquals("foo", "foo".trace()) @@ -45,106 +34,4 @@ internal class LoggingTest { assertEquals(null, null.info()) assertEquals("text", "text".info()) } - - @Test fun `Manager can check if the root logger is enabled for a given level`() { - LoggingManager.setLoggerLevel(TRACE) - assertTrue(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(DEBUG) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(INFO) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(WARN) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(ERROR) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(OFF) - assertFalse(LoggingManager.isLoggerLevelEnabled(TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(WARN)) - assertFalse(LoggingManager.isLoggerLevelEnabled(ERROR)) - - LoggingManager.setLoggerLevel(WARN) - } - - @Test fun `Manager can check if loggers are enabled for a given level`() { - val date = LocalDate.now() - - LoggingManager.setLoggerLevel(OFF) - - LoggingManager.setLoggerLevel(date::class, TRACE) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, DEBUG) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, INFO) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, WARN) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, WARN)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, ERROR) - assertTrue(LoggingManager.isLoggerLevelEnabled(LocalDate::class, ERROR)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertTrue(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(date::class, OFF) - assertFalse(LoggingManager.isLoggerLevelEnabled(LocalDate::class, ERROR)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, TRACE)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, DEBUG)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, INFO)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, WARN)) - assertFalse(LoggingManager.isLoggerLevelEnabled(date, ERROR)) - - LoggingManager.setLoggerLevel(WARN) - } } diff --git a/core/src/test/kotlin/com/hexagonkt/core/logging/SystemLoggerTest.kt b/core/src/test/kotlin/com/hexagonkt/core/logging/SystemLoggerTest.kt deleted file mode 100644 index d0f165d46c..0000000000 --- a/core/src/test/kotlin/com/hexagonkt/core/logging/SystemLoggerTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.hexagonkt.core.logging - -import com.hexagonkt.core.logging.LoggingLevel.* -import org.junit.jupiter.api.Test -import java.lang.RuntimeException -import kotlin.test.assertFailsWith - -// TODO Add string appender and assert messages -internal class SystemLoggerTest { - - @Test fun `System loggers work as expected`() { - LoggingManager.adapter = SystemLoggingAdapter(TRACE) - val l = Logger("a") - - l.log(TRACE) { "trace" } - l.log(DEBUG) { "debug" } - l.log(INFO) { "info" } - l.log(WARN) { "warn" } - l.log(ERROR) { "error" } - - l.log(TRACE, RuntimeException()) { "trace $it" } - l.log(DEBUG, RuntimeException()) { "debug $it" } - l.log(INFO, RuntimeException()) { "info $it" } - l.log(WARN, RuntimeException()) { "warn $it" } - l.log(ERROR, RuntimeException()) { "error $it" } - - LoggingManager.adapter = SystemLoggingAdapter() - } - - @Test fun `Disabled logs are not issued`() { - LoggingManager.adapter = SystemLoggingAdapter(DEBUG) - val l = Logger("a") - - l.log(TRACE) { "trace" } - l.log(TRACE, RuntimeException()) { "trace $it" } - - LoggingManager.adapter = SystemLoggingAdapter() - } - - @Test fun `Invalid log level throws an error`() { - assertFailsWith { logger.log(OFF) { "error" } } - assertFailsWith { - logger.log(OFF, RuntimeException()) { "error $it" } - } - } -} diff --git a/core/src/test/kotlin/com/hexagonkt/core/text/StringsTest.kt b/core/src/test/kotlin/com/hexagonkt/core/text/StringsTest.kt index 3fff5af84f..5fe16c8a68 100644 --- a/core/src/test/kotlin/com/hexagonkt/core/text/StringsTest.kt +++ b/core/src/test/kotlin/com/hexagonkt/core/text/StringsTest.kt @@ -131,7 +131,7 @@ internal class StringsTest { Double::class to "4.3", String::class to "text", InetAddress::class to "127.0.0.1", - URL::class to "http://example.com", + URL::class to "https://example.com", URI::class to "schema://host:0/file", File::class to "/absolute/file.txt", LocalDate::class to "2020-12-31", diff --git a/core/src/test/resources/sample.properties b/core/src/test/resources/sample.properties index 224547780c..ac1553aa19 100644 --- a/core/src/test/resources/sample.properties +++ b/core/src/test/resources/sample.properties @@ -1,6 +1,16 @@ -handlers=com.hexagonkt.core.logging.jul.SystemStreamHandler +handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +#java.util.logging.SimpleFormatter.format=%1$tH:%1$NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/http/http_client/README.md b/http/http_client/README.md index e5a9614dce..a98f184aa1 100644 --- a/http/http_client/README.md +++ b/http/http_client/README.md @@ -40,8 +40,8 @@ Check this code snippet to get a glimpse on how to send the most general request @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/ClientTest.kt?genericRequest -[Request]: /api/http_client/com.hexagonkt.http.client.model/-http-client-request -[Response]: /api/http_client/com.hexagonkt.http.client.model/-http-client-response +[Request]: /api/http/http/com.hexagonkt.http.model/-http-request +[Response]: /api/http/http/com.hexagonkt.http.model/-http-response # Simple requests shortcuts There are utility methods to make the most common request in an easy way. @@ -89,8 +89,8 @@ format. To set up client/server certificates, you need to include [SslSettings] in your [ClientSettings]. In the sections below you can see how to configure these parameters. -[SslSettings]: /api/http/com.hexagonkt.http/-ssl-settings -[ClientSettings]: /api/http_client/com.hexagonkt.http.client/-http-client-settings +[SslSettings]: /api/http/http/com.hexagonkt.http/-ssl-settings +[ClientSettings]: /api/http/http_client/com.hexagonkt.http.client/-http-client-settings ## Key Store This store holds the identity certificate, this certificate is presented to the server by the client diff --git a/http/http_client_jetty/build.gradle.kts b/http/http_client_jetty/build.gradle.kts index 2e54f10670..e714dba4d8 100644 --- a/http/http_client_jetty/build.gradle.kts +++ b/http/http_client_jetty/build.gradle.kts @@ -13,7 +13,12 @@ description = "HTTP client adapter for Jetty (without WebSockets support)." dependencies { val jettyVersion = properties["jettyVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_client")) "api"("org.eclipse.jetty.http2:jetty-http2-client-transport:$jettyVersion") + + "testImplementation"("org.slf4j:log4j-over-slf4j:$slf4jVersion") + "testImplementation"("org.slf4j:jcl-over-slf4j:$slf4jVersion") + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/http_client_jetty/src/main/kotlin/com/hexagonkt/http/client/jetty/JettyClientAdapter.kt b/http/http_client_jetty/src/main/kotlin/com/hexagonkt/http/client/jetty/JettyClientAdapter.kt index f82ccb0018..894a971997 100644 --- a/http/http_client_jetty/src/main/kotlin/com/hexagonkt/http/client/jetty/JettyClientAdapter.kt +++ b/http/http_client_jetty/src/main/kotlin/com/hexagonkt/http/client/jetty/JettyClientAdapter.kt @@ -200,7 +200,9 @@ open class JettyClientAdapter : HttpClientPort { it.put("content-type", contentType.text) if (authorization != null) it.put("authorization", authorization.text) - request.headers.values.forEach { (k, v) -> it.put(k, v.map(Any::toString)) } + request.headers.values.forEach { (k, v) -> + v.map(Any::toString).forEach { s -> it.add(k, s)} + } } .body(createBody(request)) .accept(*request.accept.map { it.text }.toTypedArray()) diff --git a/http/http_client_jetty_ws/build.gradle.kts b/http/http_client_jetty_ws/build.gradle.kts index 2af866cd63..6effedf9fc 100644 --- a/http/http_client_jetty_ws/build.gradle.kts +++ b/http/http_client_jetty_ws/build.gradle.kts @@ -13,7 +13,12 @@ description = "HTTP client adapter for Jetty (with WebSockets support)." dependencies { val jettyVersion = properties["jettyVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_client_jetty")) "api"("org.eclipse.jetty.websocket:jetty-websocket-jetty-client:$jettyVersion") + + "testImplementation"("org.slf4j:log4j-over-slf4j:$slf4jVersion") + "testImplementation"("org.slf4j:jcl-over-slf4j:$slf4jVersion") + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/http_handlers/README.md b/http/http_handlers/README.md index be496a5529..275de1f367 100644 --- a/http/http_handlers/README.md +++ b/http/http_handlers/README.md @@ -77,10 +77,10 @@ methods. @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?serverCreation -[server settings]: /api/http_server/com.hexagonkt.http.server/-http-server-settings +[server settings]: /api/http/http_server/com.hexagonkt.http.server/-http-server-settings [handlers section]: /http_server/#handlers -[start()]: /api/http_server/com.hexagonkt.http.server/-http-server/start.html -[stop()]: /api/http_server/com.hexagonkt.http.server/-http-server/stop.html +[start()]: /api/http/http_server/com.hexagonkt.http.server/-http-server +[stop()]: /api/http/http_server/com.hexagonkt.http.server/-http-server ## Servlet Web server There is a special server adapter for running inside Servlet Containers. To use it you should import @@ -103,7 +103,7 @@ full list of methods. This sample code illustrates the usage: @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?callbackCall -[API documentation]: /api/http_server/com.hexagonkt.http.server.handlers/-http-server-context +[API documentation]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-context # Handlers The main building blocks of Hexagon HTTP services are a set of handlers. A handler is made up of two @@ -139,7 +139,7 @@ Check the next snippet for Handlers usage examples: @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?routesCreation -[next]: /api/http_server/com.hexagonkt.http.server.handlers/-http-server-context/next.html +[next]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-context @@ -157,7 +157,7 @@ the following fields: It yields true if all the supplied fields matches a call context. -[HttpPredicate]: /api/http_server/com.hexagonkt.http.handlers/-http-predicate +[HttpPredicate]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-predicate ## Path Patterns Patterns to match requests paths. They can have: @@ -302,7 +302,7 @@ different handlers. Check the [CorsCallback][CORS Callbacks] class for more deta @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/CorsTest.kt?cors -[CORS Callbacks]: /api/http_server/com.hexagonkt.http.server.callbacks/-cors-callback +[CORS Callbacks]: /api/http/http_server/com.hexagonkt.http.server.callbacks/-cors-callback # HTTPS It is possible to start a secure server enabling HTTPS. For this, you have to provide a server @@ -327,14 +327,14 @@ Below you can find a simple example to set up an HTTPS server and client with mu @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/HttpsTest.kt?https -[SslSettings]: /api/http/com.hexagonkt.http/-ssl-settings +[SslSettings]: /api/http/http/com.hexagonkt.http/-ssl-settings [HTTP/2]: https://en.wikipedia.org/wiki/HTTP/2 [ALPN]: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation [Gradle]: https://gradle.org [create sample certificates]: /gradle/#certificates [mutual TLS]: https://en.wikipedia.org/wiki/Mutual_authentication -[SslSettings.clientAuth]: /api/http/com.hexagonkt.http/-ssl-settings/client-auth.html -[Request.certificateChain]: /api/http_server/com.hexagonkt.http.server.model/-http-server-request/certificate-chain.html +[SslSettings.clientAuth]: /api/http/http/com.hexagonkt.http/-ssl-settings +[Request.certificateChain]: /api/http/http/com.hexagonkt.http.model/-http-request # WebSockets A Web Socket is an HTTP(S) connection made with the GET method and the `upgrade: websocket` and diff --git a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpPredicate.kt b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpPredicate.kt index 83ed2934f9..03b7c861cd 100644 --- a/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpPredicate.kt +++ b/http/http_handlers/src/main/kotlin/com/hexagonkt/http/handlers/HttpPredicate.kt @@ -45,16 +45,10 @@ data class HttpPredicate( private fun log( predicate: (Context) -> Boolean - ): (Context) -> Boolean { - return if (logger.isDebugEnabled()) { - { - val allowed = predicate(it) - logger.debug { "${describe()} -> ${if (allowed) "ALLOWED" else "DENIED"}" } - allowed - } - } - else - predicate + ): (Context) -> Boolean = { + val allowed = predicate(it) + logger.debug { "${describe()} -> ${if (allowed) "ALLOWED" else "DENIED"}" } + allowed } private fun filterMethod(context: Context): Boolean = diff --git a/http/http_server/README.md b/http/http_server/README.md index fae94d34a1..643f99766d 100644 --- a/http/http_server/README.md +++ b/http/http_server/README.md @@ -72,10 +72,10 @@ methods. @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?serverCreation -[server settings]: /api/http_server/com.hexagonkt.http.server/-http-server-settings +[server settings]: /api/http/http_server/com.hexagonkt.http.server/-http-server-settings [handlers section]: /http_server/#handlers -[start()]: /api/http_server/com.hexagonkt.http.server/-http-server/start.html -[stop()]: /api/http_server/com.hexagonkt.http.server/-http-server/stop.html +[start()]: /api/http/http_server/com.hexagonkt.http.server/-http-server +[stop()]: /api/http/http_server/com.hexagonkt.http.server/-http-server ## Servlet Web server There is a special server adapter for running inside Servlet Containers. To use it you should import @@ -98,7 +98,7 @@ full list of methods. This sample code illustrates the usage: @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?callbackCall -[API documentation]: /api/http_server/com.hexagonkt.http.server.handlers/-http-server-context +[API documentation]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-context # Handlers The main building blocks of Hexagon HTTP services are a set of handlers. A handler is made up of two @@ -134,7 +134,7 @@ Check the next snippet for Handlers usage examples: @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt?routesCreation -[next]: /api/http_server/com.hexagonkt.http.server.handlers/-http-server-context/next.html +[next]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-context @@ -152,7 +152,7 @@ the following fields: It yields true if all the supplied fields matches a call context. -[HttpPredicate]: /api/http_server/com.hexagonkt.http.handlers/-http-predicate +[HttpPredicate]: /api/http/http_handlers/com.hexagonkt.http.handlers/-http-predicate ## Path Patterns Patterns to match requests paths. They can have: @@ -297,7 +297,7 @@ different handlers. Check the [CorsCallback][CORS Callbacks] class for more deta @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/CorsTest.kt?cors -[CORS Callbacks]: /api/http_server/com.hexagonkt.http.server.callbacks/-cors-callback +[CORS Callbacks]: /api/http/http_server/com.hexagonkt.http.server.callbacks/-cors-callback # HTTPS It is possible to start a secure server enabling HTTPS. For this, you have to provide a server @@ -322,14 +322,14 @@ Below you can find a simple example to set up an HTTPS server and client with mu @code http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/HttpsTest.kt?https -[SslSettings]: /api/http/com.hexagonkt.http/-ssl-settings +[SslSettings]: /api/http/http/com.hexagonkt.http/-ssl-settings [HTTP/2]: https://en.wikipedia.org/wiki/HTTP/2 [ALPN]: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation [Gradle]: https://gradle.org [create sample certificates]: /gradle/#certificates [mutual TLS]: https://en.wikipedia.org/wiki/Mutual_authentication -[SslSettings.clientAuth]: /api/http/com.hexagonkt.http/-ssl-settings/client-auth.html -[Request.certificateChain]: /api/http_server/com.hexagonkt.http.server.model/-http-server-request/certificate-chain.html +[SslSettings.clientAuth]: /api/http/http/com.hexagonkt.http/-ssl-settings +[Request.certificateChain]: /api/http/http/com.hexagonkt.http.model/-http-request # WebSockets A Web Socket is an HTTP(S) connection made with the GET method and the `upgrade: websocket` and @@ -395,9 +395,7 @@ Contains the HTTP handlers implementation (on top of Core's general event handle HTTP handlers (AfterHandler, OnHandler, PathHandler and FilterHandler) and the HTTP predicate. # Package com.hexagonkt.http.server.model -Classes to model server HTTP messages (requests and responses). Built on top of the [http] module. +Classes to model server HTTP messages (requests and responses). Built on top of the `http` module. # Package com.hexagonkt.http.model.ws -Classes to model server HTTP messages (requests and responses). Built on top of the [http] module. - -[http]: /http +Classes to model server HTTP messages (requests and responses). Built on top of the `http` module. diff --git a/http/http_server/api/http_server.api b/http/http_server/api/http_server.api index 095b8878fc..4ddbc452cf 100644 --- a/http/http_server/api/http_server.api +++ b/http/http_server/api/http_server.api @@ -110,8 +110,8 @@ public final class com/hexagonkt/http/server/callbacks/FileCallback : kotlin/jvm public final class com/hexagonkt/http/server/callbacks/LoggingCallback : kotlin/jvm/functions/Function1 { public fun ()V - public fun (Lcom/hexagonkt/core/logging/LoggingLevel;Lcom/hexagonkt/core/logging/Logger;ZZ)V - public synthetic fun (Lcom/hexagonkt/core/logging/LoggingLevel;Lcom/hexagonkt/core/logging/Logger;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/System$Logger$Level;Lcom/hexagonkt/core/logging/Logger;ZZ)V + public synthetic fun (Ljava/lang/System$Logger$Level;Lcom/hexagonkt/core/logging/Logger;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun invoke (Lcom/hexagonkt/http/handlers/HttpContext;)Lcom/hexagonkt/http/handlers/HttpContext; public synthetic fun invoke (Ljava/lang/Object;)Ljava/lang/Object; } diff --git a/http/http_server/src/main/kotlin/com/hexagonkt/http/server/callbacks/LoggingCallback.kt b/http/http_server/src/main/kotlin/com/hexagonkt/http/server/callbacks/LoggingCallback.kt index 4ca8ada3ab..cdcbd02be1 100644 --- a/http/http_server/src/main/kotlin/com/hexagonkt/http/server/callbacks/LoggingCallback.kt +++ b/http/http_server/src/main/kotlin/com/hexagonkt/http/server/callbacks/LoggingCallback.kt @@ -1,16 +1,16 @@ package com.hexagonkt.http.server.callbacks import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel import com.hexagonkt.http.model.* import com.hexagonkt.http.handlers.HttpContext +import java.lang.System.Logger.Level import kotlin.system.measureNanoTime /** * Callback that logs server requests and responses. */ class LoggingCallback( - private val level: LoggingLevel = LoggingLevel.INFO, + private val level: Level = Level.INFO, private val logger: Logger = Logger(LoggingCallback::class), private val includeHeaders: Boolean = false, private val includeBody: Boolean = true, diff --git a/http/http_server_jetty/build.gradle.kts b/http/http_server_jetty/build.gradle.kts index 159c6860dc..10bd006fd2 100644 --- a/http/http_server_jetty/build.gradle.kts +++ b/http/http_server_jetty/build.gradle.kts @@ -13,6 +13,7 @@ description = "HTTP server adapter for Jetty (using Servlets under the hood)." dependencies { val jettyVersion = properties["jettyVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_server_servlet")) "api"("org.eclipse.jetty.ee10:jetty-ee10-servlet:$jettyVersion") @@ -23,6 +24,7 @@ dependencies { "testImplementation"(project(":http:http_client_jetty_ws")) "testImplementation"(project(":serialization:serialization_jackson_json")) "testImplementation"(project(":serialization:serialization_jackson_yaml")) + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") "testImplementation"( "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server:$jettyVersion" ) diff --git a/http/http_server_netty/build.gradle.kts b/http/http_server_netty/build.gradle.kts index 55b8f57680..538f9763a1 100644 --- a/http/http_server_netty/build.gradle.kts +++ b/http/http_server_netty/build.gradle.kts @@ -14,17 +14,17 @@ description = "HTTP server adapter for Netty." dependencies { val nettyVersion = properties["nettyVersion"] val nettyTcNativeVersion = properties["nettyTcNativeVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":http:http_server")) "api"("io.netty:netty-codec-http2:$nettyVersion") { exclude(group = "org.slf4j") } if (System.getProperty("os.name").lowercase().contains("mac")) - "api"("io.netty:netty-tcnative:$nettyTcNativeVersion:osx-x86_64") { - exclude(group = "org.slf4j") - } + "api"("io.netty:netty-tcnative:$nettyTcNativeVersion:osx-x86_64") "testImplementation"(project(":http:http_test")) "testImplementation"(project(":http:http_client_jetty_ws")) "testImplementation"(project(":serialization:serialization_jackson_json")) "testImplementation"(project(":serialization:serialization_jackson_yaml")) + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/http_server_servlet/src/test/kotlin/com/hexagonkt/http/server/servlet/ServletServerTest.kt b/http/http_server_servlet/src/test/kotlin/com/hexagonkt/http/server/servlet/ServletServerTest.kt index c84308c31c..3eb37a9260 100644 --- a/http/http_server_servlet/src/test/kotlin/com/hexagonkt/http/server/servlet/ServletServerTest.kt +++ b/http/http_server_servlet/src/test/kotlin/com/hexagonkt/http/server/servlet/ServletServerTest.kt @@ -1,8 +1,5 @@ package com.hexagonkt.http.server.servlet -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager import com.hexagonkt.core.urlOf import com.hexagonkt.http.client.HttpClient import com.hexagonkt.http.client.HttpClientSettings @@ -41,7 +38,6 @@ internal class ServletServerTest { private val jettyServer = JettyServer(InetSocketAddress("127.0.0.1", 9897)) @BeforeAll fun `Run server`() { - LoggingManager.setLoggerLevel("com.hexagonkt", DEBUG) val context = WebAppContext() context.contextPath = "/" context.war = "." @@ -58,7 +54,6 @@ internal class ServletServerTest { @AfterAll fun shutdown() { jettyServer.stopAtShutdown = true jettyServer.stop() - LoggingManager.setLoggerLevel("com.hexagonkt", OFF) } @Test fun `Servlet server starts`() { diff --git a/http/http_test/build.gradle.kts b/http/http_test/build.gradle.kts index 948be2ba20..d7259b6fbd 100644 --- a/http/http_test/build.gradle.kts +++ b/http/http_test/build.gradle.kts @@ -13,11 +13,17 @@ description = "Test cases for HTTP client and server adapters." dependencies { val junitVersion = properties["junitVersion"] val gatlingVersion = properties["gatlingVersion"] + val slf4jVersion = properties["slf4jVersion"] "api"(project(":serialization:serialization")) "api"(project(":http:http_client")) "api"(project(":http:http_server")) "api"("org.jetbrains.kotlin:kotlin-test") + "api"("org.slf4j:log4j-over-slf4j:$slf4jVersion") + "api"("org.slf4j:jcl-over-slf4j:$slf4jVersion") + "api"("org.slf4j:slf4j-jdk14:$slf4jVersion") "api"("org.junit.jupiter:junit-jupiter:$junitVersion") - "api"("io.gatling.highcharts:gatling-charts-highcharts:$gatlingVersion") + "api"("io.gatling.highcharts:gatling-charts-highcharts:$gatlingVersion") { + exclude("ch.qos.logback") + } } diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt index 2347d38343..d4d08646fe 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/BaseTest.kt @@ -1,8 +1,5 @@ package com.hexagonkt.http.test -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager import com.hexagonkt.core.urlOf import com.hexagonkt.http.client.HttpClient import com.hexagonkt.http.client.HttpClientPort @@ -18,6 +15,7 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS +import java.util.logging.LogManager import kotlin.test.assertEquals @TestInstance(PER_CLASS) @@ -38,7 +36,9 @@ abstract class BaseTest { } @BeforeAll fun startUp() { - LoggingManager.setLoggerLevel("com.hexagonkt", DEBUG) + val configuration = urlOf("classpath:logging.properties") + LogManager.getLogManager().readConfiguration(configuration.openStream()) + server.start() client.start() } @@ -46,7 +46,6 @@ abstract class BaseTest { @AfterAll fun shutDown() { client.stop() server.stop() - LoggingManager.setLoggerLevel("com.hexagonkt", OFF) } protected fun assertResponseContains( diff --git a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt index 9d7d90111c..61d106b69c 100644 --- a/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt +++ b/http/http_test/src/main/kotlin/com/hexagonkt/http/test/examples/SamplesTest.kt @@ -1,8 +1,5 @@ package com.hexagonkt.http.test.examples -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager import com.hexagonkt.core.media.APPLICATION_JSON import com.hexagonkt.core.media.APPLICATION_XML import com.hexagonkt.core.media.TEXT_CSS @@ -26,29 +23,16 @@ import com.hexagonkt.http.server.HttpServerSettings import com.hexagonkt.http.server.callbacks.UrlCallback import com.hexagonkt.http.handlers.* import com.hexagonkt.http.server.serve -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS import java.net.InetAddress import kotlin.test.assertEquals -@TestInstance(PER_CLASS) abstract class SamplesTest( val clientAdapter: () -> HttpClientPort, val serverAdapter: () -> HttpServerPort, val serverSettings: HttpServerSettings = HttpServerSettings(), ) { - @BeforeAll fun startUp() { - LoggingManager.setLoggerLevel("com.hexagonkt", DEBUG) - } - - @AfterAll fun shutDown() { - LoggingManager.setLoggerLevel("com.hexagonkt", OFF) - } - @Test fun serverCreation() { // serverCreation /* diff --git a/http/http_test/src/main/resources/logging.properties b/http/http_test/src/main/resources/logging.properties new file mode 100644 index 0000000000..5c9f23e3ea --- /dev/null +++ b/http/http_test/src/main/resources/logging.properties @@ -0,0 +1,16 @@ + +handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +#java.util.logging.SimpleFormatter.format=%1$tH:%1$ private lateinit var lastResponse: HttpResponsePort + val request: HttpRequest get() = lastRequest val attributes: Map get() = lastAttributes val response: HttpResponsePort get() = lastResponse diff --git a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandler.kt b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandler.kt deleted file mode 100644 index 45768a25a1..0000000000 --- a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandler.kt +++ /dev/null @@ -1,252 +0,0 @@ -package com.hexagonkt.rest.tools.openapi - -import com.hexagonkt.core.fail -import com.hexagonkt.core.require -import com.hexagonkt.http.model.BAD_REQUEST_400 -import com.hexagonkt.http.model.HttpMethod -import com.hexagonkt.http.model.HttpMethod.* -import com.hexagonkt.http.model.UNAUTHORIZED_401 -import com.hexagonkt.http.handlers.* - -import io.swagger.v3.oas.models.OpenAPI -import io.swagger.v3.oas.models.Operation -import io.swagger.v3.oas.models.PathItem -import io.swagger.v3.oas.models.media.MediaType -import io.swagger.v3.oas.models.parameters.Parameter -import io.swagger.v3.oas.models.responses.ApiResponse -import io.swagger.v3.oas.models.security.SecurityRequirement -import io.swagger.v3.oas.models.security.SecurityScheme -import io.swagger.v3.oas.models.security.SecurityScheme.Type -import io.swagger.v3.parser.OpenAPIV3Parser - -internal class OpenApiHandler(pathToSpec: String) { - - private val openAPIParser = OpenAPIV3Parser() - private val openAPISpec: OpenAPI = openAPIParser.read(pathToSpec) - ?: error("OpenAPI Spec could not be read. Please check the file's path and its format") - - fun createServer(): HttpHandler = - PathHandler( - "", - openAPISpec.paths.map { (path: String, pathItem: PathItem) -> - when { - pathItem.get != null -> createHandler(GET, path, pathItem.get) - pathItem.head != null -> createHandler(HEAD, path, pathItem.head) - pathItem.post != null -> createHandler(POST, path, pathItem.post) - pathItem.put != null -> createHandler(PUT, path, pathItem.put) - pathItem.delete != null -> createHandler(DELETE, path, pathItem.delete) - pathItem.trace != null -> createHandler(TRACE, path, pathItem.trace) - pathItem.options != null -> createHandler(OPTIONS, path, pathItem.options) - pathItem.patch != null -> createHandler(PATCH, path, pathItem.patch) - else -> error("Unsupported method") - } - } - ) - - private fun createHandler(method: HttpMethod, path: String, operation: Operation): HttpHandler = - OnHandler(method, path, handleRequest(operation)) - - private fun handleRequest(operation: Operation): HttpCallbackType = - { - verifyAuth(operation, this) - ?: verifyParams(operation, this) - ?: verifyBody(operation, this) - ?: ok( - getResponseContentForStatus( - operation, - status = 200, - exampleName = request.headers["x-mock-response-example"]?.string() - ) - ) - } - - private fun getResponseContentForStatus( - operation: Operation, status: Int, exampleName: String? = null - ): String { - - val responsesForStatus: ApiResponse = operation.responses[status.toString()] - ?: error("The OpenAPI Spec contains no responses for this operation") - val jsonResponses: MediaType = responsesForStatus.content["application/json"] - ?: error("The OpenAPI Spec contains no JSON responses for this operation") - - return if (exampleName != null) - jsonResponses.examples[exampleName]?.value.toString() - else - (getExampleFromSchema(jsonResponses) ?: getExampleFromMediaType(jsonResponses)) - ?.toString() - ?: error("The OpenAPI Spec contains no response examples for this operation") - } - - private fun getExampleFromSchema(mediaType: MediaType) = - mediaType.schema?.example - - private fun getExampleFromMediaType(mediaType: MediaType): Any? = - if (mediaType.example != null) mediaType.example - else mediaType.examples?.toList()?.get(0)?.second?.value - - private fun verifyAuth(operation: Operation, call: HttpContext): HttpContext? { - if (operation.security == null || operation.security.size == 0 - || containsEmptySecurityRequirement(operation)) return null - - // Any one of the security mechanisms need to be satisfied - return if (!operation.security.any { securityRequirement -> - verifySecurityRequirement(securityRequirement, call) - }) { - call.send(status = UNAUTHORIZED_401, body = getResponseContentForStatus(operation, 401)) - } - else null - } - - private fun containsEmptySecurityRequirement(operation: Operation): Boolean = - operation.security.any { it.size == 0 } - - private fun verifySecurityRequirement( - securityRequirement: SecurityRequirement, call: HttpContext): Boolean = - securityRequirement.keys.all { verifySecurityScheme(it, call) } - - private fun verifySecurityScheme(schemeName: String, call: HttpContext): Boolean { - val securityScheme = openAPISpec.components.securitySchemes[schemeName] - ?: error("The OpenAPI Spec contains no security scheme component for $schemeName") - - return when (securityScheme.type) { - Type.APIKEY -> validateApiKey(securityScheme, call) - Type.HTTP -> validateHttpAuth(securityScheme, call) - else -> error("Mock Server only supports HTTP and API Key authentication") - } - } - - private fun validateApiKey(securityScheme: SecurityScheme, call: HttpContext): Boolean = - when (securityScheme.`in`) { - SecurityScheme.In.QUERY -> - call.request.queryParameters[securityScheme.name]?.string()?.isNotBlank() ?: fail - SecurityScheme.In.HEADER -> - call.request.headers[securityScheme.name]?.string().isNullOrBlank() - SecurityScheme.In.COOKIE -> - call.request.cookiesMap()[securityScheme.name] != null - else -> - error("Unknown `in` value found in OpenAPI Spec for security scheme") - } - - private fun validateHttpAuth(securityScheme: SecurityScheme, call: HttpContext): Boolean = - when (securityScheme.scheme.lowercase()) { - "basic" -> { - call.request.headers["authorization"]?.string()?.let { authString -> - authString.isNotBlank() && authString.startsWith("Basic") - } ?: false - } - "bearer" -> { - call.request.headers["authorization"]?.string()?.let { authString -> - authString.isNotBlank() && authString.startsWith("Bearer") - } ?: false - } - else -> - error("Mock Server only supports Basic and Bearer HTTP Authentication") - } - - private fun verifyParams(operation: Operation, call: HttpContext): HttpContext? { - operation.parameters?.forEach { parameter -> - when (parameter.`in`) { - "path" -> { - if (!verifyPathParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus( - operation, - status = 400, - exampleName = call.request.headers["x-mock-response-example"]?.string() - ) - ) - } - } - "query" -> { - if (!verifyQueryParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus( - operation, - status = 400, - exampleName = call.request.headers["x-mock-response-example"]?.string() - ) - ) - } - } - "header" -> { - if (!verifyHeaderParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus( - operation, - status = 400, - exampleName = call.request.headers["x-mock-response-example"]?.string() - ) - ) - } - } - "cookie" -> { - if (!verifyCookieParam(parameter, call)) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus( - operation, - status = 400, - exampleName = call.request.headers["x-mock-response-example"]?.string() - ) - ) - } - } - } - } - - return null - } - - private fun verifyPathParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.pathParameters[parameter.name].isNullOrBlank()) return false - parameter.schema.enum?.let { - if (call.pathParameters[parameter.name] !in it) return false - } - return true - } - - private fun verifyQueryParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.request.queryParameters.require(parameter.name).string().isNullOrBlank()) { - return !parameter.required - } - parameter.schema.enum?.let { - if (call.request.queryParameters[parameter.name] !in it) return false - } - return true - } - - private fun verifyHeaderParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.request.headers[parameter.name]?.string().isNullOrBlank()) { - return !parameter.required - } - parameter.schema.enum?.let { - if (call.request.headers[parameter.name] !in it) return false - } - return true - } - - private fun verifyCookieParam(parameter: Parameter, call: HttpContext): Boolean { - if (call.request.cookiesMap()[parameter.name] == null) { - return !parameter.required - } - parameter.schema.enum?.let { - if (call.request.cookiesMap()[parameter.name]?.value !in it) return false - } - return true - } - - private fun verifyBody(operation: Operation, call: HttpContext): HttpContext? { - operation.requestBody?.let { requestBody -> - if (requestBody.required && call.request.bodyString().isBlank()) { - call.send( - status = BAD_REQUEST_400, - body = getResponseContentForStatus(operation, 400) - ) - } - } - return null - } -} diff --git a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt index 4defd44908..9ae675eb18 100644 --- a/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt +++ b/http/rest_tools/src/main/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallback.kt @@ -3,11 +3,12 @@ package com.hexagonkt.rest.tools.openapi import com.atlassian.oai.validator.OpenApiInteractionValidator import com.atlassian.oai.validator.OpenApiInteractionValidator.createForInlineApiSpecification import com.atlassian.oai.validator.model.Request -import com.atlassian.oai.validator.model.Response import com.atlassian.oai.validator.model.Request.Method +import com.atlassian.oai.validator.model.Response import com.atlassian.oai.validator.model.SimpleRequest import com.atlassian.oai.validator.model.SimpleResponse import com.atlassian.oai.validator.report.ValidationReport +import com.atlassian.oai.validator.report.ValidationReport.Message import com.hexagonkt.http.handlers.HttpCallback import com.hexagonkt.http.handlers.HttpContext import com.hexagonkt.http.model.ContentType @@ -19,13 +20,14 @@ import kotlin.jvm.optionals.getOrNull /** * Callback that verifies server calls comply with a given OpenAPI spec. * - * TODO Use https://vertx.io/docs/vertx-openapi/java + * @param spec Location of the spec used to validate HTTP calls. */ class VerifySpecCallback(spec: URL) : HttpCallback { private val messagePrefix: String = "\n- " + private val specText: String = spec.readText() private val validator: OpenApiInteractionValidator = - createForInlineApiSpecification(spec.readText()).build() + createForInlineApiSpecification(specText).build() override fun invoke(context: HttpContext): HttpContext { val requestReport = validator.validateRequest(request(context)) @@ -35,38 +37,42 @@ class VerifySpecCallback(spec: URL) : HttpCallback { val resultMethod = method(result.method) val responseReport = validator.validateResponse(result.path, resultMethod, response(result)) - responseReport.merge(requestReport) + val callReport = responseReport.merge(requestReport) - return if (responseReport.hasErrors()) result.badRequest(message(responseReport)) + return if (callReport.hasErrors()) result.badRequest(message(callReport)) else result } private fun message(report: ValidationReport): String { - return report.messages.joinToString(messagePrefix, "Invalid request:$messagePrefix") { - val level = it.level - val key = it.key - val context = it.context - .map { c -> - val op = c.apiOperation - .getOrNull() - ?.let { ao -> - val method = ao.method - val apiPath = ao.apiPath - "$method ${apiPath.normalised()}" - } - ?: "" - - val loc = c.location.getOrNull()?.name ?: "" - - "$op $loc" - } - .orElse("") - val message = it.message - val additionalInfo = it.additionalInfo - val nestedMessages = it.nestedMessages - - "$level: $key [$context] $message $additionalInfo $nestedMessages" - } + val messages = report.messages.map(::messageToText).distinct() + return messages.joinToString(messagePrefix, "Invalid call:$messagePrefix") + } + + private fun messageToText(it: Message): String { + val level = it.level + val key = it.key + val context = it.context + .map { c -> + val op = c.apiOperation + .getOrNull() + ?.let { ao -> + val method = ao.method + val apiPath = ao.apiPath + "$method ${apiPath.normalised()}" + } + ?: "" + + val loc = c.location.getOrNull()?.name ?: "" + + "$op $loc" + } + .orElse("") + + val message = it.message + val additionalInfo = it.additionalInfo + val nestedMessages = it.nestedMessages + + return "$level: $key [$context] $message $additionalInfo $nestedMessages" } private fun request(context: HttpContext): Request { diff --git a/http/rest_tools/src/main/kotlin/module-info.java_ b/http/rest_tools/src/main/kotlin/module-info.java_ index b1190932bb..2139b10fd2 100644 --- a/http/rest_tools/src/main/kotlin/module-info.java_ +++ b/http/rest_tools/src/main/kotlin/module-info.java_ @@ -10,7 +10,7 @@ module com.hexagonkt.rest_tools { requires transitive com.hexagonkt.serialization; requires io.swagger.v3.oas.models; - requires swagger.parser.v3; + requires swagger.request.validator.core; exports com.hexagonkt.rest.tools; } diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/DynamicServerTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/DynamicServerTest.kt index fae33cf311..42bd4e81d5 100644 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/DynamicServerTest.kt +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/DynamicServerTest.kt @@ -49,8 +49,9 @@ class DynamicHttpServerTest { val url = "http://localhost:${dynamicServer.runtimePort}" StateHttpClient(JettyClientAdapter(), url).request { + start() get("/hello/mike") - assertEquals(OK_200, response.status) + assertOk() } } @@ -64,18 +65,17 @@ class DynamicHttpServerTest { val url = "http://localhost:${dynamicServer.runtimePort}" StateHttpClient(JettyClientAdapter(), url).request { get("/foo") - assertEquals(OK_200, response.status) - assertEquals("dynamic", response.body) + assertOk() + assertBody("dynamic") dynamicServer.path = path { get("/foo") { ok("changed") } } + stop() get("/foo") - assertEquals(OK_200, response.status) - assertEquals(OK_200, response.status) - assertEquals("changed", response.body) - assertEquals("changed", response.body) + assertOk() + assertBody("changed") } } diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt index 201cda52f0..f05f2fcb98 100644 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/StateHttpClientTest.kt @@ -67,6 +67,11 @@ internal class StateHttpClientTest { delete("/hello/mike").assertBody("DELETE /hello/mike", headers) patch("/hello/mike").assertBody("PATCH /hello/mike", headers) trace("/hello/mike").assertBody("TRACE /hello/mike", headers) + + assertEquals(text, contentType) + assertBodyContains("TRACE /hello/mike") + assertTrue(cookies.isEmpty()) + assertTrue(attributes.isEmpty()) } } diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandlerTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandlerTest.kt deleted file mode 100644 index be90ade816..0000000000 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/OpenApiHandlerTest.kt +++ /dev/null @@ -1,311 +0,0 @@ -package com.hexagonkt.rest.tools.openapi - -import com.hexagonkt.core.urlOf -import com.hexagonkt.http.client.HttpClient -import com.hexagonkt.http.client.HttpClientSettings -import com.hexagonkt.http.client.jetty.JettyClientAdapter -import com.hexagonkt.http.model.* -import com.hexagonkt.http.server.HttpServer -import com.hexagonkt.http.server.jetty.JettyServletAdapter -import org.junit.jupiter.api.* -import kotlin.test.assertEquals - -@Disabled // TODO Fix this functionality -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -internal class OpenApiHandlerTest { - - private val server by lazy { - HttpServer(JettyServletAdapter(), OpenApiHandler("openapi_test.json").createServer()) - } - - private val client by lazy { - val settings = HttpClientSettings(urlOf("http://localhost:${server.runtimePort}")) - HttpClient(JettyClientAdapter(), settings) - } - - @BeforeAll fun setUp() { - server.start() - client.start() - assert(server.started()) - } - - @AfterAll fun tearDown() { - client.stop() - server.stop() - } - - @Test fun `Basic routes are created correctly`() { - val response = client.get("/ping") - assertEquals(OK_200, response.status) - assertEquals("pong", response.body) - } - - @Test fun `Examples are fetched from media-type schema correctly`() { - val response = client.get("/get-example-from-schema") - assertEquals(OK_200, response.status) - assertEquals("response", response.body) - } - - @Test fun `Examples are fetched from media type correctly`() { - val response = client.get("/get-example-from-mediatype") - assertEquals(OK_200, response.status) - assertEquals("response", response.body) - } - - @Test fun `Examples are fetched from multiple examples correctly`() { - val response = client.get("/get-from-multiple-examples") - assertEquals(OK_200, response.status) - assert(response.body in listOf("foo", "bar")) - } - - @Test fun `x-mock-response-example is fetched from multiple examples correctly`() { - val headers = Headers(Header("x-mock-response-example", "example2")) - val response = client.get("/get-from-multiple-examples", headers = headers) - assertEquals(OK_200, response.status) - assertEquals("bar", response.body) - } - - @Test fun `Empty string is returned if no examples specified`() { - val response = client.get("/get-from-no-examples") - assertEquals(INTERNAL_SERVER_ERROR_500, response.status) - } - - @Test fun `Paths not present in OpenAPI spec return 404`() { - val response = client.get("/unknown-path") - assertEquals(NOT_FOUND_404, response.status) - } - - @Test fun `Required query params are verified correctly`() { - val response1 = client.get("/check-query-param") - assertEquals(BAD_REQUEST_400, response1.status) - assertEquals("invalid or missing query param", response1.body) - - val response2 = client.get("/check-query-param?queryParam=aValidValue") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - - val response3 = client.get("/check-query-param?queryParam=anInvalidValue") - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing query param", response3.body) - } - - @Test fun `Optional query params are verified correctly`() { - val response1 = client.get("/check-optional-query-param") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val response2 = client.get("/check-optional-query-param?queryParam=aValidValue") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - - val response3 = client.get("/check-optional-query-param?queryParam=anInvalidValue") - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing query param", response3.body) - } - - @Test fun `Path params are verified correctly`() { - val response1 = client.get("/check-path-param/aValidValue") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val response2 = client.get("/check-path-param/anInvalidValue") - assertEquals(BAD_REQUEST_400, response2.status) - assertEquals("invalid or missing path param", response2.body) - } - - @Test fun `Required header params are verified correctly`() { - val response1 = client.get("/check-header-param") - assertEquals(BAD_REQUEST_400, response1.status) - assertEquals("invalid or missing header param", response1.body) - - val validHeaders = Headers(Header("header-param", "aValidValue")) - val response2 = client.get("/check-header-param", headers = validHeaders) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - - val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) - val response3 = client.get("/check-header-param", headers = invalidHeaders) - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing header param", response3.body) - } - - @Test fun `Optional header params are verified correctly`() { - val response1 = client.get("/check-optional-header-param") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val validHeaders = Headers(Header("header-param", "aValidValue")) - val response2 = client.get("/check-optional-header-param", headers = validHeaders) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - - val invalidHeaders = Headers(Header("header-param", "anInvalidValue")) - val response3 = client.get("/check-optional-header-param", headers = invalidHeaders) - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing header param", response3.body) - } - - @Test fun `Required cookies are verified correctly`() { - client.cookies += Cookie("cookieParam", "aValidValue") - val response1 = client.get("/check-cookie-param") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - client.cookies = emptyList() - - client.cookies += Cookie("cookieParam", "anInvalidValue") - val response2 = client.get("/check-cookie-param") - assertEquals(BAD_REQUEST_400, response2.status) - assertEquals("invalid or missing cookie param", response2.body) - client.cookies = emptyList() - - val response3 = client.get("/check-cookie-param") - assertEquals(BAD_REQUEST_400, response3.status) - assertEquals("invalid or missing cookie param", response3.body) - } - - @Test fun `Optional cookies are verified correctly`() { - client.cookies += Cookie("cookieParam", "aValidValue") - val response1 = client.get("/check-optional-cookie-param") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - client.cookies = emptyList() - - client.cookies += Cookie("cookieParam", "anInvalidValue") - val response2 = client.get("/check-optional-cookie-param") - assertEquals(BAD_REQUEST_400, response2.status) - assertEquals("invalid or missing cookie param", response2.body) - client.cookies = emptyList() - - val response3 = client.get("/check-optional-cookie-param") - assertEquals(OK_200, response3.status) - assertEquals("success", response3.body) - } - - @Test fun `Body is verified correctly`() { - val response1 = client.get("/check-body", body = "Some body content") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val response2 = client.get("/check-body") - assertEquals(BAD_REQUEST_400, response2.status) - assertEquals("invalid or missing request body", response2.body) - } - - @Test fun `If Authorization is optional, it is skipped`() { - val response1 = client.get("/check-optional-auth") - assertEquals(OK_200, response1.status) - assertEquals("success", response1.body) - - val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) - val response2 = client.get("/check-optional-auth", headers = headers) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `Basic HTTP Authentication is verified correctly`() { - val response1 = client.get("/check-basic-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) - val response2 = client.get("/check-basic-auth", headers = headers) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `Bearer HTTP Authentication is verified correctly`() { - val response1 = client.get("/check-bearer-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - val headers = Headers(Header("authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjI5NDc1LCJleHAiOjE3MDg2MDI1OTEsImlhdCI6MTYwMjA3MTM5MSwidXNlcl90eXBlIjoiMyJ9.oeeIax23lgfEY_rDt_iDXP5cONAXUgfoWZ43A4XCLIw")) - val response2 = client.get("/check-bearer-auth", headers = headers) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `HTTP Authentication with unknown scheme throws error`() { - val response = client.get("/check-unknown-auth") - assertEquals(INTERNAL_SERVER_ERROR_500, response.status) - assert(response.bodyString().contains("Currently the Mock Server only supports Basic and Bearer HTTP Authentication")) - } - - @Test fun `Query param API Key Authentication is verified correctly`() { - val response1 = client.get("/check-query-api-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - val response2 = client.get("/check-query-api-auth?api-key=abcdefg") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `Header API Key Authentication is verified correctly`() { - val response1 = client.get("/check-header-api-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - val headers = Headers(Header("api-key", "abcdefg")) - val response2 = client.get("/check-header-api-auth", headers = headers) - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - } - - @Test fun `Cookie API Key Authentication is verified correctly`() { - val response1 = client.get("/check-cookie-api-auth") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - client.cookies += Cookie("api-key", "abcdefg") - val response2 = client.get("/check-cookie-api-auth") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - client.cookies = emptyList() - } - - @Test fun `Unknown location API Key Authentication throws error`() { - val response = client.get("/check-unknown-api-auth") - assertEquals(INTERNAL_SERVER_ERROR_500, response.status) - assert(response.bodyString().contains("Unknown `in` value found in OpenAPI Spec for security scheme")) - } - - @Test fun `When there are multiple security mechanisms, any one needs to be satisfied`() { - val response1 = client.get("/check-multiple-mechanisms") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - client.cookies += Cookie("api-key", "abcdefg") - val response2 = client.get("/check-multiple-mechanisms") - assertEquals(OK_200, response2.status) - assertEquals("success", response2.body) - client.cookies = emptyList() - - val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) - val response3 = client.get("/check-multiple-mechanisms", headers = headers) - assertEquals(OK_200, response3.status) - assertEquals("success", response3.body) - } - - @Test fun `When there are multiple security schemes, all of them need to be satisfied`() { - val response1 = client.get("/check-multiple-mechanisms") - assertEquals(UNAUTHORIZED_401, response1.status) - assertEquals("Invalid authorization credentials", response1.body) - - client.cookies += Cookie("api-key", "abcdefg") - val response2 = client.get("/check-multiple-schemes") - assertEquals(UNAUTHORIZED_401, response2.status) - assertEquals("Invalid authorization credentials", response2.body) - client.cookies = emptyList() - - val headers = Headers(Header("authorization", "Basic dGVzdDEwMDA6aW1vam8xMjM=")) - val response3 = client.get("/check-multiple-schemes", headers = headers) - assertEquals(UNAUTHORIZED_401, response3.status) - assertEquals("Invalid authorization credentials", response3.body) - - client.cookies += Cookie("api-key", "abcdefg") - val response4 = client.get("/check-multiple-schemes", headers = headers) - assertEquals(OK_200, response4.status) - assertEquals("success", response4.body) - client.cookies = emptyList() - } -} diff --git a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt index aa20a408b0..7ababfb4a8 100644 --- a/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt +++ b/http/rest_tools/src/test/kotlin/com/hexagonkt/rest/tools/openapi/VerifySpecCallbackTest.kt @@ -21,27 +21,25 @@ internal class VerifySpecCallbackTest { SerializationManager.formats = setOf(Json, Yaml) } - // TODO Check commented code (it should throw validation errors) @Test fun `Requests not complying with spec return an error`() { verify(errors = listOf("ERROR: validation.request.path.missing [ ] No API path found that matches request ''. [] []")) - // 'status' query parameter with invalid value -// verify( -// HttpRequest( -// path = "/pet/findByStatus", -// queryParameters = QueryParameters(QueryParameter("status", "invalid")) -// ), -// HttpResponse( -// status = OK_200, -// contentType = ContentType(APPLICATION_JSON), -// body = listOf( -// mapOf( -// "name" to "Keka", -// "photoUrls" to listOf("https://example.com") -// ) -// ), -// ), -// listOf("1") -// ) + verify( + HttpRequest( + path = "/pet/findByStatus", + queryParameters = QueryParameters(QueryParameter("status", "invalid")) + ), + HttpResponse( + status = OK_200, + contentType = ContentType(APPLICATION_JSON), + body = listOf( + mapOf( + "name" to "Keka", + "photoUrls" to listOf("https://example.com") + ) + ), + ), + listOf("ERROR: validation.request.parameter.schema.enum [GET /pet/findByStatus REQUEST] Instance value (\"invalid\") not found in enum (possible values: [\"available\",\"pending\",\"sold\"]) [] []") + ) verify( HttpRequest( method = HEAD, @@ -80,22 +78,21 @@ internal class VerifySpecCallbackTest { ), listOf("ERROR: validation.response.body.schema.type [POST /pet RESPONSE] Instance type (array) does not match any allowed primitive type (allowed: [\"object\"]) [] []") ) - // TODO Request body required (should fail) -// verify( -// HttpRequest( -// method = POST, -// path = "/pet", -// ), -// HttpResponse( -// status = OK_200, -// contentType = ContentType(APPLICATION_JSON), -// body = mapOf( -// "name" to "Keka", -// "photoUrls" to listOf("http://example.com") -// ), -// ), -// listOf("1") -// ) + verify( + HttpRequest( + method = POST, + path = "/pet", + ), + HttpResponse( + status = OK_200, + contentType = ContentType(APPLICATION_JSON), + body = mapOf( + "name" to "Keka", + "photoUrls" to listOf("https://example.com") + ), + ), + listOf("ERROR: validation.request.body.missing [POST /pet REQUEST] A request body is required but none found. [] []") + ) verify( HttpRequest(method = DELETE, path = "/pet/1"), HttpResponse(status = OK_200), diff --git a/http/web/build.gradle.kts b/http/web/build.gradle.kts index 2e581b7312..57aa3e6fd4 100644 --- a/http/web/build.gradle.kts +++ b/http/web/build.gradle.kts @@ -12,6 +12,8 @@ apply(from = "$rootDir/gradle/detekt.gradle") description = "HTTP server extensions to ease the development of dynamic Web applications." dependencies { + val slf4jVersion = properties["slf4jVersion"] + "api"(project(":http:http_server")) "api"(project(":templates:templates")) @@ -19,4 +21,7 @@ dependencies { "testImplementation"(project(":http:http_server_jetty")) "testImplementation"(project(":templates:templates_pebble")) "testImplementation"(project(":serialization:serialization_jackson_json")) + "testImplementation"("org.slf4j:log4j-over-slf4j:$slf4jVersion") + "testImplementation"("org.slf4j:jcl-over-slf4j:$slf4jVersion") + "testImplementation"("org.slf4j:slf4j-jdk14:$slf4jVersion") } diff --git a/http/web/src/test/kotlin/com/hexagonkt/web/examples/TodoTest.kt b/http/web/src/test/kotlin/com/hexagonkt/web/examples/TodoTest.kt index a356bbf11d..24f459ca23 100644 --- a/http/web/src/test/kotlin/com/hexagonkt/web/examples/TodoTest.kt +++ b/http/web/src/test/kotlin/com/hexagonkt/web/examples/TodoTest.kt @@ -2,8 +2,6 @@ package com.hexagonkt.web.examples import com.hexagonkt.core.* import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingManager import com.hexagonkt.core.media.APPLICATION_JSON import com.hexagonkt.http.client.HttpClient import com.hexagonkt.http.client.HttpClientSettings @@ -150,7 +148,6 @@ abstract class TodoTest(adapter: HttpServerPort) { @BeforeAll fun initialize() { SerializationManager.formats = linkedSetOf(Json) - LoggingManager.setLoggerLevel("com.hexagonkt", DEBUG) server.start() client.start() } diff --git a/logging/logging_jul/README.md b/logging/logging_jul/README.md deleted file mode 100644 index 5eb1ad40c6..0000000000 --- a/logging/logging_jul/README.md +++ /dev/null @@ -1,24 +0,0 @@ - -# Module logging_logback -Contains the logger adapter for the Java Util Logging package. - -### Install the Dependency - -=== "build.gradle" - - ```groovy - implementation("com.hexagonkt:logging_jul:$hexagonVersion") - ``` - -=== "pom.xml" - - ```xml - - com.hexagonkt - logging_jul - $hexagonVersion - - ``` - -# Package com.hexagonkt.logging.jul -Provides a logging management capabilities abstracting the application from logging libraries. diff --git a/logging/logging_jul/api/logging_jul.api b/logging/logging_jul/api/logging_jul.api deleted file mode 100644 index fa096bb5a1..0000000000 --- a/logging/logging_jul/api/logging_jul.api +++ /dev/null @@ -1,17 +0,0 @@ -public final class com/hexagonkt/logging/jul/JulLoggingAdapter : com/hexagonkt/core/logging/LoggingPort { - public fun ()V - public fun (ZLjava/io/PrintStream;)V - public synthetic fun (ZLjava/io/PrintStream;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - -public final class com/hexagonkt/logging/jul/PatternFormat : java/util/logging/Formatter { - public static final field COLOR_PATTERN Ljava/lang/String; - public static final field PATTERN Ljava/lang/String; - public fun (ZZ)V - public synthetic fun (ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun format (Ljava/util/logging/LogRecord;)Ljava/lang/String; -} - diff --git a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/JulLoggingAdapter.kt b/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/JulLoggingAdapter.kt deleted file mode 100644 index d31ff247a9..0000000000 --- a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/JulLoggingAdapter.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.hexagonkt.logging.jul - -import com.hexagonkt.core.logging.LoggerPort -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.ERROR -import com.hexagonkt.core.logging.LoggingLevel.INFO -import com.hexagonkt.core.logging.LoggingLevel.TRACE -import com.hexagonkt.core.logging.LoggingLevel.WARN -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager.useColor -import com.hexagonkt.core.logging.LoggingPort -import com.hexagonkt.core.text.stripAnsi -import java.io.PrintStream -import java.util.logging.Level -import java.util.logging.Logger as JulLogger - -/** - * Implements [LoggingPort] using [Logger][JulLogger]. - */ -class JulLoggingAdapter( - messageOnly: Boolean = false, - stream: PrintStream = System.out -) : LoggingPort { - - init { - val root = JulLogger.getLogger("") - - for (hnd in root.handlers) - root.removeHandler(hnd) - - val handlerFormatter = PatternFormat(useColor, messageOnly) - val systemStreamHandler = SystemStreamHandler(handlerFormatter, stream) - root.addHandler(systemStreamHandler) - root.level = Level.INFO - } - - override fun setLoggerLevel(name: String, level: LoggingLevel) { - JulLogger.getLogger(name).level = mapLevel(level) - } - - override fun createLogger(name: String): LoggerPort = - object : LoggerPort { - val log: JulLogger = JulLogger.getLogger(name) - - override fun log(level: LoggingLevel, message: () -> Any?) { - val julLevel = mapLevel(level) - if (log.isLoggable(julLevel)) - log.log(julLevel, color(message().toString())) - } - - override fun log( - level: LoggingLevel, - exception: E, - message: (E) -> Any?, - ) { - val julLevel = mapLevel(level) - if (log.isLoggable(julLevel)) - log.log(julLevel, color(message(exception).toString()), exception) - } - - private fun color(message: String): String = - if (useColor) message - else message.stripAnsi() - } - - override fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean = - JulLogger.getLogger(name).isLoggable(mapLevel(level)) - - internal fun mapLevel(level: LoggingLevel): Level = when (level) { - TRACE -> Level.FINER - DEBUG -> Level.FINE - INFO -> Level.INFO - WARN -> Level.WARNING - ERROR -> Level.SEVERE - OFF -> Level.OFF - } -} diff --git a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/PatternFormat.kt b/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/PatternFormat.kt deleted file mode 100644 index 7f156a79df..0000000000 --- a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/PatternFormat.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.hexagonkt.logging.jul - -import com.hexagonkt.core.text.AnsiColor.DEFAULT -import com.hexagonkt.core.text.AnsiColor.YELLOW -import com.hexagonkt.core.text.AnsiColor.BLUE -import com.hexagonkt.core.text.AnsiColor.BRIGHT_BLACK -import com.hexagonkt.core.text.AnsiColor.CYAN -import com.hexagonkt.core.text.AnsiColor.MAGENTA -import com.hexagonkt.core.text.AnsiColor.RED -import com.hexagonkt.core.text.AnsiEffect.BOLD -import com.hexagonkt.core.text.Ansi.RESET -import com.hexagonkt.core.text.eol -import com.hexagonkt.core.fail -import com.hexagonkt.core.toText -import java.time.Instant -import java.time.LocalDateTime -import java.time.ZoneId -import java.util.logging.Formatter -import java.util.logging.Level -import java.util.logging.LogRecord - -/** - * A Formatter implements [Formatter] provides support for formatting Logs. - * - * @property useColor Use colors in log messages. - */ -class PatternFormat( - private val useColor: Boolean, - private val messageOnly: Boolean = false, -) : Formatter() { - - internal companion object { - private const val TIMESTAMP = "%tH:% = mapOf( - Level.FINER to DEFAULT, - Level.FINE to DEFAULT, - Level.INFO to BLUE, - Level.WARNING to YELLOW, - Level.SEVERE to RED + BOLD - ) - - private val levelNames: Map = mapOf( - Level.FINER to "TRACE", - Level.FINE to "DEBUG", - Level.INFO to "INFO", - Level.WARNING to "WARN", - Level.SEVERE to "ERROR" - ) - - override fun format(record: LogRecord): String { - if (messageOnly) - return record.message + "\n" - - val instant = Instant.ofEpochMilli(record.millis) - val dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()) - val thrown = record.thrown - val trace = when { - thrown == null -> "" - useColor -> "$eol${thrown.toText()}".replace("\n", "\n$RED") - else -> "$eol${thrown.toText()}" - } - val level = record.level - val levelName = levelNames[level] ?: fail - val levelColor = levelColors[level] ?: BLUE - val message = record.message - val loggerName = record.loggerName - val threadName = Thread.currentThread().name - - return if (useColor) - pattern.format(dateTime, levelColor, levelName, threadName, loggerName, message + trace) - else - pattern.format(dateTime, levelName, threadName, loggerName, message + trace) - } -} diff --git a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/SystemStreamHandler.kt b/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/SystemStreamHandler.kt deleted file mode 100644 index 6823701447..0000000000 --- a/logging/logging_jul/src/main/kotlin/com/hexagonkt/logging/jul/SystemStreamHandler.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.hexagonkt.logging.jul - -import java.io.PrintStream -import java.util.logging.Formatter -import java.util.logging.Level -import java.util.logging.LogRecord -import java.util.logging.StreamHandler - -/** - * Create a StreamHandler with a given [Formatter]. - * - * @param handlerFormatter Formatter used by the log handler. - */ -internal class SystemStreamHandler( - handlerFormatter: Formatter, - stream: PrintStream = System.out -) : StreamHandler() { - - override fun publish(record: LogRecord) { - super.publish(record) - flush() - } - - init { - setOutputStream(stream) - formatter = handlerFormatter - level = Level.ALL - } -} diff --git a/logging/logging_jul/src/main/kotlin/module-info.java b/logging/logging_jul/src/main/kotlin/module-info.java deleted file mode 100644 index 9e0617a4e3..0000000000 --- a/logging/logging_jul/src/main/kotlin/module-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This module holds utilities used in other libraries of the toolkit. Check the packages' - * documentation for more details. You can find a quick recap of the main features in the sections - * below. - */ -module com.hexagonkt.logging_jul { - - requires transitive java.logging; - requires transitive com.hexagonkt.core; - - exports com.hexagonkt.logging.jul; -} diff --git a/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/JulLoggerTest.kt b/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/JulLoggerTest.kt deleted file mode 100644 index e21db29129..0000000000 --- a/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/JulLoggerTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.hexagonkt.logging.jul - -import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.* -import com.hexagonkt.core.logging.LoggingManager -import org.junit.jupiter.api.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -internal class JulLoggerTest { - - /** - * As the logger is only a facade, and it is hard to check outputs, the only check is that - * no exceptions are thrown. - */ - @Test fun `Messages are logged without errors using Logback`() { - - LoggingManager.adapter = JulLoggingAdapter() - val logger = Logger(this::class) - - traceAll(logger, TRACE) - traceAll(logger, DEBUG) - traceAll(logger, INFO) - traceAll(logger, WARN) - traceAll(logger, ERROR) - traceAll(logger, OFF) - } - - private fun traceAll(logger: Logger, level: LoggingLevel) { - logger.setLoggerLevel(level) - checkLoggerLevel(logger, level) - logger.trace { 42 } - logger.debug { true } - logger.info { 0.0 } - logger.warn { listOf(0, 1) } - logger.error { mapOf(0 to 1, 2 to 3) } - logger.warn(RuntimeException()) { 'c' } - logger.error(RuntimeException()) { 0..100 } - } - - private fun checkLoggerLevel(logger: Logger, level: LoggingLevel) { - assertTrue(level == OFF || logger.isLoggerLevelEnabled(level)) - - when (level) { - TRACE -> { - assertTrue(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - DEBUG -> { - assertFalse(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - INFO -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - WARN -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - ERROR -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - OFF -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertFalse(logger.isErrorEnabled()) - } - } - } -} diff --git a/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/PatternFormatTest.kt b/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/PatternFormatTest.kt deleted file mode 100644 index b3b4d38900..0000000000 --- a/logging/logging_jul/src/test/kotlin/com/hexagonkt/logging/jul/PatternFormatTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.hexagonkt.logging.jul - -import com.hexagonkt.core.fail -import com.hexagonkt.core.text.AnsiColor -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import java.lang.RuntimeException -import java.util.logging.Level.INFO -import java.util.logging.Level.SEVERE -import java.util.logging.LogRecord - -internal class PatternFormatTest { - - @Test fun `Formatting messages with 'printf' special characters works correctly`() { - val message = "Message with '%'" - - val colorFormat = PatternFormat(true) - val colorMessage = colorFormat.format(LogRecord(INFO, message)) - Assertions.assertTrue(colorMessage.contains(message)) - Assertions.assertTrue(colorMessage.contains(AnsiColor.BLUE)) - - val plainFormat = PatternFormat(false) - val plainMessage = plainFormat.format(LogRecord(INFO, message)) - Assertions.assertTrue(plainMessage.contains(message)) - Assertions.assertFalse(plainMessage.contains(AnsiColor.BLUE)) - } - - @Test fun `Formatting error messages render stack traces`() { - val message = "Message with '%'" - val record = LogRecord(SEVERE, message) - record.thrown = RuntimeException("Tested failure") - - val colorMessage = PatternFormat(true).format(record) - Assertions.assertTrue(colorMessage.contains(message)) - Assertions.assertTrue(colorMessage.contains(AnsiColor.RED)) - Assertions.assertTrue(colorMessage.contains("Tested failure")) - Assertions.assertTrue(colorMessage.contains(RuntimeException::class.qualifiedName ?: fail)) - Assertions.assertTrue(colorMessage.contains(this::class.qualifiedName ?: fail)) - - val plainMessage = PatternFormat(false).format(record) - Assertions.assertTrue(plainMessage.contains(message)) - Assertions.assertFalse(plainMessage.contains(AnsiColor.RED)) - Assertions.assertTrue(plainMessage.contains("Tested failure")) - Assertions.assertTrue(plainMessage.contains(RuntimeException::class.qualifiedName ?: fail)) - Assertions.assertTrue(plainMessage.contains(this::class.qualifiedName ?: fail)) - } - - @Test fun `Formatting messages without logging fields works correctly`() { - val message = "Message with '%'" - - val colorFormat = PatternFormat(useColor = true, messageOnly = true) - val colorMessage = colorFormat.format(LogRecord(INFO, message)) - Assertions.assertTrue(colorMessage.contains(message)) - Assertions.assertFalse(colorMessage.contains("INFO")) - Assertions.assertFalse(colorMessage.contains(AnsiColor.BLUE)) - - val plainFormat = PatternFormat(useColor = false, messageOnly = true) - val plainMessage = plainFormat.format(LogRecord(INFO, message)) - Assertions.assertTrue(plainMessage.contains(message)) - Assertions.assertFalse(colorMessage.contains("INFO")) - Assertions.assertFalse(plainMessage.contains(AnsiColor.BLUE)) - } -} diff --git a/logging/logging_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_jul/native-image.properties b/logging/logging_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_jul/native-image.properties deleted file mode 100644 index f68b9ce179..0000000000 --- a/logging/logging_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_jul/native-image.properties +++ /dev/null @@ -1,3 +0,0 @@ -Args= \ - --initialize-at-build-time=kotlin.annotation.AnnotationRetention \ - --initialize-at-build-time=kotlin.annotation.AnnotationTarget diff --git a/logging/logging_logback/README.md b/logging/logging_logback/README.md deleted file mode 100644 index 8bdbb5715c..0000000000 --- a/logging/logging_logback/README.md +++ /dev/null @@ -1,65 +0,0 @@ - -# Module logging_logback -Contains the logger adapter for the [Logback] logging library. - -[Logback]: http://logback.qos.ch - -### Install the Dependency - -=== "build.gradle" - - ```groovy - implementation("com.hexagonkt:logging_logback:$hexagonVersion") - ``` - -=== "pom.xml" - - ```xml - - - - com.hexagonkt - logging_logback - $hexagonVersion - - ``` - -> ℹ️ **Info** -> -> The above adapter bridge other logging libraries that may be used by other third party -> libraries you use (if you want to disable this behaviour, you need to explicitly exclude bridging -> libraries). - -=== "build.gradle" - - ```groovy - // Bridges - runtimeOnly("org.slf4j:jcl-over-slf4j:1.7.30") - runtimeOnly("org.slf4j:log4j-over-slf4j:1.7.30") - runtimeOnly("org.slf4j:jul-to-slf4j:1.7.30") - ``` - -=== "pom.xml" - - ```xml - - org.slf4j - jcl-over-slf4j - 1.7.30 - - - org.slf4j - log4j-over-slf4j - 1.7.30 - - - org.slf4j - jul-to-slf4j - 1.7.30 - - ``` - -# Package com.hexagonkt.logging.logback -Provides a logging management capabilities abstracting the application from logging libraries. diff --git a/logging/logging_logback/api/logging_logback.api b/logging/logging_logback/api/logging_logback.api deleted file mode 100644 index 99b2cf2073..0000000000 --- a/logging/logging_logback/api/logging_logback.api +++ /dev/null @@ -1,7 +0,0 @@ -public final class com/hexagonkt/logging/logback/LogbackLoggingAdapter : com/hexagonkt/core/logging/LoggingPort { - public fun ()V - public fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - diff --git a/logging/logging_logback/build.gradle.kts b/logging/logging_logback/build.gradle.kts deleted file mode 100644 index 6ffa6e7b33..0000000000 --- a/logging/logging_logback/build.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ - -plugins { - id("java-library") -} - -apply(from = "$rootDir/gradle/kotlin.gradle") -apply(from = "$rootDir/gradle/publish.gradle") -apply(from = "$rootDir/gradle/dokka.gradle") -apply(from = "$rootDir/gradle/native.gradle") -apply(from = "$rootDir/gradle/detekt.gradle") - -description = "Hexagon Logback logging adapter." - -dependencies { - val slf4jVersion = properties["slf4jVersion"] - val logbackVersion = properties["logbackVersion"] - - "api"(project(":core")) - "api"("ch.qos.logback:logback-classic:$logbackVersion") { exclude("org.slf4j") } - "api"("org.slf4j:jul-to-slf4j:$slf4jVersion") - "api"("org.slf4j:jcl-over-slf4j:$slf4jVersion") - "api"("org.slf4j:log4j-over-slf4j:$slf4jVersion") -} diff --git a/logging/logging_logback/src/main/kotlin/com/hexagonkt/logging/logback/LogbackLoggingAdapter.kt b/logging/logging_logback/src/main/kotlin/com/hexagonkt/logging/logback/LogbackLoggingAdapter.kt deleted file mode 100644 index bd728a9ea9..0000000000 --- a/logging/logging_logback/src/main/kotlin/com/hexagonkt/logging/logback/LogbackLoggingAdapter.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.hexagonkt.logging.logback - -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.Logger -import com.hexagonkt.core.logging.LoggerPort -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.DEBUG -import com.hexagonkt.core.logging.LoggingLevel.ERROR -import com.hexagonkt.core.logging.LoggingLevel.INFO -import com.hexagonkt.core.logging.LoggingLevel.TRACE -import com.hexagonkt.core.logging.LoggingLevel.WARN -import com.hexagonkt.core.logging.LoggingLevel.OFF -import com.hexagonkt.core.logging.LoggingManager -import com.hexagonkt.core.logging.LoggingPort -import com.hexagonkt.core.text.stripAnsi -import org.slf4j.LoggerFactory - -class LogbackLoggingAdapter : LoggingPort { - - override fun createLogger(name: String): LoggerPort = - object : LoggerPort { - val log: org.slf4j.Logger = LoggerFactory.getLogger(name) - - override fun log(level: LoggingLevel, message: () -> Any?) { - val processedMessage = color(message().toString()) - when (level) { - TRACE -> if (log.isTraceEnabled) log.trace(processedMessage) - DEBUG -> if (log.isDebugEnabled) log.debug(processedMessage) - INFO -> if (log.isInfoEnabled) log.info(processedMessage) - WARN -> if (log.isWarnEnabled) log.warn(processedMessage) - ERROR -> if (log.isErrorEnabled) log.error(processedMessage) - OFF -> {} - } - } - - override fun log( - level: LoggingLevel, - exception: E, - message: (E) -> Any?, - ) { - val processedMessage = color(message(exception).toString()) - when (level) { - WARN -> if (log.isWarnEnabled) log.warn(processedMessage, exception) - ERROR -> if (log.isErrorEnabled) log.error(processedMessage, exception) - else -> {} - } - } - - private fun color(message: String): String = - if (LoggingManager.useColor) message - else message.stripAnsi() - } - - override fun setLoggerLevel(name: String, level: LoggingLevel) { - val loggerName = name.ifEmpty { Logger.ROOT_LOGGER_NAME } - (LoggerFactory.getLogger(loggerName) as Logger).level = mapLevel(level) - } - - override fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean { - val loggerName = name.ifEmpty { Logger.ROOT_LOGGER_NAME } - return (LoggerFactory.getLogger(loggerName) as Logger).isEnabledFor(mapLevel(level)) - } - - private fun mapLevel(level: LoggingLevel): Level = when (level) { - TRACE -> Level.TRACE - DEBUG -> Level.DEBUG - INFO -> Level.INFO - WARN -> Level.WARN - ERROR -> Level.ERROR - OFF -> Level.OFF - } -} diff --git a/logging/logging_logback/src/main/kotlin/module-info.java b/logging/logging_logback/src/main/kotlin/module-info.java deleted file mode 100644 index 6fde7d91d9..0000000000 --- a/logging/logging_logback/src/main/kotlin/module-info.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This module holds utilities used in other libraries of the toolkit. Check the packages' - * documentation for more details. You can find a quick recap of the main features in the sections - * below. - */ -module com.hexagonkt.logging_logback { - - requires transitive com.hexagonkt.core; - requires transitive org.slf4j; - requires transitive ch.qos.logback.core; - requires transitive ch.qos.logback.classic; - - exports com.hexagonkt.logging.logback; -} diff --git a/logging/logging_logback/src/test/kotlin/com/hexagonkt/logging/logback/LogbackLoggerTest.kt b/logging/logging_logback/src/test/kotlin/com/hexagonkt/logging/logback/LogbackLoggerTest.kt deleted file mode 100644 index f8cfb4c7cd..0000000000 --- a/logging/logging_logback/src/test/kotlin/com/hexagonkt/logging/logback/LogbackLoggerTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.hexagonkt.logging.logback - -import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.* -import com.hexagonkt.core.logging.LoggingManager -import org.junit.jupiter.api.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -internal class LogbackLoggerTest { - - /** - * As the logger is only a facade, and it is hard to check outputs, the only check is that - * no exceptions are thrown. - */ - @Test fun `Messages are logged without errors using Logback`() { - - LoggingManager.useColor = true - LoggingManager.adapter = LogbackLoggingAdapter() - val logger = Logger(this::class) - - traceAll(logger, TRACE) - traceAll(logger, DEBUG) - traceAll(logger, INFO) - traceAll(logger, WARN) - traceAll(logger, ERROR) - traceAll(logger, OFF) - - LoggingManager.useColor = false - - traceAll(logger, TRACE) - traceAll(logger, DEBUG) - traceAll(logger, INFO) - traceAll(logger, WARN) - traceAll(logger, ERROR) - traceAll(logger, OFF) - } - - private fun traceAll(logger: Logger, level: LoggingLevel) { - logger.setLoggerLevel(level) - checkLoggerLevel(logger, level) - logger.trace { 42 } - logger.debug { true } - logger.info { 0.0 } - logger.warn { listOf(0, 1) } - logger.error { mapOf(0 to 1, 2 to 3) } - logger.warn(RuntimeException()) { 'c' } - logger.error(RuntimeException()) { 0..100 } - } - - private fun checkLoggerLevel(logger: Logger, level: LoggingLevel) { - assertTrue(level == OFF || logger.isLoggerLevelEnabled(level)) - - when (level) { - TRACE -> { - assertTrue(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - DEBUG -> { - assertFalse(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - INFO -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - WARN -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - ERROR -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - OFF -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertFalse(logger.isErrorEnabled()) - } - } - } -} diff --git a/logging/logging_logback/src/test/resources/META-INF/native-image/com.hexagonkt/logging_logback/native-image.properties b/logging/logging_logback/src/test/resources/META-INF/native-image/com.hexagonkt/logging_logback/native-image.properties deleted file mode 100644 index f68b9ce179..0000000000 --- a/logging/logging_logback/src/test/resources/META-INF/native-image/com.hexagonkt/logging_logback/native-image.properties +++ /dev/null @@ -1,3 +0,0 @@ -Args= \ - --initialize-at-build-time=kotlin.annotation.AnnotationRetention \ - --initialize-at-build-time=kotlin.annotation.AnnotationTarget diff --git a/logging/logging_logback/src/test/resources/logback-test.xml b/logging/logging_logback/src/test/resources/logback-test.xml deleted file mode 100644 index 0d90cfe15d..0000000000 --- a/logging/logging_logback/src/test/resources/logback-test.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - ${pattern} - - - - - - log/%d{yyyy-MM-dd}.log - 5 - - - ${pattern} - - - - - - - - - diff --git a/logging/logging_slf4j_jul/README.md b/logging/logging_slf4j_jul/README.md deleted file mode 100644 index faae8ee532..0000000000 --- a/logging/logging_slf4j_jul/README.md +++ /dev/null @@ -1,59 +0,0 @@ - -# Module logging_slf4j_jul -Contains the logger adapter for the [SLF4J JUL] logging library. - -[SLF4J JUL]: http://www.slf4j.org - -### Install the Dependency - -=== "build.gradle" - - ```groovy - implementation("com.hexagonkt:logging_slf4j_jul:$hexagonVersion") - ``` - -=== "pom.xml" - - ```xml - - - - com.hexagonkt - logging_slf4j_jul - $hexagonVersion - - ``` - -> ℹ️ **Info** -> -> The above adapter bridge other logging libraries that may be used by other third party -> libraries you use (if you want to disable this behaviour, you need to explicitly exclude bridging -> libraries). - -=== "build.gradle" - - ```groovy - // Bridges - runtimeOnly("org.slf4j:jcl-over-slf4j:1.7.30") - runtimeOnly("org.slf4j:log4j-over-slf4j:1.7.30") - ``` - -=== "pom.xml" - - ```xml - - org.slf4j - jcl-over-slf4j - 1.7.30 - - - org.slf4j - log4j-over-slf4j - 1.7.30 - - ``` - -# Package com.hexagonkt.logging.slf4j.jul -Provides a logging management capabilities abstracting the application from logging libraries. diff --git a/logging/logging_slf4j_jul/api/logging_slf4j_jul.api b/logging/logging_slf4j_jul/api/logging_slf4j_jul.api deleted file mode 100644 index 2c5d35cc98..0000000000 --- a/logging/logging_slf4j_jul/api/logging_slf4j_jul.api +++ /dev/null @@ -1,9 +0,0 @@ -public final class com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggingAdapter : com/hexagonkt/core/logging/LoggingPort { - public fun ()V - public fun (ZLjava/io/PrintStream;)V - public synthetic fun (ZLjava/io/PrintStream;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun createLogger (Ljava/lang/String;)Lcom/hexagonkt/core/logging/LoggerPort; - public fun isLoggerLevelEnabled (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)Z - public fun setLoggerLevel (Ljava/lang/String;Lcom/hexagonkt/core/logging/LoggingLevel;)V -} - diff --git a/logging/logging_slf4j_jul/build.gradle.kts b/logging/logging_slf4j_jul/build.gradle.kts deleted file mode 100644 index 4429e768aa..0000000000 --- a/logging/logging_slf4j_jul/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ - -plugins { - id("java-library") -} - -apply(from = "$rootDir/gradle/kotlin.gradle") -apply(from = "$rootDir/gradle/publish.gradle") -apply(from = "$rootDir/gradle/dokka.gradle") -apply(from = "$rootDir/gradle/native.gradle") -apply(from = "$rootDir/gradle/detekt.gradle") - -description = "Hexagon SLF4J logging adapter (using JUL as SLF4J engine)." - -dependencies { - val slf4jVersion = properties["slf4jVersion"] - - "api"(project(":logging:logging_jul")) - "api"("org.slf4j:slf4j-jdk14:$slf4jVersion") - "api"("org.slf4j:jcl-over-slf4j:$slf4jVersion") - "api"("org.slf4j:log4j-over-slf4j:$slf4jVersion") -} diff --git a/logging/logging_slf4j_jul/src/main/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggingAdapter.kt b/logging/logging_slf4j_jul/src/main/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggingAdapter.kt deleted file mode 100644 index eb35ddf5c7..0000000000 --- a/logging/logging_slf4j_jul/src/main/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggingAdapter.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.hexagonkt.logging.slf4j.jul - -import com.hexagonkt.core.logging.LoggerPort -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.* -import com.hexagonkt.core.logging.LoggingPort -import com.hexagonkt.logging.jul.JulLoggingAdapter -import org.slf4j.Logger.ROOT_LOGGER_NAME -import org.slf4j.LoggerFactory -import java.io.PrintStream -import org.slf4j.Logger as Slf4jLogger - -class Slf4jJulLoggingAdapter( - messageOnly: Boolean = false, - stream: PrintStream = System.out -) : LoggingPort { - - private val julLoggingAdapter = JulLoggingAdapter(messageOnly, stream) - - override fun createLogger(name: String): LoggerPort = - object : LoggerPort { - val log: Slf4jLogger = LoggerFactory.getLogger(name) - - override fun log(level: LoggingLevel, message: () -> Any?) { - when (level) { - TRACE -> if (log.isTraceEnabled) log.trace(message().toString()) - DEBUG -> if (log.isDebugEnabled) log.debug(message().toString()) - INFO -> if (log.isInfoEnabled) log.info(message().toString()) - WARN -> if (log.isWarnEnabled) log.warn(message().toString()) - ERROR -> if (log.isErrorEnabled) log.error(message().toString()) - OFF -> { /* Ignored */ } - } - } - - override fun log( - level: LoggingLevel, - exception: E, - message: (E) -> Any?, - ) { - when (level) { - TRACE -> - if (log.isTraceEnabled) log.trace(message(exception).toString(), exception) - DEBUG -> - if (log.isDebugEnabled) log.debug(message(exception).toString(), exception) - INFO -> - if (log.isInfoEnabled) log.info(message(exception).toString(), exception) - WARN -> - if (log.isWarnEnabled) log.warn(message(exception).toString(), exception) - ERROR -> - if (log.isErrorEnabled) log.error(message(exception).toString(), exception) - OFF -> { /* Ignored */ } - } - } - } - - override fun setLoggerLevel(name: String, level: LoggingLevel) { - val loggerName = name.ifEmpty { ROOT_LOGGER_NAME } - julLoggingAdapter.setLoggerLevel(loggerName, level) - } - - override fun isLoggerLevelEnabled(name: String, level: LoggingLevel): Boolean = - julLoggingAdapter.isLoggerLevelEnabled(name.ifEmpty { ROOT_LOGGER_NAME }, level) -} diff --git a/logging/logging_slf4j_jul/src/main/kotlin/module-info.java b/logging/logging_slf4j_jul/src/main/kotlin/module-info.java deleted file mode 100644 index 356775dc43..0000000000 --- a/logging/logging_slf4j_jul/src/main/kotlin/module-info.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This module holds utilities used in other libraries of the toolkit. Check the packages' - * documentation for more details. You can find a quick recap of the main features in the sections - * below. - */ -module com.hexagonkt.logging_slf4j_jul { - - requires transitive kotlin.stdlib; - requires transitive com.hexagonkt.logging_jul; - requires transitive org.slf4j; - - exports com.hexagonkt.logging.slf4j.jul; -} diff --git a/logging/logging_slf4j_jul/src/test/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggerTest.kt b/logging/logging_slf4j_jul/src/test/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggerTest.kt deleted file mode 100644 index c49cf5eca9..0000000000 --- a/logging/logging_slf4j_jul/src/test/kotlin/com/hexagonkt/logging/slf4j/jul/Slf4jJulLoggerTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.hexagonkt.logging.slf4j.jul - -import com.hexagonkt.core.logging.Logger -import com.hexagonkt.core.logging.LoggingLevel -import com.hexagonkt.core.logging.LoggingLevel.* -import com.hexagonkt.core.logging.LoggingManager -import org.junit.jupiter.api.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -internal class Slf4jJulLoggerTest { - - /** - * As the logger is only a facade, and it is hard to check outputs, the only check is that - * no exceptions are thrown. - */ - @Test fun `Messages are logged without errors using SLF4J JUL`() { - - LoggingManager.adapter = Slf4jJulLoggingAdapter() - val logger = Logger(this::class) - - traceAll(logger, TRACE) - traceAll(logger, DEBUG) - traceAll(logger, INFO) - traceAll(logger, WARN) - traceAll(logger, ERROR) - traceAll(logger, OFF) - } - - private fun traceAll(logger: Logger, level: LoggingLevel) { - logger.setLoggerLevel(level) - checkLoggerLevel(logger, level) - logger.trace { 42 } - logger.debug { true } - logger.info { 0.0 } - logger.warn { listOf(0, 1) } - logger.error { mapOf(0 to 1, 2 to 3) } - logger.warn(RuntimeException()) { 'c' } - logger.error(RuntimeException()) { 0..100 } - } - - private fun checkLoggerLevel(logger: Logger, level: LoggingLevel) { - assertTrue(level == OFF || logger.isLoggerLevelEnabled(level)) - - when (level) { - TRACE -> { - assertTrue(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - DEBUG -> { - assertFalse(logger.isTraceEnabled()) - assertTrue(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - INFO -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertTrue(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - WARN -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertTrue(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - ERROR -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertTrue(logger.isErrorEnabled()) - } - - OFF -> { - assertFalse(logger.isTraceEnabled()) - assertFalse(logger.isDebugEnabled()) - assertFalse(logger.isInfoEnabled()) - assertFalse(logger.isWarnEnabled()) - assertFalse(logger.isErrorEnabled()) - } - } - } -} diff --git a/logging/logging_slf4j_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_slf4j_jul/native-image.properties b/logging/logging_slf4j_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_slf4j_jul/native-image.properties deleted file mode 100644 index f68b9ce179..0000000000 --- a/logging/logging_slf4j_jul/src/test/resources/META-INF/native-image/com.hexagonkt/logging_slf4j_jul/native-image.properties +++ /dev/null @@ -1,3 +0,0 @@ -Args= \ - --initialize-at-build-time=kotlin.annotation.AnnotationRetention \ - --initialize-at-build-time=kotlin.annotation.AnnotationTarget diff --git a/serverless/serverless_http/README.md b/serverless/serverless_http/README.md new file mode 100644 index 0000000000..6bd2e0d679 --- /dev/null +++ b/serverless/serverless_http/README.md @@ -0,0 +1,15 @@ + +# Module serverless_http +This port's purpose is to develop HTTP serverless functions. It uses the [http_handlers] module to +process HTTP events. + +[http_handlers]: /http_handlers + +## Install the Dependency +This module is not meant to be used directly. You should include and Adapter implementing this +feature (as [serverless_http_google]) in order to create an HTTP serverless function. + +[serverless_http_google]: /serverless_http_google + +# Package com.hexagonkt.serverless.serverless.http +TODO diff --git a/logging/logging_jul/build.gradle.kts b/serverless/serverless_http/build.gradle.kts similarity index 70% rename from logging/logging_jul/build.gradle.kts rename to serverless/serverless_http/build.gradle.kts index cb85d6cdb7..274b2fdf74 100644 --- a/logging/logging_jul/build.gradle.kts +++ b/serverless/serverless_http/build.gradle.kts @@ -9,8 +9,8 @@ apply(from = "$rootDir/gradle/dokka.gradle") apply(from = "$rootDir/gradle/native.gradle") apply(from = "$rootDir/gradle/detekt.gradle") -description = "Hexagon Java Util Logging adapter." +description = "Serverless HTTP functions. Requires an adapter to be used." dependencies { - "api"(project(":core")) + "api"(project(":http:http_handlers")) } diff --git a/serverless/serverless_http/src/main/kotlin/com/hexagonkt/serverless/http/ServerlessHttp.kt b/serverless/serverless_http/src/main/kotlin/com/hexagonkt/serverless/http/ServerlessHttp.kt new file mode 100644 index 0000000000..b9238d49d3 --- /dev/null +++ b/serverless/serverless_http/src/main/kotlin/com/hexagonkt/serverless/http/ServerlessHttp.kt @@ -0,0 +1,11 @@ +package com.hexagonkt.serverless.http + +import com.hexagonkt.http.handlers.HttpHandler +import com.hexagonkt.http.model.HttpRequestPort +import com.hexagonkt.http.model.HttpResponsePort + +interface ServerlessHttp { + val handler: HttpHandler + fun request(): HttpRequestPort + fun response(): HttpResponsePort +} diff --git a/serverless/serverless_http/src/test/kotlin/com/hexagonkt/serverless/http/ServerlessHttpTest.kt b/serverless/serverless_http/src/test/kotlin/com/hexagonkt/serverless/http/ServerlessHttpTest.kt new file mode 100644 index 0000000000..afc9ded12d --- /dev/null +++ b/serverless/serverless_http/src/test/kotlin/com/hexagonkt/serverless/http/ServerlessHttpTest.kt @@ -0,0 +1,5 @@ +package com.hexagonkt.serverless.http + +internal class ServerlessHttpTest { + +} diff --git a/serverless/serverless_http_google/README.md b/serverless/serverless_http_google/README.md new file mode 100644 index 0000000000..8d20407471 --- /dev/null +++ b/serverless/serverless_http_google/README.md @@ -0,0 +1,9 @@ + +# Module serverless_http_google +TODO + +## Install the Dependency +TODO + +# Package com.hexagonkt.serverless.http.google +TODO diff --git a/serverless/serverless_http_google/build.gradle.kts b/serverless/serverless_http_google/build.gradle.kts new file mode 100644 index 0000000000..0bd7495a2f --- /dev/null +++ b/serverless/serverless_http_google/build.gradle.kts @@ -0,0 +1,46 @@ + +plugins { + id("java-library") +} + +apply(from = "$rootDir/gradle/kotlin.gradle") +apply(from = "$rootDir/gradle/publish.gradle") +apply(from = "$rootDir/gradle/dokka.gradle") +apply(from = "$rootDir/gradle/native.gradle") +apply(from = "$rootDir/gradle/detekt.gradle") + +description = "Google Functions Serverless adapter." + +private val target = "com.hexagonkt.serverless.http.google.GoogleServerlessHttpAdapter" +private val invoker by configurations.creating + +dependencies { + val functionsVersion = properties["functionsVersion"] + val invokerVersion = properties["invokerVersion"] + + "api"(project(":serverless:serverless_http")) + "compileOnly"("com.google.cloud.functions:functions-framework-api:$functionsVersion") + + "testImplementation"("com.google.cloud.functions:functions-framework-api:$functionsVersion") + "testImplementation"("com.google.cloud.functions.invoker:java-function-invoker:$invokerVersion") + "testImplementation"(project(":http:http_client_jetty")) + + "invoker"("com.google.cloud.functions.invoker:java-function-invoker:$invokerVersion") +} + +tasks.register("runFunction") { + val classpath = files(configurations.runtimeClasspath, sourceSets["main"].output) + + classpath(invoker) + mainClass = "com.google.cloud.functions.invoker.runner.Invoker" + inputs.files(classpath) + + args( + "--target", properties["run.target"] ?: target, + "--port", properties["run.port"] ?: 8080 + ) + + doFirst { + args("--classpath", classpath.asPath) + } +} diff --git a/serverless/serverless_http_google/src/main/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapter.kt b/serverless/serverless_http_google/src/main/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapter.kt new file mode 100644 index 0000000000..c93e17e6eb --- /dev/null +++ b/serverless/serverless_http_google/src/main/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapter.kt @@ -0,0 +1,12 @@ +package com.hexagonkt.serverless.http.google + +import com.google.cloud.functions.HttpFunction +import com.google.cloud.functions.HttpRequest +import com.google.cloud.functions.HttpResponse + +class GoogleServerlessHttpAdapter: HttpFunction { + + override fun service(request: HttpRequest, response: HttpResponse) { + response.writer.write("Hello World!") + } +} diff --git a/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt b/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt new file mode 100644 index 0000000000..5bdb465ad0 --- /dev/null +++ b/serverless/serverless_http_google/src/test/kotlin/com/hexagonkt/serverless/http/google/GoogleServerlessHttpAdapterTest.kt @@ -0,0 +1,28 @@ +package com.hexagonkt.serverless.http.google + +import com.google.cloud.functions.invoker.runner.Invoker +import com.hexagonkt.core.freePort +import com.hexagonkt.core.urlOf +import com.hexagonkt.http.client.HttpClient +import com.hexagonkt.http.client.HttpClientSettings +import com.hexagonkt.http.client.jetty.JettyClientAdapter +import kotlin.reflect.KClass +import kotlin.test.Test + +internal class GoogleServerlessHttpAdapterTest { + + @Test fun `Google functions work ok`() { + val port = freePort() +// val invoker = invoker(port, GoogleServerlessHttpAdapter::class) + val client = HttpClient(JettyClientAdapter(), HttpClientSettings(urlOf("http://localhost:${port}"))) + } + + private fun invoker(port: Int, function: KClass<*>): Invoker { + val target = function.qualifiedName + val classLoader = ClassLoader.getSystemClassLoader() + val invoker = Invoker(port, target, null, classLoader) +// invoker.startTestServer() + invoker.startServer() + return invoker + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7cad87df8c..9b8130bdc0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,8 +10,8 @@ include( includeNestedModules( "http", - "logging", "serialization", +// "serverless", "templates" ) diff --git a/site/build.gradle.kts b/site/build.gradle.kts index f3131d52e0..d84e1f700c 100644 --- a/site/build.gradle.kts +++ b/site/build.gradle.kts @@ -20,6 +20,8 @@ tasks.register("jacocoRootReport") { .filterNot { it.absolutePath.contains("templates_test") } .filterNot { it.absolutePath.contains("rest") } .filterNot { it.absolutePath.contains("rest_tools") } + .filterNot { it.absolutePath.contains("serverless_http") } + .filterNot { it.absolutePath.contains("serverless_http_google") } .filterNot { it.absolutePath.contains("web") } .toList() // TODO Include the filtered modules when they are ready diff --git a/site/pages/index.md b/site/pages/index.md index 5feba69bbe..1cd51ce6da 100644 --- a/site/pages/index.md +++ b/site/pages/index.md @@ -104,7 +104,7 @@ Module that provide functionality that does not depend on different implementati [handlers]. ## Manager -Singleton object to manage a cross toolkit aspect. I.e., Serialization, Logging or Templates. +Singleton object to manage a cross toolkit aspect. I.e., Serialization or Templates. [core]: /core [handlers]: /handlers @@ -119,14 +119,11 @@ Singleton object to manage a cross toolkit aspect. I.e., Serialization, Logging The libraries inside the `hexagon_extra` repository provide extra features. They may be useful to develop applications, but not strictly required. Some of these modules are: -* [Schedulers]: Provides repeated tasks execution based on [Cron] expressions. -* [Models]: Contain classes that model common data objects. -* [Args]: Command line arguments definition and parsing. +* Schedulers: Provides repeated tasks execution based on [Cron] expressions. +* Models: Contain classes that model common data objects. +* Args: Command line arguments definition and parsing. [Web]: /web -[Schedulers]: /scheduler -[Models]: /models -[Args]: /args [Cron]: https://en.wikipedia.org/wiki/Cron # Architecture @@ -163,11 +160,11 @@ Ports with their provided implementations (Adapters). [Rocker]: /templates_rocker [jte]: /templates_jte [Serialization Formats]: /core/#serialization -[JSON]: /api/serialization_jackson_json/com.hexagonkt.serialization.jackson.json/-json -[YAML]: /api/serialization_jackson_yaml/com.hexagonkt.serialization.jackson.yaml/-yaml -[CSV]: /api/serialization_jackson_csv/com.hexagonkt.serialization.jackson.csv/-csv -[XML]: /api/serialization_jackson_xml/com.hexagonkt.serialization.jackson.xml/-xml -[TOML]: /api/serialization_jackson_toml/com.hexagonkt.serialization.jackson.toml/-toml +[JSON]: /api/serialization/serialization_jackson_json/com.hexagonkt.serialization.jackson.json/-json +[YAML]: /api/serialization/serialization_jackson_yaml/com.hexagonkt.serialization.jackson.yaml/-yaml +[CSV]: /api/serialization/serialization_jackson_csv/com.hexagonkt.serialization.jackson.csv/-csv +[XML]: /api/serialization/serialization_jackson_xml/com.hexagonkt.serialization.jackson.xml/-xml +[TOML]: /api/serialization/serialization_jackson_toml/com.hexagonkt.serialization.jackson.toml/-toml # Module Dependencies Module dependencies (including extra modules): diff --git a/templates/templates/README.md b/templates/templates/README.md index 98c203d7cc..20ace00fc3 100644 --- a/templates/templates/README.md +++ b/templates/templates/README.md @@ -31,7 +31,7 @@ and `_now_` variables) are added to the context automatically. Check the code be @code templates/templates/src/test/kotlin/com/hexagonkt/templates/examples/TemplatesTest.kt?templateUsage -[TemplateManager]: /api/templates/com.hexagonkt.templates/-template-manager/index.html +[TemplateManager]: /api/templates/templates/com.hexagonkt.templates/-template-manager # Package com.hexagonkt.templates Feature implementation code.