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:
parent
f24b2bc48c
commit
934119111e
@ -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",
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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',
|
||||
|
||||
380
webrtc/base/filerotatingstream.cc
Normal file
380
webrtc/base/filerotatingstream.cc
Normal 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
|
||||
170
webrtc/base/filerotatingstream.h
Normal file
170
webrtc/base/filerotatingstream.h
Normal 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_
|
||||
308
webrtc/base/filerotatingstream_unittest.cc
Normal file
308
webrtc/base/filerotatingstream_unittest.cc
Normal 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
56
webrtc/base/logsinks.cc
Normal 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
67
webrtc/base/logsinks.h
Normal 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_
|
||||
Loading…
x
Reference in New Issue
Block a user