1/*
2 * Copyright (c) 2022 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 "wukong_logger.h"
17
18#include <algorithm>
19#include <chrono>
20#include <cstdarg>
21#include <cstdio>
22#include <cstring>
23#include <dirent.h>
24#include <fstream>
25#include <hilog/log_c.h>
26#include <iostream>
27#include <sys/stat.h>
28#include <sys/time.h>
29#include <sys/types.h>
30#include <thread>
31#include <unistd.h>
32
33#include "securec.h"
34#include "string_ex.h"
35#include "wukong_define.h"
36#include "wukong_util.h"
37
38namespace OHOS {
39namespace WuKong {
40namespace {
41const std::string DEFAULT_DIR = "/data/local/tmp/wukong/report/";
42const std::string LOGGER_THREAD_NAME = "wukong_logger";
43const int LOG_CONTENT_LENGTH = 256;
44const int LOG_PRINTER_TIMEOUT = 500;
45std::mutex LOCK_PRINT_BUFFER;
46}  // namespace
47
48WuKongLogger::WuKongLogger() : logPrinter_()
49{ /* init buf */
50}
51std::mutex WuKongLogger::wukongMutex_;
52std::shared_ptr<WuKongLogger> WuKongLogger::wukongInstance_ = nullptr;
53/**
54 * @brief: release logfile fd
55 */
56WuKongLogger::~WuKongLogger()
57{
58    if (outputLevel_ <= LOG_LEVEL_TRACK) {
59        std::cout << "Logger::~Logger" << std::endl;
60    }
61
62    // check logPrinter thread is running, and stop
63    if (printerRunning_ && logPrinter_.IsRunning()) {
64        Stop();
65    }
66    std::cout << "WuKongLogger::~WuKongLogger" << std::endl;
67}
68
69std::shared_ptr<WuKongLogger> WuKongLogger::GetInstance()
70{
71    if (wukongInstance_ == nullptr) {
72        std::lock_guard<std::mutex> lock(wukongMutex_);
73        if (wukongInstance_ == nullptr) {
74            wukongInstance_ = DelayedSingleton::GetInstance();
75        }
76    }
77    return wukongInstance_;
78}
79
80bool WuKongLogger::Start()
81{
82    if (outputLevel_ <= LOG_LEVEL_TRACK) {
83        std::cout << "Logger::Start" << std::endl;
84    }
85
86    logFileName_ = WuKongUtil::GetInstance()->GetCurrentTestDir() + "wukong.log";
87
88    if (logPrinter_.IsRunning()) {
89        DEBUG_LOG("Logger already started");
90        return true;
91    }
92
93    /* start read Queue Thread */
94    printerRunning_ = true;
95    logPrinter_.Start(LOGGER_THREAD_NAME);
96
97    // wait print thread started.
98    do {
99        std::unique_lock<std::mutex> lk(mtxThreadWait_);
100        cvWaitPrint_.wait(lk);
101    } while (false);
102    return true;
103}
104
105void WuKongLogger::Stop()
106{
107    /* release readQueueThread */
108    if (outputLevel_ <= LOG_LEVEL_TRACK) {
109        std::cout << "Logger::Stop" << std::endl;
110    }
111    if (printerRunning_ && logPrinter_.IsRunning()) {
112        printerRunning_ = false;
113        cvWaitPrint_.notify_all();
114        logPrinter_.NotifyExitSync();
115    }
116}
117
118void WuKongLogger::Print(LOG_LEVEL level, const char *format, ...)
119{
120    if (!printerRunning_) {
121        return;
122    }
123    LOCK_PRINT_BUFFER.lock();
124    char writeBuf[LOG_CONTENT_LENGTH] = {0};
125    /* check logger_level */
126    if (level < outputLevel_ && level < LOG_LEVEL_DEBUG) {
127        LOCK_PRINT_BUFFER.unlock();
128        return;
129    }
130    /* format output content */
131    va_list args;
132    va_start(args, format);
133    int ret = vsnprintf_s(writeBuf, LOG_CONTENT_LENGTH, LOG_CONTENT_LENGTH, format, args);
134    if (ret < 0) {
135        va_end(args);
136        LOCK_PRINT_BUFFER.unlock();
137        return;
138    }
139    va_end(args);
140
141    // write lock avoid write conflicts
142    LogInfo logInfo;
143    if (outputLevel_ >= LOG_LEVEL_TRACK) {
144        time_t currentTime = time(0);
145        char *timeChar = ctime(&currentTime);
146        logInfo.logStr_.append(timeChar, strlen(timeChar) - 1);
147        logInfo.logStr_.append(" : ");
148    }
149    logInfo.logStr_.append(writeBuf, strlen(writeBuf));
150    logInfo.level_ = level;
151    LOCK_PRINT_BUFFER.unlock();
152    // if log level is less than LOG_LEVEL_DEBUG, cout print.
153    if (outputLevel_ <= LOG_LEVEL_TRACK) {
154        std::cout << logInfo.logStr_ << std::endl;
155    }
156
157    // push log to buffer queue.
158    mtxQueue_.lock();
159    bufferQueue_.push(logInfo);
160    mtxQueue_.unlock();
161
162    // notify print log to printer thread.
163    cvWaitPrint_.notify_all();
164}
165
166/**
167 * @brief read queue and print log to file
168 */
169bool WuKongLogger::PrinterThread::Run()
170{
171    std::shared_ptr<WuKongLogger> self = WuKongLogger::GetInstance();
172    if (!self->printerRunning_) {
173        return false;
174    }
175    self->cvWaitPrint_.notify_all();
176    std::unique_lock<std::mutex> lk(self->mtxThreadWait_);
177    std::queue<LogInfo> tmpBuffer;
178    std::ofstream printer(self->logFileName_);
179    if (!printer.is_open()) {
180        std::cout << "ERR: Logger printer file cannot open" << std::endl;
181        return false;
182    }
183    /* read form queue output target fd */
184    printer << "WuKongLogger::PrinterThread::Run start" << std::endl;
185    const auto timeout = std::chrono::milliseconds(LOG_PRINTER_TIMEOUT);
186    while (true) {
187        // wait new log
188        if (self->printerRunning_) {
189            if (self->outputLevel_ <= LOG_LEVEL_TRACK) {
190                printer << "WuKongLogger::PrinterThread::Run wait start" << std::endl;
191            }
192            self->cvWaitPrint_.wait_for(lk, timeout);
193            if (self->outputLevel_ <= LOG_LEVEL_TRACK) {
194                printer << "WuKongLogger::PrinterThread::Run wait end" << std::endl;
195            }
196        }
197        // read queue buffer to thread buffer.
198        self->mtxQueue_.lock();
199        // the buffer queue is empty and main wait stop, return this thread.
200        if (self->bufferQueue_.empty() && !self->printerRunning_) {
201            self->mtxQueue_.unlock();
202            break;
203        }
204        while (!self->bufferQueue_.empty()) {
205            tmpBuffer.push(self->bufferQueue_.front());
206            self->bufferQueue_.pop();
207        }
208        self->mtxQueue_.unlock();
209        // print log to file and std::cout and hilog
210        while (!tmpBuffer.empty()) {
211            auto logInfo = tmpBuffer.front();
212            tmpBuffer.pop();
213            // output file fd
214            if (self->outputType_ & FILE_OUTPUT) {
215                printer << logInfo.logStr_ << std::endl;
216            }
217            // doesn't print STDOUT and HILOG, when log level less than output level.
218            if (logInfo.level_ < self->outputLevel_) {
219                continue;
220            }
221            // output STDOUT
222            if ((self->outputType_ & STD_OUTPUT) && (self->outputLevel_ > LOG_LEVEL_TRACK)) {
223                std::cout << logInfo.logStr_ << std::endl;
224            }
225            // output HILOG
226            if (self->outputType_ & HILOG_OUTPUT) {
227                HILOG_INFO(LOG_CORE, "%{public}s", logInfo.logStr_.c_str());
228            }
229        }
230    }
231    printer << "WuKongLogger::PrinterThread::Run end" << std::endl;
232    printer.close();
233    return false;
234}
235}  // namespace WuKong
236}  // namespace OHOS
237