/*
* 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 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.distributed.internal.InternalDistributedSystem;
import org.apache.geode.internal.Assert;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.logging.log4j.LocalizedMessage;
import org.apache.geode.i18n.StringId;
/**
* 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 */
private static final StandardErrorPrinter stderr =
new StandardErrorPrinter(InternalLogWriter.ALL_LEVEL);
/** A set of all created LoggingThreadGroups */
private static final Collection<LoggingThreadGroup> loggingThreadGroups =
new ArrayList<LoggingThreadGroup>();
/**
* 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>.
*
* author David Whitlock
* @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 = (LoggingThreadGroup) iter.next();
if (group2.isDestroyed()) {
// Clean is this guy 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>.
*
* author David Whitlock
* @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 = (LoggingThreadGroup) iter.next();
if (group2.isDestroyed()) {
// Clean is this guy 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;
}
// /**
// * @deprecated Only for use by hydra for backwards compatability reasons.
// * Returns a <code>ThreadGroup</code> whose {@link
// * ThreadGroup#uncaughtException} method logs to both {#link
// * System#err} and the given <code>LogWriterI18n</code>.
// *
// * @param name
// * The name of the <code>ThreadGroup</code>
// * @param logger
// * A <code>LogWriter</code> to log uncaught exceptions to. It
// * is okay for this argument to be <code>null</code>.
// *
// * author kbanks
// * @since GemFire 6.0
// */
// @Deprecated public static LoggingThreadGroup createThreadGroup(final String name,
// final LogWriter logger) {
// return createThreadGroup(name,
// logger != null ? logger.convertToLogWriterI18n() : null);
// }
public static void cleanUpThreadGroups() {
synchronized (loggingThreadGroups) {
LoggingThreadGroup group;
Iterator<?> itr = loggingThreadGroups.iterator();
while (itr.hasNext()) {
group = (LoggingThreadGroup) itr.next();
if (!group.getName().equals(InternalDistributedSystem.SHUTDOWN_HOOK_NAME)
&& !group.getName().equals("GemFireConnectionFactory Shutdown Hook")) {
group.cleanup();
}
}
}
}
/**
* Note: Must be used for test purposes ONLY.
*
* @param threadGroupName
* @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 Object dispatchLock = new Object();
/**
* Logs an uncaught exception to a log writer
*/
@Override
public void uncaughtException(final Thread t, final Throwable ex) {
synchronized (this.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 StringId msg =
LocalizedStrings.UNCAUGHT_EXCEPTION_IN_THREAD_0_THIS_MESSAGE_CAN_BE_DISREGARDED_IF_IT_OCCURED_DURING_AN_APPLICATION_SERVER_SHUTDOWN_THE_EXCEPTION_MESSAGE_WAS_1;
final Object[] msgArgs = new Object[] {t, ex.getLocalizedMessage()};
stderr.info(msg, msgArgs);
if (this.logger != null) {
this.logger.info(LocalizedMessage.create(msg, msgArgs));
}
if (this.logWriter != null) {
this.logWriter.info(msg, msgArgs);
}
} else {
stderr.severe(LocalizedStrings.UNCAUGHT_EXCEPTION_IN_THREAD_0, t, ex);
if (this.logger != null) {
this.logger.fatal(
LocalizedMessage.create(LocalizedStrings.UNCAUGHT_EXCEPTION_IN_THREAD_0, t), ex);
}
if (this.logWriter != null) {
this.logWriter.severe(LocalizedStrings.UNCAUGHT_EXCEPTION_IN_THREAD_0, t, ex);
}
}
// if (!(ex instanceof RuntimeException) && (ex instanceof Exception)) {
// something's fishy - checked exceptions shouldn't get here
// this.logger.severe("stack trace showing origin of uncaught checked exception", new
// Exception("stack trace"));
// }
this.uncaughtExceptionsCount++;
}
}
/**
* clear number of uncaught exceptions
*/
public void clearUncaughtExceptionsCount() {
synchronized (this.dispatchLock) {
this.uncaughtExceptionsCount = 0;
}
}
/**
* Returns the number of uncaught exceptions that occurred in threads in this thread group.
*/
public long getUncaughtExceptionsCount() {
synchronized (this.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
this.logger = null;
this.logWriter = null;
}
}