├── .classpath ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── LICENSE ├── README.md ├── beautiful_logger2.iml ├── pom.xml └── src ├── main └── java │ ├── com.github.forax.beautifullogger │ ├── Logger.java │ ├── LoggerConfig.java │ └── LoggerImpl.java │ └── com │ └── github │ └── forax │ └── beautifullogger │ └── tool │ ├── LoggerGenerator.java │ ├── ModuleInfoGenerator.java │ ├── ReflectionSupplier.java │ ├── ReflectionSupplierEncoder.java │ └── Rewriter.java └── test ├── java └── com │ └── github │ └── forax │ └── beautifullogger │ ├── LoggerConfigSupport.java │ ├── LoggerConfigurationTests.java │ ├── LoggerLevelTests.java │ ├── integration │ ├── jul │ │ └── VerySimpleTests.java │ ├── log4j │ │ └── VerySimpleTests.java │ ├── slf4j │ │ └── VerySimpleTests.java │ └── systemlogger │ │ └── VerySimpleTests.java │ └── perf │ ├── LoggerDisabledBenchMark.java │ ├── LoggerDisabledLoopBenchMark.java │ └── PatternBenchMark.java └── resources ├── com.github.forax.beautifullogger.integration.log4j └── log4j2.xml ├── com.github.forax.beautifullogger.integration.logback └── logback.xml ├── com.github.forax.beautifullogger.integration.slf4j └── logback.xml └── com.github.forax.beautifullogger.perf ├── log4j2.xml └── logback.xml /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | java: [ '8', '9', '11', '13', '15', '17', '21', '24' ] 17 | name: Java ${{ matrix.Java }} sample 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Setup java 21 | uses: actions/setup-java@v3 22 | with: 23 | distribution: 'zulu' 24 | java-version: ${{ matrix.java }} 25 | - name: build 26 | run: mvn -B package 27 | -------------------------------------------------------------------------------- /.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 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | # Everything below bin/. 25 | /bin/ 26 | 27 | # IDEA files 28 | /.idea/ 29 | 30 | /classes/ 31 | /target/ 32 | /deps/ 33 | /eclipse-output/ 34 | /pro/ 35 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | beautiful_logger2 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled 3 | org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore 4 | org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull 5 | org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= 6 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault 7 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= 8 | org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable 9 | org.eclipse.jdt.core.compiler.annotation.nullable.secondary= 10 | org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 11 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 12 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 13 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 14 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 15 | org.eclipse.jdt.core.compiler.compliance=11 16 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 17 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 18 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 19 | org.eclipse.jdt.core.compiler.doc.comment.support=enabled 20 | org.eclipse.jdt.core.compiler.problem.APILeak=warning 21 | org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning 22 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 23 | org.eclipse.jdt.core.compiler.problem.autoboxing=ignore 24 | org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning 25 | org.eclipse.jdt.core.compiler.problem.deadCode=warning 26 | org.eclipse.jdt.core.compiler.problem.deprecation=warning 27 | org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled 28 | org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled 29 | org.eclipse.jdt.core.compiler.problem.discouragedReference=warning 30 | org.eclipse.jdt.core.compiler.problem.emptyStatement=warning 31 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 32 | org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning 33 | org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning 34 | org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled 35 | org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore 36 | org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning 37 | org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning 38 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 39 | org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning 40 | org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled 41 | org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning 42 | org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning 43 | org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning 44 | org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error 45 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled 46 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled 47 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled 48 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private 49 | org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore 50 | org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning 51 | org.eclipse.jdt.core.compiler.problem.missingDefaultCase=warning 52 | org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning 53 | org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled 54 | org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning 55 | org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning 56 | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled 57 | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected 58 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags 59 | org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error 60 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=enabled 61 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled 62 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public 63 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning 64 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled 65 | org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning 66 | org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore 67 | org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning 68 | org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning 69 | org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore 70 | org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning 71 | org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning 72 | org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error 73 | org.eclipse.jdt.core.compiler.problem.nullReference=warning 74 | org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error 75 | org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning 76 | org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning 77 | org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning 78 | org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning 79 | org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning 80 | org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning 81 | org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning 82 | org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning 83 | org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning 84 | org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning 85 | org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning 86 | org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore 87 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=warning 88 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=warning 89 | org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled 90 | org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning 91 | org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled 92 | org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled 93 | org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled 94 | org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning 95 | org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning 96 | org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning 97 | org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled 98 | org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning 99 | org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning 100 | org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning 101 | org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning 102 | org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning 103 | org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled 104 | org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=warning 105 | org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning 106 | org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning 107 | org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore 108 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning 109 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled 110 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled 111 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled 112 | org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=warning 113 | org.eclipse.jdt.core.compiler.problem.unusedImport=warning 114 | org.eclipse.jdt.core.compiler.problem.unusedLabel=warning 115 | org.eclipse.jdt.core.compiler.problem.unusedLocal=warning 116 | org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning 117 | org.eclipse.jdt.core.compiler.problem.unusedParameter=warning 118 | org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled 119 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled 120 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled 121 | org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning 122 | org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning 123 | org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning 124 | org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning 125 | org.eclipse.jdt.core.compiler.release=enabled 126 | org.eclipse.jdt.core.compiler.source=11 127 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Rémi Forax 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # beautiful_logger [![.github/workflows/main.yml](https://github.com/forax/beautiful_logger/actions/workflows/main.yml/badge.svg)](https://github.com/forax/beautiful_logger/actions/workflows/main.yml) 2 | Yet another logger API in Java with beautiful features 3 | 4 | beautiful_logger is a mostly-zero-overhead wrapper on top of the existing logging implementations with a familiar API (info, error, etc) 5 | that let you configure/re-configure the logger dynamically in a programmatic way. 6 | 7 | This library requires at least Java 8 at runtime and is fully compatible with Java 9 modules. 8 | 9 | For the support of Java 15+, the version 0.10.4+ should be used. 10 | 11 | The javadoc of the latest version is [available online](https://jitpack.io/com/github/forax/beautiful_logger/master-SNAPSHOT/javadoc/). 12 | 13 | ## Features 14 | - *real* zero cost (no allocation, no branch, no assembly code) if a logger is disabled 15 | - zero overhead cost when delegating to the logging libraries you already use, SLF4J, Log4J, Logback, JUL or SystemLogger [JEP 264](http://openjdk.java.net/jeps/264). 16 | - dynamic configuration/re-configuration which doesn't use costly inter-thread signaling 17 | - no configuration file, no XML, etc, everything is done programmatically 18 | - very small modular jar with no dependency 19 | 20 | 21 | ## Why another logging API ? 22 | 23 | Technically it's more a facade like SLF4J, anyway, beautiful_logger exists because no other existing logging libraries 24 | provide at least one of features listed above. 25 | 26 | 27 | ## Why do you claim that there is no overhead ? 28 | 29 | The implementation of this API ensures that the JIT can fully inline any calls to the Logger API without decrementing your inlining budget. 30 | This is similar to the way, MethodHandle or VarHandle are optimized in the JDK. 31 | The main drawback is that it put more pressure to the JITs so it may lengthen the time to steady state of an application. 32 | 33 | 34 | ## Example 35 | 36 | ```java 37 | import com.github.forax.beautifullogger.Logger; 38 | 39 | class Example { 40 | // getLogger with no argument uses the current class as configuration class 41 | private static final Logger LOGGER = Logger.getLogger(); 42 | 43 | public static void main(String[] args) { 44 | for(int i = 0; i < 10; i++) { 45 | // use a lambda that does not capture any parameters 46 | LOGGER.error((int value) -> "message " + value, i); 47 | 48 | if (i == 1) { 49 | // disable the logger programmatically 50 | LoggerConfig.fromClass(Example.class).update(opt -> opt.enable(false)); 51 | } 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | ## Build Tool Integration [![](https://jitpack.io/v/forax/beautiful_logger.svg)](https://jitpack.io/#forax/beautiful_logger) 58 | 59 | Get latest binary distribution via [JitPack](https://jitpack.io/#forax/beautiful_logger) 60 | 61 | 62 | ### Maven 63 | 64 | ```xml 65 | 66 | 67 | jitpack.io 68 | https://jitpack.io 69 | 70 | 71 | 72 | com.github.forax 73 | beautiful_logger 74 | master-SNAPSHOT 75 | 76 | ``` 77 | 78 | ### Gradle 79 | 80 | ```gradle 81 | repositories { 82 | ... 83 | maven { url 'https://jitpack.io' } 84 | } 85 | dependencies { 86 | compile 'com.github.forax:beautiful_logger:master-SNAPSHOT' 87 | } 88 | ``` -------------------------------------------------------------------------------- /beautiful_logger2.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.forax 6 | beautifullogger 7 | 0.11 8 | 9 | ${project.groupId}:${project.artifactId} 10 | Yet another logger API in Java with beautiful features 11 | https://github.com/forax/beautiful_logger 12 | 13 | 14 | 15 | MIT License 16 | http://www.opensource.org/licenses/mit-license.php 17 | 18 | 19 | 20 | 21 | 22 | Remi Forax 23 | forax@univ-mlv.fr 24 | University Paris-East Marne-la-vallee 25 | http://www.u-pem.fr/ 26 | 27 | 28 | 29 | 30 | scm:git:git@github.com:forax/beautiful_logger.git 31 | scm:git:ssh://github.com:forax/beautiful_logger.git 32 | https://github.com/forax/beautiful_logger/blob/master/ 33 | 34 | 35 | 36 | 37 | UTF-8 38 | 39 | 40 | 41 | 42 | org.ow2.asm 43 | asm 44 | 9.7.1 45 | true 46 | 47 | 48 | 49 | org.junit.jupiter 50 | junit-jupiter-api 51 | 5.11.4 52 | test 53 | 54 | 55 | org.junit.jupiter 56 | junit-jupiter-params 57 | 5.11.4 58 | test 59 | 60 | 61 | 62 | org.openjdk.jmh 63 | jmh-core 64 | 1.37 65 | true 66 | 67 | 68 | 69 | org.apache.logging.log4j 70 | log4j-core 71 | 2.24.3 72 | true 73 | 74 | 75 | org.slf4j 76 | slf4j-api 77 | 2.0.16 78 | true 79 | 80 | 81 | ch.qos.logback 82 | logback-classic 83 | 1.3.15 84 | true 85 | 86 | 87 | ch.qos.logback 88 | logback-core 89 | 1.3.15 90 | true 91 | 92 | 93 | 94 | com.google.flogger 95 | flogger 96 | 0.8 97 | test 98 | true 99 | 100 | 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-compiler-plugin 107 | 3.13.0 108 | 109 | 110 | 1.8 111 | 1.8 112 | 113 | 114 | 115 | 116 | maven-surefire-plugin 117 | 3.5.2 118 | 119 | 120 | 121 | org.codehaus.mojo 122 | exec-maven-plugin 123 | 3.5.0 124 | 125 | 126 | prepare-package 127 | module-generator 128 | 129 | java 130 | 131 | 132 | com.github.forax.beautifullogger.tool.ModuleInfoGenerator 133 | 134 | target/classes 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-javadoc-plugin 144 | 3.11.2 145 | 146 | src/main/java/ 147 | 8 148 | 149 | 150 | 151 | 152 | attach-javadocs 153 | 154 | jar 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /src/main/java/com.github.forax.beautifullogger/Logger.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger; 2 | 3 | import java.lang.invoke.MethodHandle; 4 | import java.util.Objects; 5 | import java.util.function.BiFunction; 6 | import java.util.function.Consumer; 7 | import java.util.function.DoubleFunction; 8 | import java.util.function.Function; 9 | import java.util.function.IntFunction; 10 | import java.util.function.LongFunction; 11 | import java.util.function.Supplier; 12 | 13 | import com.github.forax.beautifullogger.LoggerConfig.ConfigOption; 14 | 15 | /** 16 | * Log events of the application. 17 | *

18 | * A logger is created using one of the methods {@link #getLogger(Class, Consumer) getLogger}. 19 | * The {@link LoggerConfig configuration} of a that Logger is associated to the class 20 | * that construct it. 21 | * 22 | *

 23 |  * import com.github.forax.beautifullogger.Logger;
 24 |  *
 25 |  * class Example {
 26 |  *   private static final Logger LOGGER = Logger.getLogger();
 27 |  *   
 28 |  *   ...
 29 |  * 
30 | * 31 | * To log an event, one should call the method named by the severity of the event, 32 | * trace for level TRACE, debug for level DEBUG, etc. 33 | * 34 | *
 35 |  *   public static void main(String[] args) {
 36 |  *     LOGGER.info((int length) -> "there are " + length + " arguments", args.length);
 37 |  *   
 38 |  *     ...
 39 |  * 
40 | * 41 | * Each logging method has several overloads allowing to choose a message provider 42 | * to avoid if possible, allocations due to concatenation, non constant lambda or 43 | * boxing of primitive values. 44 | * 45 | *

 

46 | * 47 | * 48 | * 49 | * 50 | * 51 | * 52 | * 53 | * 54 | * 55 | * 56 | *
message providers
String constant String (do not use concatenation !)
Supplier costly operation that does not need arguments
Function costly operation that needs one Object argument
IntFunction costly operation that needs one integer argument
LongFunction costly operation that needs one long argument
DoubleFunctioncostly operation that needs one double argument
BiFunction costly operation that needs two arguments
supported message providers
57 | *

 

58 | */ 59 | public interface Logger { 60 | /** 61 | * Log levels used for indicating the severity of an event. 62 | *

63 | * Log levels are ordered from the least specific to the most, 64 | * TRACE < DEBUG < INFO < WARNING < ERROR. 65 | *

66 | * All methods of {@link Logger} are emitted with a a log level 67 | * corresponding to name of the method, the configuration level 68 | * of a Logger can be changed using {@link LoggerConfig#update(Consumer) logConfig.update(upd -> upd.level(aLevel))}. 69 | * 70 | * @see LoggerConfig#level() 71 | */ 72 | public enum Level { 73 | /** a fine grained debug level */ 74 | TRACE, 75 | /** a general debug level */ 76 | DEBUG, 77 | /** an information level */ 78 | INFO, 79 | /** a non normal state level */ 80 | WARNING, 81 | /** an error level */ 82 | ERROR; 83 | 84 | static final Level[] LEVELS = values(); 85 | } 86 | 87 | /** 88 | * Log a message at {@link Level#ERROR} level with an exception. 89 | * @param message the log message. 90 | * @param context an exception or null. 91 | */ 92 | void error(String message, Throwable context); 93 | /** 94 | * Log a message provided by the supplier at {@link Level#ERROR} level. 95 | * @param messageProvider the provider of the message. 96 | */ 97 | void error(Supplier messageProvider); 98 | /** 99 | * Log a message provided by the function at {@link Level#ERROR} level. 100 | * @param the type of the first argument. 101 | * @param messageProvider the provider of the message. 102 | * @param arg0 the first argument. 103 | */ 104 | void error(Function messageProvider, T arg0); 105 | /** 106 | * Log a message provided by the function at {@link Level#ERROR} level. 107 | * @param messageProvider the provider of the message. 108 | * @param arg0 the first argument. 109 | */ 110 | void error(IntFunction messageProvider, int arg0); 111 | /** 112 | * Log a message provided by the function at {@link Level#ERROR} level. 113 | * @param messageProvider the provider of the message. 114 | * @param arg0 the first argument. 115 | */ 116 | void error(LongFunction messageProvider, long arg0); 117 | /** 118 | * Log a message provided by the function at {@link Level#ERROR} level. 119 | * @param messageProvider the provider of the message. 120 | * @param arg0 the first argument. 121 | */ 122 | void error(DoubleFunction messageProvider, double arg0); 123 | /** 124 | * Log a message provided by the function with two arguments at {@link Level#ERROR} level. 125 | * @param the type of the first argument. 126 | * @param the type of the second argument. 127 | * @param messageProvider the provider of the message. 128 | * @param arg0 the first argument. 129 | * @param arg1 the second argument. 130 | */ 131 | void error(BiFunction messageProvider, T arg0, U arg1); 132 | 133 | /** 134 | * Log a message at {@link Level#WARNING} level with an exception. 135 | * @param message the log message. 136 | * @param context an exception or null. 137 | */ 138 | void warning(String message, Throwable context); 139 | /** 140 | * Log a message provided by the supplier at {@link Level#WARNING} level. 141 | * @param messageProvider the provider of the message. 142 | */ 143 | void warning(Supplier messageProvider); 144 | /** 145 | * Log a message provided by the function at {@link Level#WARNING} level. 146 | * @param the type of the first argument. 147 | * @param messageProvider the provider of the message. 148 | * @param arg0 the first argument. 149 | */ 150 | void warning(Function messageProvider, T arg0); 151 | /** 152 | * Log a message provided by the function at {@link Level#WARNING} level. 153 | * @param messageProvider the provider of the message. 154 | * @param arg0 the first argument. 155 | */ 156 | void warning(IntFunction messageProvider, int arg0); 157 | /** 158 | * Log a message provided by the function at {@link Level#WARNING} level. 159 | * @param messageProvider the provider of the message. 160 | * @param arg0 the first argument. 161 | */ 162 | void warning(LongFunction messageProvider, long arg0); 163 | /** 164 | * Log a message provided by the function at {@link Level#WARNING} level. 165 | * @param messageProvider the provider of the message. 166 | * @param arg0 the first argument. 167 | */ 168 | void warning(DoubleFunction messageProvider, double arg0); 169 | /** 170 | * Log a message provided by the function with two arguments at {@link Level#WARNING} level. 171 | * @param the type of the first argument. 172 | * @param the type of the second argument. 173 | * @param messageProvider the provider of the message. 174 | * @param arg0 the first argument. 175 | * @param arg1 the second argument. 176 | */ 177 | void warning(BiFunction messageProvider, T arg0, U arg1); 178 | 179 | /** 180 | * Log a message at {@link Level#INFO} level with an exception. 181 | * @param message the log message. 182 | * @param context an exception or null. 183 | */ 184 | void info(String message, Throwable context); 185 | /** 186 | * Log a message provided by the supplier at {@link Level#INFO} level. 187 | * @param messageProvider the provider of the message. 188 | */ 189 | void info(Supplier messageProvider); 190 | /** 191 | * Log a message provided by the function at {@link Level#INFO} level. 192 | * @param the type of the first argument. 193 | * @param messageProvider the provider of the message. 194 | * @param arg0 the first argument. 195 | */ 196 | void info(Function messageProvider, T arg0); 197 | /** 198 | * Log a message provided by the function at {@link Level#INFO} level. 199 | * @param messageProvider the provider of the message. 200 | * @param arg0 the first argument. 201 | */ 202 | void info(IntFunction messageProvider, int arg0); 203 | /** 204 | * Log a message provided by the function at {@link Level#INFO} level. 205 | * @param messageProvider the provider of the message. 206 | * @param arg0 the first argument. 207 | */ 208 | void info(LongFunction messageProvider, long arg0); 209 | /** 210 | * Log a message provided by the function at {@link Level#INFO} level. 211 | * @param messageProvider the provider of the message. 212 | * @param arg0 the first argument. 213 | */ 214 | void info(DoubleFunction messageProvider, double arg0); 215 | /** 216 | * Log a message provided by the function with two arguments at {@link Level#INFO} level. 217 | * @param the type of the first argument. 218 | * @param the type of the second argument. 219 | * @param messageProvider the provider of the message. 220 | * @param arg0 the first argument. 221 | * @param arg1 the second argument. 222 | */ 223 | void info(BiFunction messageProvider, T arg0, U arg1); 224 | 225 | /** 226 | * Log a message at {@link Level#DEBUG} level with an exception. 227 | * @param message the log message. 228 | * @param context an exception or null. 229 | */ 230 | void debug(String message, Throwable context); 231 | /** 232 | * Log a message provided by the supplier at {@link Level#DEBUG} level. 233 | * @param messageProvider the provider of the message. 234 | */ 235 | void debug(Supplier messageProvider); 236 | /** 237 | * Log a message provided by the function at {@link Level#DEBUG} level. 238 | * @param the type of the first argument. 239 | * @param messageProvider the provider of the message. 240 | * @param arg0 the first argument. 241 | */ 242 | void debug(Function messageProvider, T arg0); 243 | /** 244 | * Log a message provided by the function at {@link Level#DEBUG} level. 245 | * @param messageProvider the provider of the message. 246 | * @param arg0 the first argument. 247 | */ 248 | void debug(IntFunction messageProvider, int arg0); 249 | /** 250 | * Log a message provided by the function at {@link Level#DEBUG} level. 251 | * @param messageProvider the provider of the message. 252 | * @param arg0 the first argument. 253 | */ 254 | void debug(LongFunction messageProvider, long arg0); 255 | /** 256 | * Log a message provided by the function at {@link Level#DEBUG} level. 257 | * @param messageProvider the provider of the message. 258 | * @param arg0 the first argument. 259 | */ 260 | void debug(DoubleFunction messageProvider, double arg0); 261 | /** 262 | * Log a message provided by the function with two arguments at {@link Level#DEBUG} level. 263 | * @param the type of the first argument. 264 | * @param the type of the second argument. 265 | * @param messageProvider the provider of the message. 266 | * @param arg0 the first argument. 267 | * @param arg1 the second argument. 268 | */ 269 | void debug(BiFunction messageProvider, T arg0, U arg1); 270 | 271 | /** 272 | * Log a message at {@link Level#TRACE} level with an exception. 273 | * @param message the log message. 274 | * @param context an exception or null. 275 | */ 276 | void trace(String message, Throwable context); 277 | /** 278 | * Log a message provided by the supplier at {@link Level#TRACE} level. 279 | * @param messageProvider the provider of the message. 280 | */ 281 | void trace(Supplier messageProvider); 282 | /** 283 | * Log a message provided by the function at {@link Level#TRACE} level. 284 | * @param the type of the first argument. 285 | * @param messageProvider the provider of the message. 286 | * @param arg0 the first argument. 287 | */ 288 | void trace(Function messageProvider, T arg0); 289 | /** 290 | * Log a message provided by the function at {@link Level#TRACE} level. 291 | * @param messageProvider the provider of the message. 292 | * @param arg0 the first argument. 293 | */ 294 | void trace(IntFunction messageProvider, int arg0); 295 | /** 296 | * Log a message provided by the function at {@link Level#TRACE} level. 297 | * @param messageProvider the provider of the message. 298 | * @param arg0 the first argument. 299 | */ 300 | void trace(LongFunction messageProvider, long arg0); 301 | /** 302 | * Log a message provided by the function at {@link Level#TRACE} level. 303 | * @param messageProvider the provider of the message. 304 | * @param arg0 the first argument. 305 | */ 306 | void trace(DoubleFunction messageProvider, double arg0); 307 | /** 308 | * Log a message provided by the function with two arguments at {@link Level#TRACE} level. 309 | * @param the type of the first argument. 310 | * @param the type of the second argument. 311 | * @param messageProvider the provider of the message. 312 | * @param arg0 the first argument. 313 | * @param arg1 the second argument. 314 | */ 315 | void trace(BiFunction messageProvider, T arg0, U arg1); 316 | 317 | /** 318 | * Create a logger with the configuration of class that calls this method. 319 | * 320 | * @return a logger. 321 | * 322 | * @see LoggerConfig#fromClass(Class) 323 | */ 324 | public static Logger getLogger() { 325 | Class callerClass; 326 | if (LoggerImpl.GET_CALLER_CLASS_MH != null) { 327 | try { 328 | callerClass = (Class) LoggerImpl.GET_CALLER_CLASS_MH.invokeExact(); 329 | } catch (Throwable e) { 330 | throw new AssertionError(e); 331 | } 332 | } else { 333 | callerClass = LoggerImpl.getCallerClassNoStackWalker(); 334 | } 335 | return getLogger(callerClass, LoggerImpl.EMPTY_CONSUMER); 336 | } 337 | 338 | /** 339 | * Create a logger with the configuration of the configClass. 340 | * 341 | * @param configClass the class that hold the configuration. 342 | * @return a logger. 343 | * @throws NullPointerException if the configuration class is null. 344 | * 345 | * @see LoggerConfig#fromClass(Class) 346 | */ 347 | public static Logger getLogger(Class configClass) { 348 | return getLogger(configClass, LoggerImpl.EMPTY_CONSUMER); 349 | } 350 | 351 | /** 352 | * Create a logger with the configuration of the configClass and an initializer to change 353 | * the configuration. 354 | * 355 | * @param configClass the class that hold the configuration. 356 | * @param configUpdater a consumer that can update a configuration. 357 | * @return a logger. 358 | * @throws NullPointerException if the configuration class or the configuration updater is null. 359 | * 360 | * @see LoggerConfig#fromClass(Class) 361 | */ 362 | public static Logger getLogger(Class configClass, Consumer configUpdater) { 363 | Objects.requireNonNull(configClass); 364 | Objects.requireNonNull(configUpdater); 365 | if (configUpdater != LoggerImpl.EMPTY_CONSUMER) { 366 | LoggerConfig.fromClass(configClass).update(configUpdater); 367 | } 368 | MethodHandle mh = LoggerImpl.getLoggingMethodHandle(configClass, 4); 369 | return LoggerImpl.createLogger(mh); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/main/java/com.github.forax.beautifullogger/LoggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger; 2 | 3 | import static com.github.forax.beautifullogger.LoggerImpl.LoggerConfigKind.CLASS; 4 | import static com.github.forax.beautifullogger.LoggerImpl.LoggerConfigKind.MODULE; 5 | import static com.github.forax.beautifullogger.LoggerImpl.LoggerConfigKind.PACKAGE; 6 | 7 | import java.lang.invoke.MethodHandle; 8 | import java.net.URL; 9 | import java.util.Arrays; 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | import java.util.function.Consumer; 13 | 14 | import com.github.forax.beautifullogger.Logger.Level; 15 | 16 | /** 17 | * The configuration of a {@link Logger}. 18 | *

19 | * The configuration can be set on a {@link LoggerConfig#fromModule(String) module}, 20 | * a {@link LoggerConfig#fromPackage(Package) package} or 21 | * a {@link LoggerConfig#fromClass(Class) class}. 22 | * When a logger read a configuration property, it first tries to read the configuration property 23 | * of its configuration class, if the property is not defined, it tries to read the configuration property of the package 24 | * and if it is not defined, it tries to read the configuration property of the module, if it is not defined, 25 | * it uses the default values below. 26 | * 27 | *

 

28 | * 29 | * 30 | * 31 | * 32 | * 33 | * 34 | *
enabletrue
level{@link Level#INFO}
levelOverridefalse
logEventFactory{@link LogFacadeFactory#defaultFactory()}
default value of the configuration properties
35 | *

 

36 | * 37 | * An example of reading the configuration 38 | *
 39 |  * class Example {
 40 |  *   private static final Logger LOGGER = Logger.getLogger(Example.class);
 41 |  *   
 42 |  *   public static void main(String[] args) {
 43 |  *     LogerConfig config = LoggerConfig.fromClass(Example.class);
 44 |  *     System.out.println("logger enabled" + config.enable());
 45 |  *     ...
 46 |  * 
47 | * 48 | * Because changing a configuration property may cause the Java Virtual Machine to de-optimize assembly codes, 49 | * the change to the properties of a LoggerConfig has to be done in bulk using the method {@link LoggerConfig#update(Consumer)}. 50 | *

51 | * For example to change the level of a configuration 52 | *

 53 |  *    config.update(opt -> opt.level(Level.TRACE, false)); 
 54 |  * 
55 | * 56 | */ 57 | public interface LoggerConfig { 58 | /** 59 | * Facade that abstract any loggers. 60 | */ 61 | @FunctionalInterface 62 | interface LogFacade { 63 | /** 64 | * Returns a method handle that can emit a logging event for a configuration class, its method type must be 65 | * {@link java.lang.invoke.MethodType#methodType(Class, Class[]) MethodType#methodType(void.class, String.class, Level.class, Throwable.class)}. 66 | * This method is not called for each event but more or less each time 67 | * the runtime detects that the configuration has changed. 68 | * 69 | * @return a method handle that can emit a logging event. 70 | */ 71 | MethodHandle getLogMethodHandle(); 72 | 73 | /** 74 | * Override the level of the underlying logger. 75 | * This method is not called for each event but more or less each time 76 | * the runtime detects that the configuration has changed. 77 | * 78 | * @param level the new level of the underlying logger. 79 | * @throws UnsupportedOperationException if overriding the level is not 80 | * supported by the underlying logger 81 | */ 82 | default void overrideLevel(Level level) { 83 | throw new UnsupportedOperationException("SLF4J do not offer to change the log level dynamically"); 84 | } 85 | } 86 | 87 | /** 88 | * The interface that provides a method handle that can emit 89 | * a logging event for a configuration class. 90 | * 91 | * @see LoggerConfig#logFacadeFactory() 92 | * @see ConfigOption#logFacadeFactory(LogFacadeFactory) 93 | */ 94 | @FunctionalInterface 95 | interface LogFacadeFactory { 96 | /** 97 | * Returns a log facade configured for the configuration class. 98 | * 99 | * @param configClass the configuration class. 100 | * @return a newly created log facade. 101 | */ 102 | LogFacade logFacade(Class configClass); 103 | 104 | /** 105 | * A strategy represent a Logger factory that may or may not be available. 106 | * 107 | * @see LogFacadeFactory#fromStrategies(Strategy...) 108 | */ 109 | enum Strategy { 110 | /** 111 | * The SLF4J strategy. 112 | */ 113 | SLF4J(Optional.of(LogFacadeFactory.slf4jFactory()).filter(__ -> isAvailable("org.slf4j.LoggerFactory"))), 114 | 115 | /** 116 | * The LOG4J strategy. 117 | */ 118 | LOG4J(Optional.of(LogFacadeFactory.log4jFactory()).filter(__ -> isAvailable("org.apache.logging.log4j.LogManager"))), 119 | 120 | /** 121 | * The System.Logger strategy. 122 | */ 123 | SYSTEM_LOGGER(Optional.of(LogFacadeFactory.systemLoggerFactory()).filter(__ -> isAvailable("java.lang.System.Logger"))), 124 | 125 | /** 126 | * The java.util.logging strategy. 127 | */ 128 | JUL(Optional.of(LogFacadeFactory.julFactory()).filter(__ -> isAvailable("java.util.logging.Logger"))) 129 | ; 130 | 131 | final Optional factory; 132 | 133 | private Strategy(Optional factory) { 134 | this.factory = factory; 135 | } 136 | 137 | private static boolean isAvailable(String className) { 138 | try { 139 | Class.forName(className); 140 | return true; 141 | } catch(@SuppressWarnings("unused") ClassNotFoundException __) { 142 | return false; 143 | } 144 | } 145 | 146 | static final LogFacadeFactory DEFAULT_FACTORY = fromStrategies(SLF4J, LOG4J, SYSTEM_LOGGER, JUL); 147 | } 148 | 149 | /** 150 | * Return the first available LogEventFactory among {@link Strategy#SLF4J}, {@link Strategy#LOG4J}, 151 | * {@link Strategy#SYSTEM_LOGGER} and {@link Strategy#JUL}. 152 | * This call is equivalent to 153 | * {@link LogFacadeFactory#fromStrategies(Strategy...) fromStrategies(SLF4J, LOG4J, LOGBACK, SYSTEM_LOGGER, JUL)}. 154 | * @return the first available LogEventFactory. 155 | */ 156 | static LogFacadeFactory defaultFactory() { 157 | return Strategy.DEFAULT_FACTORY; 158 | } 159 | 160 | /** 161 | * Returns a LogEventFactory by checking checking each {@link Strategy} to find an available logger factory 162 | * or default to the {@link #systemLoggerFactory()}. 163 | * @param strategies the strategy to pick in order. 164 | * @return the first LogEventFactory available. 165 | * @throws IllegalStateException if no LogEventFactory is available. 166 | */ 167 | static LogFacadeFactory fromStrategies(Strategy... strategies) { 168 | return Arrays.stream(strategies).flatMap(s -> LoggerImpl.asStream(s.factory)).findFirst().orElseThrow(() -> new IllegalStateException("no available LogEventFactory")); 169 | } 170 | 171 | /** 172 | * Returns a LogEventFactory that uses SLF4J to log events. 173 | * @return a LogEventFactory that uses SLF4J to log events. 174 | */ 175 | static LogFacadeFactory slf4jFactory() { 176 | return configClass -> { 177 | org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(configClass); 178 | return () -> LoggerImpl.SLF4JFactoryImpl.SLF4J_LOGGER.bindTo(logger); 179 | }; 180 | } 181 | 182 | /** 183 | * Returns a LogEventFactory that uses Log4J to log events. 184 | * @return a LogEventFactory that uses Log4J to log events. 185 | */ 186 | static LogFacadeFactory log4jFactory() { 187 | return configClass -> new LoggerImpl.Log4JFactoryImpl(org.apache.logging.log4j.LogManager.getLogger(configClass)); 188 | } 189 | 190 | /** 191 | * Returns a LogEventFactory that uses the {@code java.lang.System.Logger system logger}. 192 | * @return a new LogEventFactory that delegate the logging to the {@code java.lang.System.Logger system logger}. 193 | */ 194 | static LogFacadeFactory systemLoggerFactory() { 195 | return configClass -> { 196 | Object logger = LoggerImpl.SystemLoggerFactoryImpl.getSystemLogger(configClass.getName()); 197 | return () -> LoggerImpl.SystemLoggerFactoryImpl.SYSTEM_LOGGER.bindTo(logger); 198 | }; 199 | } 200 | 201 | /** 202 | * Returns a LogEventFactory that uses java.util.logging to log events. 203 | * @return a LogEventFactory that uses java.util.logging to log events. 204 | */ 205 | static LogFacadeFactory julFactory() { 206 | return configClass -> new LoggerImpl.JULFactoryImpl(java.util.logging.Logger.getLogger(configClass.getName())); 207 | } 208 | } 209 | 210 | 211 | /** 212 | * The configuration properties that can be mutated 213 | * by the method {@link LoggerConfig#update(Consumer)} of a {@link LoggerConfig}. 214 | */ 215 | interface ConfigOption { 216 | /** 217 | * Update the configuration to enable/disable the loggers. 218 | * @param enable true to enable the logging. 219 | * @return this configuration option 220 | * 221 | * @see LoggerConfig#enable() 222 | */ 223 | ConfigOption enable(boolean enable); 224 | /** 225 | * Update the configuration level. 226 | *

227 | * Note that the creation of the underlying logger is lazy so 228 | * if the level of the underlying logger need to be overridden 229 | * this operation will be delayed until the underlying logger is created. 230 | * 231 | * @param level the accepted logging level. 232 | * @param override the logging level of the underlying logger. 233 | * @return this configuration option 234 | * @throws NullPointerException if the level is null 235 | * 236 | * @see LoggerConfig#level() 237 | * @see LoggerConfig#levelOverride() 238 | */ 239 | ConfigOption level(Level level, boolean override); 240 | /** 241 | * Update the configuration logFacadeFactory. 242 | * @param factory the new factory to use 243 | * @return this configuration option 244 | * @throws NullPointerException if the factory is null 245 | * 246 | * @see LoggerConfig#logFacadeFactory() 247 | */ 248 | ConfigOption logFacadeFactory(LogFacadeFactory factory); 249 | } 250 | 251 | /** 252 | * Returns if the logging is enable, disable or not set. 253 | * @return true if the logging is enable, disable or not set. 254 | * 255 | * @see ConfigOption#enable(boolean) 256 | */ 257 | Optional enable(); 258 | /** 259 | * Returns the logging level if set. 260 | * @return the logging level if set. 261 | * 262 | * @see ConfigOption#level(Level, boolean) 263 | */ 264 | Optional level(); 265 | /** 266 | * Returns if the logging level override the log level of the underlying logger. 267 | * @return true if the logging level override the log level of the underlying logger. 268 | * 269 | * @see ConfigOption#level(Level, boolean) 270 | */ 271 | Optional levelOverride(); 272 | /** 273 | * Returns the log event factory if set. 274 | * @return the log event factory if set. 275 | * 276 | * @see ConfigOption#logFacadeFactory(LogFacadeFactory) 277 | */ 278 | Optional logFacadeFactory(); 279 | 280 | /** 281 | * Update the configuration by updating the value and then commit the changes. 282 | * @param configUpdater a lambda that can update the configuration. 283 | * @return this logger configuration. 284 | */ 285 | LoggerConfig update(Consumer configUpdater); 286 | 287 | /** 288 | * Returns the configuration associated with the configuration class. 289 | * @param configClass the configuration class. 290 | * @return the configuration associated with the configuration class. 291 | * @throws NullPointerException if the configuration class is null. 292 | */ 293 | static LoggerConfig fromClass(Class configClass) { 294 | return fromClass(configClass.getName()); // implicit null check 295 | } 296 | /** 297 | * Returns the configuration associated with the configuration class. 298 | * @param className the configuration class name. 299 | * @return the configuration associated with the configuration class. 300 | * @throws NullPointerException if the configuration class name is null. 301 | */ 302 | static LoggerConfig fromClass(String className) { 303 | Objects.requireNonNull(className); 304 | return LoggerImpl.configFrom(CLASS, className); 305 | } 306 | /** 307 | * Returns the configuration associated with the configuration package. 308 | * @param packaze the configuration package. 309 | * @return the configuration associated with the configuration package. 310 | * @throws NullPointerException if the configuration package is null. 311 | */ 312 | static LoggerConfig fromPackage(Package packaze) { 313 | return fromPackage(packaze.getName()); // implicit null check 314 | } 315 | /** 316 | * Returns the configuration associated with the configuration package. 317 | * @param packageName the configuration package name. 318 | * @return the configuration associated with the configuration package. 319 | * @throws NullPointerException if the configuration package name is null. 320 | */ 321 | static LoggerConfig fromPackage(String packageName) { 322 | Objects.requireNonNull(packageName); 323 | return LoggerImpl.configFrom(PACKAGE, packageName); 324 | } 325 | /** 326 | * Returns the configuration associated with the configuration module. 327 | * @param module the configuration module. 328 | * @return the configuration associated with the configuration module. 329 | * @throws NullPointerException if the configuration module is null. 330 | */ 331 | /*static LoggerConfig fromModule(Module module) { // Removed because of Java 8 support 332 | return fromModule(module.getName()); // implicit null check 333 | }*/ 334 | /** 335 | * Returns the configuration associated with the configuration module. 336 | * @param moduleName the configuration module name. 337 | * @return the configuration associated with the configuration module. 338 | * @throws NullPointerException if the configuration module name is null. 339 | */ 340 | static LoggerConfig fromModule(String moduleName) { 341 | Objects.requireNonNull(moduleName); 342 | return LoggerImpl.configFrom(MODULE, moduleName); 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /src/main/java/com.github.forax.beautifullogger/LoggerImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger; 2 | 3 | import static com.github.forax.beautifullogger.LoggerImpl.LoggerConfigFeature.ENABLE_CONF; 4 | import static com.github.forax.beautifullogger.LoggerImpl.LoggerConfigFeature.LEVEL_CONF; 5 | import static com.github.forax.beautifullogger.LoggerImpl.LoggerConfigFeature.LEVEL_OVERRIDE_CONF; 6 | import static com.github.forax.beautifullogger.LoggerImpl.LoggerConfigFeature.LOGFACADEFACTORY_CONF; 7 | import static java.lang.invoke.MethodHandles.dropArguments; 8 | import static java.lang.invoke.MethodHandles.exactInvoker; 9 | import static java.lang.invoke.MethodHandles.filterArguments; 10 | import static java.lang.invoke.MethodHandles.foldArguments; 11 | import static java.lang.invoke.MethodHandles.guardWithTest; 12 | import static java.lang.invoke.MethodHandles.identity; 13 | import static java.lang.invoke.MethodHandles.insertArguments; 14 | import static java.lang.invoke.MethodHandles.lookup; 15 | import static java.lang.invoke.MethodHandles.permuteArguments; 16 | import static java.lang.invoke.MethodHandles.publicLookup; 17 | import static java.lang.invoke.MethodType.genericMethodType; 18 | import static java.lang.invoke.MethodType.methodType; 19 | import static java.util.Collections.nCopies; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.invoke.MethodHandles; 23 | import java.lang.invoke.MethodHandles.Lookup; 24 | import java.lang.invoke.MethodType; 25 | import java.lang.invoke.MutableCallSite; 26 | import java.lang.invoke.SwitchPoint; 27 | import java.lang.invoke.WrongMethodTypeException; 28 | import java.lang.reflect.Array; 29 | import java.lang.reflect.Field; 30 | import java.lang.reflect.InvocationTargetException; 31 | import java.lang.reflect.Method; 32 | import java.lang.reflect.UndeclaredThrowableException; 33 | import java.util.AbstractMap.SimpleImmutableEntry; 34 | import java.util.Arrays; 35 | import java.util.Base64; 36 | import java.util.LinkedHashSet; 37 | import java.util.List; 38 | import java.util.Map; 39 | import java.util.Map.Entry; 40 | import java.util.Objects; 41 | import java.util.Optional; 42 | import java.util.Set; 43 | import java.util.concurrent.ConcurrentHashMap; 44 | import java.util.function.BiFunction; 45 | import java.util.function.Consumer; 46 | import java.util.function.DoubleFunction; 47 | import java.util.function.Function; 48 | import java.util.function.IntFunction; 49 | import java.util.function.LongFunction; 50 | import java.util.function.Supplier; 51 | import java.util.stream.Stream; 52 | 53 | import com.github.forax.beautifullogger.Logger.Level; 54 | import com.github.forax.beautifullogger.LoggerConfig.ConfigOption; 55 | import com.github.forax.beautifullogger.LoggerConfig.LogFacade; 56 | import com.github.forax.beautifullogger.LoggerConfig.LogFacadeFactory; 57 | 58 | class LoggerImpl { 59 | private static class None { 60 | None() { /* singleton */ } 61 | @Override public String toString() { return "NONE"; } 62 | } 63 | static final Object NONE = new None(); 64 | 65 | // used internally by Logger, should not be public 66 | static final Consumer EMPTY_CONSUMER = __ -> { /* empty */ }; 67 | 68 | private LoggerImpl() { 69 | throw new AssertionError(); 70 | } 71 | 72 | static MethodHandle getLoggingMethodHandle(Class configClass, int maxParameter) { 73 | return new CS(configClass, maxParameter).dynamicInvoker(); 74 | } 75 | 76 | private static class CS extends MutableCallSite { 77 | private static final MethodType LOG_METHOD_TYPE = methodType(void.class, String.class, Level.class, Throwable.class); 78 | private static final MethodHandle FALLBACK, NOP; 79 | private static final MethodHandle[] CHECK_LEVELS; 80 | static { 81 | Lookup lookup = lookup(); 82 | try { 83 | FALLBACK = lookup.findVirtual(CS.class, "fallback", methodType(MethodHandle.class, Level.class, Throwable.class, Object.class, Object[].class)); 84 | NOP = lookup.findStatic(CS.class, "nop", methodType(void.class)); 85 | 86 | MethodHandle[] checkLevels = new MethodHandle[Level.LEVELS.length]; 87 | for(int i = 0; i < checkLevels.length; i++) { 88 | checkLevels[i] = lookup.findStatic(CS.class, "checkLevel".concat(Level.LEVELS[i].name()), methodType(boolean.class, Level.class)); 89 | } 90 | CHECK_LEVELS = checkLevels; 91 | } catch (NoSuchMethodException | IllegalAccessException e) { 92 | throw new AssertionError(e); 93 | } 94 | } 95 | 96 | private final int maxParameters; 97 | private final Class configClass; 98 | private final MethodHandle fallback; 99 | 100 | CS(Class configClass, int maxParameters) { 101 | super(methodType(void.class, Level.class, Throwable.class, Object.class).appendParameterTypes(nCopies(maxParameters, Object.class))); 102 | this.maxParameters = maxParameters; 103 | this.configClass = configClass; 104 | MethodHandle fallback = foldArguments(exactInvoker(type()), FALLBACK.bindTo(this).asCollector(Object[].class, maxParameters)); 105 | this.fallback = fallback; 106 | setTarget(fallback); 107 | } 108 | 109 | private static MethodHandle empty_void(MethodType methodType) { 110 | return MethodHandles.dropArguments(NOP, 0, methodType.parameterList()); 111 | 112 | // not available in Java 8 113 | //return MethodHandles.empty(methodType); 114 | } 115 | 116 | @SuppressWarnings("unused") 117 | private MethodHandle fallback(Level level, Throwable context, Object messageProvider, Object[] args) { 118 | Objects.requireNonNull(level, "level is null"); 119 | Objects.requireNonNull(messageProvider, "message provider is null"); 120 | 121 | // check configuration flag 'enable' 122 | LinkedHashSet switchPoints = new LinkedHashSet<>(); 123 | boolean enable = ENABLE_CONF.findValueAndCollectSwitchPoints(configClass, switchPoints).orElse(true); 124 | 125 | MethodHandle target; 126 | MethodHandle empty = empty_void(type()); 127 | if (enable) { 128 | // get configuration facade factory 129 | LogFacadeFactory logFacadeFactory = LOGFACADEFACTORY_CONF.findValueAndCollectSwitchPoints(configClass, switchPoints) 130 | .orElseGet(LogFacadeFactory::defaultFactory); 131 | LogFacade logFacade = logFacadeFactory.logFacade(configClass); 132 | MethodHandle logMH = logFacade.getLogMethodHandle(); 133 | if (!logMH.type().equals(LOG_METHOD_TYPE)) { 134 | throw new WrongMethodTypeException("the print method handle should be typed (String, Level, Throwable)V ".concat(logMH.toString())); 135 | } 136 | 137 | // adjust to the number of parameters (+ the message provider) 138 | MethodHandle print = dropArguments(logMH, 3, nCopies(1 + maxParameters, Object.class)); 139 | 140 | // create the message provider call site, we already have the arguments of the first call here, 141 | // so we can directly call the fallback to avoid an unnecessary round trip 142 | MessageProviderCS providerCallSite = new MessageProviderCS(type(), maxParameters, print); 143 | providerCallSite.fallback(messageProvider, args); 144 | target = providerCallSite.getTarget(); 145 | 146 | // check configuration level, override the underlying logger level if necessary 147 | Level configLevel = LEVEL_CONF.findValueAndCollectSwitchPoints(configClass, switchPoints).orElse(Level.INFO); 148 | if (LEVEL_OVERRIDE_CONF.findValueAndCollectSwitchPoints(configClass, switchPoints).orElse(false)) { 149 | logFacade.overrideLevel(configLevel); 150 | } 151 | target = guardWithTest(CHECK_LEVELS[configLevel.ordinal()], target, empty); 152 | 153 | } else { 154 | // if disable, do nothing ! 155 | target = empty; 156 | } 157 | 158 | // avoid recursion (i.e. non progression) if the switch points are invalidated 159 | // between the time the configuration is read and the time the method handle is installed 160 | MethodHandle result = target; 161 | 162 | // prepend switch points 163 | for(SwitchPoint switchPoint: switchPoints) { 164 | target = switchPoint.guardWithTest(target, fallback); 165 | } 166 | 167 | setTarget(target); 168 | return result; 169 | } 170 | 171 | @SuppressWarnings("unused") 172 | private static void nop() { 173 | // does nothing ! 174 | } 175 | 176 | // use one method checkLevel* by level to allow JITs to remove those checks 177 | 178 | @SuppressWarnings("unused") 179 | private static boolean checkLevelTRACE(Level level) { 180 | return true; 181 | } 182 | @SuppressWarnings("unused") 183 | private static boolean checkLevelDEBUG(Level level) { 184 | if (level == Level.TRACE) { 185 | return false; 186 | } 187 | return true; 188 | } 189 | @SuppressWarnings("unused") 190 | private static boolean checkLevelINFO(Level level) { 191 | if (level == Level.TRACE) { 192 | return false; 193 | } 194 | if (level == Level.DEBUG) { 195 | return false; 196 | } 197 | return true; 198 | } 199 | @SuppressWarnings("unused") 200 | private static boolean checkLevelWARNING(Level level) { 201 | if (level == Level.ERROR) { 202 | return true; 203 | } 204 | if (level == Level.WARNING) { 205 | return true; 206 | } 207 | return false; 208 | } 209 | @SuppressWarnings("unused") 210 | private static boolean checkLevelERROR(Level level) { 211 | if (level == Level.ERROR) { 212 | return true; 213 | } 214 | return false; 215 | } 216 | } 217 | 218 | private static class MessageProviderCS extends MutableCallSite { 219 | private static final MethodHandle FALLBACK, IS_INSTANCE; 220 | static { 221 | Lookup lookup = lookup(); 222 | try { 223 | FALLBACK = dropArguments( 224 | lookup.findVirtual(MessageProviderCS.class, "fallback", methodType(MethodHandle.class, Object.class, Object[].class)), 225 | 0, Level.class, Throwable.class); 226 | IS_INSTANCE = lookup.findVirtual(Class.class, "isInstance", methodType(boolean.class, Object.class)); 227 | } catch (NoSuchMethodException | IllegalAccessException e) { 228 | throw new AssertionError(e); 229 | } 230 | } 231 | 232 | private final int maxParameters; 233 | private final MethodHandle print; 234 | 235 | MessageProviderCS(MethodType type, int maxParameters, MethodHandle print) { 236 | super(type); 237 | this.maxParameters = maxParameters; 238 | this.print = print; 239 | setTarget(foldArguments( 240 | exactInvoker(type()), 241 | insertArguments(FALLBACK, 2, this).asCollector(Object[].class, maxParameters))); 242 | } 243 | 244 | MethodHandle fallback(Object messageProvider, Object[] args) { 245 | Entry, MethodHandle> pair = findFunctionalInterfaceMH(messageProvider); 246 | Class providerClass = pair.getKey(); 247 | MethodHandle provider = pair.getValue(); 248 | 249 | // check if the provider parameter count and the actual number of arguments match 250 | int actualSize = findActualSize(args); 251 | int providerArgumentCount = provider.type().parameterCount() - 1; 252 | if (actualSize != providerArgumentCount) { 253 | throw new IllegalArgumentException(new StringBuilder() 254 | .append("call mismatch, actual argument count ") 255 | .append(actualSize) 256 | .append(" for method type ") 257 | .append(provider.type().dropParameterTypes(0, 1)) 258 | .toString()); 259 | } 260 | 261 | // align signature of the provider with the log signature 262 | if (providerArgumentCount != maxParameters) { 263 | provider = dropArguments(provider, provider.type().parameterCount(), nCopies(maxParameters - providerArgumentCount, Object.class)); 264 | } 265 | provider = provider.asType(genericMethodType(provider.type().parameterCount()).changeReturnType(String.class)); 266 | provider = dropArguments(provider, 0, Level.class, Throwable.class); 267 | 268 | // fold ! 269 | MethodHandle target = foldArguments(print, provider); 270 | 271 | // create the inlining cache 272 | MethodHandle guard = guardWithTest( 273 | dropArguments(IS_INSTANCE.bindTo(providerClass), 0, Level.class, Throwable.class), 274 | target, 275 | new MessageProviderCS(type(), maxParameters, print).dynamicInvoker()); 276 | setTarget(guard); 277 | 278 | return target; 279 | } 280 | } 281 | 282 | 283 | private static final List, MethodHandle>> MESSAGE_PROVIDERS = Arrays.asList( 284 | findVirtualMethod(Supplier.class, "get", methodType(String.class)), 285 | findVirtualMethod(IntFunction.class, "apply", methodType(String.class, int.class)), 286 | findVirtualMethod(LongFunction.class, "apply", methodType(String.class, long.class)), 287 | findVirtualMethod(DoubleFunction.class, "apply", methodType(String.class, double.class)), 288 | findVirtualMethod(Function.class, "apply", methodType(String.class, Object.class)), 289 | findVirtualMethod(BiFunction.class, "apply", methodType(String.class, Object.class, Object.class)), 290 | entry(String.class, identity(Object.class).asType(methodType(String.class, Object.class)))); 291 | 292 | private static Map.Entry, MethodHandle> entry(Class fun, MethodHandle mh) { 293 | return new SimpleImmutableEntry<>(fun, mh); 294 | } 295 | 296 | private static Entry, MethodHandle> findVirtualMethod(Class fun, String name, MethodType type) { 297 | MethodHandle mh; 298 | try { 299 | mh = publicLookup().findVirtual(fun, name, type.erase()); 300 | } catch (NoSuchMethodException | IllegalAccessException e) { 301 | throw new AssertionError(e); 302 | } 303 | return entry(fun, mh.asType(type.insertParameterTypes(0, fun))); 304 | } 305 | 306 | static Entry, MethodHandle> findFunctionalInterfaceMH(Object messageProvider) { 307 | return MESSAGE_PROVIDERS.stream() 308 | .filter(entry -> entry.getKey().isInstance(messageProvider)) 309 | .findFirst() 310 | .orElseThrow(() -> new IllegalArgumentException("unknown message provider type ".concat(messageProvider.getClass().getName()))); 311 | } 312 | 313 | static int findActualSize(Object[] args) { 314 | for(int i = args.length; --i >= 0;) { 315 | if (args[i] != NONE) { 316 | return i + 1; 317 | } 318 | } 319 | return 0; 320 | } 321 | 322 | static UndeclaredThrowableException rethrow(Throwable e) { 323 | if (e instanceof RuntimeException) { 324 | throw (RuntimeException)e; 325 | } 326 | if (e instanceof Error) { 327 | throw (Error)e; 328 | } 329 | return new UndeclaredThrowableException(e); 330 | } 331 | 332 | static String packageName(Class type) { 333 | if (type.isPrimitive()) { 334 | return "java.lang"; 335 | } 336 | if (type.isArray()) { 337 | return packageName(type.getComponentType()); 338 | } 339 | String name = type.getName(); 340 | int index = name.lastIndexOf('.'); 341 | return index == -1? "": name.substring(0, index); 342 | } 343 | 344 | static class ModuleNameHolder { 345 | static final MethodHandle GET_MODULE_NAME; 346 | static { 347 | MethodHandle getModuleName; 348 | try { 349 | Class moduleClass = Class.forName("java.lang.Module"); 350 | 351 | Lookup lookup = MethodHandles.lookup(); 352 | MethodHandle getModule, getName; 353 | try { 354 | getModule = lookup.findVirtual(Class.class, "getModule", methodType(moduleClass)); 355 | getName = lookup.findVirtual(moduleClass, "getName", methodType(String.class)); 356 | } catch (NoSuchMethodException | IllegalAccessException e) { 357 | throw new AssertionError(e); 358 | } 359 | getModuleName = MethodHandles.filterReturnValue(getModule, getName); 360 | 361 | } catch (ClassNotFoundException e) { 362 | getModuleName = null; 363 | } 364 | GET_MODULE_NAME = getModuleName; 365 | } 366 | } 367 | 368 | enum LoggerConfigKind { 369 | CLASS(Class::getName), 370 | PACKAGE(type -> packageName(type)), 371 | MODULE(type -> { 372 | try { 373 | return ModuleNameHolder.GET_MODULE_NAME == null? 374 | null: 375 | (String) ModuleNameHolder.GET_MODULE_NAME.invokeExact(type); 376 | } catch (Throwable e) { 377 | throw new AssertionError(e); 378 | } 379 | }); 380 | 381 | private final Function, String> nameExtractor; 382 | 383 | private LoggerConfigKind(Function, String> nameExtractor) { 384 | this.nameExtractor = nameExtractor; 385 | } 386 | 387 | String key(String name) { 388 | return new StringBuilder().append(name()).append(';').append(name).toString(); 389 | } 390 | 391 | // may return null ! 392 | String extractNameFromClass(Class type) { 393 | return nameExtractor.apply(type); 394 | } 395 | 396 | static final LoggerConfigKind[] VALUES = values(); 397 | } 398 | 399 | static class LoggerConfigFeature { 400 | static final LoggerConfigFeature ENABLE_CONF = new LoggerConfigFeature<>(LoggerConfig::enable); 401 | static final LoggerConfigFeature LEVEL_CONF = new LoggerConfigFeature<>(LoggerConfig::level); 402 | static final LoggerConfigFeature LEVEL_OVERRIDE_CONF = new LoggerConfigFeature<>(LoggerConfig::levelOverride); 403 | static final LoggerConfigFeature LOGFACADEFACTORY_CONF = new LoggerConfigFeature<>(LoggerConfig::logFacadeFactory); 404 | 405 | private final Function> extractor; 406 | 407 | private LoggerConfigFeature(Function> extractor) { 408 | this.extractor = extractor; 409 | } 410 | 411 | Optional findValueAndCollectSwitchPoints(Class type, Set switchPoints) { 412 | for(LoggerConfigKind kind: LoggerConfigKind.VALUES) { 413 | String name = kind.extractNameFromClass(type); 414 | if (name == null) { // unnamed module or no module (Java 8) 415 | continue; 416 | } 417 | LoggerConfigImpl loggerConfig = configFrom(kind, name); 418 | switchPoints.add(loggerConfig.switchPoint()); 419 | Optional value = extractor.apply(loggerConfig); 420 | if (value.isPresent()) { 421 | return value; 422 | } 423 | } 424 | return Optional.empty(); 425 | } 426 | } 427 | 428 | static class LoggerConfigImpl implements LoggerConfig { 429 | class ConfigOptionImpl implements ConfigOption { 430 | @Override 431 | public ConfigOption enable(boolean enable) { 432 | LoggerConfigImpl.this.enable = enable; 433 | return this; 434 | } 435 | @Override 436 | public ConfigOption level(Level level, boolean override) { 437 | LoggerConfigImpl.this.level = Objects.requireNonNull(level); 438 | LoggerConfigImpl.this.levelOverride = override; 439 | return this; 440 | } 441 | @Override 442 | public ConfigOption logFacadeFactory(LogFacadeFactory printFactory) { 443 | LoggerConfigImpl.this.logEventFactory = Objects.requireNonNull(printFactory); 444 | return this; 445 | } 446 | } 447 | 448 | private final Object lock = new Object(); 449 | private SwitchPoint switchPoint; 450 | 451 | volatile Boolean enable; // nullable 452 | volatile Level level; // nullable 453 | volatile Boolean levelOverride; // nullable 454 | volatile LogFacadeFactory logEventFactory; // nullable 455 | 456 | LoggerConfigImpl() { 457 | synchronized(lock) { 458 | this.switchPoint = new SwitchPoint(); 459 | } 460 | } 461 | 462 | @Override 463 | public Optional enable() { 464 | return Optional.ofNullable(enable); 465 | } 466 | @Override 467 | public Optional level() { 468 | return Optional.ofNullable(level); 469 | } 470 | @Override 471 | public Optional levelOverride() { 472 | return Optional.ofNullable(levelOverride); 473 | } 474 | @Override 475 | public Optional logFacadeFactory() { 476 | return Optional.ofNullable(logEventFactory); 477 | } 478 | 479 | SwitchPoint switchPoint() { 480 | synchronized (lock) { 481 | return switchPoint; 482 | } 483 | } 484 | 485 | @Override 486 | public LoggerConfig update(Consumer configUpdater) { 487 | synchronized(lock) { 488 | configUpdater.accept(new ConfigOptionImpl()); 489 | SwitchPoint.invalidateAll(new SwitchPoint[] { switchPoint }); 490 | switchPoint = new SwitchPoint(); 491 | } 492 | return this; 493 | } 494 | } 495 | 496 | private static final ConcurrentHashMap CONFIG = 497 | new ConcurrentHashMap<>(); 498 | 499 | static LoggerConfigImpl configFrom(LoggerConfigKind kind, String name) { 500 | return CONFIG.computeIfAbsent(kind.key(name), __ -> new LoggerConfigImpl()); 501 | } 502 | 503 | static final Object UNSAFE; 504 | static { 505 | Object unsafe; 506 | try { 507 | // jdk.unsupported module may be not available 508 | Class unsafeClass = Class.forName("sun.misc.Unsafe"); 509 | 510 | try { 511 | Field field = unsafeClass.getDeclaredField("theUnsafe"); 512 | field.setAccessible(true); 513 | unsafe = field.get(null); 514 | } catch (NoSuchFieldException | IllegalAccessException e) { 515 | throw new AssertionError(e); 516 | } 517 | } catch (ClassNotFoundException e) { 518 | unsafe = null; 519 | } 520 | UNSAFE = unsafe; 521 | } 522 | 523 | private static byte[] loggerFactoryBytecode() { 524 | String data = "yv66vgAAADQAWwEAJ2NvbS9naXRodWIvZm9yYXgvYmVhdXRpZnVsbG9nZ2VyL0xvZ2dlcgcAAQEALGNvbS9naXRodWIvZm9yYXgvYmVhdXRpZnVsbG9nZ2VyL0xvZ2dlciRTdHViBwADAQAQamF2YS9sYW5nL09iamVjdAcABQEAAm1oAQAfTGphdmEvbGFuZy9pbnZva2UvTWV0aG9kSGFuZGxlOwEABjxpbml0PgEAIihMamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGU7KVYBAAMoKVYMAAkACwoABgAMDAAHAAgJAAQADgEABmNyZWF0ZQEASihMamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGU7KUxjb20vZ2l0aHViL2ZvcmF4L2JlYXV0aWZ1bGxvZ2dlci9Mb2dnZXI7DAAJAAoKAAQAEgEABWVycm9yAQAqKExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvVGhyb3dhYmxlOylWAQAkTGphdmEvbGFuZy9pbnZva2UvTGFtYmRhRm9ybSRIaWRkZW47AQAjTGpkay9pbnRlcm5hbC92bS9hbm5vdGF0aW9uL0hpZGRlbjsBAChMamRrL2ludGVybmFsL3ZtL2Fubm90YXRpb24vRm9yY2VJbmxpbmU7AQAtY29tL2dpdGh1Yi9mb3JheC9iZWF1dGlmdWxsb2dnZXIvTG9nZ2VyJExldmVsBwAZAQAFRVJST1IBAC9MY29tL2dpdGh1Yi9mb3JheC9iZWF1dGlmdWxsb2dnZXIvTG9nZ2VyJExldmVsOwwAGwAcCQAaAB0BACtjb20vZ2l0aHViL2ZvcmF4L2JlYXV0aWZ1bGxvZ2dlci9Mb2dnZXJJbXBsBwAfAQAETk9ORQEAEkxqYXZhL2xhbmcvT2JqZWN0OwwAIQAiCQAgACMBAB1qYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZQcAJQEAC2ludm9rZUV4YWN0AQChKExjb20vZ2l0aHViL2ZvcmF4L2JlYXV0aWZ1bGxvZ2dlci9Mb2dnZXIkTGV2ZWw7TGphdmEvbGFuZy9UaHJvd2FibGU7TGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KVYMACcAKAoAJgApAQAgKExqYXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxpZXI7KVYBADQoTGphdmEvdXRpbC9mdW5jdGlvbi9TdXBwbGllcjxMamF2YS9sYW5nL1N0cmluZzs+OylWAQAyKExqYXZhL3V0aWwvZnVuY3Rpb24vRnVuY3Rpb247TGphdmEvbGFuZy9PYmplY3Q7KVYBAFE8VDpMamF2YS9sYW5nL09iamVjdDs+KExqYXZhL3V0aWwvZnVuY3Rpb24vRnVuY3Rpb248LVRUO0xqYXZhL2xhbmcvU3RyaW5nOz47VFQ7KVYBACQoTGphdmEvdXRpbC9mdW5jdGlvbi9JbnRGdW5jdGlvbjtJKVYBADgoTGphdmEvdXRpbC9mdW5jdGlvbi9JbnRGdW5jdGlvbjxMamF2YS9sYW5nL1N0cmluZzs+O0kpVgEAEWphdmEvbGFuZy9JbnRlZ2VyBwAxAQAHdmFsdWVPZgEAFihJKUxqYXZhL2xhbmcvSW50ZWdlcjsMADMANAoAMgA1AQAlKExqYXZhL3V0aWwvZnVuY3Rpb24vTG9uZ0Z1bmN0aW9uO0opVgEAOShMamF2YS91dGlsL2Z1bmN0aW9uL0xvbmdGdW5jdGlvbjxMamF2YS9sYW5nL1N0cmluZzs+O0opVgEADmphdmEvbGFuZy9Mb25nBwA5AQATKEopTGphdmEvbGFuZy9Mb25nOwwAMwA7CgA6ADwBACcoTGphdmEvdXRpbC9mdW5jdGlvbi9Eb3VibGVGdW5jdGlvbjtEKVYBADsoTGphdmEvdXRpbC9mdW5jdGlvbi9Eb3VibGVGdW5jdGlvbjxMamF2YS9sYW5nL1N0cmluZzs+O0QpVgEAEGphdmEvbGFuZy9Eb3VibGUHAEABABUoRClMamF2YS9sYW5nL0RvdWJsZTsMADMAQgoAQQBDAQBGKExqYXZhL3V0aWwvZnVuY3Rpb24vQmlGdW5jdGlvbjtMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL09iamVjdDspVgEAbjxUOkxqYXZhL2xhbmcvT2JqZWN0O1U6TGphdmEvbGFuZy9PYmplY3Q7PihMamF2YS91dGlsL2Z1bmN0aW9uL0JpRnVuY3Rpb248LVRUOy1UVTtMamF2YS9sYW5nL1N0cmluZzs+O1RUO1RVOylWAQAHd2FybmluZwEAB1dBUk5JTkcMAEgAHAkAGgBJAQAEaW5mbwEABElORk8MAEwAHAkAGgBNAQAFZGVidWcBAAVERUJVRwwAUAAcCQAaAFEBAAV0cmFjZQEABVRSQUNFDABUABwJABoAVQEAA2xvZwEABENvZGUBABlSdW50aW1lVmlzaWJsZUFubm90YXRpb25zAQAJU2lnbmF0dXJlACAABAAGAAEAAgABABIABwAIAAAAJgACAAkACgABAFgAAAAWAAIAAgAAAAoqtwANKiu1AA+xAAAAAAAJABAAEQABAFgAAAAVAAMAAQAAAAm7AARZKrcAE7AAAAAAAAEAFAAVAAIAWAAAACUACAADAAAAGSq0AA+yAB4sK7IAJLIAJLIAJLIAJLYAKrEAAAAAAFkAAAAOAAMAFgAAABcAAAAYAAAAAQAUACsAAwBYAAAAJQAIAAIAAAAZKrQAD7IAHgErsgAksgAksgAksgAktgAqsQAAAAAAWgAAAAIALABZAAAADgADABYAAAAXAAAAGAAAAAEAFAAtAAMAWAAAACMACAADAAAAFyq0AA+yAB4BKyyyACSyACSyACS2ACqxAAAAAABaAAAAAgAuAFkAAAAOAAMAFgAAABcAAAAYAAAAAQAUAC8AAwBYAAAAJgAIAAMAAAAaKrQAD7IAHgErHLgANrIAJLIAJLIAJLYAKrEAAAAAAFoAAAACADAAWQAAAA4AAwAWAAAAFwAAABgAAAABABQANwADAFgAAAAmAAgABAAAABoqtAAPsgAeASsguAA9sgAksgAksgAktgAqsQAAAAAAWgAAAAIAOABZAAAADgADABYAAAAXAAAAGAAAAAEAFAA+AAMAWAAAACYACAAEAAAAGiq0AA+yAB4BKyi4AESyACSyACSyACS2ACqxAAAAAABaAAAAAgA/AFkAAAAOAAMAFgAAABcAAAAYAAAAAQAUAEUAAwBYAAAAIQAIAAQAAAAVKrQAD7IAHgErLC2yACSyACS2ACqxAAAAAABaAAAAAgBGAFkAAAAOAAMAFgAAABcAAAAYAAAAAQBHABUAAgBYAAAAJQAIAAMAAAAZKrQAD7IASiwrsgAksgAksgAksgAktgAqsQAAAAAAWQAAAA4AAwAWAAAAFwAAABgAAAABAEcAKwADAFgAAAAlAAgAAgAAABkqtAAPsgBKASuyACSyACSyACSyACS2ACqxAAAAAABaAAAAAgAsAFkAAAAOAAMAFgAAABcAAAAYAAAAAQBHAC0AAwBYAAAAIwAIAAMAAAAXKrQAD7IASgErLLIAJLIAJLIAJLYAKrEAAAAAAFoAAAACAC4AWQAAAA4AAwAWAAAAFwAAABgAAAABAEcALwADAFgAAAAmAAgAAwAAABoqtAAPsgBKASscuAA2sgAksgAksgAktgAqsQAAAAAAWgAAAAIAMABZAAAADgADABYAAAAXAAAAGAAAAAEARwA3AAMAWAAAACYACAAEAAAAGiq0AA+yAEoBKyC4AD2yACSyACSyACS2ACqxAAAAAABaAAAAAgA4AFkAAAAOAAMAFgAAABcAAAAYAAAAAQBHAD4AAwBYAAAAJgAIAAQAAAAaKrQAD7IASgErKLgARLIAJLIAJLIAJLYAKrEAAAAAAFoAAAACAD8AWQAAAA4AAwAWAAAAFwAAABgAAAABAEcARQADAFgAAAAhAAgABAAAABUqtAAPsgBKASssLbIAJLIAJLYAKrEAAAAAAFoAAAACAEYAWQAAAA4AAwAWAAAAFwAAABgAAAABAEsAFQACAFgAAAAlAAgAAwAAABkqtAAPsgBOLCuyACSyACSyACSyACS2ACqxAAAAAABZAAAADgADABYAAAAXAAAAGAAAAAEASwArAAMAWAAAACUACAACAAAAGSq0AA+yAE4BK7IAJLIAJLIAJLIAJLYAKrEAAAAAAFoAAAACACwAWQAAAA4AAwAWAAAAFwAAABgAAAABAEsALQADAFgAAAAjAAgAAwAAABcqtAAPsgBOASsssgAksgAksgAktgAqsQAAAAAAWgAAAAIALgBZAAAADgADABYAAAAXAAAAGAAAAAEASwAvAAMAWAAAACYACAADAAAAGiq0AA+yAE4BKxy4ADayACSyACSyACS2ACqxAAAAAABaAAAAAgAwAFkAAAAOAAMAFgAAABcAAAAYAAAAAQBLADcAAwBYAAAAJgAIAAQAAAAaKrQAD7IATgErILgAPbIAJLIAJLIAJLYAKrEAAAAAAFoAAAACADgAWQAAAA4AAwAWAAAAFwAAABgAAAABAEsAPgADAFgAAAAmAAgABAAAABoqtAAPsgBOASsouABEsgAksgAksgAktgAqsQAAAAAAWgAAAAIAPwBZAAAADgADABYAAAAXAAAAGAAAAAEASwBFAAMAWAAAACEACAAEAAAAFSq0AA+yAE4BKywtsgAksgAktgAqsQAAAAAAWgAAAAIARgBZAAAADgADABYAAAAXAAAAGAAAAAEATwAVAAIAWAAAACUACAADAAAAGSq0AA+yAFIsK7IAJLIAJLIAJLIAJLYAKrEAAAAAAFkAAAAOAAMAFgAAABcAAAAYAAAAAQBPACsAAwBYAAAAJQAIAAIAAAAZKrQAD7IAUgErsgAksgAksgAksgAktgAqsQAAAAAAWgAAAAIALABZAAAADgADABYAAAAXAAAAGAAAAAEATwAtAAMAWAAAACMACAADAAAAFyq0AA+yAFIBKyyyACSyACSyACS2ACqxAAAAAABaAAAAAgAuAFkAAAAOAAMAFgAAABcAAAAYAAAAAQBPAC8AAwBYAAAAJgAIAAMAAAAaKrQAD7IAUgErHLgANrIAJLIAJLIAJLYAKrEAAAAAAFoAAAACADAAWQAAAA4AAwAWAAAAFwAAABgAAAABAE8ANwADAFgAAAAmAAgABAAAABoqtAAPsgBSASsguAA9sgAksgAksgAktgAqsQAAAAAAWgAAAAIAOABZAAAADgADABYAAAAXAAAAGAAAAAEATwA+AAMAWAAAACYACAAEAAAAGiq0AA+yAFIBKyi4AESyACSyACSyACS2ACqxAAAAAABaAAAAAgA/AFkAAAAOAAMAFgAAABcAAAAYAAAAAQBPAEUAAwBYAAAAIQAIAAQAAAAVKrQAD7IAUgErLC2yACSyACS2ACqxAAAAAABaAAAAAgBGAFkAAAAOAAMAFgAAABcAAAAYAAAAAQBTABUAAgBYAAAAJQAIAAMAAAAZKrQAD7IAViwrsgAksgAksgAksgAktgAqsQAAAAAAWQAAAA4AAwAWAAAAFwAAABgAAAABAFMAKwADAFgAAAAlAAgAAgAAABkqtAAPsgBWASuyACSyACSyACSyACS2ACqxAAAAAABaAAAAAgAsAFkAAAAOAAMAFgAAABcAAAAYAAAAAQBTAC0AAwBYAAAAIwAIAAMAAAAXKrQAD7IAVgErLLIAJLIAJLIAJLYAKrEAAAAAAFoAAAACAC4AWQAAAA4AAwAWAAAAFwAAABgAAAABAFMALwADAFgAAAAmAAgAAwAAABoqtAAPsgBWASscuAA2sgAksgAksgAktgAqsQAAAAAAWgAAAAIAMABZAAAADgADABYAAAAXAAAAGAAAAAEAUwA3AAMAWAAAACYACAAEAAAAGiq0AA+yAFYBKyC4AD2yACSyACSyACS2ACqxAAAAAABaAAAAAgA4AFkAAAAOAAMAFgAAABcAAAAYAAAAAQBTAD4AAwBYAAAAJgAIAAQAAAAaKrQAD7IAVgErKLgARLIAJLIAJLIAJLYAKrEAAAAAAFoAAAACAD8AWQAAAA4AAwAWAAAAFwAAABgAAAABAFMARQADAFgAAAAhAAgABAAAABUqtAAPsgBWASssLbIAJLIAJLYAKrEAAAAAAFoAAAACAEYAWQAAAA4AAwAWAAAAFwAAABgAAAABAFcAKAACAFgAAAAfAAgACAAAABMqtAAPKywtGQQZBRkGGQe2ACqxAAAAAABZAAAADgADABYAAAAXAAAAGAAAAAA="; 525 | return Base64.getDecoder().decode(data); 526 | } 527 | 528 | private static MethodHandle defineAnonymousClass(Class unsafeClass) { 529 | Lookup lookup = MethodHandles.lookup(); 530 | try { 531 | return lookup.findVirtual(unsafeClass, "defineAnonymousClass", methodType(Class.class, Class.class, byte[].class, Object[].class)); 532 | } catch (NoSuchMethodException e) { 533 | return null; // not found 534 | }catch (IllegalAccessException e) { 535 | throw new AssertionError(e); 536 | } 537 | } 538 | 539 | private static MethodHandle defineLoggerFactoryUsingUnsafe(MethodHandle defineAnonymousClass) { 540 | Class stubClass; 541 | try { 542 | stubClass = (Class) defineAnonymousClass.invoke(UNSAFE, Logger.class, loggerFactoryBytecode(), null); 543 | } catch (Throwable e) { 544 | throw new AssertionError(e); 545 | } 546 | 547 | Lookup lookup = MethodHandles.lookup(); 548 | try { 549 | return lookup.findStatic(stubClass, "create", methodType(Logger.class, MethodHandle.class)); 550 | } catch (NoSuchMethodException | IllegalAccessException e) { 551 | throw new IllegalStateException(e); 552 | } 553 | } 554 | 555 | private static MethodHandle defineLoggerFactoryUsingHiddenClass() { 556 | Class classOption; 557 | try { 558 | classOption = Class.forName("java.lang.invoke.MethodHandles$Lookup$ClassOption"); 559 | } catch (ClassNotFoundException e) { 560 | throw new AssertionError(e); 561 | } 562 | 563 | Class classOptionArray = Array.newInstance(classOption, 0).getClass(); 564 | Object nestmate; 565 | Object strong; 566 | try { 567 | nestmate = classOption.getField("NESTMATE").get(null); 568 | strong = classOption.getField("STRONG").get(null); 569 | } catch (NoSuchFieldException | IllegalAccessException e) { 570 | throw new AssertionError(e); 571 | } 572 | 573 | Lookup lookup = MethodHandles.lookup(); 574 | MethodHandle defineHiddenClass; 575 | MethodType methodType = methodType(Lookup.class, byte[].class, boolean.class, classOptionArray); 576 | try { 577 | defineHiddenClass = lookup.findVirtual(Lookup.class, "defineHiddenClass", methodType); 578 | } catch (NoSuchMethodException | IllegalAccessException e) { 579 | throw new AssertionError(e); 580 | } 581 | 582 | Lookup loggerClassLookup; 583 | try { 584 | loggerClassLookup = (Lookup) defineHiddenClass.invoke(lookup, loggerFactoryBytecode(), true, nestmate, strong); 585 | } catch (Throwable e) { 586 | throw new AssertionError(e); 587 | } 588 | try { 589 | return lookup.findStatic(loggerClassLookup.lookupClass(), "create", methodType(Logger.class, MethodHandle.class)); 590 | } catch (NoSuchMethodException | IllegalAccessException | IllegalStateException e) { 591 | throw new AssertionError(e); 592 | } 593 | } 594 | 595 | private static final MethodHandle LOGGER_FACTORY; 596 | static { 597 | MethodHandle loggerFactory; 598 | MethodHandle defineAnonymousClass; 599 | if (UNSAFE != null && (defineAnonymousClass = defineAnonymousClass(UNSAFE.getClass())) != null) { 600 | loggerFactory = defineLoggerFactoryUsingUnsafe(defineAnonymousClass); 601 | } else { 602 | loggerFactory = defineLoggerFactoryUsingHiddenClass(); 603 | } 604 | LOGGER_FACTORY = loggerFactory; 605 | } 606 | 607 | static MethodHandle findGetCallerClassUsingStackWalker() { 608 | Class stackWalkerClass; 609 | Class stackWalkerOptionClass; 610 | try { 611 | stackWalkerClass = Class.forName("java.lang.StackWalker"); 612 | stackWalkerOptionClass = Class.forName("java.lang.StackWalker$Option"); 613 | } catch (ClassNotFoundException e) { 614 | return null; 615 | } 616 | Object retainClassReference; 617 | try { 618 | retainClassReference = stackWalkerOptionClass.getField("RETAIN_CLASS_REFERENCE").get(null); 619 | } catch (NoSuchFieldException | IllegalAccessException e) { 620 | throw new AssertionError(e); 621 | } 622 | Lookup lookup = MethodHandles.lookup(); 623 | MethodHandle getInstance; 624 | try { 625 | getInstance = lookup.findStatic(stackWalkerClass, "getInstance", methodType(stackWalkerClass, stackWalkerOptionClass)); 626 | } catch (NoSuchMethodException | IllegalAccessException e) { 627 | throw new AssertionError(e); 628 | } 629 | Object stackWalker; 630 | try { 631 | stackWalker = getInstance.invoke(retainClassReference); 632 | } catch (Throwable e) { 633 | throw new AssertionError(e); 634 | } 635 | try { 636 | return lookup.findVirtual(stackWalkerClass, "getCallerClass", methodType(Class.class)) 637 | .bindTo(stackWalker); 638 | } catch (NoSuchMethodException | IllegalAccessException e) { 639 | throw new AssertionError(e); 640 | } 641 | } 642 | 643 | static Supplier findGetCallerClassUsingAnonymousClass() { 644 | String s = "yv66vgAAADQAIAcAAgEAM2NvbS9naXRodWIvZm9yYXgvYmVhdXRpZnVsbG9nZ2VyL1JlZmxlY3Rpb25TdXBwbGllcgcABAEAEGphdmEvbGFuZy9PYmplY3QHAAYBABtqYXZhL3V0aWwvZnVuY3Rpb24vU3VwcGxpZXIBAAY8aW5pdD4BAAMoKVYBAARDb2RlCgADAAsMAAcACAEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBADVMY29tL2dpdGh1Yi9mb3JheC9iZWF1dGlmdWxsb2dnZXIvUmVmbGVjdGlvblN1cHBsaWVyOwEAA2dldAEAEygpTGphdmEvbGFuZy9DbGFzczsBAAlTaWduYXR1cmUBABYoKUxqYXZhL2xhbmcvQ2xhc3M8Kj47CgAVABcHABYBABZzdW4vcmVmbGVjdC9SZWZsZWN0aW9uDAAYABkBAA5nZXRDYWxsZXJDbGFzcwEAFChJKUxqYXZhL2xhbmcvQ2xhc3M7AQAUKClMamF2YS9sYW5nL09iamVjdDsKAAEAHAwAEAARAQAKU291cmNlRmlsZQEAF1JlZmxlY3Rpb25TdXBwbGllci5qYXZhAQBFTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvdXRpbC9mdW5jdGlvbi9TdXBwbGllcjxMamF2YS9sYW5nL0NsYXNzPCo+Oz47ACEAAQADAAEABQAAAAMAAQAHAAgAAQAJAAAALwABAAEAAAAFKrcACrEAAAACAAwAAAAGAAEAAAAHAA0AAAAMAAEAAAAFAA4ADwAAAAEAEAARAAIAEgAAAAIAEwAJAAAALwABAAEAAAAFCLgAFLAAAAACAAwAAAAGAAEAAAAKAA0AAAAMAAEAAAAFAA4ADwAAEEEAEAAaAAEACQAAACUAAQABAAAABSq2ABuwAAAAAgAMAAAABgABAAAAAQANAAAAAgAAAAIAEgAAAAIAHwAdAAAAAgAe"; 645 | byte[] bytecode = Base64.getDecoder().decode(s); 646 | Supplier supplier; 647 | 648 | MethodHandle defineAnonymousClass = defineAnonymousClass(UNSAFE.getClass()); 649 | Class reflectionSupplier; 650 | try { 651 | reflectionSupplier = (Class) defineAnonymousClass.invoke(UNSAFE, LoggerImpl.class, bytecode, null); 652 | } catch (Throwable e) { 653 | throw new AssertionError(e); 654 | } 655 | try { 656 | return (Supplier) reflectionSupplier.getConstructor().newInstance(); 657 | } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { 658 | throw new AssertionError(e); 659 | } 660 | } 661 | 662 | static Class getCallerClassNoStackWalker() { 663 | return (Class) GET_CALLER_CLASS_SUPPLIER.get(); 664 | } 665 | 666 | static final MethodHandle GET_CALLER_CLASS_MH; 667 | private static final Supplier GET_CALLER_CLASS_SUPPLIER; 668 | static { 669 | Supplier getCallerClassSupplier = null; 670 | MethodHandle getCallerClassMH = findGetCallerClassUsingStackWalker(); 671 | if (getCallerClassMH == null) { 672 | getCallerClassSupplier = findGetCallerClassUsingAnonymousClass(); 673 | } 674 | GET_CALLER_CLASS_MH = getCallerClassMH; 675 | GET_CALLER_CLASS_SUPPLIER = getCallerClassSupplier; 676 | } 677 | 678 | static Logger createLogger(MethodHandle mh) { 679 | try { 680 | return (Logger) LOGGER_FACTORY.invokeExact(mh); 681 | } catch (Throwable e) { 682 | throw rethrow(e); 683 | } 684 | } 685 | 686 | static Stream asStream(Optional optional) { 687 | return optional.isPresent()? Stream.of(optional.get()): Stream.empty(); 688 | } 689 | 690 | static class SystemLoggerFactoryImpl { 691 | static final MethodHandle SYSTEM_LOGGER; 692 | private static final MethodHandle GET_SYSTEM_LOGGER; 693 | private static final Object ERROR, WARNING, INFO, DEBUG, TRACE; 694 | static { 695 | Lookup lookup = MethodHandles.lookup(); 696 | Class systemLoggerClass, systemLoggerLevelClass; 697 | MethodHandle mh, filter; 698 | try { 699 | systemLoggerClass = Class.forName("java.lang.System$Logger"); 700 | systemLoggerLevelClass = Class.forName("java.lang.System$Logger$Level"); 701 | mh = lookup.findVirtual(systemLoggerClass, "log", 702 | methodType(void.class, systemLoggerLevelClass, String.class, Throwable.class)); 703 | filter = lookup.findStatic(SystemLoggerFactoryImpl.class, "level", 704 | methodType(Object.class, Level.class)) 705 | .asType(methodType(systemLoggerLevelClass, Level.class)); 706 | } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) { 707 | throw new AssertionError(e); 708 | } 709 | mh = filterArguments(mh, 1, filter); 710 | SYSTEM_LOGGER = permuteArguments(mh, 711 | methodType(void.class, systemLoggerClass, String.class, Level.class, Throwable.class), 712 | 0, 2, 1, 3); 713 | 714 | try { 715 | GET_SYSTEM_LOGGER = lookup.findStatic(System.class, "getLogger", methodType(systemLoggerClass, String.class)) 716 | .asType(methodType(Object.class, String.class)); 717 | } catch (NoSuchMethodException | IllegalAccessException e) { 718 | throw new AssertionError(e); 719 | } 720 | 721 | try { 722 | ERROR = systemLoggerLevelClass.getField("ERROR").get(null); 723 | WARNING = systemLoggerLevelClass.getField("WARNING").get(null); 724 | INFO = systemLoggerLevelClass.getField("INFO").get(null); 725 | DEBUG = systemLoggerLevelClass.getField("DEBUG").get(null); 726 | TRACE = systemLoggerLevelClass.getField("TRACE").get(null); 727 | } catch (NoSuchFieldException | IllegalAccessException e) { 728 | throw new AssertionError(e); 729 | } 730 | } 731 | 732 | static Object getSystemLogger(String name) { 733 | try { 734 | return GET_SYSTEM_LOGGER.invokeExact(name); 735 | } catch (Throwable e) { 736 | throw new AssertionError(e); 737 | } 738 | } 739 | 740 | @SuppressWarnings("unused") 741 | private static Object level(Level level) { 742 | // do not use a switch here, we want this code to be inlined ! 743 | if (level == Level.ERROR) { 744 | return ERROR; 745 | } 746 | if (level == Level.WARNING) { 747 | return WARNING; 748 | } 749 | if (level == Level.INFO) { 750 | return INFO; 751 | } 752 | if (level == Level.DEBUG) { 753 | return DEBUG; 754 | } 755 | if (level == Level.TRACE) { 756 | return TRACE; 757 | } 758 | throw newIllegalStateException(); 759 | } 760 | } 761 | 762 | static class Log4JFactoryImpl implements LogFacade { 763 | private static final MethodHandle LOG4J_LOGGER; 764 | static { 765 | Lookup lookup = MethodHandles.lookup(); 766 | MethodHandle mh, filter; 767 | try { 768 | mh = lookup.findVirtual(org.apache.logging.log4j.Logger.class, "log", 769 | methodType(void.class, org.apache.logging.log4j.Level.class, Object.class, Throwable.class)); 770 | mh = mh.asType(methodType(void.class, org.apache.logging.log4j.Logger.class, org.apache.logging.log4j.Level.class, String.class, Throwable.class)); 771 | filter = lookup.findStatic(Log4JFactoryImpl.class, "level", 772 | methodType(org.apache.logging.log4j.Level.class, Level.class)); 773 | } catch (NoSuchMethodException | IllegalAccessException e) { 774 | throw new AssertionError(e); 775 | } 776 | mh = filterArguments(mh, 1, filter); 777 | LOG4J_LOGGER = permuteArguments(mh, 778 | methodType(void.class, org.apache.logging.log4j.Logger.class, String.class, Level.class, Throwable.class), 779 | 0, 2, 1, 3); 780 | } 781 | 782 | private static org.apache.logging.log4j.Level level(Level level) { 783 | // do not use a switch here, we want this code to be inlined ! 784 | if (level == Level.ERROR) { 785 | return org.apache.logging.log4j.Level.ERROR; 786 | } 787 | if (level == Level.WARNING) { 788 | return org.apache.logging.log4j.Level.WARN; 789 | } 790 | if (level == Level.INFO) { 791 | return org.apache.logging.log4j.Level.INFO; 792 | } 793 | if (level == Level.DEBUG) { 794 | return org.apache.logging.log4j.Level.DEBUG; 795 | } 796 | if (level == Level.TRACE) { 797 | return org.apache.logging.log4j.Level.TRACE; 798 | } 799 | throw newIllegalStateException(); 800 | } 801 | 802 | private final org.apache.logging.log4j.Logger logger; 803 | 804 | Log4JFactoryImpl(org.apache.logging.log4j.Logger logger) { 805 | this.logger = logger; 806 | } 807 | 808 | @Override 809 | public MethodHandle getLogMethodHandle() { 810 | return LOG4J_LOGGER.bindTo(logger); 811 | } 812 | 813 | @Override 814 | public void overrideLevel(Level level) { 815 | String name = logger.getName(); 816 | try { 817 | Class configuratorClass = Class.forName("org.apache.logging.log4j.core.config.Configurator"); 818 | Method setLevel = configuratorClass.getMethod("setLevel", String.class, org.apache.logging.log4j.Level.class); 819 | setLevel.invoke(null, name, level(level)); 820 | } catch(ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 821 | throw new UnsupportedOperationException("can not override the level of Log4J logger " + name, e); 822 | } 823 | } 824 | } 825 | 826 | static class SLF4JFactoryImpl { 827 | static final MethodHandle SLF4J_LOGGER; 828 | private static final MethodHandle ERROR_MH, WARNING_MH, INFO_MH, DEBUG_MH, TRACE_MH; 829 | static { 830 | Lookup lookup = MethodHandles.lookup(); 831 | ERROR_MH = mh(lookup, "error"); 832 | WARNING_MH = mh(lookup, "warn"); 833 | INFO_MH = mh(lookup, "info"); 834 | DEBUG_MH = mh(lookup, "debug"); 835 | TRACE_MH = mh(lookup, "trace"); 836 | 837 | MethodHandle level; 838 | try { 839 | level = lookup.findStatic(SLF4JFactoryImpl.class, "levelMH", 840 | methodType(MethodHandle.class, org.slf4j.Logger.class, String.class, Level.class)); 841 | } catch (NoSuchMethodException | IllegalAccessException e) { 842 | throw new AssertionError(e); 843 | } 844 | 845 | MethodHandle invoker = exactInvoker(methodType(void.class, org.slf4j.Logger.class, String.class, Throwable.class)); 846 | MethodHandle mh = dropArguments(invoker, 3, Level.class); 847 | SLF4J_LOGGER = foldArguments(mh, level); 848 | } 849 | 850 | private static MethodHandle mh(Lookup lookup, String name) { 851 | try { 852 | return lookup.findVirtual(org.slf4j.Logger.class, name, methodType(void.class, String.class, Throwable.class)); 853 | } catch (NoSuchMethodException | IllegalAccessException e) { 854 | throw new AssertionError(e); 855 | } 856 | } 857 | 858 | @SuppressWarnings("unused") 859 | private static MethodHandle levelMH(org.slf4j.Logger logger, String message, Level level) { 860 | // do not use a switch here, we want this code to be inlined ! 861 | if (level == Level.ERROR) { 862 | return ERROR_MH; 863 | } 864 | if (level == Level.WARNING) { 865 | return WARNING_MH; 866 | } 867 | if (level == Level.INFO) { 868 | return INFO_MH; 869 | } 870 | if (level == Level.DEBUG) { 871 | return DEBUG_MH; 872 | } 873 | if (level == Level.TRACE) { 874 | return TRACE_MH; 875 | } 876 | throw newIllegalStateException(); 877 | } 878 | } 879 | 880 | static class JULFactoryImpl implements LogFacade { 881 | private static final MethodHandle JUL_LOGGER; 882 | static { 883 | Lookup lookup = MethodHandles.lookup(); 884 | MethodHandle mh, filter; 885 | try { 886 | mh = lookup.findVirtual(java.util.logging.Logger.class, "log", 887 | methodType(void.class, java.util.logging.Level.class, String.class, Throwable.class)); 888 | filter = lookup.findStatic(JULFactoryImpl.class, "level", 889 | methodType(java.util.logging.Level.class, Level.class)); 890 | } catch (NoSuchMethodException | IllegalAccessException e) { 891 | throw new AssertionError(e); 892 | } 893 | mh = filterArguments(mh, 1, filter); 894 | JUL_LOGGER = permuteArguments(mh, 895 | methodType(void.class, java.util.logging.Logger.class, String.class, Level.class, Throwable.class), 896 | 0, 2, 1, 3); 897 | } 898 | 899 | private static java.util.logging.Level level(Level level) { 900 | // do not use a switch here, we want this code to be inlined ! 901 | if (level == Level.ERROR) { 902 | return java.util.logging.Level.SEVERE; 903 | } 904 | if (level == Level.WARNING) { 905 | return java.util.logging.Level.WARNING; 906 | } 907 | if (level == Level.INFO) { 908 | return java.util.logging.Level.INFO; 909 | } 910 | if (level == Level.DEBUG) { 911 | return java.util.logging.Level.FINE; 912 | } 913 | if (level == Level.TRACE) { 914 | return java.util.logging.Level.FINER; 915 | } 916 | throw newIllegalStateException(); 917 | } 918 | 919 | private final java.util.logging.Logger logger; 920 | 921 | JULFactoryImpl(java.util.logging.Logger logger) { 922 | this.logger = logger; 923 | } 924 | 925 | @Override 926 | public MethodHandle getLogMethodHandle() { 927 | return LoggerImpl.JULFactoryImpl.JUL_LOGGER.bindTo(logger); 928 | } 929 | 930 | @Override 931 | public void overrideLevel(Level level) { 932 | logger.setLevel(level(level)); 933 | } 934 | } 935 | 936 | static IllegalStateException newIllegalStateException() { 937 | return new IllegalStateException("unknown level"); 938 | } 939 | } 940 | -------------------------------------------------------------------------------- /src/main/java/com/github/forax/beautifullogger/tool/LoggerGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.tool; 2 | 3 | import static java.nio.charset.StandardCharsets.ISO_8859_1; 4 | import static org.objectweb.asm.ClassReader.SKIP_CODE; 5 | import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; 6 | import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; 7 | import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; 8 | import static org.objectweb.asm.Opcodes.ACC_FINAL; 9 | import static org.objectweb.asm.Opcodes.ACC_PRIVATE; 10 | import static org.objectweb.asm.Opcodes.ACC_PUBLIC; 11 | import static org.objectweb.asm.Opcodes.ACC_STATIC; 12 | import static org.objectweb.asm.Opcodes.ACC_SUPER; 13 | import static org.objectweb.asm.Opcodes.ACONST_NULL; 14 | import static org.objectweb.asm.Opcodes.ALOAD; 15 | import static org.objectweb.asm.Opcodes.ARETURN; 16 | import static org.objectweb.asm.Opcodes.ASM6; 17 | import static org.objectweb.asm.Opcodes.ASM7; 18 | import static org.objectweb.asm.Opcodes.DLOAD; 19 | import static org.objectweb.asm.Opcodes.DUP; 20 | import static org.objectweb.asm.Opcodes.GETFIELD; 21 | import static org.objectweb.asm.Opcodes.GETSTATIC; 22 | import static org.objectweb.asm.Opcodes.ILOAD; 23 | import static org.objectweb.asm.Opcodes.INVOKESPECIAL; 24 | import static org.objectweb.asm.Opcodes.INVOKESTATIC; 25 | import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; 26 | import static org.objectweb.asm.Opcodes.LLOAD; 27 | import static org.objectweb.asm.Opcodes.NEW; 28 | import static org.objectweb.asm.Opcodes.PUTFIELD; 29 | import static org.objectweb.asm.Opcodes.RETURN; 30 | import static org.objectweb.asm.Opcodes.V1_8; 31 | 32 | import java.io.IOException; 33 | import java.nio.file.Files; 34 | import java.nio.file.Path; 35 | import java.nio.file.Paths; 36 | import java.util.Base64; 37 | import java.util.Locale; 38 | 39 | import org.objectweb.asm.ClassReader; 40 | import org.objectweb.asm.ClassVisitor; 41 | import org.objectweb.asm.ClassWriter; 42 | import org.objectweb.asm.MethodVisitor; 43 | import org.objectweb.asm.Opcodes; 44 | import org.objectweb.asm.Type; 45 | 46 | public class LoggerGenerator { 47 | public static void main(String[] args) throws IOException { 48 | ClassWriter writer = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS); 49 | 50 | // reserve slot 1 of the constant pool (see LoggerServiceSPI) 51 | writer.newClass("com/github/forax/beautifullogger/Logger"); 52 | 53 | writer.visit(V1_8, ACC_SUPER, 54 | "com/github/forax/beautifullogger/Logger$Stub", null, 55 | "java/lang/Object", 56 | new String[] {"com/github/forax/beautifullogger/Logger"}); 57 | 58 | // field 59 | writer.visitField(ACC_PRIVATE|ACC_FINAL, "mh", "Ljava/lang/invoke/MethodHandle;", null, null); 60 | 61 | // constructor 62 | MethodVisitor init = writer.visitMethod(ACC_PRIVATE, "", "(Ljava/lang/invoke/MethodHandle;)V", null, null); 63 | init.visitCode(); 64 | init.visitVarInsn(ALOAD, 0); 65 | init.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); 66 | init.visitVarInsn(ALOAD, 0); 67 | init.visitVarInsn(ALOAD, 1); 68 | init.visitFieldInsn(PUTFIELD, "com/github/forax/beautifullogger/Logger$Stub", "mh", "Ljava/lang/invoke/MethodHandle;"); 69 | init.visitInsn(RETURN); 70 | init.visitMaxs(0, 0); 71 | init.visitEnd(); 72 | 73 | // static factory method 74 | MethodVisitor factory = writer.visitMethod(ACC_PUBLIC|ACC_STATIC, "create", 75 | "(Ljava/lang/invoke/MethodHandle;)Lcom/github/forax/beautifullogger/Logger;", null, null); 76 | factory.visitCode(); 77 | factory.visitTypeInsn(NEW, "com/github/forax/beautifullogger/Logger$Stub"); 78 | factory.visitInsn(DUP); 79 | factory.visitVarInsn(ALOAD, 0); 80 | factory.visitMethodInsn(INVOKESPECIAL, "com/github/forax/beautifullogger/Logger$Stub", "", 81 | "(Ljava/lang/invoke/MethodHandle;)V", false); 82 | factory.visitInsn(ARETURN); 83 | factory.visitMaxs(0, 0); 84 | factory.visitEnd(); 85 | 86 | // method 87 | generateOverride(writer, 88 | Paths.get("target/main/exploded/com.github.forax.beautifullogger/com/github/forax/beautifullogger/Logger.class")); 89 | generateOverride(writer, 90 | Paths.get("target/main/exploded/com.github.forax.beautifullogger/com/github/forax/beautifullogger/LogService.class")); 91 | 92 | writer.visitEnd(); 93 | 94 | byte[] array = writer.toByteArray(); 95 | 96 | //DEBUG 97 | //Files.write(Paths.get("Logger$Stub.class"), array); 98 | 99 | String data = new String(Base64.getEncoder().encode(array), ISO_8859_1); 100 | System.out.println(data); 101 | } 102 | 103 | private static void generateOverride(ClassWriter writer, Path loggerClass) throws IOException { 104 | ClassReader reader = new ClassReader(Files.readAllBytes(loggerClass)); 105 | reader.accept(new ClassVisitor(ASM7) { 106 | @Override 107 | public void visitInnerClass(String name, String outerName, String innerName, int access) { 108 | // skip inner class 109 | } 110 | @Override 111 | public void visitNestMember(String nestMember) { 112 | // skip nest member 113 | } 114 | 115 | @Override 116 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 117 | if ((access & ACC_ABSTRACT) == 0) { // implement abstract method 118 | return null; 119 | } 120 | 121 | MethodVisitor mv = writer.visitMethod(access & (~ACC_ABSTRACT), name, desc, signature, exceptions); 122 | // old Hidden annotation, up to jdk 12 123 | mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true) 124 | .visitEnd(); 125 | // new Hidden annotation, jdk 13+ 126 | mv.visitAnnotation("Ljdk/internal/vm/annotation/Hidden;", true) 127 | .visitEnd(); 128 | mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true) 129 | .visitEnd(); 130 | mv.visitCode(); 131 | mv.visitVarInsn(ALOAD, 0); 132 | mv.visitFieldInsn(GETFIELD, "com/github/forax/beautifullogger/Logger$Stub", "mh", 133 | "Ljava/lang/invoke/MethodHandle;"); 134 | 135 | if (!name.equals("log")) { 136 | mv.visitFieldInsn(GETSTATIC, "com/github/forax/beautifullogger/Logger$Level", name.toUpperCase(Locale.ROOT), 137 | "Lcom/github/forax/beautifullogger/Logger$Level;"); 138 | } 139 | 140 | switch(Type.getArgumentTypes(desc)[0].getDescriptor()) { 141 | case "Lcom/github/forax/beautifullogger/Logger$Level;": // log 142 | for(int i = 0; i < 7; i++) { 143 | mv.visitVarInsn(ALOAD, i + 1); 144 | } 145 | break; 146 | case "Ljava/lang/String;": // message + throwable 147 | mv.visitVarInsn(ALOAD, 2); 148 | mv.visitVarInsn(ALOAD, 1); 149 | genNONE(mv, 4); 150 | break; 151 | case "Ljava/util/function/Supplier;": 152 | mv.visitInsn(ACONST_NULL); 153 | mv.visitVarInsn(ALOAD, 1); 154 | genNONE(mv, 4); 155 | break; 156 | case "Ljava/util/function/Function;": 157 | mv.visitInsn(ACONST_NULL); 158 | mv.visitVarInsn(ALOAD, 1); 159 | mv.visitVarInsn(ALOAD, 2); 160 | genNONE(mv, 3); 161 | break; 162 | case "Ljava/util/function/IntFunction;": 163 | mv.visitInsn(ACONST_NULL); 164 | mv.visitVarInsn(ALOAD, 1); 165 | mv.visitVarInsn(ILOAD, 2); 166 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); 167 | genNONE(mv, 3); 168 | break; 169 | case "Ljava/util/function/LongFunction;": 170 | mv.visitInsn(ACONST_NULL); 171 | mv.visitVarInsn(ALOAD, 1); 172 | mv.visitVarInsn(LLOAD, 2); 173 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); 174 | genNONE(mv, 3); 175 | break; 176 | case "Ljava/util/function/DoubleFunction;": 177 | mv.visitInsn(ACONST_NULL); 178 | mv.visitVarInsn(ALOAD, 1); 179 | mv.visitVarInsn(DLOAD, 2); 180 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); 181 | genNONE(mv, 3); 182 | break; 183 | case "Ljava/util/function/BiFunction;": 184 | mv.visitInsn(ACONST_NULL); 185 | mv.visitVarInsn(ALOAD, 1); 186 | mv.visitVarInsn(ALOAD, 2); 187 | mv.visitVarInsn(ALOAD, 3); 188 | genNONE(mv, 2); 189 | break; 190 | default: 191 | throw new AssertionError("invalid method descriptor " + name + desc); 192 | } 193 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", 194 | "(Lcom/github/forax/beautifullogger/Logger$Level;Ljava/lang/Throwable;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", 195 | false); 196 | 197 | mv.visitInsn(RETURN); 198 | mv.visitMaxs(-1, -1); 199 | mv.visitEnd(); 200 | 201 | return null; 202 | } 203 | 204 | private void genNONE(MethodVisitor mv, int count) { 205 | for(int i = 0; i < count; i++) { 206 | mv.visitFieldInsn(Opcodes.GETSTATIC, "com/github/forax/beautifullogger/LoggerImpl", "NONE", "Ljava/lang/Object;"); 207 | } 208 | } 209 | 210 | }, SKIP_CODE); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/main/java/com/github/forax/beautifullogger/tool/ModuleInfoGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.tool; 2 | 3 | import org.objectweb.asm.ClassWriter; 4 | import org.objectweb.asm.ModuleVisitor; 5 | 6 | import java.io.IOException; 7 | import java.io.UncheckedIOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | import java.util.stream.Stream; 12 | 13 | import static org.objectweb.asm.Opcodes.ACC_MANDATED; 14 | import static org.objectweb.asm.Opcodes.ACC_MODULE; 15 | import static org.objectweb.asm.Opcodes.ACC_STATIC_PHASE; 16 | import static org.objectweb.asm.Opcodes.V9; 17 | 18 | public class ModuleInfoGenerator { 19 | public static byte[] generate() { 20 | 21 | ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); 22 | classWriter.visit(V9, ACC_MODULE, "module-info", null, null, null); 23 | classWriter.visitSource("module-info.java", null); 24 | 25 | ModuleVisitor moduleVisitor = 26 | classWriter.visitModule("com.github.forax.beautifullogger", 0, "0.10.6"); 27 | 28 | moduleVisitor.visitRequire("java.base", ACC_MANDATED, null); 29 | moduleVisitor.visitRequire("jdk.unsupported", ACC_STATIC_PHASE, null); 30 | 31 | moduleVisitor.visitRequire("org.apache.logging.log4j", ACC_STATIC_PHASE, "2.17.2"); 32 | moduleVisitor.visitRequire("org.slf4j", ACC_STATIC_PHASE, "2.0.0-alpha7"); 33 | moduleVisitor.visitRequire("ch.qos.logback.classic", ACC_STATIC_PHASE, "1.3.0-alpha16"); 34 | moduleVisitor.visitRequire("ch.qos.logback.core", ACC_STATIC_PHASE, "1.3.0-alpha16"); 35 | moduleVisitor.visitRequire("java.logging", ACC_STATIC_PHASE, null); 36 | //moduleVisitor.visitRequire("org.objectweb.asm", ACC_STATIC_PHASE, "9.3.0"); 37 | 38 | moduleVisitor.visitExport("com/github/forax/beautifullogger", 0); 39 | moduleVisitor.visitEnd(); 40 | 41 | classWriter.visitEnd(); 42 | 43 | return classWriter.toByteArray(); 44 | } 45 | 46 | public static void main(String[] args) throws IOException { 47 | Path directory = Paths.get(args[0]); 48 | 49 | System.out.println("generate module-info in " + directory); 50 | Files.write(directory.resolve("module-info.class"), generate()); 51 | 52 | System.out.println("remove 'tool' classes in " + directory); 53 | try(Stream stream = Files.walk(directory)) { 54 | stream 55 | .filter(path -> path.getFileName().toString().endsWith(".class")) 56 | .forEach(path -> { 57 | boolean tool = path.toString().contains("com/github/forax/beautifullogger/tool"); 58 | try { 59 | if (tool) { 60 | System.out.println("delete " + path); 61 | Files.delete(path); 62 | return; 63 | } 64 | } catch (IOException e) { 65 | throw new UncheckedIOException(e); 66 | } 67 | }); 68 | } catch(UncheckedIOException e) { 69 | throw e.getCause(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/github/forax/beautifullogger/tool/ReflectionSupplier.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.tool; 2 | 3 | import java.util.function.Supplier; 4 | 5 | public class ReflectionSupplier implements Supplier> { 6 | @Override 7 | public Class get() { 8 | //return sun.reflect.Reflection.getCallerClass(5); 9 | throw new UnsupportedOperationException(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/github/forax/beautifullogger/tool/ReflectionSupplierEncoder.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.tool; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Base64; 6 | 7 | public class ReflectionSupplierEncoder { 8 | public static void main(String[] args) throws IOException { 9 | //InputStream input = ReflectionSupplierEncoder.class.getResourceAsStream("ReflectionSupplier.class"); 10 | //byte[] code = input.readAllBytes(); 11 | //byte[] rewritedCode = Rewriter.rewrite(code); 12 | //System.out.println(Base64.getEncoder().encodeToString(rewritedCode)); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/forax/beautifullogger/tool/Rewriter.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.tool; 2 | 3 | import java.io.IOException; 4 | import java.io.UncheckedIOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.util.stream.Stream; 9 | 10 | import org.objectweb.asm.ClassReader; 11 | import org.objectweb.asm.ClassVisitor; 12 | import org.objectweb.asm.ClassWriter; 13 | import org.objectweb.asm.Opcodes; 14 | 15 | /** 16 | * Rewrite the bytecode to be Java 8 compatible 17 | */ 18 | public class Rewriter { 19 | public static byte[] rewrite(byte[] code) throws IOException { 20 | ClassReader reader = new ClassReader(code); 21 | ClassWriter writer = new ClassWriter(reader, 0); 22 | 23 | reader.accept(new ClassVisitor(Opcodes.ASM9, writer) { 24 | @Override 25 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 26 | int newVersion = name.equals("module-info")? Opcodes.V9: Opcodes.V1_8; 27 | super.visit(newVersion, access, name, signature, superName, interfaces); 28 | } 29 | }, ClassReader.SKIP_CODE); 30 | 31 | return writer.toByteArray(); 32 | } 33 | 34 | public static void main(String[] args) throws IOException { 35 | Path directory = Paths.get(args[0]); 36 | System.out.println("rewrite " + directory + " to Java 8"); 37 | 38 | try(Stream stream = Files.walk(directory)) { 39 | stream 40 | .filter(path -> path.getFileName().toString().endsWith(".class")) 41 | .forEach(path -> { 42 | boolean tool = path.toString().contains("com/github/forax/beautifullogger/tool"); 43 | try { 44 | if (tool) { 45 | System.out.println("delete " + path); 46 | Files.delete(path); 47 | return; 48 | } 49 | 50 | System.out.println("rewrite " + path); 51 | Files.write(path, rewrite(Files.readAllBytes(path))); 52 | } catch (IOException e) { 53 | throw new UncheckedIOException(e); 54 | } 55 | }); 56 | } catch(UncheckedIOException e) { 57 | throw e.getCause(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/LoggerConfigSupport.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger; 2 | 3 | import static java.lang.invoke.MethodType.methodType; 4 | 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodHandles.Lookup; 8 | import java.util.Objects; 9 | 10 | import com.github.forax.beautifullogger.Logger.Level; 11 | import com.github.forax.beautifullogger.LoggerConfig.LogFacadeFactory; 12 | 13 | class LoggerConfigSupport { 14 | /** 15 | * The interface to intercept the logging events after that logging level 16 | * have been been verified and the message have been computed. 17 | */ 18 | @FunctionalInterface 19 | interface Printer { 20 | /** 21 | * Emit the logging event. 22 | * 23 | * @param message the event message 24 | * @param level the event level 25 | * @param context the event exception or null. 26 | */ 27 | void print(String message, Level level, Throwable context); 28 | } 29 | 30 | static final MethodHandle PRINTER_PRINT; 31 | static { 32 | Lookup lookup = MethodHandles.lookup(); 33 | try { 34 | PRINTER_PRINT = lookup.findVirtual(Printer.class, "print", methodType(void.class, String.class, Level.class, Throwable.class)); 35 | } catch (NoSuchMethodException | IllegalAccessException e) { 36 | throw new AssertionError(e); 37 | } 38 | } 39 | 40 | /** 41 | * Create a PrintFactory from a printer 42 | * @param printer a printer. 43 | * @return a new PrintFactory that delegate the logging to the printer. 44 | * @throws NullPointerException if the printer is null. 45 | */ 46 | static LogFacadeFactory printer(Printer printer) { 47 | Objects.requireNonNull(printer); 48 | MethodHandle target = PRINTER_PRINT.bindTo(printer); 49 | return __ -> () -> target; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/LoggerConfigurationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNull; 5 | import static com.github.forax.beautifullogger.LoggerConfigSupport.printer; 6 | import static org.junit.jupiter.api.Assertions.assertAll; 7 | import static org.junit.jupiter.api.Assertions.fail; 8 | 9 | import org.junit.jupiter.api.Test; 10 | 11 | import com.github.forax.beautifullogger.LoggerConfig; 12 | import com.github.forax.beautifullogger.Logger.Level; 13 | import com.github.forax.beautifullogger.LoggerConfigSupport.Printer; 14 | 15 | import java.lang.invoke.MethodHandles; 16 | import java.lang.invoke.MethodHandles.Lookup; 17 | 18 | @SuppressWarnings("static-method") 19 | public class LoggerConfigurationTests { 20 | @Test 21 | public void loggerDisableAtCreationTime() { 22 | Logger logger = Logger.getLogger( 23 | new Object() { }.getClass(), 24 | upd -> upd.enable(false).logFacadeFactory(printer((message, loggerLevel, context) -> { 25 | fail("logger shouble be disable"); 26 | }))); 27 | logger.debug("exception", null); 28 | logger.error("exception", null); 29 | logger.info("exception", null); 30 | logger.trace("exception", null); 31 | logger.warning("exception", null); 32 | } 33 | 34 | @Test 35 | public void loggerDisableAfterCreationTime() { 36 | Class confClass = new Object() { }.getClass(); 37 | Logger logger = Logger.getLogger( 38 | confClass, 39 | upd -> upd.logFacadeFactory(printer((message, loggerLevel, context) -> { 40 | fail("logger shouble be disable"); 41 | }))); 42 | LoggerConfig.fromClass(confClass).update(upd -> upd.enable(false)); 43 | logger.debug("exception", null); 44 | logger.error("exception", null); 45 | logger.info("exception", null); 46 | logger.trace("exception", null); 47 | logger.warning("exception", null); 48 | } 49 | 50 | @Test 51 | public void loggerEnableThenDisable() { 52 | Class confClass = new Object() { }.getClass(); 53 | class MyPrinter implements Printer { 54 | boolean disable; 55 | 56 | @Override 57 | public void print(String message, Level level, Throwable context) { 58 | if (disable) { 59 | fail("the logger is disable"); 60 | } else { 61 | assertAll( 62 | () -> assertEquals(Level.ERROR, level), 63 | () -> assertEquals("message", message), 64 | () -> assertNull(context)); 65 | } 66 | } 67 | } 68 | MyPrinter printer = new MyPrinter(); 69 | Logger logger = Logger.getLogger(confClass, upd -> upd.logFacadeFactory(printer(printer))); 70 | 71 | for(int i = 0; i < 100_000; i++) { 72 | logger.error(() -> "message"); 73 | if (i == 20_000) { 74 | LoggerConfig.fromClass(confClass).update(upd -> upd.enable(false)); 75 | printer.disable = true; 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/LoggerLevelTests.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger; 2 | 3 | import static com.github.forax.beautifullogger.LoggerConfigSupport.printer; 4 | import static org.junit.jupiter.api.Assertions.assertAll; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertNull; 7 | import static org.junit.jupiter.api.Assertions.assertSame; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | import java.util.AbstractMap.SimpleImmutableEntry; 11 | import java.util.Arrays; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Map.Entry; 16 | import java.util.function.BiConsumer; 17 | import java.util.function.Consumer; 18 | import java.util.function.Supplier; 19 | import java.util.stream.Stream; 20 | 21 | import org.junit.jupiter.params.ParameterizedTest; 22 | import org.junit.jupiter.params.provider.Arguments; 23 | import org.junit.jupiter.params.provider.MethodSource; 24 | 25 | import com.github.forax.beautifullogger.Logger.Level; 26 | 27 | @SuppressWarnings("static-method") 28 | public class LoggerLevelTests { 29 | 30 | private static Map.Entry entry(K key, V value) { 31 | return new SimpleImmutableEntry<>(key, value); 32 | } 33 | 34 | private static List listOf(T... values) { 35 | return Collections.unmodifiableList(Arrays.asList(values)); 36 | } 37 | 38 | @SuppressWarnings("unused") 39 | private static Stream logNullThrowableAndLevelPairSource() { 40 | List, Level>> list = listOf( 41 | entry(l -> l.debug("hello", null), Level.DEBUG), 42 | entry(l -> l.error("hello", null), Level.ERROR), 43 | entry(l -> l.info("hello", null), Level.INFO), 44 | entry(l -> l.trace("hello", null), Level.TRACE), 45 | entry(l -> l.warning("hello", null), Level.WARNING)); 46 | return list.stream().map(e -> Arguments.of(e.getKey(), e.getValue())); 47 | } 48 | 49 | @ParameterizedTest 50 | @MethodSource("logNullThrowableAndLevelPairSource") 51 | public void logNullThrowableAndLevelMatch(Consumer consumer, Level level) { 52 | boolean[] marked = { false }; 53 | Logger logger = Logger.getLogger( 54 | new Object() { }.getClass(), 55 | upd -> upd.level(Level.TRACE, false).logFacadeFactory(printer((message, loggerLevel, context) -> { 56 | marked[0] = true; 57 | assertAll( 58 | () -> assertEquals(level, loggerLevel), 59 | () -> assertEquals("hello", message), 60 | () -> assertNull(context)); 61 | }))); 62 | consumer.accept(logger); 63 | assertTrue(marked[0]); 64 | } 65 | 66 | @SuppressWarnings("unused") 67 | private static Stream logThrowableAndLevelPairSource() { 68 | List, Level>> list = listOf( 69 | entry((l, t) -> l.debug("exception", t), Level.DEBUG), 70 | entry((l, t) -> l.error("exception", t), Level.ERROR), 71 | entry((l, t) -> l.info("exception", t), Level.INFO), 72 | entry((l, t) -> l.trace("exception", t), Level.TRACE), 73 | entry((l, t) -> l.warning("exception", t), Level.WARNING)); 74 | return list.stream().map(e -> Arguments.of(e.getKey(), e.getValue())); 75 | } 76 | @ParameterizedTest 77 | @MethodSource("logThrowableAndLevelPairSource") 78 | public void logThrowableAndLevelMatch(BiConsumer consumer, Level level) { 79 | Throwable throwable = new Throwable(); 80 | boolean[] marked = { false }; 81 | Logger logger = Logger.getLogger( 82 | new Object() { }.getClass(), 83 | upd -> upd.level(Level.TRACE, false).logFacadeFactory(printer((message, loggerLevel, context) -> { 84 | marked[0] = true; 85 | assertAll( 86 | () -> assertEquals(level, loggerLevel), 87 | () -> assertEquals("exception", message), 88 | () -> assertSame(throwable, context)); 89 | }))); 90 | consumer.accept(logger, throwable); 91 | assertTrue(marked[0]); 92 | } 93 | 94 | @SuppressWarnings("unused") 95 | private static Stream logSupplierAndLevelPairSource() { 96 | List>, Level>> list = listOf( 97 | entry(Logger::debug, Level.DEBUG), 98 | entry(Logger::error, Level.ERROR), 99 | entry(Logger::info, Level.INFO), 100 | entry(Logger::trace, Level.TRACE), 101 | entry(Logger::warning, Level.WARNING)); 102 | return list.stream().map(e -> Arguments.of(e.getKey(), e.getValue())); 103 | } 104 | @ParameterizedTest 105 | @MethodSource("logSupplierAndLevelPairSource") 106 | public void logSupplierAndLevelMatch(BiConsumer> consumer, Level level) { 107 | boolean[] marked = { false }; 108 | Logger logger = Logger.getLogger( 109 | new Object() { }.getClass(), 110 | upd -> upd.level(Level.TRACE, false).logFacadeFactory(printer((message, loggerLevel, context) -> { 111 | marked[0] = true; 112 | assertAll( 113 | () -> assertEquals(level, loggerLevel), 114 | () -> assertEquals("foo", message), 115 | () -> assertNull(context)); 116 | }))); 117 | consumer.accept(logger, () -> "foo"); 118 | assertTrue(marked[0]); 119 | } 120 | 121 | static Stream logIntFunctionAndLevelPairSource() { 122 | List, Level>> list = listOf( 123 | entry(l -> l.debug((int v) -> "" + v, 1), Level.DEBUG), 124 | entry(l -> l.error((int v) -> "" + v, 1), Level.ERROR), 125 | entry(l -> l.info((int v) -> "" + v, 1), Level.INFO), 126 | entry(l -> l.trace((int v) -> "" + v, 1), Level.TRACE), 127 | entry(l -> l.warning((int v) -> "" + v, 1), Level.WARNING)); 128 | return list.stream().map(e -> Arguments.of(e.getKey(), e.getValue())); 129 | } 130 | @ParameterizedTest 131 | @MethodSource("logIntFunctionAndLevelPairSource") 132 | public void logIntFunctionAndLevelMatch(Consumer consumer, Level level) { 133 | boolean[] marked = { false }; 134 | Logger logger = Logger.getLogger( 135 | new Object() { }.getClass(), 136 | upd -> upd.level(Level.TRACE, false).logFacadeFactory(printer((message, loggerLevel, context) -> { 137 | marked[0] = true; 138 | assertAll( 139 | () -> assertEquals(level, loggerLevel), 140 | () -> assertEquals("1", message), 141 | () -> assertNull(context)); 142 | }))); 143 | consumer.accept(logger); 144 | assertTrue(marked[0]); 145 | } 146 | 147 | 148 | static Stream logLongFunctionAndLevelPairSource() { 149 | List, Level>> list = listOf( 150 | entry(l -> l.debug((long v) -> "" + v, 2L), Level.DEBUG), 151 | entry(l -> l.error((long v) -> "" + v, 2L), Level.ERROR), 152 | entry(l -> l.info((long v) -> "" + v, 2L), Level.INFO), 153 | entry(l -> l.trace((long v) -> "" + v, 2L), Level.TRACE), 154 | entry(l -> l.warning((long v) -> "" + v, 2L), Level.WARNING)); 155 | return list.stream().map(e -> Arguments.of(e.getKey(), e.getValue())); 156 | } 157 | @ParameterizedTest 158 | @MethodSource("logLongFunctionAndLevelPairSource") 159 | public void logLongFunctionAndLevelMatch(Consumer consumer, Level level) { 160 | boolean[] marked = { false }; 161 | Logger logger = Logger.getLogger( 162 | new Object() { }.getClass(), 163 | upd -> upd.level(Level.TRACE, false).logFacadeFactory(printer((message, loggerLevel, context) -> { 164 | marked[0] = true; 165 | assertAll( 166 | () -> assertEquals(level, loggerLevel), 167 | () -> assertEquals("2", message), 168 | () -> assertNull(context)); 169 | }))); 170 | consumer.accept(logger); 171 | assertTrue(marked[0]); 172 | } 173 | 174 | @SuppressWarnings("unused") 175 | private static Stream logDoubleFunctionAndLevelPairSource() { 176 | List, Level>> list = listOf( 177 | entry(l -> l.debug(Double::toString, 3.0), Level.DEBUG), 178 | entry(l -> l.error(Double::toString, 3.0), Level.ERROR), 179 | entry(l -> l.info(Double::toString, 3.0), Level.INFO), 180 | entry(l -> l.trace(Double::toString, 3.0), Level.TRACE), 181 | entry(l -> l.warning(Double::toString, 3.0), Level.WARNING)); 182 | return list.stream().map(e -> Arguments.of(e.getKey(), e.getValue())); 183 | } 184 | @ParameterizedTest 185 | @MethodSource("logDoubleFunctionAndLevelPairSource") 186 | public void logDoubleFunctionAndLevelMatch(Consumer consumer, Level level) { 187 | boolean[] marked = { false }; 188 | Logger logger = Logger.getLogger( 189 | new Object() { }.getClass(), 190 | upd -> upd.level(Level.TRACE, false).logFacadeFactory(printer((message, loggerLevel, context) -> { 191 | marked[0] = true; 192 | assertAll( 193 | () -> assertEquals(level, loggerLevel), 194 | () -> assertEquals("3.0", message), 195 | () -> assertNull(context)); 196 | }))); 197 | consumer.accept(logger); 198 | assertTrue(marked[0]); 199 | } 200 | 201 | @SuppressWarnings("unused") 202 | private static Stream logFunctionAndLevelPairSource() { 203 | List, Level>> list = listOf( 204 | entry(l -> l.debug(x -> x, "bar"), Level.DEBUG), 205 | entry(l -> l.error(x -> x, "bar"), Level.ERROR), 206 | entry(l -> l.info(x -> x, "bar"), Level.INFO), 207 | entry(l -> l.trace(x -> x, "bar"), Level.TRACE), 208 | entry(l -> l.warning(x -> x, "bar"), Level.WARNING)); 209 | return list.stream().map(e -> Arguments.of(e.getKey(), e.getValue())); 210 | } 211 | @ParameterizedTest 212 | @MethodSource("logFunctionAndLevelPairSource") 213 | public void logFunctionAndLevelMatch(Consumer consumer, Level level) { 214 | boolean[] marked = { false }; 215 | Logger logger = Logger.getLogger( 216 | new Object() { }.getClass(), 217 | upd -> upd.level(Level.TRACE, false).logFacadeFactory(printer((message, loggerLevel, context) -> { 218 | marked[0] = true; 219 | assertAll( 220 | () -> assertEquals(level, loggerLevel), 221 | () -> assertEquals("bar", message), 222 | () -> assertNull(context)); 223 | }))); 224 | consumer.accept(logger); 225 | assertTrue(marked[0]); 226 | } 227 | 228 | @SuppressWarnings("unused") 229 | private static Stream logBiFunctionAndLevelPairSource() { 230 | List, Level>> list = listOf( 231 | entry(l -> l.debug((a, b) -> a + b, "foo", "bar"), Level.DEBUG), 232 | entry(l -> l.error((a, b) -> a + b, "foo", "bar"), Level.ERROR), 233 | entry(l -> l.info((a, b) -> a + b, "foo", "bar"), Level.INFO), 234 | entry(l -> l.trace((a, b) -> a + b, "foo", "bar"), Level.TRACE), 235 | entry(l -> l.warning((a, b) -> a + b, "foo", "bar"), Level.WARNING)); 236 | return list.stream().map(e -> Arguments.of(e.getKey(), e.getValue())); 237 | } 238 | @ParameterizedTest 239 | @MethodSource("logBiFunctionAndLevelPairSource") 240 | public void logBiFunctionAndLevelMatch(Consumer consumer, Level level) { 241 | boolean[] marked = { false }; 242 | Logger logger = Logger.getLogger( 243 | new Object() { }.getClass(), 244 | upd -> upd.level(Level.TRACE, false).logFacadeFactory(printer((message, loggerLevel, context) -> { 245 | marked[0] = true; 246 | assertAll( 247 | () -> assertEquals(level, loggerLevel), 248 | () -> assertEquals("foobar", message), 249 | () -> assertNull(context)); 250 | }))); 251 | consumer.accept(logger); 252 | assertTrue(marked[0]); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/integration/jul/VerySimpleTests.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.integration.jul; 2 | 3 | import static com.github.forax.beautifullogger.Logger.Level.DEBUG; 4 | import static java.lang.invoke.MethodHandles.lookup; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.github.forax.beautifullogger.Logger; 9 | import com.github.forax.beautifullogger.LoggerConfig; 10 | import com.github.forax.beautifullogger.LoggerConfig.LogFacadeFactory; 11 | 12 | import java.lang.invoke.MethodHandles; 13 | import java.lang.invoke.MethodHandles.Lookup; 14 | 15 | @SuppressWarnings("static-method") 16 | public class VerySimpleTests { 17 | private static final Logger LOGGER = Logger.getLogger(); 18 | 19 | @Test 20 | public void justAVerySimpleTest() { 21 | LoggerConfig config = LoggerConfig.fromClass(VerySimpleTests.class); 22 | config.update(upd -> upd.logFacadeFactory(LogFacadeFactory.julFactory())); 23 | 24 | for(int i = 0; i < 10; i++) { 25 | LOGGER.error((int value) -> "message " + value, i); 26 | 27 | if (i == 1) { 28 | config.update(upd -> upd.enable(false)); 29 | } 30 | } 31 | } 32 | 33 | private static void updateDefaultsLevel(java.util.logging.Level level) { 34 | for (java.util.logging.Handler handler: java.util.logging.Logger.getLogger("").getHandlers()) { 35 | handler.setLevel(level); 36 | } 37 | } 38 | 39 | @Test 40 | public void overrideLevel() { 41 | class Conf { /* empty */ } 42 | 43 | updateDefaultsLevel(java.util.logging.Level.FINE); 44 | try { 45 | LoggerConfig config = LoggerConfig.fromClass(Conf.class); 46 | config.update(upd -> upd.logFacadeFactory(LogFacadeFactory.julFactory()).level(DEBUG, true)); 47 | Logger logger = Logger.getLogger(Conf.class); 48 | logger.debug("JUL override ok !", null); 49 | } finally { 50 | updateDefaultsLevel(java.util.logging.Level.INFO); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/integration/log4j/VerySimpleTests.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.integration.log4j; 2 | 3 | import static com.github.forax.beautifullogger.Logger.Level.DEBUG; 4 | import static java.lang.invoke.MethodHandles.lookup; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.github.forax.beautifullogger.Logger; 9 | import com.github.forax.beautifullogger.LoggerConfig; 10 | import com.github.forax.beautifullogger.LoggerConfig.LogFacadeFactory; 11 | 12 | import java.lang.invoke.MethodHandles; 13 | import java.lang.invoke.MethodHandles.Lookup; 14 | 15 | @SuppressWarnings("static-method") 16 | public class VerySimpleTests { 17 | private static final Logger LOGGER = Logger.getLogger(); 18 | 19 | @Test 20 | public void justAVerySimpleTest() { 21 | LoggerConfig config = LoggerConfig.fromClass(VerySimpleTests.class); 22 | config.update(upd -> upd.logFacadeFactory(LogFacadeFactory.log4jFactory())); 23 | 24 | for(int i = 0; i < 10; i++) { 25 | LOGGER.error((int value) -> "message " + value, i); 26 | 27 | if (i == 1) { 28 | config.update(upd -> upd.enable(false)); 29 | } 30 | } 31 | } 32 | 33 | @Test 34 | public void overrideLevel() { 35 | class Conf { /* empty */ } 36 | LoggerConfig config = LoggerConfig.fromClass(Conf.class); 37 | config.update(upd -> upd.logFacadeFactory(LogFacadeFactory.log4jFactory()).level(DEBUG, true)); 38 | Logger logger = Logger.getLogger(Conf.class); 39 | logger.debug("Log4J override ok !", null); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/integration/slf4j/VerySimpleTests.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.integration.slf4j; 2 | 3 | import static com.github.forax.beautifullogger.Logger.Level.DEBUG; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import com.github.forax.beautifullogger.Logger; 9 | import com.github.forax.beautifullogger.LoggerConfig; 10 | import com.github.forax.beautifullogger.LoggerConfig.LogFacadeFactory; 11 | 12 | import java.lang.invoke.MethodHandle; 13 | import java.lang.invoke.MethodHandles; 14 | import java.lang.invoke.MethodHandles.Lookup; 15 | 16 | @SuppressWarnings("static-method") 17 | public class VerySimpleTests { 18 | private static final Logger LOGGER = Logger.getLogger(); 19 | 20 | @Test 21 | public void justAVerySimpleTest() { 22 | LoggerConfig config = LoggerConfig.fromClass(VerySimpleTests.class); 23 | config.update(upd -> upd.logFacadeFactory(LogFacadeFactory.slf4jFactory())); 24 | 25 | for(int i = 0; i < 10; i++) { 26 | LOGGER.error((int value) -> "message " + value, i); 27 | 28 | if (i == 1) { 29 | config.update(upd -> upd.enable(false)); 30 | } 31 | } 32 | } 33 | 34 | @Test 35 | public void overrideLevel() { 36 | class Conf { /* empty */ } 37 | LoggerConfig config = LoggerConfig.fromClass(Conf.class); 38 | config.update(upd -> upd.logFacadeFactory(LogFacadeFactory.slf4jFactory()).level(DEBUG, true)); 39 | Logger logger = Logger.getLogger(Conf.class); 40 | assertThrows(UnsupportedOperationException.class, () -> { 41 | logger.debug("this message should not be printed", null); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/integration/systemlogger/VerySimpleTests.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.integration.systemlogger; 2 | 3 | import static com.github.forax.beautifullogger.Logger.Level.DEBUG; 4 | import static java.lang.invoke.MethodHandles.lookup; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import com.github.forax.beautifullogger.Logger; 10 | import com.github.forax.beautifullogger.LoggerConfig; 11 | import com.github.forax.beautifullogger.LoggerConfig.LogFacadeFactory; 12 | import org.junit.jupiter.api.condition.DisabledOnJre; 13 | import org.junit.jupiter.api.condition.JRE; 14 | 15 | import java.lang.invoke.MethodHandle; 16 | import java.lang.invoke.MethodHandles; 17 | import java.lang.invoke.MethodHandles.Lookup; 18 | 19 | @SuppressWarnings("static-method") 20 | @DisabledOnJre(JRE.JAVA_8) 21 | public class VerySimpleTests { 22 | private static final Logger LOGGER = Logger.getLogger(); 23 | 24 | @Test 25 | public void justAVerySimpleTest() { 26 | LoggerConfig config = LoggerConfig.fromClass(VerySimpleTests.class); 27 | config.update(upd -> upd.logFacadeFactory(LogFacadeFactory.systemLoggerFactory())); 28 | 29 | for(int i = 0; i < 10; i++) { 30 | LOGGER.error((int value) -> "message " + value, i); 31 | 32 | if (i == 1) { 33 | config.update(upd -> upd.enable(false)); 34 | } 35 | } 36 | } 37 | 38 | @Test 39 | public void overrideLevel() { 40 | class Conf { /* empty */ } 41 | LoggerConfig config = LoggerConfig.fromClass(Conf.class); 42 | config.update(upd -> upd.logFacadeFactory(LogFacadeFactory.systemLoggerFactory()).level(DEBUG, true)); 43 | Logger logger = Logger.getLogger(Conf.class); 44 | assertThrows(UnsupportedOperationException.class, () -> { 45 | logger.debug("this message should not be printed", null); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/perf/LoggerDisabledBenchMark.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.perf; 2 | 3 | import static java.lang.invoke.MethodHandles.lookup; 4 | import static java.lang.invoke.MethodType.methodType; 5 | 6 | import java.io.PrintStream; 7 | import java.lang.invoke.MethodHandle; 8 | import java.lang.invoke.MethodHandles; 9 | import java.lang.invoke.MethodHandles.Lookup; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import org.openjdk.jmh.annotations.Benchmark; 13 | import org.openjdk.jmh.annotations.BenchmarkMode; 14 | import org.openjdk.jmh.annotations.Fork; 15 | import org.openjdk.jmh.annotations.Measurement; 16 | import org.openjdk.jmh.annotations.Mode; 17 | import org.openjdk.jmh.annotations.OutputTimeUnit; 18 | import org.openjdk.jmh.annotations.Scope; 19 | import org.openjdk.jmh.annotations.State; 20 | import org.openjdk.jmh.annotations.Warmup; 21 | import org.openjdk.jmh.runner.Runner; 22 | import org.openjdk.jmh.runner.RunnerException; 23 | import org.openjdk.jmh.runner.options.Options; 24 | import org.openjdk.jmh.runner.options.OptionsBuilder; 25 | 26 | @SuppressWarnings("static-method") 27 | @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 28 | @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 29 | @Fork(3) 30 | @BenchmarkMode(Mode.AverageTime) 31 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 32 | @State(Scope.Benchmark) 33 | public class LoggerDisabledBenchMark { 34 | static class LevelLogger { 35 | enum Level { 36 | WARNING, DEBUG 37 | } 38 | 39 | final Level level; 40 | 41 | public LevelLogger(Level level) { 42 | this.level = level; 43 | } 44 | 45 | public boolean isDebugEnabled() { 46 | return level.compareTo(Level.DEBUG) >= 0; 47 | } 48 | 49 | public void debug(String message) { 50 | if (isDebugEnabled()) { 51 | System.out.println(message); 52 | } 53 | } 54 | } 55 | 56 | interface Configuration { 57 | public Object getFilter(); 58 | } 59 | static class ConfigurationImpl implements Configuration { 60 | private final Object filter; 61 | 62 | ConfigurationImpl(Object filter) { this.filter = filter; } 63 | 64 | @Override 65 | public Object getFilter() { 66 | return filter; 67 | } 68 | } 69 | 70 | interface LambdaLogger { 71 | enum Level { 72 | WARNING, DEBUG, TRACE; 73 | } 74 | 75 | default void debug(String message) { 76 | log(Level.DEBUG, message); 77 | } 78 | void log(Level level, String messsage); 79 | 80 | public static LambdaLogger getLogger(Class configClass) { 81 | MethodHandle mh = getLoggingMethodHandle(configClass); 82 | return (level, message) -> { 83 | try { 84 | mh.invokeExact(level, message); 85 | } catch (Throwable e) { 86 | throw new AssertionError(e); 87 | } 88 | }; 89 | /*return new LambdaLogger() { 90 | @Override 91 | public void log(Level level, String message) { 92 | try { 93 | mh.invokeExact(level, message); 94 | } catch (Throwable e) { 95 | throw new AssertionError(e); 96 | } 97 | } 98 | };*/ 99 | } 100 | 101 | 102 | @SuppressWarnings("unused") 103 | static boolean isWarning(Level l) { 104 | return l == Level.WARNING; 105 | } 106 | 107 | @SuppressWarnings("unused") 108 | static void empty(Level level, String message) { 109 | // do nothing 110 | } 111 | 112 | public static MethodHandle getLoggingMethodHandle(@SuppressWarnings("unused") Class configClass) { 113 | Lookup lookup = lookup(); 114 | MethodHandle mh; 115 | try { 116 | mh = lookup.findVirtual(PrintStream.class, "println", methodType(void.class, String.class)); 117 | } catch (NoSuchMethodException | IllegalAccessException e) { 118 | throw new AssertionError(e); 119 | } 120 | mh = mh.bindTo(System.out); 121 | mh = MethodHandles.dropArguments(mh, 0, Level.class); 122 | 123 | MethodHandle test; 124 | try { 125 | test = lookup.findStatic(LambdaLogger.class, "isWarning", methodType(boolean.class, Level.class)); 126 | } catch (NoSuchMethodException | IllegalAccessException e) { 127 | throw new AssertionError(e); 128 | } 129 | MethodHandle empty; 130 | try { 131 | empty = lookup.findStatic(LambdaLogger.class, "empty", methodType(void.class, Level.class, String.class)); 132 | } catch (NoSuchMethodException | IllegalAccessException e) { 133 | throw new RuntimeException(e); 134 | } 135 | return MethodHandles.guardWithTest(test, mh, empty); 136 | } 137 | } 138 | 139 | private static final org.apache.logging.log4j.Logger LOG4J2_LOGGER = 140 | org.apache.logging.log4j.LogManager.getLogger(LoggerDisabledBenchMark.class); 141 | private static final org.slf4j.Logger LOGBACK_LOGGER = 142 | org.slf4j.LoggerFactory.getLogger(LoggerDisabledLoopBenchMark.class); 143 | private static final java.util.logging.Logger JUL_LOGGER = 144 | java.util.logging.Logger.getLogger(LoggerDisabledLoopBenchMark.class.getName()); 145 | private static final com.google.common.flogger.FluentLogger FLUENT_LOGGER = 146 | com.google.common.flogger.FluentLogger.forEnclosingClass(); 147 | private static final LevelLogger LEVEL_LOGGER = 148 | new LevelLogger(LevelLogger.Level.WARNING); 149 | private static final LambdaLogger LAMBDA_LOGGER = 150 | LambdaLogger.getLogger(LoggerDisabledBenchMark.class); 151 | private static final com.github.forax.beautifullogger.Logger BEAUTIFUL_LOGGER = 152 | com.github.forax.beautifullogger.Logger.getLogger(); 153 | 154 | @Benchmark 155 | public void no_op() { 156 | // empty 157 | } 158 | 159 | @Benchmark 160 | public void level_disabled() { 161 | LEVEL_LOGGER.debug("should not be printed !"); 162 | } 163 | 164 | @Benchmark 165 | public void lambda_disabled() { 166 | LAMBDA_LOGGER.debug("should not be printed !"); 167 | } 168 | 169 | //FIXME 170 | // @Benchmark 171 | // public void log4j2_disabled_text() { 172 | // LOG4J2_LOGGER.debug("should not be printed !"); 173 | // } 174 | // 175 | // @Benchmark 176 | // public void log4j2_disabled_block() { 177 | // if (LOG4J2_LOGGER.isDebugEnabled()) { 178 | // LOG4J2_LOGGER.debug("should not be printed !"); 179 | // } 180 | // } 181 | // 182 | // @Benchmark 183 | // public void log4j2_disable_lambda() { 184 | // LOG4J2_LOGGER.debug(() -> "should not be printed !"); 185 | // } 186 | 187 | @Benchmark 188 | public void logback_disable_lambda() { 189 | LOGBACK_LOGGER.debug("should not be printed !"); 190 | } 191 | 192 | @Benchmark 193 | public void jul_disable_lambda() { 194 | JUL_LOGGER.fine("should not be printed !"); 195 | } 196 | 197 | @Benchmark 198 | public void flogger_disable() { 199 | FLUENT_LOGGER.atFine().log("should not be printed !"); 200 | } 201 | 202 | @Benchmark 203 | public void beautiful_logger_disabled() { 204 | BEAUTIFUL_LOGGER.debug(() -> "should not be printed !"); 205 | } 206 | 207 | public static void main(String[] args) throws RunnerException { 208 | Options opt = new OptionsBuilder().include(LoggerDisabledBenchMark.class.getName()).build(); 209 | new Runner(opt).run(); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/perf/LoggerDisabledLoopBenchMark.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.perf; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.util.Arrays; 5 | import java.util.Random; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import org.openjdk.jmh.annotations.Benchmark; 9 | import org.openjdk.jmh.annotations.BenchmarkMode; 10 | import org.openjdk.jmh.annotations.Fork; 11 | import org.openjdk.jmh.annotations.Measurement; 12 | import org.openjdk.jmh.annotations.Mode; 13 | import org.openjdk.jmh.annotations.OutputTimeUnit; 14 | import org.openjdk.jmh.annotations.Scope; 15 | import org.openjdk.jmh.annotations.Setup; 16 | import org.openjdk.jmh.annotations.State; 17 | import org.openjdk.jmh.annotations.Warmup; 18 | import org.openjdk.jmh.runner.Runner; 19 | import org.openjdk.jmh.runner.RunnerException; 20 | import org.openjdk.jmh.runner.options.Options; 21 | import org.openjdk.jmh.runner.options.OptionsBuilder; 22 | 23 | import static java.lang.invoke.MethodHandles.lookup; 24 | 25 | @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 26 | @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 27 | @Fork(3) 28 | @BenchmarkMode(Mode.AverageTime) 29 | @OutputTimeUnit(TimeUnit.MILLISECONDS) 30 | @State(Scope.Benchmark) 31 | public class LoggerDisabledLoopBenchMark { 32 | //FIXME 33 | //private static final org.apache.logging.log4j.Logger LOG4J2_LOGGER = 34 | // org.apache.logging.log4j.LogManager.getLogger(LoggerDisabledLoopBenchMark.class); 35 | private static final org.slf4j.Logger LOGBACK_LOGGER = 36 | org.slf4j.LoggerFactory.getLogger(LoggerDisabledLoopBenchMark.class); 37 | private static final java.util.logging.Logger JUL_LOGGER = 38 | java.util.logging.Logger.getLogger(LoggerDisabledLoopBenchMark.class.getName()); 39 | private static final com.google.common.flogger.FluentLogger FLUENT_LOGGER = 40 | com.google.common.flogger.FluentLogger.forEnclosingClass(); 41 | private static final com.github.forax.beautifullogger.Logger BEAUTIFUL_LOGGER = 42 | com.github.forax.beautifullogger.Logger.getLogger(); 43 | 44 | static final int ARRAY_SIZE = 1 << 20; 45 | final int[] array = new int[ARRAY_SIZE]; 46 | 47 | @Setup 48 | public void setup() { 49 | Random random = new Random(0); 50 | Arrays.setAll(array, i-> random.nextInt()); 51 | } 52 | 53 | @Benchmark 54 | public int empty_sum() { 55 | int sum = 0; 56 | for(int i = 0; i < array.length; i++) { 57 | sum += array[i]; 58 | } 59 | return sum; 60 | } 61 | 62 | //FIXME 63 | // @Benchmark 64 | // public int log4j2_sum() { 65 | // int sum = 0; 66 | // for(int i = 0; i < array.length; i++) { 67 | // sum += array[i]; 68 | // LOG4J2_LOGGER.debug("should not be printed !"); 69 | // } 70 | // return sum; 71 | // } 72 | // 73 | // @Benchmark 74 | // public int log4j2_foreach_sum() { 75 | // int sum = 0; 76 | // for(int value: array) { 77 | // sum += value; 78 | // LOG4J2_LOGGER.debug("should not be printed !"); 79 | // } 80 | // return sum; 81 | // } 82 | 83 | @Benchmark 84 | public int logback_sum() { 85 | int sum = 0; 86 | for(int i = 0; i < array.length; i++) { 87 | sum += array[i]; 88 | LOGBACK_LOGGER.debug("should not be printed !"); 89 | } 90 | return sum; 91 | } 92 | 93 | @Benchmark 94 | public int logback_foreach_sum() { 95 | int sum = 0; 96 | for(int value: array) { 97 | sum += value; 98 | LOGBACK_LOGGER.debug("should not be printed !"); 99 | } 100 | return sum; 101 | } 102 | 103 | @Benchmark 104 | public int jul_foreach_sum() { 105 | int sum = 0; 106 | for(int value: array) { 107 | sum += value; 108 | JUL_LOGGER.fine("should not be printed !"); 109 | } 110 | return sum; 111 | } 112 | 113 | @Benchmark 114 | public int jul_sum() { 115 | int sum = 0; 116 | for(int i = 0; i < array.length; i++) { 117 | sum += array[i]; 118 | JUL_LOGGER.fine("should not be printed !"); 119 | } 120 | return sum; 121 | } 122 | 123 | @Benchmark 124 | public int flogger_foreach_sum() { 125 | int sum = 0; 126 | for(int value: array) { 127 | sum += value; 128 | FLUENT_LOGGER.atFine().log("should not be printed !"); 129 | } 130 | return sum; 131 | } 132 | 133 | @Benchmark 134 | public int flogger_sum() { 135 | int sum = 0; 136 | for(int i = 0; i < array.length; i++) { 137 | sum += array[i]; 138 | FLUENT_LOGGER.atFine().log("should not be printed !"); 139 | } 140 | return sum; 141 | } 142 | 143 | @Benchmark 144 | public int beautiful_loggger_sum() { 145 | int sum = 0; 146 | for(int i = 0; i < array.length; i++) { 147 | sum += array[i]; 148 | BEAUTIFUL_LOGGER.debug(() -> "should not be printed !"); 149 | } 150 | return sum; 151 | } 152 | 153 | public static void main(String[] args) throws RunnerException { 154 | Options opt = new OptionsBuilder().include(LoggerDisabledLoopBenchMark.class.getName()).build(); 155 | new Runner(opt).run(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/test/java/com/github/forax/beautifullogger/perf/PatternBenchMark.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.beautifullogger.perf; 2 | 3 | /* 4 | import java.util.Random; 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.function.IntFunction; 7 | import java.util.function.Supplier; 8 | 9 | import org.openjdk.jmh.annotations.Benchmark; 10 | import org.openjdk.jmh.annotations.BenchmarkMode; 11 | import org.openjdk.jmh.annotations.Fork; 12 | import org.openjdk.jmh.annotations.Measurement; 13 | import org.openjdk.jmh.annotations.Mode; 14 | import org.openjdk.jmh.annotations.OutputTimeUnit; 15 | import org.openjdk.jmh.annotations.Scope; 16 | import org.openjdk.jmh.annotations.Setup; 17 | import org.openjdk.jmh.annotations.State; 18 | import org.openjdk.jmh.annotations.Warmup; 19 | import org.openjdk.jmh.runner.Runner; 20 | import org.openjdk.jmh.runner.RunnerException; 21 | import org.openjdk.jmh.runner.options.Options; 22 | import org.openjdk.jmh.runner.options.OptionsBuilder; 23 | 24 | @SuppressWarnings("static-method") 25 | @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 26 | @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 27 | @Fork(3) 28 | @BenchmarkMode(Mode.AverageTime) 29 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 30 | @State(Scope.Benchmark)*/ 31 | /*public class PatternBenchMark { 32 | static class SimpleLogger { 33 | enum Level { 34 | WARNING, DEBUG 35 | } 36 | 37 | private static final int DEBUG_LEVEL = Level.DEBUG.ordinal(); 38 | 39 | final int level; 40 | 41 | public SimpleLogger(Level level) { 42 | this.level = level.ordinal(); 43 | } 44 | 45 | public boolean isDebugEnabled() { 46 | return level >= DEBUG_LEVEL; 47 | } 48 | 49 | public void debug(String message) { 50 | if (isDebugEnabled()) { 51 | System.out.println(message); 52 | } 53 | } 54 | 55 | public void debug(String format, Object... args) { 56 | if (isDebugEnabled()) { 57 | System.out.println(String.format(format, args)); 58 | } 59 | } 60 | 61 | public void debug(Supplier supplier) { 62 | if (isDebugEnabled()) { 63 | System.out.println(supplier.get()); 64 | } 65 | } 66 | 67 | public void debug(int value, IntFunction fun) { 68 | if (isDebugEnabled()) { 69 | System.out.println(fun.apply(value)); 70 | } 71 | } 72 | } 73 | 74 | private static final SimpleLogger LOGGER = 75 | new SimpleLogger(SimpleLogger.Level.WARNING); 76 | 77 | private static final org.apache.logging.log4j.Logger LOGGER = 78 | org.apache.logging.log4j.LogManager.getLogger(LoggerDisabledBenchMark.class); 79 | 80 | private int value; 81 | 82 | @Setup 83 | public void setup() { 84 | value = new Random(0).nextInt(); 85 | } 86 | 87 | @Benchmark 88 | public void logger_concat() { 89 | LOGGER.debug("processor count " + value); 90 | } 91 | 92 | @Benchmark 93 | public void logger_builder() { 94 | LOGGER.debug(new StringBuilder().append("processor count ").append(value).toString()); 95 | } 96 | 97 | @Benchmark 98 | public void logger_lambda_supplier() { 99 | LOGGER.debug(() -> "processor count " + value); 100 | } 101 | 102 | @Benchmark 103 | public void logger_inner_class_supplier() { 104 | int value = this.value; 105 | LOGGER.debug(new Supplier() { 106 | @Override 107 | public String get() { 108 | return "processor count " + value; 109 | } 110 | }); 111 | } 112 | 113 | @Benchmark 114 | public void logger_lambda_function() { 115 | LOGGER.debug(value, _value -> "processor count " + _value); 116 | } 117 | 118 | @Benchmark 119 | public void logger_format() { 120 | LOGGER.debug("processor count %d", new Object[] { value }); 121 | } 122 | 123 | public static void main(String[] args) throws RunnerException { 124 | Options opt = new OptionsBuilder().include(PatternBenchMark.class.getName()).build(); 125 | new Runner(opt).run(); 126 | } 127 | }*/ 128 | -------------------------------------------------------------------------------- /src/test/resources/com.github.forax.beautifullogger.integration.log4j/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/com.github.forax.beautifullogger.integration.logback/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/com.github.forax.beautifullogger.integration.slf4j/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/com.github.forax.beautifullogger.perf/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/resources/com.github.forax.beautifullogger.perf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | --------------------------------------------------------------------------------