Initial commit
Signed-off-by: Artem Senichev <a.senichev@yadro.com>
diff --git a/src/log_storage.cpp b/src/log_storage.cpp
new file mode 100644
index 0000000..1ada1c3
--- /dev/null
+++ b/src/log_storage.cpp
@@ -0,0 +1,173 @@
+/**
+ * @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();
+ }
+}