blob: 1ada1c37ecfeb43ebbbea9abe651039f43f0a5ce [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
21#include "config.hpp"
22#include "log_storage.hpp"
23
24#include <sys/stat.h>
25#include <fcntl.h>
26#include <unistd.h>
27#include <string.h>
28
29
30LogStorage::LogStorage()
31: last_complete_(true)
32{
33}
34
35
36void LogStorage::parse(const char* data, size_t len)
37{
38 // Split log stream to separate messages.
39 // Stream may not be ended with EOL, so we handle this situation by
40 // last_complete_ flag.
41 size_t pos = 0;
42 while (pos < len) {
43 // Search for EOL ('\n')
44 size_t eol = pos;
45 while (eol < len && data[eol] != '\n')
46 ++eol;
47 const bool eolFound = eol < len;
48 const char* msgText = data + pos;
49 size_t msgLen = (eolFound ? eol : len) - pos;
50
51 // Remove '\r' from the end of message
52 while (msgLen && msgText[msgLen - 1] == '\r')
53 --msgLen;
54
55 // Append message to store
56 if (msgLen)
57 append(msgText, msgLen);
58
59 pos = eol + 1; // Skip '\n'
60 last_complete_ = eolFound;
61 }
62}
63
64
65void LogStorage::append(const char* msg, size_t len)
66{
67 if (!last_complete_) {
68 // The last message is incomplete, add msg as part of it
69 if (!messages_.empty()) {
70 Message& last_msg = *messages_.rbegin();
71 last_msg.text.append(msg, len);
72 }
73 }
74 else {
75 Message new_msg;
76 time(&new_msg.timeStamp);
77 new_msg.text.assign(msg, len);
78 messages_.push_back(new_msg);
79 shrink();
80 }
81}
82
83
84void LogStorage::clear()
85{
86 messages_.clear();
87 last_complete_ = true;
88}
89
90
91bool LogStorage::empty() const
92{
93 return messages_.empty();
94}
95
96
97int LogStorage::write(const char* fileName) const
98{
99 int rc = 0;
100
101 if (empty()) {
102 printf("No messages to write\n");
103 return 0;
104 }
105
106 const gzFile fd = gzopen(fileName, "w");
107 if (fd == Z_NULL) {
108 rc = errno;
109 fprintf(stderr, "Unable to open file %s: error [%i] %s\n",
110 fileName, rc, strerror(rc));
111 return rc;
112 }
113
114 // Write full datetime stamp as the first record
115 const time_t& logStartTime = messages_.begin()->timeStamp;
116 tm localTime = { 0 };
117 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);
123 const Message startMsg = { logStartTime, msgText };
124 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
137
138int LogStorage::write(gzFile fd, const Message& msg) const
139{
140 // Convert timestamp to local time
141 tm localTime = { 0 };
142 localtime_r(&msg.timeStamp, &localTime);
143
144 // Write message to the file
145 const int rc = gzprintf(fd, "[ %02i:%02i:%02i ]: %s\n",
146 localTime.tm_hour,
147 localTime.tm_min,
148 localTime.tm_sec,
149 msg.text.c_str());
150 if (rc <= 0) {
151 fprintf(stderr, "Unable to write file: error [%i]\n", -rc);
152 return EIO;
153 }
154
155 return 0;
156}
157
158
159void LogStorage::shrink()
160{
161 if (loggerConfig.storageSizeLimit) {
162 while (messages_.size() > static_cast<size_t>(loggerConfig.storageSizeLimit))
163 messages_.pop_front();
164 }
165 if (loggerConfig.storageTimeLimit) {
166 // Get time for N hours ago
167 time_t oldestTimeStamp;
168 time(&oldestTimeStamp);
169 oldestTimeStamp -= loggerConfig.storageTimeLimit * 60 * 60;
170 while (!messages_.empty() && messages_.begin()->timeStamp < oldestTimeStamp)
171 messages_.pop_front();
172 }
173}