blob: 66a08c6662df0f01442b089d65280900cb957e6c [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),
Vernon Mauery8376dc02018-12-18 16:05:44 -0800101 sizeof(sa) - sizeof(sa.sun_path) +
102 sizeof(HOSTLOG_SOCKET_PATH) - 1);
Artem Senichevefd5d742018-10-24 16:14:04 +0300103 sleep(HOSTLOG_SOCKET_PAUSE);
104 }
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800105 if (rc < 0)
106 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300107 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800108 fprintf(stderr,
109 "Unable to connect to host log socket: error [%i] %s\n", rc,
110 strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300111 }
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800112 } while (false);
Artem Senichevefd5d742018-10-24 16:14:04 +0300113
114 if (rc != 0)
115 closeHostLog();
116
117 return rc;
118}
119
Artem Senichevefd5d742018-10-24 16:14:04 +0300120void LogManager::closeHostLog()
121{
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800122 if (fd_ != -1)
123 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300124 ::close(fd_);
125 fd_ = -1;
126 }
127}
128
Artem Senichevefd5d742018-10-24 16:14:04 +0300129int LogManager::getHostLogFd() const
130{
131 return fd_;
132}
133
Artem Senichevefd5d742018-10-24 16:14:04 +0300134int LogManager::handleHostLog()
135{
136 int rc = 0;
137 std::vector<char> buff(MAX_SOCKET_BUFFER_SIZE);
138 size_t readLen = MAX_SOCKET_BUFFER_SIZE;
139
140 // Read all existing data from log stream
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800141 while (rc == 0 && readLen != 0)
142 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300143 rc = readHostLog(&buff[0], buff.size(), readLen);
144 if (rc == 0 && readLen != 0)
145 storage_.parse(&buff[0], readLen);
146 }
147
148 return rc;
149}
150
Artem Senichevefd5d742018-10-24 16:14:04 +0300151int LogManager::flush()
152{
153 int rc;
154
155 if (storage_.empty())
156 return 0; // Nothing to save
157
158 const std::string logFile = prepareLogPath();
159 if (logFile.empty())
160 return EIO;
161
162 rc = storage_.write(logFile.c_str());
163 if (rc != 0)
164 return rc;
165
166 storage_.clear();
167
168 // Non critical tasks, don't check returned status
169 rotateLogFiles();
170
171 return 0;
172}
173
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800174int LogManager::readHostLog(char* buffer, size_t bufferLen,
175 size_t& readLen) const
Artem Senichevefd5d742018-10-24 16:14:04 +0300176{
177 int rc = 0;
178
179 const ssize_t rsz = ::read(fd_, buffer, bufferLen);
180 if (rsz >= 0)
181 readLen = static_cast<size_t>(rsz);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800182 else
183 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300184 readLen = 0;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800185 if (errno != EAGAIN && errno != EWOULDBLOCK)
186 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300187 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800188 fprintf(stderr, "Unable to read host log: error [%i] %s\n", rc,
189 strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300190 }
191 }
192
193 return rc;
194}
195
Artem Senichevefd5d742018-10-24 16:14:04 +0300196std::string LogManager::prepareLogPath() const
197{
198 // Create path for logs
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800199 if (::access(loggerConfig.path, F_OK) != 0)
200 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300201 const std::string logPath(loggerConfig.path);
202 const size_t len = logPath.length();
203 size_t pos = 0;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800204 while (pos < len - 1)
205 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300206 pos = logPath.find('/', pos + 1);
207 const std::string createPath = logPath.substr(0, pos);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800208 if (::mkdir(createPath.c_str(),
209 S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 &&
210 errno != EEXIST)
211 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300212 const int rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800213 fprintf(stderr, "Unable to create dir %s: error [%i] %s\n",
214 createPath.c_str(), rc, strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300215 return std::string();
216 }
217 }
218 }
219
220 // Construct log file name
221 time_t ts;
222 time(&ts);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800223 tm lt = {0};
Artem Senichevefd5d742018-10-24 16:14:04 +0300224 localtime_r(&ts, &lt);
225 char fileName[64];
226 snprintf(fileName, sizeof(fileName), "/host_%i%02i%02i_%02i%02i%02i.log.gz",
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800227 lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour,
228 lt.tm_min, lt.tm_sec);
Artem Senichevefd5d742018-10-24 16:14:04 +0300229
230 return std::string(loggerConfig.path) + fileName;
231}
232
Artem Senichevefd5d742018-10-24 16:14:04 +0300233int LogManager::rotateLogFiles() const
234{
235 if (loggerConfig.rotationLimit == 0)
236 return 0; // Not applicable
237
238 int rc = 0;
239
240 // Get file list to std::set
241 std::set<std::string> logFiles;
242 DIR* dh = opendir(loggerConfig.path);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800243 if (!dh)
244 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300245 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800246 fprintf(stderr, "Unable to open directory %s: error [%i] %s\n",
247 loggerConfig.path, rc, strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300248 return rc;
249 }
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800250 dirent* dir;
251 while ((dir = readdir(dh)))
252 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300253 if (dir->d_type != DT_DIR)
254 logFiles.insert(dir->d_name);
255 }
256 closedir(dh);
257
258 // Log file has a name with a timestamp generated by prepareLogPath().
259 // The sorted array of names (std::set) will contain the oldest file on the
260 // top.
261 // Remove oldest files.
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800262 int filesToRemove =
263 static_cast<int>(logFiles.size()) - loggerConfig.rotationLimit;
264 while (rc == 0 && --filesToRemove >= 0)
265 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300266 std::string fileToRemove = loggerConfig.path;
267 fileToRemove += '/';
268 fileToRemove += *logFiles.begin();
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800269 if (::unlink(fileToRemove.c_str()) == -1)
270 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300271 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800272 fprintf(stderr, "Unable to delete file %s: error [%i] %s\n",
273 fileToRemove.c_str(), rc, strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300274 }
275 logFiles.erase(fileToRemove);
276 }
277
278 return rc;
279}