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 */ 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 */ 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 90 public void setGlobalLogDisplayLevel(LogLevel logLevel) { 91 mGlobalLogger.setLogLevelDisplay(logLevel); 92 mHistoryLogger.setLogLevel(logLevel); 93 } 94 95 /** 96 * {@inheritDoc} 97 */ 98 @Override 99 public void setGlobalLogTagDisplay(Collection<String> logTagsDisplay) { 100 mGlobalLogger.addLogTagsDisplay(logTagsDisplay); 101 mHistoryLogger.addLogTagsDisplay(logTagsDisplay); 102 } 103 104 /** 105 * {@inheritDoc} 106 */ 107 @Override 108 public LogLevel getGlobalLogDisplayLevel() { 109 return mGlobalLogger.getLogLevelDisplay(); 110 } 111 112 /** 113 * {@inheritDoc} 114 */ 115 @Override 116 public void registerLogger(ILeveledLogOutput log) { 117 synchronized (mLogTable) { 118 ILeveledLogOutput oldValue = mLogTable.put(getCurrentThreadGroup(), log); 119 if (oldValue != null) { 120 Log.e(LOG_TAG, "Registering a new logger when one already exists for this thread!"); 121 oldValue.closeLog(); 122 } 123 } 124 } 125 126 /** 127 * {@inheritDoc} 128 */ 129 @Override 130 public void unregisterLogger() { 131 ThreadGroup currentThreadGroup = getCurrentThreadGroup(); 132 if (currentThreadGroup != null) { 133 synchronized (mLogTable) { 134 mLogTable.remove(currentThreadGroup); 135 } 136 } else { 137 printLog(LogLevel.ERROR, LOG_TAG, "Unregistering when thread has no logger " 138 + "registered."); 139 } 140 } 141 142 /** 143 * {@inheritDoc} 144 */ 145 @Override 146 public void dumpToGlobalLog(ILeveledLogOutput log) { 147 try (InputStreamSource source = log.getLog(); 148 InputStream stream = source.createInputStream()) { 149 mGlobalLogger.dumpToLog(stream); 150 } catch (IOException e) { 151 System.err.println("Failed to dump log"); 152 e.printStackTrace(); 153 } 154 } 155 156 /** 157 * Gets the current thread Group. 158 * <p/> 159 * Exposed so unit tests can mock 160 * 161 * @return the ThreadGroup that the current thread belongs to 162 */ 163 ThreadGroup getCurrentThreadGroup() { 164 return Thread.currentThread().getThreadGroup(); 165 } 166 167 /** 168 * {@inheritDoc} 169 */ 170 @Override 171 public void printLog(LogLevel logLevel, String tag, String message) { 172 ILeveledLogOutput log = getLogger(); 173 LogLevel currentLogLevel = log.getLogLevel(); 174 if (logLevel.getPriority() >= currentLogLevel.getPriority()) { 175 log.printLog(logLevel, tag, message); 176 } 177 } 178 179 /** 180 * {@inheritDoc} 181 */ 182 @Override 183 public void printAndPromptLog(LogLevel logLevel, String tag, String message) { 184 getLogger().printAndPromptLog(logLevel, tag, message); 185 } 186 187 /** 188 * Gets the underlying logger associated with this thread. 189 * 190 * @return the logger for this thread, or null if one has not been registered. 191 */ 192 ILeveledLogOutput getLogger() { 193 synchronized (mLogTable) { 194 ILeveledLogOutput log = mLogTable.get(getCurrentThreadGroup()); 195 if (log == null) { 196 // If there's no logger set for this thread, use global logger 197 log = mGlobalLogger; 198 } 199 return log; 200 } 201 } 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override 207 public void closeAndRemoveAllLogs() { 208 synchronized (mLogTable) { 209 Collection<ILeveledLogOutput> allLogs = mLogTable.values(); 210 Iterator<ILeveledLogOutput> iter = allLogs.iterator(); 211 while (iter.hasNext()) { 212 ILeveledLogOutput log = iter.next(); 213 log.closeLog(); 214 iter.remove(); 215 } 216 } 217 saveGlobalLog(); 218 mGlobalLogger.closeLog(); 219 // TODO: enable saving the history log when TF close 220 // saveHistoryToDirLog(); 221 mHistoryLogger.closeLog(); 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override 228 public void saveGlobalLog() { 229 saveGlobalLogToDir(null); 230 } 231 232 /** {@inheritDoc} */ 233 @Override 234 public void logEvent(LogLevel logLevel, EventType event, Map<String, String> args) { 235 // We always add the time of the event 236 SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z"); 237 args.put("time", sdfDate.format(new Date())); 238 mHistoryLogger.logEvent(logLevel, event, args); 239 } 240 241 /** 242 * Save the global log data to a file in the specified directory. 243 * 244 * @param dir directory to save file, can be null, file will be saved in tmp directory. 245 */ 246 private void saveGlobalLogToDir(File dir) { 247 try (InputStreamSource globalLog = mGlobalLogger.getLog()) { 248 saveLog(GLOBAL_LOG_PREFIX, globalLog, dir); 249 } 250 } 251 252 /** 253 * Save the history log data to a file in the specified directory. 254 * 255 * @param dir directory to save file, can be null, file will be saved in tmp directory. 256 */ 257 private void saveHistoryLogToDir(File dir) { 258 try (InputStreamSource globalLog = mHistoryLogger.getLog()) { 259 saveLog(HISTORY_LOG_PREFIX, globalLog, dir); 260 } 261 } 262 263 /** 264 * Save log data to a temporary file 265 * 266 * @param filePrefix the file name prefix 267 * @param logData the textual log data 268 */ 269 private void saveLog(String filePrefix, InputStreamSource logData, File parentdir) { 270 try { 271 File tradefedLog = FileUtil.createTempFile(filePrefix, ".txt", parentdir); 272 FileUtil.writeToFile(logData.createInputStream(), tradefedLog); 273 System.out.println(String.format("Saved log to %s", tradefedLog.getAbsolutePath())); 274 } catch (IOException e) { 275 // ignore 276 } 277 } 278 279 /** 280 * {@inheritDoc} 281 */ 282 @Override 283 public void dumpLogs() { 284 dumpLogsToDir(null); 285 } 286 287 /** 288 * Save the log data to files in the specified directory. 289 * 290 * @param dir directory to save file, can be null, file will be saved in tmp directory. 291 */ 292 public void dumpLogsToDir(File dir) { 293 synchronized (mLogTable) { 294 for (Map.Entry<ThreadGroup, ILeveledLogOutput> logEntry : mLogTable.entrySet()) { 295 // use thread group name as file name - assume its descriptive 296 String filePrefix = String.format("%s_log_", logEntry.getKey().getName()); 297 try (InputStreamSource logSource = logEntry.getValue().getLog()) { 298 saveLog(filePrefix, logSource, dir); 299 } 300 } 301 } 302 // save history log 303 saveHistoryLogToDir(dir); 304 // save global log last 305 saveGlobalLogToDir(dir); 306 } 307 } 308