• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &currentDateFlag);
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