View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.message;
18  
19  import java.io.InvalidObjectException;
20  import java.io.ObjectInputStream;
21  import java.io.Serializable;
22  import java.lang.management.ManagementFactory;
23  import java.lang.management.ThreadInfo;
24  import java.lang.management.ThreadMXBean;
25  import java.lang.reflect.Method;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import org.apache.logging.log4j.util.StringBuilders;
30  import org.apache.logging.log4j.util.Strings;
31  
32  /**
33   * Captures information about all running Threads.
34   */
35  public class ThreadDumpMessage implements Message {
36  
37      private static final long serialVersionUID = -1103400781608841088L;
38  
39      private static final ThreadInfoFactory FACTORY;
40  
41      private volatile Map<ThreadInformation, StackTraceElement[]> threads;
42  
43      private final String title;
44  
45      private String formattedMessage;
46  
47      static {
48          final Method[] methods = ThreadInfo.class.getMethods();
49          boolean basic = true;
50          for (final Method method : methods) {
51              if (method.getName().equals("getLockInfo")) {
52                  basic = false;
53                  break;
54              }
55          }
56          FACTORY = basic ? new BasicThreadInfoFactory() : new ExtendedThreadInfoFactory();
57      }
58  
59      /**
60       * Generate a ThreadDumpMessage with a title.
61       * @param title The title.
62       */
63      public ThreadDumpMessage(final String title) {
64          this.title = title == null ? Strings.EMPTY : title;
65          threads = FACTORY.createThreadInfo();
66      }
67  
68      private ThreadDumpMessage(final String formattedMsg, final String title) {
69          this.formattedMessage = formattedMsg;
70          this.title = title == null ? Strings.EMPTY : title;
71      }
72  
73      @Override
74      public String toString() {
75          final StringBuilder sb = new StringBuilder("ThreadDumpMessage[");
76          if (this.title.length() > 0) {
77              StringBuilders.appendKeyDqValue(sb, "Title", this.title);
78          }
79          sb.append(']');
80          return sb.toString();
81      }
82  
83      /**
84       * Returns the ThreadDump in printable format.
85       * @return the ThreadDump suitable for logging.
86       */
87      @Override
88      public String getFormattedMessage() {
89          if (formattedMessage != null) {
90              return formattedMessage;
91          }
92          final StringBuilder sb = new StringBuilder(title);
93          if (title.length() > 0) {
94              sb.append('\n');
95          }
96          for (final Map.Entry<ThreadInformation, StackTraceElement[]> entry : threads.entrySet()) {
97              final ThreadInformation info = entry.getKey();
98              info.printThreadInfo(sb);
99              info.printStack(sb, entry.getValue());
100             sb.append('\n');
101         }
102         return sb.toString();
103     }
104 
105     /**
106      * Returns the title.
107      * @return the title.
108      */
109     @Override
110     public String getFormat() {
111         return title == null ? Strings.EMPTY : title;
112     }
113 
114     /**
115      * Returns an array with a single element, a Map containing the ThreadInformation as the key.
116      * and the StackTraceElement array as the value;
117      * @return the "parameters" to this Message.
118      */
119     @Override
120     public Object[] getParameters() {
121         return null;
122     }
123 
124         /**
125      * Creates a ThreadDumpMessageProxy that can be serialized.
126      * @return a ThreadDumpMessageProxy.
127      */
128     protected Object writeReplace() {
129         return new ThreadDumpMessageProxy(this);
130     }
131 
132     private void readObject(final ObjectInputStream stream)
133         throws InvalidObjectException {
134         throw new InvalidObjectException("Proxy required");
135     }
136 
137     /**
138      * Proxy pattern used to serialize the ThreadDumpMessage.
139      */
140     private static class ThreadDumpMessageProxy implements Serializable {
141 
142         private static final long serialVersionUID = -3476620450287648269L;
143         private final String formattedMsg;
144         private final String title;
145 
146         public ThreadDumpMessageProxy(final ThreadDumpMessage msg) {
147             this.formattedMsg = msg.getFormattedMessage();
148             this.title = msg.title;
149         }
150 
151         /**
152          * Returns a ThreadDumpMessage using the data in the proxy.
153          * @return a ThreadDumpMessage.
154          */
155         protected Object readResolve() {
156             return new ThreadDumpMessage(formattedMsg, title);
157         }
158     }
159 
160     /**
161      * Factory to create Thread information.
162      */
163     private interface ThreadInfoFactory {
164         Map<ThreadInformation, StackTraceElement[]> createThreadInfo();
165     }
166 
167     /**
168      * Factory to create basic thread information.
169      */
170     private static class BasicThreadInfoFactory implements ThreadInfoFactory {
171         @Override
172         public Map<ThreadInformation, StackTraceElement[]> createThreadInfo() {
173             final Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
174             final Map<ThreadInformation, StackTraceElement[]> threads =
175                 new HashMap<>(map.size());
176             for (final Map.Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {
177                 threads.put(new BasicThreadInformation(entry.getKey()), entry.getValue());
178             }
179             return threads;
180         }
181     }
182 
183     /**
184      * Factory to create extended thread information.
185      */
186     private static class ExtendedThreadInfoFactory implements ThreadInfoFactory {
187         @Override
188         public Map<ThreadInformation, StackTraceElement[]> createThreadInfo() {
189             final ThreadMXBean bean = ManagementFactory.getThreadMXBean();
190             final ThreadInfo[] array = bean.dumpAllThreads(true, true);
191 
192             final Map<ThreadInformation, StackTraceElement[]>  threads =
193                 new HashMap<>(array.length);
194             for (final ThreadInfo info : array) {
195                 threads.put(new ExtendedThreadInformation(info), info.getStackTrace());
196             }
197             return threads;
198         }
199     }
200 
201     /**
202      * Always returns null.
203      *
204      * @return null
205      */
206     @Override
207     public Throwable getThrowable() {
208         return null;
209     }
210 }