• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tradefed.log;
17 
18 import com.android.ddmlib.Log;
19 import com.android.ddmlib.Log.LogLevel;
20 import com.android.tradefed.result.InputStreamSource;
21 import com.android.tradefed.util.FileUtil;
22 
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.text.SimpleDateFormat;
27 import java.util.Collection;
28 import java.util.Date;
29 import java.util.Hashtable;
30 import java.util.Iterator;
31 import java.util.Map;
32 
33 /**
34  * A {@link ILogRegistry} implementation that multiplexes and manages different loggers,
35  * using the appropriate one based on the {@link ThreadGroup} of the thread making the call.
36  * <p/>
37  * Note that the registry hashes on the ThreadGroup in which a thread belongs. If a thread is
38  * spawned with its own explicitly-supplied ThreadGroup, it will not inherit the parent thread's
39  * logger, and thus will need to register its own logger with the LogRegistry if it wants to log
40  * output.
41  */
42 public class LogRegistry implements ILogRegistry {
43     private static final String LOG_TAG = "LogRegistry";
44     private static final String GLOBAL_LOG_PREFIX = "tradefed_global_log_";
45     private static final String HISTORY_LOG_PREFIX = "tradefed_history_log_";
46     private static LogRegistry mLogRegistry = null;
47     private Map<ThreadGroup, ILeveledLogOutput> mLogTable = new Hashtable<>();
48     private FileLogger mGlobalLogger;
49     private HistoryLogger mHistoryLogger;
50 
51     /**
52      * Package-private constructor; callers should use {@link #getLogRegistry} to get an instance of
53      * the {@link LogRegistry}.
54      */
LogRegistry()55     LogRegistry() {
56         try {
57             mGlobalLogger = new FileLogger();
58             mGlobalLogger.init();
59         } catch (IOException e) {
60             System.err.println("Failed to create global logger");
61             throw new IllegalStateException(e);
62         }
63         try {
64             mHistoryLogger = new HistoryLogger();
65             mHistoryLogger.init();
66         } catch (IOException e) {
67             System.err.println("Failed to create history logger");
68             throw new IllegalStateException(e);
69         }
70     }
71 
72     /**
73      * Get the {@link LogRegistry} instance
74      * <p/>
75      *
76      * @return a {@link LogRegistry} that can be used to register, get, write to, and close logs
77      */
getLogRegistry()78     public static ILogRegistry getLogRegistry() {
79         if (mLogRegistry == null) {
80             mLogRegistry = new LogRegistry();
81         }
82 
83         return mLogRegistry;
84     }
85 
86     /**
87      * {@inheritDoc}
88      */
89     @Override
setGlobalLogDisplayLevel(LogLevel logLevel)90     public void setGlobalLogDisplayLevel(LogLevel logLevel) {
91         mGlobalLogger.setLogLevelDisplay(logLevel);
92         mHistoryLogger.setLogLevel(logLevel);
93     }
94 
95     /**
96      * {@inheritDoc}
97      */
98     @Override
getGlobalLogDisplayLevel()99     public LogLevel getGlobalLogDisplayLevel() {
100         return mGlobalLogger.getLogLevelDisplay();
101     }
102 
103     /**
104      * {@inheritDoc}
105      */
106     @Override
registerLogger(ILeveledLogOutput log)107     public void registerLogger(ILeveledLogOutput log) {
108         synchronized (mLogTable) {
109             ILeveledLogOutput oldValue = mLogTable.put(getCurrentThreadGroup(), log);
110             if (oldValue != null) {
111                 Log.e(LOG_TAG, "Registering a new logger when one already exists for this thread!");
112                 oldValue.closeLog();
113             }
114         }
115     }
116 
117     /**
118      * {@inheritDoc}
119      */
120     @Override
unregisterLogger()121     public void unregisterLogger() {
122         ThreadGroup currentThreadGroup = getCurrentThreadGroup();
123         if (currentThreadGroup != null) {
124             synchronized (mLogTable) {
125                 mLogTable.remove(currentThreadGroup);
126             }
127         } else {
128             printLog(LogLevel.ERROR, LOG_TAG, "Unregistering when thread has no logger "
129                     + "registered.");
130         }
131     }
132 
133     /**
134      * {@inheritDoc}
135      */
136     @Override
dumpToGlobalLog(ILeveledLogOutput log)137     public void dumpToGlobalLog(ILeveledLogOutput log) {
138         try (InputStreamSource source = log.getLog();
139                 InputStream stream = source.createInputStream()) {
140             mGlobalLogger.dumpToLog(stream);
141         } catch (IOException e) {
142             System.err.println("Failed to dump log");
143             e.printStackTrace();
144         }
145     }
146 
147     /**
148      * Gets the current thread Group.
149      * <p/>
150      * Exposed so unit tests can mock
151      *
152      * @return the ThreadGroup that the current thread belongs to
153      */
getCurrentThreadGroup()154     ThreadGroup getCurrentThreadGroup() {
155         return Thread.currentThread().getThreadGroup();
156     }
157 
158     /**
159      * {@inheritDoc}
160      */
161     @Override
printLog(LogLevel logLevel, String tag, String message)162     public void printLog(LogLevel logLevel, String tag, String message) {
163         ILeveledLogOutput log = getLogger();
164         LogLevel currentLogLevel = log.getLogLevel();
165         if (logLevel.getPriority() >= currentLogLevel.getPriority()) {
166             log.printLog(logLevel, tag, message);
167         }
168     }
169 
170     /**
171      * {@inheritDoc}
172      */
173     @Override
printAndPromptLog(LogLevel logLevel, String tag, String message)174     public void printAndPromptLog(LogLevel logLevel, String tag, String message) {
175         getLogger().printAndPromptLog(logLevel, tag, message);
176     }
177 
178     /**
179      * Gets the underlying logger associated with this thread.
180      *
181      * @return the logger for this thread group, or the global logger if one has not been registered
182      *     for the thread group.
183      */
getLogger()184     public ILeveledLogOutput getLogger() {
185         synchronized (mLogTable) {
186             ILeveledLogOutput log = mLogTable.get(getCurrentThreadGroup());
187             if (log == null) {
188                 // If there's no logger set for this thread, use global logger
189                 log = mGlobalLogger;
190             }
191             return log;
192         }
193     }
194 
195     /**
196      * {@inheritDoc}
197      */
198     @Override
closeAndRemoveAllLogs()199     public void closeAndRemoveAllLogs() {
200         synchronized (mLogTable) {
201             Collection<ILeveledLogOutput> allLogs = mLogTable.values();
202             Iterator<ILeveledLogOutput> iter = allLogs.iterator();
203             while (iter.hasNext()) {
204                 ILeveledLogOutput log = iter.next();
205                 log.closeLog();
206                 iter.remove();
207             }
208         }
209         saveGlobalLog();
210         mGlobalLogger.closeLog();
211         // TODO: enable saving the history log when TF close
212         // saveHistoryToDirLog();
213         mHistoryLogger.closeLog();
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
saveGlobalLog()220     public void saveGlobalLog() {
221         saveGlobalLogToDir(null);
222     }
223 
224     /** {@inheritDoc} */
225     @Override
logEvent(LogLevel logLevel, EventType event, Map<String, String> args)226     public void logEvent(LogLevel logLevel, EventType event, Map<String, String> args) {
227         // We always add the time of the event
228         SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z");
229         args.put("time", sdfDate.format(new Date()));
230         mHistoryLogger.logEvent(logLevel, event, args);
231     }
232 
233     /**
234      * Save the global log data to a file in the specified directory.
235      *
236      * @param dir directory to save file, can be null, file will be saved in tmp directory.
237      */
saveGlobalLogToDir(File dir)238     private void saveGlobalLogToDir(File dir) {
239         try (InputStreamSource globalLog = mGlobalLogger.getLog()) {
240             saveLog(GLOBAL_LOG_PREFIX, globalLog, dir);
241         }
242     }
243 
244     /**
245      * Save the history log data to a file in the specified directory.
246      *
247      * @param dir directory to save file, can be null, file will be saved in tmp directory.
248      */
saveHistoryLogToDir(File dir)249     private void saveHistoryLogToDir(File dir) {
250         try (InputStreamSource globalLog = mHistoryLogger.getLog()) {
251             saveLog(HISTORY_LOG_PREFIX, globalLog, dir);
252         }
253     }
254 
255     /**
256      * Save log data to a temporary file
257      *
258      * @param filePrefix the file name prefix
259      * @param logData the textual log data
260      */
saveLog(String filePrefix, InputStreamSource logData, File parentdir)261     private void saveLog(String filePrefix, InputStreamSource logData, File parentdir) {
262         try {
263             File tradefedLog = FileUtil.createTempFile(filePrefix, ".txt", parentdir);
264             FileUtil.writeToFile(logData.createInputStream(), tradefedLog);
265             // Align format to our standard logger
266             String message =
267                     LogUtil.getLogFormatString(
268                             LogLevel.VERBOSE,
269                             this.getClass().getSimpleName(),
270                             String.format("Saved log to %s", tradefedLog.getAbsolutePath()));
271             System.out.println(message);
272         } catch (IOException e) {
273             // ignore
274         }
275     }
276 
277     /**
278      * {@inheritDoc}
279      */
280     @Override
dumpLogs()281     public void dumpLogs() {
282         dumpLogsToDir(null);
283     }
284 
285     /**
286      * Save the log data to files in the specified directory.
287      *
288      * @param dir directory to save file, can be null, file will be saved in tmp directory.
289      */
dumpLogsToDir(File dir)290     public void dumpLogsToDir(File dir) {
291         synchronized (mLogTable) {
292             for (Map.Entry<ThreadGroup, ILeveledLogOutput> logEntry : mLogTable.entrySet()) {
293                 // use thread group name as file name - assume its descriptive
294                 String filePrefix = String.format("%s_log_", logEntry.getKey().getName());
295                 try (InputStreamSource logSource = logEntry.getValue().getLog()) {
296                     saveLog(filePrefix, logSource, dir);
297                 }
298             }
299         }
300         // save history log
301         saveHistoryLogToDir(dir);
302         // save global log last
303         saveGlobalLogToDir(dir);
304     }
305 }
306