1/*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2021. 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 "debug_logger.h"
17
18#include <ratio>
19
20#include "option.h"
21#if is_ohos
22#include "hiperf_hilog.h"
23#endif
24
25using namespace std::literals::chrono_literals;
26using namespace std::chrono;
27namespace OHOS {
28namespace Developtools {
29namespace NativeDaemon {
30DebugLogger::DebugLogger() : timeStamp_(steady_clock::now()), logPath_(DEFAULT_LOG_PATH)
31{
32    logFileBuffer_.resize(LOG_BUFFER_SIZE);
33    OpenLog();
34}
35
36ScopeDebugLevel::ScopeDebugLevel(DebugLevel level, bool mix)
37{
38    savedDebugLevel_ = DebugLogger::GetInstance()->SetLogLevel(level);
39    savedMixOutput_ = DebugLogger::GetInstance()->SetMixLogOutput(mix);
40}
41
42ScopeDebugLevel::~ScopeDebugLevel()
43{
44    DebugLogger::GetInstance()->SetLogLevel(savedDebugLevel_);
45    DebugLogger::GetInstance()->SetMixLogOutput(savedMixOutput_);
46}
47
48DebugLogger::~DebugLogger()
49{
50    Disable();
51    if (file_ != nullptr) {
52        fclose(file_);
53        file_ = nullptr;
54    }
55}
56
57void DebugLogger::Disable(bool disable)
58{
59    if (logDisabled_ != disable) {
60        logDisabled_ = disable;
61        if (!disable) {
62            // reopen the log file
63            OpenLog();
64        }
65    }
66}
67
68#if is_ohos
69#ifndef CONFIG_NO_HILOG
70void DebugLogger::HiLog(std::string &buffer) const
71{
72    size_t lastLF = buffer.find_last_of('\n');
73    if (lastLF != std::string::npos) {
74        buffer.erase(lastLF, 1);
75    }
76    HILOG_BASE_DEBUG(LOG_CORE, "%{public}s", buffer.c_str());
77}
78#endif
79#endif
80
81int DebugLogger::Log(DebugLevel level, const std::string &logTag, const char *fmt, ...) const
82{
83    constexpr const int DEFAULT_STRING_BUF_SIZE = 4096;
84#ifdef HIPERF_DEBUG_TIME
85    const auto startSprintf = steady_clock::now();
86#endif
87    const auto startTime = steady_clock::now();
88    if (!ShouldLog(level, logTag) or logDisabled_ or fmt == nullptr) {
89#ifdef HIPERF_DEBUG_TIME
90        logTimes_ += duration_cast<microseconds>(steady_clock::now() - startSprintf);
91#endif
92        return 0;
93    }
94    va_list va;
95    int ret = 0;
96
97    std::string buffer(DEFAULT_STRING_BUF_SIZE, '\0');
98    va_start(va, fmt);
99    ret = vsnprintf_s(buffer.data(), buffer.size(), buffer.size() - 1, fmt, va);
100    va_end(va);
101#ifdef HIPERF_DEBUG_TIME
102    logSprintfTimes_ += duration_cast<microseconds>(steady_clock::now() - startSprintf);
103#endif
104    if ((mixLogOutput_ and level < LEVEL_FATAL) or level == LEVEL_FATAL) {
105        ret = fprintf(stdout, "%s", buffer.data()); // to the stdout
106    }
107
108    if (enableHilog_) {
109#if is_ohos && !defined(CONFIG_NO_HILOG)
110        std::lock_guard<std::mutex> lock(logMutex_);
111        HiLog(buffer); // to the hilog
112#endif
113    } else if (file_ != nullptr) {
114        std::lock_guard<std::mutex> lock(logMutex_);
115#ifdef HIPERF_DEBUG_TIME
116        const auto startWriteTime = steady_clock::now();
117#endif
118        milliseconds timeStamp = duration_cast<milliseconds>(startTime - timeStamp_);
119        fprintf(file_, "%05" PRId64 " ms %s", (int64_t)timeStamp.count(), buffer.data()); // to the file
120#ifdef HIPERF_DEBUG_TIME
121        logWriteTimes_ += duration_cast<microseconds>(steady_clock::now() - startWriteTime);
122#endif
123    }
124
125#ifdef HIPERF_DEBUG_TIME
126    logTimes_ += duration_cast<microseconds>(steady_clock::now() - startTime);
127    logCount_++;
128#endif
129    if (level == LEVEL_FATAL && exitOnFatal_) {
130        fflush(file_);
131        logDisabled_ = true;
132        exit(-1);
133    }
134    return ret;
135}
136
137bool DebugLogger::EnableHiLog(bool enable)
138{
139    enableHilog_ = enable;
140    if (fprintf(stdout, "change to use hilog\n") < 0) {
141        // what can we do here ???
142    }
143    return enableHilog_;
144}
145
146bool DebugLogger::ShouldLog(DebugLevel level, const std::string &logtag) const
147{
148    return GetLogLevelByTag(logtag) <= level;
149}
150
151DebugLevel DebugLogger::SetLogLevel(DebugLevel debugLevel)
152{
153    DebugLevel lastLevel = DebugLogger::GetInstance()->debugLevel_;
154    debugLevel_ = debugLevel;
155    // force print
156    printf("setLogLevel %d\n", debugLevel);
157    return lastLevel;
158}
159
160bool DebugLogger::SetMixLogOutput(bool enable)
161{
162    bool lastMixLogOutput = mixLogOutput_;
163    mixLogOutput_ = enable;
164    return lastMixLogOutput;
165}
166
167bool DebugLogger::SetLogPath(const std::string &newLogPath)
168{
169    // make sure not write happend when rename
170    std::lock_guard<std::mutex> lock(logMutex_);
171    if (newLogPath.empty()) {
172        return false;
173    }
174    if (file_ != nullptr) {
175        fclose(file_);
176        file_ = nullptr;
177        if (rename(logPath_.c_str(), newLogPath.c_str()) != 0) {
178            // reopen the old log file path
179            OpenLog();
180            return false;
181        }
182    }
183    logPath_ = newLogPath;
184    return OpenLog();
185}
186
187void DebugLogger::SetLogTags(const std::string &tags)
188{
189    HLOGI(" tags is '%s'", tags.c_str());
190    auto tagLevels = StringSplit(tags, ",");
191    logTagLevelmap_.clear();
192    for (auto tagLevel : tagLevels) {
193        auto tagLevelPair = StringSplit(tagLevel, ":");
194        if (tagLevelPair.size() == 1) { // only tag
195            logTagLevelmap_[tagLevelPair[0]] = LEVEL_MUCH;
196        } else { // tag:level
197            logTagLevelmap_[tagLevelPair[0]] = GetLogLevelByName(tagLevelPair[1].c_str());
198        }
199    }
200    for (auto it = logTagLevelmap_.begin(); it != logTagLevelmap_.end(); it++) {
201        HLOGD(" '%s'='%s'", it->first.c_str(), GetLogLevelName(it->second).c_str());
202    }
203}
204
205DebugLevel DebugLogger::GetLogLevelByTag(const std::string &tag) const
206{
207    if (logTagLevelmap_.count(tag) > 0) {
208        return logTagLevelmap_.at(tag);
209    } else {
210        return GetLogLevel();
211    }
212}
213
214const std::string DebugLogger::GetLogLevelName(DebugLevel level) const
215{
216    return DebugLevelMap.at(level);
217}
218
219DebugLevel DebugLogger::GetLogLevelByName(const std::string &name) const
220{
221    for (auto it = DebugLevelMap.begin(); it != DebugLevelMap.end(); it++) {
222        if (it->second == name) {
223            return it->first;
224        }
225    }
226    // not found ?
227    return LEVEL_MUCH;
228}
229
230bool DebugLogger::OpenLog()
231{
232    if (logDisabled_) {
233        // don't reopen it when we crash or soemthing else.
234        return false;
235    }
236    if (file_ != nullptr) {
237        // already open
238        return true;
239    } else {
240        file_ = fopen(logPath_.c_str(), "w");
241    }
242    if (file_ == nullptr) {
243        const int bufSize = 256;
244        char buf[bufSize] = { 0 };
245        strerror_r(errno, buf, bufSize);
246        fprintf(stdout, "unable save log file to '%s' because '%d:%s'\n", logPath_.c_str(), errno, buf);
247        return false;
248    } else {
249        fseek(file_, 0, SEEK_SET);
250        // ecach log can save 6ms (29ms -> 23ms)
251        setvbuf(file_, logFileBuffer_.data(), _IOFBF, logFileBuffer_.size());
252        fprintf(stdout, "log will save at '%s'\n", logPath_.c_str());
253        return true;
254    }
255}
256
257__attribute__((weak)) DebugLevel DebugLogger::debugLevel_ = LEVEL_DEBUG;
258__attribute__((weak)) bool DebugLogger::logDisabled_ = true;
259std::unique_ptr<DebugLogger> DebugLogger::logInstance_;
260
261DebugLogger *DebugLogger::GetInstance()
262{
263    if (logInstance_ == nullptr) {
264        logInstance_ = std::make_unique<DebugLogger>();
265    }
266    return logInstance_.get();
267}
268} // namespace NativeDaemon
269} // namespace Developtools
270} // namespace OHOS
271