blob: 506e5862bf44eb5ea7312dc2369ea3e165085dd7 [file] [log] [blame]
Artem Senichevefd5d742018-10-24 16:14:04 +03001/**
2 * @brief Log manager.
3 *
4 * This file is part of HostLogger project.
5 *
6 * Copyright (c) 2018 YADRO
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
Artem Senichevefd5d742018-10-24 16:14:04 +030021#include "log_manager.hpp"
22
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080023#include "config.hpp"
24
Artem Senichevefd5d742018-10-24 16:14:04 +030025#include <dirent.h>
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080026#include <sys/ioctl.h>
Artem Senichevefd5d742018-10-24 16:14:04 +030027#include <sys/socket.h>
Artem Senichevefd5d742018-10-24 16:14:04 +030028#include <sys/stat.h>
29#include <sys/types.h>
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080030#include <sys/un.h>
31#include <unistd.h>
32
33#include <cstring>
34#include <set>
35#include <vector>
Artem Senichevefd5d742018-10-24 16:14:04 +030036
37// Path to the Unix Domain socket file used to read host's logs
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080038#define HOSTLOG_SOCKET_PATH "\0obmc-console"
Artem Senichevefd5d742018-10-24 16:14:04 +030039// Number of connection attempts
40#define HOSTLOG_SOCKET_ATTEMPTS 60
41// Pause between connection attempts in seconds
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080042#define HOSTLOG_SOCKET_PAUSE 1
Artem Senichevefd5d742018-10-24 16:14:04 +030043// Max buffer size to read from socket
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080044#define MAX_SOCKET_BUFFER_SIZE 512
Artem Senichevefd5d742018-10-24 16:14:04 +030045
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080046LogManager::LogManager() : fd_(-1)
Artem Senichevefd5d742018-10-24 16:14:04 +030047{
48}
49
Artem Senichevefd5d742018-10-24 16:14:04 +030050LogManager::~LogManager()
51{
52 closeHostLog();
53}
54
Artem Senichevefd5d742018-10-24 16:14:04 +030055int LogManager::openHostLog()
56{
57 int rc;
58
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080059 do
60 {
Artem Senichevefd5d742018-10-24 16:14:04 +030061 // Create socket
62 fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080063 if (fd_ == -1)
64 {
Artem Senichevefd5d742018-10-24 16:14:04 +030065 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080066 fprintf(stderr, "Unable to create socket: error [%i] %s\n", rc,
67 strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +030068 break;
69 }
70
71 // Set non-blocking mode for socket
72 int opt = 1;
73 rc = ioctl(fd_, FIONBIO, &opt);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080074 if (rc != 0)
75 {
Artem Senichevefd5d742018-10-24 16:14:04 +030076 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080077 fprintf(stderr,
78 "Unable to set non-blocking mode for log socket: error "
79 "[%i] %s\n",
80 rc, strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +030081 break;
82 }
83
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080084 sockaddr_un sa = {0};
Artem Senichevefd5d742018-10-24 16:14:04 +030085 sa.sun_family = AF_UNIX;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080086 constexpr int min_path =
87 sizeof(HOSTLOG_SOCKET_PATH) < sizeof(sa.sun_path)
88 ? sizeof(HOSTLOG_SOCKET_PATH)
89 : sizeof(sa.sun_path);
Artem Senichevefd5d742018-10-24 16:14:04 +030090 memcpy(&sa.sun_path, HOSTLOG_SOCKET_PATH, min_path);
91
92 // Connect to host's log stream via socket.
93 // The owner of the socket (server) is obmc-console service and
94 // we have a dependency on it written in the systemd unit file, but
95 // we can't guarantee that the socket is initialized at the moment.
96 rc = -1;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080097 for (int attempt = 0; rc != 0 && attempt < HOSTLOG_SOCKET_ATTEMPTS;
98 ++attempt)
99 {
100 rc = connect(fd_, reinterpret_cast<const sockaddr*>(&sa),
101 sizeof(sa));
Artem Senichevefd5d742018-10-24 16:14:04 +0300102 sleep(HOSTLOG_SOCKET_PAUSE);
103 }
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800104 if (rc < 0)
105 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300106 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800107 fprintf(stderr,
108 "Unable to connect to host log socket: error [%i] %s\n", rc,
109 strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300110 }
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800111 } while (false);
Artem Senichevefd5d742018-10-24 16:14:04 +0300112
113 if (rc != 0)
114 closeHostLog();
115
116 return rc;
117}
118
Artem Senichevefd5d742018-10-24 16:14:04 +0300119void LogManager::closeHostLog()
120{
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800121 if (fd_ != -1)
122 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300123 ::close(fd_);
124 fd_ = -1;
125 }
126}
127
Artem Senichevefd5d742018-10-24 16:14:04 +0300128int LogManager::getHostLogFd() const
129{
130 return fd_;
131}
132
Artem Senichevefd5d742018-10-24 16:14:04 +0300133int LogManager::handleHostLog()
134{
135 int rc = 0;
136 std::vector<char> buff(MAX_SOCKET_BUFFER_SIZE);
137 size_t readLen = MAX_SOCKET_BUFFER_SIZE;
138
139 // Read all existing data from log stream
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800140 while (rc == 0 && readLen != 0)
141 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300142 rc = readHostLog(&buff[0], buff.size(), readLen);
143 if (rc == 0 && readLen != 0)
144 storage_.parse(&buff[0], readLen);
145 }
146
147 return rc;
148}
149
Artem Senichevefd5d742018-10-24 16:14:04 +0300150int LogManager::flush()
151{
152 int rc;
153
154 if (storage_.empty())
155 return 0; // Nothing to save
156
157 const std::string logFile = prepareLogPath();
158 if (logFile.empty())
159 return EIO;
160
161 rc = storage_.write(logFile.c_str());
162 if (rc != 0)
163 return rc;
164
165 storage_.clear();
166
167 // Non critical tasks, don't check returned status
168 rotateLogFiles();
169
170 return 0;
171}
172
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800173int LogManager::readHostLog(char* buffer, size_t bufferLen,
174 size_t& readLen) const
Artem Senichevefd5d742018-10-24 16:14:04 +0300175{
176 int rc = 0;
177
178 const ssize_t rsz = ::read(fd_, buffer, bufferLen);
179 if (rsz >= 0)
180 readLen = static_cast<size_t>(rsz);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800181 else
182 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300183 readLen = 0;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800184 if (errno != EAGAIN && errno != EWOULDBLOCK)
185 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300186 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800187 fprintf(stderr, "Unable to read host log: error [%i] %s\n", rc,
188 strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300189 }
190 }
191
192 return rc;
193}
194
Artem Senichevefd5d742018-10-24 16:14:04 +0300195std::string LogManager::prepareLogPath() const
196{
197 // Create path for logs
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800198 if (::access(loggerConfig.path, F_OK) != 0)
199 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300200 const std::string logPath(loggerConfig.path);
201 const size_t len = logPath.length();
202 size_t pos = 0;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800203 while (pos < len - 1)
204 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300205 pos = logPath.find('/', pos + 1);
206 const std::string createPath = logPath.substr(0, pos);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800207 if (::mkdir(createPath.c_str(),
208 S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 &&
209 errno != EEXIST)
210 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300211 const int rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800212 fprintf(stderr, "Unable to create dir %s: error [%i] %s\n",
213 createPath.c_str(), rc, strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300214 return std::string();
215 }
216 }
217 }
218
219 // Construct log file name
220 time_t ts;
221 time(&ts);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800222 tm lt = {0};
Artem Senichevefd5d742018-10-24 16:14:04 +0300223 localtime_r(&ts, &lt);
224 char fileName[64];
225 snprintf(fileName, sizeof(fileName), "/host_%i%02i%02i_%02i%02i%02i.log.gz",
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800226 lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour,
227 lt.tm_min, lt.tm_sec);
Artem Senichevefd5d742018-10-24 16:14:04 +0300228
229 return std::string(loggerConfig.path) + fileName;
230}
231
Artem Senichevefd5d742018-10-24 16:14:04 +0300232int LogManager::rotateLogFiles() const
233{
234 if (loggerConfig.rotationLimit == 0)
235 return 0; // Not applicable
236
237 int rc = 0;
238
239 // Get file list to std::set
240 std::set<std::string> logFiles;
241 DIR* dh = opendir(loggerConfig.path);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800242 if (!dh)
243 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300244 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800245 fprintf(stderr, "Unable to open directory %s: error [%i] %s\n",
246 loggerConfig.path, rc, strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300247 return rc;
248 }
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800249 dirent* dir;
250 while ((dir = readdir(dh)))
251 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300252 if (dir->d_type != DT_DIR)
253 logFiles.insert(dir->d_name);
254 }
255 closedir(dh);
256
257 // Log file has a name with a timestamp generated by prepareLogPath().
258 // The sorted array of names (std::set) will contain the oldest file on the
259 // top.
260 // Remove oldest files.
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800261 int filesToRemove =
262 static_cast<int>(logFiles.size()) - loggerConfig.rotationLimit;
263 while (rc == 0 && --filesToRemove >= 0)
264 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300265 std::string fileToRemove = loggerConfig.path;
266 fileToRemove += '/';
267 fileToRemove += *logFiles.begin();
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800268 if (::unlink(fileToRemove.c_str()) == -1)
269 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300270 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800271 fprintf(stderr, "Unable to delete file %s: error [%i] %s\n",
272 fileToRemove.c_str(), rc, strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300273 }
274 logFiles.erase(fileToRemove);
275 }
276
277 return rc;
278}