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