1 /*
2 * InspIRCd -- Internet Relay Chat Daemon
3 *
4 * Copyright (C) 2013-2014 Attila Molnar <attilamolnar@hush.com>
5 * Copyright (C) 2013, 2017-2018, 2020-2021 Sadie Powell <sadie@witchery.services>
6 * Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org>
7 * Copyright (C) 2012 Robby <robby@chatbelgie.be>
8 * Copyright (C) 2010 Craig Edwards <brain@inspircd.org>
9 * Copyright (C) 2010 Adam <Adam@anope.org>
10 * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
11 * Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
12 * Copyright (C) 2008-2009 Robin Burchell <robin+git@viroteck.net>
13 * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
14 *
15 * This file is part of InspIRCd. InspIRCd is free software: you can
16 * redistribute it and/or modify it under the terms of the GNU General Public
17 * License as published by the Free Software Foundation, version 2.
18 *
19 * This program is distributed in the hope that it will be useful, but WITHOUT
20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
22 * details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 */
27
28
29 #include "inspircd.h"
30
31 /*
32 * Suggested implementation...
33 * class LogManager
34 * bool AddLogType(const std::string &type, enum loglevel, LogStream *)
35 * bool DelLogType(const std::string &type, LogStream *)
36 * Log(const std::string &type, enum loglevel, const std::string &msg)
37 * std::map<std::string, std::vector<LogStream *> > logstreams (holds a 'chain' of logstreams for each type that are all notified when a log happens)
38 *
39 * class LogStream
40 * std::string type
41 * virtual void OnLog(enum loglevel, const std::string &msg)
42 *
43 * How it works:
44 * Modules create their own logstream types (core will create one for 'file logging' for example) and create instances of these logstream types
45 * and register interest in a certain logtype. Globbing is not here, with the exception of * - for all events.. loglevel is used to drop
46 * events that are of no interest to a logstream.
47 *
48 * When Log is called, the vector of logstreams for that type is iterated (along with the special vector for "*"), and all registered logstreams
49 * are called back ("OnLog" or whatever) to do whatever they like with the message. In the case of the core, this will write to a file.
50 * In the case of the module I plan to write (m_logtochannel or something), it will log to the channel(s) for that logstream, etc.
51 *
52 * NOTE: Somehow we have to let LogManager manage the non-blocking file streams and provide an interface to share them with various LogStreams,
53 * as, for example, a user may want to let 'KILL' and 'XLINE' snotices go to /home/ircd/inspircd/logs/operactions.log, or whatever. How
54 * can we accomplish this easily? I guess with a map of pre-loved logpaths, and a pointer of FILE *..
55 *
56 */
57
58 const char LogStream::LogHeader[] =
59 "Log started for " INSPIRCD_VERSION;
60
LogManager()61 LogManager::LogManager()
62 : Logging(false)
63 {
64 }
65
~LogManager()66 LogManager::~LogManager()
67 {
68 }
69
OpenFileLogs()70 void LogManager::OpenFileLogs()
71 {
72 if (ServerInstance->Config->cmdline.forcedebug)
73 {
74 ServerInstance->Config->RawLog = true;
75 return;
76 }
77 /* Skip rest of logfile opening if we are running -nolog. */
78 if (!ServerInstance->Config->cmdline.writelog)
79 return;
80 std::map<std::string, FileWriter*> logmap;
81 ConfigTagList tags = ServerInstance->Config->ConfTags("log");
82 for(ConfigIter i = tags.first; i != tags.second; ++i)
83 {
84 ConfigTag* tag = i->second;
85 const std::string method = tag->getString("method", "file", 1);
86 if (!stdalgo::string::equalsci(method, "file"))
87 {
88 continue;
89 }
90 std::string type = tag->getString("type");
91 std::string level = tag->getString("level");
92 LogLevel loglevel = LOG_DEFAULT;
93 if (stdalgo::string::equalsci(level, "rawio"))
94 {
95 loglevel = LOG_RAWIO;
96 ServerInstance->Config->RawLog = true;
97 }
98 else if (stdalgo::string::equalsci(level, "debug"))
99 {
100 loglevel = LOG_DEBUG;
101 }
102 else if (stdalgo::string::equalsci(level, "verbose"))
103 {
104 loglevel = LOG_VERBOSE;
105 }
106 else if (stdalgo::string::equalsci(level, "sparse"))
107 {
108 loglevel = LOG_SPARSE;
109 }
110 else if (stdalgo::string::equalsci(level, "none"))
111 {
112 loglevel = LOG_NONE;
113 }
114 FileWriter* fw;
115 std::string target = ServerInstance->Config->Paths.PrependLog(tag->getString("target"));
116 std::map<std::string, FileWriter*>::iterator fwi = logmap.find(target);
117 if (fwi == logmap.end())
118 {
119 char realtarget[256];
120 time_t time = ServerInstance->Time();
121 struct tm *mytime = gmtime(&time);
122 strftime(realtarget, sizeof(realtarget), target.c_str(), mytime);
123 FILE* f = fopen(realtarget, "a");
124 fw = new FileWriter(f, tag->getUInt("flush", 20, 1, UINT_MAX));
125 logmap.insert(std::make_pair(target, fw));
126 }
127 else
128 {
129 fw = fwi->second;
130 }
131 FileLogStream* fls = new FileLogStream(loglevel, fw);
132 fls->OnLog(LOG_SPARSE, "HEADER", LogStream::LogHeader);
133 AddLogTypes(type, fls, true);
134 }
135 }
136
CloseLogs()137 void LogManager::CloseLogs()
138 {
139 if (ServerInstance->Config && ServerInstance->Config->cmdline.forcedebug)
140 return;
141
142 LogStreams.clear();
143 GlobalLogStreams.clear();
144
145 for (std::map<LogStream*, int>::iterator i = AllLogStreams.begin(); i != AllLogStreams.end(); ++i)
146 {
147 delete i->first;
148 }
149
150 AllLogStreams.clear();
151 }
152
AddLogTypes(const std::string & types,LogStream * l,bool autoclose)153 void LogManager::AddLogTypes(const std::string &types, LogStream* l, bool autoclose)
154 {
155 irc::spacesepstream css(types);
156 std::string tok;
157 std::vector<std::string> excludes;
158 while (css.GetToken(tok))
159 {
160 if (tok.empty())
161 {
162 continue;
163 }
164 if (tok.at(0) == '-')
165 {
166 /* Exclude! */
167 excludes.push_back(tok.substr(1));
168 }
169 else
170 {
171 AddLogType(tok, l, autoclose);
172 }
173 }
174 // Handle doing things like: USERINPUT USEROUTPUT -USERINPUT should be the same as saying just USEROUTPUT.
175 // (This is so modules could, for example, inject exclusions for logtypes they can't handle.)
176 for (std::vector<std::string>::iterator i = excludes.begin(); i != excludes.end(); ++i)
177 {
178 if (*i == "*")
179 {
180 /* -* == Exclude all. Why someone would do this, I dunno. */
181 DelLogStream(l);
182 return;
183 }
184 DelLogType(*i, l);
185 }
186 // Now if it's registered as a global, add the exclusions there too.
187 std::map<LogStream *, std::vector<std::string> >::iterator gi = GlobalLogStreams.find(l);
188 if (gi != GlobalLogStreams.end())
189 {
190 gi->second.swap(excludes); // Swap with the vector in the hash.
191 }
192 }
193
AddLogType(const std::string & type,LogStream * l,bool autoclose)194 bool LogManager::AddLogType(const std::string &type, LogStream *l, bool autoclose)
195 {
196 LogStreams[type].push_back(l);
197
198 if (type == "*")
199 GlobalLogStreams.insert(std::make_pair(l, std::vector<std::string>()));
200
201 if (autoclose)
202 AllLogStreams[l]++;
203
204 return true;
205 }
206
DelLogStream(LogStream * l)207 void LogManager::DelLogStream(LogStream* l)
208 {
209 for (std::map<std::string, std::vector<LogStream*> >::iterator i = LogStreams.begin(); i != LogStreams.end(); ++i)
210 {
211 while (stdalgo::erase(i->second, l))
212 {
213 // Keep erasing while it exists
214 }
215 }
216
217 GlobalLogStreams.erase(l);
218
219 std::map<LogStream*, int>::iterator ai = AllLogStreams.begin();
220 if (ai == AllLogStreams.end())
221 {
222 return; /* Done. */
223 }
224
225 delete ai->first;
226 AllLogStreams.erase(ai);
227 }
228
DelLogType(const std::string & type,LogStream * l)229 bool LogManager::DelLogType(const std::string &type, LogStream *l)
230 {
231 std::map<std::string, std::vector<LogStream *> >::iterator i = LogStreams.find(type);
232 if (type == "*")
233 {
234 GlobalLogStreams.erase(l);
235 }
236
237 if (i != LogStreams.end())
238 {
239 if (stdalgo::erase(i->second, l))
240 {
241 if (i->second.size() == 0)
242 {
243 LogStreams.erase(i);
244 }
245 }
246 else
247 {
248 return false;
249 }
250 }
251 else
252 {
253 return false;
254 }
255
256 std::map<LogStream*, int>::iterator ai = AllLogStreams.find(l);
257 if (ai == AllLogStreams.end())
258 {
259 return true;
260 }
261
262 if ((--ai->second) < 1)
263 {
264 AllLogStreams.erase(ai);
265 delete l;
266 }
267
268 return true;
269 }
270
Log(const std::string & type,LogLevel loglevel,const char * fmt,...)271 void LogManager::Log(const std::string &type, LogLevel loglevel, const char *fmt, ...)
272 {
273 if (Logging)
274 return;
275
276 std::string buf;
277 VAFORMAT(buf, fmt, fmt);
278 this->Log(type, loglevel, buf);
279 }
280
Log(const std::string & type,LogLevel loglevel,const std::string & msg)281 void LogManager::Log(const std::string &type, LogLevel loglevel, const std::string &msg)
282 {
283 if (Logging)
284 {
285 return;
286 }
287
288 Logging = true;
289
290 for (std::map<LogStream *, std::vector<std::string> >::iterator gi = GlobalLogStreams.begin(); gi != GlobalLogStreams.end(); ++gi)
291 {
292 if (stdalgo::isin(gi->second, type))
293 {
294 continue;
295 }
296 gi->first->OnLog(loglevel, type, msg);
297 }
298
299 std::map<std::string, std::vector<LogStream *> >::iterator i = LogStreams.find(type);
300
301 if (i != LogStreams.end())
302 {
303 for (std::vector<LogStream *>::iterator it = i->second.begin(); it != i->second.end(); ++it)
304 {
305 (*it)->OnLog(loglevel, type, msg);
306 }
307 }
308
309 Logging = false;
310 }
311
312
FileWriter(FILE * logfile,unsigned int flushcount)313 FileWriter::FileWriter(FILE* logfile, unsigned int flushcount)
314 : log(logfile)
315 , flush(flushcount)
316 , writeops(0)
317 {
318 }
319
WriteLogLine(const std::string & line)320 void FileWriter::WriteLogLine(const std::string &line)
321 {
322 if (log == NULL)
323 return;
324 // XXX: For now, just return. Don't throw an exception. It'd be nice to find out if this is happening, but I'm terrified of breaking so close to final release. -- w00t
325 // throw CoreException("FileWriter::WriteLogLine called with a closed logfile");
326
327 fputs(line.c_str(), log);
328 if (++writeops % flush == 0)
329 {
330 fflush(log);
331 }
332 }
333
~FileWriter()334 FileWriter::~FileWriter()
335 {
336 if (log)
337 {
338 fflush(log);
339 fclose(log);
340 log = NULL;
341 }
342 }
343