diff --git a/bom/pom.xml b/bom/pom.xml index 6933f5c9241..a4a1d58b248 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -841,6 +841,27 @@ helidon-microprofile-tests-junit5 ${helidon.version} + + + io.helidon.logging + helidon-logging-common + ${helidon.version} + + + io.helidon.logging + helidon-logging-jul + ${helidon.version} + + + io.helidon.logging + helidon-logging-slf4j + ${helidon.version} + + + io.helidon.logging + helidon-logging-log4j + ${helidon.version} + diff --git a/common/common/src/main/java/io/helidon/common/HelidonConsoleHandler.java b/common/common/src/main/java/io/helidon/common/HelidonConsoleHandler.java index 29f353fbedc..fb1de365202 100644 --- a/common/common/src/main/java/io/helidon/common/HelidonConsoleHandler.java +++ b/common/common/src/main/java/io/helidon/common/HelidonConsoleHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,10 @@ /** * A {@link StreamHandler} that writes to {@link System#out standard out} and uses a {@link ThreadFormatter} for formatting. * Sets the level to {@link Level#ALL} so that level filtering is performed solely by the loggers. + * + * @deprecated use io.helidon.logging.jul.HelidonConsoleHandler from helidon-logging-jul module instead */ +@Deprecated(since = "2.1.1") public class HelidonConsoleHandler extends StreamHandler { /** @@ -40,6 +43,9 @@ public HelidonConsoleHandler() { setOutputStream(System.out); setLevel(Level.ALL); // Handlers should not filter, loggers should setFormatter(new ThreadFormatter()); + System.out.println("You are using deprecated logging handler -> io.helidon.common.HelidonConsoleHandler " + + "Please use helidon-logging-jul module and change your handler to " + + "io.helidon.logging.jul.HelidonConsoleHandler"); } @Override diff --git a/common/context/pom.xml b/common/context/pom.xml index 96dc33fae59..200180d75a6 100644 --- a/common/context/pom.xml +++ b/common/context/pom.xml @@ -42,5 +42,9 @@ hamcrest-all test + + io.helidon.common + helidon-common-service-loader + diff --git a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java index b7173c6ae95..2e1660ce370 100644 --- a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java +++ b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,11 @@ package io.helidon.common.context; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.ServiceLoader; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -26,7 +29,15 @@ import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; +import io.helidon.common.context.spi.DataPropagationProvider; +import io.helidon.common.serviceloader.HelidonServiceLoader; + class ContextAwareExecutorImpl implements ContextAwareExecutorService { + + @SuppressWarnings("rawtypes") + private static final List PROVIDERS = HelidonServiceLoader + .builder(ServiceLoader.load(DataPropagationProvider.class)).build().asList(); + private final ExecutorService delegate; ContextAwareExecutorImpl(ExecutorService toWrap) { @@ -112,21 +123,39 @@ protected Collection> wrap(Collection Callable wrap(Callable task) { + @SuppressWarnings(value = "unchecked") + protected Callable wrap(Callable task) { Optional context = Contexts.context(); - if (context.isPresent()) { - return () -> Contexts.runInContext(context.get(), task); + Map, Object> properties = new HashMap<>(); + PROVIDERS.forEach(provider -> properties.put(provider.getClass(), provider.data())); + return () -> { + try { + PROVIDERS.forEach(provider -> provider.propagateData(properties.get(provider.getClass()))); + return Contexts.runInContext(context.get(), task); + } finally { + PROVIDERS.forEach(DataPropagationProvider::clearData); + } + }; } else { return task; } } + @SuppressWarnings(value = "unchecked") protected Runnable wrap(Runnable command) { Optional context = Contexts.context(); - if (context.isPresent()) { - return () -> Contexts.runInContext(context.get(), command); + Map, Object> properties = new HashMap<>(); + PROVIDERS.forEach(provider -> properties.put(provider.getClass(), provider.data())); + return () -> { + try { + PROVIDERS.forEach(provider -> provider.propagateData(properties.get(provider.getClass()))); + Contexts.runInContext(context.get(), command); + } finally { + PROVIDERS.forEach(DataPropagationProvider::clearData); + } + }; } else { return command; } diff --git a/common/context/src/main/java/io/helidon/common/context/spi/DataPropagationProvider.java b/common/context/src/main/java/io/helidon/common/context/spi/DataPropagationProvider.java new file mode 100644 index 00000000000..e1115c24be0 --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/spi/DataPropagationProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.common.context.spi; + +/** + * This is SPI provider which helps user to propagate values from one thread to another. + * + * Every provider has its method {@link #data()} invoked before thread switch, to obtain + * value for propagation. After the thread is switched, the new thread executes + * {@link #propagateData(Object)} to propagate data. + * + * @param an actual type of the data which will be propagated + */ +public interface DataPropagationProvider { + + /** + * Return data that should be propagated. + * + * @return data for propagation + */ + T data(); + + /** + * Propagates the data to be used by the new thread. + * + * @param data data for propagation + */ + void propagateData(T data); + + /** + * Clears the propagated date from the new thread when it finishes. + */ + void clearData(); + +} diff --git a/common/context/src/main/java/io/helidon/common/context/spi/package-info.java b/common/context/src/main/java/io/helidon/common/context/spi/package-info.java new file mode 100644 index 00000000000..28db72f8d14 --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/spi/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Provider for data propagation between threads in executor service. + */ +package io.helidon.common.context.spi; diff --git a/common/context/src/main/java/module-info.java b/common/context/src/main/java/module-info.java index 04ccaf0726b..449da6de941 100644 --- a/common/context/src/main/java/module-info.java +++ b/common/context/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,10 @@ module io.helidon.common.context { requires java.logging; requires io.helidon.common; + requires io.helidon.common.serviceloader; exports io.helidon.common.context; + exports io.helidon.common.context.spi; + + uses io.helidon.common.context.spi.DataPropagationProvider; } diff --git a/dependencies/pom.xml b/dependencies/pom.xml index f9873592eb6..3a4efcddaae 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -79,6 +79,7 @@ 1.1.6 5.6.2 4.13.1 + 2.13.3 2.6.2 2.10 1.4 @@ -742,6 +743,11 @@ slf4j-simple ${version.lib.slf4j} + + org.apache.logging.log4j + log4j-api + ${version.lib.log4j} + io.smallrye diff --git a/logging/common/pom.xml b/logging/common/pom.xml new file mode 100644 index 00000000000..2472ee1c173 --- /dev/null +++ b/logging/common/pom.xml @@ -0,0 +1,37 @@ + + + + 4.0.0 + + helidon-logging-project + io.helidon.logging + 2.1.1-SNAPSHOT + + + helidon-logging-common + Helidon Logging Common + + + + io.helidon.common + helidon-common-service-loader + + + + \ No newline at end of file diff --git a/logging/common/src/main/java/io/helidon/logging/common/HelidonMdc.java b/logging/common/src/main/java/io/helidon/logging/common/HelidonMdc.java new file mode 100644 index 00000000000..d0936e9609d --- /dev/null +++ b/logging/common/src/main/java/io/helidon/logging/common/HelidonMdc.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.common; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.ServiceLoader; + +import io.helidon.common.serviceloader.HelidonServiceLoader; +import io.helidon.logging.common.spi.MdcProvider; + +/** + * Helidon MDC delegates values across all of the supported logging frameworks on the classpath. + */ +public class HelidonMdc { + + private static final List MDC_PROVIDERS = HelidonServiceLoader + .builder(ServiceLoader.load(MdcProvider.class)).build().asList(); + + private HelidonMdc() { + throw new UnsupportedOperationException("This class cannot be instantiated"); + } + + /** + * Propagate value to all of the {@link MdcProvider} registered via SPI. + * + * @param key entry key + * @param value entry value + */ + public static void set(String key, String value) { + MDC_PROVIDERS.forEach(provider -> provider.put(key, value)); + } + + /** + * Remove value with the specific key from all of the instances of {@link MdcProvider}. + * + * @param key key + */ + public static void remove(String key) { + MDC_PROVIDERS.forEach(provider -> provider.remove(key)); + } + + /** + * Remove all of the entries bound to the current thread from the instances of {@link MdcProvider}. + */ + public static void clear() { + MDC_PROVIDERS.forEach(MdcProvider::clear); + } + + /** + * Return the first value found to the specific key. + * + * @param key key + * @return found value bound to key + */ + public static Optional get(String key) { + return MDC_PROVIDERS.stream() + .map(provider -> provider.get(key)) + .filter(Objects::nonNull) + .findFirst(); + } + +} diff --git a/logging/common/src/main/java/io/helidon/logging/common/package-info.java b/logging/common/src/main/java/io/helidon/logging/common/package-info.java new file mode 100644 index 00000000000..294c314c112 --- /dev/null +++ b/logging/common/src/main/java/io/helidon/logging/common/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Helidon MDC support for delegation of the MDC values to all of the logging frameworks. + */ +package io.helidon.logging.common; diff --git a/logging/common/src/main/java/io/helidon/logging/common/spi/MdcProvider.java b/logging/common/src/main/java/io/helidon/logging/common/spi/MdcProvider.java new file mode 100644 index 00000000000..be5dad0823b --- /dev/null +++ b/logging/common/src/main/java/io/helidon/logging/common/spi/MdcProvider.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.common.spi; + +import io.helidon.logging.common.HelidonMdc; + +/** + * Provider which is used to propagate values passed from {@link HelidonMdc} to the + * corresponding logging framework MDC storage. + */ +public interface MdcProvider { + + /** + * Set value to the specific logging framework MDC storage. + * @param key entry key + * @param value entry value + */ + void put(String key, String value); + + /** + * Remove value bound to the key from the specific logging framework MDC storage. + * + * @param key entry to remove + */ + void remove(String key); + + /** + * Clear all of the MDC values from the specific logging framework MDC storage. + */ + void clear(); + + /** + * Return value bound to the specific key. + * + * @param key entry key + * @return value bound to the key + */ + String get(String key); + +} diff --git a/logging/common/src/main/java/io/helidon/logging/common/spi/package-info.java b/logging/common/src/main/java/io/helidon/logging/common/spi/package-info.java new file mode 100644 index 00000000000..f1f9935c7aa --- /dev/null +++ b/logging/common/src/main/java/io/helidon/logging/common/spi/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * This package provides interface for an actual MDC support implementation. + */ +package io.helidon.logging.common.spi; diff --git a/logging/common/src/main/java/module-info.java b/logging/common/src/main/java/module-info.java new file mode 100644 index 00000000000..9a3a1fc1d35 --- /dev/null +++ b/logging/common/src/main/java/module-info.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Helidon logging. + */ +module io.helidon.logging.common { + requires io.helidon.common.serviceloader; + + exports io.helidon.logging.common; + exports io.helidon.logging.common.spi; + + uses io.helidon.logging.common.spi.MdcProvider; + +} \ No newline at end of file diff --git a/logging/jul/pom.xml b/logging/jul/pom.xml new file mode 100644 index 00000000000..8212c5aaf64 --- /dev/null +++ b/logging/jul/pom.xml @@ -0,0 +1,56 @@ + + + + + helidon-logging-project + io.helidon.logging + 2.1.1-SNAPSHOT + + 4.0.0 + + helidon-logging-jul + Helidon Java Util Logging Integration + + + + io.helidon.common + helidon-common-context + + + io.helidon.logging + helidon-logging-common + + + io.helidon.common + helidon-common + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + + + \ No newline at end of file diff --git a/logging/jul/src/main/java/io/helidon/logging/jul/HelidonConsoleHandler.java b/logging/jul/src/main/java/io/helidon/logging/jul/HelidonConsoleHandler.java new file mode 100644 index 00000000000..4ca8d088ac3 --- /dev/null +++ b/logging/jul/src/main/java/io/helidon/logging/jul/HelidonConsoleHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.jul; + +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.StreamHandler; + +/** + * A {@link StreamHandler} that writes to {@link System#out standard out} and uses a {@link HelidonFormatter} for formatting. + * Sets the level to {@link Level#ALL} so that level filtering is performed solely by the loggers. + */ +public class HelidonConsoleHandler extends StreamHandler { + + /** + * Creates a new {@link HelidonConsoleHandler} configured with: + *
    + *
  • the output stream set to {@link System#out}
  • + *
  • the formatter set to a {@link HelidonFormatter}
  • + *
  • the level set to {@link Level#ALL}
  • + *
. + */ + public HelidonConsoleHandler() { + setOutputStream(System.out); + setLevel(Level.ALL); // Handlers should not filter, loggers should + setFormatter(new HelidonFormatter()); + } + + @Override + public void publish(LogRecord record) { + super.publish(record); + flush(); + } + + @Override + public void close() { + flush(); + } + +} diff --git a/logging/jul/src/main/java/io/helidon/logging/jul/HelidonFormatter.java b/logging/jul/src/main/java/io/helidon/logging/jul/HelidonFormatter.java new file mode 100644 index 00000000000..fa9b8ea68d7 --- /dev/null +++ b/logging/jul/src/main/java/io/helidon/logging/jul/HelidonFormatter.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.jul; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.SimpleFormatter; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.helidon.logging.common.HelidonMdc; + +/** + * A {@link SimpleFormatter} that replaces all occurrences of MDC tags like {@code %X{value}} with specific values. + * It also supports replacement of {@code "!thread!"} with the current thread. + */ +public class HelidonFormatter extends SimpleFormatter { + private static final String THREAD = "thread"; + private static final String THREAD_TOKEN = "!" + THREAD + "!"; + private static final Pattern THREAD_PATTERN = Pattern.compile(THREAD_TOKEN); + private static final Pattern X_VALUE = Pattern.compile("(\\s?%X\\{)(\\S*?)(})"); + private static final Map PATTERN_CACHE = new HashMap<>(); + private static final String JUL_FORMAT_PROP_KEY = "java.util.logging.SimpleFormatter.format"; + private final String format = LogManager.getLogManager().getProperty(JUL_FORMAT_PROP_KEY); + private final Set parsedProps = new HashSet<>(); + private final boolean thread; + + HelidonFormatter() { + thread = format.contains(THREAD_TOKEN) || format.contains("%X{" + THREAD + "}"); + Matcher matcher = X_VALUE.matcher(format); + while (matcher.find()) { + parsedProps.add(matcher.group(2)); + } + } + + @Override + public String format(LogRecord record) { + String message = thread ? thread() : format; + for (String parsedKey : parsedProps) { + String value = HelidonMdc.get(parsedKey).orElse(""); + message = PATTERN_CACHE.computeIfAbsent(parsedKey, key -> Pattern.compile("%X\\{" + key + "}")) + .matcher(message).replaceAll(value); + } + return formatRow(record, message); + } + + private String thread() { + String currentThread = Thread.currentThread().toString(); + String message = PATTERN_CACHE.computeIfAbsent(THREAD, key -> Pattern.compile("%X\\{" + THREAD + "}")) + .matcher(format).replaceAll(currentThread); + message = THREAD_PATTERN.matcher(message).replaceAll(currentThread); + return message; + } + + //Copied from SimpleFormatter + private String formatRow(LogRecord record, String format) { + ZonedDateTime zdt = ZonedDateTime.ofInstant( + record.getInstant(), ZoneId.systemDefault()); + String source; + if (record.getSourceClassName() != null) { + source = record.getSourceClassName(); + if (record.getSourceMethodName() != null) { + source += " " + record.getSourceMethodName(); + } + } else { + source = record.getLoggerName(); + } + String message = formatMessage(record); + String throwable = ""; + if (record.getThrown() != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.println(); + record.getThrown().printStackTrace(pw); + pw.close(); + throwable = sw.toString(); + } + return String.format(format, + zdt, + source, + record.getLoggerName(), + record.getLevel().getLocalizedName(), + message, + throwable); + } +} diff --git a/logging/jul/src/main/java/io/helidon/logging/jul/JulMdc.java b/logging/jul/src/main/java/io/helidon/logging/jul/JulMdc.java new file mode 100644 index 00000000000..52329368cc4 --- /dev/null +++ b/logging/jul/src/main/java/io/helidon/logging/jul/JulMdc.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.jul; + +import java.util.HashMap; +import java.util.Map; + +/** + * MDC implementation for Java Util Logging framework. + */ +public class JulMdc { + + private static final ThreadLocal> MDC_PROPERTIES = ThreadLocal.withInitial(HashMap::new); + + private JulMdc() { + throw new IllegalStateException("This class cannot be instantiated"); + } + + static void put(String key, String value) { + MDC_PROPERTIES.get().put(key, value); + } + + /** + * Return value bound to the key from the MDC storage. + * + * @param key key value + * @return value bound to the key + */ + public static String get(String key) { + return MDC_PROPERTIES.get().get(key); + } + + static void remove(String key) { + MDC_PROPERTIES.get().remove(key); + } + + static void clear() { + MDC_PROPERTIES.get().clear(); + } + + static Map properties() { + return new HashMap<>(MDC_PROPERTIES.get()); + } + + static void properties(Map properties) { + MDC_PROPERTIES.set(new HashMap<>(properties)); + } + +} diff --git a/logging/jul/src/main/java/io/helidon/logging/jul/JulMdcPropagator.java b/logging/jul/src/main/java/io/helidon/logging/jul/JulMdcPropagator.java new file mode 100644 index 00000000000..e87b65c1537 --- /dev/null +++ b/logging/jul/src/main/java/io/helidon/logging/jul/JulMdcPropagator.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.jul; + +import java.util.Map; + +import io.helidon.common.context.spi.DataPropagationProvider; + +/** + * This is propagator of JUL MDC values between different threads. + * This class is loaded and used via SPI. + */ +public class JulMdcPropagator implements DataPropagationProvider> { + + @Override + public Map data() { + return JulMdc.properties(); + } + + @Override + public void propagateData(Map data) { + JulMdc.properties(data); + } + + @Override + public void clearData() { + JulMdc.clear(); + } + +} diff --git a/logging/jul/src/main/java/io/helidon/logging/jul/JulMdcProvider.java b/logging/jul/src/main/java/io/helidon/logging/jul/JulMdcProvider.java new file mode 100644 index 00000000000..2c29398a7f5 --- /dev/null +++ b/logging/jul/src/main/java/io/helidon/logging/jul/JulMdcProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.jul; + +import io.helidon.logging.common.spi.MdcProvider; + +/** + * Provider for setting MDC values to the Java Util Logging MDC support. + * This class is loaded and used via SPI. + */ +public class JulMdcProvider implements MdcProvider { + @Override + public void put(String key, String value) { + JulMdc.put(key, value); + } + + @Override + public void remove(String key) { + JulMdc.remove(key); + } + + @Override + public void clear() { + JulMdc.clear(); + } + + @Override + public String get(String key) { + return JulMdc.get(key); + } +} diff --git a/logging/jul/src/main/java/io/helidon/logging/jul/package-info.java b/logging/jul/src/main/java/io/helidon/logging/jul/package-info.java new file mode 100644 index 00000000000..6030c60ea9c --- /dev/null +++ b/logging/jul/src/main/java/io/helidon/logging/jul/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Helidon Java util logging MDC support. + */ +package io.helidon.logging.jul; diff --git a/logging/jul/src/main/java/module-info.java b/logging/jul/src/main/java/module-info.java new file mode 100644 index 00000000000..e5bf9506300 --- /dev/null +++ b/logging/jul/src/main/java/module-info.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Helidon Java Util Logging MDC support module. + */ +module io.helidon.logging.jul { + requires java.logging; + + requires io.helidon.common; + requires io.helidon.common.context; + requires io.helidon.logging.common; + + exports io.helidon.logging.jul; + + provides io.helidon.common.context.spi.DataPropagationProvider with io.helidon.logging.jul.JulMdcPropagator; + provides io.helidon.logging.common.spi.MdcProvider with io.helidon.logging.jul.JulMdcProvider; +} \ No newline at end of file diff --git a/logging/jul/src/main/resources/META-INF/services/io.helidon.common.context.spi.DataPropagationProvider b/logging/jul/src/main/resources/META-INF/services/io.helidon.common.context.spi.DataPropagationProvider new file mode 100644 index 00000000000..187ab2f953d --- /dev/null +++ b/logging/jul/src/main/resources/META-INF/services/io.helidon.common.context.spi.DataPropagationProvider @@ -0,0 +1,16 @@ +# +# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# +# 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. +# +io.helidon.logging.jul.JulMdcPropagator \ No newline at end of file diff --git a/logging/jul/src/main/resources/META-INF/services/io.helidon.logging.common.spi.MdcProvider b/logging/jul/src/main/resources/META-INF/services/io.helidon.logging.common.spi.MdcProvider new file mode 100644 index 00000000000..dfe95ac73a9 --- /dev/null +++ b/logging/jul/src/main/resources/META-INF/services/io.helidon.logging.common.spi.MdcProvider @@ -0,0 +1,16 @@ +# +# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# +# 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. +# +io.helidon.logging.jul.JulMdcProvider \ No newline at end of file diff --git a/logging/jul/src/test/java/io/helidon/logging/jul/JulMdcTest.java b/logging/jul/src/test/java/io/helidon/logging/jul/JulMdcTest.java new file mode 100644 index 00000000000..d9421669cb3 --- /dev/null +++ b/logging/jul/src/test/java/io/helidon/logging/jul/JulMdcTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.jul; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +import io.helidon.common.LogConfig; +import io.helidon.common.context.Context; +import io.helidon.common.context.Contexts; +import io.helidon.common.context.ExecutorException; +import io.helidon.logging.common.HelidonMdc; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test proper function of Jul MDC propagator and provider. + */ +public class JulMdcTest { + + private static final Logger LOGGER = Logger.getLogger(JulMdcTest.class.getName()); + private static final ByteArrayOutputStream OUTPUT_STREAM = new ByteArrayOutputStream(); + private static final PrintStream TEST_STREAM = new PrintStream(OUTPUT_STREAM); + + private static final String TEST_KEY = "test"; + private static final String TEST_VALUE = "value"; + + @Test + public void testMdc() { + PrintStream original = System.out; + try { + System.setOut(TEST_STREAM); + LogConfig.initClass(); + OUTPUT_STREAM.reset(); + HelidonMdc.set(TEST_KEY, TEST_VALUE); + String message = "This is test logging message"; + String thread = Thread.currentThread().toString(); + String logMessage = logMessage(message); + assertThat(logMessage, endsWith(thread + ": " + message + " " + TEST_VALUE + System.lineSeparator())); + + HelidonMdc.remove(TEST_KEY); + logMessage = logMessage(message); + assertThat(logMessage, endsWith(thread + ": " + message + " " + System.lineSeparator())); + + HelidonMdc.set(TEST_KEY, TEST_VALUE); + HelidonMdc.clear(); + logMessage = logMessage(message); + assertThat(logMessage, endsWith(thread + ": " + message + " " + System.lineSeparator())); + } finally { + System.setOut(original); + } + } + + private String logMessage(String message) { + try { + LOGGER.info(message); + return OUTPUT_STREAM.toString(); + } finally { + OUTPUT_STREAM.reset(); + } + } + + @Test + public void testThreadPropagation() { + HelidonMdc.set(TEST_KEY, TEST_VALUE); + Context context = Context.create(); + ExecutorService executor = Contexts.wrap(Executors.newFixedThreadPool(1)); + + Contexts.runInContext(context, () -> { + try { + String value = executor.submit(new TestCallable()).get(); + assertThat(value, is(TEST_VALUE)); + } catch (Exception e) { + throw new ExecutorException("failed to execute", e); + } + }); + } + + private static final class TestCallable implements Callable { + + @Override + public String call() { + return JulMdc.get(TEST_KEY); + } + } + +} diff --git a/logging/jul/src/test/resources/logging.properties b/logging/jul/src/test/resources/logging.properties new file mode 100644 index 00000000000..c5cbd7ed373 --- /dev/null +++ b/logging/jul/src/test/resources/logging.properties @@ -0,0 +1,24 @@ +# +# Copyright (c) 2020 Oracle and/or its affiliates. +# +# 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. +# + +# Send messages to the console +handlers=io.helidon.logging.jul.HelidonConsoleHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s %X{test}%n + +# Global logging level. Can be overridden by specific loggers +.level=INFO diff --git a/logging/log4j/pom.xml b/logging/log4j/pom.xml new file mode 100644 index 00000000000..ab3b303ba26 --- /dev/null +++ b/logging/log4j/pom.xml @@ -0,0 +1,63 @@ + + + + + helidon-logging-project + io.helidon.logging + 2.1.1-SNAPSHOT + + 4.0.0 + + helidon-logging-log4j + Helidon Log4J Integration + + + + true + + + + + io.helidon.common + helidon-common-context + + + io.helidon.logging + helidon-logging-common + + + org.apache.logging.log4j + log4j-api + provided + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + + + \ No newline at end of file diff --git a/logging/log4j/src/main/java/io/helidon/logging/log4j/Log4jMdcPropagator.java b/logging/log4j/src/main/java/io/helidon/logging/log4j/Log4jMdcPropagator.java new file mode 100644 index 00000000000..7a296c84720 --- /dev/null +++ b/logging/log4j/src/main/java/io/helidon/logging/log4j/Log4jMdcPropagator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.log4j; + +import java.util.Map; + +import io.helidon.common.context.spi.DataPropagationProvider; + +import org.apache.logging.log4j.ThreadContext; + +/** + * This is propagator of Log4j MDC values between different threads. + * This class is loaded and used via SPI. + */ +public class Log4jMdcPropagator implements DataPropagationProvider> { + + @Override + public Map data() { + return ThreadContext.getContext(); + } + + @Override + public void propagateData(Map data) { + ThreadContext.putAll(data); + } + + @Override + public void clearData() { + ThreadContext.clearAll(); + } +} diff --git a/logging/log4j/src/main/java/io/helidon/logging/log4j/Log4jMdcProvider.java b/logging/log4j/src/main/java/io/helidon/logging/log4j/Log4jMdcProvider.java new file mode 100644 index 00000000000..2b16f214d18 --- /dev/null +++ b/logging/log4j/src/main/java/io/helidon/logging/log4j/Log4jMdcProvider.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.log4j; + +import io.helidon.logging.common.spi.MdcProvider; + +import org.apache.logging.log4j.ThreadContext; + +/** + * Provider for setting MDC values to the Log4j MDC support. + * This class is loaded and used via SPI. + */ +public class Log4jMdcProvider implements MdcProvider { + @Override + public void put(String key, String value) { + ThreadContext.put(key, value); + } + + @Override + public void remove(String key) { + ThreadContext.remove(key); + } + + @Override + public void clear() { + ThreadContext.clearAll(); + } + + @Override + public String get(String key) { + return ThreadContext.get(key); + } +} diff --git a/logging/log4j/src/main/java/io/helidon/logging/log4j/package-info.java b/logging/log4j/src/main/java/io/helidon/logging/log4j/package-info.java new file mode 100644 index 00000000000..e87a17e2b43 --- /dev/null +++ b/logging/log4j/src/main/java/io/helidon/logging/log4j/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Helidon Log4j MDC propagation support. + */ +package io.helidon.logging.log4j; diff --git a/logging/log4j/src/main/java/module-info.java b/logging/log4j/src/main/java/module-info.java new file mode 100644 index 00000000000..6bb5ad11d95 --- /dev/null +++ b/logging/log4j/src/main/java/module-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Helidon Log4j MDC module. + */ +module io.helidon.logging.log4j { + requires io.helidon.common.context; + requires org.apache.logging.log4j; + requires io.helidon.logging.common; + + exports io.helidon.logging.log4j; + + provides io.helidon.logging.common.spi.MdcProvider with io.helidon.logging.log4j.Log4jMdcProvider; + provides io.helidon.common.context.spi.DataPropagationProvider with io.helidon.logging.log4j.Log4jMdcPropagator; +} \ No newline at end of file diff --git a/logging/log4j/src/main/resources/META-INF/services/io.helidon.common.context.spi.DataPropagationProvider b/logging/log4j/src/main/resources/META-INF/services/io.helidon.common.context.spi.DataPropagationProvider new file mode 100644 index 00000000000..5d524742891 --- /dev/null +++ b/logging/log4j/src/main/resources/META-INF/services/io.helidon.common.context.spi.DataPropagationProvider @@ -0,0 +1,16 @@ +# +# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# +# 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. +# +io.helidon.logging.log4j.Log4jMdcPropagator \ No newline at end of file diff --git a/logging/log4j/src/main/resources/META-INF/services/io.helidon.logging.common.spi.MdcProvider b/logging/log4j/src/main/resources/META-INF/services/io.helidon.logging.common.spi.MdcProvider new file mode 100644 index 00000000000..3b778ada6dd --- /dev/null +++ b/logging/log4j/src/main/resources/META-INF/services/io.helidon.logging.common.spi.MdcProvider @@ -0,0 +1,16 @@ +# +# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# +# 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. +# +io.helidon.logging.log4j.Log4jMdcProvider \ No newline at end of file diff --git a/logging/log4j/src/test/java/io/helidon/logging/log4j/Log4jMdcTest.java b/logging/log4j/src/test/java/io/helidon/logging/log4j/Log4jMdcTest.java new file mode 100644 index 00000000000..73ebc95443a --- /dev/null +++ b/logging/log4j/src/test/java/io/helidon/logging/log4j/Log4jMdcTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.log4j; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import io.helidon.common.context.Context; +import io.helidon.common.context.Contexts; +import io.helidon.common.context.ExecutorException; +import io.helidon.logging.common.HelidonMdc; + +import org.apache.logging.log4j.ThreadContext; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test proper function of Log4j MDC propagator and provider. + */ +public class Log4jMdcTest { + + private static final String TEST_KEY = "test"; + private static final String TEST_VALUE = "value"; + + @Test + public void testMdc() { + HelidonMdc.set(TEST_KEY, TEST_VALUE); + assertThat(ThreadContext.get(TEST_KEY), is(TEST_VALUE)); + HelidonMdc.remove(TEST_KEY); + assertThat(ThreadContext.get(TEST_KEY), nullValue()); + HelidonMdc.set(TEST_KEY, TEST_VALUE); + HelidonMdc.clear(); + assertThat(ThreadContext.get(TEST_KEY), nullValue()); + } + + @Test + public void testThreadPropagation() { + HelidonMdc.set(TEST_KEY, TEST_VALUE); + Context context = Context.create(); + ExecutorService executor = Contexts.wrap(Executors.newFixedThreadPool(1)); + + Contexts.runInContext(context, () -> { + try { + String value = executor.submit(new TestCallable()).get(); + assertThat(value, is(TEST_VALUE)); + } catch (Exception e) { + throw new ExecutorException("failed to execute", e); + } + }); + } + + private static final class TestCallable implements Callable { + + @Override + public String call() { + return ThreadContext.get(TEST_KEY); + } + } + +} diff --git a/logging/pom.xml b/logging/pom.xml new file mode 100644 index 00000000000..96ee833a1e0 --- /dev/null +++ b/logging/pom.xml @@ -0,0 +1,39 @@ + + + + 4.0.0 + + helidon-project + io.helidon + 2.1.1-SNAPSHOT + + + io.helidon.logging + helidon-logging-project + pom + Helidon Logging Project + + + common + jul + slf4j + log4j + + + \ No newline at end of file diff --git a/logging/slf4j/pom.xml b/logging/slf4j/pom.xml new file mode 100644 index 00000000000..75e9ca930f7 --- /dev/null +++ b/logging/slf4j/pom.xml @@ -0,0 +1,61 @@ + + + + 4.0.0 + + helidon-logging-project + io.helidon.logging + 2.1.1-SNAPSHOT + + + helidon-logging-slf4j + Helidon SLF4J Integration + + + + io.helidon.common + helidon-common-context + + + io.helidon.logging + helidon-logging-common + + + org.slf4j + slf4j-api + provided + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + org.slf4j + slf4j-jdk14 + test + + + + \ No newline at end of file diff --git a/logging/slf4j/src/main/java/io/helidon/logging/slf4j/Slf4jMdcPropagator.java b/logging/slf4j/src/main/java/io/helidon/logging/slf4j/Slf4jMdcPropagator.java new file mode 100644 index 00000000000..28e5f0dfef9 --- /dev/null +++ b/logging/slf4j/src/main/java/io/helidon/logging/slf4j/Slf4jMdcPropagator.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.slf4j; + +import java.util.Map; + +import io.helidon.common.context.spi.DataPropagationProvider; + +import org.slf4j.MDC; + +/** + * This is propagator of Slf4j MDC values between different threads. + * This class is loaded and used via SPI. + */ +public class Slf4jMdcPropagator implements DataPropagationProvider> { + + @Override + public Map data() { + return MDC.getCopyOfContextMap(); + } + + @Override + public void propagateData(Map data) { + MDC.setContextMap(data); + } + + @Override + public void clearData() { + MDC.clear(); + } + +} diff --git a/logging/slf4j/src/main/java/io/helidon/logging/slf4j/Slf4jMdcProvider.java b/logging/slf4j/src/main/java/io/helidon/logging/slf4j/Slf4jMdcProvider.java new file mode 100644 index 00000000000..0f069546904 --- /dev/null +++ b/logging/slf4j/src/main/java/io/helidon/logging/slf4j/Slf4jMdcProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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 io.helidon.logging.slf4j; + + +import io.helidon.logging.common.spi.MdcProvider; + +import org.slf4j.MDC; + +/** + * Provider for setting MDC values to the Slf4j MDC support. + * This class is loaded and used via SPI. + */ +public class Slf4jMdcProvider implements MdcProvider { + + @Override + public void put(String key, String value) { + MDC.put(key, value); + } + + @Override + public void remove(String key) { + MDC.remove(key); + } + + @Override + public void clear() { + MDC.clear(); + } + + @Override + public String get(String key) { + return MDC.get(key); + } +} diff --git a/logging/slf4j/src/main/java/io/helidon/logging/slf4j/package-info.java b/logging/slf4j/src/main/java/io/helidon/logging/slf4j/package-info.java new file mode 100644 index 00000000000..655ac528a3a --- /dev/null +++ b/logging/slf4j/src/main/java/io/helidon/logging/slf4j/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Helidon Slf4J MDC propagation support. + */ +package io.helidon.logging.slf4j; diff --git a/logging/slf4j/src/main/java/module-info.java b/logging/slf4j/src/main/java/module-info.java new file mode 100644 index 00000000000..c02b515e3a7 --- /dev/null +++ b/logging/slf4j/src/main/java/module-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +/** + * Helidon Slf4j MDC module. + */ +module io.helidon.logging.slf4j { + requires io.helidon.common.context; + requires io.helidon.logging.common; + + requires slf4j.api; + + exports io.helidon.logging.slf4j; + + provides io.helidon.common.context.spi.DataPropagationProvider with io.helidon.logging.slf4j.Slf4jMdcPropagator; + provides io.helidon.logging.common.spi.MdcProvider with io.helidon.logging.slf4j.Slf4jMdcProvider; +} \ No newline at end of file diff --git a/logging/slf4j/src/main/resources/META-INF/services/io.helidon.common.context.spi.DataPropagationProvider b/logging/slf4j/src/main/resources/META-INF/services/io.helidon.common.context.spi.DataPropagationProvider new file mode 100644 index 00000000000..c80d996362a --- /dev/null +++ b/logging/slf4j/src/main/resources/META-INF/services/io.helidon.common.context.spi.DataPropagationProvider @@ -0,0 +1,16 @@ +# +# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# +# 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. +# +io.helidon.logging.slf4j.Slf4jMdcPropagator \ No newline at end of file diff --git a/logging/slf4j/src/main/resources/META-INF/services/io.helidon.logging.common.spi.MdcProvider b/logging/slf4j/src/main/resources/META-INF/services/io.helidon.logging.common.spi.MdcProvider new file mode 100644 index 00000000000..a8fa3a37fcb --- /dev/null +++ b/logging/slf4j/src/main/resources/META-INF/services/io.helidon.logging.common.spi.MdcProvider @@ -0,0 +1,16 @@ +# +# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# +# 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. +# +io.helidon.logging.slf4j.Slf4jMdcProvider \ No newline at end of file diff --git a/logging/slf4j/src/test/java/Slf4jMdcTest.java b/logging/slf4j/src/test/java/Slf4jMdcTest.java new file mode 100644 index 00000000000..f5e0d243fa2 --- /dev/null +++ b/logging/slf4j/src/test/java/Slf4jMdcTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * 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. + */ + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import io.helidon.common.context.Context; +import io.helidon.common.context.Contexts; +import io.helidon.common.context.ExecutorException; +import io.helidon.logging.common.HelidonMdc; + +import org.junit.jupiter.api.Test; +import org.slf4j.MDC; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test proper function of Slf4j MDC propagator and provider. + */ +public class Slf4jMdcTest { + + private static final String TEST_KEY = "test"; + private static final String TEST_VALUE = "value"; + + @Test + public void testMdc() { + HelidonMdc.set(TEST_KEY, TEST_VALUE); + assertThat(MDC.get(TEST_KEY), is(TEST_VALUE)); + HelidonMdc.remove(TEST_KEY); + assertThat(MDC.get(TEST_KEY), nullValue()); + HelidonMdc.set(TEST_KEY, TEST_VALUE); + HelidonMdc.clear(); + assertThat(MDC.get(TEST_KEY), nullValue()); + } + + @Test + public void testThreadPropagation() { + HelidonMdc.set(TEST_KEY, TEST_VALUE); + Context context = Context.create(); + ExecutorService executor = Contexts.wrap(Executors.newFixedThreadPool(1)); + + Contexts.runInContext(context, () -> { + try { + String value = executor.submit(new TestCallable()).get(); + assertThat(value, is(TEST_VALUE)); + } catch (Exception e) { + throw new ExecutorException("failed to execute", e); + } + }); + } + + private static final class TestCallable implements Callable { + + @Override + public String call() { + return MDC.get(TEST_KEY); + } + } + +} diff --git a/pom.xml b/pom.xml index bd950db3531..e6fdbad3fa2 100644 --- a/pom.xml +++ b/pom.xml @@ -180,6 +180,7 @@ dbclient messaging fault-tolerance + logging