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