# Log系統
## log系統中類圖
在cts的log包中。

## log系統的入口
入口類為CLog。采用的是代理模式,被代理的類是DDM內部的Log類。
### CLog
~~~
public static class CLog {
/**
* The shim version of {@link Log#v(String, String)}.
*
* @param message The {@code String} to log
*/
public static void v(String message) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.v(getClassName(2), message);
}
/**
* The shim version of {@link Log#v(String, String)}. Also calls String.format for
* convenience.
*
* @param format A format string for the message to log
* @param args The format string arguments
*/
public static void v(String format, Object... args) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.v(getClassName(2), String.format(format, args));
}
/**
* The shim version of {@link Log#d(String, String)}.
*
* @param message The {@code String} to log
*/
public static void d(String message) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.d(getClassName(2), message);
}
/**
* The shim version of {@link Log#d(String, String)}. Also calls String.format for
* convenience.
*
* @param format A format string for the message to log
* @param args The format string arguments
*/
public static void d(String format, Object... args) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.d(getClassName(2), String.format(format, args));
}
/**
* The shim version of {@link Log#i(String, String)}.
*
* @param message The {@code String} to log
*/
public static void i(String message) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.i(getClassName(2), message);
}
/**
* The shim version of {@link Log#i(String, String)}. Also calls String.format for
* convenience.
*
* @param format A format string for the message to log
* @param args The format string arguments
*/
public static void i(String format, Object... args) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.i(getClassName(2), String.format(format, args));
}
/**
* The shim version of {@link Log#w(String, String)}.
*
* @param message The {@code String} to log
*/
public static void w(String message) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.w(getClassName(2), message);
}
/**
* The shim version of {@link Log#w(String, String)}. Also calls String.format for
* convenience.
*
* @param format A format string for the message to log
* @param args The format string arguments
*/
public static void w(String format, Object... args) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.w(getClassName(2), String.format(format, args));
}
/**
* The shim version of {@link Log#e(String, String)}.
*
* @param message The {@code String} to log
*/
public static void e(String message) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.e(getClassName(2), message);
}
/**
* The shim version of {@link Log#e(String, String)}. Also calls String.format for
* convenience.
*
* @param format A format string for the message to log
* @param args The format string arguments
*/
public static void e(String format, Object... args) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.e(getClassName(2), String.format(format, args));
}
/**
* The shim version of {@link Log#e(String, Throwable)}.
*
* @param t the {@link Throwable} to output.
*/
public static void e(Throwable t) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.e(getClassName(2), t);
}
/**
* The shim version of {@link Log#logAndDisplay(LogLevel, String, String)}.
*
* @param logLevel the {@link LogLevel}
* @param format A format string for the message to log
* @param args The format string arguments
*/
public static void logAndDisplay(LogLevel logLevel, String format, Object... args) {
// frame 2: skip frames 0 (#getClassName) and 1 (this method)
Log.logAndDisplay(logLevel, getClassName(2), String.format(format, args));
}
/**
* Return the simple classname from the {@code frame}th stack frame in the call path.
* Note: this method does <emph>not</emph> check array bounds for the stack trace length.
*
* @param frame The index of the stack trace frame to inspect for the class name
* @return The simple class name (or full-qualified if an error occurs getting a ref to the
* class) for the given element of the stack trace.
*/
public static String getClassName(int frame) {
StackTraceElement[] frames = (new Throwable()).getStackTrace();
String fullName = frames[frame].getClassName();
@SuppressWarnings("rawtypes")
Class klass = null;
try {
klass = Class.forName(fullName);
} catch (ClassNotFoundException e) {
// oops; not much we can do.
// Intentionally fall through so we hit the null check below
}
if (klass == null) {
return fullName;
} else {
return klass.getSimpleName();
}
}
}
~~~
所以CLog里的方法也是直接調用Log相應的方法。
仔細看看Log類中相應的log方法,比如Log.i()、Log.e()。他們都不約而同的轉向了println方法。
~~~
private static void println(LogLevel logLevel, String tag, String message) {
if (logLevel.getPriority() >= mLevel.getPriority()) {
if (sLogOutput != null) {
sLogOutput.printLog(logLevel, tag, message);
} else {
printLog(logLevel, tag, message);
}
}
}
~~~
其中sLogOutput為Log類放出的接口對象,接口定義如下所示。一般情況下如果你不設置該sLogOutput值的話,它為null。Log信息的打印會直接轉到System.out.println,直接在控制臺輸出信息。但是如果我們實現該接口,將該屬性附上值,我們就可重定向log的輸出,轉而到我們自己定義的輸出設備中。所以Cts的log系統就是利用這一點做的。
### ILogOutput
~~~
public interface ILogOutput {
/**
* Sent when a log message needs to be printed.
* @param logLevel The {@link LogLevel} enum representing the priority of the message.
* @param tag The tag associated with the message.
* @param message The message to display.
*/
public void printLog(LogLevel logLevel, String tag, String message);
/**
* Sent when a log message needs to be printed, and, if possible, displayed to the user
* in a dialog box.
* @param logLevel The {@link LogLevel} enum representing the priority of the message.
* @param tag The tag associated with the message.
* @param message The message to display.
*/
public void printAndPromptLog(LogLevel logLevel, String tag, String message);
}
private static ILogOutput sLogOutput;
~~~
上面的ILogOutput接口只有2個方法,Cts擴展了該接口,重新定義了一個接口,繼承ILogOutput接口,取名為ILeveledLogOutput。該接口主要定義輸出設備類要使用的方法。
### ILeveledLogOutput
~~~
public interface ILeveledLogOutput extends ILogOutput {
/**
* Initialize the log, creating any required IO resources.
*/
public void init() throws IOException;
/**
* Gets the minimum log level to display.
*
* @return the current {@link LogLevel}
*/
public LogLevel getLogLevel();
/**
* Sets the minimum log level to display.
*
* @param logLevel the {@link LogLevel} to display
*/
public void setLogLevel(LogLevel logLevel);
/**
* Grabs a snapshot stream of the log data.
* <p/>
* Must not be called after {@link ILeveledLogOutput#closeLog()}.
* <p/>
* The returned stream is not guaranteed to have optimal performance. Callers may wish to
* wrap result in a {@link BufferedInputStream}.
*
* @return a {@link InputStreamSource} of the log data
* @throws IllegalStateException if called when log has been closed.
*/
public InputStreamSource getLog();
/**
* Closes the log and performs any cleanup before closing, as necessary.
*/
public void closeLog();
/**
* @return a {@link ILeveledLogOutput}
*/
public ILeveledLogOutput clone();
}
~~~
ILeveledLogOutput擁有2個實現類
### FileLogger
~~~
public class FileLogger implements ILeveledLogOutput {
private static final String TEMP_FILE_PREFIX = "tradefed_log_";
private static final String TEMP_FILE_SUFFIX = ".txt";
@Option(name = "log-level", description = "the minimum log level to log.")
private LogLevel mLogLevel = LogLevel.DEBUG;
@Option(name = "log-level-display", shortName = 'l',
description = "the minimum log level to display on stdout.",
importance = Importance.ALWAYS)
private LogLevel mLogLevelDisplay = LogLevel.ERROR;
@Option(name = "log-tag-display", description = "Always display given tags logs on stdout")
private Collection<String> mLogTagsDisplay = new HashSet<String>();
@Option(name = "max-log-size", description = "maximum allowable size of tmp log data in mB.")
private long mMaxLogSizeMbytes = 20;
private SizeLimitedOutputStream mLogStream;
/**
* 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 {
mLogStream = new SizeLimitedOutputStream(mMaxLogSizeMbytes * 1024 * 1024,
TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX);
}
/**
* 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(mLogLevelDisplay);
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);
if (forceStdout
|| logLevel.getPriority() >= mLogLevelDisplay.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 (mLogStream != null) {
mLogStream.write(outMessage.getBytes());
}
}
/**
* {@inheritDoc}
*/
@Override
public LogLevel getLogLevel() {
return mLogLevel;
}
/**
* {@inheritDoc}
*/
@Override
public void setLogLevel(LogLevel logLevel) {
mLogLevel = logLevel;
}
/**
* Sets the log level filtering for stdout.
*
* @param logLevel the minimum {@link LogLevel} to display
*/
void setLogLevelDisplay(LogLevel logLevel) {
mLogLevelDisplay = logLevel;
}
/**
* Gets the log level filtering for stdout.
*
* @return the current {@link LogLevel}
*/
LogLevel getLogLevelDisplay() {
return mLogLevelDisplay;
}
/**
* {@inheritDoc}
*/
@Override
public InputStreamSource getLog() {
if (mLogStream != null) {
try {
// create a InputStream from log file
mLogStream.flush();
return new SnapshotInputStreamSource(mLogStream.getData());
} catch (IOException e) {
System.err.println("Failed to get log");
e.printStackTrace();
}
}
return new ByteArrayInputStreamSource(new byte[0]);
}
/**
* {@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 {
SizeLimitedOutputStream stream = mLogStream;
mLogStream = null;
if (stream != null) {
try {
stream.flush();
stream.close();
} finally {
stream.delete();
}
}
}
/**
* Dump the contents of the input stream to this log
*
* @param createInputStream
* @throws IOException
*/
void dumpToLog(InputStream inputStream) throws IOException {
if (mLogStream != null) {
StreamUtil.copyStreams(inputStream, mLogStream);
}
}
}
~~~
### StdoutLogger
~~~
/*
* 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 java.io.IOException;
/**
* A {@link ILeveledLogOutput} that directs log messages to stdout.
*/
@OptionClass(alias = "stdout")
public class StdoutLogger implements ILeveledLogOutput {
@Option(name="log-level", description="minimum log level to display.",
importance = Importance.ALWAYS)
private LogLevel mLogLevel = LogLevel.INFO;
//StdoutLogger(){}
/**
* {@inheritDoc}
*
*
*/
@Override
public void printAndPromptLog(LogLevel logLevel, String tag, String message) {
printLog(logLevel, tag, message);
}
/**
* {@inheritDoc}
*/
@Override
public void printLog(LogLevel logLevel, String tag, String message) {
LogUtil.printLog(logLevel, tag, message);
}
/**
* {@inheritDoc}
*/
@Override
public void setLogLevel(LogLevel logLevel) {
mLogLevel = logLevel;
}
/**
* {@inheritDoc}
*/
@Override
public LogLevel getLogLevel() {
return mLogLevel;
}
/**
* {@inheritDoc}
*/
@Override
public void closeLog() {
// ignore
}
/**
* {@inheritDoc}
*/
@Override
public InputStreamSource getLog() {
// not supported - return empty stream
return new ByteArrayInputStreamSource(new byte[0]);
}
@Override
public ILeveledLogOutput clone() {
return new StdoutLogger();
}
@Override
public void init() throws IOException {
// ignore
}
}
~~~
又定義了一個Log注冊器的接口,叫ILogRegistry。該接口也是繼承ILogOutput,但是該接口中的方法明顯不是一個log輸出系統,而是一個注冊log所要用到的方法。
### ILogRegistry
~~~
public interface ILogRegistry extends ILogOutput {
/**
* Set the log level display for the global log
*
* @param logLevel the {@link LogLevel} to use
*/
public void setGlobalLogDisplayLevel(LogLevel logLevel);
/**
* Set the log tags to display for the global log
*/
public void setGlobalLogTagDisplay(Collection<String> logTagsDisplay);
/**
* Returns current log level display for the global log
*
* @return logLevel the {@link LogLevel} to use
*/
public LogLevel getGlobalLogDisplayLevel();
/**
* Registers the logger as the instance to use for the current thread.
*/
public void registerLogger(ILeveledLogOutput log);
/**
* Unregisters the current logger in effect for the current thread.
*/
public void unregisterLogger();
/**
* Dumps the entire contents of a {@link ILeveledLogOutput} logger to the global log.
* <p/>
* This is useful in scenarios where you know the logger's output won't be saved permanently,
* yet you want the contents to be saved somewhere and not lost.
*
* @param log
*/
public void dumpToGlobalLog(ILeveledLogOutput log);
/**
* Closes and removes all logs being managed by this LogRegistry.
*/
public void closeAndRemoveAllLogs();
/**
* Diagnosis method to dump all logs to files.
*/
public void dumpLogs();
}
~~~
ILogRegistry實現類,該類采用的是單例模式
### LogRegistry
~~~
/*
* 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;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.StreamUtil;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
/**
* A {@link ILogRegistry} implementation that multiplexes and manages different loggers,
* using the appropriate one based on the {@link ThreadGroup} of the thread making the call.
* <p/>
* Note that the registry hashes on the ThreadGroup in which a thread belongs. If a thread is
* spawned with its own explicitly-supplied ThreadGroup, it will not inherit the parent thread's
* logger, and thus will need to register its own logger with the LogRegistry if it wants to log
* output.
*/
@OptionClass(alias="logreg")
public class LogRegistry implements ILogRegistry {
private static final String LOG_TAG = "LogRegistry";
private static LogRegistry mLogRegistry = null;
private Map<ThreadGroup, ILeveledLogOutput> mLogTable =
new Hashtable<ThreadGroup, ILeveledLogOutput>();
private FileLogger mGlobalLogger;
/**
* Package-private constructor; callers should use {@link #getLogRegistry} to get an instance of
* the {@link LogRegistry}.
*/
LogRegistry() {
try {
mGlobalLogger = new FileLogger();
mGlobalLogger.init();
} catch (IOException e) {
System.err.println("Failed to create global logger");
throw new IllegalStateException(e);
}
}
/**
* Get the {@link LogRegistry} instance
* <p/>
*
* @return a {@link LogRegistry} that can be used to register, get, write to, and close logs
*/
public static ILogRegistry getLogRegistry() {
if (mLogRegistry == null) {
mLogRegistry = new LogRegistry();
}
return mLogRegistry;
}
/**
* {@inheritDoc}
*/
@Override
public void setGlobalLogDisplayLevel(LogLevel logLevel) {
mGlobalLogger.setLogLevelDisplay(logLevel);
}
/**
* {@inheritDoc}
*/
@Override
public void setGlobalLogTagDisplay(Collection<String> logTagsDisplay) {
mGlobalLogger.addLogTagsDisplay(logTagsDisplay);
}
/**
* {@inheritDoc}
*/
@Override
public LogLevel getGlobalLogDisplayLevel() {
return mGlobalLogger.getLogLevelDisplay();
}
/**
* {@inheritDoc}
*/
@Override
public void registerLogger(ILeveledLogOutput log) {
ILeveledLogOutput oldValue = mLogTable.put(getCurrentThreadGroup(), log);
if (oldValue != null) {
Log.e(LOG_TAG, "Registering a new logger when one already exists for this thread!");
oldValue.closeLog();
}
}
/**
* {@inheritDoc}
*/
@Override
public void unregisterLogger() {
ThreadGroup currentThreadGroup = getCurrentThreadGroup();
if (currentThreadGroup != null) {
mLogTable.remove(currentThreadGroup);
}
else {
printLog(LogLevel.ERROR, LOG_TAG, "Unregistering when thread has no logger registered.");
}
}
/**
* {@inheritDoc}
*/
@Override
public void dumpToGlobalLog(ILeveledLogOutput log) {
InputStreamSource source = log.getLog();
try {
InputStream stream = source.createInputStream();
mGlobalLogger.dumpToLog(stream);
StreamUtil.close(stream);
} catch (IOException e) {
System.err.println("Failed to dump log");
e.printStackTrace();
} finally {
source.cancel();
}
}
/**
* Gets the current thread Group.
* <p/>
* Exposed so unit tests can mock
*
* @return the ThreadGroup that the current thread belongs to
*/
ThreadGroup getCurrentThreadGroup() {
return Thread.currentThread().getThreadGroup();
}
/**
* {@inheritDoc}
*/
@Override
public void printLog(LogLevel logLevel, String tag, String message) {
ILeveledLogOutput log = getLogger();
LogLevel currentLogLevel = log.getLogLevel();
if (logLevel.getPriority() >= currentLogLevel.getPriority()) {
log.printLog(logLevel, tag, message);
}
}
/**
* {@inheritDoc}
*/
@Override
public void printAndPromptLog(LogLevel logLevel, String tag, String message) {
getLogger().printAndPromptLog(logLevel, tag, message);
}
/**
* Gets the underlying logger associated with this thread.
*
* @return the logger for this thread, or null if one has not been registered.
*/
ILeveledLogOutput getLogger() {
ILeveledLogOutput log = mLogTable.get(getCurrentThreadGroup());
if (log == null) {
// If there's no logger set for this thread, use global logger
log = mGlobalLogger;
}
return log;
}
/**
* {@inheritDoc}
*/
@Override
public void closeAndRemoveAllLogs() {
Collection<ILeveledLogOutput> allLogs = mLogTable.values();
Iterator<ILeveledLogOutput> iter = allLogs.iterator();
while (iter.hasNext()) {
ILeveledLogOutput log = iter.next();
log.closeLog();
iter.remove();
}
saveGlobalLog();
mGlobalLogger.closeLog();
}
/**
* {@inheritDoc}
*/
private void saveGlobalLog() {
InputStreamSource globalLog = mGlobalLogger.getLog();
saveLog("tradefed_global_log_", globalLog);
globalLog.cancel();
}
/**
* Save log data to a temporary file
*
* @param filePrefix the file name prefix
* @param logData the textual log data
*/
private void saveLog(String filePrefix, InputStreamSource logData) {
try {
File tradefedLog = FileUtil.createTempFile(filePrefix, ".txt");
FileUtil.writeToFile(logData.createInputStream(), tradefedLog);
CLog.logAndDisplay(LogLevel.INFO, String.format("Saved log to %s", tradefedLog.getAbsolutePath()));
} catch (IOException e) {
// ignore
}
}
/**
* {@inheritDoc}
*/
@Override
public void dumpLogs() {
for (Map.Entry<ThreadGroup, ILeveledLogOutput> logEntry : mLogTable.entrySet()) {
// use thread group name as file name - assume its descriptive
String filePrefix = String.format("%s_log_", logEntry.getKey().getName());
InputStreamSource logSource = logEntry.getValue().getLog();
saveLog(filePrefix, logSource);
logSource.cancel();
}
// save global log last
saveGlobalLog();
}
}
~~~
## Log系統的運行過程
當cts系統開始運行的時候,我們會將LogRegistry對象注冊為Log類中sLogOutput,將DDM內部的Log輸出重定向到CTS自己的Log系統中,該對象就是LogRegistry對象,但是該對象不是直接輸出lgo的,它照樣采用代理模式或者也可以說是裝飾者模式,負責調配、管理所有的Log設備。因為cts中的允許多任務同時運行,所以管理好每一個線程的log信息,很有必要。那具體是怎么做的呢,當Cts啟動的時候,將Log注冊器設置為DDM內部的log重定向設備,代碼如下:

在initLogging()方法中,調用Log.setLogOutput方法將我們的log注冊器設置成sLogOutput屬性。
~~~
public static void setLogOutput(ILogOutput logOutput) {
sLogOutput = logOutput;
}
~~~
那么以后所有調用CLog打印的信息的操作都會轉到我們的LogRegistry對象中相應的方法中。那么就有必要來看看LogRegistry中對ILogOutput方法的實現
~~~
/**
* {@inheritDoc}
*/
@Override
public void printLog(LogLevel logLevel, String tag, String message) {
ILeveledLogOutput log = getLogger();
LogLevel currentLogLevel = log.getLogLevel();
if (logLevel.getPriority() >= currentLogLevel.getPriority()) {
log.printLog(logLevel, tag, message);
}
}
/**
* {@inheritDoc}
*/
@Override
public void printAndPromptLog(LogLevel logLevel, String tag, String message) {
getLogger().printAndPromptLog(logLevel, tag, message);
}
~~~
這兩個方法中,首先第一步都是要調用getLogger方法得到具體的輸出設備。
~~~
ILeveledLogOutput getLogger() {
ILeveledLogOutput log = mLogTable.get(getCurrentThreadGroup());
if (log == null) {
// If there's no logger set for this thread, use global logger
log = mGlobalLogger;
}
return log;
}
~~~
由上面的代碼可以看出,如果我們在Map對象中找不到log輸出設備,我們就會使用全局log設備。那么就要先看看全局log器是啥,如果能從map中得到log器,那這個log器又是啥。
首先來看全局log器
~~~
private FileLogger mGlobalLogger;
~~~
它的定義是一個FileLogger對象:該對象是一個文件log系統,它會把CLog傳入的信息在打印的同時也會保存到文件中。那么我們可以說這是個log文件。
那么如果Map不為空,我們能得到啥log器。追蹤源碼可以發現向map放入log器的代碼存在于registerLogger方法中。那么該方法為何會將當前線程所在的線程組作為關鍵字放到map中。那我們就要看看誰調用了registerLogger方法。
~~~
@Override
public void registerLogger(ILeveledLogOutput log) {
ILeveledLogOutput oldValue = mLogTable.put(getCurrentThreadGroup(), log);
if (oldValue != null) {
Log.e(LOG_TAG, "Registering a new logger when one already exists for this thread!");
oldValue.closeLog();
}
}
~~~
原來在TestInvocation類中的invoke方法中調用這個方法

如果你看過我之前的關于cts框架分析的文章,你應該會了解這個invoke在何時被調用,在每個任務啟動的時候,會創建一個單獨的線程來執行這個任務,這個時候invoke會被調用。那們我們也就明白為什么會用線程所在的線程組來當做Map的關鍵字啦。這樣以后我們就可以通過調用者所在線程組得到器log輸出設備。這樣就區別了每個線程的log。
- 前言
- (1)-windows下cts配置
- (2)-cts調試環境的搭建
- (3)-基礎庫tradefederation配置
- (4)-任務的添加
- (5)-9大組件配置
- (6)-任務的執行
- (7)-任務執行的調度室
- (8)-IBuildProvider
- (9)-IDeviceRecovery
- (10)-TestDeviceOptions
- (11)-ICommandOptions
- (12)-ITargetPreparer
- (13)-任務執行過程
- (14)-任務執行過程
- (15)-任務執行完
- (16)-logcat信息收集系統
- (17)-fastboot狀態監聽器
- (18)-設備恢復
- (19)-設備狀態的分類以及恢復模式的分類
- (20)-cts自身log系統
- (21)-測試結果收集系統
- (22)-自動檢測設備
- (23)-設備分類
- (24)-case的組織