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