001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.message; 018 019import java.io.InvalidObjectException; 020import java.io.ObjectInputStream; 021import java.io.Serializable; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.Map; 025import java.util.ServiceConfigurationError; 026import java.util.ServiceLoader; 027 028import org.apache.logging.log4j.status.StatusLogger; 029import org.apache.logging.log4j.util.StringBuilderFormattable; 030import org.apache.logging.log4j.util.Strings; 031 032/** 033 * Captures information about all running Threads. 034 */ 035@AsynchronouslyFormattable 036public class ThreadDumpMessage implements Message, StringBuilderFormattable { 037 private static final long serialVersionUID = -1103400781608841088L; 038 private static ThreadInfoFactory FACTORY; 039 040 private volatile Map<ThreadInformation, StackTraceElement[]> threads; 041 private final String title; 042 private String formattedMessage; 043 044 /** 045 * Generate a ThreadDumpMessage with a title. 046 * @param title The title. 047 */ 048 public ThreadDumpMessage(final String title) { 049 this.title = title == null ? Strings.EMPTY : title; 050 threads = getFactory().createThreadInfo(); 051 } 052 053 private ThreadDumpMessage(final String formattedMsg, final String title) { 054 this.formattedMessage = formattedMsg; 055 this.title = title == null ? Strings.EMPTY : title; 056 } 057 058 private static ThreadInfoFactory getFactory() { 059 if (FACTORY == null) { 060 FACTORY = initFactory(ThreadDumpMessage.class.getClassLoader()); 061 } 062 return FACTORY; 063 } 064 065 private static ThreadInfoFactory initFactory(final ClassLoader classLoader) { 066 final ServiceLoader<ThreadInfoFactory> serviceLoader = ServiceLoader.load(ThreadInfoFactory.class, classLoader); 067 ThreadInfoFactory result = null; 068 try { 069 final Iterator<ThreadInfoFactory> iterator = serviceLoader.iterator(); 070 while (result == null && iterator.hasNext()) { 071 result = iterator.next(); 072 } 073 } catch (ServiceConfigurationError | LinkageError | Exception unavailable) { // if java management classes not available 074 StatusLogger.getLogger().info("ThreadDumpMessage uses BasicThreadInfoFactory: " + 075 "could not load extended ThreadInfoFactory: {}", unavailable.toString()); 076 result = null; 077 } 078 return result == null ? new BasicThreadInfoFactory() : result; 079 } 080 081 @Override 082 public String toString() { 083 return getFormattedMessage(); 084 } 085 086 /** 087 * Returns the ThreadDump in printable format. 088 * @return the ThreadDump suitable for logging. 089 */ 090 @Override 091 public String getFormattedMessage() { 092 if (formattedMessage != null) { 093 return formattedMessage; 094 } 095 final StringBuilder sb = new StringBuilder(255); 096 formatTo(sb); 097 return sb.toString(); 098 } 099 100 @Override 101 public void formatTo(final StringBuilder sb) { 102 sb.append(title); 103 if (title.length() > 0) { 104 sb.append('\n'); 105 } 106 for (final Map.Entry<ThreadInformation, StackTraceElement[]> entry : threads.entrySet()) { 107 final ThreadInformation info = entry.getKey(); 108 info.printThreadInfo(sb); 109 info.printStack(sb, entry.getValue()); 110 sb.append('\n'); 111 } 112 } 113 114 /** 115 * Returns the title. 116 * @return the title. 117 */ 118 @Override 119 public String getFormat() { 120 return title == null ? Strings.EMPTY : title; 121 } 122 123 /** 124 * Returns an array with a single element, a Map containing the ThreadInformation as the key. 125 * and the StackTraceElement array as the value; 126 * @return the "parameters" to this Message. 127 */ 128 @Override 129 public Object[] getParameters() { 130 return null; 131 } 132 133 /** 134 * Creates a ThreadDumpMessageProxy that can be serialized. 135 * @return a ThreadDumpMessageProxy. 136 */ 137 protected Object writeReplace() { 138 return new ThreadDumpMessageProxy(this); 139 } 140 141 private void readObject(final ObjectInputStream stream) 142 throws InvalidObjectException { 143 throw new InvalidObjectException("Proxy required"); 144 } 145 146 /** 147 * Proxy pattern used to serialize the ThreadDumpMessage. 148 */ 149 private static class ThreadDumpMessageProxy implements Serializable { 150 151 private static final long serialVersionUID = -3476620450287648269L; 152 private final String formattedMsg; 153 private final String title; 154 155 ThreadDumpMessageProxy(final ThreadDumpMessage msg) { 156 this.formattedMsg = msg.getFormattedMessage(); 157 this.title = msg.title; 158 } 159 160 /** 161 * Returns a ThreadDumpMessage using the data in the proxy. 162 * @return a ThreadDumpMessage. 163 */ 164 protected Object readResolve() { 165 return new ThreadDumpMessage(formattedMsg, title); 166 } 167 } 168 169 /** 170 * Factory to create Thread information. 171 * <p> 172 * Implementations of this class are loaded via the standard java Service Provider interface. 173 * </p> 174 * @see /log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.message.ThreadDumpMessage$ThreadInfoFactory 175 */ 176 public static interface ThreadInfoFactory { 177 Map<ThreadInformation, StackTraceElement[]> createThreadInfo(); 178 } 179 180 /** 181 * Factory to create basic thread information. 182 */ 183 private static class BasicThreadInfoFactory implements ThreadInfoFactory { 184 @Override 185 public Map<ThreadInformation, StackTraceElement[]> createThreadInfo() { 186 final Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces(); 187 final Map<ThreadInformation, StackTraceElement[]> threads = 188 new HashMap<>(map.size()); 189 for (final Map.Entry<Thread, StackTraceElement[]> entry : map.entrySet()) { 190 threads.put(new BasicThreadInformation(entry.getKey()), entry.getValue()); 191 } 192 return threads; 193 } 194 } 195 196 /** 197 * Always returns null. 198 * 199 * @return null 200 */ 201 @Override 202 public Throwable getThrowable() { 203 return null; 204 } 205}