1 /**
2   Licensed to the Apache Software Foundation (ASF) under one
3   or more contributor license agreements.  See the NOTICE file
4   distributed with this work for additional information
5   regarding copyright ownership.  The ASF licenses this file
6   to you under the Apache License, Version 2.0 (the
7   "License"); you may not use this file except in compliance
8   with the License.  You may obtain a copy of the License at
9 
10       http://www.apache.org/licenses/LICENSE-2.0
11 
12   Unless required by applicable law or agreed to in writing, software
13   distributed under the License is distributed on an "AS IS" BASIS,
14   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   See the License for the specific language governing permissions and
16   limitations under the License.
17  */
18 /**
19  * @file Logger.cc
20  * @warning log rolling doesn't work correctly in 3.2.x see:
21  *   https://issues.apache.org/jira/browse/TS-1813
22  *   Apply the patch in TS-1813 to correct log rolling in 3.2.x
23  */
24 
25 #include "tscpp/api/Logger.h"
26 #include <cstdarg>
27 #include <vector>
28 #include <cstdio>
29 #include <string>
30 #include <cstring>
31 #include "ts/ts.h"
32 #include "tscpp/api/noncopyable.h"
33 #include "logging_internal.h"
34 
35 using std::vector;
36 using std::string;
37 
38 using atscppapi::Logger;
39 
40 /**
41  * @private
42  */
43 struct atscppapi::LoggerState : noncopyable {
44   std::string filename_;
45   bool add_timestamp_           = false;
46   bool rename_file_             = false;
47   Logger::LogLevel level_       = Logger::LOG_LEVEL_NO_LOG;
48   bool rolling_enabled_         = false;
49   int rolling_interval_seconds_ = -1;
50   TSTextLogObject text_log_obj_ = nullptr;
51   bool initialized_             = false;
52 
53   LoggerState()
54 
55     = default;
56   ;
57   ~LoggerState() = default;
58   ;
59 };
60 
61 namespace
62 {
63 // Since the TSTextLog API doesn't support override the log file sizes (I will add this to TS api at some point)
64 // we will use the roll size specified by default in records.config.
65 const int ROLL_ON_TIME = 1; // See RollingEnabledValues in LogConfig.h
66 } // namespace
67 
68 /*
69  * These have default values specified for add_timestamp and rename_file in Logger.h
70  */
Logger()71 Logger::Logger()
72 {
73   state_ = new LoggerState();
74 }
75 
~Logger()76 Logger::~Logger()
77 {
78   if (state_->initialized_ && state_->text_log_obj_) {
79     TSTextLogObjectDestroy(state_->text_log_obj_);
80   }
81 
82   delete state_;
83 }
84 
85 /*
86  * These have default values specified for rolling_enabled and rolling_interval_seconds in Logger.h
87  */
88 bool
init(const string & file,bool add_timestamp,bool rename_file,LogLevel level,bool rolling_enabled,int rolling_interval_seconds)89 Logger::init(const string &file, bool add_timestamp, bool rename_file, LogLevel level, bool rolling_enabled,
90              int rolling_interval_seconds)
91 {
92   if (state_->initialized_) {
93     LOG_ERROR("Attempt to reinitialize a logger named '%s' that's already been initialized to '%s'.", file.c_str(),
94               state_->filename_.c_str());
95     return false;
96   }
97   state_->filename_                 = file;
98   state_->add_timestamp_            = add_timestamp;
99   state_->rename_file_              = rename_file;
100   state_->level_                    = level;
101   state_->rolling_enabled_          = rolling_enabled;
102   state_->rolling_interval_seconds_ = rolling_interval_seconds;
103   state_->initialized_              = true; // set this to true always - we are not providing re-init() after a failed init()
104 
105   int mode = 0;
106   if (state_->add_timestamp_) {
107     mode |= TS_LOG_MODE_ADD_TIMESTAMP;
108   }
109 
110   if (!state_->rename_file_) {
111     mode |= TS_LOG_MODE_DO_NOT_RENAME;
112   }
113 
114   TSReturnCode result = TSTextLogObjectCreate(state_->filename_.c_str(), mode, &state_->text_log_obj_);
115 
116   if (result == TS_SUCCESS) {
117     TSTextLogObjectRollingEnabledSet(state_->text_log_obj_, state_->rolling_enabled_ ? ROLL_ON_TIME : 0);
118     TSTextLogObjectRollingIntervalSecSet(state_->text_log_obj_, state_->rolling_interval_seconds_);
119     LOG_DEBUG("Initialized log [%s]", state_->filename_.c_str());
120   } else {
121     state_->level_ = LOG_LEVEL_NO_LOG;
122     LOG_ERROR("Failed to initialize for log [%s]", state_->filename_.c_str());
123   }
124 
125   return result == TS_SUCCESS;
126 }
127 
128 void
setLogLevel(Logger::LogLevel level)129 Logger::setLogLevel(Logger::LogLevel level)
130 {
131   if (state_->initialized_) {
132     state_->level_ = level;
133     LOG_DEBUG("Set log level to %d for log [%s]", level, state_->filename_.c_str());
134   }
135 }
136 
137 Logger::LogLevel
getLogLevel() const138 Logger::getLogLevel() const
139 {
140   if (!state_->initialized_) {
141     LOG_ERROR("Not initialized");
142   }
143   return state_->level_;
144 }
145 
146 void
setRollingIntervalSeconds(int seconds)147 Logger::setRollingIntervalSeconds(int seconds)
148 {
149   if (state_->initialized_) {
150     state_->rolling_interval_seconds_ = seconds;
151     TSTextLogObjectRollingIntervalSecSet(state_->text_log_obj_, seconds);
152     LOG_DEBUG("Set rolling interval for log [%s] to %d seconds", state_->filename_.c_str(), seconds);
153   } else {
154     LOG_ERROR("Not initialized!");
155   }
156 }
157 
158 int
getRollingIntervalSeconds() const159 Logger::getRollingIntervalSeconds() const
160 {
161   if (!state_->initialized_) {
162     LOG_ERROR("Not initialized");
163   }
164   return state_->rolling_interval_seconds_;
165 }
166 
167 void
setRollingEnabled(bool enabled)168 Logger::setRollingEnabled(bool enabled)
169 {
170   if (state_->initialized_) {
171     state_->rolling_enabled_ = enabled;
172     TSTextLogObjectRollingEnabledSet(state_->text_log_obj_, enabled ? ROLL_ON_TIME : 0);
173     LOG_DEBUG("Rolling for log [%s] is now %s", state_->filename_.c_str(), (enabled ? "true" : "false"));
174   } else {
175     LOG_ERROR("Not initialized!");
176   }
177 }
178 
179 bool
isRollingEnabled() const180 Logger::isRollingEnabled() const
181 {
182   if (!state_->initialized_) {
183     LOG_ERROR("Not initialized!");
184   }
185   return state_->rolling_enabled_;
186 }
187 
188 void
flush()189 Logger::flush()
190 {
191   if (state_->initialized_) {
192     TSTextLogObjectFlush(state_->text_log_obj_);
193   } else {
194     LOG_ERROR("Not initialized!");
195   }
196 }
197 
198 namespace
199 {
200 const int DEFAULT_BUFFER_SIZE_FOR_VARARGS = 8 * 1024;
201 
202 // We use a macro here because varargs would be a pain to forward via a helper
203 // function
204 #define TS_TEXT_LOG_OBJECT_WRITE(level)                                                                               \
205   char buffer[DEFAULT_BUFFER_SIZE_FOR_VARARGS];                                                                       \
206   int n;                                                                                                              \
207   va_list ap;                                                                                                         \
208   while (true) {                                                                                                      \
209     va_start(ap, fmt);                                                                                                \
210     n = vsnprintf(&buffer[0], sizeof(buffer), fmt, ap);                                                               \
211     va_end(ap);                                                                                                       \
212     if (n > -1 && n < static_cast<int>(sizeof(buffer))) {                                                             \
213       LOG_DEBUG("logging a " level " to '%s' with length %d", state_->filename_.c_str(), n);                          \
214       TSTextLogObjectWrite(state_->text_log_obj_, const_cast<char *>("[" level "] %s"), buffer);                      \
215     } else {                                                                                                          \
216       LOG_ERROR("Unable to log " level " message to '%s' due to size exceeding %zu bytes", state_->filename_.c_str(), \
217                 sizeof(buffer));                                                                                      \
218     }                                                                                                                 \
219     return;                                                                                                           \
220   }
221 
222 } /* end anonymous namespace */
223 
224 void
logDebug(const char * fmt,...)225 Logger::logDebug(const char *fmt, ...)
226 {
227   if (state_->level_ <= LOG_LEVEL_DEBUG) {
228     TS_TEXT_LOG_OBJECT_WRITE("DEBUG");
229   }
230 }
231 
232 void
logInfo(const char * fmt,...)233 Logger::logInfo(const char *fmt, ...)
234 {
235   if (state_->level_ <= LOG_LEVEL_INFO) {
236     TS_TEXT_LOG_OBJECT_WRITE("INFO");
237   }
238 }
239 
240 void
logError(const char * fmt,...)241 Logger::logError(const char *fmt, ...)
242 {
243   if (state_->level_ <= LOG_LEVEL_ERROR) {
244     TS_TEXT_LOG_OBJECT_WRITE("ERROR");
245   }
246 }
247