普通文本  |  243行  |  9.19 KB

// Copyright 2015 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.

#include "webservd/log_manager.h"

#include <arpa/inet.h>
#include <cinttypes>
#include <netinet/in.h>
#include <set>

#include <base/bind.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/lazy_instance.h>
#include <base/strings/stringprintf.h>
#include <brillo/strings/string_utils.h>

namespace webservd {

namespace {

// Singleton instance of LogManager class.
base::LazyInstance<LogManager> g_log_manager = LAZY_INSTANCE_INITIALIZER;

// The number of files to keep in the log directory. Since there is one log
// file per day of logging, this is essentially how many days' worth of logs
// to keep. This also controls the total maximum size of the log data, which
// is (kLogFilesToKeep * kMaxLogFileSize).
const size_t kLogFilesToKeep = 7;

// Maximum log file size.
const int64_t kMaxLogFileSize = 1024 * 1024;  // 1 MB

// Obtain an IP address as a human-readable string for logging.
std::string GetIPAddress(const sockaddr* addr) {
  static_assert(INET6_ADDRSTRLEN > INET_ADDRSTRLEN, "Unexpected IP addr len.");
  char buf[INET6_ADDRSTRLEN] = "-";
  if (!addr)
    return buf;

  switch (addr->sa_family) {
    case AF_INET: {
      auto addr_in = reinterpret_cast<const sockaddr_in*>(addr);
      if (!inet_ntop(AF_INET, &addr_in->sin_addr, buf, sizeof(buf)))
        PLOG(ERROR) << "Unable to get IP address string (IPv4)";
    }
    break;

    case AF_INET6: {
      auto addr_in6 = reinterpret_cast<const sockaddr_in6*>(addr);

      // Note that inet_ntop(3) doesn't handle IPv4-mapped IPv6
      // addresses [1] the way you'd expect .. for example, it returns
      // "::ffff:172.22.72.163" instead of the more traditional IPv4
      // notation "172.22.72.163". Fortunately, this is pretty easy to
      // fix ourselves.
      //
      // [1] : see RFC 4291, section 2.5.5.2 for what that means
      //       http://tools.ietf.org/html/rfc4291#section-2.5.5
      //
      auto dwords = reinterpret_cast<const uint32_t*>(&addr_in6->sin6_addr);
      if (dwords[0] == 0x00000000 && dwords[1] == 0x00000000 &&
          dwords[2] == htonl(0x0000ffff)) {
        auto bytes = reinterpret_cast<const uint8_t*>(&addr_in6->sin6_addr);
        return base::StringPrintf("%d.%d.%d.%d",
                                  bytes[12], bytes[13], bytes[14], bytes[15]);
      } else if (!inet_ntop(AF_INET6, &addr_in6->sin6_addr, buf, sizeof(buf))) {
        PLOG(ERROR) << "Unable to get IP address string (IPv6)";
      }
    }
    break;

    default:
      LOG(ERROR) << "Unsupported address family " << addr->sa_family;
      break;
  }
  return buf;
}

}  // Anonymous namespace

// Logger class to write the log data to a log file.
class FileLogger final : public LogManager::LoggerInterface {
 public:
  FileLogger(const base::FilePath& log_directory, LogManager* log_manager)
      : log_directory_(log_directory), log_manager_{log_manager} {}

  // Write the log entry to today's log file.
  void Log(const base::Time& timestamp, const std::string& entry) override {
    tm time_buf = {};
    char file_name[32] = {};
    // Create the file name in year-month-day format so that string sort would
    // correspond to date sort.
    time_t time = timestamp.ToTimeT();
    strftime(file_name, sizeof(file_name), "%Y-%m-%d.log",
             localtime_r(&time, &time_buf));
    base::FilePath file_path = log_directory_.Append(file_name);
    bool success = false;
    bool exists = base::PathExists(file_path);
    // If the file already exists, check its size. If it is going to be larger
    // then the maximum allowed log size, archive the current log file and
    // create a new, empty one.
    if (exists) {
      int64_t file_size = 0;
      bool got_size = base::GetFileSize(file_path, &file_size);
      if (got_size && (file_size + entry.size() > kMaxLogFileSize))
        exists = !ArchiveLogFile(file_name);
    }

    if (exists) {
      success = base::AppendToFile(file_path, entry.data(), entry.size());
    } else {
      int size = static_cast<int>(entry.size());
      success = (base::WriteFile(file_path, entry.data(), size) == size);
      if (success) {
        // We just created a new file, see if we need to purge old ones.
        log_manager_->PerformLogMaintenance();
      }
    }
    PLOG_IF(ERROR, !success) << "Failed to append a log entry to log file at "
                             << file_path.value();
  }

 private:
  // Renames the log file to a next available suffix-appended archive when
  // the log file size starts to exceed the pre-defined maximum size.
  // The existing log file is renamed by changing the original |file_name| to
  // YYYY-MM-DD-<suffix>.log where suffix is characters 'a', 'b', ...
  // Since '-' comes before '.', "2015-02-25-a.log" will come before
  // "2015-02-25.log" in sort order and the previously-renamed files will be
  // considered "older" than the current one, which is what we need.
  // Returns true if the file has been successfully renamed.
  bool ArchiveLogFile(const std::string& file_name) {
    char suffix = 'a';
    auto pair = brillo::string_utils::SplitAtFirst(file_name, ".");
    // If we try all the suffixes from 'a' to 'z' and still can't find a name,
    // abandon this strategy and keep appending to the current file.
    while (suffix <= 'z') {
      base::FilePath archive_file_path = log_directory_.Append(
          base::StringPrintf("%s-%c.%s", pair.first.c_str(),
                              suffix, pair.second.c_str()));
      if (!base::PathExists(archive_file_path)) {
        base::FilePath file_path = log_directory_.Append(file_name);
        if (base::Move(file_path, archive_file_path)) {
          // Successfully renamed, start a new log file.
          return true;
        } else {
          PLOG(ERROR) << "Failed to rename log file from "
                      << file_path.value() << " to "
                      << archive_file_path.value();
        }
        break;
      }
      suffix++;
    }
    return false;
  }

  base::FilePath log_directory_;
  LogManager* log_manager_{nullptr};
};

void LogManager::Init(const base::FilePath& log_directory) {
  LogManager* inst = GetInstance();
  inst->log_directory_ = log_directory;
  inst->SetLogger(
      std::unique_ptr<LoggerInterface>{new FileLogger{log_directory, inst}});
  inst->PerformLogMaintenance();
}

void LogManager::OnRequestCompleted(const base::Time& timestamp,
                                    const sockaddr* client_addr,
                                    const std::string& method,
                                    const std::string& url,
                                    const std::string& version,
                                    int status_code,
                                    int64_t response_size) {
  std::string ip_address = GetIPAddress(client_addr);
  tm time_buf = {};
  char str_buf[32] = {};
  // Format the date/time as "25/Feb/2015:03:29:12 -0800".
  time_t time = timestamp.ToTimeT();
  strftime(str_buf, sizeof(str_buf), "%d/%b/%Y:%H:%M:%S %z",
           localtime_r(&time, &time_buf));

  // Log file entry for one HTTP request looking like this:
  // 127.0.0.1 - - [25/Feb/2015:03:29:12 -0800] "GET /test HTTP/1.1" 200 2326
  std::string size_string{"-"};
  if (response_size >= 0)
    size_string = std::to_string(response_size);
  std::string log_entry = base::StringPrintf(
      "%s - - [%s] \"%s %s %s\" %d %s\n", ip_address.c_str(), str_buf,
      method.c_str(), url.c_str(), version.c_str(), status_code,
      size_string.c_str());
  GetInstance()->logger_->Log(timestamp, log_entry);
}

void LogManager::SetLogger(std::unique_ptr<LoggerInterface> logger) {
  GetInstance()->logger_ = std::move(logger);
}

LogManager* LogManager::GetInstance() {
  return g_log_manager.Pointer();
}

void LogManager::PerformLogMaintenance() {
  // Get the list of all the log files in the log directory and put them into
  // a set which will sort the files by name (and effectively by the date since
  // we chose the file naming scheme deliberately to guarantee proper sorting
  // order).
  std::set<base::FilePath> log_files;
  base::FileEnumerator enumerator{log_directory_,
                                  false,
                                  base::FileEnumerator::FILES,
                                  "*.log"};
  base::FilePath file = enumerator.Next();
  while (!file.empty()) {
    log_files.insert(file);
    file = enumerator.Next();
  }

  // Now, if we have more files than we want to keep, purge the old files.
  while (log_files.size() > kLogFilesToKeep) {
    auto front_it = log_files.begin();
    PLOG_IF(WARNING, !base::DeleteFile(*front_it, false))
        << "Failed to delete an old log file: " << front_it->value();
    log_files.erase(front_it);
  }
}

}  // namespace webservd