├── gradle.properties ├── settings.gradle ├── docs ├── HyLogger.jpg ├── HyLoggerTerminal.jpg └── backups │ └── old_readme.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ └── kotlin │ │ └── org │ │ └── hydev │ │ └── logger │ │ ├── appenders │ │ ├── Appender.kt │ │ ├── LogData.kt │ │ ├── ColorCompatibility.kt │ │ ├── ConsoleAppender.kt │ │ └── FileAppender.kt │ │ ├── LogLevel.kt │ │ ├── TimingLogger.kt │ │ ├── coloring │ │ ├── AnsiColorMode.kt │ │ ├── GradientPoint.kt │ │ ├── GradientPresets.kt │ │ └── LinearGradient.kt │ │ ├── FancyLogger.kt │ │ ├── format │ │ ├── AnsiConstants.kt │ │ ├── AnsiFormat.kt │ │ └── AnsiColor.kt │ │ ├── utils │ │ ├── FormatUtils.kt │ │ └── HyPrintStream.kt │ │ ├── HyLoggerConfig.kt │ │ ├── HyLogger.kt │ │ └── HyLoggerUtils.kt └── test │ └── java │ ├── SysoutTest.kt │ ├── FormatUtilsTest.java │ ├── LogFormatTest.kt │ ├── HyLoggerUnitTest.kt │ ├── Xterm256Test.kt │ ├── color │ └── ColorAnalysis.kt │ ├── FullWidthUtilGenerator.kt │ └── LoggerTest.kt ├── .gitignore ├── LICENSE ├── gradlew.bat ├── README.md └── gradlew /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "HyLogger" 2 | 3 | -------------------------------------------------------------------------------- /docs/HyLogger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyDevelop/HyLogger/HEAD/docs/HyLogger.jpg -------------------------------------------------------------------------------- /docs/HyLoggerTerminal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyDevelop/HyLogger/HEAD/docs/HyLoggerTerminal.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HyDevelop/HyLogger/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/appenders/Appender.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.appenders 2 | 3 | abstract class Appender(var formatter: (LogData) -> String = {"Error: Formatter is not programmed"}) 4 | { 5 | abstract fun logRaw(message: String) 6 | 7 | fun log(data: LogData) = logRaw(formatter(data)) 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/LogLevel.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger 2 | 3 | /** 4 | * 此类由 Hykilpikonna 在 2018/07/06 创建! 5 | * Created by Hykilpikonna on 2018/07/06! 6 | * Github: https://github.com/hykilpikonna 7 | * Meow! 8 | * 9 | * @author Hykilpikonna 10 | */ 11 | enum class LogLevel(val id: Int) 12 | { 13 | LOG(0), DEBUG(1), ERROR(2), WARNING(3) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/TimingLogger.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger 2 | 3 | class TimingLogger(private val logger: HyLogger) 4 | { 5 | var startTime: Long = 0 6 | 7 | val elapsed: Long 8 | get() = System.currentTimeMillis() - startTime 9 | 10 | fun time() = apply { logger.log("$elapsed ms") } 11 | fun reset() = apply { startTime = System.currentTimeMillis() } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/coloring/AnsiColorMode.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.coloring 2 | 3 | import org.hydev.logger.b 4 | import org.hydev.logger.g 5 | import org.hydev.logger.r 6 | import org.hydev.logger.to8Bit 7 | import java.awt.Color 8 | 9 | enum class AnsiColorMode(val format: (Color) -> String) 10 | { 11 | TRUE_COLOR_24BIT({ "2;${it.r};${it.g};${it.b}" }), 12 | XTERM_256_8BIT({ "5;${it.to8Bit()}" }); 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | #*.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # Custom 26 | .gradle/ 27 | .idea/ 28 | build/ 29 | logs/ 30 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/coloring/GradientPoint.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.coloring 2 | 3 | import java.awt.Color 4 | 5 | /** 6 | * A gradient point on a line with color and position 7 | * 8 | * @author HyDEV Team (https://github.com/HyDevelop) 9 | * @author Hykilpikonna (https://github.com/hykilpikonna) 10 | * @author Vanilla (https://github.com/VergeDX) 11 | * @since 2020-07-07 16:33 12 | */ 13 | class GradientPoint(val color: Color, val pos: Int) 14 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/appenders/LogData.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.appenders 2 | 3 | import org.hydev.logger.LogLevel 4 | 5 | /** 6 | * All the data required to format a log entry 7 | * 8 | * @author HyDEV Team (https://github.com/HyDevelop) 9 | * @author Hykilpikonna (https://github.com/hykilpikonna) 10 | * @author Vanilla (https://github.com/VergeDX) 11 | * @since 2020-07-05 19:43 12 | */ 13 | data class LogData( 14 | val level: LogLevel, 15 | val prefix: String, 16 | val msg: String, 17 | val fqcn: String 18 | ) 19 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/appenders/ColorCompatibility.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.appenders 2 | 3 | import org.hydev.logger.HyLoggerConfig 4 | import org.hydev.logger.withoutFormat 5 | import org.hydev.logger.withoutRGB 6 | 7 | enum class ColorCompatibility(val log: (String) -> Unit) 8 | { 9 | // Only output preset colors and remove xterm-256 colors 10 | PRESET_ONLY({ HyLoggerConfig.out.println(it.withoutRGB()) }), 11 | 12 | // Always output color 13 | FORCED({ HyLoggerConfig.out.println(it) }), 14 | 15 | // Remove all colors 16 | DISABLED({ HyLoggerConfig.out.println(it.withoutFormat()) }), 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/FancyLogger.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger 2 | 3 | import org.hydev.logger.coloring.LinearGradient 4 | import java.awt.Color 5 | 6 | class FancyLogger(private val logger: HyLogger) 7 | { 8 | fun gradient(message: String, c1: Color, c2: Color, vararg colors: Color) 9 | = gradient(message, LinearGradient(c1, c2, *colors)) 10 | 11 | fun gradient(message: String, gradient: LinearGradient) 12 | = logger.log(gradient.colorText(message)) 13 | 14 | fun gradient(message: String, gradient: LinearGradient, degrees: Double) 15 | = logger.log(gradient.colorText(message, degrees)) 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/SysoutTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import org.hydev.logger.HyLoggerConfig 3 | import java.lang.System.err 4 | 5 | /** 6 | * TODO: Write a description for this class! 7 | * 8 | * @author HyDEV Team (https://github.com/HyDevelop) 9 | * @author Hykilpikonna (https://github.com/hykilpikonna) 10 | * @author Vanilla (https://github.com/VergeDX) 11 | * @since 2020-07-31 16:59 12 | */ 13 | fun main(args: Array) 14 | { 15 | println("安装到 Sysout 之前w") 16 | 17 | // 安装到 Sysout 18 | HyLoggerConfig.installSysOut() 19 | 20 | println("安装到 Sysout 之后w") 21 | Thread.sleep(50) 22 | 23 | err.println("丢的异常也可以哦w") 24 | val i = 1/0 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/format/AnsiConstants.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.format 2 | 3 | import java.util.* 4 | 5 | object AnsiConstants 6 | { 7 | const val ESC_PREFIX = "\u001b[" 8 | const val SUFFIX = "m" 9 | const val FORMAT_PREFIX = "&" 10 | const val FOREGROUND = "38;" 11 | const val BACKGROUND = "48;" 12 | 13 | val formatsIndex: MutableMap = HashMap() 14 | 15 | init 16 | { 17 | // Index enum values 18 | AnsiColor.values().forEach { it.placeholders.forEach { char -> formatsIndex[char] = it.value } } 19 | AnsiFormat.values().forEach { it.placeholders.forEach { char -> formatsIndex[char] = it.value } } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/format/AnsiFormat.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.format 2 | 3 | import org.hydev.logger.format.AnsiConstants.ESC_PREFIX 4 | import org.hydev.logger.format.AnsiConstants.SUFFIX 5 | 6 | /** 7 | * 此类由 Hykilpikonna 在 2018/05/04 创建! 8 | * Created by Hykilpikonna on 2018/05/04! 9 | * Github: https://github.com/hykilpikonna 10 | * Meow! 11 | * 12 | * @author Hykilpikonna 13 | */ 14 | enum class AnsiFormat(var code: Int, vararg val placeholders: Char) 15 | { 16 | RESET(0), 17 | HIGH_INTENSITY(1, 'l'), 18 | LOW_INTENSITY(2), 19 | ITALIC(3, 'o'), 20 | UNDERLINE(4, 'n'), 21 | BLINK(5), 22 | RAPID_BLINK(6), 23 | REVERSE_VIDEO(7), 24 | INVISIBLE_TEXT(8); 25 | 26 | override fun toString() = value 27 | val value = ESC_PREFIX + code + SUFFIX 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/format/AnsiColor.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.format 2 | 3 | import org.hydev.logger.format.AnsiConstants.ESC_PREFIX 4 | import org.hydev.logger.format.AnsiConstants.SUFFIX 5 | 6 | @Suppress("ConvertToStringTemplate") 7 | enum class AnsiColor(val code: Int, vararg val placeholders: Char) 8 | { 9 | RESET(0, 'r'), 10 | BLACK(30, '0', '8'), 11 | RED(31, '4', 'c'), 12 | GREEN(32, '2', 'a'), 13 | YELLOW(33, '6', 'e'), 14 | BLUE(34, '1', '9'), 15 | PURPLE(35, '5', 'd'), 16 | CYAN(36, '3', 'b'), 17 | WHITE(37, '7', 'f'); 18 | 19 | override fun toString() = value 20 | 21 | val value = ESC_PREFIX + code + SUFFIX 22 | val bright = ESC_PREFIX + code + ";1" + SUFFIX 23 | val background = ESC_PREFIX + (code + 10) + SUFFIX 24 | val brightBg = ESC_PREFIX + (code + 70) + SUFFIX 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/FormatUtilsTest.java: -------------------------------------------------------------------------------- 1 | import org.hydev.logger.utils.FormatUtils; 2 | 3 | /** 4 | * TODO: Write a description for this class! 5 | * 6 | * @author HyDEV Team (https://github.com/HyDevelop) 7 | * @author Hykilpikonna (https://github.com/hykilpikonna) 8 | * @author Vanilla (https://github.com/VergeDX) 9 | * @since 2020-07-05 19:28 10 | */ 11 | public class FormatUtilsTest 12 | { 13 | public static void main(String[] args) 14 | { 15 | System.out.println(FormatUtils.resolve("\\ab\\c{}\\as\\{}df{}", 2, 5)); 16 | System.out.println(FormatUtils.resolve("{}{}{}", 2, 5)); 17 | System.out.println(FormatUtils.resolve("{}{}w", 2, 5)); 18 | System.out.println(FormatUtils.resolve("{}{}w", 2)); 19 | System.out.println(FormatUtils.resolve("{}\\{w", 2)); 20 | System.out.println(FormatUtils.resolve("}w", 2)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/coloring/GradientPresets.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.coloring 2 | 3 | import java.awt.Color 4 | 5 | object GradientPresets 6 | { 7 | // 彩虹 8 | var RAINBOW = LinearGradient( 9 | GradientPoint(Color(255, 0, 0), 0), 10 | GradientPoint(Color(255, 0, 255), 15), 11 | GradientPoint(Color(126, 126, 255), 34), 12 | GradientPoint(Color(0, 255, 255), 50), 13 | GradientPoint(Color(0, 255, 0), 68), 14 | GradientPoint(Color(255, 255, 0), 85), 15 | GradientPoint(Color(255, 0, 0), 100) 16 | ) 17 | 18 | // 蓝橙粉 19 | var BOP = LinearGradient( 20 | Color(64, 224, 208), 21 | Color(255, 140, 0), 22 | Color(255, 0, 128) 23 | ) 24 | 25 | // 蓝紫红 26 | var BPR = LinearGradient( 27 | Color(18, 194, 233), 28 | Color(196, 113, 237), 29 | Color(246, 79, 89) 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/LogFormatTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import org.hydev.logger.HyLogger 3 | 4 | /** 5 | * 此类由 Hykilpikonna 在 2018/12/02 创建! 6 | * Created by Hykilpikonna on 2018/12/02! 7 | * Github: https://github.com/hykilpikonna 8 | * Meow! 9 | * 10 | * @author Hykilpikonna 11 | */ 12 | object LogFormatTest 13 | { 14 | @JvmStatic 15 | fun main(args: Array) 16 | { 17 | val logger = HyLogger("FormatTest") 18 | 19 | // Test obtained from: https://blog.csdn.net/skyupward/article/details/54864522 20 | logger.log("Set {1,2} differs from {}", 3) 21 | logger.log("Set \\{} differs from {}", "3") 22 | logger.log("File name is C:\\\\{}.", "file.zip") 23 | logger.newLine() 24 | 25 | // Test obtained from: https://dzone.com/articles/java-string-format-examples 26 | logger.logf("%s = %d", "joe", 35) 27 | logger.logf("PI = %f%n", Math.PI) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/HyLoggerUnitTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import org.hydev.logger.HyLoggerConfig 3 | import org.junit.jupiter.api.MethodOrderer.Alphanumeric 4 | import org.junit.jupiter.api.Test 5 | import org.junit.jupiter.api.TestInstance 6 | import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS 7 | import org.junit.jupiter.api.TestMethodOrder 8 | 9 | /** 10 | * Unit tests for 11 | * 12 | * @author HyDEV Team (https://github.com/HyDevelop) 13 | * @author Hykilpikonna (https://github.com/hykilpikonna) 14 | * @author Vanilla (https://github.com/VergeDX) 15 | * @since 2020-07-31 16:40 16 | */ 17 | @TestInstance(PER_CLASS) @TestMethodOrder(Alphanumeric::class) 18 | class HyLoggerUnitTest 19 | { 20 | @Test 21 | fun a1000_SysOut(){ 22 | fun testOnce() 23 | { 24 | print("Hello ") 25 | println("World") 26 | println(null) 27 | Thread.sleep(50) 28 | System.err.println("Error"); 29 | Thread.sleep(50) 30 | } 31 | 32 | testOnce() 33 | HyLoggerConfig.installSysOut() 34 | testOnce() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 - HyDEV - 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/java/Xterm256Test.kt: -------------------------------------------------------------------------------- 1 | 2 | import org.hydev.logger.format.AnsiColor.RESET 3 | import org.hydev.logger.format.AnsiConstants.BACKGROUND 4 | import org.hydev.logger.format.AnsiConstants.ESC_PREFIX 5 | import org.hydev.logger.format.AnsiConstants.SUFFIX 6 | 7 | /** 8 | * Test all colors in the Xterm 256 colorspace 9 | * 10 | * @author HyDEV Team (https://github.com/HyDevelop) 11 | * @author Hykilpikonna (https://github.com/hykilpikonna) 12 | * @author Vanilla (https://github.com/VergeDX) 13 | * @since 2020-07-07 16:25 14 | */ 15 | fun main(args: Array) 16 | { 17 | // Preset colors 18 | printRange(0..7) 19 | printRange(0..15) 20 | println() 21 | 22 | // Grayscale colors 23 | printRange(232..243) 24 | printRange(244..255) 25 | println() 26 | 27 | // Actual colors 28 | val lines = Array(6) {""} 29 | for (i in 0..215 step 6) 30 | { 31 | for (j in (i + 16)..(i + 21)) 32 | lines[i / 6 / 6] += toTest(j) 33 | } 34 | 35 | println(lines.joinToString("\n", "", "")) 36 | } 37 | 38 | fun printRange(range: IntRange) 39 | { 40 | for (i in range) print(toTest(i)) 41 | println() 42 | } 43 | 44 | fun toTest(i: Int) = ESC_PREFIX + BACKGROUND + "5;" + i + SUFFIX + i.toString().padStart(4, ' ') + " " + RESET 45 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/appenders/ConsoleAppender.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.appenders 2 | 3 | import org.hydev.logger.HyLoggerConfig.colorCompatibility 4 | import org.hydev.logger.HyLoggerConfig.timePattern 5 | import org.hydev.logger.LogLevel.* 6 | import org.hydev.logger.format.AnsiColor.* 7 | import org.hydev.logger.now 8 | import org.hydev.logger.parseFormats 9 | 10 | class ConsoleAppender() : Appender() 11 | { 12 | init 13 | { 14 | // Create formatter 15 | val defaultFormat = "&f[&5%s&f] [&1%s&f] [%s&f] %s&r".parseFormats() 16 | val fqcnFormat = "&f[&5%s&f] [&1%s&f] [%s&f(&e%s&f)] %s&r".parseFormats() 17 | 18 | formatter = 19 | { 20 | val time = timePattern.now() 21 | 22 | when (it.level) 23 | { 24 | LOG -> defaultFormat.format(time, it.prefix, "${GREEN}INFO", "$RESET${it.msg}") 25 | WARNING -> defaultFormat.format(time, it.prefix, "${YELLOW}WARNING", "$YELLOW${it.msg}") 26 | DEBUG -> fqcnFormat.format(time, it.prefix, "${YELLOW}DEBUG", it.fqcn, "$CYAN${it.msg}") 27 | ERROR -> fqcnFormat.format(time, it.prefix, "${RED}ERROR", it.fqcn, "$RED${it.msg}") 28 | } 29 | } 30 | } 31 | 32 | override fun logRaw(message: String) = colorCompatibility.log(message) 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/utils/FormatUtils.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.utils 2 | 3 | object FormatUtils 4 | { 5 | /** 6 | * Format a string. 7 | * 8 | * @param format Format 9 | * @param args Args 10 | * @return Formatted string. 11 | */ 12 | @JvmStatic 13 | fun resolve(format: String, vararg args: Any): String 14 | { 15 | val result = StringBuilder() 16 | val f = "$format " 17 | var count = 0 18 | var i = 0 19 | 20 | loop@ while (i < f.length - 1) 21 | { 22 | when (f.substring(i, i + 2)) 23 | { 24 | "\\{" -> 25 | { 26 | result.append("{") 27 | i += 2 28 | } 29 | 30 | "{}" -> 31 | { 32 | result.append(args[count]) 33 | count++ 34 | i += 2 35 | 36 | // End early 37 | if (args.size <= count) 38 | { 39 | result.append(f.substring(i)) 40 | break@loop 41 | } 42 | } 43 | 44 | else -> 45 | { 46 | result.append(f[i]) 47 | i++ 48 | } 49 | } 50 | } 51 | return result.toString() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/appenders/FileAppender.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.appenders 2 | 3 | import org.hydev.logger.HyLoggerConfig.fileFormat 4 | import org.hydev.logger.HyLoggerConfig.fileTimePattern 5 | import org.hydev.logger.now 6 | import org.hydev.logger.withoutFormat 7 | import java.io.File 8 | import java.io.PrintWriter 9 | 10 | open class FileAppender(file: File) : Appender() 11 | { 12 | var fileWriter: PrintWriter 13 | 14 | init 15 | { 16 | // Create formatter (File format defaults to csv) 17 | formatter = 18 | { 19 | val thread = Thread.currentThread() 20 | 21 | listOf(System.currentTimeMillis(), it.prefix, it.level, it.fqcn, it.msg, 22 | thread.id, thread.name, thread.priority).joinToString(",", "", "") 23 | } 24 | 25 | // File 26 | file.parentFile.mkdirs() 27 | if (!file.exists()) file.createNewFile() 28 | fileWriter = file.printWriter() 29 | 30 | // Save on close 31 | Runtime.getRuntime().addShutdownHook(Thread { 32 | fileWriter.flush() 33 | fileWriter.close() 34 | }) 35 | } 36 | 37 | constructor(path: String, name: String) : 38 | this(File(File(path), fileFormat.replace("{name}", name).replace("{time}", fileTimePattern.now()))) 39 | 40 | override fun logRaw(message: String) 41 | { 42 | fileWriter.write(message.withoutFormat() + "\n") 43 | fileWriter.flush() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/HyLoggerConfig.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger 2 | 3 | import org.fusesource.jansi.AnsiConsole 4 | import org.hydev.logger.HyLogger.Companion.general 5 | import org.hydev.logger.appenders.Appender 6 | import org.hydev.logger.appenders.ColorCompatibility 7 | import org.hydev.logger.appenders.ColorCompatibility.PRESET_ONLY 8 | import org.hydev.logger.appenders.ConsoleAppender 9 | import org.hydev.logger.coloring.AnsiColorMode 10 | import org.hydev.logger.utils.HyPrintStream 11 | import java.io.PrintStream 12 | 13 | /** 14 | * Global configuration for the logger 15 | * 16 | * @author HyDEV Team (https://github.com/HyDevelop) 17 | * @author Hykilpikonna (https://github.com/hykilpikonna) 18 | * @author Vanilla (https://github.com/VergeDX) 19 | * @since 2020-07-05 16:23 20 | */ 21 | object HyLoggerConfig 22 | { 23 | var colorMode = AnsiColorMode.TRUE_COLOR_24BIT 24 | var colorCompatibility = ColorCompatibility.FORCED 25 | 26 | var timePattern = "yyyy-MM-dd HH:mm:ss".toDatePattern() 27 | 28 | val appenders: MutableList = mutableListOf(ConsoleAppender()) 29 | 30 | var debug = false 31 | 32 | var fileTimePattern = "yy-MM-dd_HH-mm".toDatePattern() 33 | var fileFormat = "log-{name}@{time}.csv" 34 | 35 | var out: PrintStream = System.out 36 | 37 | val _originalOut: PrintStream = System.out 38 | val _originalErr: PrintStream = System.err 39 | 40 | /** 41 | * Make System.out.println() go through HyLogger.general.log() 42 | */ 43 | fun installSysOut() 44 | { 45 | System.setOut(HyPrintStream(_originalOut) { general.log(it) }) 46 | } 47 | 48 | /** 49 | * Make System.err.println() go through HyLogger.general.error() 50 | */ 51 | fun installSysErr() 52 | { 53 | System.setErr(HyPrintStream(_originalErr) { general.error(it) }) 54 | } 55 | 56 | /** 57 | * Enable windows cmd color compatibility 58 | */ 59 | fun enableWindowsCmdCompatibility() 60 | { 61 | out = AnsiConsole.out 62 | colorCompatibility = PRESET_ONLY 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/utils/HyPrintStream.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.utils 2 | 3 | import java.io.OutputStream 4 | import java.io.PrintStream 5 | 6 | /** 7 | * PrintStream Wrapper 8 | * 9 | * @author HyDEV Team (https://github.com/HyDevelop) 10 | * @author Hykilpikonna (https://github.com/hykilpikonna) 11 | * @author Vanilla (https://github.com/VergeDX) 12 | * @since 2020-07-13 22:05 13 | */ 14 | class HyPrintStream(val original: PrintStream, val log: (Any) -> Unit) 15 | : PrintStream(object: OutputStream() { override fun write(b: Int) {}}) 16 | { 17 | override fun checkError() = super.checkError() || original.checkError() 18 | override fun close() = run { super.close(); original.close() } 19 | override fun flush() = run { super.flush(); original.flush() } 20 | override fun write(b: Int) = original.write(b) 21 | override fun write(buf: ByteArray, off: Int, len: Int) = original.write(buf, off, len) 22 | override fun write(b: ByteArray) = original.write(b) 23 | 24 | /* Methods that do not terminate lines */ 25 | 26 | override fun print(b: Boolean) = original.print(b) 27 | override fun print(c: Char) = original.print(c) 28 | override fun print(i: Int) = original.print(i) 29 | override fun print(l: Long) = original.print(l) 30 | override fun print(f: Float) = original.print(f) 31 | override fun print(d: Double) = original.print(d) 32 | override fun print(s: CharArray) = original.print(s) 33 | override fun print(s: String?) = original.print(s) 34 | override fun print(obj: Any) = original.print(obj) 35 | 36 | /* Methods that do terminate lines */ 37 | 38 | override fun println() = log("") 39 | override fun println(x: Boolean) = log(x) 40 | override fun println(x: Char) = log(x) 41 | override fun println(x: Int) = log(x) 42 | override fun println(x: Long) = log(x) 43 | override fun println(x: Float) = log(x) 44 | override fun println(x: Double) = log(x) 45 | override fun println(x: CharArray) = log(x) 46 | override fun println(x: String?) = log(x ?: "null") 47 | override fun println(x: Any?) = log(x ?: "null") 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/color/ColorAnalysis.kt: -------------------------------------------------------------------------------- 1 | package color 2 | 3 | import org.hydev.logger.HyLogger 4 | import org.hydev.logger.HyLoggerConfig 5 | import org.hydev.logger.background 6 | import org.hydev.logger.coloring.AnsiColorMode.TRUE_COLOR_24BIT 7 | import org.hydev.logger.coloring.AnsiColorMode.XTERM_256_8BIT 8 | import org.hydev.logger.foreground 9 | import org.hydev.logger.format.AnsiColor 10 | import org.hydev.logger.format.AnsiColor.RESET 11 | import java.awt.Color 12 | 13 | /** 14 | * This class prints a detailed analysis of the color capability of the terminal client. 15 | * 16 | * @author HyDEV Team (https://github.com/HyDevelop) 17 | * @author Hykilpikonna (https://github.com/hykilpikonna) 18 | * @author Vanilla (https://github.com/VergeDX) 19 | * @since 2020-07-07 14:00 20 | */ 21 | fun main() 22 | { 23 | for (test in tests) 24 | { 25 | println() 26 | HyLoggerConfig.colorMode = TRUE_COLOR_24BIT 27 | println("${RESET}True Color ${test.key}:") 28 | test.value() 29 | println() 30 | HyLoggerConfig.colorMode = XTERM_256_8BIT 31 | println("${RESET}Xterm 256 ${test.key}:") 32 | test.value() 33 | } 34 | } 35 | 36 | // All the tests 37 | var tests = linkedMapOf( 38 | "Background Foreground Test" to { 39 | HyLogger("Test").log(AnsiColor.RED.background + AnsiColor.GREEN + "Hello world!") 40 | }, 41 | 42 | "Grayscale Test" to { 43 | for (r in 0..255 step 16) 44 | { 45 | print(Color(r, r, r).foreground() + "█") 46 | } 47 | println() 48 | }, 49 | 50 | "Blue Test" to { 51 | for (b in 0..255) 52 | { 53 | if (b % 16 == 0) println() 54 | 55 | print("${Color.LIGHT_GRAY.foreground()}${Color(0, 0, b).background()}" + 56 | "${b.toString().padStart(4, ' ')} $RESET ") 57 | } 58 | }, 59 | 60 | "All Colors Test" to { 61 | for (r in 0..255 step 16) 62 | { 63 | for (g in 0..255 step 16) 64 | { 65 | for (b in 0..255 step 16) 66 | { 67 | print(Color(r, g, b).foreground() + "█") 68 | } 69 | print(" ") 70 | } 71 | println() 72 | } 73 | } 74 | ) 75 | -------------------------------------------------------------------------------- /src/test/java/FullWidthUtilGenerator.kt: -------------------------------------------------------------------------------- 1 | import org.hydev.logger.line 2 | 3 | /** 4 | * This script is used to generate full width character detector from 5 | * EastAsianWidth.txt (http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt) 6 | * 7 | * @author HyDEV Team (https://github.com/HyDevelop) 8 | * @author Hykilpikonna (https://github.com/hykilpikonna) 9 | * @author Vanilla (https://github.com/VergeDX) 10 | * @since 2020-07-10 20:32 11 | */ 12 | fun main(args: Array) 13 | { 14 | val fileContent = {}.javaClass.getResource("/EastAsianWidth.txt").readText() 15 | val values = ArrayList() 16 | val ranges = ArrayList() 17 | 18 | for (rawLine in fileContent.lines()) 19 | { 20 | // Ignore comments 21 | val line = rawLine.substringBefore('#') 22 | if (line.isBlank()) continue 23 | 24 | val split = line.split(";") 25 | 26 | // Ignore UTF Scalar for now 27 | if (split[0].split("..")[0].length >= 5) continue 28 | 29 | // A = Ambiguous (Treated as half-width) 30 | // N, Na, H = Half-width 31 | // F, W = Full-width 32 | val code = split[1][0].toString() 33 | if (!(code == "F" || code == "W")) continue 34 | 35 | // Get range 36 | val value = "'\\u${split[0]}'" 37 | val isRange = value.contains(".."); 38 | if (!isRange) values.add(value) 39 | if (isRange) 40 | { 41 | val rangeSplit = value.split("..") 42 | ranges.add("in ${rangeSplit[0]}'..'\\u${rangeSplit[1]}") 43 | } 44 | } 45 | 46 | // Create string 47 | val result = StringBuilder() 48 | var line = " " 49 | 50 | result.line("when (char)").line("{").append("") 51 | 52 | values.forEach { 53 | if (line.length + it.length > 110) 54 | { 55 | result.line(line) 56 | line = " " 57 | } 58 | 59 | line += "$it," 60 | } 61 | 62 | result.line(line) 63 | line = " " 64 | 65 | ranges.forEach { 66 | if (line.length + it.length > 110) 67 | { 68 | result.line(line) 69 | line = " " 70 | } 71 | 72 | line += "$it," 73 | } 74 | 75 | result.line(line.trimEnd(',') + " -> true").line(" else -> false").line("}") 76 | 77 | print(result) 78 | } 79 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/HyLogger.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger 2 | 3 | import org.hydev.logger.HyLoggerConfig.appenders 4 | import org.hydev.logger.HyLoggerConfig.debug 5 | import org.hydev.logger.LogLevel.* 6 | import org.hydev.logger.appenders.LogData 7 | import org.hydev.logger.utils.FormatUtils.resolve 8 | 9 | class HyLogger(val prefix: String) 10 | { 11 | val fancy = FancyLogger(this) 12 | val timing = TimingLogger(this) 13 | 14 | /** 15 | * Log a message. 16 | */ 17 | private fun log(level: LogLevel, message: String) 18 | { 19 | if (level == DEBUG && !debug) return 20 | 21 | // Find stack trace 22 | val stOrig = Thread.currentThread().stackTrace.toMutableList().apply { removeAt(0); removeAt(0) } 23 | val fqcn = if (level == ERROR && stOrig.last().toString().startsWith("java.lang.Thread.dispatchUncaughtException(")) "Uncaught" else 24 | { 25 | val st = stOrig.filter { !it.className.startsWith("org.hydev.logger") } 26 | val stack = st.firstOrNull { !it.className.startsWith("java.") } ?: st.getOrNull(0) ?: stOrig[0] 27 | "${stack.className}.${stack.methodName}:${stack.lineNumber}" 28 | } 29 | 30 | 31 | message.lines().forEach { line -> 32 | appenders.forEach { it.log(LogData(level, prefix, line, fqcn)) } 33 | } 34 | } 35 | 36 | /** 37 | * Log message with Slf4J format. 38 | */ 39 | private fun log(level: LogLevel, format: String, vararg args: Any) = log(level, resolve(format, *args)) 40 | 41 | /** 42 | * Log message with System.printf() format. 43 | */ 44 | private fun logf(level: LogLevel, format: String, vararg args: Any) = log(level, String.format(format, *args)) 45 | 46 | /** 47 | * Log an empty line. 48 | */ 49 | fun newLine() = log(LOG, "") 50 | 51 | fun log(message: Any) = log(LOG, message.toString()) 52 | fun info(message: Any) = log(LOG, message.toString()) 53 | fun debug(message: Any) = log(DEBUG, message.toString()) 54 | fun error(message: Any) = log(ERROR, message.toString()) 55 | fun warning(message: Any) = log(WARNING, message.toString()) 56 | 57 | fun log(format: String, vararg args: Any) = log(LOG, format, *args) 58 | fun info(format: String, vararg args: Any) = log(LOG, format, *args) 59 | fun debug(format: String, vararg args: Any) = log(DEBUG, format, *args) 60 | fun error(format: String, vararg args: Any) = log(ERROR, format, *args) 61 | fun warning(format: String, vararg args: Any) = log(WARNING, format, *args) 62 | 63 | fun logf(format: String, vararg args: Any) = logf(LOG, format, *args) 64 | fun infof(format: String, vararg args: Any) = logf(LOG, format, *args) 65 | fun debugf(format: String, vararg args: Any) = logf(DEBUG, format, *args) 66 | fun errorf(format: String, vararg args: Any) = logf(ERROR, format, *args) 67 | fun warningf(format: String, vararg args: Any) = logf(WARNING, format, *args) 68 | 69 | companion object 70 | { 71 | var general = HyLogger("General") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /src/test/java/LoggerTest.kt: -------------------------------------------------------------------------------- 1 | 2 | import org.hydev.logger.HyLogger 3 | import org.hydev.logger.HyLoggerConfig 4 | import org.hydev.logger.appenders.FileAppender 5 | import org.hydev.logger.coloring.GradientPresets.BPR 6 | import org.hydev.logger.coloring.GradientPresets.RAINBOW 7 | import java.awt.Color 8 | 9 | /** 10 | * 此类由 Hykilpikonna 在 2018/05/04 创建! 11 | * Created by Hykilpikonna on 2018/05/04! 12 | * Github: https://github.com/hykilpikonna 13 | * Meow! 14 | * 15 | * @author Hykilpikonna 16 | */ 17 | object LoggerTest 18 | { 19 | @JvmStatic 20 | fun main(args: Array) 21 | { 22 | val logger = HyLogger("General") 23 | HyLoggerConfig.debug = true 24 | HyLoggerConfig.appenders.add(FileAppender("./logs/", "test.log")) 25 | 26 | logger.log("Oak logs are the best.") 27 | logger.debug("This is not logged if HyLoggerConfig.debug is off.") 28 | logger.error("This could never happen in theory, right?") 29 | logger.warning("Nerf this!") 30 | logger.log("") 31 | 32 | logger.fancy.gradient("=========== Linear Gradient ===========", Color.BLUE, Color.CYAN) 33 | logger.fancy.gradient("=========== Linear Gradient ===========", Color.ORANGE, Color.CYAN) 34 | logger.fancy.gradient("=========== Linear Gradient ===========", Color(0, 242, 96), Color(80, 161, 230)) 35 | logger.fancy.gradient("=========== Linear Gradient ===========", Color(255, 140, 0), Color(255, 0, 128)) 36 | 37 | logger.log("") 38 | logger.log("Gradient with more than 2 color positions:") 39 | logger.fancy.gradient(""" 40 | |######################################## 41 | |########## Gradient ########## 42 | |########################################""".trimMargin(), RAINBOW) 43 | 44 | logger.log("") 45 | logger.log("Angled gradient on ascii art:") 46 | logger.fancy.gradient(""" 47 | ┬ ┬┬ ┬┬ ┌─┐┌─┐┌─┐┌─┐┬─┐ 48 | ├─┤└┬┘│ │ ││ ┬│ ┬├┤ ├┬┘ 49 | ┴ ┴ ┴ ┴─┘└─┘└─┘└─┘└─┘┴└─""".trimIndent(), BPR, 15.0) 50 | 51 | // logger.fancy.gradient("Initiating...", Color.ORANGE, Color.YELLOW) 52 | // logger.timing.reset() 53 | // println() 54 | // 55 | // logger.log("一条测试Log消息") 56 | // logger.error("一条测试Error消息") 57 | // logger.warning("一条测试Warning消息") 58 | // logger.debug("一条没发出去的Debug消息") 59 | // HyLoggerConfig.debug = true 60 | // logger.debug("一条发出去了的Debug消息") 61 | // logger.timing.time().reset() 62 | // 63 | // logger.fancy.gradient("测试渐变从深蓝到浅蓝", Color.BLUE, Color.CYAN) 64 | // logger.timing.time().reset() 65 | // 66 | // logger.fancy.gradient("测试渐变从橘色到浅蓝", Color.ORANGE, Color.CYAN) 67 | // logger.timing.time().reset() 68 | // 69 | // logger.fancy.gradient("测试黄绿渐变到天蓝", Color(0, 242, 96), Color(80, 161, 230)) 70 | // logger.timing.time().reset() 71 | // 72 | // logger.fancy.gradient("测试橙色渐变到粉色", Color(255, 140, 0), Color(255, 0, 128)) 73 | // logger.timing.time().reset() 74 | // 75 | // logger.fancy.gradient(""" 76 | // |############################################ 77 | // |########## 中文不会打断渐变色啦w ########### 78 | // |############################################""".trimMargin(), RAINBOW) 79 | // logger.timing.time().reset() 80 | // 81 | // logger.fancy.gradient(""" 82 | // |############################################ 83 | // |########### Blue to Purple to Red ########## 84 | // |############################################""".trimMargin(), BPR) 85 | // logger.timing.time().reset() 86 | // 87 | // logger.log("Testing angled gradient on text block #1:") 88 | // logger.fancy.gradient(""" 89 | // ┬ ┬┬ ┬┬ ┌─┐┌─┐┌─┐┌─┐┬─┐ 90 | // ├─┤└┬┘│ │ ││ ┬│ ┬├┤ ├┬┘ 91 | // ┴ ┴ ┴ ┴─┘└─┘└─┘└─┘└─┘┴└─""".trimIndent(), BPR, 15.0) 92 | // logger.timing.time().reset() 93 | // 94 | // logger.log("Testing angled gradient on text block #2:") 95 | // logger.fancy.gradient(""" 96 | // __ __ __ 97 | // / / / /_ __/ / ____ ____ _____ ____ _____ 98 | // / /_/ / / / / / / __ \/ __ `/ __ `/ _ \/ ___/ 99 | // / __ / /_/ / /___/ /_/ / /_/ / /_/ / __/ / 100 | // /_/ /_/\__, /_____/\____/\__, /\__, /\___/_/ 101 | // /____/ /____//____/ """.trimIndent(), BOP, 60.0) 102 | // logger.timing.time().reset() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 | 5 | 6 |

7 |

8 | RGB, even in the console outputs! 9 |

10 |
11 | Introduction  |  12 | Getting Started  |  13 | License 14 |
15 | 16 |
17 | 18 |

19 | 20 |

21 | 22 |
23 | 24 | 25 | What Is This? 26 | -------- 27 | 28 | HyLogger is a console logging library designed for human programmers to read. Its APIs are simple and clean, and its color-coding features make logging more readable. It focuses on coloring features like color-coding. 29 | 30 | TODO: Add support for universal System.err override 31 | 32 |
33 | 34 | 35 | ## Getting Started 36 | 37 | ### 0. Import using Gradle / Maven: 38 | 39 | ```xml 40 | 41 | 42 | org.hydev 43 | HyLogger 44 | 2.1.0.378 45 | 46 | ``` 47 | 48 | ```groovy 49 | // https://mvnrepository.com/artifact/org.hydev/HyLogger 50 | compile group: 'org.hydev', name: 'HyLogger', version: '2.1.0.378' 51 | ``` 52 | 53 | It'll be available after clicking the reload button in IDEA! 54 | 55 | ### 1. Create Logger: 56 | 57 | Java: 58 | 59 | ```java 60 | HyLogger logger = new HyLogger("Scenario Name"); 61 | ``` 62 | 63 | Kotlin: 64 | 65 | ```kotlin 66 | val logger = HyLogger("Scenario Name") 67 | ``` 68 | 69 | ### 2. Basic Logging: 70 | 71 | Basic logging (they are basically the same for Kotlin and Java): 72 | 73 | ```java 74 | logger.log("Oak logs are the best."); 75 | logger.debug("This is not logged if HyLoggerConfig.debug is off."); 76 | logger.error("This could never happen in theory, right?"); 77 | logger.warning("Nerf this!"); 78 | ``` 79 | 80 | Formatting with `String.format()`: 81 | 82 | ```java 83 | logger.logf("Player %s just ate %.2f stardrops.", username, amount); 84 | logger.errorf("Time has just been distorted by %.5f picoseconds.", timeDiff); 85 | ``` 86 | 87 | Formatting like SLF4J (This is completely useless in Kotlin): 88 | 89 | ```java 90 | logger.log("{}! {} Everywhere!", "Brackets", "Brackets"); 91 | logger.debug("User {} just posted {} on {}", user, json, board); 92 | ``` 93 | 94 | In **Kotlin** you can do this: 95 | 96 | ```kotlin 97 | logger.debug("User $user tried to inject a SQL sequence: ${json.value}") 98 | ``` 99 | 100 | ### 3. Preset Color Logging: 101 | 102 | #### A. Use [**Minecraft Color-code**](https://www.spigotmc.org/attachments/example2-png.188806/): 103 | 104 | Java: 105 | 106 | ```java 107 | logger.log(HyLoggerUtilsKt.parseFormats("&cHeck &eyeah!")); 108 | ``` 109 | 110 | Kotlin: 111 | 112 | ```kotlin 113 | logger.log("&cHeck &eyeah!".parseFormats()) 114 | ``` 115 | 116 | #### B. Use Color Constants: 117 | 118 | Java: (Import static AnsiColor.* first) 119 | 120 | ```java 121 | logger.log("{}I'm blue {}da ba dee da ba daa", BLUE, CYAN); 122 | ``` 123 | 124 | Kotlin: (Import AnsiColor.* first) 125 | 126 | ```kotlin 127 | logger.log("${BLUE}I'm blue ${CYAN}da ba dee da ba daa") 128 | ``` 129 | 130 | ### 4. RGB!!!! Logging: 131 | 132 | #### Terminals' compatibility with TrueColor 133 | 134 | Unlike the regular logging features, 135 | these fancy logging features need proper support by the terminal program you're using. 136 | The terminal programs with **perfect compatibility** for each platform are following: 137 | (If you found a new compatible program, please post an issue) 138 | 139 | - All Platform: [Terminus Alpha](https://github.com/Eugeny/terminus) 140 | - MacOS: [iTerm](https://www.iterm2.com/) 141 | - Windows: Git Bash / MinTTY 142 | - Linux: XFCE4 Terminal Emulator / Gnome-Terminal 143 | 144 | Here is a list of **incompatible** programs: 145 | 146 | - Windows: CMD / Powershell / Babun 1.2.0 / PowerCmd 2.2 / Xshell 5 147 | - All Platform: Hyper 148 | 149 | Here is a list of **partially compatible** programs: 150 | (Might not display TrueColor properly or have some bugs) 151 | 152 | - Windows: Xshell 5 / Cmder 1.3.6 / CmdEmu 180626 153 | 154 | How to add support for **Eclipse terminal**: 155 | 156 | - Just use IntelliJ! It's 100% better. 157 | - Or install `ANSI Escape in Console` plugin in Eclipse Marketplace. 158 | 159 | #### Use RGB: 160 | 161 | Java: 162 | 163 | ```java 164 | logger.log("{}Awwwww", HyLoggerUtilsKt.foreground(new Color(252, 168, 187))); 165 | ``` 166 | 167 | Kotlin: 168 | 169 | ```kotlin 170 | logger.log("${new Color(252, 168, 187).foreground()}Awwwww"); 171 | ``` 172 | 173 | #### Gradients: 174 | 175 | LinearGradient defines a gradient pattern on a single line. (Eg. Red to Blue) 176 | There are several presets in the `GradientPresets` class, 177 | and you can create your own too. 178 | 179 | Please see [LoggerTest.kt](https://github.com/HyDevelop/HyLogger/blob/master/src/test/java/LoggerTest.kt) for examples! 180 | 181 | Eg. 182 | 183 | ```kotlin 184 | logger.fancy.gradient("Orange to Pink Gradient", Color(255, 140, 0), Color(255, 0, 128)) 185 | ``` 186 | 187 | ### 6. Extra Features: 188 | 189 | #### Timer: 190 | 191 | ```java 192 | // Resets the timer 193 | logger.timing.reset(); 194 | 195 | // Print the elapsed time in ms and reset 196 | logger.timing.time().reset(); 197 | ``` 198 | 199 | 200 | 201 |
202 | 203 | 204 | Open-source License: [MIT License](/LICENSE) 205 | -------- 206 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/HyLoggerUtils.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger 2 | 3 | import org.hydev.logger.HyLoggerConfig.colorMode 4 | import org.hydev.logger.format.AnsiConstants 5 | import org.hydev.logger.format.AnsiConstants.BACKGROUND 6 | import org.hydev.logger.format.AnsiConstants.ESC_PREFIX 7 | import org.hydev.logger.format.AnsiConstants.FOREGROUND 8 | import org.hydev.logger.format.AnsiConstants.SUFFIX 9 | import java.awt.Color 10 | import java.time.LocalDateTime 11 | import java.time.format.DateTimeFormatter 12 | import kotlin.math.roundToInt 13 | 14 | /** 15 | * Get the string with formats and colors removed. 16 | * Or this.replace("\u011B\\[[0-9;]*?m".toRegex(), "") 17 | */ 18 | fun String.withoutFormat() = this.replace("\u001B\\[[;\\d]*m".toRegex(), "") 19 | 20 | fun String.withoutRGB() = this.replace("\u001B\\[38;[25];.*?m".toRegex(), "") 21 | 22 | // Shortcuts for RGB 23 | val Color.r: Int get() = red 24 | val Color.g: Int get() = green 25 | val Color.b: Int get() = blue 26 | 27 | /** 28 | * Convert color to 8Bit color value 29 | */ 30 | fun Color.to8Bit(): Int 31 | { 32 | var grayPossible = true 33 | var gray = false 34 | var sep = 42.5f 35 | 36 | while (grayPossible) 37 | { 38 | if (r < sep || g < sep || b < sep) 39 | { 40 | gray = r < sep && g < sep && b < sep 41 | grayPossible = false 42 | } 43 | sep += 42.5f 44 | } 45 | 46 | return if (gray) (232f + (r + g + b) / 33f).roundToInt() 47 | else 16 + (r / 256f * 6f).toInt() * 36 + (g / 256f * 6f).toInt() * 6 + (b / 256f * 6f).toInt() 48 | } 49 | 50 | fun Color.foreground() = "" + ESC_PREFIX + FOREGROUND + colorMode.format(this) + SUFFIX 51 | fun Color.background() = "" + ESC_PREFIX + BACKGROUND + colorMode.format(this) + SUFFIX 52 | 53 | /** 54 | * String to date pattern 55 | */ 56 | fun String.toDatePattern(): DateTimeFormatter = DateTimeFormatter.ofPattern(this) 57 | 58 | /** 59 | * Date pattern to time now 60 | */ 61 | fun DateTimeFormatter.now(): String = LocalDateTime.now().format(this) 62 | 63 | /** 64 | * Add a line to a stringBuilder 65 | */ 66 | fun StringBuilder.line(line: String): StringBuilder = append(line).append("\n") 67 | 68 | /** 69 | * Replace format/color codes like &r with actual Ansi colors 70 | */ 71 | fun String.parseFormats(): String 72 | { 73 | // TODO: Optimize this 74 | var result = this 75 | AnsiConstants.formatsIndex.forEach { (k: Char, v: String) -> 76 | result = result.replace(AnsiConstants.FORMAT_PREFIX + k, v) 77 | } 78 | return result 79 | } 80 | 81 | /** 82 | * Half-width: Regular width characters. 83 | * Eg. 'A' and 'ニ' 84 | * 85 | * Full-width: Chars that take two monospaced English chars' space on the display 86 | * Eg. '中', 'に' and 'A' 87 | * 88 | * See FullWidthUtilGenerator.kt 89 | * 90 | * @return Is this character a full-width character or not. 91 | */ 92 | fun Char.isFullWidth(): Boolean 93 | { 94 | return when (this) 95 | { 96 | '\u2329','\u232A','\u23F0','\u23F3','\u267F','\u2693','\u26A1','\u26CE','\u26D4','\u26EA','\u26F5', 97 | '\u26FA','\u26FD','\u2705','\u2728','\u274C','\u274E','\u2757','\u27B0','\u27BF','\u2B50','\u2B55', 98 | '\u3000','\u3004','\u3005','\u3006','\u3007','\u3008','\u3009','\u300A','\u300B','\u300C','\u300D', 99 | '\u300E','\u300F','\u3010','\u3011','\u3014','\u3015','\u3016','\u3017','\u3018','\u3019','\u301A', 100 | '\u301B','\u301C','\u301D','\u3020','\u3030','\u303B','\u303C','\u303D','\u303E','\u309F','\u30A0', 101 | '\u30FB','\u30FF','\u3250','\uA015','\uFE17','\uFE18','\uFE19','\uFE30','\uFE35','\uFE36','\uFE37', 102 | '\uFE38','\uFE39','\uFE3A','\uFE3B','\uFE3C','\uFE3D','\uFE3E','\uFE3F','\uFE40','\uFE41','\uFE42', 103 | '\uFE43','\uFE44','\uFE47','\uFE48','\uFE58','\uFE59','\uFE5A','\uFE5B','\uFE5C','\uFE5D','\uFE5E', 104 | '\uFE62','\uFE63','\uFE68','\uFE69','\uFF04','\uFF08','\uFF09','\uFF0A','\uFF0B','\uFF0C','\uFF0D', 105 | '\uFF3B','\uFF3C','\uFF3D','\uFF3E','\uFF3F','\uFF40','\uFF5B','\uFF5C','\uFF5D','\uFF5E','\uFF5F', 106 | '\uFF60','\uFFE2','\uFFE3','\uFFE4', 107 | in '\u1100'..'\u115F',in '\u231A'..'\u231B',in '\u23E9'..'\u23EC',in '\u25FD'..'\u25FE', 108 | in '\u2614'..'\u2615',in '\u2648'..'\u2653',in '\u26AA'..'\u26AB',in '\u26BD'..'\u26BE', 109 | in '\u26C4'..'\u26C5',in '\u26F2'..'\u26F3',in '\u270A'..'\u270B',in '\u2753'..'\u2755', 110 | in '\u2795'..'\u2797',in '\u2B1B'..'\u2B1C',in '\u2E80'..'\u2E99',in '\u2E9B'..'\u2EF3', 111 | in '\u2F00'..'\u2FD5',in '\u2FF0'..'\u2FFB',in '\u3001'..'\u3003',in '\u3012'..'\u3013', 112 | in '\u301E'..'\u301F',in '\u3021'..'\u3029',in '\u302A'..'\u302D',in '\u302E'..'\u302F', 113 | in '\u3031'..'\u3035',in '\u3036'..'\u3037',in '\u3038'..'\u303A',in '\u3041'..'\u3096', 114 | in '\u3099'..'\u309A',in '\u309B'..'\u309C',in '\u309D'..'\u309E',in '\u30A1'..'\u30FA', 115 | in '\u30FC'..'\u30FE',in '\u3105'..'\u312F',in '\u3131'..'\u318E',in '\u3190'..'\u3191', 116 | in '\u3192'..'\u3195',in '\u3196'..'\u319F',in '\u31A0'..'\u31BF',in '\u31C0'..'\u31E3', 117 | in '\u31F0'..'\u31FF',in '\u3200'..'\u321E',in '\u3220'..'\u3229',in '\u322A'..'\u3247', 118 | in '\u3251'..'\u325F',in '\u3260'..'\u327F',in '\u3280'..'\u3289',in '\u328A'..'\u32B0', 119 | in '\u32B1'..'\u32BF',in '\u32C0'..'\u32FF',in '\u3300'..'\u33FF',in '\u3400'..'\u4DBF', 120 | in '\u4E00'..'\u9FFC',in '\u9FFD'..'\u9FFF',in '\uA000'..'\uA014',in '\uA016'..'\uA48C', 121 | in '\uA490'..'\uA4C6',in '\uA960'..'\uA97C',in '\uAC00'..'\uD7A3',in '\uF900'..'\uFA6D', 122 | in '\uFA6E'..'\uFA6F',in '\uFA70'..'\uFAD9',in '\uFADA'..'\uFAFF',in '\uFE10'..'\uFE16', 123 | in '\uFE31'..'\uFE32',in '\uFE33'..'\uFE34',in '\uFE45'..'\uFE46',in '\uFE49'..'\uFE4C', 124 | in '\uFE4D'..'\uFE4F',in '\uFE50'..'\uFE52',in '\uFE54'..'\uFE57',in '\uFE5F'..'\uFE61', 125 | in '\uFE64'..'\uFE66',in '\uFE6A'..'\uFE6B',in '\uFF01'..'\uFF03',in '\uFF05'..'\uFF07', 126 | in '\uFF0E'..'\uFF0F',in '\uFF10'..'\uFF19',in '\uFF1A'..'\uFF1B',in '\uFF1C'..'\uFF1E', 127 | in '\uFF1F'..'\uFF20',in '\uFF21'..'\uFF3A',in '\uFF41'..'\uFF5A',in '\uFFE0'..'\uFFE1', 128 | in '\uFFE5'..'\uFFE6' -> true 129 | else -> false 130 | } 131 | } 132 | 133 | /** 134 | * @return Length accounting for full-width characters 135 | */ 136 | fun String.widthLength(): Int 137 | { 138 | var len = length 139 | forEach { if (it.isFullWidth()) len++ } 140 | return len 141 | } 142 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /src/main/kotlin/org/hydev/logger/coloring/LinearGradient.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.logger.coloring 2 | 3 | import org.hydev.logger.* 4 | import org.hydev.logger.format.AnsiColor.RESET 5 | import java.awt.Color 6 | import kotlin.math.roundToInt 7 | import kotlin.math.tan 8 | 9 | class LinearGradient(private val colors: MutableList) 10 | { 11 | init 12 | { 13 | colors.sortBy { it.pos } 14 | } 15 | 16 | constructor(c1: GradientPoint, c2: GradientPoint, vararg colors: GradientPoint): 17 | this(mutableListOf(c1, c2, *colors)) 18 | 19 | constructor(c1: Color, c2: Color, vararg colors: Color) : 20 | this(mutableListOf(c1, c2, *colors).mapIndexed { i, it -> GradientPoint(it, i * 10) }.toMutableList()) 21 | 22 | /** 23 | * Create a gradient list of colors for a set size. 24 | * 25 | * @param size How many pixels (chars) are there? 26 | * @return A gradient of colors, one color for every pixel (char) 27 | */ 28 | fun getColors(size: Int): List 29 | { 30 | val result = ArrayList() 31 | val scaled = getScaled(size).toMutableList() 32 | 33 | var c1 = scaled.removeAt(0) 34 | var c2 = scaled.removeAt(0) 35 | 36 | for (x in 0 until size) 37 | { 38 | // Switches from one color to the next 39 | if (c2.pos == x) 40 | { 41 | result.add(c2.color) 42 | c1 = c2 43 | c2 = scaled.removeAt(0) 44 | continue 45 | } 46 | 47 | // If they are the same color 48 | if (c1 == c2) 49 | { 50 | result.add(c1.color) 51 | continue 52 | } 53 | 54 | // Ratio is calculated with the relative position of the two colors 55 | val ratio = (x - c1.pos).toFloat() / (c2.pos - c1.pos).toFloat() 56 | 57 | // Calculate proportional rgb values 58 | val r = getColorWithRatio(c1.color.r, c2.color.r, ratio) 59 | val g = getColorWithRatio(c1.color.g, c2.color.g, ratio) 60 | val b = getColorWithRatio(c1.color.b, c2.color.b, ratio) 61 | 62 | result.add(Color(r, g, b)) 63 | } 64 | return result 65 | } 66 | 67 | fun getColorWithRatio(v1: Int, v2: Int, ratio: Float) = (v2 * ratio + v1 * (1 - ratio)).toInt() 68 | 69 | /** 70 | * Scale a list of color points to a specific pixel size 71 | * 72 | * Example: size = 7 73 | * colors = [(0, Red), (100, Blue), (200, Orange), (250, Purple)] 74 | * 75 | * Return: [(0, Red), (3, Blue), (6, Orange), (7, Purple)] 76 | * 77 | * @param colors List of color points 78 | * @param size How many pixels (chars) are there? 79 | * @return List with positions scaled to match the size 80 | */ 81 | fun getScaled(size: Int): List 82 | { 83 | val scale = size.toDouble() / colors.last().pos 84 | return colors.map { GradientPoint(it.color, (it.pos * scale).roundToInt()) } 85 | } 86 | 87 | /** 88 | * Get a 2D plane of gradient colors, can be tilted. 89 | * 90 | * @param width 91 | * @param height 92 | * @param degrees 93 | * @return Plane of colors (Note: Y first!) 94 | */ 95 | fun getColorPlane(width: Int, height: Int, degrees: Double): List> 96 | { 97 | // y = slope * x 98 | val slope = tan(Math.toRadians(degrees)) 99 | 100 | // 0. Obtain offset: 101 | // - Offset defines how many pixels of gradient we need. 102 | // 103 | // Offset = 8 104 | // ↓ 105 | // 0 =\ 106 | // 1 = \ 107 | // 2 = \ 108 | // 3 = \ 109 | // 4 = \ 110 | // 5 = \ 111 | // 6 = \ 112 | // 7 = \ 113 | // 8 = ( \ 114 | // 0 9 ########## 115 | // 1 10 ###Text### 116 | // 2 11 ########## 117 | // ↑ 118 | // Actual lines of text 119 | 120 | val yOffset = (slope * width).toInt() 121 | val yWithOffset = height + yOffset 122 | 123 | // 1. Convert a line of gradient pixels to a plane of colors: 124 | // - Each line segment drawn parallel to the slope has a consistent 125 | // color equal to the color of the beginning of the line. 126 | // 127 | // 0 =\ 128 | // 1 =\\ 129 | // ... 130 | // 7 =\\\\\\\\ 131 | // 8 =\\\\\\\\\ 132 | // 0 9 |--------|\ 133 | // 1 10 |\\Text\\|\\ 134 | // 2 11 |________|\\\ 135 | 136 | val colorPlane = List(height) { ArrayList() } 137 | val verticalColors = getColors(yWithOffset) 138 | for (sourceY in verticalColors.indices) 139 | { 140 | for (x in 0..width) 141 | { 142 | // Use x and slope to calculate the actual y value 143 | val actualY = sourceY + (slope * x).toInt() - yOffset 144 | 145 | if (actualY in 0 until height) 146 | colorPlane[actualY].add(0, verticalColors[sourceY]) 147 | } 148 | } 149 | 150 | return colorPlane 151 | } 152 | 153 | /** 154 | * Colorize some text, no angles involved 155 | * 156 | * @param text 157 | * @return Colored text 158 | */ 159 | fun colorText(text: String): String 160 | { 161 | if (text.isBlank()) return text 162 | 163 | val lines = text.lines() 164 | val colors = getColors(lines.map { it.widthLength() }.max()!!) 165 | val result = StringBuilder() 166 | 167 | for (line in lines) 168 | { 169 | var colorIndex = 0 170 | line.forEach { c -> 171 | result.append(colors[colorIndex].foreground()).append(c) 172 | colorIndex++ 173 | 174 | // Account for full width characters 175 | if (c.isFullWidth()) colorIndex++ 176 | } 177 | result.append(RESET).append("\n") 178 | } 179 | 180 | return result.toString().trimEnd('\n') 181 | } 182 | 183 | /** 184 | * Colorize some text, can be tilted 185 | * 186 | * @param text 187 | * @param degrees 188 | * @return Colored text 189 | */ 190 | fun colorText(text: String, degrees: Double): String 191 | { 192 | // Get array of char arrays 193 | if (text.isBlank()) return text 194 | val lines = text.replace("\u0000", "").lines() 195 | 196 | // x = How many chars are in a line 197 | // y = How many lines are in text 198 | val width = lines.map { it.widthLength() }.max()!! - 1 199 | val colorPlane = getColorPlane(width, lines.size, degrees) 200 | 201 | // 2. Map the color plane to the actual texts. 202 | val result = StringBuilder() 203 | for (y in lines.indices) 204 | { 205 | var colorIndex = 0 206 | val one = lines[y].map { c -> 207 | val line = colorPlane[y][colorIndex].foreground() + c 208 | colorIndex++ 209 | 210 | // Account for full width characters 211 | if (c.isFullWidth()) colorIndex++ 212 | line 213 | }.joinToString("", "", "") 214 | 215 | result.line(one + RESET).toString() 216 | } 217 | return result.toString().trimEnd('\n') 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /docs/backups/old_readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 |

7 | 妈妈快看, 那个人的日志会变色!! 8 |

9 |
10 | Maven 导入   11 | 介绍   12 | 开发   13 | 开源条款 14 |
15 | 16 |
17 | 18 |

19 | 20 |

21 | 22 |
23 | 24 | 25 | 介绍 (v4.0.254): 26 | -------- 27 | 28 | 这是一个支持颜色和文件输出的日志工具, 理论上颜色输出支持所有支持 ANSI ESC Code 的控制台. 29 | 30 | #### 已实现功能: 31 | 32 | * **v2.5.19** 33 | * 同时向多个环境实例输出日志 34 | * 控制台环境 35 | * 带颜色的控制台环境 (如果控制台不支持颜色也不会乱码) 36 | * 文件输出环境 37 | * 带颜色的文件输出环境 (不推荐, 因为文件编辑器不支持颜色的话会乱码) 38 | * 四个日志级别 (Log, Debug, Error, Warning) 39 | * 日志显示时间 40 | * Debug 和 Error 级别日志显示当前执行的类路径和行数 41 | * 可以有多个实例 42 | * 每个实例可以用同样的环境同时有不同的前缀 43 |

44 | * **v2.5.20** 45 | * 用 RGB 获取 ANSI 颜色代码 46 | * 定向单行渐变 (影响一行, 或一串字) 47 |

48 | * **v2.5.21** 49 | * 自定义每个日志级别的输出格式 50 | * 可以调是否强制输出颜色 51 |

52 | * **v2.5.22** 53 | * 自定义颜色支持级别 (Default, Preset Only, Passthrough, Forced) 54 | * 多点单行渐变 55 |

56 | * **v2.5.23** 57 | * 定向二维渐变 (影响一个字符串数组, 输出字符画的时候特别有用) 58 | * 多点二维渐变 (带渐变角度) 59 | * 计时器 Logger 60 |

61 | * **v3.1.215** 62 | * 版本号第三位改为按提交量增加 63 | * 带Slf4j那样格式的输出 (比如`logger.log("12{}45", 3);`) 64 | * 添加DISABLED颜色支持级别 65 | * `logger.error`添加Throwable支持 66 |

67 | * **v3.5.220** 68 | * 带printf那样格式的输出 (比如`logger.logf("PI = %f%n", Math.PI);`) 69 |

70 | * **v4.0.247** 71 | * 支持`8BIT (256)`颜色环境 72 | 73 | #### 待实现(TODO)的功能: 74 | 75 | * 无视原有颜色的全局渐变 76 | 77 |
78 | 79 | 80 | Maven 导入: 81 | -------- 82 | 83 | 没有添加 JitPack 的 Repo 的话首先添加 Repo, 在 pom 里面把这些粘贴进去: 84 | 85 | ```xml 86 | 87 | 88 | jitpack.io 89 | https://jitpack.io 90 | 91 | 92 | ``` 93 | 94 | 然后添加这个库: 95 | 96 | ```xml 97 | 98 | com.github.hydevelop 99 | HyLogger 100 | 4.0.254 101 | 102 | ``` 103 | 104 | 然后 ReImport 之后就导入好了! 105 | 106 |
107 | 108 | 109 | Gradle 导入: 110 | -------- 111 | 112 | 没有添加 JitPack 的 Repo 的话首先添加 Repo, 在 pom 里面把这些粘贴进去: 113 | 114 | ```gradle 115 | allprojects { 116 | repositories { 117 | ... 118 | maven { url 'https://jitpack.io' } 119 | } 120 | } 121 | ``` 122 | 123 | 然后添加这个库: 124 | 125 | ```gradle 126 | dependencies { 127 | implementation 'com.github.hydevelop:HyLogger:4.0.254' 128 | } 129 | ``` 130 | 131 | 132 | 133 | #### [其他导入(SBT / Leiningen)](https://jitpack.io/#HyDevelop/HyLogger/4.0.254) 134 | 135 |
136 | 137 | 138 | 开发: 139 | -------- 140 | 141 | ### 1. 创建实例管理器: 142 | 143 | ```java 144 | LoggerInstanceManager lim = new LoggerInstanceManager(日志环境 ...); 145 | ``` 146 | 147 | 例子: 如果需要一边在后台输出带颜色的一边在文件里输出不带颜色的, 写成这样: 148 | 149 | ```java 150 | LoggerInstanceManager lim = new LoggerInstanceManager(new ConsoleColoredEnv(), new FileEnv("logs", "log")); 151 | ``` 152 | 153 | 可用的日志环境: 154 | 155 | ```java 156 | ConsoleEnv // 无颜色的控制台环境 157 | ConsoleColoredEnv // 带颜色的控制台环境 (使用 Jansi 类库, 控制台不支持颜色的话也不会乱码) 158 | FileEnv // 无颜色的文件环境 159 | FileColoredEnv // 带颜色的文件环境 (大部分编辑器不支持, 所以像我一样的 RGB 厨以外不推荐使用ww) 160 | ``` 161 | 162 | ### 2. 创建/获取实例: 163 | 164 | ```java 165 | // 注意: 可以有多个实例, 每个实例可以有不同的前缀和是否输出 Debug, 166 | // 但是同一个实例管理器下的所有实例都有同样的输出环境. 167 | 168 | HyLogger logger = lim.getLoggerInstance(前缀, 是否输出 Debug); 169 | ``` 170 | 171 | 例子: 如果前缀是 Main, 然后不输出 Debug: (这个很容易懂吧... 172 | 173 | ```java 174 | HyLogger logger = lim.getLoggerInstance("Main", false); 175 | ``` 176 | 177 | 例子#2: 如果前缀是线程号...: 178 | 179 | ```java 180 | HyLogger logger = lim.getLoggerInstance("线程#" + Thread.currentThread().getId(), false); 181 | ``` 182 | 183 | ### 3. 使用实例: 184 | 185 | ```java 186 | logger.log("一条 Log 消息"); // 这些是不同输出级别的日志 187 | logger.debug("一条 Debug 消息"); // Debug 日志只有开了 debug 开关才会输出 188 | logger.error("一条 Error 消息"); 189 | logger.warning("一条 Warning 消息"); 190 | 191 | // 这个渐变详细教程在下面 192 | logger.fancy.logGradient("一条从橙色渐变到粉色的 Log 消息\n", 193 | new Color(255, 140, 0), 194 | new Color(255, 0, 128)); 195 | ``` 196 | 197 | ### 4. 更改输出格式: 198 | 199 | ```java 200 | lim.setFormat(日志级别, 新的格式); // 给某个日志级别设置输出格式 201 | ``` 202 | 203 | 格式里可用的变量: 204 | 205 | | 变量占位符 | 用处 | 例子 | 206 | | :------------: | :------------: | :------------: | 207 | | {time} | 当前时间 | 18-07-06 22:05:41 | 208 | | {prefix} | 日志前缀 | Thread-1 | 209 | | {message} | 输出内容 | 竜神の剣を喰らえ! | 210 | | {st.class} | 发日志的类 | cc.moecraft.Test | 211 | | {st.method} | 发日志的方法 | main | 212 | | {st.line} | 发日志的行数 | 31 | 213 | | {st.full} | 上面三个一起 | cc.moecraft.Test.main:31 | 214 | 215 | 颜色用了简写替换, 用的颜色码和 Minecraft 的颜色码一样, &1 到 &f. 216 | [完整颜色码表](https://i.imgur.com/MSdHuMW.jpg) 217 | 218 | 格式表: 219 | 220 | | 格式简写 | 代表着什么 | 221 | | :------------: | :------------: | 222 | | &l | 加粗 | 223 | | &o | 斜体 | 224 | | &n | 下划线 | 225 | 226 | 例子 (默认格式): 227 | 228 | ```java 229 | setFormat(LOG, "&f[&5{time}&f] [&1{prefix}&f] [&aINFO&f] &r{message}&r"); 230 | setFormat(DEBUG, "&f[&5{time}&f] [&1{prefix}&f] [&bDEBUG&f(&e{st.full}&f)] &b{message}&r"); 231 | setFormat(ERROR, "&f[&5{time}&f] [&1{prefix}&f] [&cERROR&f(&e{st.full}&f)] &c{message}&r"); 232 | setFormat(WARNING, "&f[&5{time}&f] [&1{prefix}&f] [&cWARNING&f] &e{message}&r"); 233 | ``` 234 | 235 | ### 5. 添加颜色和 ANSI 格式预设: 236 | 237 | ```java 238 | // 颜色其实就是一个 Enum 啦... 239 | // 直接用字符串的+就行了_(:з」∠)_ 240 | 241 | // 注意: 必须有支持颜色的环境才有效 (废话! 242 | 243 | AnsiColor.RESET // 同时重置颜色和格式 244 | AnsiFormat.RESET // 同样是同时重置颜色和格式 245 | 246 | AnsiColor.BLACK // 黑色 247 | AnsiColor.RED // 红色 248 | AnsiColor.GREEN // 原谅色 <3 249 | AnsiColor.YELLOW // 黄色 250 | AnsiColor.BLUE // 蓝色 251 | AnsiColor.PURPLE // 紫色 252 | AnsiColor.CYAN // 青色 253 | AnsiColor.WHITE // 白色 254 | 255 | AnsiFormat.HIGH_INTENSITY // 加♂粗 256 | AnsiFormat.LOW_INTENSITY // 变细...? 257 | AnsiFormat.ITALIC // 斜体 258 | AnsiFormat.UNDERLINE // 下划线 259 | AnsiFormat.BLINK // 闪! (意义不明 260 | AnsiFormat.RAPID_BLINK // 更快的闪 (应该大部分后台都不支持这两个闪... 261 | AnsiFormat.REVERSE_VIDEO // 反色 262 | AnsiFormat.INVISIBLE_TEXT // 隐身 (意义不明 #2 263 | 264 | // 不用看了真的没有中划线和魔法随机ww 265 | ``` 266 | 267 | 例子: 268 | 269 | ```java 270 | logger.log(AnsiColor.GREEN + "" + AnsiFormat.HIGH_INTENSITY + "当然是选择原谅她!"); 271 | ``` 272 | 273 | ### 6. 添加自定义 RGB 颜色: 274 | 275 | ```java 276 | // 注意: RGB 颜色不是所有后台都支持 277 | // 大部分有独立主题配置的后台都不支持 278 | // 比如 IntelliJ IDEA 自带的那个就不支持 279 | 280 | AnsiRGB.toAnsi(红, 绿, 蓝); // 用 RGB 获取 ANSI 颜色码 281 | 282 | new AnsiRGB(颜色对象).toAnsi(); // 用 java.awt.Color 颜色对象获取 ANSI 颜色码 283 | ``` 284 | 285 | 例子: 286 | 287 | ```java 288 | logger.log(AnsiRGB.toAnsi(45, 194, 80) + "当然是选择原谅她!"); 289 | ``` 290 | 291 | ### 7. Logger.fancy 特效日志: 292 | 293 | #### 7.1. 双向/多点 线性渐变: 294 | 295 | ```java 296 | // 线性渐变: 297 | 298 | logger.fancy.logGradient(消息, 颜色或渐变对象); 299 | 300 | // 注意: 必须要支持 RGB 颜色的后台才支持定向渐变 301 | // 注意: 颜色对象的话至少要两个才行 302 | 303 | // 渐变预设在 GradientPresets 类里 304 | ``` 305 | 306 | 例子: 307 | 308 | ```java 309 | // 使用颜色渐变: 310 | logger.fancy.logGradient("测试橙色渐变到粉色", new Color(255, 140, 0), new Color(255, 0, 128)); 311 | 312 | // 使用渐变预设渐变: 313 | logger.fancy.logGradient("##############测试蓝到紫到红多点渐变##############", GradientPresets.BPR); 314 | ``` 315 | 316 | #### 7.2. 二维文字块渐变: 317 | 318 | ```java 319 | // 先创建一个文字块对象: 320 | // 注意: 句子不用加换行, 每句直接逗号分开 321 | Paragraph paragraph = new Paragraph(句子, 句子, 句子 ...); 322 | 323 | // 渐变输出 324 | // 注意: 这个角度单位不是 radian, 而是 degrees 325 | logger.fancy.logGradient(paragraph, 渐变对象, 角度); 326 | ``` 327 | 328 | 例子: 329 | 330 | ```java 331 | // 创建文字块对象 332 | Paragraph paragraph = new Paragraph( 333 | "┬ ┬┬ ┬┬ ┌─┐┌─┐┌─┐┌─┐┬─┐", 334 | "├─┤└┬┘│ │ ││ ┬│ ┬├┤ ├┬┘", 335 | "┴ ┴ ┴ ┴─┘└─┘└─┘└─┘└─┘┴└─" 336 | ); 337 | 338 | // 用渐变预设输出 339 | logger.fancy.logGradient(paragraph, GradientPresets.BPR, 15); 340 | 341 | // 用自定义颜色创建渐变对象输出 342 | logger.fancy.logGradient(paragraph, 343 | new MultiPointLinearGradient( 344 | new Color(64, 224, 208), 345 | new Color(255, 140, 0), 346 | new Color(255, 0, 128)), 15); 347 | ``` 348 | 349 | ### 8. Logger.timing 计时器: 350 | 351 | ```java 352 | // 用之前先 init: 353 | logger.timing.init(); 354 | 355 | // 输出一次时间点: 356 | logger.timing.time(); 357 | 358 | // 重置计时器时间: 359 | logger.timing.reset(); 360 | 361 | // 输出并重置: 362 | logger.timing.timeAndReset(); 363 | 364 | // 用完后 clear: 365 | logger.timing.clear(); 366 | ``` 367 | 368 | ### 9. 测试过的兼容和不兼容颜色的后台程序: 369 | 370 | 如果有新的测试结果欢迎 Email 我(me@hydev.org), 我会加进这个列表的! 371 | 注意: Independent 不是一个系统的名字, 它是指这个后台程序兼容多个系统. 372 | 373 | 完全兼容: 374 | * Ubuntu 17 - XFCE4 Terminal Emulator 375 | * Ubuntu 17 - Gnome-Terminal 376 | * Independent - Git Bash 377 | * Independent - MinGW / MinTTY 378 | * Independent - Termius 2.1.6 379 | 380 | 色段兼容: (这些会把 RGB 颜色分成更概括性的颜色段, 不会乱但是显示效果没有完全兼容的好) 381 | * Windows 10 - Cmder 1.3.6 382 | * Windows 10 - CmdEmu 180626 383 | 384 | 预设兼容: (这些只会兼容预设颜色, 不兼容完整 RGB, 所以 RGB 码会被解析成很乱的普通颜色码) 385 | * Windows 10 - 命令提示符 386 | * Independent - IntelliJ IDEA Run/Debug Console 2018.1.5 387 | * Independent - Xshell 5 388 | 389 | 不兼容: (这些完全不兼容颜色, 会乱码, 或者兼容得不完整到无法算为兼容预设的程度) 390 | * Windows 10 - Powershell 391 | * Windows 10 - Babun 1.2.0 392 | * Independent - PowerCmd 2.2 393 | * Independent - Hyper 394 | * Independent - Terminus Alpha 395 | 396 | ### 10. 给 Eclipse 添加兼容: 397 | 398 | 在 `Eclipse Marketplace` 中安装 `ANSI Escape in Console` 插件,并启用即可。 399 | 400 |
401 | 402 | 403 | 开源条款: [MIT License](/LICENSE) 404 | -------- 405 | --------------------------------------------------------------------------------