blob: b78b7e839b138349d20b92f64d2742a676506b93 [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
21#include "config.hpp"
22#include "log_manager.hpp"
23
24#include <set>
25#include <vector>
26#include <cstring>
27#include <unistd.h>
28#include <dirent.h>
29#include <sys/socket.h>
30#include <sys/un.h>
31#include <sys/stat.h>
32#include <sys/types.h>
33#include <sys/ioctl.h>
34
35// Path to the Unix Domain socket file used to read host's logs
36#define HOSTLOG_SOCKET_PATH "\0obmc-console"
37// Number of connection attempts
38#define HOSTLOG_SOCKET_ATTEMPTS 60
39// Pause between connection attempts in seconds
40#define HOSTLOG_SOCKET_PAUSE 1
41// Max buffer size to read from socket
42#define MAX_SOCKET_BUFFER_SIZE 512
43
44
45LogManager::LogManager()
46: fd_(-1)
47{
48}
49
50
51LogManager::~LogManager()
52{
53 closeHostLog();
54}
55
56
57int LogManager::openHostLog()
58{
59 int rc;
60
61 do {
62 // Create socket
63 fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
64 if (fd_ == -1) {
65 rc = errno;
66 fprintf(stderr, "Unable to create socket: error [%i] %s\n", rc, strerror(rc));
67 break;
68 }
69
70 // Set non-blocking mode for socket
71 int opt = 1;
72 rc = ioctl(fd_, FIONBIO, &opt);
73 if (rc != 0) {
74 rc = errno;
75 fprintf(stderr, "Unable to set non-blocking mode for log socket: error [%i] %s\n", rc, strerror(rc));
76 break;
77 }
78
79 sockaddr_un sa = { 0 };
80 sa.sun_family = AF_UNIX;
81 constexpr int min_path = sizeof(HOSTLOG_SOCKET_PATH) < sizeof(sa.sun_path) ?
82 sizeof(HOSTLOG_SOCKET_PATH) : sizeof(sa.sun_path);
83 memcpy(&sa.sun_path, HOSTLOG_SOCKET_PATH, min_path);
84
85 // Connect to host's log stream via socket.
86 // The owner of the socket (server) is obmc-console service and
87 // we have a dependency on it written in the systemd unit file, but
88 // we can't guarantee that the socket is initialized at the moment.
89 rc = -1;
90 for (int attempt = 0; rc != 0 && attempt < HOSTLOG_SOCKET_ATTEMPTS; ++attempt) {
91 rc = connect(fd_, reinterpret_cast<const sockaddr*>(&sa), sizeof(sa));
92 sleep(HOSTLOG_SOCKET_PAUSE);
93 }
94 if (rc < 0) {
95 rc = errno;
96 fprintf(stderr, "Unable to connect to host log socket: error [%i] %s\n", rc, strerror(rc));
97 }
98 }
99 while (false);
100
101 if (rc != 0)
102 closeHostLog();
103
104 return rc;
105}
106
107
108void LogManager::closeHostLog()
109{
110 if (fd_ != -1) {
111 ::close(fd_);
112 fd_ = -1;
113 }
114}
115
116
117int LogManager::getHostLogFd() const
118{
119 return fd_;
120}
121
122
123int LogManager::handleHostLog()
124{
125 int rc = 0;
126 std::vector<char> buff(MAX_SOCKET_BUFFER_SIZE);
127 size_t readLen = MAX_SOCKET_BUFFER_SIZE;
128
129 // Read all existing data from log stream
130 while (rc == 0 && readLen != 0) {
131 rc = readHostLog(&buff[0], buff.size(), readLen);
132 if (rc == 0 && readLen != 0)
133 storage_.parse(&buff[0], readLen);
134 }
135
136 return rc;
137}
138
139
140int LogManager::flush()
141{
142 int rc;
143
144 if (storage_.empty())
145 return 0; // Nothing to save
146
147 const std::string logFile = prepareLogPath();
148 if (logFile.empty())
149 return EIO;
150
151 rc = storage_.write(logFile.c_str());
152 if (rc != 0)
153 return rc;
154
155 storage_.clear();
156
157 // Non critical tasks, don't check returned status
158 rotateLogFiles();
159
160 return 0;
161}
162
163
164int LogManager::readHostLog(char* buffer, size_t bufferLen, size_t& readLen) const
165{
166 int rc = 0;
167
168 const ssize_t rsz = ::read(fd_, buffer, bufferLen);
169 if (rsz >= 0)
170 readLen = static_cast<size_t>(rsz);
171 else {
172 readLen = 0;
173 if (errno != EAGAIN && errno != EWOULDBLOCK) {
174 rc = errno;
175 fprintf(stderr, "Unable to read host log: error [%i] %s\n", rc, strerror(rc));
176 }
177 }
178
179 return rc;
180}
181
182
183std::string LogManager::prepareLogPath() const
184{
185 // Create path for logs
186 if (::access(loggerConfig.path, F_OK) != 0) {
187 const std::string logPath(loggerConfig.path);
188 const size_t len = logPath.length();
189 size_t pos = 0;
190 while (pos < len - 1) {
191 pos = logPath.find('/', pos + 1);
192 const std::string createPath = logPath.substr(0, pos);
193 if (::mkdir(createPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && errno != EEXIST) {
194 const int rc = errno;
195 fprintf(stderr, "Unable to create dir %s: error [%i] %s\n", createPath.c_str(), rc, strerror(rc));
196 return std::string();
197 }
198 }
199 }
200
201 // Construct log file name
202 time_t ts;
203 time(&ts);
204 tm lt = { 0 };
205 localtime_r(&ts, &lt);
206 char fileName[64];
207 snprintf(fileName, sizeof(fileName), "/host_%i%02i%02i_%02i%02i%02i.log.gz",
208 lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
209 lt.tm_hour, lt.tm_min, lt.tm_sec);
210
211 return std::string(loggerConfig.path) + fileName;
212}
213
214
215int LogManager::rotateLogFiles() const
216{
217 if (loggerConfig.rotationLimit == 0)
218 return 0; // Not applicable
219
220 int rc = 0;
221
222 // Get file list to std::set
223 std::set<std::string> logFiles;
224 DIR* dh = opendir(loggerConfig.path);
225 if (!dh) {
226 rc = errno;
227 fprintf(stderr, "Unable to open directory %s: error [%i] %s\n", loggerConfig.path, rc, strerror(rc));
228 return rc;
229 }
230 dirent *dir;
231 while ((dir = readdir(dh))) {
232 if (dir->d_type != DT_DIR)
233 logFiles.insert(dir->d_name);
234 }
235 closedir(dh);
236
237 // Log file has a name with a timestamp generated by prepareLogPath().
238 // The sorted array of names (std::set) will contain the oldest file on the
239 // top.
240 // Remove oldest files.
241 int filesToRemove = static_cast<int>(logFiles.size()) - loggerConfig.rotationLimit;
242 while (rc == 0 && --filesToRemove >= 0) {
243 std::string fileToRemove = loggerConfig.path;
244 fileToRemove += '/';
245 fileToRemove += *logFiles.begin();
246 if (::unlink(fileToRemove.c_str()) == -1) {
247 rc = errno;
248 fprintf(stderr, "Unable to delete file %s: error [%i] %s\n", fileToRemove.c_str(), rc, strerror(rc));
249 }
250 logFiles.erase(fileToRemove);
251 }
252
253 return rc;
254}