/* * Copyright (C) 2013 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.result; import com.android.tradefed.build.IBuildInfo; import com.android.tradefed.command.FatalHostError; import com.android.tradefed.config.Option; import com.android.tradefed.config.OptionClass; import com.android.tradefed.invoker.IInvocationContext; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.util.FileUtil; import com.android.tradefed.util.StreamUtil; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * Save logs to a file system. */ @OptionClass(alias = "file-system-log-saver") public class FileSystemLogSaver implements ILogSaver { private static final int BUFFER_SIZE = 64 * 1024; @Option(name = "log-file-path", description = "root file system path to store log files.") private File mRootReportDir = new File(System.getProperty("java.io.tmpdir")); @Option(name = "log-file-url", description = "root http url of log files. Assumes files placed in log-file-path are visible via " + "this url.") private String mReportUrl = null; @Option(name = "log-retention-days", description = "the number of days to keep saved log files.") private Integer mLogRetentionDays = null; @Option(name = "compress-files", description = "whether to compress files which are not already compressed") private boolean mCompressFiles = true; private File mLogReportDir = null; /** * A counter to control access to methods which modify this class's directories. Acting as a * non-blocking reentrant lock, this int blocks access to sharded child invocations from * attempting to create or delete directories. */ private int mShardingLock = 0; /** * {@inheritDoc} * *
Also, create a unique file system directory under {@code * report-dir/[branch/]build-id/test-tag/unique_dir} for saving logs. If the creation of the * directory fails, will write logs to a temporary directory on the local file system. */ @Override public void invocationStarted(IInvocationContext context) { // Create log directory on first build info IBuildInfo info = context.getBuildInfos().get(0); synchronized (this) { if (mShardingLock == 0) { mLogReportDir = createLogReportDir(info, mRootReportDir, mLogRetentionDays); } mShardingLock++; } } /** * {@inheritDoc} */ @Override public void invocationEnded(long elapsedTime) { // no clean up needed. synchronized (this) { --mShardingLock; if (mShardingLock < 0) { CLog.w( "Sharding lock exited more times than entered, possible " + "unbalanced invocationStarted/Ended calls"); } } } /** * {@inheritDoc} *
* Will zip and save the log file if {@link LogDataType#isCompressed()} returns false for * {@code dataType} and {@code compressed-files} is set, otherwise, the stream will be saved * uncompressed. *
*/ @Override public LogFile saveLogData(String dataName, LogDataType dataType, InputStream dataStream) throws IOException { if (!mCompressFiles || dataType.isCompressed()) { File log = saveLogDataInternal(dataName, dataType.getFileExt(), dataStream); return new LogFile(log.getAbsolutePath(), getUrl(log), dataType); } BufferedInputStream bufferedDataStream = null; ZipOutputStream outputStream = null; // add underscore to end of data name to make generated name more readable final String saneDataName = sanitizeFilename(dataName); File log = FileUtil.createTempFile(saneDataName + "_", "." + LogDataType.ZIP.getFileExt(), mLogReportDir); boolean setPerms = FileUtil.chmodGroupRWX(log); if (!setPerms) { CLog.w(String.format("Failed to set dir %s to be group accessible.", log)); } try { bufferedDataStream = new BufferedInputStream(dataStream); outputStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(log), BUFFER_SIZE)); outputStream.putNextEntry(new ZipEntry(saneDataName + "." + dataType.getFileExt())); StreamUtil.copyStreams(bufferedDataStream, outputStream); CLog.d("Saved log file %s", log.getAbsolutePath()); return new LogFile(log.getAbsolutePath(), getUrl(log), true, dataType, log.length()); } finally { StreamUtil.close(bufferedDataStream); StreamUtil.close(outputStream); } } /** {@inheritDoc} */ @Override public LogFile saveLogDataRaw(String dataName, LogDataType dataType, InputStream dataStream) throws IOException { File log = saveLogDataInternal(dataName, dataType.getFileExt(), dataStream); return new LogFile(log.getAbsolutePath(), getUrl(log), dataType); } private File saveLogDataInternal(String dataName, String ext, InputStream dataStream) throws IOException { final String saneDataName = sanitizeFilename(dataName); // add underscore to end of data name to make generated name more readable File log = FileUtil.createTempFile(saneDataName + "_", "." + ext, mLogReportDir); boolean setPerms = FileUtil.chmodGroupRWX(log); if (!setPerms) { CLog.w(String.format("Failed to set dir %s to be group accessible.", log)); } FileUtil.writeToFile(dataStream, log); CLog.d("Saved raw log file %s", log.getAbsolutePath()); return log; } /** * {@inheritDoc} */ @Override public LogFile getLogReportDir() { return new LogFile(mLogReportDir.getAbsolutePath(), getUrl(mLogReportDir), LogDataType.DIR); } /** * A helper method to create an invocation directory unique for saving logs. ** Create a unique file system directory with the structure * {@code report-dir/[branch/]build-id/test-tag/unique_dir} for saving logs. If the creation * of the directory fails, will write logs to a temporary directory on the local file system. *
* * @param buildInfo the {@link IBuildInfo} * @param reportDir the {@link File} for the report directory. * @param logRetentionDays how many days logs should be kept for. If {@code null}, then no log * retention file is writen. * @return The directory created. */ private File createLogReportDir(IBuildInfo buildInfo, File reportDir, Integer logRetentionDays) { File logReportDir; // now create unique directory within the buildDir try { File buildDir = createBuildDir(buildInfo, reportDir); logReportDir = FileUtil.createTempDir("inv_", buildDir); } catch (IOException e) { CLog.e("Unable to create unique directory in %s. Attempting to use tmp dir instead", reportDir.getAbsolutePath()); CLog.e(e); // try to create one in a tmp location instead logReportDir = createTempDir(); } boolean setPerms = FileUtil.chmodGroupRWX(logReportDir); if (!setPerms) { CLog.w(String.format("Failed to set dir %s to be group accessible.", logReportDir)); } if (logRetentionDays != null && logRetentionDays > 0) { new RetentionFileSaver().writeRetentionFile(logReportDir, logRetentionDays); } CLog.d("Using log file directory %s", logReportDir.getAbsolutePath()); return logReportDir; } /** * A helper method to get or create a build directory based on the build info of the invocation. ** Create a unique file system directory with the structure * {@code report-dir/[branch/]build-id/test-tag} for saving logs. *
* * @param buildInfo the {@link IBuildInfo} * @param reportDir the {@link File} for the report directory. * @return The directory where invocations for the same build should be saved. * @throws IOException if the directory could not be created because a file with the same name * exists or there are no permissions to write to it. */ private File createBuildDir(IBuildInfo buildInfo, File reportDir) throws IOException { List