diff --git a/core/src/main/java/uk/dansiviter/jule/AsyncConsoleHandler.java b/core/src/main/java/uk/dansiviter/jule/AsyncConsoleHandler.java index d9d6c6a..9c8ad77 100644 --- a/core/src/main/java/uk/dansiviter/jule/AsyncConsoleHandler.java +++ b/core/src/main/java/uk/dansiviter/jule/AsyncConsoleHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Daniel Siviter + * Copyright 2023 Daniel Siviter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,30 +16,13 @@ package uk.dansiviter.jule; /** - * Async implementation which simply delegates to {@link ConsoleHandlerExt}. + * Async implementation which simply delegates to {@link ConsoleHandler}. */ -public class AsyncConsoleHandler extends AsyncStreamHandler { +public class AsyncConsoleHandler extends AsyncStreamHandler { /** * Constructs an asynchronous {@code ConsoleHandlerExt} */ public AsyncConsoleHandler() { - super(new ConsoleHandlerExt()); - property("stdOut") - .map(Boolean::parseBoolean) - .ifPresent(this::setStdOut); - } - - /** - * @return {@code true} if using {@link System#out}. - */ - public boolean isStdOut() { - return this.delegate.isStdOut(); - } - - /** - * @param stdOut if {@code true} this uses {@link System#out}. - */ - public void setStdOut(boolean stdOut) { - this.delegate.setStdOut(stdOut); + super(new ConsoleHandler()); } } diff --git a/core/src/main/java/uk/dansiviter/jule/AsyncHandler.java b/core/src/main/java/uk/dansiviter/jule/AsyncHandler.java index a71bb07..ad0b616 100644 --- a/core/src/main/java/uk/dansiviter/jule/AsyncHandler.java +++ b/core/src/main/java/uk/dansiviter/jule/AsyncHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Daniel Siviter + * Copyright 2023 Daniel Siviter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package uk.dansiviter.jule; +import static java.nio.charset.Charset.defaultCharset; import static java.util.concurrent.ForkJoinPool.commonPool; import static java.util.logging.ErrorManager.GENERIC_FAILURE; import static java.util.logging.ErrorManager.OPEN_FAILURE; @@ -60,9 +61,10 @@ * (defaults to {@link java.util.concurrent.Flow#defaultBufferSize()}). * */ -public abstract class AsyncHandler extends AbstractHandler { - private final Subscriber subscriber = new LogSubscriber(); - private final SubmissionPublisher publisher; +public abstract class AsyncHandler extends AbstractHandler { + private static final LogRecord FLUSH = new LogRecord(Level.OFF, "flush"); + private final Subscriber subscriber = new LogSubscriber(); + private final SubmissionPublisher publisher; /** Closed status */ protected final AtomicBoolean closed = new AtomicBoolean(); @@ -101,15 +103,19 @@ public void publish(LogRecord r) { this.publisher.submit(transform(r)); } + @Override + public void flush() { + this.publisher.submit(FLUSH); + } + /** * Perform any pre-flight transformation of this record. This will be called on the log calling thread. * * @param r the record to transform. * @return the transformed record. */ - @SuppressWarnings("unchecked") - protected R transform(LogRecord r) { - return (R) r; + protected LogRecord transform(LogRecord r) { + return r; } /** @@ -117,7 +123,12 @@ protected R transform(LogRecord r) { * * @param r the log record to process. */ - protected abstract void doPublish(R r); + protected abstract void doPublish(LogRecord r); + + /** + * This will be called asynchronously. + */ + protected void doFlush() { } /** * @return {@code true} if closed. @@ -140,7 +151,7 @@ public void close() throws SecurityException { /** * */ - private class LogSubscriber implements Subscriber { + private class LogSubscriber implements Subscriber { private Subscription subscription; @Override @@ -150,13 +161,18 @@ public void onSubscribe(Subscription subscription) { } @Override - public void onNext(R item) { + public void onNext(LogRecord item) { try { + if (item == FLUSH) { + doFlush(); + } + doPublish(item); } catch (RuntimeException e) { getErrorManager().error(e.getMessage(), e, WRITE_FAILURE); + } finally { + this.subscription.request(1); } - this.subscription.request(1); } @Override diff --git a/core/src/main/java/uk/dansiviter/jule/AsyncStreamHandler.java b/core/src/main/java/uk/dansiviter/jule/AsyncStreamHandler.java index f382bad..07e71cb 100644 --- a/core/src/main/java/uk/dansiviter/jule/AsyncStreamHandler.java +++ b/core/src/main/java/uk/dansiviter/jule/AsyncStreamHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Daniel Siviter + * Copyright 2023 Daniel Siviter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * Async implementation of {@link StreamHandler} which simply delegates. */ -public abstract class AsyncStreamHandler extends AsyncHandler { +public abstract class AsyncStreamHandler extends AsyncHandler { /** Delegate {@code StreamHandler} */ protected final H delegate; @@ -57,12 +57,13 @@ protected void doPublish(LogRecord r) { } @Override - public void flush() { + protected void doFlush() { this.delegate.flush(); } @Override public void close() throws SecurityException { + super.close(); this.delegate.close(); } diff --git a/core/src/main/java/uk/dansiviter/jule/ConsoleHandler.java b/core/src/main/java/uk/dansiviter/jule/ConsoleHandler.java new file mode 100644 index 0000000..216edbc --- /dev/null +++ b/core/src/main/java/uk/dansiviter/jule/ConsoleHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Daniel Siviter + * + * 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. + */ +package uk.dansiviter.jule; + +import java.util.logging.LogRecord; +import java.util.logging.SimpleFormatter; +import java.util.logging.StreamHandler; + +/** + * A improvement on {@link java.util.logging.ConsoleHandler} where uses {@code STDOUT}. + */ +public class ConsoleHandler extends StreamHandler { + /** + * Default constructor that uses permits setting of output. + */ + public ConsoleHandler() { + super(System.out, new SimpleFormatter()); + } + + @Override + public synchronized void publish(LogRecord record) { + super.publish(record); + flush(); + } +} diff --git a/core/src/main/java/uk/dansiviter/jule/ConsoleHandlerExt.java b/core/src/main/java/uk/dansiviter/jule/ConsoleHandlerExt.java deleted file mode 100644 index 3810212..0000000 --- a/core/src/main/java/uk/dansiviter/jule/ConsoleHandlerExt.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2021 Daniel Siviter - * - * 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. - */ -package uk.dansiviter.jule; - -import static java.util.logging.LogManager.getLogManager; -import static uk.dansiviter.jule.JulUtil.property; - -import java.util.logging.ConsoleHandler; - -/** - * Variant of ConsoleHandler where I can actually set if it uses {@code STDOUT} or {@code STDERR}. - * Configuration: - * Using the following {@code LogManager} configuration properties, where {@code } refers to the - * fully-qualified class name of the handler: - *
    - *
  • {@code <handler-name>.stdOut} - * specifies if it should use {@link System#out} or not - * (defaults to {@code true}).
  • - *
- */ -public class ConsoleHandlerExt extends ConsoleHandler { - private boolean stdOut; - - /** - * Default constructor that uses permits setting of output. - */ - public ConsoleHandlerExt() { - property(getLogManager(), getClass(), "stdOut") - .map(Boolean::parseBoolean) - .ifPresentOrElse(this::setStdOut, () -> setStdOut(true)); - } - - /** - * @return {@code true} if using {@link System#out}. - */ - public boolean isStdOut() { - return this.stdOut; - } - - /** - * @param stdOut if {@code true} this uses {@link System#out}. - */ - public void setStdOut(boolean stdOut) { - setOutputStream((this.stdOut = stdOut) ? System.out : System.err); - } -} diff --git a/core/src/main/java/uk/dansiviter/jule/PlatformLoggerUtil.java b/core/src/main/java/uk/dansiviter/jule/PlatformLoggerUtil.java index 7aadbff..33c7e01 100644 --- a/core/src/main/java/uk/dansiviter/jule/PlatformLoggerUtil.java +++ b/core/src/main/java/uk/dansiviter/jule/PlatformLoggerUtil.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Daniel Siviter + * + * 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. + */ package uk.dansiviter.jule; import java.lang.System.Logger; @@ -11,7 +26,6 @@ public enum PlatformLoggerUtil { ; private static final Optional> PLATFORM_LOGGER; private static final Optional> BRIDGE; - private static final Optional> LEVEL; private static final Optional LOGP; private static final Optional LOGP_THROWN; @@ -20,25 +34,22 @@ public enum PlatformLoggerUtil { ; static { Class platformLogger; Class bridge; - Class level; Method logp; Method logpThrown; try { platformLogger = Class.forName("sun.util.logging.PlatformLogger"); bridge = Class.forName("sun.util.logging.PlatformLogger$Bridge"); - level = Class.forName("sun.util.logging.PlatformLogger$Level"); + var level = Class.forName("sun.util.logging.PlatformLogger$Level"); logp = bridge.getDeclaredMethod("logp", level, String.class, String.class, Supplier.class); logpThrown = bridge.getDeclaredMethod("logp", level, String.class, String.class, Throwable.class, Supplier.class); } catch (ReflectiveOperationException e) { platformLogger = null; bridge = null; - level = null; logp = null; logpThrown = null; } PLATFORM_LOGGER = Optional.of(platformLogger); BRIDGE = Optional.ofNullable(bridge); - LEVEL = Optional.ofNullable(level); LOGP = Optional.ofNullable(logp); LOGP_THROWN = Optional.ofNullable(logpThrown); } diff --git a/core/src/test/java/uk/dansiviter/jule/AsyncConsoleHandlerTest.java b/core/src/test/java/uk/dansiviter/jule/AsyncConsoleHandlerTest.java deleted file mode 100644 index 2205ae1..0000000 --- a/core/src/test/java/uk/dansiviter/jule/AsyncConsoleHandlerTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2022 Daniel Siviter - * - * 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. - */ -package uk.dansiviter.jule; - -import static java.lang.String.format; -import static java.util.logging.LogManager.getLogManager; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.LogManager; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * Tests for {@link AsyncConsoleHandler}. - */ -class AsyncConsoleHandlerTest { - private LogManager manager; - - @BeforeEach - void before() { - this.manager = getLogManager(); - } - - @Test - void init() throws IOException { - var handler = new AsyncConsoleHandler(); - assertThat(handler.isStdOut(), is(true)); - } - - @Test - void init_stdErr() throws IOException { - var config = format( - "%s.stdOut=%s\n", - AsyncConsoleHandler.class.getName(), - false); - try (InputStream is = new ByteArrayInputStream(config.getBytes())) { - this.manager.readConfiguration(is); - } - - var handler = new AsyncConsoleHandler(); - assertThat(handler.isStdOut(), is(false)); - } - - @AfterAll - static void afterAll() { - getLogManager().reset(); - } -} diff --git a/core/src/test/java/uk/dansiviter/jule/AsyncHandlerTest.java b/core/src/test/java/uk/dansiviter/jule/AsyncHandlerTest.java index 0b6e176..c2d7a9a 100644 --- a/core/src/test/java/uk/dansiviter/jule/AsyncHandlerTest.java +++ b/core/src/test/java/uk/dansiviter/jule/AsyncHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Daniel Siviter + * Copyright 2023 Daniel Siviter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,15 +56,13 @@ void doPublish() { log.info("hello"); log.fine("hello"); - handler.flush(); - new Thread(() -> log.info("world")).start(); await().atMost(1, SECONDS).untilAsserted(() -> { - assertThat(handler.records, hasSize(2)); - assertThat(handler.records.get(0).getMessage(), equalTo("hello")); - assertThat(handler.records.get(1).getMessage(), equalTo("world")); - }); + assertThat(handler.records, hasSize(2)); + assertThat(handler.records.get(0).getMessage(), equalTo("hello")); + assertThat(handler.records.get(1).getMessage(), equalTo("world")); + }); } @Test @@ -105,7 +103,7 @@ void after() { } } - private static class TestHandler extends AsyncHandler { + private static class TestHandler extends AsyncHandler { private final List records = new Vector<>(); @Override @@ -114,7 +112,7 @@ protected void doPublish(LogRecord record) { } } - private static class FailingHandler extends AsyncHandler { + private static class FailingHandler extends AsyncHandler { @Override protected void doPublish(LogRecord record) { throw new RuntimeException(); diff --git a/core/src/test/java/uk/dansiviter/jule/AsyncStreamHandlerTest.java b/core/src/test/java/uk/dansiviter/jule/AsyncStreamHandlerTest.java index 485698d..4393185 100644 --- a/core/src/test/java/uk/dansiviter/jule/AsyncStreamHandlerTest.java +++ b/core/src/test/java/uk/dansiviter/jule/AsyncStreamHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Daniel Siviter + * Copyright 2023 Daniel Siviter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.endsWith; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -62,7 +63,7 @@ void flush(@Mock StreamHandler delegate) { handler.flush(); - verify(delegate).flush(); + verify(delegate, timeout(500)).flush(); } @Test @@ -70,6 +71,7 @@ void close(@Mock StreamHandler delegate) { var handler = new AsyncStreamHandler<>(delegate) { }; handler.close(); + assertTrue(handler.isClosed()); verify(delegate).close(); } diff --git a/core/src/test/java/uk/dansiviter/jule/ConsoleHandlerExtTest.java b/core/src/test/java/uk/dansiviter/jule/ConsoleHandlerExtTest.java deleted file mode 100644 index 988cc63..0000000 --- a/core/src/test/java/uk/dansiviter/jule/ConsoleHandlerExtTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2022 Daniel Siviter - * - * 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. - */ -package uk.dansiviter.jule; - -import static java.lang.String.format; -import static java.util.logging.LogManager.getLogManager; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.LogManager; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** - * Tests for {@link ConsoleHandlerExt}. - */ -class ConsoleHandlerExtTest { - private LogManager manager; - - @BeforeEach - void before() { - this.manager = getLogManager(); - } - - @Test - void init() throws IOException { - var handler = new ConsoleHandlerExt(); - assertThat(handler.isStdOut(), is(true)); - } - - @Test - void init_stdErr() throws IOException { - var config = format( - "%s.stdOut=%s\n", - ConsoleHandlerExt.class.getName(), - false); - try (InputStream is = new ByteArrayInputStream(config.getBytes())) { - this.manager.readConfiguration(is); - } - - var handler = new ConsoleHandlerExt(); - assertThat(handler.isStdOut(), is(false)); - } - - @AfterAll - static void afterAll() { - getLogManager().reset(); - } -} diff --git a/processor/src/main/java/uk/dansiviter/jule/processor/LogProcessor.java b/processor/src/main/java/uk/dansiviter/jule/processor/LogProcessor.java index 6504223..43d4839 100644 --- a/processor/src/main/java/uk/dansiviter/jule/processor/LogProcessor.java +++ b/processor/src/main/java/uk/dansiviter/jule/processor/LogProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Daniel Siviter + * Copyright 2023 Daniel Siviter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,9 +25,11 @@ import static javax.tools.StandardLocation.CLASS_OUTPUT; import java.io.IOException; +import java.time.Instant; import java.util.ArrayList; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import java.util.logging.Logger; import java.util.stream.Stream; @@ -70,6 +72,19 @@ @SupportedSourceVersion(SourceVersion.RELEASE_11) public class LogProcessor extends AbstractProcessor { + private final Supplier nowSupplier; + + /** + * Creates a new processor. + */ + public LogProcessor() { + this(Instant::now); + } + + LogProcessor(Supplier nowSupplier) { + this.nowSupplier = nowSupplier; + } + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { annotations.forEach(a -> roundEnv.getElementsAnnotatedWith(a).forEach(e -> process((TypeElement) e))); @@ -145,8 +160,9 @@ private void createConcrete( .addModifiers(PUBLIC, FINAL) .addAnnotation(AnnotationSpec .builder(Generated.class) - .addMember("value", format("\"%s\"", getClass().getName())) - .addMember("comments", "\"https://jule.dansiviter.uk/\"") + .addMember("value", "$S", getClass().getName()) + .addMember("comments", "$S", "https://jule.dansiviter.uk") + .addMember("date", "$S", this.nowSupplier.get().toString()) .build()) .addSuperinterface(baseLogType) .addSuperinterface(typeMirror) diff --git a/processor/src/test/java/uk/dansiviter/jule/processor/LogProcessorTest.java b/processor/src/test/java/uk/dansiviter/jule/processor/LogProcessorTest.java index 8d68199..0413ba2 100644 --- a/processor/src/test/java/uk/dansiviter/jule/processor/LogProcessorTest.java +++ b/processor/src/test/java/uk/dansiviter/jule/processor/LogProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Daniel Siviter + * Copyright 2023 Daniel Siviter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; +import java.time.Instant; + import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -29,8 +31,9 @@ class LogProcessorTest { @Test void process() { + var instant = Instant.parse("2023-02-01T01:02:03.000004Z"); Compilation compilation = javac() - .withProcessors(new LogProcessor()) + .withProcessors(new LogProcessor(() -> instant)) .compile(JavaFileObjects.forResource("uk/dansiviter/jule/processor/Good.java")); assertThat(compilation).succeeded(); assertThat(compilation).hadNoteContaining("Generating class for: uk.dansiviter.jule.processor.Good"); diff --git a/processor/src/test/resources/uk/dansiviter/jule/processor/Bad.java b/processor/src/test/resources/uk/dansiviter/jule/processor/Bad.java index 313883b..bfa3aae 100644 --- a/processor/src/test/resources/uk/dansiviter/jule/processor/Bad.java +++ b/processor/src/test/resources/uk/dansiviter/jule/processor/Bad.java @@ -4,7 +4,7 @@ import uk.dansiviter.jule.annotations.Message; @Log -interface Foo { +interface Bad { @Message("") void empty(); diff --git a/processor/src/test/resources/uk/dansiviter/jule/processor/Good$impl.java b/processor/src/test/resources/uk/dansiviter/jule/processor/Good$impl.java index 8135c91..78a8f94 100644 --- a/processor/src/test/resources/uk/dansiviter/jule/processor/Good$impl.java +++ b/processor/src/test/resources/uk/dansiviter/jule/processor/Good$impl.java @@ -11,7 +11,8 @@ @Generated( value = "uk.dansiviter.jule.processor.LogProcessor", - comments = "https://jule.dansiviter.uk/" + comments = "https://jule.dansiviter.uk", + date = "2023-02-01T01:02:03.000004Z" ) public final class Good$impl implements BaseJulLog, Good { private static final AtomicBoolean ONCE__foo = new AtomicBoolean(); @@ -29,16 +30,16 @@ public final class Good$impl implements BaseJulLog, Good { } /** - * @returns the delegate logger. - */ + * @returns the delegate logger. + */ @Override public final Logger delegate() { return this.delegate; } /** - * @returns the annotation instance. - */ + * @returns the annotation instance. + */ @Override public final Log log() { return this.log;