blob: 3fa1e49e34ad8bd73686c8382e756282481da363 [file] [log] [blame]
Spencer Kub7028eb2021-10-26 15:27:35 +08001#pragma once
2
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08003#include "logging.hpp"
4
Spencer Kub7028eb2021-10-26 15:27:35 +08005#include <zlib.h>
6
7#include <array>
8#include <filesystem>
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08009#include <string>
Spencer Kub7028eb2021-10-26 15:27:35 +080010#include <vector>
11
12class GzFileReader
13{
14 public:
Nan Zhou9739de92022-04-06 11:07:27 -070015 bool gzGetLines(const std::string& filename, uint64_t skip, uint64_t top,
Spencer Kub7028eb2021-10-26 15:27:35 +080016 std::vector<std::string>& logEntries, size_t& logCount)
17 {
18 gzFile logStream = gzopen(filename.c_str(), "r");
Ed Tanouse662eae2022-01-25 10:39:19 -080019 if (logStream == nullptr)
Spencer Kub7028eb2021-10-26 15:27:35 +080020 {
Ed Tanous62598e32023-07-17 17:06:25 -070021 BMCWEB_LOG_ERROR("Can't open gz file: {}", filename);
Spencer Kub7028eb2021-10-26 15:27:35 +080022 return false;
23 }
24
25 if (!readFile(logStream, skip, top, logEntries, logCount))
26 {
27 gzclose(logStream);
28 return false;
29 }
30 gzclose(logStream);
31 return true;
32 }
33
34 std::string getLastMessage()
35 {
36 return lastMessage;
37 }
38
39 private:
40 std::string lastMessage;
41 std::string lastDelimiter;
42 size_t totalFilesSize = 0;
43
Ed Tanous56d23962022-02-14 20:42:02 -080044 static void printErrorMessage(gzFile logStream)
Spencer Kub7028eb2021-10-26 15:27:35 +080045 {
46 int errNum = 0;
47 const char* errMsg = gzerror(logStream, &errNum);
48
Ed Tanous62598e32023-07-17 17:06:25 -070049 BMCWEB_LOG_ERROR(
50 "Error reading gz compressed data.\nError Message: {}\nError Number: {}",
51 errMsg, errNum);
Spencer Kub7028eb2021-10-26 15:27:35 +080052 }
53
Nan Zhou9739de92022-04-06 11:07:27 -070054 bool readFile(gzFile logStream, uint64_t skip, uint64_t top,
Spencer Kub7028eb2021-10-26 15:27:35 +080055 std::vector<std::string>& logEntries, size_t& logCount)
56 {
57 constexpr int bufferLimitSize = 1024;
58 do
59 {
60 std::string bufferStr;
61 bufferStr.resize(bufferLimitSize);
62
63 int bytesRead = gzread(logStream, bufferStr.data(),
64 static_cast<unsigned int>(bufferStr.size()));
65 // On errors, gzread() shall return a value less than 0.
66 if (bytesRead < 0)
67 {
68 printErrorMessage(logStream);
69 return false;
70 }
71 bufferStr.resize(static_cast<size_t>(bytesRead));
72 if (!hostLogEntryParser(bufferStr, skip, top, logEntries, logCount))
73 {
Ed Tanous62598e32023-07-17 17:06:25 -070074 BMCWEB_LOG_ERROR("Error occurs during parsing host log.");
Spencer Kub7028eb2021-10-26 15:27:35 +080075 return false;
76 }
Ed Tanouse662eae2022-01-25 10:39:19 -080077 } while (gzeof(logStream) != 1);
Spencer Kub7028eb2021-10-26 15:27:35 +080078
79 return true;
80 }
81
Nan Zhou9739de92022-04-06 11:07:27 -070082 bool hostLogEntryParser(const std::string& bufferStr, uint64_t skip,
83 uint64_t top, std::vector<std::string>& logEntries,
Spencer Kub7028eb2021-10-26 15:27:35 +080084 size_t& logCount)
85 {
86 // Assume we have 8 files, and the max size of each file is
87 // 16k, so define the max size as 256kb (double of 8 files *
88 // 16kb)
89 constexpr size_t maxTotalFilesSize = 262144;
90
91 // It may contain several log entry in one line, and
92 // the end of each log entry will be '\r\n' or '\r'.
93 // So we need to go through and split string by '\n' and '\r'
94 size_t pos = bufferStr.find_first_of("\n\r");
95 size_t initialPos = 0;
96 std::string newLastMessage;
97
98 while (pos != std::string::npos)
99 {
Patrick Williams89492a12023-05-10 07:51:34 -0500100 std::string logEntry = bufferStr.substr(initialPos,
101 pos - initialPos);
Spencer Kub7028eb2021-10-26 15:27:35 +0800102 // Since there might be consecutive delimiters like "\r\n", we need
103 // to filter empty strings.
104 if (!logEntry.empty())
105 {
106 logCount++;
107 if (!lastMessage.empty())
108 {
109 logEntry.insert(0, lastMessage);
110 lastMessage.clear();
111 }
112 if (logCount > skip && logCount <= (skip + top))
113 {
114 totalFilesSize += logEntry.size();
115 if (totalFilesSize > maxTotalFilesSize)
116 {
Ed Tanous62598e32023-07-17 17:06:25 -0700117 BMCWEB_LOG_ERROR(
118 "File size exceeds maximum allowed size of {}",
119 maxTotalFilesSize);
Spencer Kub7028eb2021-10-26 15:27:35 +0800120 return false;
121 }
122 logEntries.push_back(logEntry);
123 }
124 }
125 else
126 {
127 // Handle consecutive delimiter. '\r\n' act as a single
128 // delimiter, the other case like '\n\n', '\n\r' or '\r\r' will
129 // push back a "\n" as a log.
130 std::string delimiters;
131 if (pos > 0)
132 {
133 delimiters = bufferStr.substr(pos - 1, 2);
134 }
135 // Handle consecutive delimiter but spilt between two files.
136 if (pos == 0 && !(lastDelimiter.empty()))
137 {
138 delimiters = lastDelimiter + bufferStr.substr(0, 1);
139 }
140 if (delimiters != "\r\n")
141 {
142 logCount++;
143 if (logCount > skip && logCount <= (skip + top))
144 {
145 totalFilesSize++;
146 if (totalFilesSize > maxTotalFilesSize)
147 {
Ed Tanous62598e32023-07-17 17:06:25 -0700148 BMCWEB_LOG_ERROR(
149 "File size exceeds maximum allowed size of {}",
150 maxTotalFilesSize);
Spencer Kub7028eb2021-10-26 15:27:35 +0800151 return false;
152 }
153 logEntries.emplace_back("\n");
154 }
155 }
156 }
157 initialPos = pos + 1;
158 pos = bufferStr.find_first_of("\n\r", initialPos);
159 }
160
161 // Store the last message
162 if (initialPos < bufferStr.size())
163 {
164 newLastMessage = bufferStr.substr(initialPos);
165 }
166 // If consecutive delimiter spilt by buffer or file, the last character
167 // must be the delimiter.
168 else if (initialPos == bufferStr.size())
169 {
170 lastDelimiter = std::string(1, bufferStr.back());
171 }
172 // If file doesn't contain any "\r" or "\n", initialPos should be zero
173 if (initialPos == 0)
174 {
175 // Solved an edge case that the log doesn't in skip and top range,
176 // but consecutive files don't contain a single delimiter, this
177 // lastMessage becomes unnecessarily large. Since last message will
178 // prepend to next log, logCount need to plus 1
179 if ((logCount + 1) > skip && (logCount + 1) <= (skip + top))
180 {
181 lastMessage.insert(
182 lastMessage.end(),
183 std::make_move_iterator(newLastMessage.begin()),
184 std::make_move_iterator(newLastMessage.end()));
185
186 // Following the previous question, protect lastMessage don't
187 // larger than max total files size
188 size_t tmpMessageSize = totalFilesSize + lastMessage.size();
189 if (tmpMessageSize > maxTotalFilesSize)
190 {
Ed Tanous62598e32023-07-17 17:06:25 -0700191 BMCWEB_LOG_ERROR(
192 "File size exceeds maximum allowed size of {}",
193 maxTotalFilesSize);
Spencer Kub7028eb2021-10-26 15:27:35 +0800194 return false;
195 }
196 }
197 }
198 else
199 {
200 if (!newLastMessage.empty())
201 {
202 lastMessage = std::move(newLastMessage);
203 }
204 }
205 return true;
206 }
207
208 public:
209 GzFileReader() = default;
210 ~GzFileReader() = default;
211 GzFileReader(const GzFileReader&) = delete;
212 GzFileReader& operator=(const GzFileReader&) = delete;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800213 GzFileReader(GzFileReader&&) = delete;
214 GzFileReader& operator=(GzFileReader&&) = delete;
Spencer Kub7028eb2021-10-26 15:27:35 +0800215};