| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| * agreements. See the NOTICE file distributed with this work for additional information regarding |
| * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.internal.logging; |
| |
| import static org.apache.geode.logging.internal.spi.LogWriterLevel.ALL; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Iterator; |
| |
| import org.apache.logging.log4j.Logger; |
| |
| import org.apache.geode.SystemFailure; |
| import org.apache.geode.annotations.Immutable; |
| import org.apache.geode.annotations.internal.MakeNotStatic; |
| import org.apache.geode.distributed.internal.InternalDistributedSystem; |
| import org.apache.geode.internal.Assert; |
| |
| /** |
| * A <code>ThreadGroup</code> that logs all {@linkplain #uncaughtException uncaught exceptions} to a |
| * GemFire <code>LogWriterI18n</code>. It also keeps track of the uncaught exceptions that were |
| * thrown by its threads. This is comes in handy when a thread fails to initialize properly (see bug |
| * 32550). |
| * |
| * @see LoggingThreadGroup#createThreadGroup |
| * |
| * @since GemFire 4.0 |
| */ |
| public class LoggingThreadGroup extends ThreadGroup { |
| |
| /** A "local" log writer that logs exceptions to standard error */ |
| @Immutable |
| private static final StandardErrorPrinter stderr = new StandardErrorPrinter(ALL.intLevel()); |
| |
| /** A set of all created LoggingThreadGroups */ |
| @MakeNotStatic |
| private static final Collection<LoggingThreadGroup> loggingThreadGroups = new ArrayList<>(); |
| |
| /** |
| * Returns a <code>ThreadGroup</code> whose {@link ThreadGroup#uncaughtException} method logs to |
| * both {#link System#err} and the given <code>InternalLogWriter</code>. |
| * |
| * @param name The name of the <code>ThreadGroup</code> |
| */ |
| public static LoggingThreadGroup createThreadGroup(final String name) { |
| return createThreadGroup(name, (Logger) null); |
| } |
| |
| /** |
| * Returns a <code>ThreadGroup</code> whose {@link ThreadGroup#uncaughtException} method logs to |
| * both {#link System#err} and the given <code>InternalLogWriter</code>. |
| * |
| * @param name The name of the <code>ThreadGroup</code> |
| * @param logWriter A <code>InternalLogWriter</code> to log uncaught exceptions to. It is okay for |
| * this argument to be <code>null</code>. |
| * |
| * @since GemFire 3.0 |
| */ |
| public static LoggingThreadGroup createThreadGroup(final String name, |
| final InternalLogWriter logWriter) { |
| // Cache the LoggingThreadGroups so that we don't create a |
| // gazillion of them. |
| LoggingThreadGroup group = null; |
| synchronized (loggingThreadGroups) { |
| for (Iterator<LoggingThreadGroup> iter = loggingThreadGroups.iterator(); iter.hasNext();) { |
| |
| LoggingThreadGroup group2 = iter.next(); |
| if (group2.isDestroyed()) { |
| // Clean is this iterator out |
| iter.remove(); |
| continue; |
| } |
| |
| if (name.equals(group2.getName())) { |
| // We already have one! |
| // Change the underlying logger to point to new one (creating new |
| // thread groups for different loggers leaks groups for repeated |
| // connect/disconnects as in dunits for example) |
| if (logWriter != group2.logWriter) { |
| group2.logWriter = logWriter; |
| } |
| group = group2; |
| break; |
| } |
| } |
| |
| if (group == null) { |
| group = new LoggingThreadGroup(name, logWriter); |
| // force autoclean to false and not inherit from parent group |
| group.setDaemon(false); |
| loggingThreadGroups.add(group); |
| } |
| } |
| |
| Assert.assertTrue(!group.isDestroyed()); |
| return group; |
| } |
| |
| /** |
| * Returns a <code>ThreadGroup</code> whose {@link ThreadGroup#uncaughtException} method logs to |
| * both {#link System#err} and the given <code>InternalLogWriter</code>. |
| * |
| * @param name The name of the <code>ThreadGroup</code> |
| * @param logger A <code>InternalLogWriter</code> to log uncaught exceptions to. It is okay for |
| * this argument to be <code>null</code>. |
| * |
| * @since GemFire 3.0 |
| */ |
| public static LoggingThreadGroup createThreadGroup(final String name, final Logger logger) { |
| // Cache the LoggingThreadGroups so that we don't create a |
| // gazillion of them. |
| LoggingThreadGroup group = null; |
| synchronized (loggingThreadGroups) { |
| for (Iterator<LoggingThreadGroup> iter = loggingThreadGroups.iterator(); iter.hasNext();) { |
| |
| LoggingThreadGroup group2 = iter.next(); |
| if (group2.isDestroyed()) { |
| // Clean is this iterator out |
| iter.remove(); |
| continue; |
| } |
| |
| if (name.equals(group2.getName())) { |
| // We already have one! |
| // Change the underlying logger to point to new one (creating new |
| // thread groups for different loggers leaks groups for repeated |
| // connect/disconnects as in dunits for example) |
| if (logger != group2.logger) { |
| group2.logger = logger; |
| } |
| group = group2; |
| break; |
| } |
| } |
| |
| if (group == null) { |
| group = new LoggingThreadGroup(name, logger); |
| // force autoclean to false and not inherit from parent group |
| group.setDaemon(false); |
| loggingThreadGroups.add(group); |
| } |
| } |
| |
| Assert.assertTrue(!group.isDestroyed()); |
| return group; |
| } |
| |
| public static void cleanUpThreadGroups() { |
| synchronized (loggingThreadGroups) { |
| for (LoggingThreadGroup loggingThreadGroup : loggingThreadGroups) { |
| if (!loggingThreadGroup.getName().equals(InternalDistributedSystem.SHUTDOWN_HOOK_NAME) |
| && !loggingThreadGroup.getName().equals("GemFireConnectionFactory Shutdown Hook")) { |
| loggingThreadGroup.cleanup(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Note: Must be used for test purposes ONLY. |
| * |
| * @return thread group with given name. |
| */ |
| public static ThreadGroup getThreadGroup(final String threadGroupName) { |
| synchronized (loggingThreadGroups) { |
| for (Object object : loggingThreadGroups) { |
| LoggingThreadGroup threadGroup = (LoggingThreadGroup) object; |
| if (threadGroup.getName().equals(threadGroupName)) { |
| return threadGroup; |
| } |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * A log writer that the user has specified for logging uncaught exceptions. |
| */ |
| protected volatile InternalLogWriter logWriter; |
| |
| /** |
| * A logger that the user has specified for logging uncaught exceptions. |
| */ |
| protected volatile Logger logger; |
| |
| /** |
| * The count uncaught exceptions that were thrown by threads in this thread group. |
| */ |
| private long uncaughtExceptionsCount; |
| |
| /** |
| * Creates a new <code>LoggingThreadGroup</code> that logs uncaught exceptions to the given log |
| * writer. |
| * |
| * @param name The name of the thread group |
| * @param logWriter A logWriter to which uncaught exceptions are logged. May be <code>null</code>. |
| */ |
| LoggingThreadGroup(final String name, final InternalLogWriter logWriter) { |
| super(name); |
| this.logWriter = logWriter; |
| } |
| |
| /** |
| * Creates a new <code>LoggingThreadGroup</code> that logs uncaught exceptions to the given |
| * logger. |
| * |
| * @param name The name of the thread group |
| * @param logger A logger to which uncaught exceptions are logged. May be <code>null</code>. |
| */ |
| LoggingThreadGroup(final String name, final Logger logger) { |
| super(name); |
| this.logger = logger; |
| } |
| |
| private final Object dispatchLock = new Object(); |
| |
| /** |
| * Logs an uncaught exception to a log writer |
| */ |
| @Override |
| public void uncaughtException(final Thread t, final Throwable ex) { |
| synchronized (dispatchLock) { |
| if (ex instanceof VirtualMachineError) { |
| SystemFailure.setFailure((VirtualMachineError) ex); // don't throw |
| } |
| // Solution to treat the shutdown hook error as a special case. |
| // Do not change the hook's thread name without also changing it here. |
| String threadName = t.getName(); |
| if ((ex instanceof NoClassDefFoundError) |
| && (threadName.equals(InternalDistributedSystem.SHUTDOWN_HOOK_NAME))) { |
| final String msg = |
| "Uncaught exception in thread %s this message can be disregarded if it occurred during an Application Server shutdown. The Exception message was: %s"; |
| final Object[] msgArgs = new Object[] {t, ex.getLocalizedMessage()}; |
| stderr.info(String.format(msg, msgArgs)); |
| if (logger != null) { |
| logger.info(String.format(msg, msgArgs)); |
| } |
| if (logWriter != null) { |
| logWriter.info(String.format(msg, msgArgs)); |
| } |
| } else { |
| stderr.severe(String.format("Uncaught exception in thread %s", t), ex); |
| if (logger != null) { |
| logger.fatal(String.format("Uncaught exception in thread %s", t), ex); |
| } |
| if (logWriter != null) { |
| logWriter.severe(String.format("Uncaught exception in thread %s", t), ex); |
| } |
| } |
| uncaughtExceptionsCount++; |
| } |
| } |
| |
| |
| /** |
| * clear number of uncaught exceptions |
| */ |
| public void clearUncaughtExceptionsCount() { |
| synchronized (dispatchLock) { |
| uncaughtExceptionsCount = 0; |
| } |
| } |
| |
| /** |
| * Returns the number of uncaught exceptions that occurred in threads in this thread group. |
| */ |
| public long getUncaughtExceptionsCount() { |
| synchronized (dispatchLock) { |
| return uncaughtExceptionsCount; |
| } |
| } |
| |
| /** |
| * clean up the threadgroup, releasing resources that could be problematic (bug 35388) |
| * |
| * @since GemFire 4.2.3 |
| */ |
| public synchronized void cleanup() { |
| // the logwriter holds onto a distribution config, which holds onto |
| // the InternalDistributedSystem, which holds onto the |
| // DistributionManager, which holds onto ... you get the idea |
| logger = null; |
| logWriter = null; |
| } |
| } |