1 /*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2023. All rights reserved.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "sp_log.h"
17 #include "securec.h"
18 #include <sys/stat.h>
19 #include <string>
20 #include <sstream>
21 #include <iostream>
22 #include <cstring>
23 #include <cstdint>
24 #include <dirent.h>
25 #include <unistd.h>
26 #include <mutex>
27 #include <ctime>
28 #include <fstream>
29 #include <vector>
30 #include <filesystem>
31 #include <iomanip>
32 #include <chrono>
33 #include <regex>
34 #include <thread>
35 #include <cstdlib>
36 #include <cstdio>
37 #include "include/smartperf_command.h"
38
39 #ifdef HI_LOG_ENABLE
40 #include "hilog/log.h"
41 #endif
42
43 namespace OHOS {
44 namespace SmartPerf {
45 const int32_t LOG_MAX_LEN = 10000;
46 const long long MAX_FILE_SIZE = 4 * 1024 * 1024; // 4MB
47 const double MAX_FILE_KEEP_TIME = 60 * 60 * 24 * 7; // 7days
48 const int MAX_FILE_COUNT = 256;
49 const mode_t LOG_FILE_MODE = 0755;
50 std::mutex g_mtx;
51 std::string g_currentLogFilePath;
52 std::string g_currentDate;
53 bool g_writeEnable = false;
54
SpLogOut(SpLogLevel logLevel,const char * logBuf)55 static void SpLogOut(SpLogLevel logLevel, const char *logBuf)
56 {
57 #ifdef HI_LOG_ENABLE
58 LogLevel hiLogLevel = LOG_INFO;
59 switch (logLevel) {
60 case SP_LOG_DEBUG:
61 hiLogLevel = LOG_DEBUG;
62 break;
63 case SP_LOG_INFO:
64 hiLogLevel = LOG_INFO;
65 break;
66 case SP_LOG_WARN:
67 hiLogLevel = LOG_WARN;
68 break;
69 case SP_LOG_ERROR:
70 hiLogLevel = LOG_ERROR;
71 break;
72 default:
73 break;
74 }
75 (void)HiLogPrint(LOG_CORE, hiLogLevel, LOG_DOMAIN, "SP_daemon", "%{public}s", logBuf);
76 #else
77 switch (logLevel) {
78 case SP_LOG_DEBUG:
79 printf("[D]%s\n", logBuf);
80 break;
81 case SP_LOG_INFO:
82 printf("[I]%s\n", logBuf);
83 break;
84 case SP_LOG_WARN:
85 printf("[W]%s\n", logBuf);
86 break;
87 case SP_LOG_ERROR:
88 printf("[E]%s\n", logBuf);
89 break;
90 default:
91 break;
92 }
93 #endif
94 }
95
EnsureLogDirectoryExists()96 static bool EnsureLogDirectoryExists()
97 {
98 if (!std::__fs::filesystem::exists(LOG_FILE_DIR)) {
99 LOGD("Try create dir: %s", LOG_FILE_DIR.c_str());
100 try {
101 if (mkdir(LOG_FILE_DIR.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) != 0) {
102 LOGE("Failed to create log directory: %s", strerror(errno));
103 return false;
104 }
105
106 if (!std::__fs::filesystem::exists(LOG_FILE_DIR)) {
107 LOGE("Failed to create log directory.");
108 return false;
109 }
110 } catch (const std::exception& e) {
111 LOGE("Exception while creating log directory: %s", e.what());
112 return false;
113 }
114 }
115
116 return true;
117 }
118
GetCurrentDate()119 static std::string GetCurrentDate()
120 {
121 auto now = std::chrono::system_clock::now();
122 std::time_t time = std::chrono::system_clock::to_time_t(now);
123 std::tm* tm = std::localtime(&time);
124 if (tm == nullptr) {
125 return "";
126 }
127
128 std::ostringstream oss;
129 oss << std::put_time(tm, "%Y%m%d");
130 return oss.str();
131 }
GetCurrentLogFilePath(int maxLogNumber)132 static std::string GetCurrentLogFilePath(int maxLogNumber)
133 {
134 auto now = std::chrono::system_clock::now();
135 std::time_t time = std::chrono::system_clock::to_time_t(now);
136 std::tm* tm = std::localtime(&time);
137 if (tm == nullptr) {
138 return "";
139 }
140 std::ostringstream oss;
141 oss << LOG_FILE_DIR << "log." << maxLogNumber << "." <<
142 g_currentDate << "-" << std::put_time(tm, "%H%M%S");
143 return oss.str();
144 }
IsFilePathValid(const std::string & filePath)145 static bool IsFilePathValid(const std::string& filePath)
146 {
147 char filePathChar[PATH_MAX] = {0x00};
148 if ((realpath(filePath.c_str(), filePathChar) == nullptr)) {
149 LOGE("Log file %s is not exist.", filePath.c_str());
150 return false;
151 }
152 std::ifstream file(filePathChar, std::ios::binary | std::ios::ate);
153 if (file.is_open()) {
154 return file.tellg() <= MAX_FILE_SIZE;
155 }
156 return false;
157 }
158
CheckFileNameAndGetNumber(const std::string & fileName,const std::string & date,bool * currentDateFlag)159 static int CheckFileNameAndGetNumber(const std::string& fileName, const std::string& date, bool* currentDateFlag)
160 {
161 std::regex pattern(R"(^log\.(\d+)\.(\d{8})-\d{6}$)");
162 std::smatch matches;
163
164 if (std::regex_match(fileName, matches, pattern)) {
165 std::string fileDate = matches[2].str();
166 if (fileDate == date) {
167 *currentDateFlag = true;
168 }
169 return SPUtilesTye::StringToSometype<int>(matches[1].str());
170 }
171
172 return 0;
173 }
174
Chmod(const std::string & sourceFilePath,mode_t mode)175 static bool Chmod(const std::string& sourceFilePath, mode_t mode)
176 {
177 int retCode = chmod(sourceFilePath.c_str(), mode);
178 if (retCode != 0) {
179 LOGE("Failed to set %s permission, error code %d.", sourceFilePath.c_str(), retCode);
180 return false;
181 }
182
183 return true;
184 }
185
TarFile(const std::string & sourceFileName)186 static void TarFile(const std::string& sourceFileName)
187 {
188 std::ostringstream compressedFilePath;
189 compressedFilePath << LOG_FILE_DIR << sourceFileName << ".tar.gz";
190
191 std::string tarStr = compressedFilePath.str() + " -C " + LOG_FILE_DIR + " " + sourceFileName;
192 std::string tarCommand = CMD_COMMAND_MAP.at(CmdCommand::TAR) + tarStr;
193 std::string cmdResult;
194 if (!SPUtils::LoadCmd(tarCommand, cmdResult)) {
195 LOGE("Failed to compress file %s", sourceFileName.c_str());
196 return;
197 }
198
199 Chmod(compressedFilePath.str(), LOG_FILE_MODE);
200
201 tarCommand = CMD_COMMAND_MAP.at(CmdCommand::REMOVE) + LOG_FILE_DIR + sourceFileName;
202 if (!SPUtils::LoadCmd(tarCommand, cmdResult)) {
203 LOGE("Failed to delete original file %s", sourceFileName.c_str());
204 return;
205 }
206
207 LOGD("Successfully compressed and deleted file: %s", sourceFileName.c_str());
208 return;
209 }
210
GetLogFilePath()211 static bool GetLogFilePath()
212 {
213 std::string date = GetCurrentDate();
214 if (date == "") {
215 LOGE("Get current date failed");
216 return false;
217 }
218 if ((date == g_currentDate) && IsFilePathValid(g_currentLogFilePath)) {
219 return true;
220 }
221 LOGE("Current log file path invalid: %s", g_currentLogFilePath.c_str());
222 g_currentDate = date;
223 std::string fileName;
224 bool currentDateFlag = false;
225 int fileNumber = 0;
226 for (const auto& entry : std::__fs::filesystem::directory_iterator(LOG_FILE_DIR)) {
227 if (entry.is_regular_file()) {
228 fileName = entry.path().filename().string();
229 fileNumber = CheckFileNameAndGetNumber(fileName, date, ¤tDateFlag);
230 if (fileNumber != 0) {
231 break;
232 }
233 }
234 }
235 if (fileNumber == 0) {
236 g_currentLogFilePath = GetCurrentLogFilePath(1);
237 if (g_currentLogFilePath == "") {
238 LOGE("Get current log file data is null");
239 return false;
240 }
241 return true;
242 }
243 std::string filePath = LOG_FILE_DIR + fileName;
244 if (currentDateFlag && IsFilePathValid(filePath)) {
245 g_currentLogFilePath = filePath;
246 return true;
247 }
248 TarFile(fileName);
249 if (fileNumber >= MAX_FILE_COUNT) {
250 LOGE("Log file full!");
251 return false;
252 }
253 g_currentLogFilePath = GetCurrentLogFilePath(fileNumber + 1);
254 return true;
255 }
256
WriteMessage(const char * logMessage)257 static void WriteMessage(const char* logMessage)
258 {
259 bool chmodFlag = !std::__fs::filesystem::exists(g_currentLogFilePath);
260
261 char logFilePathChar[PATH_MAX] = {0x00};
262 if ((realpath(g_currentLogFilePath.c_str(), logFilePathChar) == nullptr)) {
263 errno_t result = strncpy_s(logFilePathChar, PATH_MAX,
264 g_currentLogFilePath.c_str(), g_currentLogFilePath.size());
265 if (result != 0) {
266 LOGE("strncpy_s failed with error: %d", result);
267 return;
268 }
269 LOGI("Log file %s is not exist, will create", g_currentLogFilePath.c_str());
270 }
271
272 std::ofstream logFile(logFilePathChar, std::ios::app);
273 if (logFile.is_open()) {
274 if (chmodFlag) {
275 if (!Chmod(logFilePathChar, LOG_FILE_MODE)) {
276 logFile.close();
277 return;
278 }
279 }
280
281 auto now = std::chrono::system_clock::now();
282 std::time_t time = std::chrono::system_clock::to_time_t(now);
283 std::tm* tm = std::localtime(&time);
284 if (tm == nullptr) {
285 LOGE("Write Message get current data is null");
286 logFile.close();
287 return;
288 }
289
290 std::ostringstream timeStamp;
291 timeStamp << std::put_time(tm, "[%H:%M:%S]");
292 logFile << timeStamp.str() << logMessage << std::endl;
293
294 logFile.close();
295 } else {
296 LOGE("Unable to open log file for writing: %s", logFilePathChar);
297 }
298
299 return;
300 }
301
EnableWriteLogAndDeleteOldLogFiles()302 void EnableWriteLogAndDeleteOldLogFiles()
303 {
304 g_writeEnable = true;
305 std::lock_guard<std::mutex> lock(g_mtx);
306
307 if (!EnsureLogDirectoryExists()) {
308 return;
309 }
310
311 DIR* dir = opendir(LOG_FILE_DIR.c_str());
312 if (dir == nullptr) {
313 LOGE("Failed to open log directory: %s", LOG_FILE_DIR.c_str());
314 return;
315 }
316
317 struct dirent* entry;
318 time_t currentTime = time(nullptr);
319
320 while ((entry = readdir(dir)) != nullptr) {
321 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
322 continue;
323 }
324
325 std::string filePath = LOG_FILE_DIR + entry->d_name;
326 char filePathChar[PATH_MAX] = {0x00};
327 if ((realpath(filePath.c_str(), filePathChar) == nullptr)) {
328 LOGE("Log file %s is not exist.", filePath.c_str());
329 continue;
330 }
331
332 struct stat fileStat = {0};
333 if (stat(filePathChar, &fileStat) != 0) {
334 LOGE("Failed to get stats for file: %s", filePathChar);
335 continue;
336 }
337
338 double diffSeconds = difftime(currentTime, fileStat.st_mtime);
339 if (diffSeconds > MAX_FILE_KEEP_TIME) {
340 if (remove(filePathChar) == 0) {
341 LOGD("Deleted file: %s, last modified: %.2f seconds ago", filePathChar, diffSeconds);
342 } else {
343 LOGE("Failed to delete file: %s", filePathChar);
344 }
345 }
346 }
347
348 closedir(dir);
349 }
350
EscapeForCSV(std::string str)351 void EscapeForCSV(std::string str)
352 {
353 std::string escapedStr;
354 for (char ch : str) {
355 if (ch == '"') {
356 escapedStr += "\"\"";
357 } else if (ch == ',' || ch == '\n' || ch == '\r') {
358 escapedStr += '"';
359 escapedStr += ch;
360 escapedStr += '"';
361 } else {
362 escapedStr += ch;
363 }
364 }
365 str = escapedStr;
366 }
367
SpLog(SpLogLevel logLevel,bool isWriteLog,const char * fmt,...)368 void SpLog(SpLogLevel logLevel, bool isWriteLog, const char *fmt, ...)
369 {
370 if (fmt == nullptr) {
371 SpLogOut(logLevel, "SP log format string is NULL.");
372 return;
373 }
374 size_t fmtLength = strlen(fmt);
375 if (fmtLength == 0) {
376 SpLogOut(logLevel, "SP log format string is empty.");
377 return;
378 }
379 char logBuf[LOG_MAX_LEN] = {0};
380 int32_t ret = 0;
381 va_list arg;
382 va_start(arg, fmt);
383 va_list bkArg;
384 va_copy(bkArg, arg);
385 ret = vsnprintf_s(logBuf, sizeof(logBuf), sizeof(logBuf) - 1, fmt, bkArg);
386 va_end(bkArg);
387 va_end(arg);
388 if (ret < 0) {
389 SpLogOut(logLevel, "SP log length error.");
390 return;
391 }
392 if (ret >= static_cast<int32_t>(sizeof(logBuf) - 1)) {
393 SpLogOut(logLevel, "SP log error: log message truncated.");
394 return;
395 }
396 std::string logStr(logBuf);
397 EscapeForCSV(logStr);
398 SpLogOut(logLevel, logStr.c_str());
399
400 if (!isWriteLog) {
401 return;
402 }
403
404 std::lock_guard<std::mutex> lock(g_mtx);
405
406 if (!g_writeEnable || !EnsureLogDirectoryExists() || !GetLogFilePath()) {
407 return;
408 }
409
410 WriteMessage(logStr.c_str());
411 return;
412 }
413 } // namespace SmartPerf
414 } // namespace OHOS