blob: 1ada1c37ecfeb43ebbbea9abe651039f43f0a5ce [file] [log] [blame]
/**
* @brief Log storage.
*
* This file is part of HostLogger project.
*
* Copyright (c) 2018 YADRO
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "config.hpp"
#include "log_storage.hpp"
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
LogStorage::LogStorage()
: last_complete_(true)
{
}
void LogStorage::parse(const char* data, size_t len)
{
// Split log stream to separate messages.
// Stream may not be ended with EOL, so we handle this situation by
// last_complete_ flag.
size_t pos = 0;
while (pos < len) {
// Search for EOL ('\n')
size_t eol = pos;
while (eol < len && data[eol] != '\n')
++eol;
const bool eolFound = eol < len;
const char* msgText = data + pos;
size_t msgLen = (eolFound ? eol : len) - pos;
// Remove '\r' from the end of message
while (msgLen && msgText[msgLen - 1] == '\r')
--msgLen;
// Append message to store
if (msgLen)
append(msgText, msgLen);
pos = eol + 1; // Skip '\n'
last_complete_ = eolFound;
}
}
void LogStorage::append(const char* msg, size_t len)
{
if (!last_complete_) {
// The last message is incomplete, add msg as part of it
if (!messages_.empty()) {
Message& last_msg = *messages_.rbegin();
last_msg.text.append(msg, len);
}
}
else {
Message new_msg;
time(&new_msg.timeStamp);
new_msg.text.assign(msg, len);
messages_.push_back(new_msg);
shrink();
}
}
void LogStorage::clear()
{
messages_.clear();
last_complete_ = true;
}
bool LogStorage::empty() const
{
return messages_.empty();
}
int LogStorage::write(const char* fileName) const
{
int rc = 0;
if (empty()) {
printf("No messages to write\n");
return 0;
}
const gzFile fd = gzopen(fileName, "w");
if (fd == Z_NULL) {
rc = errno;
fprintf(stderr, "Unable to open file %s: error [%i] %s\n",
fileName, rc, strerror(rc));
return rc;
}
// Write full datetime stamp as the first record
const time_t& logStartTime = messages_.begin()->timeStamp;
tm localTime = { 0 };
localtime_r(&logStartTime, &localTime);
char msgText[64];
snprintf(msgText, sizeof(msgText),
">>> Log collection started at %02i.%02i.%i %02i:%02i:%02i",
localTime.tm_mday, localTime.tm_mon + 1, localTime.tm_year + 1900,
localTime.tm_hour, localTime.tm_min, localTime.tm_sec);
const Message startMsg = { logStartTime, msgText };
rc |= write(fd, startMsg);
// Write messages
for (auto it = messages_.begin(); rc == 0 && it != messages_.end(); ++it)
rc |= write(fd, *it);
rc = gzclose_w(fd);
if (rc != Z_OK)
fprintf(stderr, "Unable to close file %s: error [%i]\n", fileName, rc);
return rc;
}
int LogStorage::write(gzFile fd, const Message& msg) const
{
// Convert timestamp to local time
tm localTime = { 0 };
localtime_r(&msg.timeStamp, &localTime);
// Write message to the file
const int rc = gzprintf(fd, "[ %02i:%02i:%02i ]: %s\n",
localTime.tm_hour,
localTime.tm_min,
localTime.tm_sec,
msg.text.c_str());
if (rc <= 0) {
fprintf(stderr, "Unable to write file: error [%i]\n", -rc);
return EIO;
}
return 0;
}
void LogStorage::shrink()
{
if (loggerConfig.storageSizeLimit) {
while (messages_.size() > static_cast<size_t>(loggerConfig.storageSizeLimit))
messages_.pop_front();
}
if (loggerConfig.storageTimeLimit) {
// Get time for N hours ago
time_t oldestTimeStamp;
time(&oldestTimeStamp);
oldestTimeStamp -= loggerConfig.storageTimeLimit * 60 * 60;
while (!messages_.empty() && messages_.begin()->timeStamp < oldestTimeStamp)
messages_.pop_front();
}
}