initial commit
This commit is contained in:
commit
81f3cc723d
246 changed files with 43251 additions and 0 deletions
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal 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
202
LICENSE.txt
Normal 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
21
NOTICE.txt
Normal 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
35
build.gradle
Normal 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
3
gradle.properties
Normal file
|
@ -0,0 +1,3 @@
|
|||
group = org.xbib
|
||||
name = logging
|
||||
version = 0.0.1
|
34
gradle/compile/groovy.gradle
Normal file
34
gradle/compile/groovy.gradle
Normal 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"
|
||||
}
|
||||
}
|
47
gradle/compile/java.gradle
Normal file
47
gradle/compile/java.gradle
Normal 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()
|
||||
}
|
||||
}
|
55
gradle/documentation/asciidoc.gradle
Normal file
55
gradle/documentation/asciidoc.gradle
Normal 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
13
gradle/ide/idea.gradle
Normal 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")
|
||||
}
|
16
gradle/publish/forgejo.gradle
Normal file
16
gradle/publish/forgejo.gradle
Normal 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
27
gradle/publish/ivy.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
gradle/publish/maven.gradle
Normal file
51
gradle/publish/maven.gradle
Normal 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}"
|
||||
}
|
||||
}
|
11
gradle/publish/sonatype.gradle
Normal file
11
gradle/publish/sonatype.gradle
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
gradle/quality/checkstyle.gradle
Normal file
19
gradle/quality/checkstyle.gradle
Normal 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
|
||||
}
|
||||
}
|
333
gradle/quality/checkstyle.xml
Normal file
333
gradle/quality/checkstyle.xml
Normal 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>
|
||||
|
11
gradle/quality/cyclonedx.gradle
Normal file
11
gradle/quality/cyclonedx.gradle
Normal 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
17
gradle/quality/pmd.gradle
Normal 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')
|
||||
}
|
1650
gradle/quality/pmd/category/java/bestpractices.xml
Normal file
1650
gradle/quality/pmd/category/java/bestpractices.xml
Normal file
File diff suppressed because it is too large
Load diff
10
gradle/quality/pmd/category/java/categories.properties
Normal file
10
gradle/quality/pmd/category/java/categories.properties
Normal 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
|
2176
gradle/quality/pmd/category/java/codestyle.xml
Normal file
2176
gradle/quality/pmd/category/java/codestyle.xml
Normal file
File diff suppressed because it is too large
Load diff
1657
gradle/quality/pmd/category/java/design.xml
Normal file
1657
gradle/quality/pmd/category/java/design.xml
Normal file
File diff suppressed because it is too large
Load diff
144
gradle/quality/pmd/category/java/documentation.xml
Normal file
144
gradle/quality/pmd/category/java/documentation.xml
Normal 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>
|
3383
gradle/quality/pmd/category/java/errorprone.xml
Normal file
3383
gradle/quality/pmd/category/java/errorprone.xml
Normal file
File diff suppressed because it is too large
Load diff
393
gradle/quality/pmd/category/java/multithreading.xml
Normal file
393
gradle/quality/pmd/category/java/multithreading.xml
Normal 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: <http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html>
|
||||
or <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>
|
1006
gradle/quality/pmd/category/java/performance.xml
Normal file
1006
gradle/quality/pmd/category/java/performance.xml
Normal file
File diff suppressed because it is too large
Load diff
65
gradle/quality/pmd/category/java/security.xml
Normal file
65
gradle/quality/pmd/category/java/security.xml
Normal 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>
|
37
gradle/quality/sonarqube.gradle
Normal file
37
gradle/quality/sonarqube.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
15
gradle/quality/spotbugs.gradle
Normal file
15
gradle/quality/spotbugs.gradle
Normal 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)
|
||||
}
|
||||
}
|
4
gradle/repositories/maven.gradle
Normal file
4
gradle/repositories/maven.gradle
Normal file
|
@ -0,0 +1,4 @@
|
|||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
29
gradle/test/junit5.gradle
Normal file
29
gradle/test/junit5.gradle
Normal 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
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
249
gradlew
vendored
Executable 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
92
gradlew.bat
vendored
Normal 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
|
16
logging-adapter-log4j/build.gradle
Normal file
16
logging-adapter-log4j/build.gradle
Normal 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
|
||||
}
|
||||
|
9
logging-adapter-log4j/src/main/java/module-info.java
Normal file
9
logging-adapter-log4j/src/main/java/module-info.java
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.xbib.logging.log4j.XbibProvider
|
8
logging-adapter-log4j/src/test/java/module-info.java
Normal file
8
logging-adapter-log4j/src/test/java/module-info.java
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
13
logging-adapter-log4j/src/test/resources/log4j2.xml
Normal file
13
logging-adapter-log4j/src/test/resources/log4j2.xml
Normal 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>
|
5
logging-adapter-slf4j/build.gradle
Normal file
5
logging-adapter-slf4j/build.gradle
Normal file
|
@ -0,0 +1,5 @@
|
|||
dependencies {
|
||||
api project(':logging')
|
||||
api libs.slf4j.api
|
||||
}
|
||||
|
9
logging-adapter-slf4j/src/main/java/module-info.java
Normal file
9
logging-adapter-slf4j/src/main/java/module-info.java
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
8
logging-adapter-slf4j/src/test/java/module-info.java
Normal file
8
logging-adapter-slf4j/src/test/java/module-info.java
Normal 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;
|
||||
}
|
|
@ -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
67
logging/build.gradle
Normal 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
|
7
logging/src/integration/java/module-info.java
Normal file
7
logging/src/integration/java/module-info.java
Normal 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;
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
17
logging/src/main/java/module-info.java
Normal file
17
logging/src/main/java/module-info.java
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
57
logging/src/main/java/org/xbib/logging/ExtErrorManager.java
Normal file
57
logging/src/main/java/org/xbib/logging/ExtErrorManager.java
Normal 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;
|
||||
}
|
||||
}
|
196
logging/src/main/java/org/xbib/logging/ExtFormatter.java
Normal file
196
logging/src/main/java/org/xbib/logging/ExtFormatter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
494
logging/src/main/java/org/xbib/logging/ExtHandler.java
Normal file
494
logging/src/main/java/org/xbib/logging/ExtHandler.java
Normal 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());
|
||||
}
|
||||
}
|
615
logging/src/main/java/org/xbib/logging/ExtLogRecord.java
Normal file
615
logging/src/main/java/org/xbib/logging/ExtLogRecord.java
Normal 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;
|
||||
}
|
||||
}
|
23
logging/src/main/java/org/xbib/logging/Level.java
Normal file
23
logging/src/main/java/org/xbib/logging/Level.java
Normal 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);
|
||||
}
|
557
logging/src/main/java/org/xbib/logging/LogContext.java
Normal file
557
logging/src/main/java/org/xbib/logging/LogContext.java
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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) -> {
|
||||
};
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
186
logging/src/main/java/org/xbib/logging/LogManager.java
Normal file
186
logging/src/main/java/org/xbib/logging/LogManager.java
Normal 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);
|
||||
}
|
||||
}
|
987
logging/src/main/java/org/xbib/logging/Logger.java
Normal file
987
logging/src/main/java/org/xbib/logging/Logger.java
Normal 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();
|
||||
}
|
||||
}
|
123
logging/src/main/java/org/xbib/logging/LoggerFinder.java
Normal file
123
logging/src/main/java/org/xbib/logging/LoggerFinder.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
621
logging/src/main/java/org/xbib/logging/LoggerNode.java
Normal file
621
logging/src/main/java/org/xbib/logging/LoggerNode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
135
logging/src/main/java/org/xbib/logging/MDC.java
Normal file
135
logging/src/main/java/org/xbib/logging/MDC.java
Normal 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();
|
||||
}
|
||||
}
|
83
logging/src/main/java/org/xbib/logging/MDCProvider.java
Normal file
83
logging/src/main/java/org/xbib/logging/MDCProvider.java
Normal 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();
|
||||
|
||||
}
|
103
logging/src/main/java/org/xbib/logging/NDC.java
Normal file
103
logging/src/main/java/org/xbib/logging/NDC.java
Normal 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);
|
||||
}
|
||||
}
|
55
logging/src/main/java/org/xbib/logging/NDCProvider.java
Normal file
55
logging/src/main/java/org/xbib/logging/NDCProvider.java
Normal 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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
84
logging/src/main/java/org/xbib/logging/ThreadLocalMDC.java
Normal file
84
logging/src/main/java/org/xbib/logging/ThreadLocalMDC.java
Normal 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<>();
|
||||
}
|
||||
}
|
||||
}
|
122
logging/src/main/java/org/xbib/logging/ThreadLocalNDC.java
Normal file
122
logging/src/main/java/org/xbib/logging/ThreadLocalNDC.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
230
logging/src/main/java/org/xbib/logging/WrappedExtLogRecord.java
Normal file
230
logging/src/main/java/org/xbib/logging/WrappedExtLogRecord.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue