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.StringBuilderFormattable;
30  import org.apache.logging.log4j.util.Strings;
31  
32  /**
33   * Captures information about all running Threads.
34   */
35  public class ThreadDumpMessage implements Message, StringBuilderFormattable {
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          return getFormattedMessage();
76      }
77  
78      /**
79       * Returns the ThreadDump in printable format.
80       * @return the ThreadDump suitable for logging.
81       */
82      @Override
83      public String getFormattedMessage() {
84          if (formattedMessage != null) {
85              return formattedMessage;
86          }
87          final StringBuilder sb = new StringBuilder(255);
88          formatTo(sb);
89          return sb.toString();
90      }
91  
92      @Override
93      public void formatTo(final StringBuilder sb) {
94          sb.append(title);
95          if (title.length() > 0) {
96              sb.append('\n');
97          }
98          for (final Map.Entry<ThreadInformation, StackTraceElement[]> entry : threads.entrySet()) {
99              final ThreadInformation info = entry.getKey();
100             info.printThreadInfo(sb);
101             info.printStack(sb, entry.getValue());
102             sb.append('\n');
103         }
104     }
105 
106     /**
107      * Returns the title.
108      * @return the title.
109      */
110     @Override
111     public String getFormat() {
112         return title == null ? Strings.EMPTY : title;
113     }
114 
115     /**
116      * Returns an array with a single element, a Map containing the ThreadInformation as the key.
117      * and the StackTraceElement array as the value;
118      * @return the "parameters" to this Message.
119      */
120     @Override
121     public Object[] getParameters() {
122         return null;
123     }
124 
125         /**
126      * Creates a ThreadDumpMessageProxy that can be serialized.
127      * @return a ThreadDumpMessageProxy.
128      */
129     protected Object writeReplace() {
130         return new ThreadDumpMessageProxy(this);
131     }
132 
133     private void readObject(final ObjectInputStream stream)
134         throws InvalidObjectException {
135         throw new InvalidObjectException("Proxy required");
136     }
137 
138     /**
139      * Proxy pattern used to serialize the ThreadDumpMessage.
140      */
141     private static class ThreadDumpMessageProxy implements Serializable {
142 
143         private static final long serialVersionUID = -3476620450287648269L;
144         private final String formattedMsg;
145         private final String title;
146 
147         ThreadDumpMessageProxy(final ThreadDumpMessage msg) {
148             this.formattedMsg = msg.getFormattedMessage();
149             this.title = msg.title;
150         }
151 
152         /**
153          * Returns a ThreadDumpMessage using the data in the proxy.
154          * @return a ThreadDumpMessage.
155          */
156         protected Object readResolve() {
157             return new ThreadDumpMessage(formattedMsg, title);
158         }
159     }
160 
161     /**
162      * Factory to create Thread information.
163      */
164     private interface ThreadInfoFactory {
165         Map<ThreadInformation, StackTraceElement[]> createThreadInfo();
166     }
167 
168     /**
169      * Factory to create basic thread information.
170      */
171     private static class BasicThreadInfoFactory implements ThreadInfoFactory {
172         @Override
173         public Map<ThreadInformation, StackTraceElement[]> createThreadInfo() {
174             final Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
175             final Map<ThreadInformation, StackTraceElement[]> threads =
176                 new HashMap<>(map.size());
177             for (final Map.Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {
178                 threads.put(new BasicThreadInformation(entry.getKey()), entry.getValue());
179             }
180             return threads;
181         }
182     }
183 
184     /**
185      * Factory to create extended thread information.
186      */
187     private static class ExtendedThreadInfoFactory implements ThreadInfoFactory {
188         @Override
189         public Map<ThreadInformation, StackTraceElement[]> createThreadInfo() {
190             final ThreadMXBean bean = ManagementFactory.getThreadMXBean();
191             final ThreadInfo[] array = bean.dumpAllThreads(true, true);
192 
193             final Map<ThreadInformation, StackTraceElement[]>  threads =
194                 new HashMap<>(array.length);
195             for (final ThreadInfo info : array) {
196                 threads.put(new ExtendedThreadInformation(info), info.getStackTrace());
197             }
198             return threads;
199         }
200     }
201 
202     /**
203      * Always returns null.
204      *
205      * @return null
206      */
207     @Override
208     public Throwable getThrowable() {
209         return null;
210     }
211 }