Public MP headers in include/metaproxy
[metaproxy-moved-to-github.git] / src / filter_log.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2010 Index Data
3
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "config.hpp"
20 #include "filter_log.hpp"
21 #include <metaproxy/package.hpp>
22
23 #include <string>
24 #include <sstream>
25 #include <iomanip>
26 #include <boost/thread/mutex.hpp>
27
28 #include "gduutil.hpp"
29 #include <metaproxy/util.hpp>
30 #include <metaproxy/xmlutil.hpp>
31
32 #include <yaz/zgdu.h>
33 #include <yaz/wrbuf.h>
34 #include <yaz/log.h>
35 #include <yaz/querytowrbuf.h>
36 #include <yaz/timing.h>
37 #include <stdio.h>
38 #include <time.h>
39
40 namespace mp = metaproxy_1;
41 namespace yf = metaproxy_1::filter;
42
43 namespace metaproxy_1 {
44     namespace filter {
45         class Log::Impl {
46         public:
47             class LFile;
48             typedef boost::shared_ptr<Log::Impl::LFile> LFilePtr;
49         public:
50             //Impl();
51             Impl(const std::string &x = "-");
52            ~Impl();
53             void process(metaproxy_1::Package & package);
54             void configure(const xmlNode * ptr);
55         private:
56             void openfile(const std::string &fname);
57             // needs to be static to be called by C pointer-to-function-syntax
58             static void stream_write(ODR o, void *handle, int type, 
59                               const char *buf, int len);
60             // needs to be static to be called by C pointer-to-function-syntax
61             static void option_write(const char *name, void *handle);
62         private:
63             std::string m_msg_config;
64             bool m_access;
65             bool m_user_access;
66             bool m_req_apdu;
67             bool m_res_apdu;
68             bool m_req_session;
69             bool m_res_session;
70             bool m_init_options;
71             LFilePtr m_file;
72             std::string m_time_format;
73             // Only used during confiqgure stage (no threading), 
74             // for performance avoid opening files which other log filter 
75             // instances already have opened
76             static std::list<LFilePtr> filter_log_files;
77
78             boost::mutex m_session_mutex;
79             std::map<mp::Session, std::string> m_sessions;
80        };
81
82         class Log::Impl::LFile {
83         public:
84             boost::mutex m_mutex;
85             std::string m_fname;
86             FILE *fhandle;
87             ~LFile();
88             LFile(std::string fname);
89             LFile(std::string fname, FILE *outf);
90             void log(const std::string &date_format,
91                      std::ostringstream &os);
92             void flush();
93         };
94         
95     }
96 }
97
98 // define Pimpl wrapper forwarding to Impl
99  
100 yf::Log::Log() : m_p(new Impl)
101 {
102 }
103
104 yf::Log::Log(const std::string &x) : m_p(new Impl(x))
105 {
106 }
107
108 yf::Log::~Log()
109 {  // must have a destructor because of boost::scoped_ptr
110 }
111
112 void yf::Log::configure(const xmlNode *xmlnode, bool test_only)
113 {
114     m_p->configure(xmlnode);
115 }
116
117 void yf::Log::process(mp::Package &package) const
118 {
119     m_p->process(package);
120 }
121
122
123 // static initialization
124 std::list<yf::Log::Impl::LFilePtr> yf::Log::Impl::filter_log_files;
125
126
127 yf::Log::Impl::Impl(const std::string &x)
128     : m_msg_config(x),
129       m_access(true),
130       m_user_access(false),
131       m_req_apdu(false),
132       m_res_apdu(false),
133       m_req_session(false),
134       m_res_session(false),
135       m_init_options(false),
136       m_time_format("%H:%M:%S-%d/%m")
137 {
138     openfile("");
139 }
140
141
142 yf::Log::Impl::~Impl() 
143 {
144 }
145
146
147 void yf::Log::Impl::configure(const xmlNode *ptr)
148 {
149     for (ptr = ptr->children; ptr; ptr = ptr->next)
150     {
151         if (ptr->type != XML_ELEMENT_NODE)
152             continue;
153         if (!strcmp((const char *) ptr->name, "message"))
154             m_msg_config = mp::xml::get_text(ptr);
155         else if (!strcmp((const char *) ptr->name, "filename"))
156         {
157             std::string fname = mp::xml::get_text(ptr);
158             openfile(fname);
159         }
160         else if (!strcmp((const char *) ptr->name, "time-format"))
161         {
162             m_time_format = mp::xml::get_text(ptr);
163         }
164         else if (!strcmp((const char *) ptr->name, "category"))
165         {
166             const struct _xmlAttr *attr;
167             for (attr = ptr->properties; attr; attr = attr->next)
168             {
169                 if (!strcmp((const char *) attr->name,  "access"))
170                     m_access = mp::xml::get_bool(attr->children, true);
171                 else if (!strcmp((const char *) attr->name, "user-access"))
172                     m_user_access = mp::xml::get_bool(attr->children, true);
173                 else if (!strcmp((const char *) attr->name, "request-apdu"))
174                     m_req_apdu = mp::xml::get_bool(attr->children, true);
175                 else if (!strcmp((const char *) attr->name, "response-apdu"))
176                     m_res_apdu = mp::xml::get_bool(attr->children, true);
177                 else if (!strcmp((const char *) attr->name, "apdu"))
178                 {
179                     m_req_apdu = mp::xml::get_bool(attr->children, true);
180                     m_res_apdu = m_req_apdu;
181                 }
182                 else if (!strcmp((const char *) attr->name,
183                                  "request-session"))
184                     m_req_session = 
185                         mp::xml::get_bool(attr->children, true);
186                 else if (!strcmp((const char *) attr->name, 
187                                  "response-session"))
188                     m_res_session = 
189                         mp::xml::get_bool(attr->children, true);
190                 else if (!strcmp((const char *) attr->name,
191                                  "session"))
192                 {
193                     m_req_session = 
194                         mp::xml::get_bool(attr->children, true);
195                     m_res_session = m_req_session;
196                 }
197                 else if (!strcmp((const char *) attr->name, 
198                                  "init-options"))
199                     m_init_options = 
200                         mp::xml::get_bool(attr->children, true);
201                 else if (!strcmp((const char *) attr->name, 
202                                  "init-options"))
203                     m_init_options = 
204                         mp::xml::get_bool(attr->children, true);
205                 else
206                     throw mp::filter::FilterException(
207                         "Bad attribute " + std::string((const char *)
208                                                        attr->name));
209             }
210         }
211         else
212         {
213             throw mp::filter::FilterException("Bad element " 
214                                                + std::string((const char *)
215                                                              ptr->name));
216         }
217     }
218 }
219
220 void yf::Log::Impl::process(mp::Package &package)
221 {
222     Z_GDU *gdu = package.request().get();
223     std::string user("-");
224
225     yaz_timing_t timer = yaz_timing_create();
226
227     // scope for session lock
228     {
229         boost::mutex::scoped_lock scoped_lock(m_session_mutex);
230         
231         if (gdu && gdu->which == Z_GDU_Z3950)
232         {
233             Z_APDU *apdu_req = gdu->u.z3950;
234             if (apdu_req->which == Z_APDU_initRequest)
235             {
236                 Z_InitRequest *req = apdu_req->u.initRequest;
237                 Z_IdAuthentication *a = req->idAuthentication;
238                 if (a)
239                 {
240                     if (a->which == Z_IdAuthentication_idPass)
241                         user = a->u.idPass->userId;
242                     else if (a->which == Z_IdAuthentication_open)
243                         user = a->u.open;
244                 
245                     m_sessions[package.session()] = user;
246                 }
247             }
248         }
249         std::map<mp::Session,std::string>::iterator it = 
250             m_sessions.find(package.session());
251         if (it != m_sessions.end())
252             user = it->second;
253         
254         if (package.session().is_closed())
255             m_sessions.erase(package.session());
256     }
257     // scope for locking Ostream
258     { 
259         boost::mutex::scoped_lock scoped_lock(m_file->m_mutex);
260  
261         if (m_access)
262         {
263             if (gdu)          
264             {
265                 std::ostringstream os;
266                 os  << m_msg_config << " "
267                     << package << " "
268                     << "0.000000" << " " 
269                     << *gdu;
270                 m_file->log(m_time_format, os);
271             }
272         }
273
274         if (m_user_access)
275         {
276             if (gdu)          
277             {
278                 std::ostringstream os;
279                 os  << m_msg_config << " " << user << " "
280                     << package << " "
281                     << "0.000000" << " " 
282                     << *gdu;
283                 m_file->log(m_time_format, os);
284             }
285         }
286
287         if (m_req_session)
288         {
289             std::ostringstream os;
290             os << m_msg_config;
291             os << " request id=" << package.session().id();
292             os << " close=" 
293                << (package.session().is_closed() ? "yes" : "no");
294             m_file->log(m_time_format, os);
295         }
296
297         if (m_init_options)
298         {
299             if (gdu && gdu->which == Z_GDU_Z3950 &&
300                 gdu->u.z3950->which == Z_APDU_initRequest)
301             {
302                 std::ostringstream os;
303                 os << m_msg_config << " init options:";
304                 yaz_init_opt_decode(gdu->u.z3950->u.initRequest->options,
305                                     option_write, &os);
306                 m_file->log(m_time_format, os);
307             }
308         }
309         
310         if (m_req_apdu)
311         {
312             if (gdu)
313             {
314                 mp::odr odr(ODR_PRINT);
315                 odr_set_stream(odr, m_file->fhandle, stream_write, 0);
316                 z_GDU(odr, &gdu, 0, 0);
317             }
318         }
319     }
320     
321     // unlocked during move
322     package.move();
323
324     gdu = package.response().get();
325
326     yaz_timing_stop(timer);
327     double duration = yaz_timing_get_real(timer);
328
329     // scope for locking Ostream 
330     { 
331         boost::mutex::scoped_lock scoped_lock(m_file->m_mutex);
332
333         if (m_access)
334         {
335             if (gdu)
336             {
337                 std::ostringstream os;
338                 os  << m_msg_config << " "
339                     << package << " "
340                     << std::fixed << std::setprecision (6) << duration
341                     << " "
342                     << *gdu;
343                 m_file->log(m_time_format, os);
344             }
345         }
346         if (m_user_access)
347         {
348             if (gdu)
349             {
350                 std::ostringstream os;
351                 os  << m_msg_config << " " << user << " "
352                     << package << " "
353                     << std::fixed << std::setprecision (6) << duration << " "
354                     << *gdu;
355                 m_file->log(m_time_format, os);
356             }   
357         }
358
359         if (m_res_session)
360         {
361             std::ostringstream os;
362             os << m_msg_config;
363             os << " response id=" << package.session().id();
364             os << " close=" 
365                << (package.session().is_closed() ? "yes " : "no ")
366                << "duration=" 
367                << std::fixed << std::setprecision (6) << duration;
368             m_file->log(m_time_format, os);
369         }
370
371         if (m_init_options)
372         {
373             if (gdu && gdu->which == Z_GDU_Z3950 &&
374                 gdu->u.z3950->which == Z_APDU_initResponse)
375             {
376                 std::ostringstream os;
377                 os << m_msg_config;
378                 os << " init options:";
379                 yaz_init_opt_decode(gdu->u.z3950->u.initResponse->options,
380                                     option_write, &os);
381                 m_file->log(m_time_format, os);
382             }
383         }
384         
385         if (m_res_apdu)
386         {
387             if (gdu)
388             {
389                 mp::odr odr(ODR_PRINT);
390                 odr_set_stream(odr, m_file->fhandle, stream_write, 0);
391                 z_GDU(odr, &gdu, 0, 0);
392             }
393         }
394     }
395     m_file->flush();
396     yaz_timing_destroy(&timer);
397 }
398
399
400 void yf::Log::Impl::openfile(const std::string &fname)
401 {
402     std::list<LFilePtr>::const_iterator it
403         = filter_log_files.begin();
404     for (; it != filter_log_files.end(); it++)
405     {
406         if ((*it)->m_fname == fname)
407         {
408             m_file = *it;
409             return;
410         }
411     }
412     LFilePtr newfile(new LFile(fname));
413     filter_log_files.push_back(newfile);
414     m_file = newfile;
415 }
416
417
418 void yf::Log::Impl::stream_write(ODR o, void *handle, int type, const char *buf, int len)
419 {
420     FILE *f = (FILE*) handle;
421     fwrite(buf, len, 1, f ? f : yaz_log_file());
422 }
423
424 void yf::Log::Impl::option_write(const char *name, void *handle)
425 {
426     std::ostringstream *os = (std::ostringstream *) handle;
427     *os << " " << name;
428 }
429
430
431 yf::Log::Impl::LFile::LFile(std::string fname) : 
432     m_fname(fname)
433     
434 {
435     if (fname.c_str())
436         fhandle = fopen(fname.c_str(), "a");
437     else
438         fhandle = 0;
439 }
440
441 yf::Log::Impl::LFile::~LFile()
442 {
443 }
444
445 void yf::Log::Impl::LFile::log(const std::string &date_format,
446                                std::ostringstream &os)
447 {
448     if (fhandle)
449     {
450         char datestr[80];
451         time_t ti = time(0);
452 #if HAVE_LOCALTIME_R
453         struct tm tm0, *tm = &tm0;
454         localtime_r(&ti, tm);
455 #else
456         struct tm *tm = localtime(&ti);
457 #endif
458         if (strftime(datestr, sizeof(datestr)-1, date_format.c_str(), tm))
459         {
460             fputs(datestr, fhandle);
461             fputs(" ", fhandle);
462         }
463         fputs(os.str().c_str(), fhandle);
464         fputc('\n', fhandle);
465     }    
466     else
467         yaz_log(YLOG_LOG, "%s", os.str().c_str());
468 }
469
470 void yf::Log::Impl::LFile::flush()
471 {
472     if (fhandle)
473         fflush(fhandle);
474 }
475
476 static mp::filter::Base* filter_creator()
477 {
478     return new mp::filter::Log;
479 }
480
481 extern "C" {
482     struct metaproxy_1_filter_struct metaproxy_1_filter_log = {
483         0,
484         "log",
485         filter_creator
486     };
487 }
488
489 /*
490  * Local variables:
491  * c-basic-offset: 4
492  * c-file-style: "Stroustrup"
493  * indent-tabs-mode: nil
494  * End:
495  * vim: shiftwidth=4 tabstop=8 expandtab
496  */
497