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.StringBuilders;
030    import org.apache.logging.log4j.util.Strings;
031    
032    /**
033     * Captures information about all running Threads.
034     */
035    public class ThreadDumpMessage implements Message {
036    
037        private static final long serialVersionUID = -1103400781608841088L;
038    
039        private static final ThreadInfoFactory FACTORY;
040    
041        private volatile Map<ThreadInformation, StackTraceElement[]> threads;
042    
043        private final String title;
044    
045        private String formattedMessage;
046    
047        static {
048            final Method[] methods = ThreadInfo.class.getMethods();
049            boolean basic = true;
050            for (final Method method : methods) {
051                if (method.getName().equals("getLockInfo")) {
052                    basic = false;
053                    break;
054                }
055            }
056            FACTORY = basic ? new BasicThreadInfoFactory() : new ExtendedThreadInfoFactory();
057        }
058    
059        /**
060         * Generate a ThreadDumpMessage with a title.
061         * @param title The title.
062         */
063        public ThreadDumpMessage(final String title) {
064            this.title = title == null ? Strings.EMPTY : title;
065            threads = FACTORY.createThreadInfo();
066        }
067    
068        private ThreadDumpMessage(final String formattedMsg, final String title) {
069            this.formattedMessage = formattedMsg;
070            this.title = title == null ? Strings.EMPTY : title;
071        }
072    
073        @Override
074        public String toString() {
075            final StringBuilder sb = new StringBuilder("ThreadDumpMessage[");
076            if (this.title.length() > 0) {
077                StringBuilders.appendKeyDqValue(sb, "Title", this.title);
078            }
079            sb.append(']');
080            return sb.toString();
081        }
082    
083        /**
084         * Returns the ThreadDump in printable format.
085         * @return the ThreadDump suitable for logging.
086         */
087        @Override
088        public String getFormattedMessage() {
089            if (formattedMessage != null) {
090                return formattedMessage;
091            }
092            final StringBuilder sb = new StringBuilder(title);
093            if (title.length() > 0) {
094                sb.append('\n');
095            }
096            for (final Map.Entry<ThreadInformation, StackTraceElement[]> entry : threads.entrySet()) {
097                final ThreadInformation info = entry.getKey();
098                info.printThreadInfo(sb);
099                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<ThreadInformation, StackTraceElement[]>(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<ThreadInformation, StackTraceElement[]>(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    }