Provides log sinks for rotating logs. Intended for use on mobile devices to record call logs.

BUG=4838

Review URL: https://codereview.webrtc.org/1230823009

Cr-Commit-Position: refs/heads/master@{#9615}
This commit is contained in:
tkchin 2015-07-22 12:12:17 -07:00 committed by Commit bot
parent f24b2bc48c
commit 934119111e
8 changed files with 992 additions and 0 deletions

View File

@ -224,6 +224,8 @@ static_library("rtc_base") {
"cryptstring.h",
"diskcache.cc",
"diskcache.h",
"filerotatingstream.cc",
"filerotatingstream.h",
"fileutils.cc",
"fileutils.h",
"firewallsocketserver.cc",
@ -368,6 +370,8 @@ static_library("rtc_base") {
"httpserver.h",
"json.cc",
"json.h",
"logsinks.cc",
"logsinks.h",
"mathutils.h",
"multipart.cc",
"multipart.h",

View File

@ -152,6 +152,8 @@
'diskcache.h',
'diskcache_win32.cc',
'diskcache_win32.h',
'filerotatingstream.cc',
'filerotatingstream.h',
'fileutils.cc',
'fileutils.h',
'fileutils_mock.h',
@ -190,6 +192,8 @@
'linuxfdwalk.c',
'linuxfdwalk.h',
'linked_ptr.h',
'logsinks.cc',
'logsinks.h',
'macasyncsocket.cc',
'macasyncsocket.h',
'maccocoasocketserver.h',
@ -401,6 +405,8 @@
'x11windowpicker.h',
'logging.cc',
'logging.h',
'logsinks.cc',
'logsinks.h',
'macasyncsocket.cc',
'macasyncsocket.h',
'maccocoasocketserver.h',

View File

@ -64,6 +64,7 @@
'event_tracer_unittest.cc',
'event_unittest.cc',
'exp_filter_unittest.cc',
'filerotatingstream_unittest.cc',
'fileutils_unittest.cc',
'helpers_unittest.cc',
'httpbase_unittest.cc',

View File

@ -0,0 +1,380 @@
/*
* Copyright 2015 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/base/filerotatingstream.h"
#include <algorithm>
#include <iostream>
#include <string>
#include "webrtc/base/checks.h"
#include "webrtc/base/fileutils.h"
#include "webrtc/base/pathutils.h"
// Note: We use std::cerr for logging in the write paths of this stream to avoid
// infinite loops when logging.
namespace rtc {
FileRotatingStream::FileRotatingStream(const std::string& dir_path,
const std::string& file_prefix)
: FileRotatingStream(dir_path, file_prefix, 0, 0, kRead) {
}
FileRotatingStream::FileRotatingStream(const std::string& dir_path,
const std::string& file_prefix,
size_t max_file_size,
size_t num_files)
: FileRotatingStream(dir_path,
file_prefix,
max_file_size,
num_files,
kWrite) {
DCHECK_GT(max_file_size, 0u);
DCHECK_GT(num_files, 1u);
}
FileRotatingStream::FileRotatingStream(const std::string& dir_path,
const std::string& file_prefix,
size_t max_file_size,
size_t num_files,
Mode mode)
: dir_path_(dir_path),
file_prefix_(file_prefix),
mode_(mode),
file_stream_(nullptr),
max_file_size_(max_file_size),
current_file_index_(0),
rotation_index_(0),
current_bytes_written_(0),
disable_buffering_(false) {
DCHECK(Filesystem::IsFolder(dir_path));
switch (mode) {
case kWrite: {
file_names_.clear();
for (size_t i = 0; i < num_files; ++i) {
file_names_.push_back(GetFilePath(i, num_files));
}
rotation_index_ = num_files - 1;
break;
}
case kRead: {
file_names_ = GetFilesWithPrefix();
std::sort(file_names_.begin(), file_names_.end());
if (file_names_.size() > 0) {
// |file_names_| is sorted newest first, so read from the end.
current_file_index_ = file_names_.size() - 1;
}
break;
}
}
}
FileRotatingStream::~FileRotatingStream() {
}
StreamState FileRotatingStream::GetState() const {
if (mode_ == kRead && current_file_index_ < file_names_.size()) {
return SS_OPEN;
}
if (!file_stream_) {
return SS_CLOSED;
}
return file_stream_->GetState();
}
StreamResult FileRotatingStream::Read(void* buffer,
size_t buffer_len,
size_t* read,
int* error) {
DCHECK(buffer);
if (mode_ != kRead) {
return SR_EOS;
}
if (current_file_index_ >= file_names_.size()) {
return SR_EOS;
}
// We will have no file stream initially, and when we are finished with the
// previous file.
if (!file_stream_) {
if (!OpenCurrentFile()) {
return SR_ERROR;
}
}
int local_error = 0;
if (!error) {
error = &local_error;
}
StreamResult result = file_stream_->Read(buffer, buffer_len, read, error);
if (result == SR_EOS || result == SR_ERROR) {
if (result == SR_ERROR) {
LOG(LS_ERROR) << "Failed to read from: "
<< file_names_[current_file_index_] << "Error: " << error;
}
// Reached the end of the file, read next file. If there is an error return
// the error status but allow for a next read by reading next file.
CloseCurrentFile();
if (current_file_index_ == 0) {
// Just finished reading the last file, signal EOS by setting index.
current_file_index_ = file_names_.size();
} else {
--current_file_index_;
}
if (read) {
*read = 0;
}
return result == SR_EOS ? SR_SUCCESS : result;
} else if (result == SR_SUCCESS) {
// Succeeded, continue reading from this file.
return SR_SUCCESS;
} else {
RTC_NOTREACHED();
}
return result;
}
StreamResult FileRotatingStream::Write(const void* data,
size_t data_len,
size_t* written,
int* error) {
if (mode_ != kWrite) {
return SR_EOS;
}
if (!file_stream_) {
std::cerr << "Open() must be called before Write." << std::endl;
return SR_ERROR;
}
// Write as much as will fit in to the current file.
DCHECK_LT(current_bytes_written_, max_file_size_);
size_t remaining_bytes = max_file_size_ - current_bytes_written_;
size_t write_length = std::min(data_len, remaining_bytes);
size_t local_written = 0;
if (!written) {
written = &local_written;
}
StreamResult result = file_stream_->Write(data, write_length, written, error);
current_bytes_written_ += *written;
// If we're done with this file, rotate it out.
if (current_bytes_written_ >= max_file_size_) {
DCHECK_EQ(current_bytes_written_, max_file_size_);
RotateFiles();
}
return result;
}
bool FileRotatingStream::Flush() {
if (!file_stream_) {
return false;
}
return file_stream_->Flush();
}
void FileRotatingStream::Close() {
CloseCurrentFile();
}
bool FileRotatingStream::Open() {
switch (mode_) {
case kRead:
// Defer opening to when we first read since we want to return read error
// if we fail to open next file.
return true;
case kWrite: {
// Delete existing files when opening for write.
std::vector<std::string> matching_files = GetFilesWithPrefix();
for (auto matching_file : matching_files) {
if (!Filesystem::DeleteFile(matching_file)) {
std::cerr << "Failed to delete: " << matching_file << std::endl;
}
}
return OpenCurrentFile();
}
}
return false;
}
bool FileRotatingStream::DisableBuffering() {
disable_buffering_ = true;
if (!file_stream_) {
std::cerr << "Open() must be called before DisableBuffering()."
<< std::endl;
return false;
}
return file_stream_->DisableBuffering();
}
std::string FileRotatingStream::GetFilePath(size_t index) const {
DCHECK_LT(index, file_names_.size());
return file_names_[index];
}
bool FileRotatingStream::OpenCurrentFile() {
CloseCurrentFile();
// Opens the appropriate file in the appropriate mode.
DCHECK_LT(current_file_index_, file_names_.size());
std::string file_path = file_names_[current_file_index_];
file_stream_.reset(new FileStream());
const char* mode = nullptr;
switch (mode_) {
case kWrite:
mode = "w+";
// We should always we writing to the zero-th file.
DCHECK_EQ(current_file_index_, 0u);
break;
case kRead:
mode = "r";
break;
}
int error = 0;
if (!file_stream_->Open(file_path, mode, &error)) {
std::cerr << "Failed to open: " << file_path << "Error: " << error
<< std::endl;
file_stream_.reset();
return false;
}
if (disable_buffering_) {
file_stream_->DisableBuffering();
}
return true;
}
void FileRotatingStream::CloseCurrentFile() {
if (!file_stream_) {
return;
}
current_bytes_written_ = 0;
file_stream_.reset();
}
void FileRotatingStream::RotateFiles() {
DCHECK_EQ(mode_, kWrite);
CloseCurrentFile();
// Rotates the files by deleting the file at |rotation_index_|, which is the
// oldest file and then renaming the newer files to have an incremented index.
// See header file comments for example.
DCHECK_LE(rotation_index_, file_names_.size());
std::string file_to_delete = file_names_[rotation_index_];
if (Filesystem::IsFile(file_to_delete)) {
if (!Filesystem::DeleteFile(file_to_delete)) {
std::cerr << "Failed to delete: " << file_to_delete << std::endl;
}
}
for (auto i = rotation_index_; i > 0; --i) {
std::string rotated_name = file_names_[i];
std::string unrotated_name = file_names_[i - 1];
if (Filesystem::IsFile(unrotated_name)) {
if (!Filesystem::MoveFile(unrotated_name, rotated_name)) {
std::cerr << "Failed to move: " << unrotated_name << " to "
<< rotated_name << std::endl;
}
}
}
// Create a new file for 0th index.
OpenCurrentFile();
OnRotation();
}
std::vector<std::string> FileRotatingStream::GetFilesWithPrefix() const {
std::vector<std::string> files;
// Iterate over the files in the directory.
DirectoryIterator it;
Pathname dir_path;
dir_path.SetFolder(dir_path_);
if (!it.Iterate(dir_path)) {
return files;
}
do {
std::string current_name = it.Name();
if (current_name.size() && !it.IsDirectory() &&
current_name.compare(0, file_prefix_.size(), file_prefix_) == 0) {
Pathname path(dir_path_, current_name);
files.push_back(path.pathname());
}
} while (it.Next());
return files;
}
std::string FileRotatingStream::GetFilePath(size_t index,
size_t num_files) const {
DCHECK_LT(index, num_files);
std::ostringstream file_name;
// The format will be "_%<num_digits>zu". We want to zero pad the index so
// that it will sort nicely.
size_t max_digits = ((num_files - 1) / 10) + 1;
size_t num_digits = (index / 10) + 1;
DCHECK_LE(num_digits, max_digits);
size_t padding = max_digits - num_digits;
file_name << file_prefix_ << "_";
for (size_t i = 0; i < padding; ++i) {
file_name << "0";
}
file_name << index;
Pathname file_path(dir_path_, file_name.str());
return file_path.pathname();
}
CallSessionFileRotatingStream::CallSessionFileRotatingStream(
const std::string& dir_path)
: FileRotatingStream(dir_path, kLogPrefix),
max_total_log_size_(0),
num_rotations_(0) {
}
CallSessionFileRotatingStream::CallSessionFileRotatingStream(
const std::string& dir_path,
size_t max_total_log_size)
: FileRotatingStream(dir_path,
kLogPrefix,
max_total_log_size / 2,
GetNumRotatingLogFiles(max_total_log_size) + 1),
max_total_log_size_(max_total_log_size),
num_rotations_(0) {
DCHECK_GE(max_total_log_size, 4u);
}
const char* CallSessionFileRotatingStream::kLogPrefix = "webrtc_log";
const size_t CallSessionFileRotatingStream::kRotatingLogFileDefaultSize =
1024 * 1024;
void CallSessionFileRotatingStream::OnRotation() {
++num_rotations_;
if (num_rotations_ == 1) {
// On the first rotation adjust the max file size so subsequent files after
// the first are smaller.
SetMaxFileSize(GetRotatingLogSize(max_total_log_size_));
} else if (num_rotations_ == (GetNumFiles() - 1)) {
// On the next rotation the very first file is going to be deleted. Change
// the rotation index so this doesn't happen.
SetRotationIndex(GetRotationIndex() - 1);
}
}
size_t CallSessionFileRotatingStream::GetRotatingLogSize(
size_t max_total_log_size) {
size_t num_rotating_log_files = GetNumRotatingLogFiles(max_total_log_size);
size_t rotating_log_size = num_rotating_log_files > 2
? kRotatingLogFileDefaultSize
: max_total_log_size / 4;
return rotating_log_size;
}
size_t CallSessionFileRotatingStream::GetNumRotatingLogFiles(
size_t max_total_log_size) {
// At minimum have two rotating files. Otherwise split the available log size
// evenly across 1MB files.
return std::max((size_t)2,
(max_total_log_size / 2) / kRotatingLogFileDefaultSize);
}
} // namespace rtc

View File

@ -0,0 +1,170 @@
/*
* Copyright 2015 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef WEBRTC_BASE_FILEROTATINGSTREAM_H_
#define WEBRTC_BASE_FILEROTATINGSTREAM_H_
#include <string>
#include <vector>
#include "webrtc/base/constructormagic.h"
#include "webrtc/base/stream.h"
namespace rtc {
// FileRotatingStream writes to a file in the directory specified in the
// constructor. It rotates the files once the current file is full. The
// individual file size and the number of files used is configurable in the
// constructor. Open() must be called before using this stream.
class FileRotatingStream : public StreamInterface {
public:
// Use this constructor for reading a directory previously written to with
// this stream.
FileRotatingStream(const std::string& dir_path,
const std::string& file_prefix);
// Use this constructor for writing to a directory. Files in the directory
// matching the prefix will be deleted on open.
FileRotatingStream(const std::string& dir_path,
const std::string& file_prefix,
size_t max_file_size,
size_t num_files);
~FileRotatingStream() override;
// StreamInterface methods.
StreamState GetState() const override;
StreamResult Read(void* buffer,
size_t buffer_len,
size_t* read,
int* error) override;
StreamResult Write(const void* data,
size_t data_len,
size_t* written,
int* error) override;
bool Flush() override;
void Close() override;
// Opens the appropriate file(s). Call this before using the stream.
bool Open();
// Disabling buffering causes writes to block until disk is updated. This is
// enabled by default for performance.
bool DisableBuffering();
// Returns the path used for the i-th newest file, where the 0th file is the
// newest file. The file may or may not exist, this is just used for
// formatting. Index must be less than GetNumFiles().
std::string GetFilePath(size_t index) const;
// Returns the number of files that will used by this stream.
size_t GetNumFiles() { return file_names_.size(); }
protected:
size_t GetMaxFileSize() const { return max_file_size_; }
void SetMaxFileSize(size_t size) { max_file_size_ = size; }
size_t GetRotationIndex() const { return rotation_index_; }
void SetRotationIndex(size_t index) { rotation_index_ = index; }
virtual void OnRotation() {}
private:
enum Mode { kRead, kWrite };
FileRotatingStream(const std::string& dir_path,
const std::string& file_prefix,
size_t max_file_size,
size_t num_files,
Mode mode);
bool OpenCurrentFile();
void CloseCurrentFile();
// Rotates the files by creating a new current file, renaming the
// existing files, and deleting the oldest one. e.g.
// file_0 -> file_1
// file_1 -> file_2
// file_2 -> delete
// create new file_0
void RotateFiles();
// Returns a list of file names in the directory beginning with the prefix.
std::vector<std::string> GetFilesWithPrefix() const;
// Private version of GetFilePath.
std::string GetFilePath(size_t index, size_t num_files) const;
const std::string dir_path_;
const std::string file_prefix_;
const Mode mode_;
// FileStream is used to write to the current file.
scoped_ptr<FileStream> file_stream_;
// Convenience storage for file names so we don't generate them over and over.
std::vector<std::string> file_names_;
size_t max_file_size_;
size_t current_file_index_;
// The rotation index indicates the index of the file that will be
// deleted first on rotation. Indices lower than this index will be rotated.
size_t rotation_index_;
// Number of bytes written to current file. We need this because with
// buffering the file size read from disk might not be accurate.
size_t current_bytes_written_;
bool disable_buffering_;
DISALLOW_COPY_AND_ASSIGN(FileRotatingStream);
};
// CallSessionFileRotatingStream is meant to be used in situations where we will
// have limited disk space. Its purpose is to read and write logs up to a
// maximum size. Once the maximum size is exceeded, logs from the middle are
// deleted whereas logs from the beginning and end are preserved. The reason for
// this is because we anticipate that in WebRTC the beginning and end of the
// logs are most useful for call diagnostics.
//
// This implementation simply writes to a single file until
// |max_total_log_size| / 2 bytes are written to it, and subsequently writes to
// a set of rotating files. We do this by inheriting FileRotatingStream and
// setting the appropriate internal variables so that we don't delete the last
// (earliest) file on rotate, and that that file's size is bigger.
//
// Open() must be called before using this stream.
class CallSessionFileRotatingStream : public FileRotatingStream {
public:
// Use this constructor for reading a directory previously written to with
// this stream.
explicit CallSessionFileRotatingStream(const std::string& dir_path);
// Use this constructor for writing to a directory. Files in the directory
// matching what's used by the stream will be deleted. |max_total_log_size|
// must be at least 4.
CallSessionFileRotatingStream(const std::string& dir_path,
size_t max_total_log_size);
~CallSessionFileRotatingStream() override {}
protected:
void OnRotation() override;
private:
static size_t GetRotatingLogSize(size_t max_total_log_size);
static size_t GetNumRotatingLogFiles(size_t max_total_log_size);
static const char* kLogPrefix;
static const size_t kRotatingLogFileDefaultSize;
const size_t max_total_log_size_;
size_t num_rotations_;
DISALLOW_COPY_AND_ASSIGN(CallSessionFileRotatingStream);
};
} // namespace rtc
#endif // WEBRTC_BASE_FILEROTATINGSTREAM_H_

View File

@ -0,0 +1,308 @@
/*
* Copyright 2015 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/base/arraysize.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/filerotatingstream.h"
#include "webrtc/base/fileutils.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/pathutils.h"
namespace rtc {
class FileRotatingStreamTest : public ::testing::Test {
protected:
static const char* kFilePrefix;
static const size_t kMaxFileSize;
void Init(const std::string& dir_name,
const std::string& file_prefix,
size_t max_file_size,
size_t num_log_files) {
Pathname test_path;
ASSERT_TRUE(Filesystem::GetAppTempFolder(&test_path));
// Append per-test output path in order to run within gtest parallel.
test_path.AppendFolder(dir_name);
ASSERT_TRUE(Filesystem::CreateFolder(test_path));
dir_path_ = test_path.pathname();
ASSERT_TRUE(dir_path_.size());
stream_.reset(new FileRotatingStream(dir_path_, file_prefix, max_file_size,
num_log_files));
}
void TearDown() override {
stream_.reset();
if (dir_path_.size() && Filesystem::IsFolder(dir_path_) &&
Filesystem::IsTemporaryPath(dir_path_)) {
Filesystem::DeleteFolderAndContents(dir_path_);
}
}
// Writes the data to the stream and flushes it.
void WriteAndFlush(const void* data, const size_t data_len) {
EXPECT_EQ(SR_SUCCESS, stream_->WriteAll(data, data_len, nullptr, nullptr));
EXPECT_TRUE(stream_->Flush());
}
// Checks that the stream reads in the expected contents and then returns an
// end of stream result.
void VerifyStreamRead(const char* expected_contents,
const size_t expected_length,
const std::string& dir_path,
const char* file_prefix) {
scoped_ptr<FileRotatingStream> stream;
stream.reset(new FileRotatingStream(dir_path, file_prefix));
ASSERT_TRUE(stream->Open());
scoped_ptr<uint8_t[]> buffer(new uint8_t[expected_length]);
EXPECT_EQ(SR_SUCCESS,
stream->ReadAll(buffer.get(), expected_length, nullptr, nullptr));
EXPECT_EQ(0, memcmp(expected_contents, buffer.get(), expected_length));
EXPECT_EQ(SR_EOS, stream->ReadAll(buffer.get(), 1, nullptr, nullptr));
}
void VerifyFileContents(const char* expected_contents,
const size_t expected_length,
const std::string& file_path) {
scoped_ptr<uint8_t[]> buffer(new uint8_t[expected_length]);
scoped_ptr<FileStream> stream(Filesystem::OpenFile(file_path, "r"));
EXPECT_TRUE(stream);
if (!stream) {
return;
}
EXPECT_EQ(rtc::SR_SUCCESS,
stream->ReadAll(buffer.get(), expected_length, nullptr, nullptr));
EXPECT_EQ(0, memcmp(expected_contents, buffer.get(), expected_length));
size_t file_size = 0;
EXPECT_TRUE(stream->GetSize(&file_size));
EXPECT_EQ(file_size, expected_length);
}
scoped_ptr<FileRotatingStream> stream_;
std::string dir_path_;
};
const char* FileRotatingStreamTest::kFilePrefix = "FileRotatingStreamTest";
const size_t FileRotatingStreamTest::kMaxFileSize = 2;
// Tests that stream state is correct before and after Open / Close.
TEST_F(FileRotatingStreamTest, State) {
Init("FileRotatingStreamTestState", kFilePrefix, kMaxFileSize, 3);
EXPECT_EQ(SS_CLOSED, stream_->GetState());
ASSERT_TRUE(stream_->Open());
EXPECT_EQ(SS_OPEN, stream_->GetState());
stream_->Close();
EXPECT_EQ(SS_CLOSED, stream_->GetState());
}
// Tests that nothing is written to file when data of length zero is written.
TEST_F(FileRotatingStreamTest, EmptyWrite) {
Init("FileRotatingStreamTestEmptyWrite", kFilePrefix, kMaxFileSize, 3);
ASSERT_TRUE(stream_->Open());
WriteAndFlush("a", 0);
std::string logfile_path = stream_->GetFilePath(0);
scoped_ptr<FileStream> stream(Filesystem::OpenFile(logfile_path, "r"));
size_t file_size = 0;
EXPECT_TRUE(stream->GetSize(&file_size));
EXPECT_EQ(0u, file_size);
}
// Tests that a write operation followed by a read returns the expected data
// and writes to the expected files.
TEST_F(FileRotatingStreamTest, WriteAndRead) {
Init("FileRotatingStreamTestWriteAndRead", kFilePrefix, kMaxFileSize, 3);
ASSERT_TRUE(stream_->Open());
// The test is set up to create three log files of length 2. Write and check
// contents.
std::string messages[3] = {"aa", "bb", "cc"};
for (size_t i = 0; i < arraysize(messages); ++i) {
const std::string& message = messages[i];
WriteAndFlush(message.c_str(), message.size());
// Since the max log size is 2, we will be causing rotation. Read from the
// next file.
VerifyFileContents(message.c_str(), message.size(),
stream_->GetFilePath(1));
}
// Check that exactly three files exist.
for (size_t i = 0; i < arraysize(messages); ++i) {
EXPECT_TRUE(Filesystem::IsFile(stream_->GetFilePath(i)));
}
std::string message("d");
WriteAndFlush(message.c_str(), message.size());
for (size_t i = 0; i < arraysize(messages); ++i) {
EXPECT_TRUE(Filesystem::IsFile(stream_->GetFilePath(i)));
}
// TODO(tkchin): Maybe check all the files in the dir.
// Reopen for read.
std::string expected_contents("bbccd");
VerifyStreamRead(expected_contents.c_str(), expected_contents.size(),
dir_path_, kFilePrefix);
}
// Tests that writing data greater than the total capacity of the files
// overwrites the files correctly and is read correctly after.
TEST_F(FileRotatingStreamTest, WriteOverflowAndRead) {
Init("FileRotatingStreamTestWriteOverflowAndRead", kFilePrefix, kMaxFileSize,
3);
ASSERT_TRUE(stream_->Open());
// This should cause overflow across all three files, such that the first file
// we wrote to also gets overwritten.
std::string message("foobarbaz");
WriteAndFlush(message.c_str(), message.size());
std::string expected_file_contents("z");
VerifyFileContents(expected_file_contents.c_str(),
expected_file_contents.size(), stream_->GetFilePath(0));
std::string expected_stream_contents("arbaz");
VerifyStreamRead(expected_stream_contents.c_str(),
expected_stream_contents.size(), dir_path_, kFilePrefix);
}
// Tests that the returned file paths have the right folder and prefix.
TEST_F(FileRotatingStreamTest, GetFilePath) {
Init("FileRotatingStreamTestGetFilePath", kFilePrefix, kMaxFileSize, 20);
for (auto i = 0; i < 20; ++i) {
Pathname path(stream_->GetFilePath(i));
EXPECT_EQ(0, path.folder().compare(dir_path_));
EXPECT_EQ(0, path.filename().compare(0, strlen(kFilePrefix), kFilePrefix));
}
}
class CallSessionFileRotatingStreamTest : public ::testing::Test {
protected:
void Init(const std::string& dir_name, size_t max_total_log_size) {
Pathname test_path;
ASSERT_TRUE(Filesystem::GetAppTempFolder(&test_path));
// Append per-test output path in order to run within gtest parallel.
test_path.AppendFolder(dir_name);
ASSERT_TRUE(Filesystem::CreateFolder(test_path));
dir_path_ = test_path.pathname();
ASSERT_TRUE(dir_path_.size());
stream_.reset(
new CallSessionFileRotatingStream(dir_path_, max_total_log_size));
}
virtual void TearDown() {
stream_.reset();
if (dir_path_.size() && Filesystem::IsFolder(dir_path_) &&
Filesystem::IsTemporaryPath(dir_path_)) {
Filesystem::DeleteFolderAndContents(dir_path_);
}
}
// Writes the data to the stream and flushes it.
void WriteAndFlush(const void* data, const size_t data_len) {
EXPECT_EQ(SR_SUCCESS, stream_->WriteAll(data, data_len, nullptr, nullptr));
EXPECT_TRUE(stream_->Flush());
}
// Checks that the stream reads in the expected contents and then returns an
// end of stream result.
void VerifyStreamRead(const char* expected_contents,
const size_t expected_length,
const std::string& dir_path) {
scoped_ptr<CallSessionFileRotatingStream> stream(
new CallSessionFileRotatingStream(dir_path));
ASSERT_TRUE(stream->Open());
scoped_ptr<uint8_t[]> buffer(new uint8_t[expected_length]);
EXPECT_EQ(SR_SUCCESS,
stream->ReadAll(buffer.get(), expected_length, nullptr, nullptr));
EXPECT_EQ(0, memcmp(expected_contents, buffer.get(), expected_length));
EXPECT_EQ(SR_EOS, stream->ReadAll(buffer.get(), 1, nullptr, nullptr));
}
scoped_ptr<CallSessionFileRotatingStream> stream_;
std::string dir_path_;
};
// Tests that writing and reading to a stream with the smallest possible
// capacity works.
TEST_F(CallSessionFileRotatingStreamTest, WriteAndReadSmallest) {
Init("CallSessionFileRotatingStreamTestWriteAndReadSmallest", 4);
ASSERT_TRUE(stream_->Open());
std::string message("abcde");
WriteAndFlush(message.c_str(), message.size());
std::string expected_contents("abe");
VerifyStreamRead(expected_contents.c_str(), expected_contents.size(),
dir_path_);
}
// Tests that writing and reading to a stream with capacity lesser than 4MB
// behaves correctly.
TEST_F(CallSessionFileRotatingStreamTest, WriteAndReadSmall) {
Init("CallSessionFileRotatingStreamTestWriteAndReadSmall", 8);
ASSERT_TRUE(stream_->Open());
std::string message("123456789");
WriteAndFlush(message.c_str(), message.size());
std::string expected_contents("1234789");
VerifyStreamRead(expected_contents.c_str(), expected_contents.size(),
dir_path_);
}
// Tests that writing and reading to a stream with capacity greater than 4MB
// behaves correctly.
TEST_F(CallSessionFileRotatingStreamTest, WriteAndReadLarge) {
Init("CallSessionFileRotatingStreamTestWriteAndReadLarge", 6 * 1024 * 1024);
ASSERT_TRUE(stream_->Open());
const size_t buffer_size = 1024 * 1024;
scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
for (int i = 0; i < 8; i++) {
memset(buffer.get(), i, buffer_size);
EXPECT_EQ(SR_SUCCESS,
stream_->WriteAll(buffer.get(), buffer_size, nullptr, nullptr));
}
stream_.reset(new CallSessionFileRotatingStream(dir_path_));
ASSERT_TRUE(stream_->Open());
scoped_ptr<uint8_t[]> expected_buffer(new uint8_t[buffer_size]);
int expected_vals[] = {0, 1, 2, 6, 7};
for (size_t i = 0; i < arraysize(expected_vals); ++i) {
memset(expected_buffer.get(), expected_vals[i], buffer_size);
EXPECT_EQ(SR_SUCCESS,
stream_->ReadAll(buffer.get(), buffer_size, nullptr, nullptr));
EXPECT_EQ(0, memcmp(buffer.get(), expected_buffer.get(), buffer_size));
}
EXPECT_EQ(SR_EOS, stream_->ReadAll(buffer.get(), 1, nullptr, nullptr));
}
// Tests that writing and reading to a stream where only the first file is
// written to behaves correctly.
TEST_F(CallSessionFileRotatingStreamTest, WriteAndReadFirstHalf) {
Init("CallSessionFileRotatingStreamTestWriteAndReadFirstHalf",
6 * 1024 * 1024);
ASSERT_TRUE(stream_->Open());
const size_t buffer_size = 1024 * 1024;
scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
for (int i = 0; i < 2; i++) {
memset(buffer.get(), i, buffer_size);
EXPECT_EQ(SR_SUCCESS,
stream_->WriteAll(buffer.get(), buffer_size, nullptr, nullptr));
}
stream_.reset(new CallSessionFileRotatingStream(dir_path_));
ASSERT_TRUE(stream_->Open());
scoped_ptr<uint8_t[]> expected_buffer(new uint8_t[buffer_size]);
int expected_vals[] = {0, 1};
for (size_t i = 0; i < arraysize(expected_vals); ++i) {
memset(expected_buffer.get(), expected_vals[i], buffer_size);
EXPECT_EQ(SR_SUCCESS,
stream_->ReadAll(buffer.get(), buffer_size, nullptr, nullptr));
EXPECT_EQ(0, memcmp(buffer.get(), expected_buffer.get(), buffer_size));
}
EXPECT_EQ(SR_EOS, stream_->ReadAll(buffer.get(), 1, nullptr, nullptr));
}
} // namespace rtc

56
webrtc/base/logsinks.cc Normal file
View File

@ -0,0 +1,56 @@
/*
* Copyright 2015 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/base/logsinks.h"
#include <string>
namespace rtc {
FileRotatingLogSink::FileRotatingLogSink(const std::string& log_dir_path,
const std::string& log_prefix,
size_t max_log_size,
size_t num_log_files)
: FileRotatingLogSink(new FileRotatingStream(log_dir_path,
log_prefix,
max_log_size,
num_log_files)) {
}
FileRotatingLogSink::FileRotatingLogSink(FileRotatingStream* stream)
: stream_(stream) {
}
FileRotatingLogSink::~FileRotatingLogSink() {
}
void FileRotatingLogSink::OnLogMessage(const std::string& message) {
if (!stream_ || stream_->GetState() != SS_OPEN) {
LOG(LS_WARNING) << "Init() must be called before adding this sink.";
return;
}
stream_->WriteAll(message.c_str(), message.size(), nullptr, nullptr);
}
bool FileRotatingLogSink::Init() {
return stream_->Open();
}
CallSessionFileRotatingLogSink::CallSessionFileRotatingLogSink(
const std::string& log_dir_path,
size_t max_total_log_size)
: FileRotatingLogSink(
new CallSessionFileRotatingStream(log_dir_path, max_total_log_size)) {
}
CallSessionFileRotatingLogSink::~CallSessionFileRotatingLogSink() {
}
} // namespace rtc

67
webrtc/base/logsinks.h Normal file
View File

@ -0,0 +1,67 @@
/*
* Copyright 2015 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef WEBRTC_BASE_FILE_ROTATING_LOG_SINK_H_
#define WEBRTC_BASE_FILE_ROTATING_LOG_SINK_H_
#include <string>
#include "webrtc/base/constructormagic.h"
#include "webrtc/base/filerotatingstream.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/scoped_ptr.h"
namespace rtc {
// Log sink that uses a FileRotatingStream to write to disk.
// Init() must be called before adding this sink.
class FileRotatingLogSink : public LogSink {
public:
// |num_log_files| must be greater than 1 and |max_log_size| must be greater
// than 0.
FileRotatingLogSink(const std::string& log_dir_path,
const std::string& log_prefix,
size_t max_log_size,
size_t num_log_files);
~FileRotatingLogSink() override;
// Writes the message to the current file. It will spill over to the next
// file if needed.
void OnLogMessage(const std::string& message) override;
// Deletes any existing files in the directory and creates a new log file.
virtual bool Init();
protected:
explicit FileRotatingLogSink(FileRotatingStream* stream);
private:
scoped_ptr<FileRotatingStream> stream_;
DISALLOW_COPY_AND_ASSIGN(FileRotatingLogSink);
};
// Log sink that uses a CallSessionFileRotatingStream to write to disk.
// Init() must be called before adding this sink.
class CallSessionFileRotatingLogSink : public FileRotatingLogSink {
public:
CallSessionFileRotatingLogSink(const std::string& log_dir_path,
size_t max_total_log_size);
~CallSessionFileRotatingLogSink() override;
private:
scoped_ptr<CallSessionFileRotatingStream> stream_;
DISALLOW_COPY_AND_ASSIGN(CallSessionFileRotatingLogSink);
};
} // namespace rtc
#endif // WEBRTC_BASE_FILE_ROTATING_LOG_SINK_H_