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