blob: 50b930354ae6ef902681edc15208cc087e3e3d7b [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.log;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.SnapshotInputStreamSource;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.StreamUtil;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
/**
* A {@link ILeveledLogOutput} that directs log messages to a file and to stdout.
*/
@OptionClass(alias = "file")
public class FileLogger implements ILeveledLogOutput {
private static final String TEMP_FILE_PREFIX = "tradefed_log_";
private static final String TEMP_FILE_SUFFIX = ".txt";
private File mTempLogFile = null;
private BufferedWriter mLogWriter = null;
@Option(name = "log-level", description = "the minimum log level to log. Must be one of "
+ LogUtil.LOG_LEVEL_LIST + ".")
private String mLogLevel = LogLevel.DEBUG.getStringValue();
@Option(name = "log-level-display", shortName = 'l', description =
"the minimum log level to display on stdout. Must be one of " + LogUtil.LOG_LEVEL_LIST +
".", importance = Importance.ALWAYS)
private String mLogLevelStringDisplay = LogLevel.ERROR.getStringValue();
@Option(name = "log-tag-display", description = "Always display given tags logs on stdout")
private Collection<String> mLogTagsDisplay = new HashSet<String>();
// temp: track where this log was closed
private StackTraceElement[] mCloseStackFrames = null;
/**
* Sets the log level filtering for stdout.
*
* @param logLevel the {@link LogLevel#getStringValue()} log level to display
*/
void setLogLevelDisplay(String logLevel) {
mLogLevelStringDisplay = logLevel;
}
/**
* Gets the log level filtering for stdout.
*
* @return the {@link String} form of the {@link LogLevel}
*/
String getLogLevelDisplay() {
return mLogLevelStringDisplay;
}
/**
* Adds tags to the log-tag-display list
*
* @param tags collection of tags to add
*/
void addLogTagsDisplay(Collection<String> tags) {
mLogTagsDisplay.addAll(tags);
}
public FileLogger() {
}
/**
* {@inheritDoc}
*/
@Override
public void init() throws IOException {
try {
mTempLogFile = FileUtil.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX);
mLogWriter = new BufferedWriter(new FileWriter(mTempLogFile));
}
catch (IOException e) {
if (mTempLogFile != null) {
mTempLogFile.delete();
}
throw e;
}
}
/**
* Creates a new {@link FileLogger} with the same log level settings as the current object.
* <p/>
* Does not copy underlying log file content (ie the clone's log data will be written to a new
* file.)
*/
@Override
public ILeveledLogOutput clone() {
FileLogger logger = new FileLogger();
logger.setLogLevelDisplay(mLogLevelStringDisplay);
logger.setLogLevel(mLogLevel);
logger.addLogTagsDisplay(mLogTagsDisplay);
return logger;
}
/**
* {@inheritDoc}
*/
@Override
public void printAndPromptLog(LogLevel logLevel, String tag, String message) {
internalPrintLog(logLevel, tag, message, true /* force print to stdout */);
}
/**
* {@inheritDoc}
*/
@Override
public void printLog(LogLevel logLevel, String tag, String message) {
internalPrintLog(logLevel, tag, message, false /* don't force stdout */);
}
/**
* A version of printLog(...) which can be forced to print to stdout, even if the log level
* isn't above the urgency threshold.
*/
private void internalPrintLog(LogLevel logLevel, String tag, String message,
boolean forceStdout) {
String outMessage = LogUtil.getLogFormatString(logLevel, tag, message);
LogLevel displayLevel = LogLevel.getByString(mLogLevelStringDisplay);
if (forceStdout
|| logLevel.getPriority() >= displayLevel.getPriority()
|| mLogTagsDisplay.contains(tag)) {
System.out.print(outMessage);
}
try {
writeToLog(outMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Writes given message to log.
* <p/>
* Exposed for unit testing.
*
* @param outMessage the entry to write to log
* @throws IOException
*/
void writeToLog(String outMessage) throws IOException {
if (mLogWriter != null) {
mLogWriter.write(outMessage);
}
}
/**
* {@inheritDoc}
*/
@Override
public String getLogLevel() {
return mLogLevel;
}
/**
* {@inheritDoc}
*/
@Override
public void setLogLevel(String logLevel) {
mLogLevel = logLevel;
}
/**
* Returns the path representation of the file being logged to by this file logger
*/
String getFilename() throws SecurityException {
if (mTempLogFile == null) {
throw new IllegalStateException(
"logger has already been closed or has not been initialized");
}
return mTempLogFile.getAbsolutePath();
}
/**
* {@inheritDoc}
*/
public InputStreamSource getLog() {
if (mLogWriter == null) {
// TODO: change this back to throw new IllegalStateException(
System.err.println(String.format(
"logger has already been closed or has not been initialized, Thread %s",
Thread.currentThread().getName()));
System.err.println("Current stack:");
printStackTrace(Thread.currentThread().getStackTrace());
System.err.println("\nLog closed at:");
printStackTrace(mCloseStackFrames);
} else {
try {
// create a InputStream from log file
mLogWriter.flush();
return new SnapshotInputStreamSource(new FileInputStream(mTempLogFile));
} catch (IOException e) {
System.err.println("Failed to get log");
e.printStackTrace();
}
}
return new ByteArrayInputStreamSource(new byte[0]);
}
private void printStackTrace(StackTraceElement[] trace) {
if (trace == null) {
System.err.println("no stack");
return;
}
for (StackTraceElement element : trace) {
System.err.println("\tat " + element);
}
}
/**
* {@inheritDoc}
*/
@Override
public void closeLog() {
try {
doCloseLog();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Flushes stream and closes log file.
* <p/>
* Exposed for unit testing.
*
* @throws IOException
*/
void doCloseLog() throws IOException {
try {
if (mLogWriter != null) {
// TODO: temp: track where this log was closed
mCloseStackFrames = Thread.currentThread().getStackTrace();
// set mLogWriter to null first before closing, to prevent "write" calls after
// "close"
BufferedWriter writer = mLogWriter;
mLogWriter = null;
writer.flush();
writer.close();
}
} finally {
if (mTempLogFile != null) {
mTempLogFile.delete();
mTempLogFile = null;
}
}
}
/**
* Dump the contents of the input stream to this log
*
* @param createInputStream
* @throws IOException
*/
void dumpToLog(InputStream inputStream) throws IOException {
if (mLogWriter != null) {
StreamUtil.copyStreamToWriter(inputStream, mLogWriter);
}
}
}