blob: 8661c0729c4289854577a70af8d6ad9da82a46d7 [file] [log] [blame]
/*
* 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.internal.logging.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;
}
}