diff --git a/Jenkinsfile b/Jenkinsfile
index ea2d75d..7f442a8 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,186 +1,99 @@
-#!groovy
-
-// Required Jenkins plugins:
-// https://wiki.jenkins-ci.org/display/JENKINS/Timestamper
-// https://wiki.jenkins-ci.org/display/JENKINS/Static+Code+Analysis+Plug-ins
-// https://wiki.jenkins-ci.org/display/JENKINS/Checkstyle+Plugin ?
-// https://wiki.jenkins-ci.org/display/JENKINS/FindBugs+Plugin
-// https://wiki.jenkins-ci.org/display/JENKINS/PMD+Plugin ?
-// https://wiki.jenkins-ci.org/display/JENKINS/DRY+Plugin ?
-// https://wiki.jenkins-ci.org/display/JENKINS/Task+Scanner+Plugin
-// https://wiki.jenkins-ci.org/display/JENKINS/Javadoc+Plugin
-// https://wiki.jenkins-ci.org/display/JENKINS/JaCoCo+Plugin ?
-
-
-init()
-
-def branch_name
-def branch_name_base
-def build_number
-def build_url
-def git_commit
-def job_name
-def tag
-def version
-def build_type
-def display_name
-
-def init() {
-
- // Keep the 5 most recent builds
- properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '5']]])
-
- build_number = env.BUILD_NUMBER
- build_url = env.BUILD_URL
- job_name = "${env.JOB_NAME}"
- branch_name = env.BRANCH_NAME
- branch_name_docker = branch_name.replaceAll(/\//,'.')
- persist = "/var/lib/jenkins/PERSIST/${branch_name_docker}"
-
- // execute the branch type specific pipeline code
- try {
-
- if (branch_name.indexOf('release/')==0) build_type='release'
- if (branch_name.indexOf('feature/')==0) build_type='feature'
- if (branch_name.indexOf('develop')==0) build_type='develop'
- if (branch_name.indexOf('hotfix')==0) build_type='hotfix'
- if (branch_name.indexOf('bugfix')==0) build_type='bugfix'
- if (branch_name.indexOf('master')==0) build_type='master'
-
- // common pipeline elements
- node() {
- Initialize()
- SetVersion(build_type)
- print_vars() // after SetVersion - all variables now defined
- set_result('INPROGRESS')
- Build() // builds database via flyway migration
- }
-
- if (branch_name.indexOf('develop')==0) {
- node() {
- Deploy();
- }
- } else if (branch_name.indexOf('release/')==0) {
- node() {
- Deploy();
- }
- }
-
- node() {
- set_result('SUCCESS')
- }
-
- } catch (err) {
- node() {
- set_result('FAILURE')
- }
- throw err
- }
-}
-
-def Build() {
- stage ('build') {
- mvn "install -DskipTests=true -Dbuild.revision=${git_commit}"
- step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])
- }
-}
-
-def Initialize() {
- stage ('initialize') {
-
- // get new code
- checkout scm
-
- git_commit = getSha1()
- }
-}
-
-def Deploy() {
- stage ('deploy') {
- mvn "deploy -DskipTests=true -Dbuild.number=${build_number} -Dbuild.revision=${git_commit}"
- }
-}
-
-def getSha1() {
- sha1 = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
- echo "sha1 is ${sha1}"
- return sha1
-}
-
-def mvn(args) {
- withMaven(
- maven: 'maven-3.6.1',
- globalMavenSettingsConfig: 'locusworks-settings'
- ) {
-
- sh "mvn ${args}"
-
- }
-}
-
-def mvn_initial(args) {
- mvn(args)
-}
-
-def set_result(status) {
- if ( status == 'SUCCESS' ) {
- currentBuild.result = status
- notify_bitbucket('SUCCESSFUL')
- } else if ( status == 'FAILURE' ) {
- currentBuild.result = status
- notify_bitbucket('FAILED')
- } else if ( status == 'INPROGRESS' ) {
- notify_bitbucket('INPROGRESS')
- } else {
- error ("unknown status")
- }
-
- // save in persistence file for access the status page
- // make sure the directory exists first
- sh "mkdir -p $persist && echo $status > $persist/build.result"
-}
-
-def notify_bitbucket(state) {
-}
-
-def print_vars() {
- echo "build_number = ${build_number}"
- echo "build_url = ${build_url}"
- echo "job_name = ${job_name}"
- echo "branch_name = ${branch_name}"
- echo "branch_name_base = ${branch_name_base}"
- echo "build_type = ${build_type}"
- echo "display_name = ${currentBuild.displayName}"
- echo "version = ${version}"
- echo "git_commit = ${git_commit}"
-
-}
-
-def SetVersion( v ) {
- stage ('set version') {
- echo "set version ${v}"
- branch_name_base = (branch_name =~ /([^\/]+$)/)[0][0]
- if ( v == 'release' ) {
- // for release branches, where the branch is named "release/1.2.3",
- // derive the version and display name derive from the numeric suffix and append the build number
- // 3.2.1.100
- version = branch_name_base + "." + build_number + "-RELEASE";
- //version = branch_name.substring('release/'.length()) + "." + build_number
- currentBuild.displayName = version
- } else if (v == 'develop') {
- version = branch_name_base + "." + build_number + "-SNAPSHOT";
- currentBuild.displayName = version
- } else {
- // for all other branches the version number is 0 with an appended build number
- // and for the display name use the jenkins default #n and add the branch name
- // #101 - feature/user/foo
- //version = '0.' + build_number
- version = branch_name_base + "." + build_number
- currentBuild.displayName = "#" + build_number + " - " + branch_name_base
- }
- display_name = currentBuild.displayName
- mvn_initial "versions:set -DnewVersion=${version}"
- }
-}
-
-return this
+pipeline {
+ agent any
+
+ options {
+ buildDiscarder(logRotator(numToKeepStr: '5'))
+ disableConcurrentBuilds()
+ skipDefaultCheckout(true)
+ timestamps()
+ }
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+
+ stage('Set version') {
+ steps {
+ script {
+ String branch = env.BRANCH_NAME
+ if (!branch) {
+ error('BRANCH_NAME is unavailable; this pipeline must run as a multibranch job')
+ }
+
+ env.DEPLOY_BUILD = 'false'
+
+ def releaseMatch = branch =~ /^release\/(\d+\.\d+\.\d+)$/
+ if (releaseMatch.matches()) {
+ env.BUILD_VERSION = "${releaseMatch[0][1]}.${env.BUILD_NUMBER}-RELEASE"
+ env.DEPLOY_BUILD = 'true'
+ } else if (branch == 'develop') {
+ env.BUILD_VERSION = "develop.${env.BUILD_NUMBER}-SNAPSHOT"
+ env.DEPLOY_BUILD = 'true'
+ } else {
+ String safeBranch = branch.replaceAll(/[^A-Za-z0-9._-]/, '-')
+ env.BUILD_VERSION = "0.0.${env.BUILD_NUMBER}-${safeBranch}-SNAPSHOT"
+ }
+ currentBuild.displayName = env.BUILD_VERSION
+ }
+
+ withMaven(maven: 'maven-3.9.16', globalMavenSettingsConfig: 'locusworks-settings') {
+ sh 'mvn -B org.codehaus.mojo:versions-maven-plugin:2.21.0:set -DgenerateBackupPoms=false -DnewVersion="$BUILD_VERSION"'
+ }
+ }
+ }
+
+ stage('Prepare OWASP cache') {
+ steps {
+ lock(resource: 'owasp-nvd-cache') {
+ withMaven(maven: 'maven-3.9.16', globalMavenSettingsConfig: 'locusworks-settings') {
+ sh 'mkdir -p "$HOME/.cache/dependency-check"'
+ sh 'mvn -B org.owasp:dependency-check-maven:13.0.0:update-only -DdataDirectory="$HOME/.cache/dependency-check"'
+ }
+ }
+ }
+ }
+
+ stage('Build and test') {
+ steps {
+ withMaven(maven: 'maven-3.9.16', globalMavenSettingsConfig: 'locusworks-settings') {
+ sh 'mvn -B clean verify -DdataDirectory="$HOME/.cache/dependency-check" -DautoUpdate=false -Dbuild.revision="$GIT_COMMIT"'
+ }
+ }
+ post {
+ always {
+ junit allowEmptyResults: true, testResults: '**/target/surefire-reports/*.xml'
+ }
+ success {
+ archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
+ archiveArtifacts artifacts: '**/target/site/jacoco/**', fingerprint: true
+ }
+ }
+ }
+
+ stage('Deploy') {
+ when {
+ expression { env.DEPLOY_BUILD == 'true' }
+ }
+ steps {
+ withMaven(maven: 'maven-3.9.16', globalMavenSettingsConfig: 'locusworks-settings') {
+ sh 'mvn -B deploy -DskipTests -Ddependency-check.skip=true -Dbuild.number="$BUILD_NUMBER" -Dbuild.revision="$GIT_COMMIT"'
+ }
+ }
+ }
+ }
+
+ post {
+ success {
+ echo "Published build ${env.BUILD_VERSION} successfully"
+ }
+ failure {
+ echo "Build ${env.BUILD_VERSION ?: env.BUILD_NUMBER} failed"
+ }
+ cleanup {
+ deleteDir()
+ }
+ }
+}
diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml
deleted file mode 100644
index 8a373e0..0000000
--- a/bitbucket-pipelines.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-# This is a sample build configuration for Java (Maven).
-# Check our guides at https://confluence.atlassian.com/x/zd-5Mw for more examples.
-# Only use spaces to indent your .yml configuration.
-# -----
-# You can specify a custom docker image from Docker Hub as your build environment.
-image: maven:3.3.9
-
-pipelines:
- default:
- - step:
- caches:
- - maven
- script: # Modify the commands below to build your repository.
- - mvn -B verify # -B batch mode makes Maven less verbose
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 795a5e6..c418f14 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,156 +1,197 @@
-
- 4.0.0
- net.locusworks
- applogger
- 0.0.1-SNAPSHOT
- Application Logger
- Logger library for applications
-
-
- ssh://gitea@gitea.locusworks.net:7999/locusworks/app-logger.git
-
-
- locusworks
- www.locusworks.net
-
-
-
- ${project.basedir}
- 1.8
- 1.8
- 2.19.0
- 2.0.1
- https://nexus.locusworks.net
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 3.0.0-M7
-
- always
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.10.1
-
- ${maven.compiler.source}
- ${maven.compiler.target}
- true
-
- -Xlint:all
-
-
-
-
- org.owasp
- dependency-check-maven
- 7.2.0
-
-
-
- check
-
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
- 3.1.0
-
-
- enforce
-
-
-
-
-
-
- enforce
-
-
-
-
-
-
-
-
-
- org.apache.logging.log4j
- log4j-api
- ${log4j.version}
-
-
- org.apache.logging.log4j
- log4j-core
- ${log4j.version}
-
-
- org.apache.logging.log4j
- log4j-slf4j-impl
- ${log4j.version}
-
-
- org.slf4j
- slf4j-api
-
-
-
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
-
-
- org.slf4j
- jcl-over-slf4j
- ${slf4j.version}
-
-
-
-
-
- nexus-snapshot
- ${nexus.repo}/repository/locusworks-snapshot/
-
-
- nexus-release
- ${nexus.repo}/repository/locusworks-release/
-
-
-
-
-
- locusworks-public
- locusworks-public
- ${nexus.repo}/repository/locusworks-public/
-
- true
-
-
- true
-
-
-
-
-
-
- locusworks-public
- ${nexus.repo}/repository/locusworks-public/
-
- true
-
-
- true
-
-
-
-
-
+
+ 4.0.0
+ net.locusworks
+ applogger
+ 0.0.1-SNAPSHOT
+ Application Logger
+ Logger library for applications
+
+
+ ssh://gitea@gitea.locusworks.net:7999/locusworks/app-logger.git
+
+
+ locusworks
+ www.locusworks.net
+
+
+
+ 2.0.18
+ 5.14.4
+ 5.23.0
+ 0.8.15
+ 21
+ 21
+ UTF-8
+ https://nexus.locusworks.net
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+
+ prepare-agent
+
+
+
+ report
+ verify
+
+ report
+
+
+
+ coverage-check
+ verify
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ LINE
+ COVEREDRATIO
+ 0.75
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.6
+
+ @{argLine} -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.15.0
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+ true
+
+ -Xlint:all
+
+
+
+
+ org.owasp
+ dependency-check-maven
+ 13.0.0
+
+ true
+ true
+ true
+ true
+ e2eb1036-9b5b-4df9-95e7-0888be947011
+
+
+
+
+ check
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.6.3
+
+
+ enforce
+
+
+
+
+
+
+ enforce
+
+
+
+
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
+
+
+
+ nexus-snapshot
+ ${nexus.repo}/repository/locusworks-snapshot/
+
+
+ nexus-release
+ ${nexus.repo}/repository/locusworks-release/
+
+
+
+
+
+ locusworks-public
+ locusworks-public
+ ${nexus.repo}/repository/locusworks-public/
+
+ true
+
+
+ true
+
+
+
+
+
+
+ locusworks-public
+ ${nexus.repo}/repository/locusworks-public/
+
+ true
+
+
+ true
+
+
+
+
+
diff --git a/src/main/java/net/locusworks/logger/ApplicationLogger.java b/src/main/java/net/locusworks/logger/ApplicationLogger.java
index a6f2b52..a0c8027 100644
--- a/src/main/java/net/locusworks/logger/ApplicationLogger.java
+++ b/src/main/java/net/locusworks/logger/ApplicationLogger.java
@@ -4,7 +4,9 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.slf4j.Marker;
+import org.slf4j.Marker;
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MessageFormatter;
/**
* Logger implementation to use across applications. Extends slf4j logger to work with other logging applications
@@ -13,9 +15,9 @@ import org.slf4j.Marker;
*/
public class ApplicationLogger implements org.slf4j.Logger {
- protected static final String DEFAULT_FORMAT = "%s";
-
- private static final Logger logger = LoggerFactory.getLogger(ApplicationLogger.class);
+ protected static final String DEFAULT_FORMAT = "{}";
+
+ private final Logger logger;
private final String name;
@@ -41,10 +43,15 @@ public class ApplicationLogger implements org.slf4j.Logger {
* @param name name of the logger
* @param level level of logging to set the logger to
*/
- protected ApplicationLogger(String name, LogLevel level) {
- this.name = name;
- this.level = level;
- }
+ protected ApplicationLogger(String name, LogLevel level) {
+ this(name, level, LoggerFactory.getLogger(name));
+ }
+
+ ApplicationLogger(String name, LogLevel level, Logger logger) {
+ this.name = name;
+ this.level = level;
+ this.logger = logger;
+ }
/**
* Constructor
@@ -104,8 +111,8 @@ public class ApplicationLogger implements org.slf4j.Logger {
}
@Override
- public boolean isTraceEnabled() {
- return this.level.log(LogLevel.TRACE);
+ public boolean isTraceEnabled() {
+ return enabled(LogLevel.TRACE) && logger.isTraceEnabled();
}
@Override
@@ -129,43 +136,43 @@ public class ApplicationLogger implements org.slf4j.Logger {
}
@Override
- public void trace(String msg, Throwable t) {
- trace("%s%n%s", msg, getStackTrace(t.getStackTrace()));
+ public void trace(String msg, Throwable t) {
+ log(LogLevel.TRACE, msg, t);
}
@Override
- public boolean isTraceEnabled(Marker marker) {
- return isTraceEnabled();
+ public boolean isTraceEnabled(Marker marker) {
+ return enabled(LogLevel.TRACE) && logger.isTraceEnabled(marker);
}
@Override
- public void trace(Marker marker, String msg) {
- trace(msg);
+ public void trace(Marker marker, String msg) {
+ markerLog(LogLevel.TRACE, marker, msg);
}
@Override
public void trace(Marker marker, String format, Object arg) {
- trace(format, arg);
+ markerLog(LogLevel.TRACE, marker, format, arg);
}
@Override
public void trace(Marker marker, String format, Object arg1, Object arg2) {
- trace(format, arg1, arg2);
+ markerLog(LogLevel.TRACE, marker, format, arg1, arg2);
}
@Override
public void trace(Marker marker, String format, Object... argArray) {
- trace(format, argArray);
+ markerLog(LogLevel.TRACE, marker, format, argArray);
}
@Override
public void trace(Marker marker, String msg, Throwable t) {
- trace(msg, t);
+ markerLog(LogLevel.TRACE, marker, msg, t);
}
@Override
- public boolean isDebugEnabled() {
- return this.level.log(LogLevel.DEBUG);
+ public boolean isDebugEnabled() {
+ return enabled(LogLevel.DEBUG) && logger.isDebugEnabled();
}
@Override
@@ -189,43 +196,43 @@ public class ApplicationLogger implements org.slf4j.Logger {
}
@Override
- public void debug(String msg, Throwable t) {
- debug("%s%n%s", msg, getStackTrace(t.getStackTrace()));
+ public void debug(String msg, Throwable t) {
+ log(LogLevel.DEBUG, msg, t);
}
@Override
- public boolean isDebugEnabled(Marker marker) {
- return isDebugEnabled();
+ public boolean isDebugEnabled(Marker marker) {
+ return enabled(LogLevel.DEBUG) && logger.isDebugEnabled(marker);
}
@Override
- public void debug(Marker marker, String msg) {
- debug(msg);
+ public void debug(Marker marker, String msg) {
+ markerLog(LogLevel.DEBUG, marker, msg);
}
@Override
public void debug(Marker marker, String format, Object arg) {
- debug(format, arg);
+ markerLog(LogLevel.DEBUG, marker, format, arg);
}
@Override
public void debug(Marker marker, String format, Object arg1, Object arg2) {
- debug(format, arg1, arg2);
+ markerLog(LogLevel.DEBUG, marker, format, arg1, arg2);
}
@Override
public void debug(Marker marker, String format, Object... arguments) {
- debug(format, arguments);
+ markerLog(LogLevel.DEBUG, marker, format, arguments);
}
@Override
public void debug(Marker marker, String msg, Throwable t) {
- debug(msg, t);
+ markerLog(LogLevel.DEBUG, marker, msg, t);
}
@Override
- public boolean isInfoEnabled() {
- return this.level.log(LogLevel.INFO);
+ public boolean isInfoEnabled() {
+ return enabled(LogLevel.INFO) && logger.isInfoEnabled();
}
@Override
@@ -249,8 +256,8 @@ public class ApplicationLogger implements org.slf4j.Logger {
}
@Override
- public void info(String msg, Throwable t) {
- info("%s%n%s", msg, getStackTrace(t.getStackTrace()));
+ public void info(String msg, Throwable t) {
+ log(LogLevel.INFO, msg, t);
}
/**
@@ -282,38 +289,38 @@ public class ApplicationLogger implements org.slf4j.Logger {
}
@Override
- public boolean isInfoEnabled(Marker marker) {
- return isInfoEnabled();
+ public boolean isInfoEnabled(Marker marker) {
+ return enabled(LogLevel.INFO) && logger.isInfoEnabled(marker);
}
@Override
- public void info(Marker marker, String msg) {
- info(msg);
+ public void info(Marker marker, String msg) {
+ markerLog(LogLevel.INFO, marker, msg);
}
@Override
public void info(Marker marker, String format, Object arg) {
- info(format, arg);
+ markerLog(LogLevel.INFO, marker, format, arg);
}
@Override
public void info(Marker marker, String format, Object arg1, Object arg2) {
- info(format, arg1, arg2);
+ markerLog(LogLevel.INFO, marker, format, arg1, arg2);
}
@Override
public void info(Marker marker, String format, Object... arguments) {
- info(format, arguments);
+ markerLog(LogLevel.INFO, marker, format, arguments);
}
@Override
public void info(Marker marker, String msg, Throwable t) {
- info(msg, t);
+ markerLog(LogLevel.INFO, marker, msg, t);
}
@Override
- public boolean isWarnEnabled() {
- return this.level.log(LogLevel.WARN);
+ public boolean isWarnEnabled() {
+ return enabled(LogLevel.WARN) && logger.isWarnEnabled();
}
@Override
@@ -337,38 +344,38 @@ public class ApplicationLogger implements org.slf4j.Logger {
}
@Override
- public void warn(String msg, Throwable t) {
- warn("%s%n%s", msg, getStackTrace(t.getStackTrace()));
+ public void warn(String msg, Throwable t) {
+ log(LogLevel.WARN, msg, t);
}
@Override
- public boolean isWarnEnabled(Marker marker) {
- return isWarnEnabled();
+ public boolean isWarnEnabled(Marker marker) {
+ return enabled(LogLevel.WARN) && logger.isWarnEnabled(marker);
}
@Override
- public void warn(Marker marker, String msg) {
- warn(msg);
+ public void warn(Marker marker, String msg) {
+ markerLog(LogLevel.WARN, marker, msg);
}
@Override
public void warn(Marker marker, String format, Object arg) {
- warn(format, arg);
+ markerLog(LogLevel.WARN, marker, format, arg);
}
@Override
public void warn(Marker marker, String format, Object arg1, Object arg2) {
- warn(format, arg1, arg2);
+ markerLog(LogLevel.WARN, marker, format, arg1, arg2);
}
@Override
public void warn(Marker marker, String format, Object... arguments) {
- warn(format, arguments);
+ markerLog(LogLevel.WARN, marker, format, arguments);
}
@Override
public void warn(Marker marker, String msg, Throwable t) {
- warn(msg, t);
+ markerLog(LogLevel.WARN, marker, msg, t);
}
/**
@@ -399,8 +406,8 @@ public class ApplicationLogger implements org.slf4j.Logger {
}
@Override
- public boolean isErrorEnabled() {
- return this.level.log(LogLevel.ERROR);
+ public boolean isErrorEnabled() {
+ return enabled(LogLevel.ERROR) && logger.isErrorEnabled();
}
@Override
@@ -428,8 +435,8 @@ public class ApplicationLogger implements org.slf4j.Logger {
}
@Override
- public void error(String msg, Throwable t) {
- error("%s%n%s", msg, getStackTrace(t.getStackTrace()));
+ public void error(String msg, Throwable t) {
+ log(LogLevel.ERROR, msg, t);
}
/**
@@ -447,8 +454,8 @@ public class ApplicationLogger implements org.slf4j.Logger {
* @param sb String builder to append message to
* @param e throwable error to log
*/
- public void error(Map values, StringBuilder sb, Throwable e) {
- error(values, null, sb);
+ public void error(Map values, StringBuilder sb, Throwable e) {
+ error(values, null, sb, e);
}
/**
@@ -468,41 +475,38 @@ public class ApplicationLogger implements org.slf4j.Logger {
* @param sb string builder to append messages to
* @param e Throwable error to log
*/
- public void error(Map values, String valueFormat, StringBuilder sb, Throwable e) {
- msgHelper(LogLevel.ERROR, values, valueFormat, sb);
- if (e != null) {
- msgHelper(LogLevel.ERROR,"", e);
- }
+ public void error(Map values, String valueFormat, StringBuilder sb, Throwable e) {
+ log(LogLevel.ERROR, formatValues(values, valueFormat, sb), e);
}
@Override
- public boolean isErrorEnabled(Marker marker) {
- return isErrorEnabled();
+ public boolean isErrorEnabled(Marker marker) {
+ return enabled(LogLevel.ERROR) && logger.isErrorEnabled(marker);
}
@Override
- public void error(Marker marker, String msg) {
- error(msg);
+ public void error(Marker marker, String msg) {
+ markerLog(LogLevel.ERROR, marker, msg);
}
@Override
public void error(Marker marker, String format, Object arg) {
- error(format, arg);
+ markerLog(LogLevel.ERROR, marker, format, arg);
}
@Override
public void error(Marker marker, String format, Object arg1, Object arg2) {
- error(format, arg1, arg2);
+ markerLog(LogLevel.ERROR, marker, format, arg1, arg2);
}
@Override
public void error(Marker marker, String format, Object... arguments) {
- error(format, arguments);
+ markerLog(LogLevel.ERROR, marker, format, arguments);
}
@Override
public void error(Marker marker, String msg, Throwable t) {
- error(msg, t);
+ markerLog(LogLevel.ERROR, marker, msg, t);
}
/**
@@ -510,7 +514,7 @@ public class ApplicationLogger implements org.slf4j.Logger {
* @param level log level to use
* @param message message to log
*/
- protected void log(LogLevel level, String message) {
+ protected void log(LogLevel level, String message) {
if (level == null ||this.level == null || level.toInt() < this.level.toInt()) return;
switch (level) {
@@ -535,7 +539,43 @@ public class ApplicationLogger implements org.slf4j.Logger {
default:
break;
}
- }
+ }
+
+ private boolean enabled(LogLevel messageLevel) {
+ LogLevel current = this.level;
+ return current != null && current != LogLevel.OFF && messageLevel.toInt() >= current.toInt();
+ }
+
+ protected void log(LogLevel level, String message, Throwable throwable) {
+ if (level == null || this.level == null || level.toInt() < this.level.toInt()) return;
+ switch (level) {
+ case ALL:
+ case TRACE: logger.trace(message, throwable); break;
+ case DEBUG: logger.debug(message, throwable); break;
+ case INFO: logger.info(message, throwable); break;
+ case WARN: logger.warn(message, throwable); break;
+ case ERROR:
+ case FATAL: logger.error(message, throwable); break;
+ case OFF:
+ default: break;
+ }
+ }
+
+ private void markerLog(LogLevel level, Marker marker, String message, Object... arguments) {
+ if (level == null || this.level == null || level.toInt() < this.level.toInt()) return;
+ FormattingTuple tuple = MessageFormatter.arrayFormat(message, arguments);
+ switch (level) {
+ case ALL:
+ case TRACE: logger.trace(marker, tuple.getMessage(), tuple.getThrowable()); break;
+ case DEBUG: logger.debug(marker, tuple.getMessage(), tuple.getThrowable()); break;
+ case INFO: logger.info(marker, tuple.getMessage(), tuple.getThrowable()); break;
+ case WARN: logger.warn(marker, tuple.getMessage(), tuple.getThrowable()); break;
+ case ERROR:
+ case FATAL: logger.error(marker, tuple.getMessage(), tuple.getThrowable()); break;
+ case OFF:
+ default: break;
+ }
+ }
/**
* Print the exception stack trace to a string
@@ -566,11 +606,13 @@ public class ApplicationLogger implements org.slf4j.Logger {
* @param valueFormat value format to map values as
* @param sb String builder to append messages to
*/
- private void msgHelper(LogLevel level, Map values, String valueFormat, StringBuilder sb) {
-
- if (sb == null) {
- sb = new StringBuilder();
- }
+ private void msgHelper(LogLevel level, Map values, String valueFormat, StringBuilder sb) {
+ log(level, formatValues(values, valueFormat, sb));
+ }
+
+ private String formatValues(Map values, String valueFormat, StringBuilder sb) {
+ if (values == null) throw new IllegalArgumentException("values must not be null");
+ if (sb == null) sb = new StringBuilder();
if (valueFormat == null || valueFormat.trim().isEmpty()) {
int longest = 0;
@@ -590,8 +632,8 @@ public class ApplicationLogger implements org.slf4j.Logger {
.map(entry -> String.format(format, entry.getKey(), entry.getValue()))
.forEach(builder::append);
- log(level, builder.toString());
- }
+ return builder.toString();
+ }
/**
* Help format a message for logging
@@ -599,8 +641,9 @@ public class ApplicationLogger implements org.slf4j.Logger {
* @param format The string format to use
* @param args Arguments to use in the string format
*/
- private void msgHelper(LogLevel level, String format, Object...args) {
- log(level, String.format(format, args));
+ private void msgHelper(LogLevel level, String format, Object...args) {
+ FormattingTuple tuple = MessageFormatter.arrayFormat(format, args);
+ log(level, tuple.getMessage(), tuple.getThrowable());
}
/**
@@ -619,7 +662,7 @@ public class ApplicationLogger implements org.slf4j.Logger {
* @param message the message to log
* @return the formatted string
*/
- private String format(String message, LogLevel level) {
- return String.format("th%06d [%-5s] [%s] %s", Thread.currentThread().getId(), level, this.name, message);
- }
+ private String format(String message, LogLevel level) {
+ return String.format("th%06d [%-5s] [%s] %s", Thread.currentThread().threadId(), level, this.name, message);
+ }
}
diff --git a/src/main/java/net/locusworks/logger/ApplicationLoggerFactory.java b/src/main/java/net/locusworks/logger/ApplicationLoggerFactory.java
index d43d811..11729af 100644
--- a/src/main/java/net/locusworks/logger/ApplicationLoggerFactory.java
+++ b/src/main/java/net/locusworks/logger/ApplicationLoggerFactory.java
@@ -2,7 +2,7 @@ package net.locusworks.logger;
import java.util.List;
import java.util.Map;
-import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/***
@@ -12,9 +12,9 @@ import java.util.stream.Collectors;
* @date 02/15/2018
*/
public class ApplicationLoggerFactory {
- private static Map loggers = new TreeMap<>();
- private static LogLevel DEFAULT_LEVEL = LogLevel.ALL;
- private static ApplicationLoggerInitializer initializer;
+ private static final Map loggers = new ConcurrentHashMap<>();
+ private static volatile LogLevel defaultLevel = LogLevel.ALL;
+ private static volatile ApplicationLoggerInitializer initializer;
/**
* Need to get the log level from the properties file.
@@ -24,7 +24,8 @@ public class ApplicationLoggerFactory {
*/
public static void init(ApplicationLoggerInitializer init) {
initializer = init;
- DEFAULT_LEVEL = initializer == null ? LogLevel.INFO : initializer.initialize();
+ LogLevel initializedLevel = initializer == null ? LogLevel.INFO : initializer.initialize();
+ defaultLevel = initializedLevel == null ? LogLevel.INFO : initializedLevel;
loadLoggers();
}
@@ -60,7 +61,7 @@ public class ApplicationLoggerFactory {
* @return
*/
private static LogLevel getDefaultLevel() {
- return DEFAULT_LEVEL;
+ return defaultLevel;
}
/**
@@ -70,10 +71,8 @@ public class ApplicationLoggerFactory {
* @return the logger represented by the given name
*/
public static ApplicationLogger getLogger(String name, LogLevel level) {
- if (!loggers.containsKey(name)) {
- loggers.put(name, new ApplicationLogger(name, level));
- }
- ApplicationLogger logger = loggers.get(name);
+ ApplicationLogger logger = loggers.computeIfAbsent(name,
+ loggerName -> new ApplicationLogger(loggerName, level == null ? getDefaultLevel() : level));
if (initializer != null) {
initializer.postGetLogger(logger.getName(), logger.getLogLevel().toName());
}
diff --git a/src/main/java/net/locusworks/logger/LineLoggingOutputStream.java b/src/main/java/net/locusworks/logger/LineLoggingOutputStream.java
new file mode 100644
index 0000000..b0dd5a3
--- /dev/null
+++ b/src/main/java/net/locusworks/logger/LineLoggingOutputStream.java
@@ -0,0 +1,60 @@
+package net.locusworks.logger;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.function.Consumer;
+
+/** Buffers redirected output so each complete UTF-8 line becomes one log event. */
+final class LineLoggingOutputStream extends OutputStream {
+ private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ private final Consumer sink;
+
+ LineLoggingOutputStream(Consumer sink) {
+ this.sink = sink;
+ }
+
+ @Override
+ public synchronized void write(int value) {
+ if (value == '\n') {
+ emit();
+ } else {
+ buffer.write(value);
+ }
+ }
+
+ @Override
+ public synchronized void write(byte[] bytes, int offset, int length) {
+ int end = offset + length;
+ int start = offset;
+ for (int index = offset; index < end; index++) {
+ if (bytes[index] == '\n') {
+ buffer.write(bytes, start, index - start);
+ emit();
+ start = index + 1;
+ }
+ }
+ buffer.write(bytes, start, end - start);
+ }
+
+ @Override
+ public synchronized void flush() {
+ if (buffer.size() > 0) emit();
+ }
+
+ @Override
+ public void close() throws IOException {
+ flush();
+ super.close();
+ }
+
+ private void emit() {
+ byte[] bytes = buffer.toByteArray();
+ buffer.reset();
+ int length = bytes.length;
+ if (length > 0 && bytes[length - 1] == '\r') length--;
+ sink.accept(new String(bytes, 0, length, UTF_8));
+ }
+}
diff --git a/src/main/java/net/locusworks/logger/LogLevel.java b/src/main/java/net/locusworks/logger/LogLevel.java
index ad108c8..d11846a 100644
--- a/src/main/java/net/locusworks/logger/LogLevel.java
+++ b/src/main/java/net/locusworks/logger/LogLevel.java
@@ -16,10 +16,10 @@ public enum LogLevel {
TRACE(1, "TRACE"),
ALL(0, "ALL");
- private Integer intLevel;
- private String levelName;
+ private final int intLevel;
+ private final String levelName;
- private LogLevel(Integer intLevel, String levelName) {
+ LogLevel(int intLevel, String levelName) {
this.intLevel = intLevel;
this.levelName = levelName;
}
@@ -41,7 +41,7 @@ public enum LogLevel {
* Get the integer representation of the log level
* @return intLevel
*/
- public Integer toInt() {
+ public int toInt() {
return this.intLevel;
}
@@ -64,7 +64,7 @@ public enum LogLevel {
*/
public static LogLevel getEnum(Integer level) {
for (LogLevel l : values()) {
- if (l.toInt().equals(level)) {
+ if (level != null && l.toInt() == level) {
return l;
}
}
@@ -77,6 +77,7 @@ public enum LogLevel {
* @return the log level for the name or null if it cant be found
*/
public static LogLevel getEnum(String levelName) {
+ if (levelName == null) return null;
for (LogLevel l : values()) {
if (l.toName().equalsIgnoreCase(levelName)) {
return l;
@@ -84,4 +85,4 @@ public enum LogLevel {
}
return null;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/net/locusworks/logger/SystemDebugLogger.java b/src/main/java/net/locusworks/logger/SystemDebugLogger.java
index 5a46181..942fd4d 100644
--- a/src/main/java/net/locusworks/logger/SystemDebugLogger.java
+++ b/src/main/java/net/locusworks/logger/SystemDebugLogger.java
@@ -1,36 +1,15 @@
package net.locusworks.logger;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
+import java.io.PrintStream;
@ApplicationLoggerInfo(name="System.Debug", defaultLevel=LogLevel.DEBUG)
public class SystemDebugLogger extends PrintStream {
- private ApplicationLogger logger;
-
public SystemDebugLogger() {
this(ApplicationLoggerFactory.getLogger(SystemDebugLogger.class));
}
- public SystemDebugLogger(ApplicationLogger logger) {
- super(new ByteArrayOutputStream());
- this.logger = logger;
- }
-
- @Override
- public void write(byte[] b) {
- logger.debug(new String(b, UTF_8));
- }
-
- @Override
- public void write(byte[] b, int off, int len) {
- logger.debug(new String(b, off, len, UTF_8));
- }
-
- @Override
- public void write(int b) {
- write(new byte[] { (byte)b });
- }
-}
+ public SystemDebugLogger(ApplicationLogger logger) {
+ super(new LineLoggingOutputStream(logger::debug), true);
+ }
+}
diff --git a/src/main/java/net/locusworks/logger/SystemErrLogger.java b/src/main/java/net/locusworks/logger/SystemErrLogger.java
index f7a9552..de0936e 100644
--- a/src/main/java/net/locusworks/logger/SystemErrLogger.java
+++ b/src/main/java/net/locusworks/logger/SystemErrLogger.java
@@ -1,36 +1,15 @@
package net.locusworks.logger;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
+import java.io.PrintStream;
@ApplicationLoggerInfo(name="System.Err", defaultLevel=LogLevel.ERROR)
public class SystemErrLogger extends PrintStream {
- private ApplicationLogger logger;
-
public SystemErrLogger() {
this(ApplicationLoggerFactory.getLogger(SystemErrLogger.class));
}
- public SystemErrLogger(ApplicationLogger logger) {
- super(new ByteArrayOutputStream());
- this.logger = logger;
- }
-
- @Override
- public void write(byte[] b) {
- logger.error(new String(b, UTF_8));
- }
-
- @Override
- public void write(byte[] b, int off, int len) {
- logger.error(new String(b, off, len, UTF_8));
- }
-
- @Override
- public void write(int b) {
- write(new byte[] { (byte)b });
- }
-}
+ public SystemErrLogger(ApplicationLogger logger) {
+ super(new LineLoggingOutputStream(logger::error), true);
+ }
+}
diff --git a/src/main/java/net/locusworks/logger/SystemOutLogger.java b/src/main/java/net/locusworks/logger/SystemOutLogger.java
index bf9e057..f67a136 100644
--- a/src/main/java/net/locusworks/logger/SystemOutLogger.java
+++ b/src/main/java/net/locusworks/logger/SystemOutLogger.java
@@ -1,36 +1,15 @@
package net.locusworks.logger;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
+import java.io.PrintStream;
@ApplicationLoggerInfo(name="System.Out", defaultLevel=LogLevel.OFF)
public class SystemOutLogger extends PrintStream {
- private ApplicationLogger logger;
-
public SystemOutLogger() {
this(ApplicationLoggerFactory.getLogger(SystemOutLogger.class));
}
- public SystemOutLogger(ApplicationLogger logger) {
- super(new ByteArrayOutputStream());
- this.logger = logger;
- }
-
- @Override
- public void write(byte[] b) {
- logger.info(new String(b, UTF_8));
- }
-
- @Override
- public void write(byte[] b, int off, int len) {
- logger.info(new String(b, off, len, UTF_8));
- }
-
- @Override
- public void write(int b) {
- write(new byte[] { (byte)b });
- }
-}
+ public SystemOutLogger(ApplicationLogger logger) {
+ super(new LineLoggingOutputStream(logger::info), true);
+ }
+}
diff --git a/src/main/resources/log4j.xml b/src/main/resources/log4j.xml
deleted file mode 100644
index f381635..0000000
--- a/src/main/resources/log4j.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
- %d{dd-MMM-yyyy HH:mm:ss.SSS} %m%n
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/test/java/net/locusworks/logger/ApplicationLoggerDelegationTest.java b/src/test/java/net/locusworks/logger/ApplicationLoggerDelegationTest.java
new file mode 100644
index 0000000..e078842
--- /dev/null
+++ b/src/test/java/net/locusworks/logger/ApplicationLoggerDelegationTest.java
@@ -0,0 +1,101 @@
+package net.locusworks.logger;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.*;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+class ApplicationLoggerDelegationTest {
+ @Test
+ void delegatesFormattingMarkersAndThrowablesAtEveryLevel() {
+ Logger delegate = mock(Logger.class);
+ Marker marker = mock(Marker.class);
+ RuntimeException failure = new RuntimeException("boom");
+ when(delegate.getName()).thenReturn("test");
+ when(delegate.isTraceEnabled()).thenReturn(true);
+ when(delegate.isDebugEnabled()).thenReturn(true);
+ when(delegate.isInfoEnabled()).thenReturn(true);
+ when(delegate.isWarnEnabled()).thenReturn(true);
+ when(delegate.isErrorEnabled()).thenReturn(true);
+ when(delegate.isTraceEnabled(marker)).thenReturn(true);
+ when(delegate.isDebugEnabled(marker)).thenReturn(true);
+ when(delegate.isInfoEnabled(marker)).thenReturn(true);
+ when(delegate.isWarnEnabled(marker)).thenReturn(true);
+ when(delegate.isErrorEnabled(marker)).thenReturn(true);
+
+ ApplicationLogger logger = new ApplicationLogger("test", LogLevel.ALL, delegate);
+ assertEquals("test", logger.getName());
+ assertTrue(logger.isTraceEnabled());
+ assertTrue(logger.isDebugEnabled(marker));
+ assertTrue(logger.isInfoEnabled());
+ assertTrue(logger.isWarnEnabled(marker));
+ assertTrue(logger.isErrorEnabled());
+
+ logger.trace("trace {}", 1);
+ logger.trace("trace {} {}", 1, 2);
+ logger.trace("trace {}", new Object[] {1});
+ logger.trace("trace failure", failure);
+ logger.debug("debug {}", 1);
+ logger.debug("debug {} {}", 1, 2);
+ logger.debug("debug {}", new Object[] {1});
+ logger.debug("debug failure", failure);
+ logger.info("info {}", 1);
+ logger.info("info {} {}", 1, 2);
+ logger.info("info {}", new Object[] {1});
+ logger.info("info failure", failure);
+ logger.warn("warn {}", 1);
+ logger.warn("warn {} {}", 1, 2);
+ logger.warn("warn {}", new Object[] {1});
+ logger.warn("warn failure", failure);
+ logger.error("error {}", 1);
+ logger.error("error {} {}", 1, 2);
+ logger.error("error {}", new Object[] {1});
+ logger.error("error failure", failure);
+ logger.error(failure);
+
+ logger.trace(marker, "trace {}", 1);
+ logger.trace(marker, "trace {} {}", 1, 2);
+ logger.trace(marker, "trace", new Object[0]);
+ logger.trace(marker, "trace failure", failure);
+ logger.debug(marker, "debug {}", 1);
+ logger.debug(marker, "debug {} {}", 1, 2);
+ logger.debug(marker, "debug", new Object[0]);
+ logger.debug(marker, "debug failure", failure);
+ logger.info(marker, "info {}", 1);
+ logger.info(marker, "info {} {}", 1, 2);
+ logger.info(marker, "info", new Object[0]);
+ logger.info(marker, "info failure", failure);
+ logger.warn(marker, "warn {}", 1);
+ logger.warn(marker, "warn {} {}", 1, 2);
+ logger.warn(marker, "warn", new Object[0]);
+ logger.warn(marker, "warn failure", failure);
+ logger.error(marker, "error {}", 1);
+ logger.error(marker, "error {} {}", 1, 2);
+ logger.error(marker, "error", new Object[0]);
+ logger.error(marker, "error failure", failure);
+
+ Map values = new LinkedHashMap<>();
+ values.put("a", "one");
+ values.put("long", "two");
+ logger.info(values);
+ logger.warn(values, new StringBuilder("prefix"));
+ logger.error(values, "%s=%s", new StringBuilder(), failure);
+
+ verify(delegate, atLeastOnce()).info(contains("info 1"), org.mockito.ArgumentMatchers.isNull(Throwable.class));
+ verify(delegate).error("error failure", failure);
+ verify(delegate).error(marker, "error failure", failure);
+ verify(delegate).error(contains("a=one"), same(failure));
+
+ logger.setLogLevel(LogLevel.OFF);
+ assertEquals(LogLevel.OFF, logger.getLogLevel());
+ assertFalse(logger.isErrorEnabled());
+ logger.error("not logged");
+ verify(delegate, never()).error(contains("not logged"));
+ }
+}
diff --git a/src/test/java/net/locusworks/logger/ApplicationLoggerFactoryTest.java b/src/test/java/net/locusworks/logger/ApplicationLoggerFactoryTest.java
new file mode 100644
index 0000000..cb19805
--- /dev/null
+++ b/src/test/java/net/locusworks/logger/ApplicationLoggerFactoryTest.java
@@ -0,0 +1,51 @@
+package net.locusworks.logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class ApplicationLoggerFactoryTest {
+ @ApplicationLoggerInfo(name = "annotated-name", defaultLevel = LogLevel.WARN)
+ private static final class AnnotatedType { }
+
+ @Test
+ void initializesLoadsConfiguresAndSavesLoggers() {
+ ApplicationLoggerInitializer initializer = mock(ApplicationLoggerInitializer.class);
+ when(initializer.initialize()).thenReturn(LogLevel.DEBUG);
+ ApplicationLoggerFactory.init(initializer);
+ verify(initializer).loadLoggers();
+
+ ApplicationLogger annotated = ApplicationLoggerFactory.getLogger(AnnotatedType.class);
+ assertEquals("annotated-name", annotated.getName());
+ assertEquals(LogLevel.WARN, annotated.getLogLevel());
+ verify(initializer).postGetLogger("annotated-name", "WARN");
+
+ ApplicationLoggerFactory.setLogLevels(List.of(new ApplicationLoggerFactory.LoggerInfo("annotated-name", "ERROR")));
+ assertEquals(LogLevel.ERROR, annotated.getLogLevel());
+ ApplicationLogger added = new ApplicationLogger("added-test", LogLevel.INFO);
+ ApplicationLoggerFactory.addLogger(added);
+ assertSame(added, ApplicationLoggerFactory.getLogger("added-test"));
+ ApplicationLoggerFactory.addLoggers(List.of(new ApplicationLoggerFactory.LoggerInfo("list-test", "TRACE")));
+ assertEquals(LogLevel.TRACE, ApplicationLoggerFactory.getLogger("list-test").getLogLevel());
+
+ ApplicationLoggerFactory.saveLogLevels();
+ verify(initializer).saveLogLevels(anyMap());
+ ApplicationLoggerFactory.loadLoggers(null);
+ ApplicationLoggerFactory.saveLogLevels(null);
+ }
+
+ @Test
+ void loggerInfoSupportsBeanAccess() {
+ ApplicationLoggerFactory.LoggerInfo info = new ApplicationLoggerFactory.LoggerInfo();
+ info.setLogger("logger");
+ info.setLevel("INFO");
+ assertEquals("logger", info.getLogger());
+ assertEquals("INFO", info.getLevel());
+ }
+}
diff --git a/src/test/java/net/locusworks/logger/ApplicationLoggerTest.java b/src/test/java/net/locusworks/logger/ApplicationLoggerTest.java
new file mode 100644
index 0000000..a439957
--- /dev/null
+++ b/src/test/java/net/locusworks/logger/ApplicationLoggerTest.java
@@ -0,0 +1,65 @@
+package net.locusworks.logger;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+import org.junit.jupiter.api.Test;
+
+class ApplicationLoggerTest {
+ @Test
+ void filtersMessagesBelowConfiguredLevel() {
+ ApplicationLogger logger = new ApplicationLogger("level-test", LogLevel.INFO);
+ assertTrue(logger.isInfoEnabled());
+ assertFalse(logger.isDebugEnabled());
+ }
+
+ @Test
+ void factoryReturnsOneInstanceUnderConcurrency() {
+ List results = IntStream.range(0, 100).parallel()
+ .mapToObj(i -> ApplicationLoggerFactory.getLogger("concurrent-test"))
+ .toList();
+ results.forEach(logger -> assertSame(results.get(0), logger));
+ }
+
+ @Test
+ void supportsSlf4jFormattingAndPreservesThrowable() throws Exception {
+ PrintStream original = System.err;
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ try (PrintStream capture = new PrintStream(output, true, UTF_8)) {
+ System.setErr(capture);
+ ApplicationLogger logger = new ApplicationLogger("format-test", LogLevel.INFO);
+ IllegalStateException failure = new IllegalStateException("failure-detail");
+ logger.info("Hello {}", "world");
+ logger.error("Operation failed", failure);
+ } finally {
+ System.setErr(original);
+ }
+ String logged = output.toString(UTF_8);
+ assertTrue(logged.contains("Hello world"));
+ assertTrue(logged.contains("IllegalStateException: failure-detail"));
+ }
+
+ @Test
+ void systemStreamBuffersUtf8ByLine() {
+ CapturingLogger logger = new CapturingLogger();
+ try (PrintStream stream = new SystemOutLogger(logger)) {
+ byte[] text = "héllo\nsecond\n".getBytes(UTF_8);
+ for (byte value : text) stream.write(value);
+ }
+ assertEquals(List.of("héllo", "second"), logger.messages);
+ }
+
+ private static final class CapturingLogger extends ApplicationLogger {
+ private final List messages = new ArrayList<>();
+ private CapturingLogger() { super("capture", LogLevel.ALL); }
+ @Override public void info(String message) { messages.add(message); }
+ }
+}
diff --git a/src/test/java/net/locusworks/logger/LogLevelTest.java b/src/test/java/net/locusworks/logger/LogLevelTest.java
new file mode 100644
index 0000000..73892c2
--- /dev/null
+++ b/src/test/java/net/locusworks/logger/LogLevelTest.java
@@ -0,0 +1,26 @@
+package net.locusworks.logger;
+
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+
+class LogLevelTest {
+ @Test
+ void convertsNamesAndNumbers() {
+ for (LogLevel level : LogLevel.values()) {
+ assertEquals(level, LogLevel.getEnum(level.toInt()));
+ assertEquals(level, LogLevel.getEnum(level.toName().toLowerCase()));
+ assertEquals(level.toName(), level.toString());
+ }
+ assertNull(LogLevel.getEnum(99));
+ assertNull(LogLevel.getEnum((Integer) null));
+ assertNull(LogLevel.getEnum("unknown"));
+ assertNull(LogLevel.getEnum((String) null));
+ }
+
+ @Test
+ void comparesSeverityAndHandlesNull() {
+ assertTrue(LogLevel.INFO.log(LogLevel.DEBUG));
+ assertFalse(LogLevel.DEBUG.log(LogLevel.INFO));
+ assertFalse(LogLevel.INFO.log(null));
+ }
+}
diff --git a/src/test/java/net/locusworks/logger/SystemLoggerTest.java b/src/test/java/net/locusworks/logger/SystemLoggerTest.java
new file mode 100644
index 0000000..689c7dc
--- /dev/null
+++ b/src/test/java/net/locusworks/logger/SystemLoggerTest.java
@@ -0,0 +1,35 @@
+package net.locusworks.logger;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.io.PrintStream;
+import org.junit.jupiter.api.Test;
+
+class SystemLoggerTest {
+ @Test
+ void routesCompleteLinesToTheirConfiguredLevels() {
+ ApplicationLogger logger = mock(ApplicationLogger.class);
+ try (PrintStream out = new SystemOutLogger(logger);
+ PrintStream err = new SystemErrLogger(logger);
+ PrintStream debug = new SystemDebugLogger(logger)) {
+ out.print("out\r\n");
+ err.print("err\n");
+ debug.print("debug");
+ debug.flush();
+ }
+ verify(logger).info("out");
+ verify(logger).error("err");
+ verify(logger).debug("debug");
+ }
+
+ @Test
+ void preservesUtf8SplitAcrossIndividualWrites() {
+ ApplicationLogger logger = mock(ApplicationLogger.class);
+ try (PrintStream out = new SystemOutLogger(logger)) {
+ for (byte value : "h\u00e9llo\n".getBytes(UTF_8)) out.write(value);
+ }
+ verify(logger).info("h\u00e9llo");
+ }
+}