/* * 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; } }