initial commit

This commit is contained in:
Jörg Prante 2024-07-05 22:23:39 +02:00
commit 81f3cc723d
246 changed files with 43251 additions and 0 deletions

21
.gitignore vendored Normal file
View file

@ -0,0 +1,21 @@
data
work
out
logs
/.idea
/target
/.settings
/.classpath
/.project
/.gradle
/plugins
/sessions
.DS_Store
*.iml
*~
.secret
build
**/*.key
**/*.crt
**/*.pkcs8
**/*.gz

202
LICENSE.txt Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
NOTICE.txt Normal file
View file

@ -0,0 +1,21 @@
This work is based on
JBoss LogManager https://github.com/jboss-logging/jboss-logmanager
Apache License 2.0
JBoss log4j2-jboss-logmanager
https://github.com/jboss-logging/log4j2-jboss-logmanager
Apache License 2.0
and
JBoss SLF4J LogManager
https://github.com/jboss-logging/slf4j-jboss-logmanager
Apache License 2.0
as of 1 July, 2024

35
build.gradle Normal file
View file

@ -0,0 +1,35 @@
plugins {
id 'maven-publish'
id 'signing'
id "io.github.gradle-nexus.publish-plugin" version "2.0.0-rc-1"
}
wrapper {
gradleVersion = libs.versions.gradle.get()
distributionType = Wrapper.DistributionType.BIN
}
ext {
user = 'joerg'
name = 'logging'
description = 'Logging for Java 21+, a reimplementation of JBoss LogManager'
inceptionYear = '2024'
url = 'https://xbib.org/' + user + '/' + name
scmUrl = 'https://xbib.org/' + user + '/' + name
scmConnection = 'scm:git:git://xbib.org/' + user + '/' + name + '.git'
scmDeveloperConnection = 'scm:git:ssh://forgejo@xbib.org:' + user + '/' + name + '.git'
issueManagementSystem = 'Github'
issueManagementUrl = ext.scmUrl + '/issues'
licenseName = 'The Apache License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
subprojects {
apply from: rootProject.file('gradle/repositories/maven.gradle')
apply from: rootProject.file('gradle/compile/java.gradle')
apply from: rootProject.file('gradle/test/junit5.gradle')
apply from: rootProject.file('gradle/publish/maven.gradle')
}
apply from: rootProject.file('gradle/publish/sonatype.gradle')
apply from: rootProject.file('gradle/publish/forgejo.gradle')

3
gradle.properties Normal file
View file

@ -0,0 +1,3 @@
group = org.xbib
name = logging
version = 0.0.1

View file

@ -0,0 +1,34 @@
apply plugin: 'groovy'
dependencies {
implementation "org.codehaus.groovy:groovy:${project.property('groovy.version')}:indy"
}
compileGroovy {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
compileTestGroovy {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks.withType(GroovyCompile) {
options.compilerArgs
if (!options.compilerArgs.contains("-processor")) {
options.compilerArgs << '-proc:none'
}
groovyOptions.optimizationOptions.indy = true
}
task groovydocJar(type: Jar, dependsOn: 'groovydoc') {
from groovydoc.destinationDir
archiveClassifier.set('javadoc')
}
configurations.all {
resolutionStrategy {
force "org.codehaus.groovy:groovy:${project.property('groovy.version')}:indy"
}
}

View file

@ -0,0 +1,47 @@
apply plugin: 'java-library'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
modularity.inferModulePath.set(true)
withSourcesJar()
withJavadocJar()
}
jar {
manifest {
attributes('Implementation-Version': project.version)
}
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
tasks.withType(JavaCompile).configureEach {
doFirst {
options.fork = true
options.forkOptions.jvmArgs += ['-Duser.language=en', '-Duser.country=US']
options.encoding = 'UTF-8'
// -classfile because of log4j2 issues "warning: Cannot find annotation method"
options.compilerArgs.add('-Xlint:all,-classfile')
options.compilerArgs.add("--module-version")
options.compilerArgs.add(project.version as String)
options.compilerArgs.add("--module-path")
options.compilerArgs.add(classpath.asPath)
classpath = files()
}
}
tasks.withType(Javadoc).configureEach {
doFirst {
options.addStringOption('Xdoclint:none', '-quiet')
options.encoding = 'UTF-8'
}
}
tasks.withType(JavaExec).configureEach {
doFirst {
jvmArguments.add("--module-path")
jvmArguments.add(classpath.asPath)
classpath = files()
}
}

View file

@ -0,0 +1,55 @@
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
configurations {
asciidoclet
}
dependencies {
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
}
asciidoctor {
backends 'html5'
outputDir = file("${rootProject.projectDir}/docs")
separateOutputDirs = false
attributes 'source-highlighter': 'coderay',
idprefix: '',
idseparator: '-',
toc: 'left',
doctype: 'book',
icons: 'font',
encoding: 'utf-8',
sectlink: true,
sectanchors: true,
linkattrs: true,
imagesdir: 'img',
stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css"
}
/*javadoc {
options.docletpath = configurations.asciidoclet.files.asType(List)
options.doclet = 'org.asciidoctor.Asciidoclet'
//options.overview = "src/docs/asciidoclet/overview.adoc"
options.addStringOption "-base-dir", "${projectDir}"
options.addStringOption "-attribute",
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
configure(options) {
noTimestamp = true
}
}*/
/*javadoc {
options.docletpath = configurations.asciidoclet.files.asType(List)
options.doclet = 'org.asciidoctor.Asciidoclet'
options.overview = "${rootProject.projectDir}/src/docs/asciidoclet/overview.adoc"
options.addStringOption "-base-dir", "${projectDir}"
options.addStringOption "-attribute",
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
options.destinationDirectory(file("${projectDir}/docs/javadoc"))
configure(options) {
noTimestamp = true
}
}*/

13
gradle/ide/idea.gradle Normal file
View file

@ -0,0 +1,13 @@
apply plugin: 'idea'
idea {
module {
outputDir file('build/classes/java/main')
testOutputDir file('build/classes/java/test')
}
}
if (project.convention.findPlugin(JavaPluginConvention)) {
//sourceSets.main.output.classesDirs = file("build/classes/java/main")
//sourceSets.test.output.classesDirs = file("build/classes/java/test")
}

View file

@ -0,0 +1,16 @@
if (project.hasProperty('forgeJoToken')) {
publishing {
repositories {
maven {
url 'https://xbib.org/api/packages/joerg/maven'
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = "token ${project.property('forgeJoToken')}"
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
}
}

27
gradle/publish/ivy.gradle Normal file
View file

@ -0,0 +1,27 @@
apply plugin: 'ivy-publish'
publishing {
repositories {
ivy {
url = "https://xbib.org/repo"
}
}
publications {
ivy(IvyPublication) {
from components.java
descriptor {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
author {
name = 'Jörg Prante'
url = 'http://example.com/users/jane'
}
descriptor.description {
text = rootProject.ext.description
}
}
}
}
}

View file

@ -0,0 +1,51 @@
publishing {
publications {
"${project.name}"(MavenPublication) {
from components.java
pom {
artifactId = project.name
name = project.name
description = rootProject.ext.description
url = rootProject.ext.url
inceptionYear = rootProject.ext.inceptionYear
packaging = 'jar'
organization {
name = 'xbib'
url = 'https://xbib.org'
}
developers {
developer {
id = 'jprante'
name = 'Jörg Prante'
email = 'joergprante@gmail.com'
url = 'https://github.com/jprante'
}
}
scm {
url = rootProject.ext.scmUrl
connection = rootProject.ext.scmConnection
developerConnection = rootProject.ext.scmDeveloperConnection
}
issueManagement {
system = rootProject.ext.issueManagementSystem
url = rootProject.ext.issueManagementUrl
}
licenses {
license {
name = rootProject.ext.licenseName
url = rootProject.ext.licenseUrl
distribution = 'repo'
}
}
}
}
}
}
if (project.hasProperty("signing.keyId")) {
apply plugin: 'signing'
signing {
sign publishing.publications."${project.name}"
}
}

View file

@ -0,0 +1,11 @@
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
nexusPublishing {
repositories {
sonatype {
username = project.property('ossrhUsername')
password = project.property('ossrhPassword')
packageGroup = "org.xbib"
}
}
}
}

View file

@ -0,0 +1,19 @@
apply plugin: 'checkstyle'
tasks.withType(Checkstyle) {
ignoreFailures = true
reports {
xml.getRequired().set(true)
html.getRequired().set(true)
}
}
checkstyle {
configFile = rootProject.file('gradle/quality/checkstyle.xml')
ignoreFailures = true
showViolations = true
checkstyleMain {
source = sourceSets.main.allSource
}
}

View file

@ -0,0 +1,333 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!-- This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html -->
<module name="Checker">
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value=".*(Example|Test|module-info)(\$.*)?"/>
</module>
<module name="FileTabCharacter">
<!-- Checks that there are no tab characters in the file.
-->
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<module name="RegexpSingleline">
<!-- Checks that FIXME is not used in comments. TODO is preferred.
-->
<property name="format" value="((//.*)|(\*.*))FIXME" />
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="RegexpSingleline">
<!-- Checks that TODOs are named. (Actually, just that they are followed
by an open paren.)
-->
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="JavadocPackage">
<!-- Checks that each Java package has a Javadoc file used for commenting.
Only allows a package-info.java, not package.html. -->
</module>
<!-- All Java AST specific tests live under TreeWalker module. -->
<module name="TreeWalker">
<!--
IMPORT CHECKS
-->
<module name="RedundantImport">
<!-- Checks for redundant import statements. -->
<property name="severity" value="error"/>
</module>
<module name="ImportOrder">
<!-- Checks for out of order import statements. -->
<property name="severity" value="warning"/>
<!-- <property name="tokens" value="IMPORT, STATIC_IMPORT"/> -->
<property name="separated" value="false"/>
<property name="groups" value="*"/>
<!-- <property name="option" value="above"/> -->
<property name="sortStaticImportsAlphabetically" value="true"/>
</module>
<module name="CustomImportOrder">
<!-- <property name="customImportOrderRules" value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/> -->
<!-- <property name="specialImportsRegExp" value="^javax\."/> -->
<!-- <property name="standardPackageRegExp" value="^java\."/> -->
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="false"/>
</module>
<!--
JAVADOC CHECKS
-->
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod">
<property name="accessModifiers" value="protected"/>
<property name="severity" value="warning"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
</module>
<module name="JavadocType">
<property name="scope" value="protected"/>
<property name="severity" value="error"/>
</module>
<module name="JavadocStyle">
<property name="severity" value="warning"/>
</module>
<!--
NAMING CHECKS
-->
<!-- Item 38 - Adhere to generally accepted naming conventions -->
<module name="PackageName">
<!-- Validates identifiers for package names against the
supplied expression. -->
<!-- Here the default checkstyle rule restricts package name parts to
seven characters, this is not in line with common practice at Google.
-->
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
<property name="severity" value="warning"/>
</module>
<module name="TypeNameCheck">
<!-- Validates static, final fields against the
expression "^[A-Z][a-zA-Z0-9]*$". -->
<metadata name="altname" value="TypeName"/>
<property name="severity" value="warning"/>
</module>
<module name="ConstantNameCheck">
<!-- Validates non-private, static, final fields against the supplied
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
<metadata name="altname" value="ConstantName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="false"/>
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
<message key="name.invalidPattern"
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
<property name="severity" value="warning"/>
</module>
<module name="StaticVariableNameCheck">
<!-- Validates static, non-final fields against the supplied
expression "^[a-z][a-zA-Z0-9]*_?$". -->
<metadata name="altname" value="StaticVariableName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
<property name="severity" value="warning"/>
</module>
<module name="MemberNameCheck">
<!-- Validates non-static members against the supplied expression. -->
<metadata name="altname" value="MemberName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<property name="severity" value="warning"/>
</module>
<module name="MethodNameCheck">
<!-- Validates identifiers for method names. -->
<metadata name="altname" value="MethodName"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
<property name="severity" value="warning"/>
</module>
<module name="ParameterName">
<!-- Validates identifiers for method parameters against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalFinalVariableName">
<!-- Validates identifiers for local final variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalVariableName">
<!-- Validates identifiers for local variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<!--
LENGTH and CODING CHECKS
-->
<module name="LeftCurly">
<!-- Checks for placement of the left curly brace ('{'). -->
<property name="severity" value="warning"/>
</module>
<module name="RightCurly">
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
the same line. e.g., the following example is fine:
<pre>
if {
...
} else
</pre>
-->
<!-- This next example is not fine:
<pre>
if {
...
}
else
</pre>
-->
<property name="option" value="same"/>
<property name="severity" value="warning"/>
</module>
<!-- Checks for braces around if and else blocks -->
<module name="NeedBraces">
<property name="severity" value="warning"/>
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
</module>
<module name="UpperEll">
<!-- Checks that long constants are defined with an upper ell.-->
<property name="severity" value="error"/>
</module>
<module name="FallThrough">
<!-- Warn about falling through to the next case statement. Similar to
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
some other variants which we don't publicized to promote consistency).
-->
<property name="reliefPattern"
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
<property name="severity" value="error"/>
</module>
<!--
MODIFIERS CHECKS
-->
<module name="ModifierOrder">
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
8.4.3. The prescribed order is:
public, protected, private, abstract, static, final, transient, volatile,
synchronized, native, strictfp
-->
</module>
<!--
WHITESPACE CHECKS
-->
<module name="WhitespaceAround">
<!-- Checks that various tokens are surrounded by whitespace.
This includes most binary operators and keywords followed
by regular or curly braces.
-->
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
<property name="severity" value="error"/>
</module>
<module name="WhitespaceAfter">
<!-- Checks that commas, semicolons and typecasts are followed by
whitespace.
-->
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
</module>
<module name="NoWhitespaceAfter">
<!-- Checks that there is no whitespace after various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
UNARY_PLUS"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="NoWhitespaceBefore">
<!-- Checks that there is no whitespace before various unary operators.
Linebreaks are allowed.
-->
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="ParenPad">
<!-- Checks that there is no whitespace before close parens or after
open parens.
-->
<property name="severity" value="warning"/>
</module>
</module>
<module name="LineLength">
<!-- Checks if a line is too long. -->
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
<property name="severity" value="error"/>
<!--
The default ignore pattern exempts the following elements:
- import statements
- long URLs inside comments
-->
<property name="ignorePattern"
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
</module>
</module>

View file

@ -0,0 +1,11 @@
cyclonedxBom {
includeConfigs = [ 'runtimeClasspath' ]
skipConfigs = [ 'compileClasspath', 'testCompileClasspath' ]
projectType = "library"
schemaVersion = "1.4"
destination = file("build/reports")
outputName = "bom"
outputFormat = "json"
includeBomSerialNumber = true
componentVersion = "2.0.0"
}

17
gradle/quality/pmd.gradle Normal file
View file

@ -0,0 +1,17 @@
apply plugin: 'pmd'
tasks.withType(Pmd) {
ignoreFailures = true
reports {
xml.getRequired().set(true)
html.getRequired().set(true)
}
}
pmd {
ignoreFailures = true
consoleOutput = false
toolVersion = "6.51.0"
ruleSetFiles = rootProject.files('gradle/quality/pmd/category/java/bestpractices.xml')
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
rulesets.filenames=\
category/java/bestpractices.xml,\
category/java/codestyle.xml,\
category/java/design.xml,\
category/java/documentation.xml,\
category/java/errorprone.xml,\
category/java/multithreading.xml,\
category/java/performance.xml,\
category/java/security.xml

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,144 @@
<?xml version="1.0"?>
<ruleset name="Documentation"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules that are related to code documentation.
</description>
<rule name="CommentContent"
since="5.0"
message="Invalid words or phrases found"
class="net.sourceforge.pmd.lang.java.rule.documentation.CommentContentRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#commentcontent">
<description>
A rule for the politically correct... we don't want to offend anyone.
</description>
<priority>3</priority>
<example>
<![CDATA[
//OMG, this is horrible, Bob is an idiot !!!
]]>
</example>
</rule>
<rule name="CommentRequired"
since="5.1"
message="Comment is required"
class="net.sourceforge.pmd.lang.java.rule.documentation.CommentRequiredRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#commentrequired">
<description>
Denotes whether comments are required (or unwanted) for specific language elements.
</description>
<priority>3</priority>
<example>
<![CDATA[
/**
*
*
* @author Jon Doe
*/
]]>
</example>
</rule>
<rule name="CommentSize"
since="5.0"
message="Comment is too large"
class="net.sourceforge.pmd.lang.java.rule.documentation.CommentSizeRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#commentsize">
<description>
Determines whether the dimensions of non-header comments found are within the specified limits.
</description>
<priority>3</priority>
<example>
<![CDATA[
/**
*
* too many lines!
*
*
*
*
*
*
*
*
*
*
*
*
*/
]]>
</example>
</rule>
<rule name="UncommentedEmptyConstructor"
language="java"
since="3.4"
message="Document empty constructor"
class="net.sourceforge.pmd.lang.rule.XPathRule"
typeResolution="true"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#uncommentedemptyconstructor">
<description>
Uncommented Empty Constructor finds instances where a constructor does not
contain statements, but there is no comment. By explicitly commenting empty
constructors it is easier to distinguish between intentional (commented)
and unintentional empty constructors.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>
<![CDATA[
//ConstructorDeclaration[@Private='false']
[count(BlockStatement) = 0 and ($ignoreExplicitConstructorInvocation = 'true' or not(ExplicitConstructorInvocation)) and @containsComment = 'false']
[not(../Annotation/MarkerAnnotation/Name[pmd-java:typeIs('javax.inject.Inject')])]
]]>
</value>
</property>
<property name="ignoreExplicitConstructorInvocation" type="Boolean" description="Ignore explicit constructor invocation when deciding whether constructor is empty or not" value="false"/>
</properties>
<example>
<![CDATA[
public Foo() {
// This constructor is intentionally empty. Nothing special is needed here.
}
]]>
</example>
</rule>
<rule name="UncommentedEmptyMethodBody"
language="java"
since="3.4"
message="Document empty method body"
class="net.sourceforge.pmd.lang.rule.XPathRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#uncommentedemptymethodbody">
<description>
Uncommented Empty Method Body finds instances where a method body does not contain
statements, but there is no comment. By explicitly commenting empty method bodies
it is easier to distinguish between intentional (commented) and unintentional
empty methods.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>
<![CDATA[
//MethodDeclaration/Block[count(BlockStatement) = 0 and @containsComment = 'false']
]]>
</value>
</property>
</properties>
<example>
<![CDATA[
public void doSomething() {
}
]]>
</example>
</rule>
</ruleset>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,393 @@
<?xml version="1.0"?>
<ruleset name="Multithreading"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules that flag issues when dealing with multiple threads of execution.
</description>
<rule name="AvoidSynchronizedAtMethodLevel"
language="java"
since="3.0"
message="Use block level rather than method level synchronization"
class="net.sourceforge.pmd.lang.rule.XPathRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#avoidsynchronizedatmethodlevel">
<description>
Method-level synchronization can cause problems when new code is added to the method.
Block-level synchronization helps to ensure that only the code that needs synchronization
gets it.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>//MethodDeclaration[@Synchronized='true']</value>
</property>
</properties>
<example>
<![CDATA[
public class Foo {
// Try to avoid this:
synchronized void foo() {
}
// Prefer this:
void bar() {
synchronized(this) {
}
}
// Try to avoid this for static methods:
static synchronized void fooStatic() {
}
// Prefer this:
static void barStatic() {
synchronized(Foo.class) {
}
}
}
]]>
</example>
</rule>
<rule name="AvoidThreadGroup"
language="java"
since="3.6"
message="Avoid using java.lang.ThreadGroup; it is not thread safe"
class="net.sourceforge.pmd.lang.rule.XPathRule"
typeResolution="true"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#avoidthreadgroup">
<description>
Avoid using java.lang.ThreadGroup; although it is intended to be used in a threaded environment
it contains methods that are not thread-safe.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>
<![CDATA[
//AllocationExpression/ClassOrInterfaceType[pmd-java:typeIs('java.lang.ThreadGroup')]|
//PrimarySuffix[contains(@Image, 'getThreadGroup')]
]]>
</value>
</property>
</properties>
<example>
<![CDATA[
public class Bar {
void buz() {
ThreadGroup tg = new ThreadGroup("My threadgroup");
tg = new ThreadGroup(tg, "my thread group");
tg = Thread.currentThread().getThreadGroup();
tg = System.getSecurityManager().getThreadGroup();
}
}
]]>
</example>
</rule>
<rule name="AvoidUsingVolatile"
language="java"
since="4.1"
class="net.sourceforge.pmd.lang.rule.XPathRule"
message="Use of modifier volatile is not recommended."
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#avoidusingvolatile">
<description>
Use of the keyword 'volatile' is generally used to fine tune a Java application, and therefore, requires
a good expertise of the Java Memory Model. Moreover, its range of action is somewhat misknown. Therefore,
the volatile keyword should not be used for maintenance purpose and portability.
</description>
<priority>2</priority>
<properties>
<property name="xpath">
<value>//FieldDeclaration[contains(@Volatile,'true')]</value>
</property>
</properties>
<example>
<![CDATA[
public class ThrDeux {
private volatile String var1; // not suggested
private String var2; // preferred
}
]]>
</example>
</rule>
<rule name="DoNotUseThreads"
language="java"
since="4.1"
class="net.sourceforge.pmd.lang.rule.XPathRule"
message="To be compliant to J2EE, a webapp should not use any thread."
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#donotusethreads">
<description>
The J2EE specification explicitly forbids the use of threads.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>//ClassOrInterfaceType[@Image = 'Thread' or @Image = 'Runnable']</value>
</property>
</properties>
<example>
<![CDATA[
// This is not allowed
public class UsingThread extends Thread {
}
// Neither this,
public class OtherThread implements Runnable {
// Nor this ...
public void methode() {
Runnable thread = new Thread(); thread.run();
}
}
]]>
</example>
</rule>
<rule name="DontCallThreadRun"
language="java"
since="4.3"
message="Don't call Thread.run() explicitly, use Thread.start()"
class="net.sourceforge.pmd.lang.rule.XPathRule"
typeResolution="true"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#dontcallthreadrun">
<description>
Explicitly calling Thread.run() method will execute in the caller's thread of control. Instead, call Thread.start() for the intended behavior.
</description>
<priority>4</priority>
<properties>
<property name="xpath">
<value>
<![CDATA[
//StatementExpression/PrimaryExpression
[
PrimaryPrefix
[
./Name[ends-with(@Image, '.run') or @Image = 'run']
and substring-before(Name/@Image, '.') =//VariableDeclarator/VariableDeclaratorId/@Image
[../../../Type/ReferenceType/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')]]
or (./AllocationExpression/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')]
and ../PrimarySuffix[@Image = 'run'])
]
]
]]>
</value>
</property>
</properties>
<example>
<![CDATA[
Thread t = new Thread();
t.run(); // use t.start() instead
new Thread().run(); // same violation
]]>
</example>
</rule>
<rule name="DoubleCheckedLocking"
language="java"
since="1.04"
message="Double checked locking is not thread safe in Java."
class="net.sourceforge.pmd.lang.java.rule.multithreading.DoubleCheckedLockingRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#doublecheckedlocking">
<description>
Partially created objects can be returned by the Double Checked Locking pattern when used in Java.
An optimizing JRE may assign a reference to the baz variable before it calls the constructor of the object the
reference points to.
Note: With Java 5, you can make Double checked locking work, if you declare the variable to be `volatile`.
For more details refer to: &lt;http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html>
or &lt;http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html>
</description>
<priority>1</priority>
<example>
<![CDATA[
public class Foo {
/*volatile */ Object baz = null; // fix for Java5 and later: volatile
Object bar() {
if (baz == null) { // baz may be non-null yet not fully created
synchronized(this) {
if (baz == null) {
baz = new Object();
}
}
}
return baz;
}
}
]]>
</example>
</rule>
<rule name="NonThreadSafeSingleton"
since="3.4"
message="Singleton is not thread safe"
class="net.sourceforge.pmd.lang.java.rule.multithreading.NonThreadSafeSingletonRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#nonthreadsafesingleton">
<description>
Non-thread safe singletons can result in bad state changes. Eliminate
static singletons if possible by instantiating the object directly. Static
singletons are usually not needed as only a single instance exists anyway.
Other possible fixes are to synchronize the entire method or to use an
[initialize-on-demand holder class](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom).
Refrain from using the double-checked locking pattern. The Java Memory Model doesn't
guarantee it to work unless the variable is declared as `volatile`, adding an uneeded
performance penalty. [Reference](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)
See Effective Java, item 48.
</description>
<priority>3</priority>
<example>
<![CDATA[
private static Foo foo = null;
//multiple simultaneous callers may see partially initialized objects
public static Foo getFoo() {
if (foo==null) {
foo = new Foo();
}
return foo;
}
]]>
</example>
</rule>
<rule name="UnsynchronizedStaticDateFormatter"
since="3.6"
deprecated="true"
message="Static DateFormatter objects should be accessed in a synchronized manner"
class="net.sourceforge.pmd.lang.java.rule.multithreading.UnsynchronizedStaticDateFormatterRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#unsynchronizedstaticdateformatter">
<description>
SimpleDateFormat instances are not synchronized. Sun recommends using separate format instances
for each thread. If multiple threads must access a static formatter, the formatter must be
synchronized either on method or block level.
This rule has been deprecated in favor of the rule {% rule UnsynchronizedStaticFormatter %}.
</description>
<priority>3</priority>
<example>
<![CDATA[
public class Foo {
private static final SimpleDateFormat sdf = new SimpleDateFormat();
void bar() {
sdf.format(); // poor, no thread-safety
}
synchronized void foo() {
sdf.format(); // preferred
}
}
]]>
</example>
</rule>
<rule name="UnsynchronizedStaticFormatter"
since="6.11.0"
message="Static Formatter objects should be accessed in a synchronized manner"
class="net.sourceforge.pmd.lang.java.rule.multithreading.UnsynchronizedStaticFormatterRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#unsynchronizedstaticformatter">
<description>
Instances of `java.text.Format` are generally not synchronized.
Sun recommends using separate format instances for each thread.
If multiple threads must access a static formatter, the formatter must be
synchronized either on method or block level.
</description>
<priority>3</priority>
<example>
<![CDATA[
public class Foo {
private static final SimpleDateFormat sdf = new SimpleDateFormat();
void bar() {
sdf.format(); // poor, no thread-safety
}
synchronized void foo() {
sdf.format(); // preferred
}
}
]]>
</example>
</rule>
<rule name="UseConcurrentHashMap"
language="java"
minimumLanguageVersion="1.5"
since="4.2.6"
message="If you run in Java5 or newer and have concurrent access, you should use the ConcurrentHashMap implementation"
class="net.sourceforge.pmd.lang.rule.XPathRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#useconcurrenthashmap">
<description>
Since Java5 brought a new implementation of the Map designed for multi-threaded access, you can
perform efficient map reads without blocking other threads.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>
<![CDATA[
//Type[../VariableDeclarator/VariableInitializer//AllocationExpression/ClassOrInterfaceType[@Image != 'ConcurrentHashMap']]
/ReferenceType/ClassOrInterfaceType[@Image = 'Map']
]]>
</value>
</property>
</properties>
<example>
<![CDATA[
public class ConcurrentApp {
public void getMyInstance() {
Map map1 = new HashMap(); // fine for single-threaded access
Map map2 = new ConcurrentHashMap(); // preferred for use with multiple threads
// the following case will be ignored by this rule
Map map3 = someModule.methodThatReturnMap(); // might be OK, if the returned map is already thread-safe
}
}
]]>
</example>
</rule>
<rule name="UseNotifyAllInsteadOfNotify"
language="java"
since="3.0"
message="Call Thread.notifyAll() rather than Thread.notify()"
class="net.sourceforge.pmd.lang.rule.XPathRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#usenotifyallinsteadofnotify">
<description>
Thread.notify() awakens a thread monitoring the object. If more than one thread is monitoring, then only
one is chosen. The thread chosen is arbitrary; thus its usually safer to call notifyAll() instead.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>
<![CDATA[
//StatementExpression/PrimaryExpression
[PrimarySuffix/Arguments[@ArgumentCount = '0']]
[
PrimaryPrefix[
./Name[@Image='notify' or ends-with(@Image,'.notify')]
or ../PrimarySuffix/@Image='notify'
or (./AllocationExpression and ../PrimarySuffix[@Image='notify'])
]
]
]]>
</value>
</property>
</properties>
<example>
<![CDATA[
void bar() {
x.notify();
// If many threads are monitoring x, only one (and you won't know which) will be notified.
// use instead:
x.notifyAll();
}
]]>
</example>
</rule>
</ruleset>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,65 @@
<?xml version="1.0"?>
<ruleset name="Security" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Rules that flag potential security flaws.
</description>
<rule name="HardCodedCryptoKey"
since="6.4.0"
message="Do not use hard coded encryption keys"
class="net.sourceforge.pmd.lang.java.rule.security.HardCodedCryptoKeyRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_security.html#hardcodedcryptokey">
<description>
Do not use hard coded values for cryptographic operations. Please store keys outside of source code.
</description>
<priority>3</priority>
<example>
<![CDATA[
public class Foo {
void good() {
SecretKeySpec secretKeySpec = new SecretKeySpec(Properties.getKey(), "AES");
}
void bad() {
SecretKeySpec secretKeySpec = new SecretKeySpec("my secret here".getBytes(), "AES");
}
}
]]>
</example>
</rule>
<rule name="InsecureCryptoIv"
since="6.3.0"
message="Do not use hard coded initialization vector in crypto operations"
class="net.sourceforge.pmd.lang.java.rule.security.InsecureCryptoIvRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_security.html#insecurecryptoiv">
<description>
Do not use hard coded initialization vector in cryptographic operations. Please use a randomly generated IV.
</description>
<priority>3</priority>
<example>
<![CDATA[
public class Foo {
void good() {
SecureRandom random = new SecureRandom();
byte iv[] = new byte[16];
random.nextBytes(bytes);
}
void bad() {
byte[] iv = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, };
}
void alsoBad() {
byte[] iv = "secret iv in here".getBytes();
}
}
]]>
</example>
</rule>
</ruleset>

View file

@ -0,0 +1,37 @@
subprojects {
sonarqube {
properties {
property "sonar.projectName", "${project.group} ${project.name}"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.tests", "src/test/java"
property "sonar.scm.provider", "git"
property "sonar.junit.reportsPath", "build/test-results/test/"
}
}
tasks.withType(Pmd) {
ignoreFailures = true
reports {
xml.enabled = true
html.enabled = true
}
}
spotbugs {
effort = "max"
reportLevel = "low"
//includeFilter = file("findbugs-exclude.xml")
}
tasks.withType(com.github.spotbugs.SpotBugsTask) {
ignoreFailures = true
reports {
xml.enabled = false
html.enabled = true
}
}
}

View file

@ -0,0 +1,15 @@
apply plugin: 'com.github.spotbugs'
spotbugs {
effort = "max"
reportLevel = "low"
ignoreFailures = true
}
spotbugsMain {
reports {
xml.getRequired().set(false)
html.getRequired().set(true)
}
}

View file

@ -0,0 +1,4 @@
repositories {
mavenLocal()
mavenCentral()
}

29
gradle/test/junit5.gradle Normal file
View file

@ -0,0 +1,29 @@
dependencies {
testImplementation testLibs.junit.jupiter.api
testImplementation testLibs.hamcrest
testRuntimeOnly testLibs.junit.jupiter.engine
testRuntimeOnly testLibs.junit.jupiter.platform.launcher
}
test {
useJUnitPlatform {
filter {
includeTestsMatching "*Test"
includeTestsMatching "*Tests"
excludeTestsMatching "*IT"
}
}
failFast = false
testLogging {
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
}
afterSuite { desc, result ->
if (!desc.parent) {
println "\nTest result: ${result.resultType}"
println "Test summary: ${result.testCount} tests, " +
"${result.successfulTestCount} succeeded, " +
"${result.failedTestCount} failed, " +
"${result.skippedTestCount} skipped"
}
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
gradlew vendored Executable file
View file

@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
gradlew.bat vendored Normal file
View file

@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1,16 @@
dependencies {
api project(':logging')
api libs.log4j.core
}
def moduleName = 'org.xbib.logging.log4j.test'
def patchArgs = ['--patch-module', "$moduleName=" + files(sourceSets.test.resources.srcDirs).asPath ]
tasks.named('compileTestJava') {
options.compilerArgs += patchArgs
}
tasks.named('test') {
jvmArgs += patchArgs
}

View file

@ -0,0 +1,9 @@
import org.apache.logging.log4j.spi.Provider;
import org.xbib.logging.log4j.XbibProvider;
module org.xbib.logging.adapter.log4j {
requires transitive org.apache.logging.log4j;
requires transitive org.xbib.logging;
exports org.xbib.logging.log4j;
provides Provider with XbibProvider;
}

View file

@ -0,0 +1,94 @@
package org.xbib.logging.log4j;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.Level;
/**
* A utility to translate levels.
*/
public class LevelTranslator {
private static final Level DEFAULT_LOG4J_LEVEL = Level.DEBUG;
private static final org.xbib.logging.Level DEFAULT_LEVEL = org.xbib.logging.Level.DEBUG;
private final Map<Integer, Level> julToLog4j = new HashMap<>();
private final Map<Integer, java.util.logging.Level> log4jToJul = new HashMap<>();
private static class Holder {
static final LevelTranslator INSTANCE = new LevelTranslator();
}
private LevelTranslator() {
// Add JUL levels
julToLog4j.put(java.util.logging.Level.FINEST.intValue(), Level.TRACE);
// This has a intValue() of 700 which is really between INFO and DEBUG, we'll default to DEBUG
julToLog4j.put(java.util.logging.Level.CONFIG.intValue(), Level.DEBUG);
// Note these should be added last to override any values that match
julToLog4j.put(org.xbib.logging.Level.ALL.intValue(), Level.ALL);
julToLog4j.put(org.xbib.logging.Level.TRACE.intValue(), Level.TRACE);
julToLog4j.put(org.xbib.logging.Level.DEBUG.intValue(), Level.DEBUG);
julToLog4j.put(org.xbib.logging.Level.INFO.intValue(), Level.INFO);
julToLog4j.put(org.xbib.logging.Level.WARN.intValue(), Level.WARN);
julToLog4j.put(org.xbib.logging.Level.ERROR.intValue(), Level.ERROR);
julToLog4j.put(org.xbib.logging.Level.FATAL.intValue(), Level.FATAL);
julToLog4j.put(org.xbib.logging.Level.OFF.intValue(), Level.OFF);
log4jToJul.put(Level.ALL.intLevel(), org.xbib.logging.Level.ALL);
log4jToJul.put(Level.TRACE.intLevel(), org.xbib.logging.Level.TRACE);
log4jToJul.put(Level.DEBUG.intLevel(), org.xbib.logging.Level.DEBUG);
log4jToJul.put(Level.INFO.intLevel(), org.xbib.logging.Level.INFO);
log4jToJul.put(Level.WARN.intLevel(), org.xbib.logging.Level.WARN);
log4jToJul.put(Level.ERROR.intLevel(), org.xbib.logging.Level.ERROR);
log4jToJul.put(Level.FATAL.intLevel(), org.xbib.logging.Level.FATAL);
log4jToJul.put(Level.OFF.intLevel(), org.xbib.logging.Level.OFF);
}
/**
* Returns an instance of the level translator.
*
* @return an instance
*/
public static LevelTranslator getInstance() {
return Holder.INSTANCE;
}
/**
* Translates a {@linkplain Level log4j level} to a {@linkplain java.util.logging.Level JUL level}.
*
* @param level the log4j level
*
* @return the closest match of a JUL level
*/
public java.util.logging.Level translateLevel(final Level level) {
final java.util.logging.Level result = level == null ? null : log4jToJul.get(level.intLevel());
return result == null ? DEFAULT_LEVEL : result;
}
/**
* Translates a {@linkplain java.util.logging.Level JUL level} to a {@linkplain Level log4j level}.
*
* @param level the JUL level
*
* @return the log4j level
*/
public Level translateLevel(final java.util.logging.Level level) {
return level == null ? DEFAULT_LOG4J_LEVEL : translateLevel(level.intValue());
}
/**
* Translates a {@linkplain java.util.logging.Level#intValue()} JUL level} to a {@linkplain Level log4j level}.
*
* @param level the JUL level int value
*
* @return the log4j level
*/
public Level translateLevel(final int level) {
final Level result = julToLog4j.get(level);
return result == null ? DEFAULT_LOG4J_LEVEL : result;
}
}

View file

@ -0,0 +1,60 @@
package org.xbib.logging.log4j;
import java.util.Map;
import org.apache.logging.log4j.spi.ThreadContextMap;
import org.xbib.logging.MDC;
/**
* A {@link ThreadContextMap} implementation which delegates to {@link MDC}.
*/
public class ThreadContextMDCMap implements ThreadContextMap {
public ThreadContextMDCMap() {
}
@Override
public void clear() {
MDC.clear();
}
@Override
public boolean containsKey(final String key) {
return MDC.get(key) != null;
}
@Override
public String get(final String key) {
return MDC.get(key);
}
@Override
public Map<String, String> getCopy() {
return MDC.copy();
}
@Override
public Map<String, String> getImmutableMapOrNull() {
final Map<String, String> copy = MDC.copy();
return copy.isEmpty() ? null : Map.copyOf(copy);
}
@Override
public boolean isEmpty() {
return MDC.isEmpty();
}
@Override
public void put(final String key, final String value) {
if (value == null) {
MDC.remove(key);
} else {
MDC.put(key, value);
}
}
@Override
public void remove(final String key) {
MDC.remove(key);
}
}

View file

@ -0,0 +1,161 @@
package org.xbib.logging.log4j;
import java.util.Collections;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.xbib.logging.ExtLogRecord;
/**
* An implementation of a log4j2 {@linkplain org.apache.logging.log4j.Logger logger} that delegates
* to a LogManager logger.
* <p>
* Only the {@linkplain Level level} is used to determine the result {@code isEnabled()} methods. All other parameters
* are ignored.
* </p>
*/
@SuppressWarnings("serial")
class XbibLogger extends AbstractLogger {
private transient final org.xbib.logging.Logger logger;
private transient final LevelTranslator levelTranslator;
XbibLogger(final org.xbib.logging.Logger logger, final MessageFactory messageFactory) {
super(logger.getName(), messageFactory);
this.logger = logger;
this.levelTranslator = LevelTranslator.getInstance();
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1,
final Object p2) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1,
final Object p2, final Object p3) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1,
final Object p2, final Object p3, final Object p4) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1,
final Object p2, final Object p3, final Object p4, final Object p5) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1,
final Object p2, final Object p3, final Object p4, final Object p5, final Object p6) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1,
final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1,
final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7,
final Object p8) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1,
final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7,
final Object p8, final Object p9) {
return logger.isLoggable(levelTranslator.translateLevel(level));
}
@Override
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
final Throwable t) {
// Ignore null messages
if (message != null) {
final ExtLogRecord record = new ExtLogRecord(levelTranslator.translateLevel(level),
message.getFormattedMessage(), ExtLogRecord.FormatStyle.NO_FORMAT, fqcn);
if (message.getParameters() != null) {
record.setParameters(message.getParameters());
}
if (ThreadContext.isEmpty()) {
record.setMdc(Collections.emptyMap());
} else {
record.setMdc(ThreadContext.getContext());
}
record.setNdc(getNdc());
record.setThrown(t == null ? message.getThrowable() : t);
logger.log(record);
}
}
@Override
public Level getLevel() {
final java.util.logging.Level level = logger.getLevel();
if (level != null) {
return levelTranslator.translateLevel(level);
}
return levelTranslator.translateLevel(logger.getEffectiveLevel());
}
private String getNdc() {
final ThreadContext.ContextStack contextStack = ThreadContext.getImmutableStack();
if (contextStack.isEmpty()) {
return "";
}
return String.join(".", contextStack);
}
}

View file

@ -0,0 +1,125 @@
package org.xbib.logging.log4j;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.spi.ExtendedLogger;
import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.spi.LoggerRegistry;
import org.xbib.logging.LogContext;
/**
* Represents a {@link LoggerContext} backed by a {@link LogContext}.
*/
class XbibLoggerContext implements LoggerContext {
private final LogContext logContext;
private final Object externalContext;
private final LoggerRegistry<XbibLogger> loggerRegistry = new LoggerRegistry<>();
private final ConcurrentMap<String, Object> map = new ConcurrentHashMap<>();
/**
* Creates a new logger context.
*
* @param logContext the LogManager context to use
* @param externalContext the external context provided
*/
XbibLoggerContext(final LogContext logContext, final Object externalContext) {
this.logContext = logContext;
this.externalContext = externalContext;
}
@Override
public Object getExternalContext() {
return externalContext;
}
@Override
public ExtendedLogger getLogger(final String name) {
return getLogger(name, null);
}
@Override
public ExtendedLogger getLogger(final String name, final MessageFactory messageFactory) {
XbibLogger logger = loggerRegistry.getLogger(name, messageFactory);
if (logger != null) {
AbstractLogger.checkMessageFactory(logger, messageFactory);
return logger;
}
logger = new XbibLogger(logContext.getLogger(name), messageFactory);
loggerRegistry.putIfAbsent(name, messageFactory, logger);
return loggerRegistry.getLogger(name, messageFactory);
}
@Override
public boolean hasLogger(final String name) {
return loggerRegistry.hasLogger(name);
}
@Override
public boolean hasLogger(final String name, final MessageFactory messageFactory) {
return loggerRegistry.hasLogger(name, messageFactory);
}
@Override
public boolean hasLogger(final String name, final Class<? extends MessageFactory> messageFactoryClass) {
return loggerRegistry.hasLogger(name, messageFactoryClass);
}
@Override
public Object getObject(final String key) {
return map.get(key);
}
@Override
public Object putObject(final String key, final Object value) {
return map.put(key, value);
}
@Override
public Object putObjectIfAbsent(final String key, final Object value) {
return map.putIfAbsent(key, value);
}
@Override
public Object removeObject(final String key) {
return map.remove(key);
}
@Override
public boolean removeObject(final String key, final Object value) {
return map.remove(key, value);
}
@Override
public int hashCode() {
return Objects.hash(logContext, loggerRegistry, externalContext);
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof XbibLoggerContext other)) {
return false;
}
return Objects.equals(logContext, other.logContext) && Objects.equals(loggerRegistry, other.loggerRegistry)
&& Objects.equals(externalContext, other.externalContext);
}
/**
* Returns the LogManager log context associated with the log4j logger context.
*
* @return the LogManager log context
*/
LogContext getLogContext() {
return logContext;
}
}

View file

@ -0,0 +1,124 @@
package org.xbib.logging.log4j;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.spi.LoggerContextFactory;
import org.apache.logging.log4j.status.StatusLogger;
import org.xbib.logging.LogContext;
import org.xbib.logging.Logger;
/**
* A context factory backed by LogManager.
*/
public class XbibLoggerContextFactory implements LoggerContextFactory {
private static final Logger.AttachmentKey<Map<Object, LoggerContext>> CONTEXT_KEY = new Logger.AttachmentKey<>();
private static final String ROOT_LOGGER_NAME = "";
private final ReentrantLock lock = new ReentrantLock();
public XbibLoggerContextFactory() {
}
@Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
final boolean currentContext) {
return getLoggerContext(loader, externalContext, currentContext);
}
@Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
final boolean currentContext, final URI configLocation, final String name) {
try {
return getLoggerContext(loader, externalContext, currentContext);
} finally {
// Done in a finally block as the StatusLogger may not be configured until the call to getLoggerContext()
if (configLocation != null) {
StatusLogger.getLogger().warn(
"Configuration is not allowed for the LogManager binding. Ignoring configuration file {}",
configLocation);
}
}
}
@Override
public void removeContext(final LoggerContext context) {
// Check the context type and if it's not a XbibLoggerContext there is nothing for us to do.
if (context instanceof XbibLoggerContext) {
final LogContext logContext = ((XbibLoggerContext) context).getLogContext();
lock.lock();
try {
final Map<Object, LoggerContext> contexts = logContext.getAttachment(ROOT_LOGGER_NAME, CONTEXT_KEY);
if (contexts != null) {
final Iterator<LoggerContext> iter = contexts.values().iterator();
while (iter.hasNext()) {
final LoggerContext c = iter.next();
if (c.equals(context)) {
iter.remove();
break;
}
}
if (contexts.isEmpty()) {
final Logger rootLogger = logContext.getLogger(ROOT_LOGGER_NAME);
detach(rootLogger);
XbibStatusListener.remove(logContext);
}
}
} finally {
lock.unlock();
}
}
}
private LoggerContext getLoggerContext(final ClassLoader classLoader, final Object externalContext,
final boolean currentContext) {
if (currentContext || classLoader == null) {
return getOrCreateLoggerContext(LogContext.getLogContext(), externalContext);
}
final ClassLoader current = getTccl();
try {
setTccl(classLoader);
return getOrCreateLoggerContext(LogContext.getLogContext(), externalContext);
} finally {
setTccl(current);
}
}
private LoggerContext getOrCreateLoggerContext(final LogContext logContext, final Object externalContext) {
final Logger rootLogger = logContext.getLogger(ROOT_LOGGER_NAME);
lock.lock();
try {
Map<Object, LoggerContext> contexts = rootLogger.getAttachment(CONTEXT_KEY);
if (contexts == null) {
contexts = new HashMap<>();
attach(rootLogger, contexts);
}
XbibStatusListener.registerIfAbsent(logContext);
return contexts.computeIfAbsent(externalContext, o -> new XbibLoggerContext(logContext, externalContext));
} finally {
lock.unlock();
}
}
private static void attach(final Logger logger, final Map<Object, LoggerContext> value) {
logger.attach(CONTEXT_KEY, value);
}
private static void detach(final Logger logger) {
logger.detach(CONTEXT_KEY);
}
private static ClassLoader getTccl() {
return Thread.currentThread().getContextClassLoader();
}
private static void setTccl(final ClassLoader classLoader) {
Thread.currentThread().setContextClassLoader(classLoader);
}
}

View file

@ -0,0 +1,21 @@
package org.xbib.logging.log4j;
import org.apache.logging.log4j.spi.Provider;
import org.apache.logging.log4j.spi.ThreadContextMap;
public class XbibProvider extends Provider {
public XbibProvider() {
super(500, "2.6.0", XbibLoggerContextFactory.class);
}
@Override
public String getThreadContextMap() {
return ThreadContextMDCMap.class.getName();
}
@Override
public Class<? extends ThreadContextMap> loadThreadContextMap() {
return ThreadContextMDCMap.class;
}
}

View file

@ -0,0 +1,92 @@
package org.xbib.logging.log4j;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.status.StatusData;
import org.apache.logging.log4j.status.StatusListener;
import org.apache.logging.log4j.status.StatusLogger;
import org.xbib.logging.LogContext;
import org.xbib.logging.Logger;
/**
* A status logger which logs to a LogManager Logger.
*/
public class XbibStatusListener implements StatusListener {
private static final String NAME = "org.xbib.logging.log4j.status";
private static final Logger.AttachmentKey<StatusListener> STATUS_LISTENER_KEY = new Logger.AttachmentKey<>();
private final Logger logger;
private final LevelTranslator levelTranslator;
private XbibStatusListener(final Logger logger, final LevelTranslator levelTranslator) {
this.logger = logger;
this.levelTranslator = levelTranslator;
}
/**
* Registers a status listener with the log context if one does not already exist.
*
* @param logContext the log context to possibly register the status listener with
*/
public static void registerIfAbsent(final LogContext logContext) {
final LevelTranslator levelTranslator = LevelTranslator.getInstance();
Logger logger = logContext.getLoggerIfExists(NAME);
if (logger == null) {
logger = logContext.getLogger(NAME);
logger.setLevel(levelTranslator.translateLevel(StatusLogger.getLogger().getFallbackListener().getStatusLevel()));
}
StatusListener listener = logger.getAttachment(STATUS_LISTENER_KEY);
if (listener == null) {
listener = new XbibStatusListener(logger, levelTranslator);
if (attachIfAbsent(logger, listener) == null) {
StatusLogger.getLogger().registerListener(listener);
}
}
}
/**
* Removes the status listener from the log context.
*
* @param logContext the log context to remove the status listener from
*/
public static void remove(final LogContext logContext) {
final Logger logger = logContext.getLoggerIfExists(NAME);
if (logger != null) {
detach(logger);
}
}
@Override
public void log(final StatusData data) {
// Verify we can log at this level
if (getStatusLevel().isLessSpecificThan(data.getLevel())) {
logger.log(
levelTranslator.translateLevel(data.getLevel()),
data.getMessage().getFormattedMessage(),
data.getThrowable());
}
}
@Override
public Level getStatusLevel() {
return levelTranslator.translateLevel(logger.getLevel());
}
@Override
public void close() {
detach(logger);
}
private static StatusListener attachIfAbsent(final Logger logger, final StatusListener value) {
return logger.attachIfAbsent(STATUS_LISTENER_KEY, value);
}
private static void detach(final Logger logger) {
final StatusListener listener = logger.detach(STATUS_LISTENER_KEY);
if (listener != null) {
StatusLogger.getLogger().removeListener(listener);
}
}
}

View file

@ -0,0 +1 @@
org.xbib.logging.log4j.XbibProvider

View file

@ -0,0 +1,8 @@
module org.xbib.logging.log4j.test {
requires transitive java.logging;
requires org.apache.logging.log4j;
requires org.junit.jupiter.api;
requires transitive org.xbib.logging;
requires org.xbib.logging.adapter.log4j;
exports org.xbib.logging.log4j.test to org.junit.platform.commons;
}

View file

@ -0,0 +1,85 @@
package org.xbib.logging.log4j.test;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.logging.Formatter;
import org.xbib.logging.ExtHandler;
import org.xbib.logging.ExtLogRecord;
import org.xbib.logging.LogContext;
import org.xbib.logging.LogContextSelector;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
public class AbstractTest {
private static class TestLogContextSelector implements LogContextSelector {
private final LogContext logContext;
private TestLogContextSelector(final LogContext logContext) {
this.logContext = logContext;
}
@Override
public LogContext getLogContext() {
return logContext;
}
}
@BeforeEach
public void logContextSetup() {
LogContext.setLogContextSelector(new TestLogContextSelector(LogContext.create()));
}
@AfterEach
public void resetLogContext() {
LogContext.setLogContextSelector(LogContext.DEFAULT_LOG_CONTEXT_SELECTOR);
}
static class TestQueueHandler extends ExtHandler {
private final Deque<ExtLogRecord> collected = new ArrayDeque<>();
TestQueueHandler() {
}
TestQueueHandler(final Formatter formatter) {
setFormatter(formatter);
}
@Override
protected void doPublish(final ExtLogRecord record) {
collected.addLast(record);
}
ExtLogRecord pollFirst() {
return collected.pollFirst();
}
ExtLogRecord poll() {
return collected.pollLast();
}
String pollFormatted() {
return format(poll());
}
String pollFirstFormatted() {
return format(pollFirst());
}
boolean isEmpty() {
return collected.isEmpty();
}
private String format(final ExtLogRecord record) {
if (record == null) {
return null;
}
final Formatter formatter = getFormatter();
if (formatter == null) {
return record.getMessage();
}
return formatter.format(record);
}
}
}

View file

@ -0,0 +1,80 @@
package org.xbib.logging.log4j.test;
import org.apache.logging.log4j.Level;
import org.junit.jupiter.api.Test;
import org.xbib.logging.log4j.LevelTranslator;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class LevelTranslatorTest {
private final LevelTranslator levelTranslator = LevelTranslator.getInstance();
@Test
public void testOff() {
testLevel(Level.OFF, java.util.logging.Level.OFF);
}
@Test
public void testFatal() {
testLevel(Level.FATAL, org.xbib.logging.Level.FATAL);
}
@Test
public void testError() {
testLevel(Level.ERROR, org.xbib.logging.Level.ERROR);
testLevel(Level.ERROR, java.util.logging.Level.SEVERE, org.xbib.logging.Level.ERROR);
}
@Test
public void testWarn() {
testLevel(Level.WARN, org.xbib.logging.Level.WARN);
testLevel(Level.WARN, java.util.logging.Level.WARNING, org.xbib.logging.Level.WARN);
}
@Test
public void testInfo() {
testLevel(Level.INFO, org.xbib.logging.Level.INFO);
testLevel(Level.INFO, java.util.logging.Level.INFO);
}
@Test
public void testDebug() {
testLevel(Level.DEBUG, org.xbib.logging.Level.DEBUG);
testLevel(Level.DEBUG, java.util.logging.Level.FINE, org.xbib.logging.Level.DEBUG);
testLevel(Level.DEBUG, java.util.logging.Level.CONFIG, org.xbib.logging.Level.DEBUG);
}
@Test
public void testTrace() {
testLevel(Level.TRACE, org.xbib.logging.Level.TRACE);
testLevel(Level.TRACE, java.util.logging.Level.FINER, org.xbib.logging.Level.TRACE);
testLevel(Level.TRACE, java.util.logging.Level.FINEST, org.xbib.logging.Level.TRACE);
}
@Test
public void testAll() {
testLevel(Level.ALL, java.util.logging.Level.ALL);
}
@Test
public void testNull() {
assertEquals(org.xbib.logging.Level.DEBUG, levelTranslator.translateLevel((Level) null),
"Expected null log4j level to map to INFO");
assertEquals(Level.DEBUG, levelTranslator.translateLevel((java.util.logging.Level) null),
"Expected null JUL level to map to INFO");
assertEquals(Level.DEBUG, levelTranslator.translateLevel(-1), "" +
"Expected a -1 effective level to map to INFO");
}
private void testLevel(final Level log4jLevel, final java.util.logging.Level julLevel) {
testLevel(log4jLevel, julLevel, julLevel);
}
private void testLevel(final Level log4jLevel, final java.util.logging.Level julLevel,
final java.util.logging.Level expectedJulLevel) {
assertEquals(log4jLevel, levelTranslator.translateLevel(julLevel),
String.format("Expected log4j level %s to equal JUL level %s", log4jLevel, julLevel));
assertEquals(expectedJulLevel, levelTranslator.translateLevel(log4jLevel),
String.format("Expected JUL level %s to equal log4j level %s", julLevel, log4jLevel));
}
}

View file

@ -0,0 +1,16 @@
package org.xbib.logging.log4j.test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.spi.LoggerContextFactory;
import org.junit.jupiter.api.Test;
import org.xbib.logging.log4j.XbibLoggerContextFactory;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class LoggerContextFactoryTest {
@Test
public void testCorrectFactory() {
final LoggerContextFactory factory = LogManager.getFactory();
assertEquals(XbibLoggerContextFactory.class, factory.getClass());
}
}

View file

@ -0,0 +1,31 @@
package org.xbib.logging.log4j.test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.StringFormatterMessageFactory;
import org.apache.logging.log4j.spi.LoggerContext;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class LoggerContextTest extends AbstractTest {
@Test
public void testHasLogger() {
final LoggerContext loggerContext = LogManager.getContext();
final Logger logger = LogManager.getFormatterLogger(LoggerTest.class);
assertFalse(loggerContext.hasLogger("org.xbib.logging"));
assertFalse(loggerContext.hasLogger(logger.getName()));
assertTrue(loggerContext.hasLogger(logger.getName(), StringFormatterMessageFactory.INSTANCE));
assertTrue(loggerContext.hasLogger(logger.getName(), StringFormatterMessageFactory.class));
}
@Test
public void testExternalContext() {
final Object externalContext = new Object();
final LoggerContext loggerContext = LogManager.getContext(LoggerContextTest.class.getClassLoader(), true,
externalContext);
assertEquals(externalContext, loggerContext.getExternalContext());
}
}

View file

@ -0,0 +1,322 @@
package org.xbib.logging.log4j.test;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.UUID;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.message.AbstractMessageFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.message.MessageFactory2;
import org.xbib.logging.ExtLogRecord;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class LoggerTest extends AbstractTest {
private final String loggerName = LoggerTest.class.getPackage().getName();
private final Marker marker = MarkerManager.getMarker("test");
private TestQueueHandler handler;
private org.xbib.logging.Logger lmLogger;
@BeforeEach
public void setup() {
lmLogger = org.xbib.logging.Logger.getLogger("org.xbib.logging.log4j");
lmLogger.setLevel(java.util.logging.Level.INFO);
final TestQueueHandler handler = new TestQueueHandler();
lmLogger.addHandler(handler);
this.handler = handler;
}
@AfterEach
public void tearDown() {
handler.close();
lmLogger.removeHandler(handler);
}
@Test
public void testNamedLogger() {
final Logger logger = LogManager.getLogger(loggerName);
logger.info("Test message");
ExtLogRecord record = handler.poll();
assertNotNull(record);
assertEquals("Test message", record.getMessage());
logger.info("Test message parameter {}", 1);
record = handler.poll();
assertNotNull(record);
assertEquals("Test message parameter 1", record.getMessage());
}
@SuppressWarnings("PlaceholderCountMatchesArgumentCount")
@Test
public void testNamedFormatterLogger() {
final Logger logger = LogManager.getFormatterLogger(loggerName);
logger.info("Test message parameter %s", 1);
final ExtLogRecord record = handler.poll();
assertNotNull(record);
assertEquals("Test message parameter 1", record.getMessage());
}
@Test
public void testCurrentClassLogger() {
final String expectedName = LoggerTest.class.getName();
final Logger logger = LogManager.getLogger();
assertEquals(expectedName, logger.getName());
logger.info("Test message");
final ExtLogRecord record = handler.poll();
assertNotNull(record);
assertEquals("Test message", record.getMessage());
assertEquals(expectedName, record.getLoggerName());
}
@Test
public void testObjectLogger() {
final String expectedName = LoggerTest.class.getName();
final Logger logger = LogManager.getLogger(this);
assertEquals(expectedName, logger.getName());
logger.info("Test message");
final ExtLogRecord record = handler.poll();
assertNotNull(record);
assertEquals("Test message", record.getMessage());
assertEquals(expectedName, record.getLoggerName());
}
@Test
public void testCurrentClassMessageFactoryLogger() {
final String prefix = generatePrefix();
final String expectedName = LoggerTest.class.getName();
final MessageFactory messageFactory = new TestMessageFactory(prefix);
final Logger logger = LogManager.getLogger(messageFactory);
assertEquals(expectedName, logger.getName());
logger.info("Test message");
final ExtLogRecord record = handler.poll();
assertNotNull(record);
assertEquals(prefix + "Test message", record.getMessage());
assertEquals(expectedName, record.getLoggerName());
}
@Test
public void testObjectMessageFactoryLogger() {
final String prefix = generatePrefix();
final String expectedName = LoggerTest.class.getName();
final MessageFactory messageFactory = new TestMessageFactory(prefix);
final Logger logger = LogManager.getLogger(this, messageFactory);
assertEquals(expectedName, logger.getName());
logger.info("Test message");
final ExtLogRecord record = handler.poll();
assertNotNull(record);
assertEquals(prefix + "Test message", record.getMessage());
assertEquals(expectedName, record.getLoggerName());
}
@Test
public void testNamedMessageFactoryLogger() {
final String prefix = generatePrefix();
final String expectedName = loggerName;
final MessageFactory messageFactory = new TestMessageFactory(prefix);
final Logger logger = LogManager.getLogger(loggerName, messageFactory);
assertEquals(expectedName, logger.getName());
logger.info("Test message");
final ExtLogRecord record = handler.poll();
assertNotNull(record);
assertEquals(prefix + "Test message", record.getMessage());
assertEquals(expectedName, record.getLoggerName());
}
@Test
public void tesTypeMessageFactoryLogger() {
final String prefix = generatePrefix();
final String expectedName = LoggerTest.class.getName();
final MessageFactory messageFactory = new TestMessageFactory(prefix);
final Logger logger = LogManager.getLogger(LoggerTest.class, messageFactory);
assertEquals(expectedName, logger.getName());
logger.info("Test message");
final ExtLogRecord record = handler.poll();
assertNotNull(record);
assertEquals(prefix + "Test message", record.getMessage());
assertEquals(expectedName, record.getLoggerName());
}
@Test
public void testAllEnabled() {
lmLogger.setLevel(org.xbib.logging.Level.ALL);
testLevelEnabled(LogManager.getLogger(loggerName), Level.ALL);
testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.ALL);
}
@Test
public void testTraceEnabled() {
lmLogger.setLevel(org.xbib.logging.Level.TRACE);
testLevelEnabled(LogManager.getLogger(loggerName), Level.TRACE);
testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.TRACE);
}
@Test
public void testDebugEnabled() {
lmLogger.setLevel(org.xbib.logging.Level.DEBUG);
testLevelEnabled(LogManager.getLogger(loggerName), Level.DEBUG);
testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.DEBUG);
}
@Test
public void testInfoEnabled() {
testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.INFO);
}
@Test
public void testWarnEnabled() {
testLevelEnabled(LogManager.getLogger(loggerName), Level.WARN);
testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.WARN);
}
@Test
public void testErrorEnabled() {
testLevelEnabled(LogManager.getLogger(loggerName), Level.ERROR);
testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.ERROR);
}
@Test
public void testFatalEnabled() {
testLevelEnabled(LogManager.getLogger(loggerName), Level.FATAL);
testLevelEnabled(LogManager.getFormatterLogger(loggerName), Level.FATAL);
}
@Test
public void testOffEnabled() {
assertFalse(LogManager.getLogger(loggerName).isEnabled(Level.OFF));
assertFalse(LogManager.getLogger(loggerName).isEnabled(Level.OFF, marker));
assertFalse(LogManager.getFormatterLogger(loggerName).isEnabled(Level.OFF));
assertFalse(LogManager.getFormatterLogger(loggerName).isEnabled(Level.OFF, marker));
}
private void testLevelEnabled(final Logger logger, final Level level) {
final String msg = String.format("Expected level %s to be enabled on logger %s", level, logger);
final String markerMsg = String.format("Expected level %s to be enabled on logger %s with marker %s", level, logger,
marker);
assertTrue(logger.isEnabled(level), msg);
assertTrue(logger.isEnabled(level, marker), markerMsg);
if (level.equals(Level.FATAL)) {
assertTrue(logger.isFatalEnabled(), msg);
assertTrue(logger.isFatalEnabled(marker), markerMsg);
} else if (level.equals(Level.ERROR)) {
assertTrue(logger.isErrorEnabled(), msg);
assertTrue(logger.isErrorEnabled(marker), markerMsg);
} else if (level.equals(Level.WARN)) {
assertTrue(logger.isWarnEnabled(), msg);
assertTrue(logger.isWarnEnabled(marker), markerMsg);
} else if (level.equals(Level.INFO)) {
assertTrue(logger.isInfoEnabled(), msg);
assertTrue(logger.isInfoEnabled(marker), markerMsg);
} else if (level.equals(Level.DEBUG)) {
assertTrue(logger.isDebugEnabled(), msg);
assertTrue(logger.isDebugEnabled(marker), markerMsg);
} else if (level.equals(Level.TRACE)) {
assertTrue(logger.isTraceEnabled(), msg);
assertTrue(logger.isTraceEnabled(marker), markerMsg);
}
}
private static String generatePrefix() {
return "[" + UUID.randomUUID() + "] ";
}
@SuppressWarnings("serial")
private static class TestMessageFactory extends AbstractMessageFactory implements MessageFactory2 {
private final String prefix;
private TestMessageFactory(final String prefix) {
this.prefix = prefix;
}
@Override
public Message newMessage(final CharSequence charSequence) {
return newMessage(String.valueOf(charSequence));
}
/*
* (non-Javadoc)
*
* @see org.apache.logging.log4j.message.MessageFactory#newMessage(java.lang.Object)
*/
@Override
public Message newMessage(final Object message) {
return newMessage(String.valueOf(message));
}
/*
* (non-Javadoc)
*
* @see org.apache.logging.log4j.message.MessageFactory#newMessage(java.lang.String)
*/
@Override
public Message newMessage(final String message) {
return new TestMessage(prefix, null, message);
}
@Override
public Message newMessage(final String message, final Object... params) {
return new TestMessage(prefix, null, message, params);
}
}
@SuppressWarnings("serial")
private static class TestMessage implements Message {
private final String format;
private transient final Object[] params;
private final Throwable cause;
TestMessage(final String prefix, final Throwable cause, final String format, final Object... params) {
this.format = prefix == null ? "" : prefix + format;
this.params = params == null ? new Object[0] : Arrays.copyOf(params, params.length);
this.cause = cause;
}
@Override
public String getFormattedMessage() {
if (format == null) {
return null;
}
if (cause == null) {
return String.format(format, params);
}
final StringWriter writer = new StringWriter();
writer.append(String.format(format, params));
writer.write(System.lineSeparator());
cause.printStackTrace(new PrintWriter(writer));
return writer.toString();
}
@Override
public String getFormat() {
return format;
}
@Override
public Object[] getParameters() {
return params;
}
@Override
public Throwable getThrowable() {
return cause;
}
}
}

View file

@ -0,0 +1,109 @@
package org.xbib.logging.log4j.test;
import java.net.URI;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.status.StatusListener;
import org.apache.logging.log4j.status.StatusLogger;
import org.xbib.logging.Level;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xbib.logging.log4j.XbibStatusListener;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class StatusLoggerTest extends AbstractTest {
private TestQueueHandler handler;
private org.xbib.logging.Logger lmLogger;
private StatusLogger statusLogger;
@BeforeEach
public void setup() {
lmLogger = org.xbib.logging.Logger.getLogger("org.xbib.logging.log4j.status");
final TestQueueHandler handler = new TestQueueHandler();
lmLogger.addHandler(handler);
this.handler = handler;
// Required to initialize the XbibStatusListener
LogManager.getContext();
statusLogger = StatusLogger.getLogger();
lmLogger.setLevel(Level.WARN);
}
@AfterEach
public void tearDown() {
handler.close();
lmLogger.removeHandler(handler);
}
@Test
public void testListenerAttached() {
boolean found = false;
for (StatusListener listener : statusLogger.getListeners()) {
if (listener.getClass().equals(XbibStatusListener.class)) {
found = true;
break;
}
}
assertTrue(found,
"Expected to find " + XbibStatusListener.class.getName() + " registered: " + statusLogger.getListeners());
}
@Test
public void testError() {
// Log an error which should show up on the handler
statusLogger.error("Test status message");
checkEmpty(false);
final String msg = handler.pollFirstFormatted();
assertNotNull(msg);
assertEquals("Test status message", msg);
}
@Test
public void testLevelChange() {
// Log a warning message which should be ignored
statusLogger.info("Test info message 1");
checkEmpty(true);
// Set the level to warn and log another message
lmLogger.setLevel(Level.INFO);
statusLogger.info("Test info message 2");
checkEmpty(false);
assertEquals("Test info message 2", handler.pollFormatted());
}
@Test
public void testConfiguration() throws Exception {
final URI config = LoggerContextTest.class.getResource("/log4j2.xml").toURI();
final LoggerContext loggerContext = LogManager.getContext(LoggerContextTest.class.getClassLoader(), true, config);
assertNotNull(loggerContext);
// The status logger should contain a message
checkEmpty(false);
final String foundMsg = handler.pollFirstFormatted();
assertTrue(foundMsg.contains(config.toString()),
String.format("Expected the log message to contain %s. Found %s", config, foundMsg));
}
private void checkEmpty(final boolean expectEmpty) {
if (handler.isEmpty() != expectEmpty) {
final StringBuilder msg = new StringBuilder("Expect the data to ");
if (expectEmpty) {
msg.append("be empty, found:")
.append(System.lineSeparator());
String logMsg;
while ((logMsg = handler.pollFirstFormatted()) != null) {
msg.append(logMsg).append(System.lineSeparator());
}
} else {
msg.append("not be empty");
}
fail(msg.toString());
}
}
}

View file

@ -0,0 +1,140 @@
package org.xbib.logging.log4j.test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.xbib.logging.MDC;
import org.xbib.logging.NDC;
import org.xbib.logging.formatters.PatternFormatter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ThreadContextMapTest extends AbstractTest {
public ThreadContextMapTest() {
}
@BeforeEach
public void clear() {
ThreadContext.clearAll();
MDC.clear();
NDC.clear();
}
@Test
public void clearThreadContext() {
final String key = "test.clear.key";
ThreadContext.put(key, "test clear value");
assertEquals("test clear value", ThreadContext.get(key));
assertEquals("test clear value", MDC.get(key));
ThreadContext.clearMap();
assertTrue(ThreadContext.isEmpty());
assertTrue(MDC.isEmpty());
}
@Test
public void clearMdc() {
final String key = "test.clear.key";
ThreadContext.put(key, "test clear value");
assertEquals("test clear value", ThreadContext.get(key));
assertEquals("test clear value", MDC.get(key));
MDC.clear();
assertTrue(ThreadContext.isEmpty());
assertTrue(MDC.isEmpty());
}
@Test
public void putThreadContext() {
final String key = "test.key";
final TestQueueHandler handler = new TestQueueHandler(new PatternFormatter("%X{" + key + "}"));
org.xbib.logging.Logger.getLogger("").addHandler(handler);
ThreadContext.put(key, "test value");
final Logger logger = LogManager.getLogger();
logger.info("Test message");
assertEquals("test value", handler.pollFormatted());
ThreadContext.remove(key);
logger.info("Test message");
assertEquals("", handler.pollFormatted());
}
@Test
public void putMdc() {
final String key = "test.key";
final TestQueueHandler handler = new TestQueueHandler(new PatternFormatter("%X{" + key + "}"));
org.xbib.logging.Logger.getLogger("").addHandler(handler);
MDC.put(key, "test value");
final Logger logger = LogManager.getLogger();
logger.info("Test message");
assertEquals("test value", handler.pollFormatted());
ThreadContext.remove(key);
logger.info("Test message");
assertEquals("", handler.pollFormatted());
}
@Test
public void pushThreadContext() {
final TestQueueHandler handler = new TestQueueHandler(new PatternFormatter("%x"));
org.xbib.logging.Logger.getLogger("").addHandler(handler);
ThreadContext.push("value-1");
ThreadContext.push("value-2");
ThreadContext.push("value-3");
final Logger logger = LogManager.getLogger();
logger.info("Test message");
assertEquals("value-1.value-2.value-3", handler.pollFormatted());
ThreadContext.trim(2);
assertEquals(2, ThreadContext.getDepth());
logger.info("Test message");
assertEquals("value-1.value-2", handler.pollFormatted());
}
@Test
public void removeThreadContext() {
final String key = "test.clear.key";
ThreadContext.put(key, "test clear value");
assertEquals("test clear value", ThreadContext.get(key));
assertEquals("test clear value", MDC.get(key));
ThreadContext.remove(key);
assertNull(ThreadContext.get(key));
assertNull(MDC.get(key));
}
@Test
public void removeMdc() {
final String key = "test.clear.key";
MDC.put(key, "test clear value");
assertEquals("test clear value", ThreadContext.get(key));
assertEquals("test clear value", MDC.get(key));
MDC.remove(key);
assertNull(ThreadContext.get(key));
assertNull(MDC.get(key));
}
}

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[log4j2] %d [%t] %-5level: %msg%n%throwable" />
</Console>
</Appenders>
<Loggers>
<Root level="TRACE">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>

View file

@ -0,0 +1,5 @@
dependencies {
api project(':logging')
api libs.slf4j.api
}

View file

@ -0,0 +1,9 @@
import org.slf4j.impl.XbibSlf4jServiceProvider;
import org.slf4j.spi.SLF4JServiceProvider;
module org.xbib.logging.adapter.slf4j {
requires transitive org.slf4j;
requires transitive org.xbib.logging;
exports org.slf4j.impl;
provides SLF4JServiceProvider with XbibSlf4jServiceProvider;
}

View file

@ -0,0 +1,553 @@
package org.slf4j.impl;
import org.xbib.logging.ExtLogRecord;
import org.xbib.logging.Level;
import org.xbib.logging.Logger;
import org.slf4j.Marker;
import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;
import org.slf4j.spi.LocationAwareLogger;
public final class Slf4jLogger implements LocationAwareLogger {
private final Logger logger;
private static final String LOGGER_CLASS_NAME = Slf4jLogger.class.getName();
private static final int ALT_ERROR_INT = org.xbib.logging.Level.ERROR.intValue();
private static final int ALT_WARN_INT = org.xbib.logging.Level.WARN.intValue();
private static final int ALT_INFO_INT = org.xbib.logging.Level.INFO.intValue();
private static final int ALT_DEBUG_INT = org.xbib.logging.Level.DEBUG.intValue();
private static final int ALT_TRACE_INT = org.xbib.logging.Level.TRACE.intValue();
public Slf4jLogger(final Logger logger) {
this.logger = logger;
}
public String getName() {
return logger.getName();
}
@Override
public void log(final Marker marker, final String fqcn, final int levelVal, final String fmt, final Object[] argArray,
final Throwable t) {
final java.util.logging.Level level = switch (levelVal) {
case LocationAwareLogger.TRACE_INT -> Level.TRACE;
case LocationAwareLogger.DEBUG_INT -> Level.DEBUG;
case LocationAwareLogger.INFO_INT -> Level.INFO;
case LocationAwareLogger.WARN_INT -> Level.WARN;
case LocationAwareLogger.ERROR_INT -> Level.ERROR;
default -> Level.DEBUG;
};
if (logger.isLoggable(level)) {
final String message = MessageFormatter.arrayFormat(fmt, argArray).getMessage();
log(marker, level, fqcn, message, t, argArray);
}
}
@Override
public boolean isTraceEnabled() {
return logger.isLoggable(Level.TRACE);
}
@Override
public void trace(final String msg) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.TRACE, msg, null);
}
@Override
public void trace(final String format, final Object arg) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(null, org.xbib.logging.Level.TRACE, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void trace(final String format, final Object arg1, final Object arg2) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(null, org.xbib.logging.Level.TRACE, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void trace(final String format, final Object... arguments) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(null, org.xbib.logging.Level.TRACE, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void trace(final String msg, final Throwable t) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.TRACE, msg, t);
}
@Override
public boolean isTraceEnabled(Marker marker) {
return isTraceEnabled();
}
@Override
public void trace(Marker marker, String msg) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.TRACE, msg, null);
}
@Override
public void trace(Marker marker, String format, Object arg) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(marker, org.xbib.logging.Level.TRACE, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void trace(Marker marker, String format, Object arg1, Object arg2) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(marker, org.xbib.logging.Level.TRACE, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void trace(Marker marker, String format, Object... arguments) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(marker, org.xbib.logging.Level.TRACE, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void trace(Marker marker, String msg, Throwable t) {
if (ALT_TRACE_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.TRACE, msg, t);
}
@Override
public boolean isDebugEnabled() {
return logger.isLoggable(Level.DEBUG);
}
@Override
public void debug(final String msg) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.DEBUG, msg, null);
}
@Override
public void debug(final String format, final Object arg) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(null, org.xbib.logging.Level.DEBUG, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void debug(final String format, final Object arg1, final Object arg2) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(null, org.xbib.logging.Level.DEBUG, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void debug(final String format, final Object... arguments) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(null, org.xbib.logging.Level.DEBUG, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void debug(final String msg, final Throwable t) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.DEBUG, msg, t);
}
@Override
public boolean isDebugEnabled(Marker marker) {
return isDebugEnabled();
}
@Override
public void debug(Marker marker, String msg) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.DEBUG, msg, null);
}
@Override
public void debug(Marker marker, String format, Object arg) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(marker, org.xbib.logging.Level.DEBUG, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void debug(Marker marker, String format, Object arg1, Object arg2) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(marker, org.xbib.logging.Level.DEBUG, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void debug(Marker marker, String format, Object... arguments) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(marker, org.xbib.logging.Level.DEBUG, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void debug(Marker marker, String msg, Throwable t) {
if (ALT_DEBUG_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.DEBUG, msg, t);
}
@Override
public boolean isInfoEnabled() {
return logger.isLoggable(Level.INFO);
}
@Override
public void info(final String msg) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.INFO, msg, null);
}
@Override
public void info(final String format, final Object arg) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(null, org.xbib.logging.Level.INFO, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void info(final String format, final Object arg1, final Object arg2) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(null, org.xbib.logging.Level.INFO, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void info(final String format, final Object... arguments) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(null, org.xbib.logging.Level.INFO, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void info(final String msg, final Throwable t) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.INFO, msg, t);
}
@Override
public boolean isInfoEnabled(Marker marker) {
return isInfoEnabled();
}
@Override
public void info(Marker marker, String msg) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.INFO, msg, null);
}
@Override
public void info(Marker marker, String format, Object arg) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(marker, org.xbib.logging.Level.INFO, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void info(Marker marker, String format, Object arg1, Object arg2) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(marker, org.xbib.logging.Level.INFO, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void info(Marker marker, String format, Object... arguments) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(marker, org.xbib.logging.Level.INFO, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void info(Marker marker, String msg, Throwable t) {
if (ALT_INFO_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.INFO, msg, t);
}
@Override
public boolean isWarnEnabled() {
return logger.isLoggable(Level.WARN);
}
@Override
public void warn(final String msg) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.WARN, msg, null);
}
@Override
public void warn(final String format, final Object arg) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(null, org.xbib.logging.Level.WARN, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void warn(final String format, final Object... arguments) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(null, org.xbib.logging.Level.WARN, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void warn(final String format, final Object arg1, final Object arg2) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(null, org.xbib.logging.Level.WARN, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void warn(final String msg, final Throwable t) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.WARN, msg, t);
}
@Override
public boolean isWarnEnabled(Marker marker) {
return isWarnEnabled();
}
@Override
public void warn(Marker marker, String msg) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.WARN, msg, null);
}
@Override
public void warn(Marker marker, String format, Object arg) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(marker, org.xbib.logging.Level.WARN, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void warn(Marker marker, String format, Object arg1, Object arg2) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(marker, org.xbib.logging.Level.WARN, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void warn(Marker marker, String format, Object... arguments) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(marker, org.xbib.logging.Level.WARN, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void warn(Marker marker, String msg, Throwable t) {
if (ALT_WARN_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.WARN, msg, t);
}
@Override
public boolean isErrorEnabled() {
return logger.isLoggable(Level.ERROR);
}
@Override
public void error(final String msg) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.ERROR, msg, null);
}
@Override
public void error(final String format, final Object arg) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(null, org.xbib.logging.Level.ERROR, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void error(final String format, final Object arg1, final Object arg2) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(null, org.xbib.logging.Level.ERROR, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void error(final String format, final Object... arguments) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(null, org.xbib.logging.Level.ERROR, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void error(final String msg, final Throwable t) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
log(null, org.xbib.logging.Level.ERROR, msg, t);
}
@Override
public boolean isErrorEnabled(Marker marker) {
return isErrorEnabled();
}
@Override
public void error(Marker marker, String msg) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.ERROR, msg, null);
}
@Override
public void error(Marker marker, String format, Object arg) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg);
log(marker, org.xbib.logging.Level.ERROR, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg);
}
@Override
public void error(Marker marker, String format, Object arg1, Object arg2) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
log(marker, org.xbib.logging.Level.ERROR, formattingTuple.getMessage(), formattingTuple.getThrowable(), arg1, arg2);
}
@Override
public void error(Marker marker, String format, Object... arguments) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
final FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
log(marker, org.xbib.logging.Level.ERROR, formattingTuple.getMessage(), formattingTuple.getThrowable(), arguments);
}
@Override
public void error(Marker marker, String msg, Throwable t) {
if (ALT_ERROR_INT < logger.getEffectiveLevel()) {
return;
}
log(marker, org.xbib.logging.Level.ERROR, msg, t);
}
private void log(final Marker marker, final java.util.logging.Level level, final String message, final Throwable t) {
final ExtLogRecord rec = new ExtLogRecord(level, message, LOGGER_CLASS_NAME);
rec.setThrown(t);
setMarker(rec, marker);
logger.logRaw(rec);
}
private void log(final Marker marker, final java.util.logging.Level level, final String message, final Throwable t,
final Object... params) {
log(marker, level, LOGGER_CLASS_NAME, message, t, params);
}
private void log(final Marker marker, final java.util.logging.Level level, final String fqcn, final String message,
final Throwable t, final Object[] params) {
final ExtLogRecord rec = new ExtLogRecord(level, message, ExtLogRecord.FormatStyle.NO_FORMAT, fqcn);
rec.setThrown(t);
rec.setParameters(params);
setMarker(rec, marker);
logger.logRaw(rec);
}
private void setMarker(ExtLogRecord rec, Marker marker) {
rec.setMarker(marker);
}
}

View file

@ -0,0 +1,25 @@
package org.slf4j.impl;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.xbib.logging.LogContext;
public final class Slf4jLoggerFactory implements ILoggerFactory {
private static final org.xbib.logging.Logger.AttachmentKey<Logger> key = new org.xbib.logging.Logger.AttachmentKey<>();
public Slf4jLoggerFactory() {
}
@Override
public Logger getLogger(final String name) {
final org.xbib.logging.Logger lmLogger = LogContext.getLogContext().getLogger(name);
final Logger logger = lmLogger.getAttachment(key);
if (logger != null) {
return logger;
}
final Logger newLogger = new Slf4jLogger(lmLogger);
final Logger appearingLogger = lmLogger.attachIfAbsent(key, newLogger);
return appearingLogger != null ? appearingLogger : newLogger;
}
}

View file

@ -0,0 +1,50 @@
package org.slf4j.impl;
import java.util.Map;
import org.slf4j.helpers.BasicMDCAdapter;
import org.slf4j.spi.MDCAdapter;
import org.xbib.logging.MDC;
public final class Slf4jMDCAdapter extends BasicMDCAdapter implements MDCAdapter {
public Slf4jMDCAdapter() {
}
@Override
public void put(final String key, final String val) {
MDC.put(key, val);
}
@Override
public String get(final String key) {
return MDC.get(key);
}
@Override
public void remove(final String key) {
MDC.remove(key);
}
@Override
public void clear() {
MDC.clear();
}
@Override
public Map<String, String> getCopyOfContextMap() {
return MDC.copy();
}
@Override
public void setContextMap(final Map<String, String> contextMap) {
MDC.clear();
for (Map.Entry<?, ?> entry : ((Map<?, ?>) contextMap).entrySet()) {
final Object key = entry.getKey();
final Object value = entry.getValue();
if (key != null && value != null) {
MDC.put(key.toString(), value.toString());
}
}
}
}

View file

@ -0,0 +1,27 @@
package org.slf4j.impl;
import org.slf4j.ILoggerFactory;
import org.slf4j.spi.LoggerFactoryBinder;
@Deprecated(forRemoval = true)
public final class StaticLoggerBinder implements LoggerFactoryBinder {
public static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
public StaticLoggerBinder() {
}
@Override
public ILoggerFactory getLoggerFactory() {
return new Slf4jLoggerFactory();
}
@Override
public String getLoggerFactoryClassStr() {
return Slf4jLoggerFactory.class.getName();
}
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
}

View file

@ -0,0 +1,20 @@
package org.slf4j.impl;
import org.slf4j.spi.MDCAdapter;
@Deprecated(forRemoval = true)
public final class StaticMDCBinder {
public static final StaticMDCBinder SINGLETON = new StaticMDCBinder();
private StaticMDCBinder() {
}
public MDCAdapter getMDCA() {
return new Slf4jMDCAdapter();
}
public String getMDCAdapterClassStr() {
return Slf4jMDCAdapter.class.getName();
}
}

View file

@ -0,0 +1,24 @@
package org.slf4j.impl;
import org.slf4j.IMarkerFactory;
import org.slf4j.helpers.BasicMarkerFactory;
import org.slf4j.spi.MarkerFactoryBinder;
@Deprecated(forRemoval = true)
public final class StaticMarkerBinder implements MarkerFactoryBinder {
public static final StaticMarkerBinder SINGLETON = new StaticMarkerBinder();
private final IMarkerFactory markerFactory = new BasicMarkerFactory();
private StaticMarkerBinder() {
}
public IMarkerFactory getMarkerFactory() {
return markerFactory;
}
public String getMarkerFactoryClassStr() {
return BasicMarkerFactory.class.getName();
}
}

View file

@ -0,0 +1,45 @@
package org.slf4j.impl;
import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.helpers.BasicMarkerFactory;
import org.slf4j.spi.MDCAdapter;
import org.slf4j.spi.SLF4JServiceProvider;
public class XbibSlf4jServiceProvider implements SLF4JServiceProvider {
private final ILoggerFactory loggerFactory;
private final IMarkerFactory markerFactory;
private final MDCAdapter mdcAdapter;
public XbibSlf4jServiceProvider() {
this.loggerFactory = new Slf4jLoggerFactory();
this.markerFactory = new BasicMarkerFactory();
this.mdcAdapter = new Slf4jMDCAdapter();
}
@Override
public ILoggerFactory getLoggerFactory() {
return loggerFactory;
}
@Override
public IMarkerFactory getMarkerFactory() {
return markerFactory;
}
@Override
public MDCAdapter getMDCAdapter() {
return mdcAdapter;
}
@Override
public String getRequestedApiVersion() {
return "2.0.13";
}
@Override
public void initialize() {
// do nothing
}
}

View file

@ -0,0 +1,20 @@
#
# JBoss, Home of Professional Open Source.
#
# Copyright 2023 Red Hat, Inc., and individual contributors
# as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
org.slf4j.impl.XbibSlf4jServiceProvider

View file

@ -0,0 +1,8 @@
module org.xbib.logging.adapter.slf4j.test {
requires java.logging;
requires org.junit.jupiter.api;
requires org.slf4j;
requires org.xbib.logging;
requires org.xbib.logging.adapter.slf4j;
exports org.slf4j.impl.test to org.junit.platform.commons;
}

View file

@ -0,0 +1,144 @@
package org.slf4j.impl.test;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.slf4j.impl.Slf4jLogger;
import org.slf4j.impl.Slf4jMDCAdapter;
import org.xbib.logging.ExtHandler;
import org.xbib.logging.ExtLogRecord;
import org.xbib.logging.LogContext;
import org.xbib.logging.LogContextSelector;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.slf4j.Marker;
import org.slf4j.helpers.BasicMarkerFactory;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class LoggerTest {
private static final LogContext LOG_CONTEXT = LogContext.create();
private static final java.util.logging.Logger ROOT = LOG_CONTEXT.getLogger("");
private static final QueueHandler HANDLER = new QueueHandler();
private static final LogContextSelector DEFAULT_SELECTOR = LogContext.getLogContextSelector();
@BeforeAll
public static void configureLogManager() {
LogContext.setLogContextSelector(() -> LOG_CONTEXT);
ROOT.addHandler(HANDLER);
}
@AfterAll
public static void cleanup() throws Exception {
LOG_CONTEXT.close();
LogContext.setLogContextSelector(DEFAULT_SELECTOR);
}
@AfterEach
public void clearHandler() {
HANDLER.close();
}
@Test
public void testLogger() {
final Logger logger = LoggerFactory.getLogger(LoggerTest.class);
assertTrue(logger instanceof Slf4jLogger, expectedTypeMessage(Slf4jLogger.class, logger.getClass()));
// Ensure the logger logs something
final String testMsg = "This is a test message";
logger.info(testMsg);
ExtLogRecord record = HANDLER.messages.poll();
assertNotNull(record);
assertEquals(testMsg, record.getMessage());
assertNull(record.getParameters());
// Test a formatted message
logger.info("This is a test formatted {}", "{message}");
record = HANDLER.messages.poll();
assertNotNull(record);
assertEquals("This is a test formatted {message}", record.getFormattedMessage());
assertArrayEquals(new Object[] { "{message}" }, record.getParameters(), "Expected parameter not found.");
}
@Test
public void testLoggerWithExceptions() {
final Logger logger = LoggerFactory.getLogger(LoggerTest.class);
final RuntimeException e = new RuntimeException("Test exception");
final String testMsg = "This is a test message";
logger.info(testMsg, e);
LogRecord record = HANDLER.messages.poll();
assertNotNull(record);
assertEquals(testMsg, record.getMessage());
assertEquals(e, record.getThrown(), "Cause is different from the expected cause");
// Test format with the last parameter being the throwable which should set be set on the record
logger.info("This is a test formatted {}", "{message}", e);
record = HANDLER.messages.poll();
assertNotNull(record);
assertEquals("This is a test formatted {message}", record.getMessage());
assertEquals(e, record.getThrown(), "Cause is different from the expected cause");
}
@Test
public void testLoggerWithMarkers() {
final Logger logger = LoggerFactory.getLogger(LoggerTest.class);
final Marker marker = new BasicMarkerFactory().getMarker("test");
logger.info(marker, "log message");
LogRecord record = HANDLER.messages.poll();
assertNotNull(record);
// TODO: record.getMarker() must be same instance of "marker"
}
@Test
public void testMDC() {
assertSame(MDC.getMDCAdapter()
.getClass(), Slf4jMDCAdapter.class,
expectedTypeMessage(Slf4jMDCAdapter.class, MDC.getMDCAdapter()
.getClass()));
final String key = Long.toHexString(System.currentTimeMillis());
MDC.put(key, "value");
assertEquals("value", MDC.get(key), "MDC value should be \"value\"");
assertEquals("value", org.xbib.logging.MDC.get(key), "MDC value should be \"value\"");
}
private static Supplier<String> expectedTypeMessage(final Class<?> expected, final Class<?> found) {
return () -> String.format("Expected type %s but found type %s", expected.getName(), found.getName());
}
private static class QueueHandler extends ExtHandler {
final BlockingDeque<ExtLogRecord> messages = new LinkedBlockingDeque<>();
@Override
protected void doPublish(final ExtLogRecord record) {
messages.add(record);
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
messages.clear();
setLevel(Level.ALL);
}
}
}

67
logging/build.gradle Normal file
View file

@ -0,0 +1,67 @@
test {
systemProperty 'java.util.logging.manager', 'org.xbib.logging.LogManager'
systemProperty 'javax.net.ssl.keyStore', 'src/test/resources/server-keystore.jks'
systemProperty 'javax.net.ssl.keyStorePassword', 'testpassword'
systemProperty 'javax.net.ssl.trustStore', 'src/test/resources/client-keystore.jks'
systemProperty 'javax.net.ssl.trustStorePassword', 'testpassword'
}
def moduleName = 'org.xbib.logging.test'
def patchArgs = ['--patch-module', "$moduleName=" + files(sourceSets.test.resources.srcDirs).asPath ]
tasks.named('compileTestJava') {
options.compilerArgs += patchArgs
}
tasks.named('test') {
jvmArgs += patchArgs
}
sourceSets {
integration {
java.srcDir "$projectDir/src/integration/java"
resources.srcDir "$projectDir/src/integration/resources"
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
}
}
configurations {
integrationImplementation.extendsFrom testImplementation
integrationRuntime.extendsFrom testRuntime
}
dependencies {
integrationImplementation testLibs.junit.jupiter.api
integrationImplementation testLibs.hamcrest
integrationRuntimeOnly testLibs.junit.jupiter.engine
integrationRuntimeOnly testLibs.junit.jupiter.platform.launcher
}
tasks.register('integrationTest', Test) {
mustRunAfter test
testClassesDirs = sourceSets.integration.output.classesDirs
classpath = sourceSets.integration.runtimeClasspath
useJUnitPlatform() {
filter {
includeTestsMatching "*IT"
excludeTestsMatching "*Test"
}
}
failFast = false
ignoreFailures = true
testLogging {
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
}
afterSuite { desc, result ->
if (!desc.parent) {
println "\nIntegration result: ${result.resultType}"
println "Integration summary: ${result.testCount} integrations, " +
"${result.successfulTestCount} succeeded, " +
"${result.failedTestCount} failed, " +
"${result.skippedTestCount} skipped"
}
}
}
check.dependsOn integrationTest

View file

@ -0,0 +1,7 @@
module org.xbib.logging.integration {
requires java.logging;
requires org.junit.jupiter.api;
requires org.xbib.logging;
requires org.xbib.logging.test;
exports org.xbib.logging.integration to org.junit.platform.commons;
}

View file

@ -0,0 +1,166 @@
package org.xbib.logging.integration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class SystemLoggerIT {
private Path stdout;
private Path logFile;
@BeforeEach
public void setup() throws Exception {
stdout = Files.createTempFile("stdout", ".txt");
logFile = Files.createTempFile("system-logger", ".log");
}
@AfterEach
public void killProcess() throws IOException {
Files.deleteIfExists(stdout);
Files.deleteIfExists(logFile);
}
@Test
public void testSystemLoggerActivated() throws Exception {
final Process process = createProcess();
if (process.waitFor(3L, TimeUnit.SECONDS)) {
final int exitCode = process.exitValue();
final StringBuilder msg = new StringBuilder("Expected exit value 0 got ")
.append(exitCode);
appendStdout(msg);
Assertions.assertEquals(0, exitCode, msg.toString());
} else {
final Process destroyed = process.destroyForcibly();
final StringBuilder msg = new StringBuilder("Failed to exit process within 3 seconds. Exit Code: ")
.append(destroyed.exitValue());
appendStdout(msg);
Assertions.fail(msg.toString());
}
/*
final JsonObject json = readLogFile(logFile);
final JsonArray lines = json.getJsonArray("lines");
Assertions.assertEquals(2, lines.size());
// The first line should be from a SystemLogger
JsonObject line = lines.getJsonObject(0);
Assertions.assertEquals("org.xbib.logging.LoggerFinder$SystemLogger", line.getString("loggerClassName"));
JsonObject mdc = line.getJsonObject("mdc");
Assertions.assertEquals("org.xbib.logging.LoggerFinder$SystemLogger", mdc.getString("logger.type"));
Assertions.assertEquals(LogManager.class.getName(), mdc.getString("java.util.logging.LogManager"));
Assertions.assertEquals(LogManager.class.getName(), mdc.getString("java.util.logging.manager"));
// The second line should be from a JUL logger
line = lines.getJsonObject(1);
Assertions.assertEquals(Logger.class.getName(), line.getString("loggerClassName"));
mdc = line.getJsonObject("mdc");
Assertions.assertEquals(Logger.class.getName(), mdc.getString("logger.type"));
Assertions.assertEquals(LogManager.class.getName(), mdc.getString("java.util.logging.LogManager"));
Assertions.assertEquals(LogManager.class.getName(), mdc.getString("java.util.logging.manager"));
*/
}
@Test
public void testSystemLoggerAccessedBeforeActivated() throws Exception {
final Process process = createProcess("-Dsystem.logger.test.jul=true",
"-Djava.util.logging.config.class=" + TestLogContextConfigurator.class.getName());
if (process.waitFor(3L, TimeUnit.SECONDS)) {
final int exitCode = process.exitValue();
final StringBuilder msg = new StringBuilder("Expected exit value 0 got ")
.append(exitCode);
appendStdout(msg);
Assertions.assertEquals(0, exitCode, msg.toString());
} else {
final Process destroyed = process.destroyForcibly();
final StringBuilder msg = new StringBuilder("Failed to exit process within 3 seconds. Exit Code: ")
.append(destroyed.exitValue());
appendStdout(msg);
Assertions.fail(msg.toString());
}
/*
final JsonObject json = readLogFile(logFile);
final JsonArray lines = json.getJsonArray("lines");
Assertions.assertEquals(3, lines.size());
// The first line should be an error indicating the java.util.logging.manager wasn't set before the LogManager
// was accessed
JsonObject line = lines.getJsonObject(0);
Assertions.assertEquals("ERROR", line.getString("level"));
final String message = line.getString("message");
Assertions.assertNotNull(message);
Assertions.assertTrue(message.contains("java.util.logging.manager"));
// The second line should be from a SystemLogger
line = lines.getJsonObject(1);
Assertions.assertEquals("org.xbib.logging.LoggerFinder$SystemLogger", line.getString("loggerClassName"));
JsonObject mdc = line.getJsonObject("mdc");
Assertions.assertEquals("org.xbib.logging.LoggerFinder$SystemLogger", mdc.getString("logger.type"));
Assertions.assertEquals("java.util.logging.LogManager", mdc.getString("java.util.logging.LogManager"));
// The third line should be from a JUL logger
line = lines.getJsonObject(2);
Assertions.assertEquals("java.util.logging.Logger", line.getString("loggerClassName"));
mdc = line.getJsonObject("mdc");
Assertions.assertEquals("java.util.logging.Logger", mdc.getString("logger.type"));
Assertions.assertEquals("java.util.logging.LogManager", mdc.getString("java.util.logging.LogManager"));
*/
}
private Process createProcess(final String... javaOpts) throws IOException {
final List<String> cmd = new ArrayList<>();
cmd.add(findJavaCommand());
cmd.add("--add-modules=ALL-MODULE-PATH");
cmd.add("-Dorg.xbib.logging.test.configure=true");
cmd.add("-Dtest.log.file.name=" + logFile.toString());
Collections.addAll(cmd, javaOpts);
cmd.add("-cp");
cmd.add(System.getProperty("java.class.path")); // does not work in Gradle!
cmd.add(SystemLoggerMain.class.getName());
System.out.println(cmd);
return new ProcessBuilder(cmd)
.redirectErrorStream(true)
.redirectOutput(stdout.toFile())
.start();
}
private void appendStdout(final StringBuilder builder) throws IOException {
for (String line : Files.readAllLines(stdout)) {
builder.append(System.lineSeparator())
.append(line);
}
}
/*private static JsonObject readLogFile(final Path logFile) throws IOException {
final JsonArrayBuilder builder = Json.createArrayBuilder();
try (BufferedReader reader = Files.newBufferedReader(logFile, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
try (JsonReader jsonReader = Json.createReader(new StringReader(line))) {
builder.add(jsonReader.read());
}
}
}
return Json.createObjectBuilder().add("lines", builder).build();
}*/
// this hack does not respect Gradle toolchain
private static String findJavaCommand() {
String javaHome = System.getProperty("java.home");
if (javaHome != null) {
return Paths.get(javaHome, "bin", "java").toAbsolutePath().toString();
}
return "java";
}
}

View file

@ -0,0 +1,32 @@
package org.xbib.logging.integration;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.xbib.logging.MDC;
public class SystemLoggerMain {
public SystemLoggerMain() {
}
public static void main(final String[] args) {
if (Boolean.getBoolean("system.logger.test.jul")) {
// Access the log manager to ensure it's configured before the system property is set
LogManager.getLogManager();
}
final System.Logger.Level level = System.Logger.Level.valueOf(System.getProperty("system.logger.test.level", "INFO"));
final String msgId = System.getProperty("system.logger.test.msg.id");
final String msg = String.format("Test message from %s id %s", SystemLoggerMain.class.getName(), msgId);
final System.Logger systemLogger = System.getLogger(SystemLoggerMain.class.getName());
MDC.put("logger.type", systemLogger.getClass().getName());
MDC.put("java.util.logging.LogManager", LogManager.getLogManager().getClass().getName());
MDC.put("java.util.logging.manager", System.getProperty("java.util.logging.manager"));
systemLogger.log(level, msg);
final Logger logger = Logger.getLogger(SystemLoggerMain.class.getName());
MDC.put("logger.type", logger.getClass().getName());
MDC.put("java.util.logging.LogManager", LogManager.getLogManager().getClass().getName());
MDC.put("java.util.logging.manager", System.getProperty("java.util.logging.manager"));
logger.info(msg);
}
}

View file

@ -0,0 +1,77 @@
package org.xbib.logging.integration;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xbib.logging.ExtFormatter;
import org.xbib.logging.ExtLogRecord;
import org.xbib.logging.LogContext;
import org.xbib.logging.LogContextConfigurator;
import org.xbib.logging.handlers.FileHandler;
public class TestLogContextConfigurator implements LogContextConfigurator {
public TestLogContextConfigurator() {
this(true);
}
TestLogContextConfigurator(final boolean assumedJul) {
if (assumedJul) {
configure(null);
}
}
@Override
public void configure(final LogContext logContext, final InputStream inputStream) {
configure(logContext);
}
private static void configure(final LogContext logContext) {
if (Boolean.getBoolean("org.xbib.logging.test.configure")) {
final Logger rootLogger;
if (logContext == null) {
rootLogger = Logger.getLogger("");
} else {
rootLogger = logContext.getLogger("");
}
try {
final String fileName = System.getProperty("test.log.file.name");
final FileHandler handler = new FileHandler(fileName, false);
handler.setAutoFlush(true);
handler.setFormatter(new TestFormatter());
rootLogger.addHandler(handler);
rootLogger.setLevel(Level.INFO);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
private static class TestFormatter extends ExtFormatter {
@Override
public String format(final ExtLogRecord record) {
StringWriter stringWriter = new StringWriter();
/*try (JsonGenerator generator = Json.createGenerator(stringWriter)) {
generator.writeStartObject();
generator.write("loggerClassName", record.getLoggerClassName());
generator.write("level", record.getLevel().toString());
generator.write("message", record.getMessage());
generator.writeStartObject("mdc");
final Map<String, String> mdc = record.getMdcCopy();
mdc.forEach(generator::write);
generator.writeEnd(); // end MDC
generator.writeEnd(); // end object
generator.flush();
stringWriter.write(System.lineSeparator());
return stringWriter.toString();
}*/
return stringWriter.toString();
}
}
}

View file

@ -0,0 +1,17 @@
module org.xbib.logging {
uses org.xbib.logging.LogContextInitializer;
uses org.xbib.logging.LogContextConfiguratorFactory;
uses org.xbib.logging.LogContextConfigurator;
uses org.xbib.logging.NDCProvider;
uses org.xbib.logging.MDCProvider;
requires transitive java.logging;
requires java.xml;
requires java.management;
exports org.xbib.logging;
exports org.xbib.logging.configuration;
exports org.xbib.logging.filters;
exports org.xbib.logging.formatters;
exports org.xbib.logging.handlers;
exports org.xbib.logging.net;
exports org.xbib.logging.util;
}

View file

@ -0,0 +1,151 @@
package org.xbib.logging;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import org.xbib.logging.util.CopyOnWriteMap;
import org.xbib.logging.util.StackWalkerUtil;
/**
* A log context selector which chooses a log context based on the caller's classloader. The first caller that is not
* a {@linkplain #addLogApiClassLoader(ClassLoader) log API} or does not have a {@code null} classloader will be the
* class loader used.
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public final class CallerClassLoaderLogContextSelector implements LogContextSelector {
/**
* Construct a new instance. If no matching log context is found, the provided default selector is consulted.
*
* @param defaultSelector the selector to consult if no matching log context is found
*/
public CallerClassLoaderLogContextSelector(final LogContextSelector defaultSelector) {
this(defaultSelector, false);
}
/**
* Construct a new instance. If no matching log context is found, the provided default selector is consulted.
* <p>
* If the {@code checkParentClassLoaders} is set to {@code true} this selector recursively searches the class loader
* parents until a match is found or a {@code null} parent is found.
* </p>
*
* @param defaultSelector the selector to consult if no matching log context is found
* @param checkParentClassLoaders {@code true} if the {@link LogContext log context} could not
* found for the class loader and the {@link ClassLoader#getParent() parent class
* loader} should be checked
*/
public CallerClassLoaderLogContextSelector(final LogContextSelector defaultSelector,
final boolean checkParentClassLoaders) {
this.defaultSelector = defaultSelector == null ? LogContext.DEFAULT_LOG_CONTEXT_SELECTOR : defaultSelector;
this.checkParentClassLoaders = checkParentClassLoaders;
}
/**
* Construct a new instance. If no matching log context is found, the system context is used.
*/
public CallerClassLoaderLogContextSelector() {
this(false);
}
/**
* Construct a new instance. If no matching log context is found, the system context is used.
* <p>
* If the {@code checkParentClassLoaders} is set to {@code true} this selector recursively searches the class loader
* parents until a match is found or a {@code null} parent is found.
* </p>
*
* @param checkParentClassLoaders {@code true} if the {@link LogContext log context} could not
* found for the class loader and the {@link ClassLoader#getParent() parent class
* loader} should be checked
*/
public CallerClassLoaderLogContextSelector(final boolean checkParentClassLoaders) {
this(LogContext.DEFAULT_LOG_CONTEXT_SELECTOR, checkParentClassLoaders);
}
private final LogContextSelector defaultSelector;
private final ConcurrentMap<ClassLoader, LogContext> contextMap = new CopyOnWriteMap<ClassLoader, LogContext>();
private final Set<ClassLoader> logApiClassLoaders = Collections.newSetFromMap(new CopyOnWriteMap<ClassLoader, Boolean>());
private final boolean checkParentClassLoaders;
/* private final PrivilegedAction<LogContext> logContextAction = new PrivilegedAction<LogContext>() {
public LogContext run() {
final Class<?> callingClass = JDKSpecific.findCallingClass(logApiClassLoaders);
return callingClass == null ? defaultSelector.getLogContext() : check(callingClass.getClassLoader());
}
};*/
private LogContext check(final ClassLoader classLoader) {
final LogContext context = contextMap.get(classLoader);
if (context != null) {
return context;
}
final ClassLoader parent = classLoader.getParent();
if (parent != null && checkParentClassLoaders && !logApiClassLoaders.contains(parent)) {
return check(parent);
}
return defaultSelector.getLogContext();
}
/**
* {@inheritDoc} This instance will consult the call stack to see if the first callers classloader is associated
* with a log context. The first caller is determined by the first class loader that is not registered as a
* {@linkplain #addLogApiClassLoader(ClassLoader) log API}.
*/
@Override
public LogContext getLogContext() {
final Class<?> callingClass = StackWalkerUtil.findCallingClass(logApiClassLoaders);
return callingClass == null ? defaultSelector.getLogContext() : check(callingClass.getClassLoader());
}
/**
* Register a class loader which is a known log API, and thus should be skipped over when searching for the
* log context to use for the caller class.
*
* @param apiClassLoader the API class loader
* @return {@code true} if this class loader was previously unknown, or {@code false} if it was already registered
*/
public boolean addLogApiClassLoader(ClassLoader apiClassLoader) {
return logApiClassLoaders.add(apiClassLoader);
}
/**
* Remove a class loader from the known log APIs set.
*
* @param apiClassLoader the API class loader
* @return {@code true} if the class loader was removed, or {@code false} if it was not known to this selector
*/
public boolean removeLogApiClassLoader(ClassLoader apiClassLoader) {
return logApiClassLoaders.remove(apiClassLoader);
}
/**
* Register a class loader with a log context. This method requires the {@code registerLogContext}
* {@link RuntimePermission}.
*
* @param classLoader the classloader
* @param logContext the log context
* @throws IllegalArgumentException if the classloader is already associated with a log context
*/
public void registerLogContext(ClassLoader classLoader, LogContext logContext) throws IllegalArgumentException {
if (contextMap.putIfAbsent(classLoader, logContext) != null) {
throw new IllegalArgumentException(
"ClassLoader instance is already registered to a log context (" + classLoader + ")");
}
}
/**
* Unregister a class loader/log context association. This method requires the {@code unregisterLogContext}
* {@link RuntimePermission}.
*
* @param classLoader the classloader
* @param logContext the log context
* @return {@code true} if the association exists and was removed, {@code false} otherwise
*/
public boolean unregisterLogContext(ClassLoader classLoader, LogContext logContext) {
return contextMap.remove(classLoader, logContext);
}
}

View file

@ -0,0 +1,142 @@
package org.xbib.logging;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import org.xbib.logging.util.CopyOnWriteMap;
import org.xbib.logging.util.StackWalkerUtil;
/**
* A log context selector which chooses a log context based on the caller's classloader.
*/
public final class ClassLoaderLogContextSelector implements LogContextSelector {
/**
* Construct a new instance. If no matching log context is found, the provided default selector is consulted.
*
* @param defaultSelector the selector to consult if no matching log context is found
*/
public ClassLoaderLogContextSelector(final LogContextSelector defaultSelector) {
this(defaultSelector, false);
}
/**
* Construct a new instance. If no matching log context is found, the provided default selector is consulted.
* <p/>
* If the {@code checkParentClassLoaders} is set to {@code true} this selector recursively searches the class loader
* parents until a match is found or a {@code null} parent is found.
*
* @param defaultSelector the selector to consult if no matching log context is found
* @param checkParentClassLoaders {@code true} if the {@link LogContext log context} could not
* found for the class loader and the {@link ClassLoader#getParent() parent class
* loader} should be checked
*/
public ClassLoaderLogContextSelector(final LogContextSelector defaultSelector, final boolean checkParentClassLoaders) {
this.defaultSelector = defaultSelector;
this.checkParentClassLoaders = checkParentClassLoaders;
}
/**
* Construct a new instance. If no matching log context is found, the system context is used.
*/
public ClassLoaderLogContextSelector() {
this(false);
}
/**
* Construct a new instance. If no matching log context is found, the system context is used.
* <p/>
* If the {@code checkParentClassLoaders} is set to {@code true} this selector recursively searches the class loader
* parents until a match is found or a {@code null} parent is found.
*
* @param checkParentClassLoaders {@code true} if the {@link LogContext log context} could not
* found for the class loader and the {@link ClassLoader#getParent() parent class
* loader} should be checked
*/
public ClassLoaderLogContextSelector(final boolean checkParentClassLoaders) {
this(LogContext.DEFAULT_LOG_CONTEXT_SELECTOR, checkParentClassLoaders);
}
private final LogContextSelector defaultSelector;
private final ConcurrentMap<ClassLoader, LogContext> contextMap = new CopyOnWriteMap<>();
private final Set<ClassLoader> logApiClassLoaders = Collections.newSetFromMap(new CopyOnWriteMap<>());
private final boolean checkParentClassLoaders;
private final Function<ClassLoader, LogContext> logContextFinder = new Function<>() {
@Override
public LogContext apply(final ClassLoader classLoader) {
final LogContext context = contextMap.get(classLoader);
if (context != null) {
return context;
}
final ClassLoader parent = classLoader.getParent();
if (parent != null && checkParentClassLoaders && !logApiClassLoaders.contains(parent)) {
return apply(parent);
}
return null;
}
};
/**
* {@inheritDoc} This instance will consult the call stack to see if any calling classloader is associated
* with any log context.
*/
public LogContext getLogContext() {
final LogContext result = StackWalkerUtil.logContextFinder(logApiClassLoaders, logContextFinder);
if (result != null) {
return result;
}
return defaultSelector.getLogContext();
}
/**
* Register a class loader which is a known log API, and thus should be skipped over when searching for the
* log context to use for the caller class.
*
* @param apiClassLoader the API class loader
* @return {@code true} if this class loader was previously unknown, or {@code false} if it was already registered
*/
public boolean addLogApiClassLoader(ClassLoader apiClassLoader) {
return logApiClassLoaders.add(apiClassLoader);
}
/**
* Remove a class loader from the known log APIs set.
*
* @param apiClassLoader the API class loader
* @return {@code true} if the class loader was removed, or {@code false} if it was not known to this selector
*/
public boolean removeLogApiClassLoader(ClassLoader apiClassLoader) {
return logApiClassLoaders.remove(apiClassLoader);
}
/**
* Register a class loader with a log context. This method requires the {@code registerLogContext}
* {@link RuntimePermission}.
*
* @param classLoader the classloader
* @param logContext the log context
* @throws IllegalArgumentException if the classloader is already associated with a log context
*/
public void registerLogContext(ClassLoader classLoader, LogContext logContext) throws IllegalArgumentException {
if (contextMap.putIfAbsent(classLoader, logContext) != null) {
throw new IllegalArgumentException(
"ClassLoader instance is already registered to a log context (" + classLoader + ")");
}
}
/**
* Unregister a class loader/log context association. This method requires the {@code unregisterLogContext}
* {@link RuntimePermission}.
*
* @param classLoader the classloader
* @param logContext the log context
* @return {@code true} if the association exists and was removed, {@code false} otherwise
*/
public boolean unregisterLogContext(ClassLoader classLoader, LogContext logContext) {
return contextMap.remove(classLoader, logContext);
}
}

View file

@ -0,0 +1,68 @@
package org.xbib.logging;
import java.util.concurrent.ConcurrentMap;
import org.xbib.logging.util.CopyOnWriteMap;
import static java.lang.Thread.currentThread;
/**
* A log context selector which chooses a log context based on the thread context classloader.
*/
public final class ContextClassLoaderLogContextSelector implements LogContextSelector {
/**
* Construct a new instance. If no matching log context is found, the provided default selector is consulted.
*
* @param defaultSelector the selector to consult if no matching log context is found
*/
public ContextClassLoaderLogContextSelector(final LogContextSelector defaultSelector) {
this.defaultSelector = defaultSelector;
}
/**
* Construct a new instance. If no matching log context is found, the system context is used.
*/
public ContextClassLoaderLogContextSelector() {
this(LogContext.DEFAULT_LOG_CONTEXT_SELECTOR);
}
private final LogContextSelector defaultSelector;
private final ConcurrentMap<ClassLoader, LogContext> contextMap = new CopyOnWriteMap<>();
public LogContext getLogContext() {
ClassLoader cl = currentThread().getContextClassLoader();
if (cl != null) {
final LogContext mappedContext = contextMap.get(cl);
if (mappedContext != null) {
return mappedContext;
}
}
return defaultSelector.getLogContext();
}
/**
* Register a class loader with a log context. This method requires the {@code registerLogContext}
* {@link RuntimePermission}.
*
* @param classLoader the classloader
* @param logContext the log context
* @throws IllegalArgumentException if the classloader is already associated with a log context
*/
public void registerLogContext(ClassLoader classLoader, LogContext logContext) throws IllegalArgumentException {
if (contextMap.putIfAbsent(classLoader, logContext) != null) {
throw new IllegalArgumentException(
"ClassLoader instance is already registered to a log context (" + classLoader + ")");
}
}
/**
* Unregister a class loader/log context association. This method requires the {@code unregisterLogContext}
* {@link RuntimePermission}.
*
* @param classLoader the classloader
* @param logContext the log context
* @return {@code true} if the association exists and was removed, {@code false} otherwise
*/
public boolean unregisterLogContext(ClassLoader classLoader, LogContext logContext) {
return contextMap.remove(classLoader, logContext);
}
}

View file

@ -0,0 +1,57 @@
package org.xbib.logging;
import java.util.logging.ErrorManager;
/**
* An extended error manager, which contains additional useful utilities for error managers.
*/
public abstract class ExtErrorManager extends ErrorManager {
public ExtErrorManager() {
}
/**
* Get the name corresponding to the given error code.
*
* @param code the error code
* @return the corresponding name (not {@code null})
*/
protected String nameForCode(int code) {
return switch (code) {
case CLOSE_FAILURE -> "CLOSE_FAILURE";
case FLUSH_FAILURE -> "FLUSH_FAILURE";
case FORMAT_FAILURE -> "FORMAT_FAILURE";
case GENERIC_FAILURE -> "GENERIC_FAILURE";
case OPEN_FAILURE -> "OPEN_FAILURE";
case WRITE_FAILURE -> "WRITE_FAILURE";
default -> "INVALID (" + code + ")";
};
}
public void error(final String msg, final Exception ex, final int code) {
super.error(msg, ex, code);
}
/**
* Convert the given error to a log record which can be published to handler(s) or stored. Care should
* be taken not to publish the log record to a logger that writes to the same handler that produced the error.
*
* @param msg the error message (possibly {@code null})
* @param ex the error exception (possibly {@code null})
* @param code the error code
* @return the log record (not {@code null})
*/
protected ExtLogRecord errorToLogRecord(String msg, Exception ex, int code) {
final ExtLogRecord record = new ExtLogRecord(Level.ERROR, "Failed to publish log record (%s[%d]): %s",
ExtLogRecord.FormatStyle.PRINTF, getClass().getName());
final String codeStr = nameForCode(code);
record.setParameters(new Object[]{
codeStr,
code,
msg,
});
record.setThrown(ex);
record.setLoggerName("");
return record;
}
}

View file

@ -0,0 +1,196 @@
package org.xbib.logging;
import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
/**
* A formatter which handles {@link ExtLogRecord ExtLogRecord} instances.
*/
public abstract class ExtFormatter extends Formatter {
/**
* Construct a new instance.
*/
public ExtFormatter() {
}
/**
* Wrap an existing formatter with an {@link ExtFormatter}, optionally replacing message formatting with
* the default extended message formatting capability.
*
* @param formatter the formatter to wrap (must not be {@code null})
* @param formatMessages {@code true} to replace message formatting, {@code false} to let the original formatter do it
* @return the extended formatter (not {@code null})
*/
public static ExtFormatter wrap(Formatter formatter, boolean formatMessages) {
if (formatter instanceof ExtFormatter && !formatMessages) {
return (ExtFormatter) formatter;
} else {
return new WrappedFormatter(formatter, formatMessages);
}
}
/**
* {@inheritDoc}
*/
public final String format(final LogRecord record) {
return format(ExtLogRecord.wrap(record));
}
/**
* Format a message using an extended log record.
*
* @param record the log record
* @return the formatted message
*/
public abstract String format(ExtLogRecord record);
@Override
public String formatMessage(LogRecord record) {
final ResourceBundle bundle = record.getResourceBundle();
String msg = record.getMessage();
if (msg == null) {
return null;
}
if (bundle != null) {
try {
msg = bundle.getString(msg);
} catch (MissingResourceException ex) {
// ignore
}
}
final Object[] parameters = record.getParameters();
if (parameters == null || parameters.length == 0) {
return formatMessageNone(record);
}
if (record instanceof ExtLogRecord extLogRecord) {
final ExtLogRecord.FormatStyle formatStyle = extLogRecord.getFormatStyle();
if (formatStyle == ExtLogRecord.FormatStyle.PRINTF) {
return formatMessagePrintf(record);
} else if (formatStyle == ExtLogRecord.FormatStyle.NO_FORMAT) {
return formatMessageNone(record);
}
}
return msg.indexOf('{') >= 0 ? formatMessageLegacy(record) : formatMessageNone(record);
}
/**
* Determines whether or not this formatter will require caller, source level, information when a log record is
* formatted.
*
* @return {@code true} if the formatter will need caller information, otherwise {@code false}
* @see LogRecord#getSourceClassName()
* @see ExtLogRecord#getSourceFileName()
* @see ExtLogRecord#getSourceLineNumber()
* @see LogRecord#getSourceMethodName()
*/
public boolean isCallerCalculationRequired() {
return true;
}
/**
* Format the message text as if there are no parameters. The default implementation delegates to
* {@link LogRecord#getMessage() record.getMessage()}.
*
* @param record the record to format
* @return the formatted string
*/
protected String formatMessageNone(LogRecord record) {
return record.getMessage();
}
/**
* Format the message text as if there are no parameters. The default implementation delegates to
* {@link MessageFormat#format(String, Object[]) MessageFormat.format(record.getMessage(),record.getParameters())}.
*
* @param record the record to format
* @return the formatted string
*/
protected String formatMessageLegacy(LogRecord record) {
return MessageFormat.format(record.getMessage(), record.getParameters());
}
/**
* Format the message text as if there are no parameters. The default implementation delegates to
* {@link String#format(String, Object[]) String.format(record.getMessage(),record.getParameters())}.
*
* @param record the record to format
* @return the formatted string
*/
protected String formatMessagePrintf(LogRecord record) {
return String.format(record.getMessage(), record.getParameters());
}
static class WrappedFormatter extends ExtFormatter {
private final Formatter formatter;
private final boolean formatMessages;
WrappedFormatter(Formatter formatter, boolean formatMessages) {
this.formatter = formatter;
this.formatMessages = formatMessages;
}
@Override
public String format(ExtLogRecord record) {
return formatter.format(record);
}
@Override
public String formatMessage(LogRecord record) {
return formatMessages ? super.formatMessage(record) : formatter.formatMessage(record);
}
@Override
public String getHead(Handler h) {
return formatter.getHead(h);
}
@Override
public String getTail(Handler h) {
return formatter.getTail(h);
}
}
/**
* A base class for formatters which wrap other formatters.
*/
public abstract static class Delegating extends ExtFormatter {
/**
* The delegate formatter.
*/
protected final ExtFormatter delegate;
/**
* Construct a new instance.
*
* @param delegate the delegate formatter (must not be {@code null})
*/
public Delegating(final ExtFormatter delegate) {
this.delegate = Objects.requireNonNull(delegate, "delegate");
}
public String format(final ExtLogRecord record) {
return delegate.format(record);
}
public String formatMessage(final LogRecord record) {
return delegate.formatMessage(record);
}
public boolean isCallerCalculationRequired() {
return delegate.isCallerCalculationRequired();
}
public String getHead(final Handler h) {
return delegate.getHead(h);
}
public String getTail(final Handler h) {
return delegate.getTail(h);
}
}
}

View file

@ -0,0 +1,494 @@
package org.xbib.logging;
import java.io.Flushable;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.ErrorManager;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.xbib.logging.errormanager.OnlyOnceErrorManager;
import org.xbib.logging.util.AtomicArray;
/**
* An extended logger handler. Use this class as a base class for log handlers which require {@code ExtLogRecord}
* instances.
*/
public abstract class ExtHandler extends Handler implements AutoCloseable, Flushable {
private static final ErrorManager DEFAULT_ERROR_MANAGER = new OnlyOnceErrorManager();
protected final ReentrantLock lock = new ReentrantLock();
// we keep our own copies of these fields so that they are protected with *our* lock:
private volatile Filter filter;
private volatile Formatter formatter;
private volatile Level level = Level.ALL;
private volatile ErrorManager errorManager;
// (skip `encoding` because we replace it with `charset` below)
private volatile boolean autoFlush = true;
private volatile boolean enabled = true;
private volatile boolean closeChildren;
private volatile Charset charset = StandardCharsets.UTF_8;
/**
* The sub-handlers for this handler. May only be updated using the {@link #handlersUpdater} atomic updater. The array
* instance should not be modified (treat as immutable).
*/
@SuppressWarnings("unused")
protected volatile Handler[] handlers;
/**
* The atomic updater for the {@link #handlers} field.
*/
private static final AtomicArray<ExtHandler, Handler> handlersUpdater =
AtomicArray.create(AtomicReferenceFieldUpdater.newUpdater(ExtHandler.class, Handler[].class, "handlers"), Handler.class);
/**
* Construct a new instance.
*/
protected ExtHandler() {
handlersUpdater.clear(this);
closeChildren = true;
errorManager = DEFAULT_ERROR_MANAGER;
}
/**
* {@inheritDoc}
*/
public void publish(final LogRecord record) {
if (enabled && record != null && isLoggable(record)) {
doPublish(ExtLogRecord.wrap(record));
}
}
/**
* Publish an {@code ExtLogRecord}.
* <p/>
* The logging request was made initially to a Logger object, which initialized the LogRecord and forwarded it here.
* <p/>
* The {@code ExtHandler} is responsible for formatting the message, when and if necessary. The formatting should
* include localization.
*
* @param record the log record to publish
*/
public void publish(final ExtLogRecord record) {
if (enabled && record != null && isLoggable(record))
try {
doPublish(record);
} catch (Exception e) {
reportError("Handler publication threw an exception", e, ErrorManager.WRITE_FAILURE);
} catch (Throwable ignored) {
}
}
/**
* Publish a log record to each nested handler.
*
* @param record the log record to publish
*/
@SuppressWarnings("deprecation") // record.getFormattedMessage()
protected void publishToNestedHandlers(final ExtLogRecord record) {
if (record != null) {
LogRecord oldRecord = null;
for (Handler handler : getHandlers())
try {
if (handler != null) {
if (handler instanceof ExtHandler || handler.getFormatter() instanceof ExtFormatter) {
handler.publish(record);
} else {
// old-style handlers generally don't know how to handle printf formatting
if (oldRecord == null) {
if (record.getFormatStyle() == ExtLogRecord.FormatStyle.PRINTF) {
// reformat it in a simple way, but only for legacy handler usage
oldRecord = new ExtLogRecord(record);
oldRecord.setMessage(record.getFormattedMessage());
oldRecord.setParameters(null);
} else {
oldRecord = record;
}
}
handler.publish(oldRecord);
}
}
} catch (Exception e) {
reportError(handler, "Nested handler publication threw an exception", e, ErrorManager.WRITE_FAILURE);
} catch (Throwable ignored) {
}
}
}
/**
* Do the actual work of publication; the record will have been filtered already. The default implementation
* does nothing except to flush if the {@code autoFlush} property is set to {@code true}; if this behavior is to be
* preserved in a subclass then this method should be called after the record is physically written.
*
* @param record the log record to publish
*/
protected void doPublish(final ExtLogRecord record) {
if (autoFlush)
flush();
}
/**
* Add a sub-handler to this handler. Some handler types do not utilize sub-handlers.
*
* @param handler the handler to add
*/
public void addHandler(Handler handler) {
if (handler == null) {
throw new NullPointerException("handler is null");
}
handlersUpdater.add(this, handler);
}
/**
* Remove a sub-handler from this handler. Some handler types do not utilize sub-handlers.
*
* @param handler the handler to remove
*/
public void removeHandler(Handler handler) {
if (handler == null) {
return;
}
handlersUpdater.remove(this, handler, true);
}
/**
* Get a copy of the sub-handlers array. Since the returned value is a copy, it may be freely modified.
*
* @return a copy of the sub-handlers array
*/
public Handler[] getHandlers() {
final Handler[] handlers = this.handlers;
return handlers.length > 0 ? handlers.clone() : handlers;
}
/**
* A convenience method to atomically get and clear all sub-handlers.
*
* @return the old sub-handler array
*/
public Handler[] clearHandlers() {
final Handler[] handlers = this.handlers;
handlersUpdater.clear(this);
return handlers.length > 0 ? handlers.clone() : handlers;
}
/**
* A convenience method to atomically get and replace the sub-handler array.
*
* @param newHandlers the new sub-handlers
* @return the old sub-handler array
*/
public Handler[] setHandlers(final Handler[] newHandlers) {
if (newHandlers == null) {
throw new IllegalArgumentException("newHandlers is null");
}
if (newHandlers.length == 0) {
return clearHandlers();
} else {
final Handler[] handlers = handlersUpdater.getAndSet(this, newHandlers);
return handlers.length > 0 ? handlers.clone() : handlers;
}
}
/**
* Determine if this handler will auto-flush.
*
* @return {@code true} if auto-flush is enabled
*/
public boolean isAutoFlush() {
return autoFlush;
}
/**
* Change the autoflush setting for this handler.
*
* @param autoFlush {@code true} to automatically flush after each write; {@code false} otherwise
*/
public void setAutoFlush(final boolean autoFlush) {
this.autoFlush = autoFlush;
if (autoFlush) {
flush();
}
}
/**
* Enables or disables the handler based on the value passed in.
*
* @param enabled {@code true} to enable the handler or {@code false} to disable the handler.
*/
public final void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
/**
* Determine if the handler is enabled.
*
* @return {@code true} if the handler is enabled, otherwise {@code false}.
*/
public final boolean isEnabled() {
return enabled;
}
/**
* Indicates whether or not children handlers should be closed when this handler is {@linkplain #close() closed}.
*
* @return {@code true} if the children handlers should be closed when this handler is closed, {@code false} if
* children handlers should not be closed when this handler is closed
*/
public boolean isCloseChildren() {
return closeChildren;
}
/**
* Sets whether or not children handlers should be closed when this handler is {@linkplain #close() closed}.
*
* @param closeChildren {@code true} if all children handlers should be closed when this handler is closed,
* {@code false} if children handlers will <em>not</em> be closed when this handler
* is closed
*/
public void setCloseChildren(final boolean closeChildren) {
this.closeChildren = closeChildren;
}
/**
* Flush all child handlers.
*/
@Override
public void flush() {
for (Handler handler : handlers)
try {
handler.flush();
} catch (Exception ex) {
reportError("Failed to flush child handler", ex, ErrorManager.FLUSH_FAILURE);
} catch (Throwable ignored) {
}
}
/**
* Close all child handlers.
*/
@Override
public void close() {
if (closeChildren) {
for (Handler handler : handlers)
try {
handler.close();
} catch (Exception ex) {
reportError("Failed to close child handler", ex, ErrorManager.CLOSE_FAILURE);
} catch (Throwable ignored) {
}
}
}
@Override
public void setFormatter(final Formatter newFormatter) {
Objects.requireNonNull(newFormatter);
lock.lock();
try {
formatter = newFormatter;
} finally {
lock.unlock();
}
}
@Override
public Formatter getFormatter() {
return formatter;
}
@Override
public void setFilter(final Filter newFilter) {
lock.lock();
try {
filter = newFilter;
} finally {
lock.unlock();
}
}
@Override
public Filter getFilter() {
return filter;
}
/**
* Set the handler's character set by name. This is roughly equivalent to calling {@link #setCharset(Charset)} with
* the results of {@link Charset#forName(String)}.
*
* @param encoding the name of the encoding
* @throws UnsupportedEncodingException if no character set could be found for the encoding name
*/
@Override
public void setEncoding(final String encoding) throws UnsupportedEncodingException {
if (encoding != null) {
try {
setCharset(Charset.forName(encoding));
} catch (IllegalArgumentException e) {
final UnsupportedEncodingException e2 = new UnsupportedEncodingException(
"Unable to set encoding to \"" + encoding + "\"");
e2.initCause(e);
throw e2;
}
} else {
setCharset(StandardCharsets.UTF_8);
}
}
/**
* Get the name of the {@linkplain #getCharset() handler's character set}.
*
* @return the handler character set name
*/
@Override
public String getEncoding() {
return getCharset().name();
}
/**
* Set the handler's character set. If not set, the handler's character set is initialized to the platform default
* character set.
*
* @param charset the character set (must not be {@code null})
*/
public void setCharset(final Charset charset) {
setCharsetPrivate(charset);
}
/**
* Set the handler's character set from within this handler. If not set, the handler's character set is initialized
* to the platform default character set.
*
* @param charset the character set (must not be {@code null})
*/
protected void setCharsetPrivate(final Charset charset) {
Objects.requireNonNull(charset, "charset");
lock.lock();
try {
this.charset = charset;
} finally {
lock.unlock();
}
}
/**
* Get the handler's character set.
*
* @return the character set in use (not {@code null})
*/
public Charset getCharset() {
return charset;
}
@Override
public void setErrorManager(final ErrorManager em) {
Objects.requireNonNull(em);
lock.lock();
try {
errorManager = em;
} finally {
lock.unlock();
}
}
@Override
public ErrorManager getErrorManager() {
return errorManager;
}
@Override
public void setLevel(final Level newLevel) {
Objects.requireNonNull(newLevel);
lock.lock();
try {
level = newLevel;
} finally {
lock.unlock();
}
}
@Override
public Level getLevel() {
return level;
}
/**
* Indicates whether or not the {@linkplain #getFormatter() formatter} associated with this handler or a formatter
* from a {@linkplain #getHandlers() child handler} requires the caller to be calculated.
* <p>
* Calculating the caller on a {@linkplain ExtLogRecord log record} can be an expensive operation. Some handlers
* may be required to copy some data from the log record, but may not need the caller information. If the
* {@linkplain #getFormatter() formatter} is a {@link ExtFormatter} the
* {@link ExtFormatter#isCallerCalculationRequired()} is used to determine if calculation of the caller is
* required.
* </p>
*
* @return {@code true} if the caller should be calculated, otherwise {@code false} if it can be skipped
* @see LogRecord#getSourceClassName()
* @see ExtLogRecord#getSourceFileName()
* @see ExtLogRecord#getSourceLineNumber()
* @see LogRecord#getSourceMethodName()
*/
@SuppressWarnings("WeakerAccess")
public boolean isCallerCalculationRequired() {
Formatter formatter = getFormatter();
if (formatterRequiresCallerCalculation(formatter)) {
return true;
} else
for (Handler handler : getHandlers()) {
if (handler instanceof ExtHandler) {
if (((ExtHandler) handler).isCallerCalculationRequired()) {
return true;
}
} else {
formatter = handler.getFormatter();
if (formatterRequiresCallerCalculation(formatter)) {
return true;
}
}
}
return false;
}
@Override
protected void reportError(String msg, Exception ex, int code) {
final ErrorManager errorManager = this.errorManager;
errorManager.error(msg, ex, code);
}
/**
* Report an error using a handler's specific error manager, if any.
*
* @param handler the handler
* @param msg the error message
* @param ex the exception
* @param code the error code
*/
public static void reportError(Handler handler, String msg, Exception ex, int code) {
if (handler != null) {
ErrorManager errorManager = handler.getErrorManager();
if (errorManager != null)
try {
errorManager.error(msg, ex, code);
} catch (Exception ex2) {
// use the same message as the JDK
System.err.println("Handler.reportError caught:");
ex2.printStackTrace();
}
}
}
private static boolean formatterRequiresCallerCalculation(final Formatter formatter) {
return formatter != null
&& (!(formatter instanceof ExtFormatter) || ((ExtFormatter) formatter).isCallerCalculationRequired());
}
}

View file

@ -0,0 +1,615 @@
package org.xbib.logging;
import java.text.MessageFormat;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.LogRecord;
import org.xbib.logging.os.HostName;
import org.xbib.logging.os.Process;
import org.xbib.logging.util.FastCopyHashMap;
import org.xbib.logging.util.StackWalkerUtil;
/**
* An extended log record, which includes additional information including MDC/NDC and correct
* caller location (even in the presence of a logging facade).
*/
@SuppressWarnings("serial")
public class ExtLogRecord extends LogRecord {
/**
* The format style to use.
*/
public enum FormatStyle {
/**
* Format the message using the {@link MessageFormat} parameter style.
*/
MESSAGE_FORMAT,
/**
* Format the message using the {@link java.util.Formatter} (also known as {@code printf()}) parameter style.
*/
PRINTF,
/**
* Do not format the message; parameters are ignored.
*/
NO_FORMAT,
}
/**
* Construct a new instance. Grabs the current NDC immediately. MDC is deferred.
*
* @param level a logging level value
* @param msg the raw non-localized logging message (may be null)
* @param loggerClassName the name of the logger class
*/
public ExtLogRecord(final java.util.logging.Level level, final String msg, final String loggerClassName) {
this(level, msg, FormatStyle.MESSAGE_FORMAT, loggerClassName);
}
/**
* Construct a new instance. Grabs the current NDC immediately. MDC is deferred.
*
* @param level a logging level value
* @param msg the raw non-localized logging message (may be null)
* @param formatStyle the parameter format style to use
* @param loggerClassName the name of the logger class
*/
public ExtLogRecord(final java.util.logging.Level level, final String msg, final FormatStyle formatStyle,
final String loggerClassName) {
super(level, msg);
this.formatStyle = formatStyle == null ? FormatStyle.MESSAGE_FORMAT : formatStyle;
this.loggerClassName = loggerClassName;
ndc = NDC.get();
threadName = Thread.currentThread().getName();
longThreadID = Thread.currentThread().threadId();
hostName = HostName.getQualifiedHostName();
processName = Process.getProcessName();
processId = ProcessHandle.current().pid();
}
/**
* Make a copy of a log record.
*
* @param original the original
*/
public ExtLogRecord(final ExtLogRecord original) {
super(original.getLevel(), original.getMessage());
// LogRecord fields
setLoggerName(original.getLoggerName());
setInstant(original.getInstant());
setParameters(original.getParameters());
setResourceBundle(original.getResourceBundle());
setResourceBundleName(original.getResourceBundleName());
setSequenceNumber(original.getSequenceNumber());
setThrown(original.getThrown());
if (!original.calculateCaller) {
setSourceClassName(original.getSourceClassName());
setSourceMethodName(original.getSourceMethodName());
sourceFileName = original.sourceFileName;
sourceLineNumber = original.sourceLineNumber;
sourceModuleName = original.sourceModuleName;
sourceModuleVersion = original.sourceModuleVersion;
}
setLongThreadID(original.getLongThreadID());
formatStyle = original.formatStyle;
marker = original.marker;
mdcCopy = original.mdcCopy;
ndc = original.ndc;
loggerClassName = original.loggerClassName;
threadName = original.threadName;
hostName = original.hostName;
processName = original.processName;
processId = original.processId;
}
/**
* Wrap a JDK log record. If the target record is already an {@code ExtLogRecord}, it is simply returned. Otherwise
* a wrapper record is created and returned.
*
* @param rec the original record
* @return the wrapped record
*/
public static ExtLogRecord wrap(LogRecord rec) {
if (rec == null) {
return null;
} else if (rec instanceof ExtLogRecord) {
return (ExtLogRecord) rec;
} else {
return new WrappedExtLogRecord(rec);
}
}
private final transient String loggerClassName;
private transient boolean calculateCaller = true;
private String ndc;
private FormatStyle formatStyle;
private FastCopyHashMap<String, Object> mdcCopy;
private int sourceLineNumber = -1;
private String sourceFileName;
private String threadName;
private String hostName;
private String processName;
private long processId = -1;
private String sourceModuleName;
private String sourceModuleVersion;
private Object marker;
private long longThreadID;
/**
* Disable caller calculation for this record. If the caller has already been calculated, leave it; otherwise
* set the caller to {@code "unknown"}.
*/
public void disableCallerCalculation() {
if (calculateCaller) {
setUnknownCaller();
}
}
/**
* Copy all fields and prepare this object to be passed to another thread. Calling this method
* more than once has no additional effect and will not incur extra copies.
*/
public void copyAll() {
copyMdc();
calculateCaller();
}
/**
* Copy the MDC. Call this method before passing this log record to another thread. Calling this method
* more than once has no additional effect and will not incur extra copies.
*/
public void copyMdc() {
if (mdcCopy == null) {
mdcCopy = FastCopyHashMap.of(MDC.getMDCProvider().copyObject());
}
}
/**
* Get the value of an MDC property.
*
* @param key the property key
* @return the property value
*/
public String getMdc(String key) {
final Map<String, Object> mdcCopy = this.mdcCopy;
if (mdcCopy == null) {
return MDC.get(key);
}
final Object value = mdcCopy.get(key);
return value == null ? null : value.toString();
}
/**
* Get a copy of all the MDC properties for this log record. If the MDC has not yet been copied, this method will copy it.
*
* @return a copy of the MDC map
*/
public Map<String, String> getMdcCopy() {
copyMdc();
// Create a new map with string values
final FastCopyHashMap<String, String> newMdc = new FastCopyHashMap<String, String>();
for (Map.Entry<String, Object> entry : mdcCopy.entrySet()) {
final String key = entry.getKey();
final Object value = entry.getValue();
newMdc.put(key, (value == null ? null : value.toString()));
}
return newMdc;
}
/**
* Change an MDC value on this record. If the MDC has not yet been copied, this method will copy it.
*
* @param key the key to set
* @param value the value to set it to
* @return the old value, if any
*/
public String putMdc(String key, String value) {
copyMdc();
final Object oldValue = mdcCopy.put(key, value);
return oldValue == null ? null : oldValue.toString();
}
/**
* Remove an MDC value on this record. If the MDC has not yet been copied, this method will copy it.
*
* @param key the key to remove
* @return the old value, if any
*/
public String removeMdc(String key) {
copyMdc();
final Object oldValue = mdcCopy.remove(key);
return oldValue == null ? null : oldValue.toString();
}
/**
* Create a new MDC using a copy of the source map.
*
* @param sourceMap the source man, must not be {@code null}
*/
public void setMdc(Map<?, ?> sourceMap) {
final FastCopyHashMap<String, Object> newMdc = new FastCopyHashMap<String, Object>();
for (Map.Entry<?, ?> entry : sourceMap.entrySet()) {
final Object key = entry.getKey();
final Object value = entry.getValue();
if (key != null && value != null) {
newMdc.put(key.toString(), value);
}
}
mdcCopy = newMdc;
}
/**
* Get the NDC for this log record.
*
* @return the NDC
*/
public String getNdc() {
return ndc;
}
/**
* Change the NDC for this log record.
*
* @param value the new NDC value
*/
public void setNdc(String value) {
ndc = value;
}
/**
* Get the class name of the logger which created this record.
*
* @return the class name
*/
public String getLoggerClassName() {
return loggerClassName;
}
/**
* Get the format style for the record.
*
* @return the format style
*/
public FormatStyle getFormatStyle() {
return formatStyle;
}
/**
* Find the first stack frame below the call to the logger, and populate the log record with that information.
*/
private void calculateCaller() {
if (!calculateCaller) {
return;
}
calculateCaller = false;
StackWalkerUtil.calculateCaller(this);
}
public void setUnknownCaller() {
setSourceClassName(null);
setSourceMethodName(null);
setSourceLineNumber(-1);
setSourceFileName(null);
setSourceModuleName(null);
setSourceModuleVersion(null);
}
/**
* Get the source line number for this log record.
* <p/>
* Note that this line number is not verified and may be spoofed. This information may either have been
* provided as part of the logging call, or it may have been inferred automatically by the logging framework. In the
* latter case, the information may only be approximate and may in fact describe an earlier call on the stack frame.
* May be -1 if no information could be obtained.
*
* @return the source line number
*/
public int getSourceLineNumber() {
calculateCaller();
return sourceLineNumber;
}
/**
* Set the source line number for this log record.
*
* @param sourceLineNumber the source line number
*/
public void setSourceLineNumber(final int sourceLineNumber) {
calculateCaller = false;
this.sourceLineNumber = sourceLineNumber;
}
/**
* Get the source file name for this log record.
* <p/>
* Note that this file name is not verified and may be spoofed. This information may either have been
* provided as part of the logging call, or it may have been inferred automatically by the logging framework. In the
* latter case, the information may only be approximate and may in fact describe an earlier call on the stack frame.
* May be {@code null} if no information could be obtained.
*
* @return the source file name
*/
public String getSourceFileName() {
calculateCaller();
return sourceFileName;
}
/**
* Set the source file name for this log record.
*
* @param sourceFileName the source file name
*/
public void setSourceFileName(final String sourceFileName) {
calculateCaller = false;
this.sourceFileName = sourceFileName;
}
/**
* {@inheritDoc}
*/
public String getSourceClassName() {
calculateCaller();
return super.getSourceClassName();
}
/**
* {@inheritDoc}
*/
public void setSourceClassName(final String sourceClassName) {
calculateCaller = false;
super.setSourceClassName(sourceClassName);
}
/**
* {@inheritDoc}
*/
public String getSourceMethodName() {
calculateCaller();
return super.getSourceMethodName();
}
/**
* {@inheritDoc}
*/
public void setSourceMethodName(final String sourceMethodName) {
calculateCaller = false;
super.setSourceMethodName(sourceMethodName);
}
/**
* Get the name of the module that initiated the logging request, if known.
*
* @return the name of the module that initiated the logging request
*/
public String getSourceModuleName() {
calculateCaller();
return sourceModuleName;
}
/**
* Set the source module name of this record.
*
* @param sourceModuleName the source module name
*/
public void setSourceModuleName(final String sourceModuleName) {
calculateCaller = false;
this.sourceModuleName = sourceModuleName;
}
/**
* Get the version of the module that initiated the logging request, if known.
*
* @return the version of the module that initiated the logging request
*/
public String getSourceModuleVersion() {
calculateCaller();
return sourceModuleVersion;
}
/**
* Set the source module version of this record.
*
* @param sourceModuleVersion the source module version
*/
public void setSourceModuleVersion(final String sourceModuleVersion) {
calculateCaller = false;
this.sourceModuleVersion = sourceModuleVersion;
}
/**
* Get the fully formatted log record, with resources resolved and parameters applied.
*
* @return the formatted log record
* @deprecated The formatter should normally be used to format the message contents.
*/
@Deprecated
public String getFormattedMessage() {
final ResourceBundle bundle = getResourceBundle();
String msg = getMessage();
if (msg == null)
return null;
if (bundle != null) {
try {
msg = bundle.getString(msg);
} catch (MissingResourceException ex) {
// ignore
}
}
final Object[] parameters = getParameters();
if (parameters == null || parameters.length == 0) {
return msg;
}
return switch (formatStyle) {
case PRINTF -> String.format(msg, parameters);
case MESSAGE_FORMAT -> msg.indexOf('{') >= 0 ? MessageFormat.format(msg, parameters) : msg;
default -> msg;
};
}
/**
* Get the resource key, if any. If the log message is not localized, then the key is {@code null}.
*
* @return the resource key
*/
public String getResourceKey() {
final String msg = getMessage();
if (msg == null)
return null;
if (getResourceBundleName() == null && getResourceBundle() == null)
return null;
return msg;
}
/**
* Get the thread name of this logging event.
*
* @return the thread name
*/
public String getThreadName() {
return threadName;
}
/**
* Set the thread name of this logging event.
*
* @param threadName the thread name
*/
public void setThreadName(final String threadName) {
this.threadName = threadName;
}
/**
* Get the host name of the record, if known.
*
* @return the host name of the record, if known
*/
public String getHostName() {
return hostName;
}
/**
* Set the host name of the record.
*
* @param hostName the host name of the record
*/
public void setHostName(final String hostName) {
this.hostName = hostName;
}
/**
* Get the process name of the record, if known.
*
* @return the process name of the record, if known
*/
public String getProcessName() {
return processName;
}
/**
* Set the process name of the record.
*
* @param processName the process name of the record
*/
public void setProcessName(final String processName) {
this.processName = processName;
}
/**
* Get the process ID of the record, if known.
*
* @return the process ID of the record, or -1 if not known
*/
public long getProcessId() {
return processId;
}
/**
* Set the process ID of the record.
*
* @param processId the process ID of the record
*/
public void setProcessId(final long processId) {
this.processId = processId;
}
/**
* Set the raw message. Any cached formatted message is discarded. The parameter format is set to be
* {@link MessageFormat}-style.
*
* @param message the new raw message
*/
public void setMessage(final String message) {
setMessage(message, FormatStyle.MESSAGE_FORMAT);
}
/**
* Set the raw message. Any cached formatted message is discarded. The parameter format is set according to the
* given argument.
*
* @param message the new raw message
* @param formatStyle the format style to use
*/
public void setMessage(final String message, final FormatStyle formatStyle) {
this.formatStyle = formatStyle == null ? FormatStyle.MESSAGE_FORMAT : formatStyle;
super.setMessage(message);
}
/**
* Set the parameters to the log message. Any cached formatted message is discarded.
*
* @param parameters the log message parameters. (may be null)
*/
public void setParameters(final Object[] parameters) {
super.setParameters(parameters);
}
/**
* Set the localization resource bundle. Any cached formatted message is discarded.
*
* @param bundle localization bundle (may be null)
*/
public void setResourceBundle(final ResourceBundle bundle) {
super.setResourceBundle(bundle);
}
/**
* Set the localization resource bundle name. Any cached formatted message is discarded.
*
* @param name localization bundle name (may be null)
*/
public void setResourceBundleName(final String name) {
super.setResourceBundleName(name);
}
/**
* Set the marker for this event. Markers are used mostly by SLF4J and Log4j.
*/
public void setMarker(Object marker) {
this.marker = marker;
}
public Object getMarker() {
return marker;
}
@Deprecated
@SuppressWarnings("deprecation")
@Override
public void setThreadID(final int threadID) {
super.setThreadID(threadID);
this.longThreadID = threadID;
}
@Override
public long getLongThreadID() {
return longThreadID;
}
@Override
public ExtLogRecord setLongThreadID(final long id) {
this.longThreadID = id;
return this;
}
}

View file

@ -0,0 +1,23 @@
package org.xbib.logging;
/**
* Log4j-like levels.
*/
@SuppressWarnings("serial")
public final class Level extends java.util.logging.Level {
private Level(final String name, final int value) {
super(name, value);
}
private Level(final String name, final int value, final String resourceBundleName) {
super(name, value, resourceBundleName);
}
public static final Level FATAL = new Level("FATAL", 1100);
public static final Level ERROR = new Level("ERROR", 1000);
public static final Level WARN = new Level("WARN", 900);
public static final Level INFO = new Level("INFO", 800);
public static final Level DEBUG = new Level("DEBUG", 500);
public static final Level TRACE = new Level("TRACE", 400);
}

View file

@ -0,0 +1,557 @@
package org.xbib.logging;
import java.io.IOException;
import java.lang.invoke.ConstantBootstraps;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import org.xbib.logging.ref.Reference;
import org.xbib.logging.ref.References;
import org.xbib.logging.util.CopyOnWriteMap;
import org.xbib.logging.util.CopyOnWriteWeakMap;
/**
* A logging context, for producing isolated logging environments.
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public final class LogContext implements AutoCloseable {
private static final LogContext SYSTEM_CONTEXT = new LogContext(false, discoverDefaultInitializer());
private static LogContextInitializer discoverDefaultInitializer() {
return discoverDefaultInitializer0();
}
private static LogContextInitializer discoverDefaultInitializer0() {
// allow exceptions to bubble up, otherwise logging won't work with no indication as to why
final ServiceLoader<LogContextInitializer> loader =
ServiceLoader.load(LogContextInitializer.class, LogContext.class.getClassLoader());
final Iterator<LogContextInitializer> iterator = loader.iterator();
return iterator.hasNext() ? iterator.next() : LogContextInitializer.DEFAULT;
}
private final LoggerNode rootLogger;
private final boolean strong;
private final LogContextInitializer initializer;
private final Set<LoggerNode> pinnedSet;
// must not be final because of VarHandle
private Map<Logger.AttachmentKey<?>, Object> attachments;
private static final VarHandle attachmentHandle =
ConstantBootstraps.fieldVarHandle(MethodHandles.lookup(), "attachments", VarHandle.class, LogContext.class, Map.class);
/**
* This lazy holder class is required to prevent a problem due to a LogContext instance being constructed
* before the class init is complete.
*/
private static final class LazyHolder {
private static final HashMap<String, Reference<Level, Void>> INITIAL_LEVEL_MAP;
private LazyHolder() {
}
private static void addStrong(Map<String, Reference<Level, Void>> map, Level level) {
map.put(level.getName().toUpperCase(), References.create(Reference.Type.STRONG, level, null));
}
static {
final HashMap<String, Reference<Level, Void>> map = new HashMap<String, Reference<Level, Void>>();
addStrong(map, Level.OFF);
addStrong(map, Level.ALL);
addStrong(map, Level.SEVERE);
addStrong(map, Level.WARNING);
addStrong(map, Level.CONFIG);
addStrong(map, Level.INFO);
addStrong(map, Level.FINE);
addStrong(map, Level.FINER);
addStrong(map, Level.FINEST);
addStrong(map, org.xbib.logging.Level.FATAL);
addStrong(map, org.xbib.logging.Level.ERROR);
addStrong(map, org.xbib.logging.Level.WARN);
addStrong(map, org.xbib.logging.Level.INFO);
addStrong(map, org.xbib.logging.Level.DEBUG);
addStrong(map, org.xbib.logging.Level.TRACE);
INITIAL_LEVEL_MAP = map;
}
}
private final AtomicReference<Map<String, Reference<Level, Void>>> levelMapReference;
private final Set<AutoCloseable> closeHandlers;
/**
* This lock is taken any time a change is made which affects multiple nodes in the hierarchy.
*/
final ReentrantLock treeLock = new ReentrantLock();
LogContext(final boolean strong, LogContextInitializer initializer) {
this.initializer = initializer;
this.strong = strong || initializer.useStrongReferences();
levelMapReference = new AtomicReference<Map<String, Reference<Level, Void>>>(LazyHolder.INITIAL_LEVEL_MAP);
rootLogger = new LoggerNode(this);
closeHandlers = new LinkedHashSet<>();
attachments = Map.of();
pinnedSet = this.strong ? Set.of() : ConcurrentHashMap.newKeySet();
}
/**
* Create a new log context.
* {@link RuntimePermission RuntimePermission} to invoke this method.
*
* @param strong {@code true} if the context should use strong references, {@code false} to use (default) weak
* references for automatic logger GC
* @return a new log context
*/
public static LogContext create(boolean strong) {
return create(strong, LogContextInitializer.DEFAULT);
}
/**
* Create a new log context.
* {@link RuntimePermission RuntimePermission} to invoke this method.
*
* @param strong {@code true} if the context should use strong references, {@code false} to use (default) weak
* references for automatic logger GC
* @param initializer the log context initializer to use (must not be {@code null})
* @return a new log context
*/
public static LogContext create(boolean strong, LogContextInitializer initializer) {
return new LogContext(strong, initializer);
}
/**
* Create a new log context.
* {@link RuntimePermission RuntimePermission} to invoke this method.
*
* @return a new log context
*/
public static LogContext create() {
return create(false);
}
/**
* Create a new log context.
* {@link RuntimePermission RuntimePermission} to invoke this method.
*
* @param initializer the log context initializer to use (must not be {@code null})
* @return a new log context
*/
public static LogContext create(LogContextInitializer initializer) {
return create(false, initializer);
}
// Attachment mgmt
/**
* Get the attachment value for a given key, or {@code null} if there is no such attachment.
* Log context attachments are placed on the root logger and can also be accessed there.
*
* @param key the key
* @param <V> the attachment value type
* @return the attachment, or {@code null} if there is none for this key
*/
@SuppressWarnings("unchecked")
public <V> V getAttachment(Logger.AttachmentKey<V> key) {
return (V) attachments.get(key);
}
/**
* Attach an object to this log context under a given key.
* A strong reference is maintained to the key and value for as long as this log context exists.
* Log context attachments are placed on the root logger and can also be accessed there.
*
* @param key the attachment key
* @param value the attachment value
* @param <V> the attachment value type
* @return the old attachment, if there was one
* @throws IllegalArgumentException if the attachment cannot be added because the maximum has been reached
*/
@SuppressWarnings("unchecked")
public <V> V attach(Logger.AttachmentKey<V> key, V value) {
Map<Logger.AttachmentKey<?>, Object> oldAttachments;
Map<Logger.AttachmentKey<?>, Object> newAttachments;
V old;
do {
oldAttachments = attachments;
newAttachments = new HashMap<>(oldAttachments);
old = (V) newAttachments.put(key, value);
} while (!attachmentHandle.compareAndSet(this, oldAttachments, Map.copyOf(newAttachments)));
return old;
}
/**
* Attach an object to this log context under a given key, if such an attachment does not already exist.
* A strong reference is maintained to the key and value for as long as this log context exists.
* Log context attachments are placed on the root logger and can also be accessed there.
*
* @param key the attachment key
* @param value the attachment value
* @param <V> the attachment value type
* @return the current attachment, if there is one, or {@code null} if the value was successfully attached
* @throws IllegalArgumentException if the attachment cannot be added because the maximum has been reached
*/
@SuppressWarnings("unchecked")
public <V> V attachIfAbsent(Logger.AttachmentKey<V> key, V value) {
Map<Logger.AttachmentKey<?>, Object> oldAttachments;
Map<Logger.AttachmentKey<?>, Object> newAttachments;
do {
oldAttachments = attachments;
if (oldAttachments.containsKey(key)) {
return (V) oldAttachments.get(key);
}
newAttachments = new HashMap<>(oldAttachments);
newAttachments.put(key, value);
} while (!attachmentHandle.compareAndSet(this, oldAttachments, Map.copyOf(newAttachments)));
return null;
}
/**
* Remove an attachment.
* Log context attachments are placed on the root logger and can also be accessed there.
*
* @param key the attachment key
* @param <V> the attachment value type
* @return the old value, or {@code null} if there was none
*/
@SuppressWarnings("unchecked")
public <V> V detach(Logger.AttachmentKey<V> key) {
Map<Logger.AttachmentKey<?>, Object> oldAttachments;
Map<Logger.AttachmentKey<?>, Object> newAttachments;
V result;
do {
oldAttachments = attachments;
result = (V) oldAttachments.get(key);
if (result == null) {
return null;
}
final int size = oldAttachments.size();
if (size == 1) {
// special case - the new map is empty
newAttachments = Map.of();
} else {
newAttachments = new HashMap<>(oldAttachments);
newAttachments.remove(key);
}
} while (!attachmentHandle.compareAndSet(this, oldAttachments, Map.copyOf(newAttachments)));
return result;
}
/**
* Get a logger with the given name from this logging context.
*
* @param name the logger name
* @return the logger instance
* @see java.util.logging.LogManager#getLogger(String)
*/
public Logger getLogger(String name) {
return rootLogger.getOrCreate(name).createLogger();
}
/**
* Get a logger with the given name from this logging context, if a logger node exists at that location.
*
* @param name the logger name
* @return the logger instance, or {@code null} if no such logger node exists
*/
public Logger getLoggerIfExists(String name) {
final LoggerNode node = rootLogger.getIfExists(name);
return node == null ? null : node.createLogger();
}
/**
* Get a logger attachment for a logger name, if it exists.
*
* @param loggerName the logger name
* @param key the attachment key
* @param <V> the attachment value type
* @return the attachment or {@code null} if the logger or the attachment does not exist
*/
public <V> V getAttachment(String loggerName, Logger.AttachmentKey<V> key) {
final LoggerNode node = rootLogger.getIfExists(loggerName);
if (node == null)
return null;
return node.getAttachment(key);
}
/**
* Get the level for a name.
*
* @param name the name
* @return the level
* @throws IllegalArgumentException if the name is not known
*/
public Level getLevelForName(String name) throws IllegalArgumentException {
if (name != null) {
final Map<String, Reference<Level, Void>> map = levelMapReference.get();
final Reference<Level, Void> levelRef = map.get(name);
if (levelRef != null) {
final Level level = levelRef.get();
if (level != null) {
return level;
}
}
}
throw new IllegalArgumentException("Unknown level \"" + name + "\"");
}
/**
* Register a level instance with this log context. The level can then be looked up by name. Only a weak
* reference to the level instance will be kept. Any previous level registration for the given level's name
* will be overwritten.
*
* @param level the level to register
*/
public void registerLevel(Level level) {
registerLevel(level, false);
}
/**
* Register a level instance with this log context. The level can then be looked up by name. Any previous level
* registration for the given level's name will be overwritten.
*
* @param level the level to register
* @param strong {@code true} to strongly reference the level, or {@code false} to weakly reference it
*/
public void registerLevel(Level level, boolean strong) {
for (; ; ) {
final Map<String, Reference<Level, Void>> oldLevelMap = levelMapReference.get();
final Map<String, Reference<Level, Void>> newLevelMap = new HashMap<>(oldLevelMap.size());
for (Map.Entry<String, Reference<Level, Void>> entry : oldLevelMap.entrySet()) {
final String name = entry.getKey();
final Reference<Level, Void> levelRef = entry.getValue();
if (levelRef.get() != null) {
newLevelMap.put(name, levelRef);
}
}
newLevelMap.put(level.getName(),
References.create(strong ? Reference.Type.STRONG : Reference.Type.WEAK, level, null));
if (levelMapReference.compareAndSet(oldLevelMap, newLevelMap)) {
return;
}
}
}
/**
* Unregister a previously registered level. Log levels that are not registered may still be used, they just will
* not be findable by name.
*
* @param level the level to unregister
*/
public void unregisterLevel(Level level) {
for (; ; ) {
final Map<String, Reference<Level, Void>> oldLevelMap = levelMapReference.get();
final Reference<Level, Void> oldRef = oldLevelMap.get(level.getName());
if (oldRef == null || oldRef.get() != level) {
// not registered, or the registration expired naturally
return;
}
final Map<String, Reference<Level, Void>> newLevelMap = new HashMap<>(oldLevelMap.size());
for (Map.Entry<String, Reference<Level, Void>> entry : oldLevelMap.entrySet()) {
final String name = entry.getKey();
final Reference<Level, Void> levelRef = entry.getValue();
final Level oldLevel = levelRef.get();
if (oldLevel != null && oldLevel != level) {
newLevelMap.put(name, levelRef);
}
}
newLevelMap.put(level.getName(), References.create(Reference.Type.WEAK, level, null));
if (levelMapReference.compareAndSet(oldLevelMap, newLevelMap)) {
return;
}
}
}
/**
* Get the system log context.
*
* @return the system log context
*/
public static LogContext getSystemLogContext() {
return SYSTEM_CONTEXT;
}
/**
* The default log context selector, which always returns the system log context.
*/
public static final LogContextSelector DEFAULT_LOG_CONTEXT_SELECTOR = new LogContextSelector() {
public LogContext getLogContext() {
return SYSTEM_CONTEXT;
}
};
private static volatile LogContextSelector logContextSelector = DEFAULT_LOG_CONTEXT_SELECTOR;
/**
* Get the currently active log context.
*
* @return the currently active log context
*/
public static LogContext getLogContext() {
return logContextSelector.getLogContext();
}
/**
* Set a new log context selector.
* {@code "setLogContextSelector"}
* {@link RuntimePermission RuntimePermission} to invoke this method.
*
* @param newSelector the new selector.
*/
public static void setLogContextSelector(LogContextSelector newSelector) {
if (newSelector == null) {
throw new NullPointerException("newSelector is null");
}
logContextSelector = newSelector;
}
/**
* Returns the currently set log context selector.
*
* @return the log context selector
*/
public static LogContextSelector getLogContextSelector() {
return logContextSelector;
}
@Override
public void close() throws IOException {
treeLock.lock();
try {
// First we want to close all loggers
recursivelyClose(rootLogger);
// Next process the close handlers associated with this log context
for (AutoCloseable handler : closeHandlers) {
try {
handler.close();
} catch (Exception e) {
throw new IOException(e);
}
}
final Map<?, ?> oldAttachments = (Map<?, ?>) attachmentHandle.getAndSet(this, Map.of());
for (Object value : oldAttachments.values()) {
if (value instanceof AutoCloseable) {
try {
((AutoCloseable) value).close();
} catch (Exception ignore) {
}
}
}
if (!pinnedSet.isEmpty()) {
pinnedSet.clear();
}
} finally {
treeLock.unlock();
}
}
/**
* Returns an enumeration of the logger names that have been created. This does not return names of loggers that
* may have been garbage collected. Logger names added after the enumeration has been retrieved may also be added to
* the enumeration.
*
* @return an enumeration of the logger names
* @see java.util.logging.LogManager#getLoggerNames()
*/
public Enumeration<String> getLoggerNames() {
return rootLogger.getLoggerNames();
}
/**
* Adds a handler invoked during the {@linkplain #close() close} of this log context. The close handlers will be
* invoked in the order they are added.
* <p>
* The loggers associated with this context will always be closed.
* </p>
*
* @param closeHandler the close handler to use
*/
public void addCloseHandler(final AutoCloseable closeHandler) {
treeLock.lock();
try {
closeHandlers.add(closeHandler);
} finally {
treeLock.unlock();
}
}
/**
* Gets the current close handlers associated with this log context.
*
* @return the current close handlers
*/
public Set<AutoCloseable> getCloseHandlers() {
treeLock.lock();
try {
return new LinkedHashSet<>(closeHandlers);
} finally {
treeLock.unlock();
}
}
/**
* Clears any current close handlers associated with log context, then adds the handlers to be invoked during
* the {@linkplain #close() close} of this log context. The close handlers will be invoked in the order they are
* added.
* <p>
* The loggers associated with this context will always be closed.
* </p>
*
* @param closeHandlers the close handlers to use
*/
public void setCloseHandlers(final Collection<AutoCloseable> closeHandlers) {
treeLock.lock();
try {
this.closeHandlers.clear();
this.closeHandlers.addAll(closeHandlers);
} finally {
treeLock.unlock();
}
}
public LoggerNode getRootLoggerNode() {
return rootLogger;
}
public ConcurrentMap<String, LoggerNode> createChildMap() {
return strong ? new CopyOnWriteMap<>() : new CopyOnWriteWeakMap<>();
}
public boolean pin(LoggerNode node) {
return !strong && pinnedSet.add(node);
}
public boolean unpin(LoggerNode node) {
return !strong && pinnedSet.remove(node);
}
public LogContextInitializer getInitializer() {
return initializer;
}
private void recursivelyClose(final LoggerNode loggerNode) {
assert treeLock.isHeldByCurrentThread();
for (LoggerNode child : loggerNode.getChildren()) {
recursivelyClose(child);
}
loggerNode.close();
}
}

View file

@ -0,0 +1,25 @@
package org.xbib.logging;
import java.io.InputStream;
/**
* A configurator for a log context. A log context configurator should set up all the log categories,
* handlers, formatters, filters, attachments, and other constructs as specified by the configuration.
*/
public interface LogContextConfigurator {
/**
* Configure the given log context according to this configurator's policy. If a configuration stream was
* provided, that is passed in to this method to be used or ignored. The stream should remain open after
* this method is called.
*
* @param logContext the log context to configure (not {@code null})
* @param inputStream the input stream that was requested to be used, or {@code null} if none was provided
*/
void configure(LogContext logContext, InputStream inputStream);
/**
* A constant representing an empty configuration. The configurator does nothing.
*/
LogContextConfigurator EMPTY = (logContext, inputStream) -> {
};
}

View file

@ -0,0 +1,25 @@
package org.xbib.logging;
/**
* Used to create a {@link LogContextConfigurator}. The {@linkplain #priority() priority} is used to determine which
* factory should be used. The lowest priority factory is used. If two factories have the same priority the second
* factory will not be used. The order of loading the factories for determining priority is done via the
* {@link java.util.ServiceLoader#load(Class, ClassLoader)}.
*/
public interface LogContextConfiguratorFactory {
/**
* Creates the {@link LogContextConfigurator}.
*
* @return the log context configurator
*/
LogContextConfigurator create();
/**
* The priority for the factory which is used to determine which factory should be used to create a
* {@link LogContextConfigurator}. The lowest priority factory will be used.
*
* @return the priority for this factory
*/
int priority();
}

View file

@ -0,0 +1,77 @@
package org.xbib.logging;
import java.util.logging.Handler;
import java.util.logging.Level;
/**
* An initializer for log contexts. The initializer provides initial values for log instances within the context
* for properties like levels, handlers, and so on.
* <p>
* The initial log context will be configured using a context initializer that is located via the
* {@linkplain java.util.ServiceLoader JDK SPI mechanism}.
* <p>
* This interface is intended to be forward-extensible. If new methods are added, they will include a default implementation.
* Implementations of this interface should accommodate the possibility of new methods being added; as a matter of convention,
* such methods should begin with the prefix {@code getInitial}, which will minimize the possibility of conflict.
*/
public interface LogContextInitializer {
/**
* An array containing zero handlers.
*/
Handler[] NO_HANDLERS = new Handler[0];
/**
* The default log context initializer, which is used when none is specified. This instance uses only
* default implementations for all the given methods.
*/
LogContextInitializer DEFAULT = new LogContextInitializer() {
};
/**
* Get the initial level for the given logger name. If the initializer returns a {@code null} level for the
* root logger, then a level of {@link org.xbib.logging.Level#INFO INFO} will be used.
* <p>
* The default implementation returns {@code null}.
*
* @param loggerName the logger name (must not be {@code null})
* @return the level to use, or {@code null} to inherit the level from the parent
*/
default Level getInitialLevel(String loggerName) {
return null;
}
/**
* Get the minimum (most verbose) level allowed for the given logger name. If the initializer returns a
* {@code null} level for the root logger, then a level of {@link Level#ALL ALL} will be used.
* <p>
* The default implementation returns {@code null}.
*
* @param loggerName the logger name (must not be {@code null})
* @return the level to use, or {@code null} to inherit the level from the parent
*/
default Level getMinimumLevel(String loggerName) {
return null;
}
/**
* Get the initial set of handlers to configure for the given logger name.
* <p>
* The default implementation returns {@link #NO_HANDLERS}. A value of {@code null} is considered
* to be the same as {@link #NO_HANDLERS}.
*
* @param loggerName the logger name (must not be {@code null})
* @return the handlers to use (should not be {@code null})
*/
default Handler[] getInitialHandlers(String loggerName) {
return NO_HANDLERS;
}
/**
* Establish whether strong references should be used for logger nodes.
*
* @return {@code true} to use strong references, or {@code false} to use weak references
*/
default boolean useStrongReferences() {
return false;
}
}

View file

@ -0,0 +1,15 @@
package org.xbib.logging;
/**
* A mechanism for determining what the current log context is. This method is used primarily when constructing
* new loggers to determine what context the constructed logger should be installed into.
*/
public interface LogContextSelector {
/**
* Get the current log context.
*
* @return the current log context
*/
LogContext getLogContext();
}

View file

@ -0,0 +1,186 @@
package org.xbib.logging;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.xbib.logging.configuration.PropertyLogContextConfigurator;
import org.xbib.logging.util.StandardOutputStreams;
/**
* Simplified log manager. Designed to work around the (many) design flaws of the JDK platform log manager.
*/
public final class LogManager extends java.util.logging.LogManager {
static {
try {
// Ensure the StandardOutputStreams are initialized early to capture the current System.out and System.err.
Class.forName(StandardOutputStreams.class.getName());
} catch (ClassNotFoundException ignore) {
// ignore
}
}
/**
* Construct a new logmanager instance. Attempts to plug a known memory leak in {@link java.util.logging.Level} as
* well.
*/
public LogManager() {
}
// Configuration
private final AtomicReference<LogContextConfigurator> configuratorRef = new AtomicReference<>();
/**
* Configure the system log context initially.
*/
public void readConfiguration() {
doConfigure(null);
}
/**
* Configure the system log context initially withe given input stream.
*
* @param inputStream ignored
*/
public void readConfiguration(InputStream inputStream) {
doConfigure(inputStream);
}
private void doConfigure(InputStream inputStream) {
final AtomicReference<LogContextConfigurator> configuratorRef = this.configuratorRef;
LogContextConfigurator configurator = configuratorRef.get();
if (configurator == null) {
synchronized (configuratorRef) {
configurator = configuratorRef.get();
if (configurator == null) {
int best = Integer.MAX_VALUE;
LogContextConfiguratorFactory factory = null;
final ServiceLoader<LogContextConfiguratorFactory> serviceLoader =
ServiceLoader.load(LogContextConfiguratorFactory.class, LogManager.class.getClassLoader());
final Iterator<LogContextConfiguratorFactory> iterator = serviceLoader.iterator();
List<Throwable> problems = null;
for (; ; )
try {
if (!iterator.hasNext())
break;
final LogContextConfiguratorFactory f = iterator.next();
if (f.priority() < best || factory == null) {
best = f.priority();
factory = f;
}
} catch (Throwable t) {
if (problems == null)
problems = new ArrayList<>(4);
problems.add(t);
}
configurator = factory == null ? null : factory.create();
if (configurator == null) {
if (problems == null) {
configuratorRef.set(configurator = new PropertyLogContextConfigurator());
} else {
final ServiceConfigurationError e = new ServiceConfigurationError(
"Failed to configure log configurator service");
for (Throwable problem : problems) {
e.addSuppressed(problem);
}
throw e;
}
}
}
}
}
configurator.configure(LogContext.getSystemLogContext(), inputStream);
}
/**
* Does nothing.
*
* @param mapper not used
*/
public void updateConfiguration(final Function<String, BiFunction<String, String, String>> mapper) throws IOException {
// no operation the configuration API should be used
}
/**
* Does nothing.
*
* @param ins not used
* @param mapper not used
*/
public void updateConfiguration(final InputStream ins, final Function<String, BiFunction<String, String, String>> mapper)
throws IOException {
// no operation the configuration API should be used
}
/**
* Configuration listeners are not currently supported.
*
* @param listener not used
* @return this log manager
*/
public java.util.logging.LogManager addConfigurationListener(final Runnable listener) {
// no operation
return this;
}
/**
* Configuration listeners are not currently supported.
*
* @param listener not used
*/
public void removeConfigurationListener(final Runnable listener) {
// no operation
}
/**
* Does nothing. Properties are not supported.
*
* @param name ignored
* @return {@code null}
*/
public String getProperty(String name) {
// no properties
return null;
}
/**
* Does nothing. This method only causes trouble.
*/
public void reset() {
// no operation!
}
@Override
public Enumeration<String> getLoggerNames() {
return LogContext.getLogContext().getLoggerNames();
}
/**
* Do nothing. Loggers are only added/acquired via {@link #getLogger(String)}.
*
* @param logger ignored
* @return {@code false}
*/
public boolean addLogger(java.util.logging.Logger logger) {
return false;
}
/**
* Get or create a logger with the given name.
*
* @param name the logger name
* @return the corresponding logger
*/
public Logger getLogger(String name) {
return LogContext.getLogContext().getLogger(name);
}
}

View file

@ -0,0 +1,987 @@
package org.xbib.logging;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.function.Supplier;
import java.util.logging.Filter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
/**
* An actual logger instance. This is the end-user interface into the logging system.
*/
public final class Logger extends java.util.logging.Logger {
private static final ResourceBundle TOMBSTONE = new ResourceBundle() {
@Override
protected Object handleGetObject(final String key) {
return null;
}
@Override
public Enumeration<String> getKeys() {
return null;
}
};
/**
* The named logger tree node.
*/
private final LoggerNode loggerNode;
/**
* The resource bundle for this logger.
*/
private volatile ResourceBundle resourceBundle;
private static final String LOGGER_CLASS_NAME = Logger.class.getName();
/**
* Static logger factory method which returns a LogManager logger.
*
* @param name the logger name
* @return the logger
*/
public static Logger getLogger(final String name) {
return LogContext.getLogContext().getLogger(name);
}
/**
* Static logger factory method which returns a LogManager logger.
*
* @param name the logger name
* @param bundleName the bundle name
* @return the logger
*/
public static Logger getLogger(final String name, final String bundleName) {
final Logger logger = LogContext.getLogContext().getLogger(name);
logger.resourceBundle = ResourceBundle.getBundle(bundleName, Locale.getDefault(), Logger.class.getClassLoader());
return logger;
}
/**
* Construct a new instance of an actual logger.
*
* @param loggerNode the node in the named logger tree
* @param name the fully-qualified name of this node
*/
Logger(final LoggerNode loggerNode, final String name) {
// Don't set up the bundle in the parent...
super(name, null);
// We have to propagate our level to an internal data structure in the superclass
super.setLevel(loggerNode.getLevel());
this.loggerNode = loggerNode;
}
// Filter mgmt
/**
* {@inheritDoc}
*/
public void setFilter(Filter filter) {
loggerNode.setFilter(filter);
}
/**
* {@inheritDoc}
*/
public Filter getFilter() {
return loggerNode.getFilter();
}
// Level mgmt
/**
* {@inheritDoc} This implementation grabs a lock, so that only one thread may update the log level of any
* logger at a time, in order to allow readers to never block (though there is a window where retrieving the
* log level reflects an older effective level than the actual level).
*/
public void setLevel(Level newLevel) {
// We have to propagate our level to an internal data structure in the superclass
super.setLevel(newLevel);
loggerNode.setLevel(newLevel);
}
/**
* Set the log level by name. Uses the parent logging context's name registry; otherwise behaves
* identically to {@link #setLevel(Level)}.
*
* @param newLevelName the name of the level to set
*/
public void setLevelName(String newLevelName) {
setLevel(loggerNode.getContext().getLevelForName(newLevelName));
}
/**
* Get the effective numerical log level, inherited from the parent.
*
* @return the effective level
*/
public int getEffectiveLevel() {
return loggerNode.getEffectiveLevel();
}
/**
* {@inheritDoc}
*/
public Level getLevel() {
return loggerNode.getLevel();
}
/**
* {@inheritDoc}
*/
public boolean isLoggable(Level level) {
return loggerNode.isLoggableLevel(level.intValue());
}
// Attachment mgmt
/**
* Get the attachment value for a given key, or {@code null} if there is no such attachment.
*
* @param key the key
* @param <V> the attachment value type
* @return the attachment, or {@code null} if there is none for this key
*/
@SuppressWarnings({"unchecked"})
public <V> V getAttachment(AttachmentKey<V> key) {
return loggerNode.getAttachment(key);
}
/**
* Attach an object to this logger under a given key.
* A strong reference is maintained to the key and value for as long as this logger exists.
*
* @param key the attachment key
* @param value the attachment value
* @param <V> the attachment value type
* @return the old attachment, if there was one
* @throws IllegalArgumentException if the attachment cannot be added because the maximum has been reached
*/
public <V> V attach(AttachmentKey<V> key, V value) {
return loggerNode.attach(key, value);
}
/**
* Attach an object to this logger under a given key, if such an attachment does not already exist.
* A strong reference is maintained to the key and value for as long as this logger exists.
*
* @param key the attachment key
* @param value the attachment value
* @param <V> the attachment value type
* @return the current attachment, if there is one, or {@code null} if the value was successfully attached
* @throws IllegalArgumentException if the attachment cannot be added because the maximum has been reached
*/
@SuppressWarnings({"unchecked"})
public <V> V attachIfAbsent(AttachmentKey<V> key, V value) {
return loggerNode.attachIfAbsent(key, value);
}
/**
* Remove an attachment.
*
* @param key the attachment key
* @param <V> the attachment value type
* @return the old value, or {@code null} if there was none
*/
@SuppressWarnings({"unchecked"})
public <V> V detach(AttachmentKey<V> key) {
return loggerNode.detach(key);
}
// Handler mgmt
/**
* {@inheritDoc}
*/
public void addHandler(Handler handler) {
if (handler == null) {
throw new NullPointerException("handler is null");
}
loggerNode.addHandler(handler);
}
/**
* {@inheritDoc}
*/
public void removeHandler(Handler handler) {
if (handler == null) {
return;
}
loggerNode.removeHandler(handler);
}
/**
* {@inheritDoc}
*/
public Handler[] getHandlers() {
final Handler[] handlers = loggerNode.getHandlers();
return handlers.length > 0 ? handlers.clone() : handlers;
}
/**
* A convenience method to atomically replace the handler list for this logger.
*
* @param handlers the new handlers
*/
public void setHandlers(final Handler[] handlers) {
final Handler[] safeHandlers = handlers.clone();
for (Handler handler : safeHandlers) {
if (handler == null) {
throw new IllegalArgumentException("A handler is null");
}
}
loggerNode.setHandlers(safeHandlers);
}
/**
* Atomically get and set the handler list for this logger.
*
* @param handlers the new handler set
* @return the old handler set
*/
public Handler[] getAndSetHandlers(final Handler[] handlers) {
final Handler[] safeHandlers = handlers.clone();
for (Handler handler : safeHandlers) {
if (handler == null) {
throw new IllegalArgumentException("A handler is null");
}
}
return loggerNode.setHandlers(safeHandlers);
}
/**
* Atomically compare and set the handler list for this logger.
*
* @param expected the expected list of handlers
* @param newHandlers the replacement list of handlers
* @return {@code true} if the handler list was updated or {@code false} if the current handlers did not match the expected
* handlers list
*/
public boolean compareAndSetHandlers(final Handler[] expected, final Handler[] newHandlers) {
final Handler[] safeExpectedHandlers = expected.clone();
final Handler[] safeNewHandlers = newHandlers.clone();
for (Handler handler : safeNewHandlers) {
if (handler == null) {
throw new IllegalArgumentException("A handler is null");
}
}
Handler[] oldHandlers;
do {
oldHandlers = loggerNode.getHandlers();
if (!Arrays.equals(oldHandlers, safeExpectedHandlers)) {
return false;
}
} while (!loggerNode.compareAndSetHandlers(oldHandlers, safeNewHandlers));
return true;
}
/**
* A convenience method to atomically get and clear all handlers.
*/
public Handler[] clearHandlers() {
return loggerNode.clearHandlers();
}
/**
* {@inheritDoc}
*/
public void setUseParentHandlers(boolean useParentHandlers) {
loggerNode.setUseParentHandlers(useParentHandlers);
}
/**
* {@inheritDoc}
*/
public boolean getUseParentHandlers() {
return loggerNode.getUseParentHandlers();
}
/**
* Specify whether or not filters should be inherited from parent loggers.
* <p>
* Setting this value to {@code false} has the same behaviour as {@linkplain java.util.logging.Logger}.
* </p>
*
* @param useParentFilter {@code true} to inherit a parents filter, otherwise {@code false}
*/
public void setUseParentFilters(final boolean useParentFilter) {
loggerNode.setUseParentFilters(useParentFilter);
}
/**
* Indicates whether or not this logger inherits filters from it's parent logger.
*
* @return {@code true} if filters are inherited, otherwise {@code false}
*/
public boolean getUseParentFilters() {
return loggerNode.getUseParentFilters();
}
// Parent/child
/**
* {@inheritDoc}
*/
public Logger getParent() {
final LoggerNode parentNode = loggerNode.getParent();
return parentNode == null ? null : parentNode.createLogger();
}
/**
* <b>Not allowed.</b> This method may never be called.
*/
public void setParent(java.util.logging.Logger parent) {
throw new UnsupportedOperationException();
}
/**
* Get the log context to which this logger belongs.
*
* @return the log context
*/
public LogContext getLogContext() {
return loggerNode.getContext();
}
// Logger
static final int OFF_INT = Level.OFF.intValue();
static final int SEVERE_INT = Level.SEVERE.intValue();
static final int WARNING_INT = Level.WARNING.intValue();
static final int INFO_INT = Level.INFO.intValue();
static final int CONFIG_INT = Level.CONFIG.intValue();
static final int FINE_INT = Level.FINE.intValue();
static final int FINER_INT = Level.FINER.intValue();
static final int FINEST_INT = Level.FINEST.intValue();
/**
* {@inheritDoc}
*/
public void log(LogRecord record) {
if (!loggerNode.isLoggableLevel(record.getLevel().intValue())) {
return;
}
logRaw(record);
}
/**
* {@inheritDoc}
*/
public void entering(final String sourceClass, final String sourceMethod) {
if (!loggerNode.isLoggableLevel(FINER_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINER, "ENTRY", LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void entering(final String sourceClass, final String sourceMethod, final Object param1) {
if (!loggerNode.isLoggableLevel(FINER_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINER, "ENTRY {0}", LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
rec.setParameters(new Object[]{param1});
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void entering(final String sourceClass, final String sourceMethod, final Object[] params) {
if (!loggerNode.isLoggableLevel(FINER_INT)) {
return;
}
final StringBuilder builder = new StringBuilder("ENTRY");
if (params != null)
for (int i = 0; i < params.length; i++) {
builder.append(" {").append(i).append('}');
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINER, builder.toString(), LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
if (params != null)
rec.setParameters(params);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void exiting(final String sourceClass, final String sourceMethod) {
if (!loggerNode.isLoggableLevel(FINER_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINER, "RETURN", LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void exiting(final String sourceClass, final String sourceMethod, final Object result) {
if (!loggerNode.isLoggableLevel(FINER_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINER, "RETURN {0}", LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
rec.setParameters(new Object[]{result});
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void throwing(final String sourceClass, final String sourceMethod, final Throwable thrown) {
if (!loggerNode.isLoggableLevel(FINER_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINER, "THROW", LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
rec.setThrown(thrown);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void severe(final String msg) {
if (!loggerNode.isLoggableLevel(SEVERE_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.SEVERE, msg, LOGGER_CLASS_NAME);
logRaw(rec);
}
@Override
public void severe(final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(SEVERE_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.SEVERE, msgSupplier.get(), LOGGER_CLASS_NAME);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void warning(final String msg) {
if (!loggerNode.isLoggableLevel(WARNING_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.WARNING, msg, LOGGER_CLASS_NAME);
logRaw(rec);
}
@Override
public void warning(final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(WARNING_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.WARNING, msgSupplier.get(), LOGGER_CLASS_NAME);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void info(final String msg) {
if (!loggerNode.isLoggableLevel(INFO_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.INFO, msg, LOGGER_CLASS_NAME);
logRaw(rec);
}
@Override
public void info(final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(INFO_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.INFO, msgSupplier.get(), LOGGER_CLASS_NAME);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void config(final String msg) {
if (!loggerNode.isLoggableLevel(CONFIG_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.CONFIG, msg, LOGGER_CLASS_NAME);
logRaw(rec);
}
@Override
public void config(final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(CONFIG_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.CONFIG, msgSupplier.get(), LOGGER_CLASS_NAME);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void fine(final String msg) {
if (!loggerNode.isLoggableLevel(FINE_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINE, msg, LOGGER_CLASS_NAME);
logRaw(rec);
}
@Override
public void fine(final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(FINE_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINE, msgSupplier.get(), LOGGER_CLASS_NAME);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void finer(final String msg) {
if (!loggerNode.isLoggableLevel(FINER_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINER, msg, LOGGER_CLASS_NAME);
logRaw(rec);
}
@Override
public void finer(final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(FINER_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINER, msgSupplier.get(), LOGGER_CLASS_NAME);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void finest(final String msg) {
if (!loggerNode.isLoggableLevel(FINEST_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINEST, msg, LOGGER_CLASS_NAME);
logRaw(rec);
}
@Override
public void finest(final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(FINEST_INT)) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(Level.FINEST, msgSupplier.get(), LOGGER_CLASS_NAME);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void log(final Level level, final String msg) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msg, LOGGER_CLASS_NAME);
logRaw(rec);
}
@Override
public void log(final Level level, final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msgSupplier.get(), LOGGER_CLASS_NAME);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void log(final Level level, final String msg, final Object param1) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msg, LOGGER_CLASS_NAME);
rec.setParameters(new Object[]{param1});
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void log(final Level level, final String msg, final Object[] params) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msg, LOGGER_CLASS_NAME);
if (params != null)
rec.setParameters(params);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void log(final Level level, final String msg, final Throwable thrown) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msg, LOGGER_CLASS_NAME);
rec.setThrown(thrown);
logRaw(rec);
}
@Override
public void log(final Level level, final Throwable thrown, final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msgSupplier.get(), LOGGER_CLASS_NAME);
rec.setThrown(thrown);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msg, LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
logRaw(rec);
}
@Override
public void logp(final Level level, final String sourceClass, final String sourceMethod,
final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msgSupplier.get(), LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg,
final Object param1) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msg, LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
rec.setParameters(new Object[]{param1});
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg,
final Object[] params) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msg, LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
if (params != null)
rec.setParameters(params);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg,
final Throwable thrown) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msg, LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
rec.setThrown(thrown);
logRaw(rec);
}
@Override
public void logp(final Level level, final String sourceClass, final String sourceMethod, final Throwable thrown,
final Supplier<String> msgSupplier) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, msgSupplier.get(), LOGGER_CLASS_NAME);
rec.setSourceClassName(sourceClass);
rec.setSourceMethodName(sourceMethod);
rec.setThrown(thrown);
logRaw(rec);
}
/**
* {@inheritDoc}
*/
@Deprecated(since = "3.0", forRemoval = true)
public void logrb(final Level level, final String sourceClass, final String sourceMethod, final String bundleName,
final String msg) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
// No local check is needed here as this will delegate to log(LogRecord)
super.logrb(level, sourceClass, sourceMethod, bundleName, msg);
}
/**
* {@inheritDoc}
*/
@Deprecated(since = "3.0", forRemoval = true)
public void logrb(final Level level, final String sourceClass, final String sourceMethod, final String bundleName,
final String msg, final Object param1) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
// No local check is needed here as this will delegate to log(LogRecord)
super.logrb(level, sourceClass, sourceMethod, bundleName, msg, param1);
}
/**
* {@inheritDoc}
*/
@Deprecated(since = "3.0", forRemoval = true)
public void logrb(final Level level, final String sourceClass, final String sourceMethod, final String bundleName,
final String msg, final Object[] params) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
// No local check is needed here as this will delegate to log(LogRecord)
super.logrb(level, sourceClass, sourceMethod, bundleName, msg, params);
}
/**
* {@inheritDoc}
*/
@Deprecated(since = "3.0", forRemoval = true)
public void logrb(final Level level, final String sourceClass, final String sourceMethod, final String bundleName,
final String msg, final Throwable thrown) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
// No local check is needed here as this will delegate to log(LogRecord)
super.logrb(level, sourceClass, sourceMethod, bundleName, msg, thrown);
}
@Override
public void logrb(final Level level, final String sourceClass, final String sourceMethod, final ResourceBundle bundle,
final String msg, final Object... params) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
// No local check is needed here as this will delegate to log(LogRecord)
super.logrb(level, sourceClass, sourceMethod, bundle, msg, params);
}
@Override
public void logrb(final Level level, final ResourceBundle bundle, final String msg, final Object... params) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
// No local check is needed here as this will delegate to log(LogRecord)
super.logrb(level, bundle, msg, params);
}
@Override
public void logrb(final Level level, final String sourceClass, final String sourceMethod, final ResourceBundle bundle,
final String msg, final Throwable thrown) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
// No local check is needed here as this will delegate to log(LogRecord)
super.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown);
}
@Override
public void logrb(final Level level, final ResourceBundle bundle, final String msg, final Throwable thrown) {
if (!loggerNode.isLoggableLevel(level.intValue())) {
return;
}
// No local check is needed here as this will delegate to log(LogRecord)
super.logrb(level, bundle, msg, thrown);
}
// alternate SPI hooks
/**
* SPI interface method to log a message at a given level, with a specific resource bundle.
*
* @param fqcn the fully qualified class name of the first logger class
* @param level the level to log at
* @param message the message
* @param bundleName the resource bundle name
* @param style the message format style
* @param params the log parameters
* @param t the throwable, if any
*/
public void log(final String fqcn, final Level level, final String message, final String bundleName,
final ExtLogRecord.FormatStyle style, final Object[] params, final Throwable t) {
if (level == null || fqcn == null || message == null
|| !loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, message, style, fqcn);
rec.setResourceBundleName(bundleName);
rec.setParameters(params);
rec.setThrown(t);
logRaw(rec);
}
/**
* SPI interface method to log a message at a given level.
*
* @param fqcn the fully qualified class name of the first logger class
* @param level the level to log at
* @param message the message
* @param style the message format style
* @param params the log parameters
* @param t the throwable, if any
*/
public void log(final String fqcn, final Level level, final String message, final ExtLogRecord.FormatStyle style,
final Object[] params, final Throwable t) {
if (level == null || fqcn == null || message == null
|| !loggerNode.isLoggableLevel(level.intValue())) {
return;
}
final ExtLogRecord rec = new ExtLogRecord(level, message, style, fqcn);
rec.setParameters(params);
rec.setThrown(t);
logRaw(rec);
}
/**
* SPI interface method to log a message at a given level.
*
* @param fqcn the fully qualified class name of the first logger class
* @param level the level to log at
* @param message the message
* @param t the throwable, if any
*/
public void log(final String fqcn, final Level level, final String message, final Throwable t) {
log(fqcn, level, message, ExtLogRecord.FormatStyle.MESSAGE_FORMAT, null, t);
}
/**
* Do the logging with no level checks (they've already been done).
*
* @param record the extended log record
*/
public void logRaw(final ExtLogRecord record) {
record.setLoggerName(getName());
final ResourceBundle bundle = getResourceBundle();
if (bundle != null) {
record.setResourceBundleName(bundle.getBaseBundleName());
record.setResourceBundle(bundle);
}
try {
if (!loggerNode.isLoggable(record)) {
return;
}
} catch (VirtualMachineError e) {
throw e;
} catch (Throwable t) {
// todo - error handler
// treat an errored filter as "pass" (I guess?)
}
loggerNode.publish(record);
}
/**
* Set the resource bundle for this logger.
*
* @param resourceBundle the resource bundle (must not be {@code null})
*/
@Override
public void setResourceBundle(ResourceBundle resourceBundle) {
super.setResourceBundle(resourceBundle);
synchronized (this) {
this.resourceBundle = resourceBundle;
}
}
/**
* Get the resource bundle for this logger.
*
* @return the resource bundle, or {@code null} if none is configured for this logger
*/
@Override
public ResourceBundle getResourceBundle() {
if (resourceBundle == null) {
synchronized (this) {
if (resourceBundle == null) {
resourceBundle = super.getResourceBundle();
if (resourceBundle == null) {
resourceBundle = TOMBSTONE;
}
}
}
}
return resourceBundle == TOMBSTONE ? null : resourceBundle;
}
/**
* Do the logging with no level checks (they've already been done). Creates an extended log record if the
* provided record is not one.
*
* @param record the log record
*/
public void logRaw(final LogRecord record) {
logRaw(ExtLogRecord.wrap(record));
}
/**
* An attachment key instance.
*
* @param <V> the attachment value type
*/
@SuppressWarnings({"UnusedDeclaration"})
public static final class AttachmentKey<V> {
/**
* Construct a new instance.
*/
public AttachmentKey() {
}
}
public String toString() {
return "Logger '" + getName() + "' in context " + loggerNode.getContext();
}
}

View file

@ -0,0 +1,123 @@
package org.xbib.logging;
import java.util.EnumMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
/**
* Implements the {@code System.LoggerFinder}. It will make an attempt to set the {@code java.util.logging.manager}
* system property before a logger is accessed.
*/
public class LoggerFinder extends System.LoggerFinder {
private static final Map<System.Logger.Level, java.util.logging.Level> LEVELS = new EnumMap<>(System.Logger.Level.class);
private static final AtomicBoolean LOGGED = new AtomicBoolean(false);
private static volatile boolean PROPERTY_SET = false;
static {
LEVELS.put(System.Logger.Level.ALL, Level.ALL);
LEVELS.put(System.Logger.Level.TRACE, Level.TRACE);
LEVELS.put(System.Logger.Level.DEBUG, Level.DEBUG);
LEVELS.put(System.Logger.Level.INFO, Level.INFO);
LEVELS.put(System.Logger.Level.WARNING, Level.WARN);
LEVELS.put(System.Logger.Level.ERROR, Level.ERROR);
LEVELS.put(System.Logger.Level.OFF, Level.OFF);
}
public LoggerFinder() {
super();
}
@Override
public System.Logger getLogger(final String name, final Module module) {
if (!PROPERTY_SET) {
synchronized (this) {
if (!PROPERTY_SET) {
if (System.getProperty("java.util.logging.manager") == null) {
System.setProperty("java.util.logging.manager", "org.xbib.logging.LogManager");
}
}
PROPERTY_SET = true;
}
}
final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(name);
if (!logger.getClass().getName().equals("org.xbib.logging.Logger")) {
if (LOGGED.compareAndSet(false, true)) {
logger.log(Level.ERROR,
"The LogManager accessed before the \"java.util.logging.manager\" system property was set to \"org.xbib.loggingr.LogManager\". Results may be unexpected.");
}
}
return new SystemLogger(logger);
}
private static class SystemLogger implements System.Logger {
private static final String LOGGER_CLASS_NAME = SystemLogger.class.getName();
private final java.util.logging.Logger delegate;
private SystemLogger(final java.util.logging.Logger delegate) {
this.delegate = delegate;
}
@Override
public String getName() {
return delegate.getName();
}
@Override
public boolean isLoggable(final Level level) {
return delegate.isLoggable(LEVELS.getOrDefault(level, java.util.logging.Level.INFO));
}
public void log(final Level level, final String msg) {
log(level, null, msg, (Object[]) null);
}
public void log(final Level level, final Supplier<String> msgSupplier) {
if (isLoggable(level)) {
log(level, null, msgSupplier.get(), (Object[]) null);
}
}
public void log(final Level level, final Object obj) {
if (isLoggable(level)) {
this.log(level, null, obj.toString(), (Object[]) null);
}
}
public void log(final Level level, final String msg, final Throwable thrown) {
this.log(level, null, msg, thrown);
}
public void log(final Level level, final Supplier<String> msgSupplier, final Throwable thrown) {
if (isLoggable(level)) {
this.log(level, null, msgSupplier.get(), thrown);
}
}
public void log(final Level level, final String format, final Object... params) {
this.log(level, null, format, params);
}
@Override
public void log(final Level level, final ResourceBundle bundle, final String msg, final Throwable thrown) {
final ExtLogRecord record = new ExtLogRecord(LEVELS.getOrDefault(level, java.util.logging.Level.INFO), msg,
LOGGER_CLASS_NAME);
record.setThrown(thrown);
record.setResourceBundle(bundle);
delegate.log(record);
}
@Override
public void log(final Level level, final ResourceBundle bundle, final String format, final Object... params) {
final ExtLogRecord record = new ExtLogRecord(LEVELS.getOrDefault(level, java.util.logging.Level.INFO), format,
ExtLogRecord.FormatStyle.MESSAGE_FORMAT, LOGGER_CLASS_NAME);
record.setParameters(params);
record.setResourceBundle(bundle);
delegate.log(record);
}
}
}

View file

@ -0,0 +1,621 @@
package org.xbib.logging;
import java.lang.invoke.ConstantBootstraps;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.ErrorManager;
import java.util.logging.Filter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.xbib.logging.ref.PhantomReference;
import org.xbib.logging.ref.Reaper;
import org.xbib.logging.ref.Reference;
import org.xbib.logging.util.AtomicArray;
import org.xbib.logging.util.StandardOutputStreams;
/**
* A node in the tree of logger names. Maintains weak references to children and a strong reference to its parent.
*/
public final class LoggerNode implements AutoCloseable {
private static final Reaper<Logger, LoggerNode> REAPER = reference ->
reference.getAttachment().activeLoggers.remove(reference);
private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0];
/**
* The log context.
*/
private final LogContext context;
/**
* The parent node, or {@code null} if this is the root logger node.
*/
private final LoggerNode parent;
/**
* The fully-qualified name of this logger.
*/
private final String fullName;
/**
* The map of names to child nodes. The child node references are weak.
*/
private final ConcurrentMap<String, LoggerNode> children;
/**
* The handlers for this logger. May only be updated using the {@link #handlersUpdater} atomic updater. The array
* instance should not be modified (treat as immutable).
*/
@SuppressWarnings({"UnusedDeclaration"})
private volatile Handler[] handlers;
/**
* Flag to specify whether parent handlers are used.
*/
private volatile boolean useParentHandlers = true;
/**
* The filter for this logger instance.
*/
private volatile Filter filter;
/**
* Flag to specify whether parent filters are used.
*/
private volatile boolean useParentFilter = false;
/**
* The set of phantom references to active loggers.
*/
private final Set<Reference<Logger, LoggerNode>> activeLoggers =
ConcurrentHashMap.newKeySet();
// must not be final
private Map<Logger.AttachmentKey<?>, Object> attachments;
private static final VarHandle attachmentHandle =
ConstantBootstraps.fieldVarHandle(MethodHandles.lookup(), "attachments", VarHandle.class, LoggerNode.class, Map.class);
/**
* The atomic updater for the {@link #handlers} field.
*/
private static final AtomicArray<LoggerNode, Handler> handlersUpdater =
AtomicArray.create(AtomicReferenceFieldUpdater.newUpdater(LoggerNode.class, Handler[].class, "handlers"), Handler.class);
/**
* The actual level. May only be modified when the context's level change lock is held; in addition, changing
* this field must be followed immediately by recursively updating the effective loglevel of the child tree.
*/
private volatile Level level;
/**
* The effective level. May only be modified when the context's level change lock is held; in addition, changing
* this field must be followed immediately by recursively updating the effective loglevel of the child tree.
*/
private volatile int effectiveLevel;
/**
* The effective minimum level, which may not be modified.
*/
private final int effectiveMinLevel;
/**
* Construct a new root instance.
*
* @param context the logmanager
*/
public LoggerNode(final LogContext context) {
parent = null;
fullName = "";
this.context = context;
final LogContextInitializer initializer = context.getInitializer();
final Level minLevel = initializer.getMinimumLevel(fullName);
effectiveMinLevel = Objects.requireNonNullElse(minLevel, Level.ALL).intValue();
final Level initialLevel = initializer.getInitialLevel(fullName);
if (initialLevel != null) {
level = initialLevel;
effectiveLevel = initialLevel.intValue();
} else {
effectiveLevel = Logger.INFO_INT;
}
handlers = safeCloneHandlers(initializer.getInitialHandlers(fullName));
children = context.createChildMap();
attachments = Map.of();
}
/**
* Construct a child instance.
*
* @param context the logmanager
* @param parent the parent node
* @param nodeName the name of this subnode
*/
private LoggerNode(LogContext context, LoggerNode parent, String nodeName) {
nodeName = nodeName.trim();
if (nodeName.isEmpty() && parent == null) {
throw new IllegalArgumentException("nodeName is empty, or just whitespace and has no parent");
}
this.parent = parent;
if (parent.parent == null) {
if (nodeName.isEmpty()) {
fullName = ".";
} else {
fullName = nodeName;
}
} else {
fullName = parent.fullName + "." + nodeName;
}
this.context = context;
final LogContextInitializer initializer = context.getInitializer();
final Level minLevel = initializer.getMinimumLevel(fullName);
effectiveMinLevel = minLevel != null ? minLevel.intValue() : parent.effectiveMinLevel;
final Level initialLevel = initializer.getInitialLevel(fullName);
if (initialLevel != null) {
level = initialLevel;
effectiveLevel = initialLevel.intValue();
} else {
effectiveLevel = parent.effectiveLevel;
}
handlers = safeCloneHandlers(initializer.getInitialHandlers(fullName));
children = context.createChildMap();
attachments = Map.of();
}
public static Handler[] safeCloneHandlers(Handler... initialHandlers) {
if (initialHandlers == null || initialHandlers.length == 0) {
return LogContextInitializer.NO_HANDLERS;
}
final Handler[] clone = initialHandlers.clone();
final int length = clone.length;
for (int i = 0; i < length; i++) {
if (clone[i] == null) {
// our clone contains nulls; we have to clone again to be safe
int cnt;
for (cnt = 1, i++; i < length; i++) {
if (clone[i] == null)
cnt++;
}
final int newLen = length - cnt;
if (newLen == 0) {
return LogContextInitializer.NO_HANDLERS;
}
final Handler[] newClone = new Handler[newLen];
for (int j = 0, k = 0; j < length; j++) {
if (clone[j] != null)
newClone[k++] = clone[j];
}
return newClone;
}
}
// original contained no nulls, so carry on
return clone;
}
@Override
public void close() {
final ReentrantLock treeLock = context.treeLock;
treeLock.lock();
try {
// Reset everything to defaults
filter = null;
if ("".equals(fullName)) {
level = Level.INFO;
effectiveLevel = Level.INFO.intValue();
} else {
level = null;
effectiveLevel = parent.effectiveLevel;
}
handlersUpdater.clear(this);
useParentFilter = false;
useParentHandlers = true;
attachmentHandle.setVolatile(this, Map.of());
children.clear();
} finally {
treeLock.unlock();
}
}
/**
* Get or create a relative logger node. The name is relatively qualified to this node.
*
* @param name the name
* @return the corresponding logger node
*/
public LoggerNode getOrCreate(final String name) {
if (name == null || name.isEmpty()) {
return this;
} else {
int i = name.indexOf('.');
final String nextName = i == -1 ? name : name.substring(0, i);
LoggerNode nextNode = children.get(nextName);
if (nextNode == null) {
nextNode = new LoggerNode(context, this, nextName);
LoggerNode appearingNode = children.putIfAbsent(nextName, nextNode);
if (appearingNode != null) {
nextNode = appearingNode;
}
}
if (i == -1) {
return nextNode;
} else {
return nextNode.getOrCreate(name.substring(i + 1));
}
}
}
/**
* Get a relative logger, if it exists.
*
* @param name the name
* @return the corresponding logger
*/
public LoggerNode getIfExists(final String name) {
if (name == null || name.isEmpty()) {
return this;
} else {
int i = name.indexOf('.');
final String nextName = i == -1 ? name : name.substring(0, i);
LoggerNode nextNode = children.get(nextName);
if (nextNode == null) {
return null;
}
if (i == -1) {
return nextNode;
} else {
return nextNode.getIfExists(name.substring(i + 1));
}
}
}
public Logger createLogger() {
final Logger logger = new Logger(this, fullName);
activeLoggers.add(new PhantomReference<>(logger, LoggerNode.this, REAPER));
return logger;
}
/**
* Get the children of this logger.
*
* @return the children
*/
public Collection<LoggerNode> getChildren() {
return children.values();
}
/**
* Get the log context.
*
* @return the log context
*/
public LogContext getContext() {
return context;
}
/**
* Update the effective level if it is inherited from a parent. Must only be called while the logmanager's level
* change lock is held.
*
* @param newLevel the new effective level
*/
public void setEffectiveLevel(int newLevel) {
if (level == null) {
effectiveLevel = newLevel;
for (LoggerNode node : children.values()) {
if (node != null) {
node.setEffectiveLevel(newLevel);
}
}
}
}
public void setFilter(final Filter filter) {
this.filter = filter;
if (filter != null) {
context.pin(this);
}
}
public Filter getFilter() {
return filter;
}
public boolean getUseParentFilters() {
return useParentFilter;
}
public void setUseParentFilters(final boolean useParentFilter) {
this.useParentFilter = useParentFilter;
if (useParentFilter) {
context.pin(this);
}
}
public int getEffectiveLevel() {
// this can be inlined
return effectiveLevel;
}
public boolean isLoggableLevel(int level) {
// this can be inlined
return level != Logger.OFF_INT && level >= effectiveMinLevel && level >= effectiveLevel;
}
public Handler[] getHandlers() {
Handler[] handlers = this.handlers;
if (handlers == null) {
synchronized (this) {
handlers = this.handlers;
if (handlers == null) {
handlers = this.handlers = safeCloneHandlers(context.getInitializer().getInitialHandlers(fullName));
}
}
}
return handlers;
}
public Handler[] clearHandlers() {
final Handler[] handlers = this.handlers;
handlersUpdater.clear(this);
return safeCloneHandlers(handlers);
}
public void removeHandler(final Handler handler) {
getHandlers();
handlersUpdater.remove(this, handler, true);
}
public void addHandler(final Handler handler) {
getHandlers();
handlersUpdater.add(this, handler);
context.pin(this);
}
public Handler[] setHandlers(final Handler[] handlers) {
if (handlers.length > 0) {
context.pin(this);
}
return handlersUpdater.getAndSet(this, handlers);
}
public boolean compareAndSetHandlers(final Handler[] oldHandlers, final Handler[] newHandlers) {
return handlersUpdater.compareAndSet(this, oldHandlers, newHandlers);
}
public boolean getUseParentHandlers() {
return useParentHandlers;
}
public void setUseParentHandlers(final boolean useParentHandlers) {
this.useParentHandlers = useParentHandlers;
if (!useParentHandlers) {
context.pin(this);
}
}
public void publish(final ExtLogRecord record) {
LogRecord oldRecord = null;
for (Handler handler : getHandlers())
try {
if (handler instanceof ExtHandler || handler.getFormatter() instanceof ExtFormatter) {
handler.publish(record);
} else {
// old-style handlers generally don't know how to handle printf formatting
if (oldRecord == null) {
if (record.getFormatStyle() == ExtLogRecord.FormatStyle.PRINTF) {
// reformat it in a simple way, but only for legacy handler usage
oldRecord = new ExtLogRecord(record);
oldRecord.setMessage(record.getFormattedMessage());
oldRecord.setParameters(null);
} else {
oldRecord = record;
}
}
handler.publish(oldRecord);
}
} catch (VirtualMachineError e) {
throw e;
} catch (Throwable t) {
ErrorManager errorManager = handler.getErrorManager();
if (errorManager != null) {
Exception e;
if (t instanceof Exception) {
e = (Exception) t;
} else {
e = new UndeclaredThrowableException(t);
e.setStackTrace(EMPTY_STACK);
}
try {
errorManager.error("Handler publication threw an exception", e, ErrorManager.WRITE_FAILURE);
} catch (Throwable t2) {
StandardOutputStreams.printError(t2, "Handler.reportError caught an exception");
}
}
}
if (useParentHandlers) {
final LoggerNode parent = this.parent;
if (parent != null)
parent.publish(record);
}
}
public void setLevel(final Level newLevel) {
final ReentrantLock treeLock = context.treeLock;
treeLock.lock();
try {
final int oldEffectiveLevel = effectiveLevel;
final int newEffectiveLevel;
if (newLevel != null) {
level = newLevel;
newEffectiveLevel = newLevel.intValue();
context.pin(this);
} else {
final LoggerNode parent = this.parent;
if (parent == null) {
level = Level.INFO;
newEffectiveLevel = Logger.INFO_INT;
} else {
level = null;
newEffectiveLevel = parent.effectiveLevel;
}
}
effectiveLevel = newEffectiveLevel;
if (oldEffectiveLevel != newEffectiveLevel) {
// our level changed, recurse down to children
for (LoggerNode node : children.values()) {
if (node != null) {
node.setEffectiveLevel(newEffectiveLevel);
}
}
}
} finally {
treeLock.unlock();
}
}
public Level getLevel() {
return level;
}
@SuppressWarnings({"unchecked"})
public <V> V getAttachment(final Logger.AttachmentKey<V> key) {
return (V) attachments.get(key);
}
@SuppressWarnings({"unchecked"})
public <V> V attach(final Logger.AttachmentKey<V> key, final V value) {
Map<Logger.AttachmentKey<?>, Object> oldAttachments;
Map<Logger.AttachmentKey<?>, Object> newAttachments;
V old;
do {
oldAttachments = attachments;
newAttachments = new HashMap<>(oldAttachments);
old = (V) newAttachments.put(key, value);
} while (!attachmentHandle.compareAndSet(this, oldAttachments, Map.copyOf(newAttachments)));
return old;
}
@SuppressWarnings({"unchecked"})
public <V> V attachIfAbsent(final Logger.AttachmentKey<V> key, final V value) {
Map<Logger.AttachmentKey<?>, Object> oldAttachments;
Map<Logger.AttachmentKey<?>, Object> newAttachments;
do {
oldAttachments = attachments;
if (oldAttachments.containsKey(key)) {
return (V) oldAttachments.get(key);
}
newAttachments = new HashMap<>(oldAttachments);
newAttachments.put(key, value);
} while (!attachmentHandle.compareAndSet(this, oldAttachments, Map.copyOf(newAttachments)));
return null;
}
@SuppressWarnings({"unchecked"})
public <V> V detach(final Logger.AttachmentKey<V> key) {
Map<Logger.AttachmentKey<?>, Object> oldAttachments;
Map<Logger.AttachmentKey<?>, Object> newAttachments;
V result;
do {
oldAttachments = attachments;
result = (V) oldAttachments.get(key);
if (result == null) {
return null;
}
final int size = oldAttachments.size();
if (size == 1) {
// special case - the new map is empty
newAttachments = Map.of();
} else {
newAttachments = new HashMap<>(oldAttachments);
newAttachments.remove(key);
}
} while (!attachmentHandle.compareAndSet(this, oldAttachments, Map.copyOf(newAttachments)));
return result;
}
public String getFullName() {
return fullName;
}
public LoggerNode getParent() {
return parent;
}
/**
* Checks the filter to see if the record is loggable. If the {@link #getUseParentFilters()} is set to {@code true}
* the parent loggers are checked.
*
* @param record the log record to check against the filter
* @return {@code true} if the record is loggable, otherwise {@code false}
*/
public boolean isLoggable(final ExtLogRecord record) {
if (!useParentFilter) {
final Filter filter = this.filter;
return filter == null || filter.isLoggable(record);
}
final ReentrantLock treeLock = context.treeLock;
treeLock.lock();
try {
return isLoggable(this, record);
} finally {
treeLock.unlock();
}
}
private static boolean isLoggable(final LoggerNode loggerNode, final ExtLogRecord record) {
if (loggerNode == null) {
return true;
}
final Filter filter = loggerNode.filter;
return !(filter != null && !filter.isLoggable(record))
&& (!loggerNode.useParentFilter || isLoggable(loggerNode.getParent(), record));
}
public Enumeration<String> getLoggerNames() {
return new Enumeration<>() {
final Iterator<LoggerNode> children = getChildren().iterator();
String next = activeLoggers.isEmpty() ? null : fullName;
Enumeration<String> sub;
@Override
public boolean hasMoreElements() {
while (next == null) {
while (sub == null) {
if (children.hasNext()) {
final LoggerNode child = children.next();
sub = child.getLoggerNames();
} else {
return false;
}
}
if (sub.hasMoreElements()) {
next = sub.nextElement();
return true;
} else {
sub = null;
}
}
return true;
}
@Override
public String nextElement() {
try {
return next;
} finally {
next = null;
}
}
};
}
}

View file

@ -0,0 +1,135 @@
package org.xbib.logging;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
/**
* Mapped diagnostic context. This is a thread-local map used to hold loggable information.
*/
public final class MDC {
private static final MDCProvider mdcProvider = getDefaultMDCProvider();
private MDC() {
}
static MDCProvider getMDCProvider() {
return mdcProvider;
}
private static MDCProvider getDefaultMDCProvider() {
return doGetDefaultMDCProvider();
}
private static MDCProvider doGetDefaultMDCProvider() {
final ServiceLoader<MDCProvider> configLoader = ServiceLoader.load(MDCProvider.class, MDC.class.getClassLoader());
final Iterator<MDCProvider> iterator = configLoader.iterator();
for (; ; )
try {
if (!iterator.hasNext()) {
return new ThreadLocalMDC();
}
return iterator.next();
} catch (ServiceConfigurationError | RuntimeException e) {
System.err.print("Warning: failed to load MDC Provider: ");
e.printStackTrace(System.err);
}
}
/**
* Get the value for a key, or {@code null} if there is no mapping.
*
* @param key the key
* @return the value
*/
public static String get(String key) {
return mdcProvider.get(key);
}
/**
* Get the value for a key, or {@code null} if there is no mapping.
*
* @param key the key
* @return the value
*/
public static Object getObject(String key) {
return mdcProvider.getObject(key);
}
/**
* Set the value of a key, returning the old value (if any) or {@code null} if there was none.
*
* @param key the key
* @param value the new value
* @return the old value or {@code null} if there was none
*/
public static String put(String key, String value) {
return mdcProvider.put(key, value);
}
/**
* Set the value of a key, returning the old value (if any) or {@code null} if there was none.
*
* @param key the key
* @param value the new value
* @return the old value or {@code null} if there was none
*/
public static Object putObject(String key, Object value) {
return mdcProvider.putObject(key, value);
}
/**
* Remove a key.
*
* @param key the key
* @return the old value or {@code null} if there was none
*/
public static String remove(String key) {
return mdcProvider.remove(key);
}
/**
* Remove a key.
*
* @param key the key
* @return the old value or {@code null} if there was none
*/
public static Object removeObject(String key) {
return mdcProvider.removeObject(key);
}
/**
* Get a copy of the MDC map. This is a relatively expensive operation.
*
* @return a copy of the map
*/
public static Map<String, String> copy() {
return mdcProvider.copy();
}
/**
* Get a copy of the MDC map. This is a relatively expensive operation.
*
* @return a copy of the map
*/
public static Map<String, Object> copyObject() {
return mdcProvider.copyObject();
}
/**
* Checks of the MDC map is empty.
*
* @return {@code true} if the MDC map is empty, otherwise {@code false}
*/
public static boolean isEmpty() {
return mdcProvider.isEmpty();
}
/**
* Clear the current MDC map.
*/
public static void clear() {
mdcProvider.clear();
}
}

View file

@ -0,0 +1,83 @@
package org.xbib.logging;
import java.util.Map;
public interface MDCProvider {
/**
* Get the value for a key, or {@code null} if there is no mapping.
*
* @param key the key
* @return the value
*/
String get(String key);
/**
* Get the value for a key, or {@code null} if there is no mapping.
*
* @param key the key
* @return the value
*/
Object getObject(String key);
/**
* Set the value of a key, returning the old value (if any) or {@code null} if there was none.
*
* @param key the key
* @param value the new value
* @return the old value or {@code null} if there was none
*/
String put(String key, String value);
/**
* Set the value of a key, returning the old value (if any) or {@code null} if there was none.
*
* @param key the key
* @param value the new value
* @return the old value or {@code null} if there was none
*/
Object putObject(String key, Object value);
/**
* Remove a key.
*
* @param key the key
* @return the old value or {@code null} if there was none
*/
String remove(String key);
/**
* Remove a key.
*
* @param key the key
* @return the old value or {@code null} if there was none
*/
Object removeObject(String key);
/**
* Get a copy of the MDC map. This is a relatively expensive operation.
*
* @return a copy of the map
*/
Map<String, String> copy();
/**
* Get a copy of the MDC map. This is a relatively expensive operation.
*
* @return a copy of the map
*/
Map<String, Object> copyObject();
/**
* Returns {@code true} if the backing MDC map is empty, otherwise {@code false}.
*
* @return {@code true} if the MDC map is empty
*/
boolean isEmpty();
/**
* Clear the current MDC map.
*/
void clear();
}

View file

@ -0,0 +1,103 @@
package org.xbib.logging;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
/**
* Nested diagnostic context. This is basically a thread-local stack that holds a string which can be included
* in a log message.
*/
public final class NDC {
private static final NDCProvider ndcProvider = getDefaultNDCProvider();
private NDC() {
}
static NDCProvider getNDCProvider() {
return ndcProvider;
}
static NDCProvider getDefaultNDCProvider() {
return doGetDefaultNDCProvider();
}
static NDCProvider doGetDefaultNDCProvider() {
final ServiceLoader<NDCProvider> configLoader = ServiceLoader.load(NDCProvider.class, NDC.class.getClassLoader());
final Iterator<NDCProvider> iterator = configLoader.iterator();
for (; ; )
try {
if (!iterator.hasNext()) {
return new ThreadLocalNDC();
}
return iterator.next();
} catch (ServiceConfigurationError | RuntimeException e) {
System.err.print("Warning: failed to load NDC Provider: ");
e.printStackTrace(System.err);
}
}
/**
* Push a value on to the NDC stack, returning the new stack depth which should later be used to restore the stack.
*
* @param context the new value
* @return the new stack depth
*/
public static int push(String context) {
return ndcProvider.push(context);
}
/**
* Pop the topmost value from the NDC stack and return it.
*
* @return the old topmost value
*/
public static String pop() {
return ndcProvider.pop();
}
/**
* Clear the thread's NDC stack.
*/
public static void clear() {
ndcProvider.clear();
}
/**
* Trim the thread NDC stack down to no larger than the given size. Used to restore the stack to the depth returned
* by a {@code push()}.
*
* @param size the new size
*/
public static void trimTo(int size) {
ndcProvider.trimTo(size);
}
/**
* Get the current NDC stack depth.
*
* @return the stack depth
*/
public static int getDepth() {
return ndcProvider.getDepth();
}
/**
* Get the current NDC value.
*
* @return the current NDC value, or {@code ""} if there is none
*/
public static String get() {
return ndcProvider.get();
}
/**
* Provided for compatibility with log4j. Get the NDC value that is {@code n} entries from the bottom.
*
* @param n the index
* @return the value or {@code null} if there is none
*/
public static String get(int n) {
return ndcProvider.get(n);
}
}

View file

@ -0,0 +1,55 @@
package org.xbib.logging;
public interface NDCProvider {
/**
* Push a value on to the NDC stack, returning the new stack depth which should later be used to restore the stack.
*
* @param context the new value
* @return the new stack depth
*/
int push(String context);
/**
* Pop the topmost value from the NDC stack and return it.
*
* @return the old topmost value
*/
String pop();
/**
* Clear the thread's NDC stack.
*/
void clear();
/**
* Trim the thread NDC stack down to no larger than the given size. Used to restore the stack to the depth returned
* by a {@code push()}.
*
* @param size the new size
*/
void trimTo(int size);
/**
* Get the current NDC stack depth.
*
* @return the stack depth
*/
int getDepth();
/**
* Get the current NDC value.
*
* @return the current NDC value, or {@code ""} if there is none
*/
String get();
/**
* Provided for compatibility with log4j. Get the NDC value that is {@code n} entries from the bottom.
*
* @param n the index
* @return the value or {@code null} if there is none
*/
String get(int n);
}

View file

@ -0,0 +1,57 @@
package org.xbib.logging;
/**
* A log context selector which stores the chosen log context in a thread-local.
*/
public final class ThreadLocalLogContextSelector implements LogContextSelector {
private final Object securityKey;
private final LogContextSelector delegate;
private final ThreadLocal<LogContext> context = new ThreadLocal<>();
/**
* Construct a new instance.
*
* @param delegate the selector to delegate to if no context is chosen
*/
public ThreadLocalLogContextSelector(final LogContextSelector delegate) {
this(null, delegate);
}
/**
* Construct a new instance.
*
* @param securityKey the security key required to push or pop a log context.
* @param delegate the selector to delegate to if no context is chosen
*/
public ThreadLocalLogContextSelector(final Object securityKey, final LogContextSelector delegate) {
this.securityKey = securityKey;
this.delegate = delegate;
}
public LogContext getLogContext() {
final LogContext localContext = context.get();
return localContext != null ? localContext : delegate.getLogContext();
}
/**
* Get and set the log context.
*
* @param securityKey the security key to check (ignored if none was set on construction)
* @param newValue the new log context value, or {@code null} to clear
* @return the previous log context value, or {@code null} if none was set
*/
public LogContext getAndSet(Object securityKey, LogContext newValue) {
if (this.securityKey != null && securityKey != this.securityKey) {
throw new IllegalArgumentException("invalid security key for ThreadLocalLogContextSelector modification");
}
try {
return context.get();
} finally {
if (newValue == null)
context.remove();
else
context.set(newValue);
}
}
}

View file

@ -0,0 +1,84 @@
package org.xbib.logging;
import java.util.Map;
import org.xbib.logging.util.FastCopyHashMap;
final class ThreadLocalMDC implements MDCProvider {
private static final Holder mdc = new Holder();
@Override
public String get(String key) {
final Object value = getObject(key);
return value == null ? null : value.toString();
}
@Override
public Object getObject(String key) {
return mdc.get().get(key);
}
@Override
public String put(String key, String value) {
final Object oldValue = putObject(key, value);
return oldValue == null ? null : oldValue.toString();
}
@Override
public Object putObject(String key, Object value) {
if (key == null) {
throw new NullPointerException("key is null");
}
if (value == null) {
throw new NullPointerException("value is null");
}
return mdc.get().put(key, value);
}
@Override
public String remove(String key) {
final Object oldValue = removeObject(key);
return oldValue == null ? null : oldValue.toString();
}
@Override
public Object removeObject(String key) {
return mdc.get().remove(key);
}
@Override
public Map<String, String> copy() {
final FastCopyHashMap<String, String> result = new FastCopyHashMap<>();
for (Map.Entry<String, Object> entry : mdc.get().entrySet()) {
result.put(entry.getKey(), entry.getValue().toString());
}
return result;
}
@Override
public Map<String, Object> copyObject() {
return mdc.get().clone();
}
@Override
public boolean isEmpty() {
return mdc.get().isEmpty();
}
@Override
public void clear() {
mdc.get().clear();
}
private static final class Holder extends InheritableThreadLocal<FastCopyHashMap<String, Object>> {
@Override
protected FastCopyHashMap<String, Object> childValue(final FastCopyHashMap<String, Object> parentValue) {
return new FastCopyHashMap<>(parentValue);
}
@Override
protected FastCopyHashMap<String, Object> initialValue() {
return new FastCopyHashMap<>();
}
}
}

View file

@ -0,0 +1,122 @@
package org.xbib.logging;
import java.util.Arrays;
final class ThreadLocalNDC implements NDCProvider {
private static final Holder ndc = new Holder();
@Override
public int push(String context) {
final Stack<String> stack = ndc.get();
try {
return stack.depth();
} finally {
stack.push(context);
}
}
@Override
public String pop() {
final Stack<String> stack = ndc.get();
if (stack.isEmpty()) {
return "";
} else {
return stack.pop();
}
}
@Override
public void clear() {
ndc.get().trimTo(0);
}
@Override
public void trimTo(int size) {
ndc.get().trimTo(size);
}
@Override
public int getDepth() {
return ndc.get().depth();
}
@Override
public String get() {
final Stack<String> stack = ndc.get();
if (stack.isEmpty()) {
return "";
} else {
return stack.toString();
}
}
@Override
public String get(int n) {
return ndc.get().get(n);
}
private static final class Holder extends ThreadLocal<Stack<String>> {
protected Stack<String> initialValue() {
return new Stack<>();
}
}
private static final class Stack<T> {
private Object[] data = new Object[32];
private int sp;
public void push(T value) {
if (sp == data.length) {
data = Arrays.copyOf(data, (data.length << 1) + data.length >>> 1);
}
data[sp++] = value;
}
@SuppressWarnings("unchecked")
public T pop() {
try {
return (T) data[--sp];
} finally {
data[sp] = null;
}
}
@SuppressWarnings("unchecked")
public T top() {
return (T) data[sp - 1];
}
public boolean isEmpty() {
return sp == 0;
}
public int depth() {
return sp;
}
public void trimTo(int max) {
final int sp = this.sp;
if (sp > max) {
Arrays.fill(data, max, sp - 1, null);
this.sp = max;
}
}
@SuppressWarnings("unchecked")
public T get(int n) {
return n < sp ? (T) data[n] : null;
}
public String toString() {
final StringBuilder b = new StringBuilder();
final int sp = this.sp;
for (int i = 0; i < sp; i++) {
b.append(data[i]);
if ((i + 1) < sp) {
b.append('.');
}
}
return b.toString();
}
}
}

View file

@ -0,0 +1,230 @@
package org.xbib.logging;
import java.time.Instant;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.LogRecord;
@SuppressWarnings("serial")
class WrappedExtLogRecord extends ExtLogRecord {
private static final String LOGGER_CLASS_NAME = java.util.logging.Logger.class.getName();
private transient final LogRecord logRecord;
private transient boolean resolved;
WrappedExtLogRecord(final LogRecord logRecord) {
super(logRecord.getLevel(), logRecord.getMessage(), LOGGER_CLASS_NAME);
this.logRecord = logRecord;
}
public String getLoggerName() {
return logRecord.getLoggerName();
}
public void setLoggerName(final String name) {
super.setLoggerName(name);
logRecord.setLoggerName(name);
}
@Override
public ResourceBundle getResourceBundle() {
return logRecord.getResourceBundle();
}
@Override
public void setResourceBundle(final ResourceBundle bundle) {
super.setResourceBundle(bundle);
logRecord.setResourceBundle(bundle);
}
@Override
public String getResourceBundleName() {
return logRecord.getResourceBundleName();
}
@Override
public void setResourceBundleName(final String name) {
super.setResourceBundleName(name);
logRecord.setResourceBundleName(name);
}
@Override
public Level getLevel() {
return logRecord.getLevel();
}
@Override
public void setLevel(final Level level) {
super.setLevel(level);
logRecord.setLevel(level);
}
@Override
public long getSequenceNumber() {
return logRecord.getSequenceNumber();
}
@Override
public void setSequenceNumber(final long seq) {
super.setSequenceNumber(seq);
logRecord.setSequenceNumber(seq);
}
@Override
public String getSourceClassName() {
if (!resolved) {
resolve();
}
return super.getSourceClassName();
}
@Override
public void setSourceClassName(final String sourceClassName) {
resolved = true;
super.setSourceClassName(sourceClassName);
logRecord.setSourceClassName(sourceClassName);
}
@Override
public String getSourceMethodName() {
if (!resolved) {
resolve();
}
return super.getSourceMethodName();
}
@Override
public void setSourceMethodName(final String sourceMethodName) {
resolved = true;
super.setSourceMethodName(sourceMethodName);
logRecord.setSourceMethodName(sourceMethodName);
}
private void resolve() {
resolved = true;
final String sourceMethodName = logRecord.getSourceMethodName();
final String sourceClassName = logRecord.getSourceClassName();
super.setSourceMethodName(sourceMethodName);
super.setSourceClassName(sourceClassName);
final StackTraceElement[] st = new Throwable().getStackTrace();
for (StackTraceElement element : st) {
if (element.getClassName().equals(sourceClassName) && element.getMethodName().equals(sourceMethodName)) {
super.setSourceLineNumber(element.getLineNumber());
super.setSourceFileName(element.getFileName());
return;
}
}
}
@Override
public int getSourceLineNumber() {
if (!resolved) {
resolve();
}
return super.getSourceLineNumber();
}
public void setSourceLineNumber(final int sourceLineNumber) {
resolved = true;
super.setSourceLineNumber(sourceLineNumber);
}
public String getSourceFileName() {
if (!resolved) {
resolve();
}
return super.getSourceFileName();
}
@Override
public void setSourceFileName(final String sourceFileName) {
resolved = true;
super.setSourceFileName(sourceFileName);
}
@Override
public String getMessage() {
return logRecord.getMessage();
}
@Override
public void setMessage(final String message) {
super.setMessage(message);
logRecord.setMessage(message);
}
@Override
public void setMessage(String message, FormatStyle formatStyle) {
super.setMessage(message, formatStyle);
logRecord.setMessage(message);
}
@Override
public Object[] getParameters() {
return logRecord.getParameters();
}
@Override
public void setParameters(final Object[] parameters) {
logRecord.setParameters(parameters);
}
@Deprecated
@SuppressWarnings("deprecation")
public int getThreadID() {
return logRecord.getThreadID();
}
@Deprecated
@SuppressWarnings("deprecation")
public void setThreadID(final int threadID) {
super.setThreadID(threadID);
logRecord.setThreadID(threadID);
}
@Override
public long getLongThreadID() {
return logRecord.getLongThreadID();
}
@Override
public ExtLogRecord setLongThreadID(final long id) {
super.setLongThreadID(id);
logRecord.setLongThreadID(id);
return this;
}
@Override
public long getMillis() {
return logRecord.getMillis();
}
@Deprecated
@SuppressWarnings("deprecation")
@Override
public void setMillis(final long millis) {
logRecord.setMillis(millis);
}
@Override
public Instant getInstant() {
return logRecord.getInstant();
}
@Override
public void setInstant(Instant instant) {
logRecord.setInstant(instant);
}
@Override
public Throwable getThrown() {
return logRecord.getThrown();
}
@Override
public void setThrown(final Throwable thrown) {
logRecord.setThrown(thrown);
}
}

View file

@ -0,0 +1,35 @@
package org.xbib.logging.configuration;
import java.util.function.Supplier;
/**
* Represents a configuration resource. If the resource is a {@link AutoCloseable}, then invoking {@link #close()} on
* this resource will close the resource.
*/
public interface ConfigurationResource<T> extends Supplier<T>, AutoCloseable {
/**
* Creates a configuration resource which lazily invokes the supplier. Note that {@link #close()} will only close
* the resource if {@link #get()} was first invoked to retrieve the value from the supplier.
*
* @param supplier the supplier used to create the configuration resource
* @return the configuration resource represented by a lazy instance
*/
static <T> ConfigurationResource<T> of(final Supplier<T> supplier) {
if (supplier instanceof ConfigurationResource) {
return (ConfigurationResource<T>) supplier;
}
return new LazyConfigurationResource<>(supplier);
}
/**
* Creates a configuration resource with the instance as a constant. Note that if {@link #close()} is invoked,
* {@link #get()} will return {@code null}.
*
* @param instance the constant instance
* @return the configuration resource represented by a constant instance
*/
static <T> ConfigurationResource<T> of(final T instance) {
return new ConstantConfigurationResource<>(instance);
}
}

View file

@ -0,0 +1,25 @@
package org.xbib.logging.configuration;
import java.util.concurrent.atomic.AtomicReference;
class ConstantConfigurationResource<T> implements ConfigurationResource<T> {
private final AtomicReference<T> instance;
ConstantConfigurationResource(final T instance) {
this.instance = new AtomicReference<>(instance);
}
@Override
public T get() {
return instance.get();
}
@Override
public void close() throws Exception {
final T instance = this.instance.getAndSet(null);
if (instance instanceof AutoCloseable) {
((AutoCloseable) instance).close();
}
}
}

View file

@ -0,0 +1,398 @@
package org.xbib.logging.configuration;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.logging.ErrorManager;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import org.xbib.logging.LogContext;
import org.xbib.logging.Logger;
/**
* A configuration which can be stored on a {@linkplain LogContext log context} to store information
* about the configured error managers, handlers, filters, formatters and objects that might be associated with a
* configured object.
* <p>
* The {@link #addObject(String, Supplier)} can be used to allow objects to be set when configuring error managers,
* handlers, filters and formatters.
* </p>
* <p>
* If the {@linkplain Supplier supplier} os not already an instance of a {@link ConfigurationResource}, then it is
* wrapped and considered a {@linkplain ConfigurationResource#of(Supplier) lazy resource}.
* </p>
*/
@SuppressWarnings({"UnusedReturnValue", "unused"})
public class ContextConfiguration implements AutoCloseable {
public static final Logger.AttachmentKey<ContextConfiguration> CONTEXT_CONFIGURATION_KEY = new Logger.AttachmentKey<>();
private final LogContext context;
private final Map<String, ConfigurationResource<ErrorManager>> errorManagers;
private final Map<String, ConfigurationResource<Filter>> filters;
private final Map<String, ConfigurationResource<Formatter>> formatters;
private final Map<String, ConfigurationResource<Handler>> handlers;
private final Map<String, ConfigurationResource<Object>> objects;
/**
* Creates a new context configuration.
*/
public ContextConfiguration(final LogContext context) {
this.context = context;
errorManagers = new ConcurrentHashMap<>();
handlers = new ConcurrentHashMap<>();
formatters = new ConcurrentHashMap<>();
filters = new ConcurrentHashMap<>();
objects = new ConcurrentHashMap<>();
}
/**
* Returns the {@linkplain LogContext context} for this configuration.
*
* @return the context for this configuration
*/
public LogContext getContext() {
return context;
}
/**
* Checks if the logger exists in this context.
*
* @param name the logger name
* @return {@code true} if the logger exists in this context, otherwise {@code false}
*/
public boolean hasLogger(final String name) {
return getContext().getLoggerIfExists(Objects.requireNonNull(name, "The name cannot be null")) != null;
}
/**
* Gets the logger if it exists.
*
* @param name the name of the logger
* @return the logger or {@code null} if the logger does not exist
*/
public Logger getLogger(final String name) {
return getContext().getLogger(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Returns an unmodifiable set of the configured logger names
*
* @return an unmodified set of the logger names
*/
public Set<String> getLoggers() {
return Set.copyOf(Collections.list(getContext().getLoggerNames()));
}
/**
* Adds an error manager to the context configuration.
*
* @param name the name for the error manager
* @param errorManager the error manager to add
* @return the previous error manager associated with the name or {@code null} if one did not exist
*/
public ConfigurationResource<ErrorManager> addErrorManager(final String name, final Supplier<ErrorManager> errorManager) {
if (errorManager == null) {
return removeErrorManager(name);
}
return errorManagers.putIfAbsent(Objects.requireNonNull(name, "The name cannot be null"),
ConfigurationResource.of(errorManager));
}
/**
* Removes the error manager from the context configuration.
*
* @param name the name of the error manager
* @return the error manager removed or {@code null} if the error manager did not exist
*/
public ConfigurationResource<ErrorManager> removeErrorManager(final String name) {
return errorManagers.remove(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Checks if the error manager exists with the name provided.
*
* @param name the name for the error manager
* @return {@code true} if the error manager exists in this context, otherwise {@code false}
*/
public boolean hasErrorManager(final String name) {
return errorManagers.containsKey(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Gets the error manager if it exists.
*
* @param name the name of the error manager
* @return the error manager or {@code null} if the error manager does not exist
*/
public ErrorManager getErrorManager(final String name) {
if (errorManagers.containsKey(Objects.requireNonNull(name, "The name cannot be null"))) {
return errorManagers.get(name).get();
}
return null;
}
/**
* Returns an unmodifiable map of the error managers and the suppliers used to create them.
*
* @return an unmodified map of the error managers
*/
public Map<String, ConfigurationResource<ErrorManager>> getErrorManagers() {
return Collections.unmodifiableMap(errorManagers);
}
/**
* Adds a handler to the context configuration.
*
* @param name the name for the handler
* @param handler the handler to add
* @return the previous handler associated with the name or {@code null} if one did not exist
*/
public ConfigurationResource<Handler> addHandler(final String name, final Supplier<Handler> handler) {
if (handler == null) {
return removeHandler(name);
}
return handlers.putIfAbsent(Objects.requireNonNull(name, "The name cannot be null"),
ConfigurationResource.of(handler));
}
/**
* Removes the handler from the context configuration.
*
* @param name the name of the handler
* @return the handler removed or {@code null} if the handler did not exist
*/
public ConfigurationResource<Handler> removeHandler(final String name) {
return handlers.remove(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Checks if the handler exists with the name provided.
*
* @param name the name for the handler
* @return {@code true} if the handler exists in this context, otherwise {@code false}
*/
public boolean hasHandler(final String name) {
return handlers.containsKey(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Gets the handler if it exists.
*
* @param name the name of the handler
* @return the handler or {@code null} if the handler does not exist
*/
public Handler getHandler(final String name) {
if (handlers.containsKey(Objects.requireNonNull(name, "The name cannot be null"))) {
return handlers.get(name).get();
}
return null;
}
/**
* Returns an unmodifiable map of the handlers and the suppliers used to create them.
*
* @return an unmodified map of the handlers
*/
public Map<String, ConfigurationResource<Handler>> getHandlers() {
return Collections.unmodifiableMap(handlers);
}
/**
* Adds a formatter to the context configuration.
*
* @param name the name for the formatter
* @param formatter the formatter to add
* @return the previous formatter associated with the name or {@code null} if one did not exist
*/
public ConfigurationResource<Formatter> addFormatter(final String name, final Supplier<Formatter> formatter) {
if (formatter == null) {
return removeFormatter(name);
}
return formatters.putIfAbsent(Objects.requireNonNull(name, "The name cannot be null"),
ConfigurationResource.of(formatter));
}
/**
* Removes the formatter from the context configuration.
*
* @param name the name of the formatter
* @return the formatter removed or {@code null} if the formatter did not exist
*/
public ConfigurationResource<Formatter> removeFormatter(final String name) {
return formatters.remove(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Checks if the formatter exists with the name provided.
*
* @param name the name for the formatter
* @return {@code true} if the formatter exists in this context, otherwise {@code false}
*/
public boolean hasFormatter(final String name) {
return formatters.containsKey(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Gets the formatter if it exists.
*
* @param name the name of the formatter
* @return the formatter or {@code null} if the formatter does not exist
*/
public Formatter getFormatter(final String name) {
if (formatters.containsKey(Objects.requireNonNull(name, "The name cannot be null"))) {
return formatters.get(name).get();
}
return null;
}
/**
* Returns an unmodifiable map of the formatters and the suppliers used to create them.
*
* @return an unmodified map of the formatters
*/
public Map<String, Supplier<Formatter>> getFormatters() {
return Collections.unmodifiableMap(formatters);
}
/**
* Adds a filter to the context configuration.
*
* @param name the name for the filter
* @param filter the filter to add
* @return the previous filter associated with the name or {@code null} if one did not exist
*/
public ConfigurationResource<Filter> addFilter(final String name, final Supplier<Filter> filter) {
if (filter == null) {
return removeFilter(name);
}
return filters.putIfAbsent(Objects.requireNonNull(name, "The name cannot be null"),
ConfigurationResource.of(filter));
}
/**
* Removes the filter from the context configuration.
*
* @param name the name of the filter
* @return the filter removed or {@code null} if the filter did not exist
*/
public ConfigurationResource<Filter> removeFilter(final String name) {
return filters.remove(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Checks if the filter exists with the name provided.
*
* @param name the name for the filter
* @return {@code true} if the filter exists in this context, otherwise {@code false}
*/
public boolean hasFilter(final String name) {
return filters.containsKey(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Gets the filter if it exists.
*
* @param name the name of the filter
* @return the filer or {@code null} if the filter does not exist
*/
public Filter getFilter(final String name) {
if (filters.containsKey(Objects.requireNonNull(name, "The name cannot be null"))) {
return filters.get(name).get();
}
return null;
}
/**
* Returns an unmodifiable map of the filters and the suppliers used to create them.
*
* @return an unmodified map of the filters
*/
public Map<String, ConfigurationResource<Filter>> getFilters() {
return Collections.unmodifiableMap(filters);
}
/**
* Adds an object that can be used as a configuration property for another configuration type. This is used for
* cases when an object cannot simply be converted from a string.
*
* @param name the name for the configuration object
* @param object the configuration object to add
* @return the previous configuration object associated with the name or {@code null} if one did not exist
*/
public ConfigurationResource<Object> addObject(final String name, final Supplier<Object> object) {
if (object == null) {
return removeObject(name);
}
return objects.putIfAbsent(Objects.requireNonNull(name, "The name cannot be null"),
ConfigurationResource.of(object));
}
/**
* Removes the configuration object from the context configuration.
*
* @param name the name of the configuration object
* @return the configuration object removed or {@code null} if the configuration object did not exist
*/
public ConfigurationResource<Object> removeObject(final String name) {
return objects.remove(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Checks if the configuration object exists with the name provided.
*
* @param name the name for the configuration object
* @return {@code true} if the configuration object exists in this context, otherwise {@code false}
*/
public boolean hasObject(final String name) {
return objects.containsKey(Objects.requireNonNull(name, "The name cannot be null"));
}
/**
* Gets the configuration object if it exists.
*
* @param name the name of the configuration object
* @return the configuration object or {@code null} if the configuration object does not exist
*/
public Object getObject(final String name) {
if (objects.containsKey(Objects.requireNonNull(name, "The name cannot be null"))) {
return objects.get(name).get();
}
return null;
}
/**
* Returns an unmodifiable map of the configuration objects and the suppliers used to create them.
*
* @return an unmodified map of the configuration objects
*/
public Map<String, ConfigurationResource<Object>> getObjects() {
return Collections.unmodifiableMap(objects);
}
@Override
public void close() throws Exception {
context.close();
closeResources(handlers);
closeResources(filters);
closeResources(formatters);
closeResources(errorManagers);
closeResources(objects);
}
private static void closeResources(final Map<String, ? extends ConfigurationResource<?>> resources) {
final var iter = resources.entrySet().iterator();
while (iter.hasNext()) {
var entry = iter.next();
iter.remove();
try {
entry.getValue().close();
} catch (Throwable ignore) {
// do nothing
}
}
}
}

View file

@ -0,0 +1,23 @@
package org.xbib.logging.configuration;
import org.xbib.logging.LogContextConfigurator;
import org.xbib.logging.LogContextConfiguratorFactory;
/**
* The default configuration factory which has a priority of 100.
*/
public class DefaultLogContextConfiguratorFactory implements LogContextConfiguratorFactory {
public DefaultLogContextConfiguratorFactory() {
}
@Override
public LogContextConfigurator create() {
return new PropertyLogContextConfigurator();
}
@Override
public int priority() {
return 100;
}
}

Some files were not shown because too many files have changed in this diff Show more