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