• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "webservd/log_manager.h"
16 
17 #include <arpa/inet.h>
18 #include <cinttypes>
19 #include <netinet/in.h>
20 #include <set>
21 
22 #include <base/bind.h>
23 #include <base/files/file_enumerator.h>
24 #include <base/files/file_util.h>
25 #include <base/lazy_instance.h>
26 #include <base/strings/stringprintf.h>
27 #include <brillo/strings/string_utils.h>
28 
29 namespace webservd {
30 
31 namespace {
32 
33 // Singleton instance of LogManager class.
34 base::LazyInstance<LogManager> g_log_manager = LAZY_INSTANCE_INITIALIZER;
35 
36 // The number of files to keep in the log directory. Since there is one log
37 // file per day of logging, this is essentially how many days' worth of logs
38 // to keep. This also controls the total maximum size of the log data, which
39 // is (kLogFilesToKeep * kMaxLogFileSize).
40 const size_t kLogFilesToKeep = 7;
41 
42 // Maximum log file size.
43 const int64_t kMaxLogFileSize = 1024 * 1024;  // 1 MB
44 
45 // Obtain an IP address as a human-readable string for logging.
GetIPAddress(const sockaddr * addr)46 std::string GetIPAddress(const sockaddr* addr) {
47   static_assert(INET6_ADDRSTRLEN > INET_ADDRSTRLEN, "Unexpected IP addr len.");
48   char buf[INET6_ADDRSTRLEN] = "-";
49   if (!addr)
50     return buf;
51 
52   switch (addr->sa_family) {
53     case AF_INET: {
54       auto addr_in = reinterpret_cast<const sockaddr_in*>(addr);
55       if (!inet_ntop(AF_INET, &addr_in->sin_addr, buf, sizeof(buf)))
56         PLOG(ERROR) << "Unable to get IP address string (IPv4)";
57     }
58     break;
59 
60     case AF_INET6: {
61       auto addr_in6 = reinterpret_cast<const sockaddr_in6*>(addr);
62 
63       // Note that inet_ntop(3) doesn't handle IPv4-mapped IPv6
64       // addresses [1] the way you'd expect .. for example, it returns
65       // "::ffff:172.22.72.163" instead of the more traditional IPv4
66       // notation "172.22.72.163". Fortunately, this is pretty easy to
67       // fix ourselves.
68       //
69       // [1] : see RFC 4291, section 2.5.5.2 for what that means
70       //       http://tools.ietf.org/html/rfc4291#section-2.5.5
71       //
72       auto dwords = reinterpret_cast<const uint32_t*>(&addr_in6->sin6_addr);
73       if (dwords[0] == 0x00000000 && dwords[1] == 0x00000000 &&
74           dwords[2] == htonl(0x0000ffff)) {
75         auto bytes = reinterpret_cast<const uint8_t*>(&addr_in6->sin6_addr);
76         return base::StringPrintf("%d.%d.%d.%d",
77                                   bytes[12], bytes[13], bytes[14], bytes[15]);
78       } else if (!inet_ntop(AF_INET6, &addr_in6->sin6_addr, buf, sizeof(buf))) {
79         PLOG(ERROR) << "Unable to get IP address string (IPv6)";
80       }
81     }
82     break;
83 
84     default:
85       LOG(ERROR) << "Unsupported address family " << addr->sa_family;
86       break;
87   }
88   return buf;
89 }
90 
91 }  // Anonymous namespace
92 
93 // Logger class to write the log data to a log file.
94 class FileLogger final : public LogManager::LoggerInterface {
95  public:
FileLogger(const base::FilePath & log_directory,LogManager * log_manager)96   FileLogger(const base::FilePath& log_directory, LogManager* log_manager)
97       : log_directory_(log_directory), log_manager_{log_manager} {}
98 
99   // Write the log entry to today's log file.
Log(const base::Time & timestamp,const std::string & entry)100   void Log(const base::Time& timestamp, const std::string& entry) override {
101     tm time_buf = {};
102     char file_name[32] = {};
103     // Create the file name in year-month-day format so that string sort would
104     // correspond to date sort.
105     time_t time = timestamp.ToTimeT();
106     strftime(file_name, sizeof(file_name), "%Y-%m-%d.log",
107              localtime_r(&time, &time_buf));
108     base::FilePath file_path = log_directory_.Append(file_name);
109     bool success = false;
110     bool exists = base::PathExists(file_path);
111     // If the file already exists, check its size. If it is going to be larger
112     // then the maximum allowed log size, archive the current log file and
113     // create a new, empty one.
114     if (exists) {
115       int64_t file_size = 0;
116       bool got_size = base::GetFileSize(file_path, &file_size);
117       if (got_size && (file_size + entry.size() > kMaxLogFileSize))
118         exists = !ArchiveLogFile(file_name);
119     }
120 
121     if (exists) {
122       success = base::AppendToFile(file_path, entry.data(), entry.size());
123     } else {
124       int size = static_cast<int>(entry.size());
125       success = (base::WriteFile(file_path, entry.data(), size) == size);
126       if (success) {
127         // We just created a new file, see if we need to purge old ones.
128         log_manager_->PerformLogMaintenance();
129       }
130     }
131     PLOG_IF(ERROR, !success) << "Failed to append a log entry to log file at "
132                              << file_path.value();
133   }
134 
135  private:
136   // Renames the log file to a next available suffix-appended archive when
137   // the log file size starts to exceed the pre-defined maximum size.
138   // The existing log file is renamed by changing the original |file_name| to
139   // YYYY-MM-DD-<suffix>.log where suffix is characters 'a', 'b', ...
140   // Since '-' comes before '.', "2015-02-25-a.log" will come before
141   // "2015-02-25.log" in sort order and the previously-renamed files will be
142   // considered "older" than the current one, which is what we need.
143   // Returns true if the file has been successfully renamed.
ArchiveLogFile(const std::string & file_name)144   bool ArchiveLogFile(const std::string& file_name) {
145     char suffix = 'a';
146     auto pair = brillo::string_utils::SplitAtFirst(file_name, ".");
147     // If we try all the suffixes from 'a' to 'z' and still can't find a name,
148     // abandon this strategy and keep appending to the current file.
149     while (suffix <= 'z') {
150       base::FilePath archive_file_path = log_directory_.Append(
151           base::StringPrintf("%s-%c.%s", pair.first.c_str(),
152                               suffix, pair.second.c_str()));
153       if (!base::PathExists(archive_file_path)) {
154         base::FilePath file_path = log_directory_.Append(file_name);
155         if (base::Move(file_path, archive_file_path)) {
156           // Successfully renamed, start a new log file.
157           return true;
158         } else {
159           PLOG(ERROR) << "Failed to rename log file from "
160                       << file_path.value() << " to "
161                       << archive_file_path.value();
162         }
163         break;
164       }
165       suffix++;
166     }
167     return false;
168   }
169 
170   base::FilePath log_directory_;
171   LogManager* log_manager_{nullptr};
172 };
173 
Init(const base::FilePath & log_directory)174 void LogManager::Init(const base::FilePath& log_directory) {
175   LogManager* inst = GetInstance();
176   inst->log_directory_ = log_directory;
177   inst->SetLogger(
178       std::unique_ptr<LoggerInterface>{new FileLogger{log_directory, inst}});
179   inst->PerformLogMaintenance();
180 }
181 
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)182 void LogManager::OnRequestCompleted(const base::Time& timestamp,
183                                     const sockaddr* client_addr,
184                                     const std::string& method,
185                                     const std::string& url,
186                                     const std::string& version,
187                                     int status_code,
188                                     int64_t response_size) {
189   std::string ip_address = GetIPAddress(client_addr);
190   tm time_buf = {};
191   char str_buf[32] = {};
192   // Format the date/time as "25/Feb/2015:03:29:12 -0800".
193   time_t time = timestamp.ToTimeT();
194   strftime(str_buf, sizeof(str_buf), "%d/%b/%Y:%H:%M:%S %z",
195            localtime_r(&time, &time_buf));
196 
197   // Log file entry for one HTTP request looking like this:
198   // 127.0.0.1 - - [25/Feb/2015:03:29:12 -0800] "GET /test HTTP/1.1" 200 2326
199   std::string size_string{"-"};
200   if (response_size >= 0)
201     size_string = std::to_string(response_size);
202   std::string log_entry = base::StringPrintf(
203       "%s - - [%s] \"%s %s %s\" %d %s\n", ip_address.c_str(), str_buf,
204       method.c_str(), url.c_str(), version.c_str(), status_code,
205       size_string.c_str());
206   GetInstance()->logger_->Log(timestamp, log_entry);
207 }
208 
SetLogger(std::unique_ptr<LoggerInterface> logger)209 void LogManager::SetLogger(std::unique_ptr<LoggerInterface> logger) {
210   GetInstance()->logger_ = std::move(logger);
211 }
212 
GetInstance()213 LogManager* LogManager::GetInstance() {
214   return g_log_manager.Pointer();
215 }
216 
PerformLogMaintenance()217 void LogManager::PerformLogMaintenance() {
218   // Get the list of all the log files in the log directory and put them into
219   // a set which will sort the files by name (and effectively by the date since
220   // we chose the file naming scheme deliberately to guarantee proper sorting
221   // order).
222   std::set<base::FilePath> log_files;
223   base::FileEnumerator enumerator{log_directory_,
224                                   false,
225                                   base::FileEnumerator::FILES,
226                                   "*.log"};
227   base::FilePath file = enumerator.Next();
228   while (!file.empty()) {
229     log_files.insert(file);
230     file = enumerator.Next();
231   }
232 
233   // Now, if we have more files than we want to keep, purge the old files.
234   while (log_files.size() > kLogFilesToKeep) {
235     auto front_it = log_files.begin();
236     PLOG_IF(WARNING, !base::DeleteFile(*front_it, false))
237         << "Failed to delete an old log file: " << front_it->value();
238     log_files.erase(front_it);
239   }
240 }
241 
242 }  // namespace webservd
243