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.LogLevel; 19 import com.android.tradefed.config.Option; 20 import com.android.tradefed.config.Option.Importance; 21 import com.android.tradefed.config.OptionClass; 22 import com.android.tradefed.config.OptionCopier; 23 import com.android.tradefed.result.ByteArrayInputStreamSource; 24 import com.android.tradefed.result.InputStreamSource; 25 import com.android.tradefed.result.SnapshotInputStreamSource; 26 import com.android.tradefed.util.SizeLimitedOutputStream; 27 import com.android.tradefed.util.StreamUtil; 28 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.util.HashMap; 32 import java.util.Map; 33 34 /** A {@link ILeveledLogOutput} that directs log messages to a file and to stdout. */ 35 @OptionClass(alias = "file") 36 public class FileLogger extends BaseLeveledLogOutput { 37 private static final String TEMP_FILE_PREFIX = "tradefed_log_"; 38 private static final String TEMP_FILE_SUFFIX = ".txt"; 39 40 /** 41 * Map of log tag to a level they are forced at for writing to log file purpose. This ensure 42 * that some logs we have less control over can still be regulated. 43 */ 44 private static final Map<String, LogLevel> FORCED_LOG_LEVEL = new HashMap<>(); 45 46 static { 47 FORCED_LOG_LEVEL.put("ddms", LogLevel.WARN); 48 } 49 50 @Option(name = "log-level", description = "the minimum log level to log.") 51 private LogLevel mLogLevel = LogLevel.DEBUG; 52 53 @Option(name = "log-level-display", shortName = 'l', 54 description = "the minimum log level to display on stdout.", 55 importance = Importance.ALWAYS) 56 private LogLevel mLogLevelDisplay = LogLevel.ERROR; 57 58 @Option(name = "max-log-size", description = "maximum allowable size of tmp log data in mB.") 59 private long mMaxLogSizeMbytes = 20; 60 61 private SizeLimitedOutputStream mLogStream; 62 FileLogger()63 public FileLogger() { 64 } 65 66 /** 67 * {@inheritDoc} 68 */ 69 @Override init()70 public void init() throws IOException { 71 init(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX); 72 } 73 74 /** 75 * Alternative to {@link #init()} where we can specify the file name and suffix. 76 * 77 * @param logPrefix the file name where to log without extension. 78 * @param fileSuffix the extension of the file where to log. 79 */ init(String logPrefix, String fileSuffix)80 protected void init(String logPrefix, String fileSuffix) { 81 mLogStream = 82 new SizeLimitedOutputStream(mMaxLogSizeMbytes * 1024 * 1024, logPrefix, fileSuffix); 83 } 84 85 /** 86 * Creates a new {@link FileLogger} with the same log level settings as the current object. 87 * <p/> 88 * Does not copy underlying log file content (ie the clone's log data will be written to a new 89 * file.) 90 */ 91 @Override clone()92 public ILeveledLogOutput clone() { 93 FileLogger logger = new FileLogger(); 94 OptionCopier.copyOptionsNoThrow(this, logger); 95 return logger; 96 } 97 98 /** 99 * {@inheritDoc} 100 */ 101 @Override printAndPromptLog(LogLevel logLevel, String tag, String message)102 public void printAndPromptLog(LogLevel logLevel, String tag, String message) { 103 internalPrintLog(logLevel, tag, message, true /* force print to stdout */); 104 } 105 106 /** 107 * {@inheritDoc} 108 */ 109 @Override printLog(LogLevel logLevel, String tag, String message)110 public void printLog(LogLevel logLevel, String tag, String message) { 111 internalPrintLog(logLevel, tag, message, false /* don't force stdout */); 112 } 113 114 /** 115 * A version of printLog(...) which can be forced to print to stdout, even if the log level 116 * isn't above the urgency threshold. 117 */ internalPrintLog(LogLevel logLevel, String tag, String message, boolean forceStdout)118 private void internalPrintLog(LogLevel logLevel, String tag, String message, 119 boolean forceStdout) { 120 String outMessage = LogUtil.getLogFormatString(logLevel, tag, message); 121 if (shouldDisplay(forceStdout, mLogLevelDisplay, logLevel, tag)) { 122 System.out.print(outMessage); 123 } 124 try { 125 if (shouldWrite(tag, logLevel, mLogLevel)) { 126 writeToLog(outMessage); 127 } 128 } catch (IOException e) { 129 e.printStackTrace(); 130 } 131 } 132 133 /** 134 * Writes given message to log. 135 * <p/> 136 * Exposed for unit testing. 137 * 138 * @param outMessage the entry to write to log 139 * @throws IOException 140 */ writeToLog(String outMessage)141 void writeToLog(String outMessage) throws IOException { 142 if (mLogStream != null) { 143 mLogStream.write(outMessage.getBytes()); 144 } 145 } 146 147 /** 148 * {@inheritDoc} 149 */ 150 @Override getLogLevel()151 public LogLevel getLogLevel() { 152 return mLogLevel; 153 } 154 155 /** 156 * {@inheritDoc} 157 */ 158 @Override setLogLevel(LogLevel logLevel)159 public void setLogLevel(LogLevel logLevel) { 160 mLogLevel = logLevel; 161 } 162 163 /** 164 * Sets the log level filtering for stdout. 165 * 166 * @param logLevel the minimum {@link LogLevel} to display 167 */ setLogLevelDisplay(LogLevel logLevel)168 public void setLogLevelDisplay(LogLevel logLevel) { 169 mLogLevelDisplay = logLevel; 170 } 171 172 /** 173 * Gets the log level filtering for stdout. 174 * 175 * @return the current {@link LogLevel} 176 */ getLogLevelDisplay()177 LogLevel getLogLevelDisplay() { 178 return mLogLevelDisplay; 179 } 180 181 /** Returns the max log size of the log in MBytes. */ getMaxLogSizeMbytes()182 public long getMaxLogSizeMbytes() { 183 return mMaxLogSizeMbytes; 184 } 185 186 /** 187 * {@inheritDoc} 188 */ 189 @Override getLog()190 public InputStreamSource getLog() { 191 if (mLogStream != null) { 192 try { 193 // create a InputStream from log file 194 mLogStream.flush(); 195 return new SnapshotInputStreamSource("FileLogger", mLogStream.getData()); 196 } catch (IOException e) { 197 System.err.println("Failed to get log"); 198 e.printStackTrace(); 199 } 200 } 201 return new ByteArrayInputStreamSource(new byte[0]); 202 } 203 204 /** 205 * {@inheritDoc} 206 */ 207 @Override closeLog()208 public void closeLog() { 209 doCloseLog(); 210 } 211 212 /** 213 * Flushes stream and closes log file. 214 * <p/> 215 * Exposed for unit testing. 216 */ doCloseLog()217 void doCloseLog() { 218 SizeLimitedOutputStream stream = mLogStream; 219 mLogStream = null; 220 StreamUtil.flushAndCloseStream(stream); 221 if (stream != null) { 222 stream.delete(); 223 } 224 } 225 226 /** 227 * Dump the contents of the input stream to this log 228 * 229 * @param inputStream 230 * @throws IOException 231 */ dumpToLog(InputStream inputStream)232 void dumpToLog(InputStream inputStream) throws IOException { 233 if (mLogStream != null) { 234 StreamUtil.copyStreams(inputStream, mLogStream); 235 } 236 } 237 shouldWrite(String tag, LogLevel messageLogLevel, LogLevel invocationLogLevel)238 private boolean shouldWrite(String tag, LogLevel messageLogLevel, LogLevel invocationLogLevel) { 239 LogLevel forcedLevel = FORCED_LOG_LEVEL.get(tag); 240 if (forcedLevel == null) { 241 return true; 242 } 243 // Use the highest level of our forced and invocation to decide if we should log the 244 // particular tag. 245 int minWriteLevel = Math.max(forcedLevel.getPriority(), invocationLogLevel.getPriority()); 246 if (messageLogLevel.getPriority() >= minWriteLevel) { 247 return true; 248 } 249 return false; 250 } 251 } 252