blob: 96b0b12d65ad4abe6f6adfe3527160cf27ba2e04 [file] [log] [blame]
Artem Senichevefd5d742018-10-24 16:14:04 +03001/**
2 * @brief Log storage.
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_storage.hpp"
22
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080023#include "config.hpp"
24
Artem Senichevefd5d742018-10-24 16:14:04 +030025#include <fcntl.h>
Artem Senichevefd5d742018-10-24 16:14:04 +030026#include <string.h>
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080027#include <sys/stat.h>
28#include <unistd.h>
Artem Senichevefd5d742018-10-24 16:14:04 +030029
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080030LogStorage::LogStorage() : last_complete_(true)
Artem Senichevefd5d742018-10-24 16:14:04 +030031{
32}
33
Artem Senichevefd5d742018-10-24 16:14:04 +030034void LogStorage::parse(const char* data, size_t len)
35{
36 // Split log stream to separate messages.
37 // Stream may not be ended with EOL, so we handle this situation by
38 // last_complete_ flag.
39 size_t pos = 0;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080040 while (pos < len)
41 {
Artem Senichevefd5d742018-10-24 16:14:04 +030042 // Search for EOL ('\n')
43 size_t eol = pos;
44 while (eol < len && data[eol] != '\n')
45 ++eol;
46 const bool eolFound = eol < len;
47 const char* msgText = data + pos;
48 size_t msgLen = (eolFound ? eol : len) - pos;
49
50 // Remove '\r' from the end of message
51 while (msgLen && msgText[msgLen - 1] == '\r')
52 --msgLen;
53
54 // Append message to store
55 if (msgLen)
56 append(msgText, msgLen);
57
58 pos = eol + 1; // Skip '\n'
59 last_complete_ = eolFound;
60 }
61}
62
Artem Senichevefd5d742018-10-24 16:14:04 +030063void LogStorage::append(const char* msg, size_t len)
64{
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080065 if (!last_complete_)
66 {
Artem Senichevefd5d742018-10-24 16:14:04 +030067 // The last message is incomplete, add msg as part of it
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080068 if (!messages_.empty())
69 {
Artem Senichevefd5d742018-10-24 16:14:04 +030070 Message& last_msg = *messages_.rbegin();
71 last_msg.text.append(msg, len);
72 }
73 }
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080074 else
75 {
Artem Senichevefd5d742018-10-24 16:14:04 +030076 Message new_msg;
77 time(&new_msg.timeStamp);
78 new_msg.text.assign(msg, len);
79 messages_.push_back(new_msg);
80 shrink();
81 }
82}
83
Artem Senichevefd5d742018-10-24 16:14:04 +030084void LogStorage::clear()
85{
86 messages_.clear();
87 last_complete_ = true;
88}
89
Artem Senichevefd5d742018-10-24 16:14:04 +030090bool LogStorage::empty() const
91{
92 return messages_.empty();
93}
94
Artem Senichevefd5d742018-10-24 16:14:04 +030095int LogStorage::write(const char* fileName) const
96{
97 int rc = 0;
98
Patrick Venture4d5a5dc2018-11-14 08:51:13 -080099 if (empty())
100 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300101 printf("No messages to write\n");
102 return 0;
103 }
104
105 const gzFile fd = gzopen(fileName, "w");
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800106 if (fd == Z_NULL)
107 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300108 rc = errno;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800109 fprintf(stderr, "Unable to open file %s: error [%i] %s\n", fileName, rc,
110 strerror(rc));
Artem Senichevefd5d742018-10-24 16:14:04 +0300111 return rc;
112 }
113
114 // Write full datetime stamp as the first record
115 const time_t& logStartTime = messages_.begin()->timeStamp;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800116 tm localTime = {0};
Artem Senichevefd5d742018-10-24 16:14:04 +0300117 localtime_r(&logStartTime, &localTime);
118 char msgText[64];
119 snprintf(msgText, sizeof(msgText),
120 ">>> Log collection started at %02i.%02i.%i %02i:%02i:%02i",
121 localTime.tm_mday, localTime.tm_mon + 1, localTime.tm_year + 1900,
122 localTime.tm_hour, localTime.tm_min, localTime.tm_sec);
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800123 const Message startMsg = {logStartTime, msgText};
Artem Senichevefd5d742018-10-24 16:14:04 +0300124 rc |= write(fd, startMsg);
125
126 // Write messages
127 for (auto it = messages_.begin(); rc == 0 && it != messages_.end(); ++it)
128 rc |= write(fd, *it);
129
130 rc = gzclose_w(fd);
131 if (rc != Z_OK)
132 fprintf(stderr, "Unable to close file %s: error [%i]\n", fileName, rc);
133
134 return rc;
135}
136
Artem Senichevefd5d742018-10-24 16:14:04 +0300137int LogStorage::write(gzFile fd, const Message& msg) const
138{
139 // Convert timestamp to local time
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800140 tm localTime = {0};
Artem Senichevefd5d742018-10-24 16:14:04 +0300141 localtime_r(&msg.timeStamp, &localTime);
142
143 // Write message to the file
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800144 const int rc =
145 gzprintf(fd, "[ %02i:%02i:%02i ]: %s\n", localTime.tm_hour,
146 localTime.tm_min, localTime.tm_sec, msg.text.c_str());
147 if (rc <= 0)
148 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300149 fprintf(stderr, "Unable to write file: error [%i]\n", -rc);
150 return EIO;
151 }
152
153 return 0;
154}
155
Artem Senichevefd5d742018-10-24 16:14:04 +0300156void LogStorage::shrink()
157{
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800158 if (loggerConfig.storageSizeLimit)
159 {
160 while (messages_.size() >
161 static_cast<size_t>(loggerConfig.storageSizeLimit))
Artem Senichevefd5d742018-10-24 16:14:04 +0300162 messages_.pop_front();
163 }
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800164 if (loggerConfig.storageTimeLimit)
165 {
Artem Senichevefd5d742018-10-24 16:14:04 +0300166 // Get time for N hours ago
167 time_t oldestTimeStamp;
168 time(&oldestTimeStamp);
169 oldestTimeStamp -= loggerConfig.storageTimeLimit * 60 * 60;
Patrick Venture4d5a5dc2018-11-14 08:51:13 -0800170 while (!messages_.empty() &&
171 messages_.begin()->timeStamp < oldestTimeStamp)
Artem Senichevefd5d742018-10-24 16:14:04 +0300172 messages_.pop_front();
173 }
174}