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     */
017    package org.apache.logging.log4j.message;
018    
019    import java.io.InvalidObjectException;
020    import java.io.ObjectInputStream;
021    import java.io.Serializable;
022    import java.lang.management.ManagementFactory;
023    import java.lang.management.ThreadInfo;
024    import java.lang.management.ThreadMXBean;
025    import java.lang.reflect.Method;
026    import java.util.HashMap;
027    import java.util.Map;
028    
029    import org.apache.logging.log4j.util.Strings;
030    
031    /**
032     * Captures information about all running Threads.
033     */
034    public class ThreadDumpMessage implements Message {
035    
036        private static final long serialVersionUID = -1103400781608841088L;
037    
038        private static final ThreadInfoFactory FACTORY;
039    
040        private volatile Map<ThreadInformation, StackTraceElement[]> threads;
041    
042        private final String title;
043    
044        private String formattedMessage;
045    
046        static {
047            final Method[] methods = ThreadInfo.class.getMethods();
048            boolean basic = true;
049            for (final Method method : methods) {
050                if (method.getName().equals("getLockInfo")) {
051                    basic = false;
052                    break;
053                }
054            }
055            FACTORY = basic ? new BasicThreadInfoFactory() : new ExtendedThreadInfoFactory();
056        }
057    
058        /**
059         * Generate a ThreadDumpMessage with a title.
060         * @param title The title.
061         */
062        public ThreadDumpMessage(final String title) {
063            this.title = title == null ? Strings.EMPTY : title;
064            threads = FACTORY.createThreadInfo();
065        }
066    
067        private ThreadDumpMessage(final String formattedMsg, final String title) {
068            this.formattedMessage = formattedMsg;
069            this.title = title == null ? Strings.EMPTY : title;
070        }
071    
072        @Override
073        public String toString() {
074            final StringBuilder sb = new StringBuilder("ThreadDumpMessage[");
075            if (this.title.length() > 0) {
076                sb.append("Title=\"").append(this.title).append('"');
077            }
078            sb.append(']');
079            return sb.toString();
080        }
081    
082        /**
083         * Returns the ThreadDump in printable format.
084         * @return the ThreadDump suitable for logging.
085         */
086        @Override
087        public String getFormattedMessage() {
088            if (formattedMessage != null) {
089                return formattedMessage;
090            }
091            final StringBuilder sb = new StringBuilder(title);
092            if (title.length() > 0) {
093                sb.append('\n');
094            }
095            for (final Map.Entry<ThreadInformation, StackTraceElement[]> entry : threads.entrySet()) {
096                final ThreadInformation info = entry.getKey();
097                info.printThreadInfo(sb);
098                info.printStack(sb, entry.getValue());
099                sb.append('\n');
100            }
101            return sb.toString();
102        }
103    
104        /**
105         * Returns the title.
106         * @return the title.
107         */
108        @Override
109        public String getFormat() {
110            return title == null ? Strings.EMPTY : title;
111        }
112    
113        /**
114         * Returns an array with a single element, a Map containing the ThreadInformation as the key.
115         * and the StackTraceElement array as the value;
116         * @return the "parameters" to this Message.
117         */
118        @Override
119        public Object[] getParameters() {
120            return null;
121        }
122    
123            /**
124         * Creates a ThreadDumpMessageProxy that can be serialized.
125         * @return a ThreadDumpMessageProxy.
126         */
127        protected Object writeReplace() {
128            return new ThreadDumpMessageProxy(this);
129        }
130    
131        private void readObject(final ObjectInputStream stream)
132            throws InvalidObjectException {
133            throw new InvalidObjectException("Proxy required");
134        }
135    
136        /**
137         * Proxy pattern used to serialize the ThreadDumpMessage.
138         */
139        private static class ThreadDumpMessageProxy implements Serializable {
140    
141            private static final long serialVersionUID = -3476620450287648269L;
142            private final String formattedMsg;
143            private final String title;
144    
145            public ThreadDumpMessageProxy(final ThreadDumpMessage msg) {
146                this.formattedMsg = msg.getFormattedMessage();
147                this.title = msg.title;
148            }
149    
150            /**
151             * Returns a ThreadDumpMessage using the data in the proxy.
152             * @return a ThreadDumpMessage.
153             */
154            protected Object readResolve() {
155                return new ThreadDumpMessage(formattedMsg, title);
156            }
157        }
158    
159        /**
160         * Factory to create Thread information.
161         */
162        private interface ThreadInfoFactory {
163            Map<ThreadInformation, StackTraceElement[]> createThreadInfo();
164        }
165    
166        /**
167         * Factory to create basic thread information.
168         */
169        private static class BasicThreadInfoFactory implements ThreadInfoFactory {
170            @Override
171            public Map<ThreadInformation, StackTraceElement[]> createThreadInfo() {
172                final Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
173                final Map<ThreadInformation, StackTraceElement[]> threads =
174                    new HashMap<ThreadInformation, StackTraceElement[]>(map.size());
175                for (final Map.Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {
176                    threads.put(new BasicThreadInformation(entry.getKey()), entry.getValue());
177                }
178                return threads;
179            }
180        }
181    
182        /**
183         * Factory to create extended thread information.
184         */
185        private static class ExtendedThreadInfoFactory implements ThreadInfoFactory {
186            @Override
187            public Map<ThreadInformation, StackTraceElement[]> createThreadInfo() {
188                final ThreadMXBean bean = ManagementFactory.getThreadMXBean();
189                final ThreadInfo[] array = bean.dumpAllThreads(true, true);
190    
191                final Map<ThreadInformation, StackTraceElement[]>  threads =
192                    new HashMap<ThreadInformation, StackTraceElement[]>(array.length);
193                for (final ThreadInfo info : array) {
194                    threads.put(new ExtendedThreadInformation(info), info.getStackTrace());
195                }
196                return threads;
197            }
198        }
199    
200        /**
201         * Always returns null.
202         *
203         * @return null
204         */
205        @Override
206        public Throwable getThrowable() {
207            return null;
208        }
209    }