ArmarXFileLogger.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package ArmarXCore::ArmarXObjects::ArmarXFileLogger
17 * @author Mirko Waechter ( mirko dot waechter at kit dot edu )
18 * @date 2017
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
23#include "ArmarXFileLogger.h"
24
25#include <boost/format.hpp>
26#include <boost/range/adaptor/reversed.hpp>
27
32
33namespace armarx
34{
35
38 {
39 //defineRequiredProperty<std::string>("PropertyName", "Description");
40 defineOptionalProperty<int>("MaxLogFileCount",
41 100,
42 "Number of log files to keep before starting to delete old "
43 "files. If zero, no file will be deleted.");
45 "LogFileName", "ArmarXLog", "Logfilename without ending");
46 defineOptionalProperty<std::string>("LogFileEnding", "log", "Logfilename ending");
48 "LogDir",
49 "",
50 "If set, log will be stored in this path. If not set, log will be stored "
51 "in $ARMARX_USER_CONFIG_DIR/log/ (or $HOME/.armarx/log/ if $ARMARX_USER_CONFIG_DIR is "
52 "not set).");
53 defineOptionalProperty<std::string>("LogTopicName", "Log", "Name of the topic to be used");
55 "SplitByApplication", true, "Create additional log files for each application");
56 }
57
58 void
60 {
61 std::string directoryStr;
62 if (getProperty<std::string>("LogDir").isSet())
63 {
64 directoryStr = getProperty<std::string>("LogDir").getValue();
66 }
67 else if (const char* workspace = std::getenv("ARMARX_USER_CONFIG_DIR"))
68 {
69 directoryStr = (std::filesystem::path(workspace) / "log").string();
70 }
71 else
72 {
73 directoryStr = std::string("$HOME/.armarx/log/");
75 }
76
77 auto directory = remove_trailing_separator(directoryStr);
78 ARMARX_DEBUG << "Checking " << directory.string();
79 if (!std::filesystem::exists(directory))
80 {
81 ARMARX_INFO << "Log path not found - creating it: " << directory.string();
82 std::error_code errcode;
83 std::filesystem::create_directories(directory, errcode);
84 if (errcode)
85 {
86 ARMARX_ERROR << "Creating directories failed: " << errcode.message();
87 return;
88 }
89 }
90
91 auto ending = getProperty<std::string>("LogFileEnding").getValue();
92 auto fileBasename = getProperty<std::string>("LogFileName").getValue();
94 directoryStr, getProperty<int>("MaxLogFileCount").getValue(), fileBasename, ending);
95
96
97 if (getProperty<bool>("SplitByApplication").getValue())
98 {
99 auto filePath = std::filesystem::path(filename);
100 directory /= filePath.stem();
101 if (!std::filesystem::exists(directory))
102 {
103 ARMARX_INFO << "Log path not found - creating it: " << directory;
104 std::error_code errcode;
105 std::filesystem::create_directories(directory, errcode);
106 if (errcode)
107 {
108 ARMARX_ERROR << "Creating directories failed: " << errcode.message();
109 return;
110 }
111 }
113 directory.string() + "/" + filePath.stem().string() + "-%1%." + ending;
114 }
115
116 // FileSystemPathBuilder builder(getProperty<std::string>("LogFileName").getValue());
117 auto logfilePath = std::filesystem::path(directoryStr) / std::filesystem::path(filename);
118 fileStream.open(logfilePath.string().c_str(), std::ofstream::out);
119 if (!fileStream.is_open())
120 {
121 ARMARX_ERROR << "Failed to open " << logfilePath.string();
122 return;
123 }
124 ARMARX_INFO << "Logging to " << logfilePath.string();
125 fileStream << "Log starting at " << IceUtil::Time::now().toDateTime() << ", "
126 << "with log name " << filename << "\n";
127 usingTopic(getProperty<std::string>("LogTopicName").getValue());
128 }
129
130 void
134
135 void
137 {
138 std::unique_lock lock(mutex);
139 fileStream << std::flush;
140 fileStream.close();
142 }
143
144 void
148
155
156 std::vector<std::string>
157 ArmarXFileLogger::getDirListOrderedByDate(const std::string& dir) const
158 {
159 std::vector<std::string> result;
160 namespace fs = std::filesystem;
161 fs::path someDir(dir);
162 fs::directory_iterator end_iter;
163
164 using result_set_t = std::multimap<std::filesystem::file_time_type, fs::path>;
165 result_set_t result_set;
166
167 if (fs::exists(someDir) && fs::is_directory(someDir))
168 {
169 for (fs::directory_iterator dir_iter(someDir); dir_iter != end_iter; ++dir_iter)
170 {
171 if (fs::is_regular_file(dir_iter->status()))
172 {
173 result_set.insert(
174 result_set_t::value_type(fs::last_write_time(dir_iter->path()), *dir_iter));
175 }
176 }
177 }
178 for (auto& elem : boost::adaptors::reverse(result_set))
179 {
180 result.push_back(elem.second.string());
181 }
182 return result;
183 }
184
185 std::string
187 int maxLogFileCount,
188 const std::string& fileBasename,
189 const std::string& fileNameEnding) const
190 {
191 auto files = getDirListOrderedByDate(dir);
192 // ARMARX_INFO << files;
193 std::string connectorString = "-";
194 const boost::regex e(fileBasename + connectorString + "([0-9]+)\\.");
195 int trials = 0;
196 int highestNumber = 0;
197 for (auto file : files)
198 {
199 boost::match_results<std::string::const_iterator> what;
200 file = std::filesystem::path(file).filename().string();
201 ARMARX_DEBUG << file;
202 bool found_match = boost::regex_search(file, what, e);
203 if (found_match)
204 {
205 for (size_t i = 1; i < what.size(); i += 2)
206 {
207 std::string numberStr = what[i];
208 int number = atoi(numberStr.c_str());
209 highestNumber = std::max(number, highestNumber);
210 // ARMARX_INFO << "testing number " << number << VAROUT( trials) << VAROUT(highestNumber);
211 auto filenameCandidate = fileBasename + connectorString +
212 ValueToString(number) + "." + fileNameEnding;
213 if (std::filesystem::exists(dir + "/" + filenameCandidate))
214 {
215 // ARMARX_INFO << "File exists " << VAROUT(filenameCandidate) << VAROUT(trials);
216 if (trials >= maxLogFileCount && maxLogFileCount > 0)
217 {
218 ARMARX_VERBOSE << "Deleting old log " << filenameCandidate;
219 std::filesystem::remove(dir + "/" + filenameCandidate);
220 }
221 }
222 trials++;
223 }
224 }
225 }
226 highestNumber++;
227 return fileBasename + connectorString + ValueToString(highestNumber) + "." + fileNameEnding;
228 }
229
230 std::string
232 {
233 return "ArmarXFileLogger";
234 }
235
236 void
237 armarx::ArmarXFileLogger::writeLog(const LogMessage& msg, const Ice::Current&)
238 {
239 std::string catStr;
240
241 if (!msg.tag.empty())
242 {
243 catStr = "[" + msg.tag + "]: ";
244 }
245 else
246 {
247 catStr = "[" + LogSender::CropFunctionName(msg.function) + "]: ";
248 }
249 IceUtil::Time time = IceUtil::Time::microSeconds(msg.time);
250 std::unique_lock lock(mutex);
251 if (!fileStream.is_open())
252 {
253 return;
254 }
255
256 std::string logLevel;
257 switch (msg.type)
258 {
259 case armarx::MessageType::eUNDEFINED:
260 logLevel = "UNDEFINED";
261 break;
262 case armarx::MessageType::eDEBUG:
263 logLevel = "DEBUG";
264 break;
265 case armarx::MessageType::eVERBOSE:
266 logLevel = "VERBOSE";
267 break;
268 case armarx::MessageType::eINFO:
269 logLevel = "INFO";
270 break;
271 case armarx::MessageType::eIMPORTANT:
272 logLevel = "IMPORTANT";
273 break;
274 case armarx::MessageType::eWARN:
275 logLevel = "WARN";
276 break;
277 case armarx::MessageType::eERROR:
278 logLevel = "ERROR";
279 break;
280 case armarx::MessageType::eFATAL:
281 logLevel = "FATAL";
282 break;
283 case armarx::MessageType::eLogLevelCount:
284 logLevel = "LogLevelCount";
285 break;
286 }
287
288 std::stringstream newLines;
289 newLines << "[" + time.toDateTime().substr(time.toDateTime().find(' ') + 1) + "]" + "[" +
290 msg.who + "]" + catStr + "[" + logLevel + "]";
291 newLines << msg.what << "\n";
292 fileStream << newLines.str();
293 if (getProperty<bool>("SplitByApplication").getValue())
294 {
295 auto it = applicationFileStreams.find(msg.who);
296 if (it == applicationFileStreams.end())
297 {
298 auto filepath = boost::format(applicationsLogFileBasePath) % msg.who;
300 .emplace(msg.who,
301 std::shared_ptr<std::ofstream>(
302 new std::ofstream(filepath.str().c_str())))
303 .first;
304 // it = applicationFileStreams.find(msg.who);
305 if (!it->second->is_open())
306 {
307 ARMARX_WARNING << "Failed to open for logging: " << filepath.str();
308 return;
309 }
310 }
311
312 *(it->second) << newLines.str();
313 }
314 }
315} // namespace armarx
static bool ReplaceEnvVars(std::string &string)
ReplaceEnvVars replaces environment variables in a string with their values, if the env.
std::string applicationsLogFileBasePath
void onInitComponent() override
Pure virtual hook for the subclass.
std::vector< std::string > getDirListOrderedByDate(const std::string &dir) const
void onDisconnectComponent() override
Hook for subclass.
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
Creates the property definition container.
void onConnectComponent() override
Pure virtual hook for the subclass.
void writeLog(const LogMessage &, const Ice::Current &) override
std::map< std::string, std::shared_ptr< std::ofstream > > applicationFileStreams
std::string getNextLogFileNameAndDeleteOldFiles(const std::string &dir, int maxLogFileCount, const std::string &fileBasename, const std::string &fileNameEnding) const
void onExitComponent() override
Hook for subclass.
std::string getDefaultName() const override
Retrieve default name of component.
ComponentPropertyDefinitions(std::string prefix, bool hasObjectNameParameter=true)
Definition Component.cpp:46
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
Property< PropertyType > getProperty(const std::string &name)
static std::string CropFunctionName(const std::string &originalFunctionName)
void usingTopic(const std::string &name, bool orderedPublishing=false)
Registers a proxy for subscription after initialization.
std::string prefix
Prefix of the properties such as namespace, domain, component name, etc.
PropertyDefinition< PropertyType > & defineOptionalProperty(const std::string &name, PropertyType defaultValue, const std::string &description="", PropertyDefinitionBase::PropertyConstness constness=PropertyDefinitionBase::eConstant)
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
This file offers overloads of toIce() and fromIce() functions for STL container types.
fs::path remove_trailing_separator(fs::path p)
Definition filesystem.h:34
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
std::string ValueToString(const T &value)