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.lang.management.ManagementFactory;
023import java.lang.management.ThreadInfo;
024import java.lang.management.ThreadMXBean;
025import java.lang.reflect.Method;
026import java.util.HashMap;
027import java.util.Map;
028
029import org.apache.logging.log4j.util.Strings;
030
031/**
032 * Captures information about all running Threads.
033 */
034public 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}