1 /*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
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 "util.h"
17 #include <fstream>
18 #include <regex>
19
20 #include <sys/prctl.h>
21 #include <sys/stat.h>
22 #include <sys/syscall.h>
23 #include <unistd.h>
24
25 #include "aggregator.h"
26 #include "error_multimodal.h"
27 #include "securec.h"
28
29 #undef MMI_LOG_TAG
30 #define MMI_LOG_TAG "Util"
31
32 namespace OHOS {
33 namespace MMI {
34 namespace {
35 constexpr int32_t FILE_SIZE_MAX { 0x6C445 };
36 constexpr int32_t MAX_PRO_FILE_SIZE { 128000 };
37 constexpr int32_t INVALID_FILE_SIZE { -1 };
38 constexpr int32_t MIN_INTERVALTIME { 36 };
39 constexpr int32_t MAX_INTERVALTIME { 100 };
40 constexpr int32_t MIN_DELAYTIME { 300 };
41 constexpr int32_t MAX_DELAYTIME { 1000 };
42 constexpr int32_t COMMENT_SUBSCRIPT { 0 };
43 const std::string CONFIG_ITEM_REPEAT = "Key.autorepeat";
44 const std::string CONFIG_ITEM_DELAY = "Key.autorepeat.delaytime";
45 const std::string CONFIG_ITEM_INTERVAL = "Key.autorepeat.intervaltime";
46 const std::string CONFIG_ITEM_TYPE = "Key.keyboard.type";
47 const std::string CURSORSTYLE_PATH = "/system/etc/multimodalinput/mouse_icon/";
48 const std::string DATA_PATH = "/data";
49 const std::string INPUT_PATH = "/system/";
50 const std::string SYS_PROD_PATH = "/sys_prod/";
51 const std::string KEY_PATH = "/vendor/etc/keymap/";
52 constexpr size_t BUF_TID_SIZE { 10 };
53 constexpr size_t BUF_CMD_SIZE { 512 };
54 constexpr size_t PROGRAM_NAME_SIZE { 256 };
55 constexpr int32_t TIME_CONVERSION_UNIT { 1000 };
56 constexpr int32_t COLOR_FIXEX_WIDTH { 6 };
57 const std::string COLOR_PREFIX = "#";
58 const char COLOR_FILL = '0';
59 } // namespace
60
GetSysClockTime()61 int64_t GetSysClockTime()
62 {
63 struct timespec ts = { 0, 0 };
64 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
65 MMI_HILOGD("clock_gettime failed:%{public}d", errno);
66 return 0;
67 }
68 return (ts.tv_sec * TIME_CONVERSION_UNIT * TIME_CONVERSION_UNIT) + (ts.tv_nsec / TIME_CONVERSION_UNIT);
69 }
70
GetMillisTime()71 int64_t GetMillisTime()
72 {
73 auto timeNow = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now());
74 auto tmp = std::chrono::duration_cast<std::chrono::milliseconds>(timeNow.time_since_epoch());
75 return tmp.count();
76 }
77
GetThisThreadIdOfString()78 static std::string GetThisThreadIdOfString()
79 {
80 thread_local std::string threadLocalId;
81 if (threadLocalId.empty()) {
82 long tid = syscall(SYS_gettid);
83 char buf[BUF_TID_SIZE] = {};
84 const int32_t ret = sprintf_s(buf, BUF_TID_SIZE, "%06d", tid);
85 if (ret < 0) {
86 printf("ERR: in %s, #%d, call sprintf_s failed, ret = %d", __func__, __LINE__, ret);
87 return threadLocalId;
88 }
89 buf[BUF_TID_SIZE - 1] = '\0';
90 threadLocalId = buf;
91 }
92
93 return threadLocalId;
94 }
95
GetThisThreadId()96 uint64_t GetThisThreadId()
97 {
98 std::string stid = GetThisThreadIdOfString();
99 auto tid = std::atoll(stid.c_str());
100 return tid;
101 }
102
StringToken(std::string & str,const std::string & sep,std::string & token)103 static size_t StringToken(std::string &str, const std::string &sep, std::string &token)
104 {
105 token = "";
106 if (str.empty()) {
107 return str.npos;
108 }
109 size_t pos = str.npos;
110 size_t tmp = 0;
111 for (auto &item : sep) {
112 tmp = str.find(item);
113 if (str.npos != tmp) {
114 pos = (std::min)(pos, tmp);
115 }
116 }
117 if (str.npos != pos) {
118 token = str.substr(0, pos);
119 if (str.npos != pos + 1) {
120 str = str.substr(pos + 1, str.npos);
121 }
122 if (pos == 0) {
123 return StringToken(str, sep, token);
124 }
125 } else {
126 token = str;
127 str = "";
128 }
129 return token.size();
130 }
131
StringSplit(const std::string & str,const std::string & sep,std::vector<std::string> & vecList)132 size_t StringSplit(const std::string &str, const std::string &sep, std::vector<std::string> &vecList)
133 {
134 size_t size;
135 auto strs = str;
136 std::string token;
137 while (str.npos != (size = StringToken(strs, sep, token))) {
138 vecList.push_back(token);
139 }
140 return vecList.size();
141 }
142
IdsListToString(const std::vector<int32_t> & list,const std::string & sep)143 std::string IdsListToString(const std::vector<int32_t> &list, const std::string &sep)
144 {
145 std::string str;
146 for (const auto &it : list) {
147 str += std::to_string(it) + sep;
148 }
149 if (str.size() > 0) {
150 str.resize(str.size() - sep.size());
151 }
152 return str;
153 }
154
GetPid()155 int32_t GetPid()
156 {
157 return static_cast<int32_t>(getpid());
158 }
159
GetFileName(const std::string & strPath)160 static std::string GetFileName(const std::string &strPath)
161 {
162 size_t nPos = strPath.find_last_of('/');
163 if (strPath.npos == nPos) {
164 nPos = strPath.find_last_of('\\');
165 }
166 if (strPath.npos == nPos) {
167 return strPath;
168 }
169
170 return strPath.substr(nPos + 1, strPath.npos);
171 }
172
GetProgramName()173 const char *GetProgramName()
174 {
175 static char programName[PROGRAM_NAME_SIZE] = {};
176 if (programName[0] != '\0') {
177 return programName;
178 }
179
180 char buf[BUF_CMD_SIZE] = { 0 };
181 if (sprintf_s(buf, BUF_CMD_SIZE, "/proc/%d/cmdline", static_cast<int32_t>(getpid())) == -1) {
182 KMSG_LOGE("GetProcessInfo sprintf_s /proc/.../cmdline error");
183 return "";
184 }
185 FILE *fp = fopen(buf, "rb");
186 CHKPS(fp);
187 static constexpr size_t bufLineSize = 512;
188 char bufLine[bufLineSize] = { 0 };
189 if ((fgets(bufLine, bufLineSize, fp) == nullptr)) {
190 KMSG_LOGE("fgets failed");
191 if (fclose(fp) != 0) {
192 KMSG_LOGW("Close file:%s failed", buf);
193 }
194 fp = nullptr;
195 return "";
196 }
197 if (fclose(fp) != 0) {
198 KMSG_LOGW("Close file:%s failed", buf);
199 }
200 fp = nullptr;
201
202 std::string tempName(bufLine);
203 tempName = GetFileName(tempName);
204 if (tempName.empty()) {
205 KMSG_LOGE("tempName is empty");
206 return "";
207 }
208 const size_t copySize = std::min(tempName.size(), PROGRAM_NAME_SIZE - 1);
209 if (copySize == 0) {
210 KMSG_LOGE("The copySize is 0");
211 return "";
212 }
213 errno_t ret = memcpy_s(programName, PROGRAM_NAME_SIZE, tempName.c_str(), copySize);
214 if (ret != EOK) {
215 return "";
216 }
217 KMSG_LOGI("GetProgramName success. programName = %s", programName);
218
219 return programName;
220 }
221
SetThreadName(const std::string & name)222 void SetThreadName(const std::string &name)
223 {
224 prctl(PR_SET_NAME, name.c_str());
225 }
226
IsFileExists(const std::string & fileName)227 static bool IsFileExists(const std::string &fileName)
228 {
229 return (access(fileName.c_str(), F_OK) == 0);
230 }
231
CheckFileExtendName(const std::string & filePath,const std::string & checkExtension)232 static bool CheckFileExtendName(const std::string &filePath, const std::string &checkExtension)
233 {
234 std::string::size_type pos = filePath.find_last_of('.');
235 if (pos == std::string::npos) {
236 MMI_HILOGE("File is not find extension");
237 return false;
238 }
239 return (filePath.substr(pos + 1, filePath.npos) == checkExtension);
240 }
241
GetFileSize(const std::string & filePath)242 static int32_t GetFileSize(const std::string &filePath)
243 {
244 struct stat statbuf = { 0 };
245 if (stat(filePath.c_str(), &statbuf) != 0) {
246 MMI_HILOGE("Get file size error");
247 return INVALID_FILE_SIZE;
248 }
249 return statbuf.st_size;
250 }
251
ReadFile(const std::string & filePath)252 static std::string ReadFile(const std::string &filePath)
253 {
254 FILE *fp = fopen(filePath.c_str(), "r");
255 CHKPS(fp);
256 std::string dataStr;
257 char buf[256] = {};
258 while (fgets(buf, sizeof(buf), fp) != nullptr) {
259 dataStr += buf;
260 }
261 if (fclose(fp) != 0) {
262 MMI_HILOGW("Close file failed");
263 }
264 return dataStr;
265 }
266
IsValidPath(const std::string & rootDir,const std::string & filePath)267 static bool IsValidPath(const std::string &rootDir, const std::string &filePath)
268 {
269 return (filePath.compare(0, rootDir.size(), rootDir) == 0);
270 }
271
IsValidJsonPath(const std::string & filePath)272 bool IsValidJsonPath(const std::string &filePath)
273 {
274 return (IsValidPath(DATA_PATH, filePath) ||
275 IsValidPath(INPUT_PATH, filePath) ||
276 IsValidPath(SYS_PROD_PATH, filePath));
277 }
278
IsValidProPath(const std::string & filePath)279 static bool IsValidProPath(const std::string &filePath)
280 {
281 return IsValidPath(KEY_PATH, filePath);
282 }
283
IsValidTomlPath(const std::string & filePath)284 static bool IsValidTomlPath(const std::string &filePath)
285 {
286 return IsValidPath(KEY_PATH, filePath);
287 }
288
ReadProFile(const std::string & filePath,int32_t deviceId,std::map<int32_t,std::map<int32_t,int32_t>> & configMap)289 void ReadProFile(const std::string &filePath, int32_t deviceId,
290 std::map<int32_t, std::map<int32_t, int32_t>> &configMap)
291 {
292 CALL_DEBUG_ENTER;
293 if (filePath.empty()) {
294 MMI_HILOGE("FilePath is empty");
295 return;
296 }
297 char realPath[PATH_MAX] = {};
298 CHKPV(realpath(filePath.c_str(), realPath));
299 if (!IsValidProPath(realPath)) {
300 MMI_HILOGE("File path is error");
301 return;
302 }
303 if (!IsFileExists(realPath)) {
304 MMI_HILOGE("File is not existent");
305 return;
306 }
307 if (!CheckFileExtendName(realPath, "pro")) {
308 MMI_HILOGE("Unable to parse files other than json format");
309 return;
310 }
311 auto fileSize = GetFileSize(realPath);
312 if ((fileSize == INVALID_FILE_SIZE) || (fileSize >= MAX_PRO_FILE_SIZE)) {
313 MMI_HILOGE("The configuration file size is incorrect");
314 return;
315 }
316 ReadProConfigFile(realPath, deviceId, configMap);
317 }
318
IsNum(const std::string & str)319 static inline bool IsNum(const std::string &str)
320 {
321 std::istringstream sin(str);
322 double num;
323 return (sin >> num) && sin.eof();
324 }
325
ReadProConfigFile(const std::string & realPath,int32_t deviceId,std::map<int32_t,std::map<int32_t,int32_t>> & configKey)326 void ReadProConfigFile(const std::string &realPath, int32_t deviceId,
327 std::map<int32_t, std::map<int32_t, int32_t>> &configKey)
328 {
329 CALL_DEBUG_ENTER;
330 std::ifstream reader(realPath);
331 if (!reader.is_open()) {
332 MMI_HILOGE("Failed to open config file");
333 return;
334 }
335 std::string strLine;
336 int32_t sysKeyValue;
337 int32_t nativeKeyValue;
338 int32_t elementKey = 0;
339 int32_t elementValue = 0;
340 std::map<int32_t, int32_t> tmpConfigKey;
341 while (std::getline(reader, strLine)) {
342 const char* line = strLine.c_str();
343 int32_t len = strlen(line);
344 char* realLine = static_cast<char*>(malloc(len + 1));
345 CHKPV(realLine);
346 if (strcpy_s(realLine, len + 1, line) != EOK) {
347 MMI_HILOGE("strcpy_s error");
348 free(realLine);
349 realLine = nullptr;
350 return;
351 }
352 *(realLine + len + 1) = '\0';
353 int32_t ret = ReadConfigInfo(realLine, len, &elementKey, &elementValue);
354 free(realLine);
355 realLine = nullptr;
356 if (ret != RET_OK) {
357 MMI_HILOGE("Failed to read from line of config info");
358 reader.close();
359 return;
360 }
361 nativeKeyValue = elementKey;
362 sysKeyValue = elementValue;
363 MMI_HILOGD("The nativeKeyValue is:%{public}d, sysKeyValue is:%{public}d", nativeKeyValue, sysKeyValue);
364 tmpConfigKey.insert(std::pair<int32_t, int32_t>(nativeKeyValue, sysKeyValue));
365 }
366 reader.close();
367 auto iter = configKey.insert(std::make_pair(deviceId, tmpConfigKey));
368 if (!iter.second) {
369 MMI_HILOGE("The file name is duplicated");
370 return;
371 }
372 }
373
ReadJsonFile(const std::string & filePath)374 std::string ReadJsonFile(const std::string &filePath)
375 {
376 if (filePath.empty()) {
377 MMI_HILOGE("FilePath is empty");
378 return "";
379 }
380 char realPath[PATH_MAX] = {};
381 CHKPS(realpath(filePath.c_str(), realPath));
382 if (!IsValidJsonPath(realPath)) {
383 MMI_HILOGE("File path is error");
384 return "";
385 }
386 if (!CheckFileExtendName(realPath, "json")) {
387 MMI_HILOGE("Unable to parse files other than json format");
388 return "";
389 }
390 if (!IsFileExists(realPath)) {
391 MMI_HILOGE("File is not existent");
392 return "";
393 }
394 int32_t fileSize = GetFileSize(realPath);
395 if ((fileSize <= 0) || (fileSize > FILE_SIZE_MAX)) {
396 MMI_HILOGE("File size out of read range");
397 return "";
398 }
399 return ReadFile(filePath);
400 }
401
ConfigItemSwitch(const std::string & configItem,const std::string & value,DeviceConfig & devConf)402 static int32_t ConfigItemSwitch(const std::string &configItem, const std::string &value, DeviceConfig &devConf)
403 {
404 CALL_DEBUG_ENTER;
405 if (configItem.empty() || value.empty()) {
406 MMI_HILOGE("Get key config item is invalid");
407 return RET_ERR;
408 }
409 if (!IsNum(value)) {
410 MMI_HILOGE("Get key config item is invalid");
411 return RET_ERR;
412 }
413 if (configItem == CONFIG_ITEM_REPEAT) {
414 devConf.autoSwitch = stoi(value);
415 } else if (configItem == CONFIG_ITEM_DELAY) {
416 devConf.delayTime = stoi(value);
417 if (devConf.delayTime < MIN_DELAYTIME || devConf.delayTime > MAX_DELAYTIME) {
418 MMI_HILOGE("Unusual the delaytime");
419 return RET_ERR;
420 }
421 } else if (configItem == CONFIG_ITEM_INTERVAL) {
422 devConf.intervalTime = stoi(value);
423 if (devConf.intervalTime < MIN_INTERVALTIME || devConf.intervalTime > MAX_INTERVALTIME) {
424 MMI_HILOGE("Unusual the intervaltime");
425 return RET_ERR;
426 }
427 } else if (configItem == CONFIG_ITEM_TYPE) {
428 devConf.keyboardType = stoi(value);
429 }
430 return RET_OK;
431 }
432
ReadConfigFile(const std::string & realPath,DeviceConfig & devConf)433 static int32_t ReadConfigFile(const std::string &realPath, DeviceConfig &devConf)
434 {
435 CALL_DEBUG_ENTER;
436 std::ifstream cfgFile(realPath);
437 if (!cfgFile.is_open()) {
438 MMI_HILOGE("Failed to open config file");
439 return FILE_OPEN_FAIL;
440 }
441 std::string tmp;
442 while (std::getline(cfgFile, tmp)) {
443 RemoveSpace(tmp);
444 size_t pos = tmp.find('#');
445 if (pos != tmp.npos && pos != COMMENT_SUBSCRIPT) {
446 MMI_HILOGE("File format is error");
447 cfgFile.close();
448 return RET_ERR;
449 }
450 if (tmp.empty() || tmp.front() == '#') {
451 continue;
452 }
453 pos = tmp.find('=');
454 if ((pos == std::string::npos) || (tmp.back() == '=')) {
455 MMI_HILOGE("Find config item error");
456 cfgFile.close();
457 return RET_ERR;
458 }
459 std::string configItem = tmp.substr(0, pos);
460 std::string value = tmp.substr(pos + 1);
461 if (ConfigItemSwitch(configItem, value, devConf) == RET_ERR) {
462 MMI_HILOGE("Configuration item error");
463 cfgFile.close();
464 return RET_ERR;
465 }
466 }
467 cfgFile.close();
468 return RET_OK;
469 }
470
ReadTomlFile(const std::string & filePath,DeviceConfig & devConf)471 int32_t ReadTomlFile(const std::string &filePath, DeviceConfig &devConf)
472 {
473 if (filePath.empty()) {
474 MMI_HILOGE("FilePath is empty");
475 return RET_ERR;
476 }
477 char realPath[PATH_MAX] = {};
478 CHKPR(realpath(filePath.c_str(), realPath), RET_ERR);
479 if (!IsValidTomlPath(realPath)) {
480 MMI_HILOGE("File path is error");
481 return RET_ERR;
482 }
483 if (!IsFileExists(realPath)) {
484 MMI_HILOGE("File is not existent");
485 return RET_ERR;
486 }
487 if (!CheckFileExtendName(realPath, "TOML")) {
488 MMI_HILOGE("Unable to parse files other than json format");
489 return RET_ERR;
490 }
491 int32_t fileSize = GetFileSize(realPath);
492 if ((fileSize <= 0) || (fileSize > FILE_SIZE_MAX)) {
493 MMI_HILOGE("File size out of read range");
494 return RET_ERR;
495 }
496 if (ReadConfigFile(realPath, devConf) == RET_ERR) {
497 MMI_HILOGE("Read device config file failed");
498 return RET_ERR;
499 }
500 return RET_OK;
501 }
502
ReadCursorStyleFile(const std::string & filePath)503 int32_t ReadCursorStyleFile(const std::string &filePath)
504 {
505 CALL_DEBUG_ENTER;
506 if (filePath.empty()) {
507 MMI_HILOGE("FilePath is empty");
508 return RET_ERR;
509 }
510 if (!IsFileExists(filePath)) {
511 MMI_HILOGE("File is not existent");
512 return RET_ERR;
513 }
514 char realPath[PATH_MAX] = {};
515 CHKPR(realpath(filePath.c_str(), realPath), RET_ERR);
516 int32_t fileSize = GetFileSize(realPath);
517 if ((fileSize <= 0) || (fileSize > FILE_SIZE_MAX)) {
518 MMI_HILOGE("File size out of read range");
519 return RET_ERR;
520 }
521 return RET_OK;
522 }
523
StringPrintf(const char * format,...)524 std::string StringPrintf(const char *format, ...)
525 {
526 char space[1024];
527
528 va_list ap;
529 va_start(ap, format);
530 std::string result;
531 int32_t ret = vsnprintf_s(space, sizeof(space), sizeof(space) - 1, format, ap);
532 if (ret >= RET_OK && (size_t)ret < sizeof(space)) {
533 result = space;
534 } else {
535 MMI_HILOGE("The buffer is overflow");
536 }
537 va_end(ap);
538 return result;
539 }
540
FileVerification(std::string & filePath,const std::string & checkExtension)541 std::string FileVerification(std::string &filePath, const std::string &checkExtension)
542 {
543 if (filePath.empty()) {
544 MMI_HILOGE("FilePath is empty");
545 return "";
546 }
547 char realPath[PATH_MAX] = {};
548 CHKPS(realpath(filePath.c_str(), realPath));
549 if (!IsFileExists(realPath)) {
550 MMI_HILOGE("File is not existent");
551 return "";
552 }
553 if (!CheckFileExtendName(realPath, checkExtension)) {
554 MMI_HILOGE("Unable to parse files other than json format");
555 return "";
556 }
557 int32_t fileSize = GetFileSize(realPath);
558 if ((fileSize <= 0) || (fileSize > FILE_SIZE_MAX)) {
559 MMI_HILOGE("File size out of read range");
560 return "";
561 }
562 return realPath;
563 }
564
ReadFile(const std::string & path,std::string & content)565 bool ReadFile(const std::string &path, std::string &content)
566 {
567 if (path.empty()) {
568 MMI_HILOGE("path is empty");
569 return false;
570 }
571 char realPath[PATH_MAX] = {};
572 CHKPF(realpath(path.c_str(), realPath));
573 std::ifstream file(realPath);
574 if (!file) {
575 return false;
576 }
577 std::stringstream buffer;
578 buffer << file.rdbuf();
579 content = buffer.str();
580 return true;
581 }
582
IntToHexRGB(int32_t color)583 std::string IntToHexRGB(int32_t color)
584 {
585 std::ostringstream oss;
586 oss << COLOR_PREFIX << std::setfill(COLOR_FILL) << std::setw(COLOR_FIXEX_WIDTH) << std::hex << std::uppercase
587 << color;
588 return oss.str();
589 }
590
StringReplace(std::string & str,const std::string & oldStr,const std::string & newStr)591 void StringReplace(std::string &str, const std::string &oldStr, const std::string &newStr)
592 {
593 std::regex re(oldStr);
594 str = std::regex_replace(str, re, newStr);
595 }
596
Record(const LogHeader & lh,const std::string & key,const std::string & record)597 bool Aggregator::Record(const LogHeader &lh, const std::string &key, const std::string &record)
598 {
599 constexpr int32_t oneSecond = 1000;
600 if (timerId_ != -1) {
601 resetTimer_(timerId_);
602 } else {
603 timerId_ = addTimer_(oneSecond, 1, [this, lh]() {
604 FlushRecords(lh);
605 timerId_ = -1;
606 });
607 }
608 if (key == key_) {
609 auto now = std::chrono::system_clock::now();
610 records_.push_back({record, now});
611 if (records_.size() >= maxRecordCount_) {
612 FlushRecords(lh);
613 }
614 return true;
615 } else {
616 FlushRecords(lh, key, record);
617 key_ = key;
618 return false;
619 }
620 }
621
FlushRecords(const LogHeader & lh,const std::string & key,const std::string & extraRecord)622 void Aggregator::FlushRecords(const LogHeader &lh, const std::string &key, const std::string &extraRecord)
623 {
624 constexpr uint32_t milliSecondWidth = 3;
625 constexpr uint32_t microToMilli = 1000;
626 size_t recordCount = records_.size();
627 std::ostringstream oss;
628 if (!records_.empty()) {
629 oss << key_;
630 oss << ", first: " << records_.front().record << "-(";
631 auto firstTime = records_.front().timestamp;
632 time_t firstTimeT = std::chrono::system_clock::to_time_t(firstTime);
633 std::tm *bt = std::localtime(&firstTimeT);
634 if (bt == nullptr) {
635 MMI_HILOGE("The bt is nullptr, this is a invalid time");
636 return;
637 }
638 oss << std::put_time(bt, "%Y-%m-%d %H:%M:%S");
639 auto since_epoch = firstTime.time_since_epoch();
640 auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(since_epoch).count() % microToMilli;
641 oss << '.' << std::setfill('0') << std::setw(milliSecondWidth) << millis << "ms)";
642
643 if (records_.size() > 1) {
644 size_t i = records_.size() - 1;
645 const auto &recordInfo = records_[i];
646 oss << ", " << recordInfo.record;
647 }
648 records_.clear();
649 oss << ", count: " << recordCount;
650 }
651 if (!extraRecord.empty()) {
652 if (!oss.str().empty()) {
653 oss << ", last: ";
654 }
655 oss << key << ": " << extraRecord;
656 }
657 if (!oss.str().empty()) {
658 MMI_HILOG_HEADER(LOG_INFO, lh, "%{public}s", oss.str().c_str());
659 }
660 }
661
~Aggregator()662 Aggregator::~Aggregator()
663 {
664 FlushRecords(MMI_LOG_HEADER);
665 }
666 } // namespace MMI
667 } // namespace OHOS
668