diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..4cc1c2291a3c2067241d5b4a7507f8091eb703bc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [1.2.0](https://transfer.hft-stuttgart.de///compare/1.1.0...1.2.0) (2020-12-30) + + +### Features + +* **ci:** set git describe as build number ([d0c16d1](https://transfer.hft-stuttgart.de///commit/d0c16d15786a9971e9ca0b6a0814ac42af5f253e)) + + +### Bug Fixes + +* **app:** buildClasspath: build classpath differently for compile and runtime ([695f4e4](https://transfer.hft-stuttgart.de///commit/695f4e40829100a00a51c28f9a30e0745bf5ceb3)) +* **app:** buildClasspath: check if folder exists before walk-through ([7c08633](https://transfer.hft-stuttgart.de///commit/7c0863382a1514856d4c2fe936844a12a74e1f76)) +* add missing junit dependencies ([0c57620](https://transfer.hft-stuttgart.de///commit/0c576205318a827935975898964479d984234d03)) + +## [1.1.0](https://transfer.hft-stuttgart.de///compare/1.0.1...1.1.0) (2020-12-15) + + +### Features + +* store compilation errors separately and with line,col and pos number ([29b68ed](https://transfer.hft-stuttgart.de///commit/29b68edb3fa6afdfcf237c0d5f8a277129550746)) + +### [1.0.1](https://transfer.hft-stuttgart.de///compare/1.0.0...1.0.1) (2020-12-14) + + +### Bug Fixes + +* **ci:** registry name had a typo ([a3e5b2e](https://transfer.hft-stuttgart.de///commit/a3e5b2e21bd40389598f212b25561c0fdc50fb94)) + +## 1.0.0 (2020-12-14) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7733b95caab10e8d3250197d827840b339aa541f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM amazoncorretto:17.0.3-alpine as corretto-jdk + +env BASEDIR /data +env TESTDIR $BASEDIR/test +env SOURCEDIR $BASEDIR/src +env RESULTDIR $BASEDIR/result +env LIBSDIR $BASEDIR/libs + +run mkdir -p $TESTDIR \ + && mkdir $SOURCEDIR \ + && mkdir $RESULTDIR \ + && mkdir -p /$LIBSDIR/additional + +add target/dta-jdk17-junit5-runner-jar-with-dependencies.jar /$BASEDIR/app.jar +add https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/5.10.0/junit-jupiter-api-5.10.0.jar /$LIBSDIR/ +add https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-engine/5.10.0/junit-jupiter-engine-5.10.0.jar /$LIBSDIR/ +add https://repo1.maven.org/maven2/org/junit/platform/junit-platform-engine/1.10.0/junit-platform-engine-1.10.0.jar /$LIBSDIR/ +add https://repo1.maven.org/maven2/org/junit/platform/junit-platform-commons/1.10.0/junit-platform-commons-1.10.0.jar /$LIBSDIR/ +add https://repo1.maven.org/maven2/org/junit/platform/junit-platform-launcher/1.10.0/junit-platform-launcher-1.10.0.jar /$LIBSDIR/ +add https://repo1.maven.org/maven2/org/apiguardian/apiguardian-api/1.1.1/apiguardian-api-1.1.1.jar /$LIBSDIR/ +add https://repo1.maven.org/maven2/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar /$LIBSDIR/ + +workdir $BASEDIR + +entrypoint java -Djava.security.egd=file:/dev/./urandom -jar /$BASEDIR/app.jar "$SOURCEDIR/src:$TESTDIR/test" "$LIBSDIR/*:$TESTDIR/libs/*" $RESULTDIR diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000000000000000000000000000000000..9f7c36de9d99c12093306d2a2605e50cdb68744f --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,74 @@ + +def version = "" + +pipeline { + + environment { + registry = "hftstuttgart/dtt-openjdk11-junit5-testrunner" + registryCredential = 'Dockerhub' + dockerImage = '' + } + + agent any + + tools { + jdk 'Java11' + maven 'Maven_Home' + } + + stages { + stage('prepare') { + steps { + checkout ([ + $class: 'GitSCM', + branches: scm.branches, + extensions: scm.extensions + [[$class: 'CloneOption', noTags: false, reference: '', shallow: false]], + userRemoteConfigs: scm.userRemoteConfigs + ]) + script { + version = sh(script: 'git describe --tags --always', returnStdout: true).trim() + echo sh(script: 'env|sort', returnStdout: true) + } + + } + } + + stage('compile') { + steps { + sh "BUILD_NUMBER=${version} mvn clean package" + } + } + + stage('build Docker image') { + steps { + script { + dockerImage = docker.build registry + } + } + } + + stage('push development image') { + steps { + script { + docker.withRegistry( '', registryCredential ) { + dockerImage.push("${env.GIT_BRANCH}") + } + } + } + } + + stage('release') { + when { + expression { version ==~ /[0-9]+.[0-9]+.[0-9]+/ } + } + steps { + script { + docker.withRegistry( '', registryCredential ) { + dockerImage.push("latest") + dockerImage.push("${version}") + } + } + } + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..bbfb737a94a21bb837648af139bcf6842a2f86d6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>de.hftstuttgart</groupId> + <artifactId>dta-jdk17-junit5-runner</artifactId> + <version>${env.BUILD_NUMBER}</version> + <packaging>jar</packaging> + + <properties> + <buildNumber>${env.BUILD_NUMBER}</buildNumber> + <java.version>17</java.version> + <maven.compiler.source>17</maven.compiler.source> + <maven.compiler.target>17</maven.compiler.target> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <profiles> + <profile> + <id>ci</id> + <activation> + <property><name>env.BUILD_NUMBER</name></property> + </activation> + <properties> + <buildNumber>${env.BUILD_NUMBER}</buildNumber> + </properties> + </profile> + </profiles> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.10.0</version> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.10.0</version> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-launcher</artifactId> + <version>1.10.0</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.15.3</version> + </dependency> + <dependency> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <version>3.6.0</version> + </dependency> + </dependencies> + + <build> + <finalName>${project.artifactId}</finalName> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + <configuration> + <archive> + <manifest> + <mainClass>de.hftstuttgart.dta.Testrunner</mainClass> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/src/main/java/de/hftstuttgart/dta/Testrunner.java b/src/main/java/de/hftstuttgart/dta/Testrunner.java new file mode 100644 index 0000000000000000000000000000000000000000..d7503d785a5b7d627c2b10ec6e84d4c8265570cd --- /dev/null +++ b/src/main/java/de/hftstuttgart/dta/Testrunner.java @@ -0,0 +1,374 @@ +package de.hftstuttgart.dta; + +import de.hftstuttgart.dta.model.Result; +import de.hftstuttgart.dta.model.ResultSummary; +import de.hftstuttgart.dta.util.MySummaryGeneratingListener; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +import javax.tools.*; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import java.util.*; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Testrunner +{ + static + { + InputStream stream = Testrunner.class.getClassLoader() + .getResourceAsStream("logging.properties"); + try + { + LogManager.getLogManager().readConfiguration(stream); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + private static final Logger LOG = Logger.getLogger(Testrunner.class.getName()); + + public static String[] sourceFolders; // folders with Java Files to compile + public static String[] libraryFolders; // folders with libraries in jar-file format + public static String classFolder; // folder to put compiled class files into + public static String[] classPathItems; // items for the classpath + public static String resultFolder; // folder the result file gets serialized to + + public static void main(String[] args) throws Exception + { + LOG.info("OpenJDK11 JUnit5/Jupiter Testrunner started"); + + LOG.info("initializing fields..."); + sourceFolders = args[0].split(":"); + libraryFolders = args[1].split(":"); + resultFolder = args[2]; + classFolder = Files.createTempDirectory("testrunner").toAbsolutePath().toString(); + + // finding all source files + Set<File> sourceFiles = new HashSet<>(); + for (String folder : sourceFolders) + { + sourceFiles.addAll(getAllJavaFilesInFolder(Paths.get(folder).toFile())); + } + + // call compilation and generate Results for failed compiles + Set<Result> compilationErrors = generateCompileResults(compile(sourceFiles, new File(classFolder))); + + // run unit tests found in the compiled class files + ResultSummary resultSummary = runTests(); + + // add compilation errors to summary + resultSummary.results.addAll(compilationErrors); + + // serialize result + writeResult(resultSummary); + } + + public static String[] buildClassPathItems(boolean runtime) + { + Set<String> classPathItemsBuild = new HashSet<>(Arrays.asList(libraryFolders)); + classPathItemsBuild.add(classFolder); + if (runtime) + { + classPathItemsBuild.addAll(Arrays.stream(sourceFolders).collect(Collectors.toSet())); + } + return classPathItemsBuild.toArray(new String[0]); + } + + public static Set<File> buildClassPath(String... paths) + { + Set<File> files = new HashSet<>(); + + for (String path : paths) + { + if (path.endsWith("*")) + { + path = path.substring(0, path.length() - 1); + File pathFile = new File(path); + if (!pathFile.exists() || !pathFile.isDirectory()) + { + continue; + } + + for (File file : Objects.requireNonNull(pathFile.listFiles())) + { + if (file.isFile() && file.getName().endsWith(".jar")) + { + files.add(file); + } else + { + files.addAll(buildClassPath(Paths.get(file.getPath(), "*").toString())); + } + } + } else + { + File file = new File(path); + if (file.exists()) + { + files.add(file); + } + } + } + return files; + } + + public static List<Diagnostic> compile(Set<File> files, File outputDir) + { + classPathItems = buildClassPathItems(false); + LOG.info("compilation started"); + List<Diagnostic> compilationErrors = new LinkedList<>(); + + // Create the compiler and add a diagnostic listener to get the compilation errors + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + DiagnosticListener listener = compilationErrors::add; + StandardJavaFileManager fileManager = compiler.getStandardFileManager(listener, null, StandardCharsets.UTF_8); + Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjects(files.toArray(new File[0])); + + if (!outputDir.exists()) + { + outputDir.mkdir(); + } + + // Set the compiler option for a specific output path + List<String> options = new ArrayList<>(); + options.add("-d"); // output dir + options.add(outputDir.getAbsolutePath()); + options.add("-cp"); // custom classpath + String os=System.getProperty("os.name"); + final String osSpecificCpDelim=(os.indexOf("Windows")>-1?";":":"); + String cp = buildClassPath(classPathItems).stream() + .map(f -> f.getPath()) + .reduce((s1, s2) -> s1 + osSpecificCpDelim + s2).orElse(""); + LOG.info("classpath for compilation: " + cp); + options.add(cp); + + // compile it + JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, listener, options, null, fileObjects); + boolean compileResult = task.call(); + + // If the compilation failed, remove the failed file from the pathsToCompile list and try to compile again without this file + if (!compileResult) + { + File currentFile = new File(((JavaFileObject) compilationErrors.get(compilationErrors.size() - 1).getSource()).toUri().getPath()); + LOG.log(Level.WARNING,"compilation of file '" + currentFile.getAbsolutePath() + "' failed"); + files.removeIf(file -> file.getAbsolutePath().equalsIgnoreCase(currentFile.getAbsolutePath())); + if (files.size() > 0) + { + LOG.info(String.format("retry compilation without %s", currentFile.getName())); + compile(files, outputDir); + } + } else { + LOG.info("compilation finished"); + } + + return compilationErrors; + } + + public static ClassLoader createCustomClassLoader() throws MalformedURLException + { + LOG.info("creating custom class loader for testing"); + URL[] urls = buildClassPath(classPathItems).stream().map(f -> { + try { + return f.toURI().toURL(); + } catch (MalformedURLException e) { + LOG.log(Level.SEVERE, e.getMessage(), e); + throw new RuntimeException(e.getMessage(), e); + } + }).toArray(URL[]::new); + + LOG.info(String.format("JUnit ClassLoader context classpath: %s", Arrays.deepToString(urls))); + ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader(); + return URLClassLoader.newInstance(urls, parentClassLoader); + } + + public static Set<Result> generateCompileResults(List<Diagnostic> compilationErrors) + { + return compilationErrors.stream().map(e -> + { + Result result = new Result(); +// Pattern pattern = Pattern.compile(String.format("^.*%s(.*\\.java).*$", File.separator)); +// Matcher matcher = pattern.matcher(String.valueOf(e.getSource())); +// +// result.name = (matcher.matches() && matcher.group(1) != null) ? matcher.group(1) : String.valueOf(e.getSource()); + String sourcePath=String.valueOf(e.getSource()); + result.name=sourcePath.substring(sourcePath.lastIndexOf(File.separator)+1, sourcePath.length()-1); + result.state = Result.State.COMPILATIONERROR.ordinal(); + result.failureReason = e.getMessage(Locale.ENGLISH); + result.failureType = "Compilation Failed"; + result.stacktrace = e.toString(); + + result.lineNumber = (int) e.getLineNumber(); + result.columnNumber = (int) e.getColumnNumber(); + result.position = (int) e.getPosition(); + + return result; + }) + .collect(Collectors.toCollection(HashSet::new)); + } + + public static ResultSummary generateResultSummary(TestExecutionSummary summary, Set<TestIdentifier> successes) + { + LOG.info("JUnit results:"); + LOG.info(String.format( + "Number of Tests: %d, Number of fails: %d, Successful tests: %s, Failed tests: %s", + summary.getTestsFoundCount(), + summary.getTestsFailedCount(), + successes.stream() + .map(s -> s.getDisplayName()) + .reduce((s1, s2) -> s1 + ":" + s2).orElse("-"), + summary.getFailures().stream() + .map(f -> f.getTestIdentifier().getDisplayName()) + .reduce((s1, s2) -> s1 + ":" + s2).orElse("-") + )); + + ResultSummary resultSummary = new ResultSummary(); + resultSummary.results.addAll(successes.stream().map(s -> + { + Result result = new Result(); + result.name = s.getDisplayName(); + result.state = Result.State.SUCCESS.ordinal(); + + return result; + }) + .collect(Collectors.toCollection(HashSet::new))); + + resultSummary.results.addAll(summary.getFailures().stream().map(f -> + { + Result result = new Result(); + result.name = f.getTestIdentifier().getDisplayName(); + result.state = Result.State.FAILURE.ordinal(); + + result.failureReason = f.getException().getMessage(); + result.failureType = f.getException().getClass().getName(); + result.stacktrace = Arrays.stream(f.getException().getStackTrace()) + .map(s -> s.toString()) + .reduce((s1, s2) -> s1 + "\n" + s2) + .orElse(null); + + return result; + }) + .collect(Collectors.toCollection(HashSet::new))); + + return resultSummary; + } + + public static List<File> getAllJavaFilesInFolder(File path) + { + // check if provided path is a directory, otherwise throw a IllegalArgumentException + if (!path.isDirectory()) + { + String error = path.getAbsolutePath() + " is not a path"; + LOG.severe(error); + throw new IllegalArgumentException(error); + } + + List<File> files = new LinkedList<>(); + + // recursively check for java files + Stream.of(Objects.requireNonNull(path.listFiles())) + .forEach(file -> + { + // if directory, make recursion + if (file.isDirectory()) + { + files.addAll(getAllJavaFilesInFolder(file)); + } + // if java file add to list + else if (file.getAbsolutePath().endsWith(".java")) + { + files.add(file); + } + }); + + return files; + } + + public static ResultSummary runTests() throws MalformedURLException + { + classPathItems = buildClassPathItems(true); + LOG.info("saving original class loader"); + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + // get custom one + ClassLoader customClassLoader = createCustomClassLoader(); + + TestExecutionSummary summary; + Set<TestIdentifier> successes; + + try + { + LOG.info("changing classloader"); + Thread.currentThread().setContextClassLoader(customClassLoader); + LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + .selectors(DiscoverySelectors.selectClasspathRoots(Collections.singleton(Paths.get(classFolder).toAbsolutePath()))) + .build(); + + MySummaryGeneratingListener listener = new MySummaryGeneratingListener(); + Launcher launcher = LauncherFactory.create(); + launcher.registerTestExecutionListeners(listener); + + LOG.info("discovering UnitTests..."); + TestPlan plan = launcher.discover(request); + + for (TestIdentifier root : plan.getRoots()) + { + for (TestIdentifier test : plan.getChildren(root)) + { + LOG.info(String.format("Testclass identified: %s", test.getDisplayName())); + } + } + + LOG.info("launching tests"); + launcher.execute(plan); + + LOG.info("catching test results"); + summary = listener.getSummary(); + successes = listener.getSuccessfulTestidentifiers(); + } finally + { + LOG.info("restore original classloader"); + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + + LOG.info("generate result summary from junit"); + return generateResultSummary(summary, successes); + } + + public static void writeResult(ResultSummary resultSummary) throws IOException + { + Path fileName = Paths.get(resultFolder, "result.json"); + LOG.info(String.format("serializing result as json into %s", fileName.toAbsolutePath().toString())); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper + .writerWithDefaultPrettyPrinter() + .writeValue(fileName.toFile(), resultSummary); + } +} diff --git a/src/main/java/de/hftstuttgart/dta/model/Result.java b/src/main/java/de/hftstuttgart/dta/model/Result.java new file mode 100644 index 0000000000000000000000000000000000000000..8432c6653cf33bc3e3e82f7c4b9d63912b92258d --- /dev/null +++ b/src/main/java/de/hftstuttgart/dta/model/Result.java @@ -0,0 +1,24 @@ +package de.hftstuttgart.dta.model; + +public class Result +{ + public String name; + public int state; + + public String failureType; + public String failureReason; + public String stacktrace; + + // only for compilation Errors + public int columnNumber; + public int lineNumber; + public int position; + + public static enum State + { + UNKNOWN, + SUCCESS, + FAILURE, + COMPILATIONERROR, + } +} diff --git a/src/main/java/de/hftstuttgart/dta/model/ResultSummary.java b/src/main/java/de/hftstuttgart/dta/model/ResultSummary.java new file mode 100644 index 0000000000000000000000000000000000000000..295af95c875705d65b0f6160c6a47b6a1698fad5 --- /dev/null +++ b/src/main/java/de/hftstuttgart/dta/model/ResultSummary.java @@ -0,0 +1,11 @@ +package de.hftstuttgart.dta.model; + +import java.util.HashSet; +import java.util.Set; + +public class ResultSummary +{ + public long timestamp = System.currentTimeMillis() / 1000; + public String globalStacktrace = null; + public Set<Result> results = new HashSet<>(); +} diff --git a/src/main/java/de/hftstuttgart/dta/util/MySummaryGeneratingListener.java b/src/main/java/de/hftstuttgart/dta/util/MySummaryGeneratingListener.java new file mode 100644 index 0000000000000000000000000000000000000000..55788ab2455cfe72c21037d54a628c95cf173a32 --- /dev/null +++ b/src/main/java/de/hftstuttgart/dta/util/MySummaryGeneratingListener.java @@ -0,0 +1,31 @@ +package de.hftstuttgart.dta.util; + +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; + +import java.util.HashSet; +import java.util.Set; + +public class MySummaryGeneratingListener extends org.junit.platform.launcher.listeners.SummaryGeneratingListener +{ + protected Set<TestIdentifier> successful = new HashSet<>(); + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) + { + super.executionFinished(testIdentifier, testExecutionResult); + + if (testExecutionResult.getStatus().equals(TestExecutionResult.Status.SUCCESSFUL) + && testIdentifier.getType().equals(TestDescriptor.Type.TEST)) + { + successful.add(testIdentifier); + } + } + + public Set<TestIdentifier> getSuccessfulTestidentifiers() + { + return successful; + } +} diff --git a/src/main/java/de/hftstuttgart/dta/util/logging/CustomFormatter.java b/src/main/java/de/hftstuttgart/dta/util/logging/CustomFormatter.java new file mode 100644 index 0000000000000000000000000000000000000000..488b0be6c94c31dd8ddfba89f378f5c47d35e84e --- /dev/null +++ b/src/main/java/de/hftstuttgart/dta/util/logging/CustomFormatter.java @@ -0,0 +1,20 @@ +package de.hftstuttgart.dta.util.logging; + +import java.util.Date; +import java.util.logging.LogRecord; +import java.util.logging.SimpleFormatter; + +public class CustomFormatter extends SimpleFormatter +{ + final String format = "[%1$tFT%1$tT%1$tz] [%2$-7s] [%3$s.%4$s] %5$s%n"; + @Override + public synchronized String format(LogRecord lr) { + return String.format(format, + new Date(lr.getMillis()), + lr.getLevel(), + lr.getSourceClassName(), + lr.getSourceMethodName(), + lr.getMessage() + ); + } +} diff --git a/src/main/resources/logging.properties b/src/main/resources/logging.properties new file mode 100644 index 0000000000000000000000000000000000000000..ba6bc52110e639f0e9f46c6eb08e2e91a7e7eac2 --- /dev/null +++ b/src/main/resources/logging.properties @@ -0,0 +1,4 @@ +handlers=java.util.logging.ConsoleHandler +.level = INFO +java.util.logging.ConsoleHandler.level=INFO +java.util.logging.ConsoleHandler.formatter=de.hftstuttgart.dtt.util.logging.CustomFormatter