Add path to configure method of filter.
[metaproxy-moved-to-github.git] / src / filter_log.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2011 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                         const char *path)
114 {
115     m_p->configure(xmlnode);
116 }
117
118 void yf::Log::process(mp::Package &package) const
119 {
120     m_p->process(package);
121 }
122
123
124 // static initialization
125 std::list<yf::Log::Impl::LFilePtr> yf::Log::Impl::filter_log_files;
126
127
128 yf::Log::Impl::Impl(const std::string &x)
129     : m_msg_config(x),
130       m_access(true),
131       m_user_access(false),
132       m_req_apdu(false),
133       m_res_apdu(false),
134       m_req_session(false),
135       m_res_session(false),
136       m_init_options(false),
137       m_time_format("%H:%M:%S-%d/%m")
138 {
139     openfile("");
140 }
141
142
143 yf::Log::Impl::~Impl() 
144 {
145 }
146
147
148 void yf::Log::Impl::configure(const xmlNode *ptr)
149 {
150     for (ptr = ptr->children; ptr; ptr = ptr->next)
151     {
152         if (ptr->type != XML_ELEMENT_NODE)
153             continue;
154         if (!strcmp((const char *) ptr->name, "message"))
155             m_msg_config = mp::xml::get_text(ptr);
156         else if (!strcmp((const char *) ptr->name, "filename"))
157         {
158             std::string fname = mp::xml::get_text(ptr);
159             openfile(fname);
160         }
161         else if (!strcmp((const char *) ptr->name, "time-format"))
162         {
163             m_time_format = mp::xml::get_text(ptr);
164         }
165         else if (!strcmp((const char *) ptr->name, "category"))
166         {
167             const struct _xmlAttr *attr;
168             for (attr = ptr->properties; attr; attr = attr->next)
169             {
170                 if (!strcmp((const char *) attr->name,  "access"))
171                     m_access = mp::xml::get_bool(attr->children, true);
172                 else if (!strcmp((const char *) attr->name, "user-access"))
173                     m_user_access = mp::xml::get_bool(attr->children, true);
174                 else if (!strcmp((const char *) attr->name, "request-apdu"))
175                     m_req_apdu = mp::xml::get_bool(attr->children, true);
176                 else if (!strcmp((const char *) attr->name, "response-apdu"))
177                     m_res_apdu = mp::xml::get_bool(attr->children, true);
178                 else if (!strcmp((const char *) attr->name, "apdu"))
179                 {
180                     m_req_apdu = mp::xml::get_bool(attr->children, true);
181                     m_res_apdu = m_req_apdu;
182                 }
183                 else if (!strcmp((const char *) attr->name,
184                                  "request-session"))
185                     m_req_session = 
186                         mp::xml::get_bool(attr->children, true);
187                 else if (!strcmp((const char *) attr->name, 
188                                  "response-session"))
189                     m_res_session = 
190                         mp::xml::get_bool(attr->children, true);
191                 else if (!strcmp((const char *) attr->name,
192                                  "session"))
193                 {
194                     m_req_session = 
195                         mp::xml::get_bool(attr->children, true);
196                     m_res_session = m_req_session;
197                 }
198                 else if (!strcmp((const char *) attr->name, 
199                                  "init-options"))
200                     m_init_options = 
201                         mp::xml::get_bool(attr->children, true);
202                 else if (!strcmp((const char *) attr->name, 
203                                  "init-options"))
204                     m_init_options = 
205                         mp::xml::get_bool(attr->children, true);
206                 else
207                     throw mp::filter::FilterException(
208                         "Bad attribute " + std::string((const char *)
209                                                        attr->name));
210             }
211         }
212         else
213         {
214             throw mp::filter::FilterException("Bad element " 
215                                                + std::string((const char *)
216                                                              ptr->name));
217         }
218     }
219 }
220
221 void yf::Log::Impl::process(mp::Package &package)
222 {
223     Z_GDU *gdu = package.request().get();
224     std::string user("-");
225
226     yaz_timing_t timer = yaz_timing_create();
227
228     // scope for session lock
229     {
230         boost::mutex::scoped_lock scoped_lock(m_session_mutex);
231         
232         if (gdu && gdu->which == Z_GDU_Z3950)
233         {
234             Z_APDU *apdu_req = gdu->u.z3950;
235             if (apdu_req->which == Z_APDU_initRequest)
236             {
237                 Z_InitRequest *req = apdu_req->u.initRequest;
238                 Z_IdAuthentication *a = req->idAuthentication;
239                 if (a)
240                 {
241                     if (a->which == Z_IdAuthentication_idPass)
242                         user = a->u.idPass->userId;
243                     else if (a->which == Z_IdAuthentication_open)
244                         user = a->u.open;
245                 
246                     m_sessions[package.session()] = user;
247                 }
248             }
249         }
250         std::map<mp::Session,std::string>::iterator it = 
251             m_sessions.find(package.session());
252         if (it != m_sessions.end())
253             user = it->second;
254         
255         if (package.session().is_closed())
256             m_sessions.erase(package.session());
257     }
258     // scope for locking Ostream
259     { 
260         boost::mutex::scoped_lock scoped_lock(m_file->m_mutex);
261  
262         if (m_access)
263         {
264             if (gdu)          
265             {
266                 std::ostringstream os;
267                 os  << m_msg_config << " "
268                     << package << " "
269                     << "0.000000" << " " 
270                     << *gdu;
271                 m_file->log(m_time_format, os);
272             }
273         }
274
275         if (m_user_access)
276         {
277             if (gdu)          
278             {
279                 std::ostringstream os;
280                 os  << m_msg_config << " " << user << " "
281                     << package << " "
282                     << "0.000000" << " " 
283                     << *gdu;
284                 m_file->log(m_time_format, os);
285             }
286         }
287
288         if (m_req_session)
289         {
290             std::ostringstream os;
291             os << m_msg_config;
292             os << " request id=" << package.session().id();
293             os << " close=" 
294                << (package.session().is_closed() ? "yes" : "no");
295             m_file->log(m_time_format, os);
296         }
297
298         if (m_init_options)
299         {
300             if (gdu && gdu->which == Z_GDU_Z3950 &&
301                 gdu->u.z3950->which == Z_APDU_initRequest)
302             {
303                 std::ostringstream os;
304                 os << m_msg_config << " init options:";
305                 yaz_init_opt_decode(gdu->u.z3950->u.initRequest->options,
306                                     option_write, &os);
307                 m_file->log(m_time_format, os);
308             }
309         }
310         
311         if (m_req_apdu)
312         {
313             if (gdu)
314             {
315                 mp::odr odr(ODR_PRINT);
316                 odr_set_stream(odr, m_file->fhandle, stream_write, 0);
317                 z_GDU(odr, &gdu, 0, 0);
318             }
319         }
320     }
321     
322     // unlocked during move
323     package.move();
324
325     gdu = package.response().get();
326
327     yaz_timing_stop(timer);
328     double duration = yaz_timing_get_real(timer);
329
330     // scope for locking Ostream 
331     { 
332         boost::mutex::scoped_lock scoped_lock(m_file->m_mutex);
333
334         if (m_access)
335         {
336             if (gdu)
337             {
338                 std::ostringstream os;
339                 os  << m_msg_config << " "
340                     << package << " "
341                     << std::fixed << std::setprecision (6) << duration
342                     << " "
343                     << *gdu;
344                 m_file->log(m_time_format, os);
345             }
346         }
347         if (m_user_access)
348         {
349             if (gdu)
350             {
351                 std::ostringstream os;
352                 os  << m_msg_config << " " << user << " "
353                     << package << " "
354                     << std::fixed << std::setprecision (6) << duration << " "
355                     << *gdu;
356                 m_file->log(m_time_format, os);
357             }   
358         }
359
360         if (m_res_session)
361         {
362             std::ostringstream os;
363             os << m_msg_config;
364             os << " response id=" << package.session().id();
365             os << " close=" 
366                << (package.session().is_closed() ? "yes " : "no ")
367                << "duration=" 
368                << std::fixed << std::setprecision (6) << duration;
369             m_file->log(m_time_format, os);
370         }
371
372         if (m_init_options)
373         {
374             if (gdu && gdu->which == Z_GDU_Z3950 &&
375                 gdu->u.z3950->which == Z_APDU_initResponse)
376             {
377                 std::ostringstream os;
378                 os << m_msg_config;
379                 os << " init options:";
380                 yaz_init_opt_decode(gdu->u.z3950->u.initResponse->options,
381                                     option_write, &os);
382                 m_file->log(m_time_format, os);
383             }
384         }
385         
386         if (m_res_apdu)
387         {
388             if (gdu)
389             {
390                 mp::odr odr(ODR_PRINT);
391                 odr_set_stream(odr, m_file->fhandle, stream_write, 0);
392                 z_GDU(odr, &gdu, 0, 0);
393             }
394         }
395     }
396     m_file->flush();
397     yaz_timing_destroy(&timer);
398 }
399
400
401 void yf::Log::Impl::openfile(const std::string &fname)
402 {
403     std::list<LFilePtr>::const_iterator it
404         = filter_log_files.begin();
405     for (; it != filter_log_files.end(); it++)
406     {
407         if ((*it)->m_fname == fname)
408         {
409             m_file = *it;
410             return;
411         }
412     }
413     LFilePtr newfile(new LFile(fname));
414     filter_log_files.push_back(newfile);
415     m_file = newfile;
416 }
417
418
419 void yf::Log::Impl::stream_write(ODR o, void *handle, int type, const char *buf, int len)
420 {
421     FILE *f = (FILE*) handle;
422     fwrite(buf, len, 1, f ? f : yaz_log_file());
423 }
424
425 void yf::Log::Impl::option_write(const char *name, void *handle)
426 {
427     std::ostringstream *os = (std::ostringstream *) handle;
428     *os << " " << name;
429 }
430
431
432 yf::Log::Impl::LFile::LFile(std::string fname) : 
433     m_fname(fname)
434     
435 {
436     if (fname.c_str())
437         fhandle = fopen(fname.c_str(), "a");
438     else
439         fhandle = 0;
440 }
441
442 yf::Log::Impl::LFile::~LFile()
443 {
444 }
445
446 void yf::Log::Impl::LFile::log(const std::string &date_format,
447                                std::ostringstream &os)
448 {
449     if (fhandle)
450     {
451         char datestr[80];
452         time_t ti = time(0);
453 #if HAVE_LOCALTIME_R
454         struct tm tm0, *tm = &tm0;
455         localtime_r(&ti, tm);
456 #else
457         struct tm *tm = localtime(&ti);
458 #endif
459         if (strftime(datestr, sizeof(datestr)-1, date_format.c_str(), tm))
460         {
461             fputs(datestr, fhandle);
462             fputs(" ", fhandle);
463         }
464         fputs(os.str().c_str(), fhandle);
465         fputc('\n', fhandle);
466     }    
467     else
468         yaz_log(YLOG_LOG, "%s", os.str().c_str());
469 }
470
471 void yf::Log::Impl::LFile::flush()
472 {
473     if (fhandle)
474         fflush(fhandle);
475 }
476
477 static mp::filter::Base* filter_creator()
478 {
479     return new mp::filter::Log;
480 }
481
482 extern "C" {
483     struct metaproxy_1_filter_struct metaproxy_1_filter_log = {
484         0,
485         "log",
486         filter_creator
487     };
488 }
489
490 /*
491  * Local variables:
492  * c-basic-offset: 4
493  * c-file-style: "Stroustrup"
494  * indent-tabs-mode: nil
495  * End:
496  * vim: shiftwidth=4 tabstop=8 expandtab
497  */
498