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.util.HashMap;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.ServiceConfigurationError;
026import java.util.ServiceLoader;
027
028import org.apache.logging.log4j.status.StatusLogger;
029import org.apache.logging.log4j.util.StringBuilderFormattable;
030import org.apache.logging.log4j.util.Strings;
031
032/**
033 * Captures information about all running Threads.
034 */
035@AsynchronouslyFormattable
036public class ThreadDumpMessage implements Message, StringBuilderFormattable {
037    private static final long serialVersionUID = -1103400781608841088L;
038    private static ThreadInfoFactory FACTORY;
039
040    private volatile Map<ThreadInformation, StackTraceElement[]> threads;
041    private final String title;
042    private String formattedMessage;
043
044    /**
045     * Generate a ThreadDumpMessage with a title.
046     * @param title The title.
047     */
048    public ThreadDumpMessage(final String title) {
049        this.title = title == null ? Strings.EMPTY : title;
050        threads = getFactory().createThreadInfo();
051    }
052
053    private ThreadDumpMessage(final String formattedMsg, final String title) {
054        this.formattedMessage = formattedMsg;
055        this.title = title == null ? Strings.EMPTY : title;
056    }
057
058    private static ThreadInfoFactory getFactory() {
059        if (FACTORY == null) {
060            FACTORY = initFactory(ThreadDumpMessage.class.getClassLoader());
061        }
062        return FACTORY;
063    }
064
065    private static ThreadInfoFactory initFactory(final ClassLoader classLoader) {
066        final ServiceLoader<ThreadInfoFactory> serviceLoader = ServiceLoader.load(ThreadInfoFactory.class, classLoader);
067        ThreadInfoFactory result = null;
068        try {
069            final Iterator<ThreadInfoFactory> iterator = serviceLoader.iterator();
070            while (result == null && iterator.hasNext()) {
071                result = iterator.next();
072            }
073        } catch (ServiceConfigurationError | LinkageError | Exception unavailable) { // if java management classes not available
074            StatusLogger.getLogger().info("ThreadDumpMessage uses BasicThreadInfoFactory: " +
075                            "could not load extended ThreadInfoFactory: {}", unavailable.toString());
076            result = null;
077        }
078        return result == null ? new BasicThreadInfoFactory() : result;
079    }
080
081    @Override
082    public String toString() {
083        return getFormattedMessage();
084    }
085
086    /**
087     * Returns the ThreadDump in printable format.
088     * @return the ThreadDump suitable for logging.
089     */
090    @Override
091    public String getFormattedMessage() {
092        if (formattedMessage != null) {
093            return formattedMessage;
094        }
095        final StringBuilder sb = new StringBuilder(255);
096        formatTo(sb);
097        return sb.toString();
098    }
099
100    @Override
101    public void formatTo(final StringBuilder sb) {
102        sb.append(title);
103        if (title.length() > 0) {
104            sb.append('\n');
105        }
106        for (final Map.Entry<ThreadInformation, StackTraceElement[]> entry : threads.entrySet()) {
107            final ThreadInformation info = entry.getKey();
108            info.printThreadInfo(sb);
109            info.printStack(sb, entry.getValue());
110            sb.append('\n');
111        }
112    }
113
114    /**
115     * Returns the title.
116     * @return the title.
117     */
118    @Override
119    public String getFormat() {
120        return title == null ? Strings.EMPTY : title;
121    }
122
123    /**
124     * Returns an array with a single element, a Map containing the ThreadInformation as the key.
125     * and the StackTraceElement array as the value;
126     * @return the "parameters" to this Message.
127     */
128    @Override
129    public Object[] getParameters() {
130        return null;
131    }
132
133        /**
134     * Creates a ThreadDumpMessageProxy that can be serialized.
135     * @return a ThreadDumpMessageProxy.
136     */
137    protected Object writeReplace() {
138        return new ThreadDumpMessageProxy(this);
139    }
140
141    private void readObject(final ObjectInputStream stream)
142        throws InvalidObjectException {
143        throw new InvalidObjectException("Proxy required");
144    }
145
146    /**
147     * Proxy pattern used to serialize the ThreadDumpMessage.
148     */
149    private static class ThreadDumpMessageProxy implements Serializable {
150
151        private static final long serialVersionUID = -3476620450287648269L;
152        private final String formattedMsg;
153        private final String title;
154
155        ThreadDumpMessageProxy(final ThreadDumpMessage msg) {
156            this.formattedMsg = msg.getFormattedMessage();
157            this.title = msg.title;
158        }
159
160        /**
161         * Returns a ThreadDumpMessage using the data in the proxy.
162         * @return a ThreadDumpMessage.
163         */
164        protected Object readResolve() {
165            return new ThreadDumpMessage(formattedMsg, title);
166        }
167    }
168
169    /**
170     * Factory to create Thread information.
171     * <p>
172     * Implementations of this class are loaded via the standard java Service Provider interface.
173     * </p>
174     * @see /log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.message.ThreadDumpMessage$ThreadInfoFactory
175     */
176    public static interface ThreadInfoFactory {
177        Map<ThreadInformation, StackTraceElement[]> createThreadInfo();
178    }
179
180    /**
181     * Factory to create basic thread information.
182     */
183    private static class BasicThreadInfoFactory implements ThreadInfoFactory {
184        @Override
185        public Map<ThreadInformation, StackTraceElement[]> createThreadInfo() {
186            final Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
187            final Map<ThreadInformation, StackTraceElement[]> threads =
188                new HashMap<>(map.size());
189            for (final Map.Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {
190                threads.put(new BasicThreadInformation(entry.getKey()), entry.getValue());
191            }
192            return threads;
193        }
194    }
195
196    /**
197     * Always returns null.
198     *
199     * @return null
200     */
201    @Override
202    public Throwable getThrowable() {
203        return null;
204    }
205}