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