blocxx
LogMessagePatternFormatter.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2 * Copyright (C) 2005, Vintela, Inc. All rights reserved.
3 * Copyright (C) 2006, Novell, Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of
14 * Vintela, Inc.,
15 * nor Novell, Inc.,
16 * nor the names of its contributors or employees may be used to
17 * endorse or promote products derived from this software without
18 * specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *******************************************************************************/
32 
33 
38 #include "blocxx/BLOCXX_config.h"
40 #include "blocxx/String.hpp"
41 #include "blocxx/LogMessage.hpp"
42 #include "blocxx/StringBuffer.hpp"
44 #include "blocxx/Format.hpp"
45 #include "blocxx/ExceptionIds.hpp"
46 #include "blocxx/DateTime.hpp"
47 #include "blocxx/ThreadImpl.hpp"
48 #include "blocxx/GlobalString.hpp"
49 
50 #include <vector>
51 #include <cstdlib> // for strtol
52 #include <climits> // for CHAR_MAX
53 
54 #ifdef BLOCXX_HAVE_UNISTD_H
55 #include <unistd.h>
56 #endif
57 
58 extern "C"
59 {
60 #include <errno.h>
61 }
62 
63 namespace BLOCXX_NAMESPACE
64 {
65 
67 
68 namespace LogMessagePatternFormatterImpl // if this is anonymous, gcc 4.1.1 spits out a warning about using a data member from an unnamed namespace.
69 {
70 
72 {
75 };
76 
77 struct Formatting
78 {
79  int minWidth;
80  int maxWidth;
82 
83  static const int NO_MIN_WIDTH = -1;
84  static const int NO_MAX_WIDTH = 0x7FFFFFFF;
85 
90  {}
91 };
92 
93 } // end namespace LogMessagePatternFormatterImpl
94 
95 using namespace LogMessagePatternFormatterImpl;
96 
99 {
100 public:
102  {}
103 
105  : m_formatting(formatting)
106  {}
107 
108  virtual ~Converter() {}
109 
110  virtual void formatMessage(const LogMessage& message, StringBuffer& output) const
111  {
112  if ((m_formatting.minWidth == Formatting::NO_MIN_WIDTH) && (m_formatting.maxWidth == Formatting::NO_MAX_WIDTH))
113  {
114  convert(message, output);
115  }
116  else
117  {
118  StringBuffer buf;
119  convert(message, buf);
120 
121  if (buf.length() == 0)
122  {
123  if (m_formatting.minWidth > 0)
124  {
125  output.append(&(std::vector<char>(size_t(m_formatting.minWidth), ' ')[0]), m_formatting.minWidth);
126  }
127  return;
128  }
129 
130  int len = buf.length();
131  if (len > m_formatting.maxWidth)
132  {
133  if (m_formatting.justification == E_LEFT_JUSTIFY)
134  {
135  buf.truncate(m_formatting.maxWidth);
136  output += buf;
137  }
138  else
139  {
140  output += buf.releaseString().substring(len - m_formatting.maxWidth);
141  }
142  }
143  else if (len < m_formatting.minWidth)
144  {
145  if (m_formatting.justification == E_LEFT_JUSTIFY)
146  {
147  output += buf;
148  output.append(&(std::vector<char>(size_t(m_formatting.minWidth - len), ' ')[0]), m_formatting.minWidth - len);
149  }
150  else
151  {
152  output.append(&(std::vector<char>(size_t(m_formatting.minWidth - len), ' ')[0]), m_formatting.minWidth - len);
153  output += buf;
154  }
155  }
156  else
157  {
158  output += buf;
159  }
160  }
161  }
162 
163  virtual void convert(const LogMessage& message, StringBuffer& output) const = 0;
164 
165 private:
167 
168 };
169 
172 
175 {
176 }
177 
179 void
181 {
183  iter_t end(m_patternConverters.end());
184  for (iter_t i(m_patternConverters.begin()); i != end; ++i)
185  {
186  (*i)->formatMessage(message, output);
187  }
188 }
189 
191 namespace
192 {
193 
194 typedef LogMessagePatternFormatter::Converter Converter;
195 typedef LogMessagePatternFormatter::ConverterRef ConverterRef;
196 
198 class MessageConverter : public Converter
199 {
200 public:
201  MessageConverter(const Formatting& formatting)
202  : Converter(formatting)
203  {}
204 
205  virtual void convert(const LogMessage &message, StringBuffer &output) const
206  {
207  output += message.message;
208  }
209 };
210 
211 #define CDATA_START_DEF "<![CDATA["
212 #define CDATA_END_DEF "]]>"
213 #define CDATA_PSEUDO_END_DEF "]]&gt;"
214 
219 
221 class XMLMessageConverter : public Converter
222 {
223 public:
224  XMLMessageConverter(const Formatting& formatting)
225  : Converter(formatting)
226  {}
227 
228  virtual void convert(const LogMessage &message, StringBuffer &output) const
229  {
230  output += CDATA_START;
231  const String& msg(message.message);
232  if (!msg.empty())
233  {
234  size_t end = msg.indexOf(CDATA_END);
235  if (end == String::npos)
236  {
237  output += msg;
238  }
239 
240  size_t start(0);
241  while (end != String::npos)
242  {
243  output.append(&msg[start], end - start);
244  output += CDATA_EMBEDDED_END;
245  start = end + static_cast<String>(CDATA_END).length();
246  if (start < msg.length())
247  {
248  end = msg.indexOf(CDATA_END, start);
249  }
250  else
251  {
252  break;
253  }
254  }
255  }
256  output += CDATA_END;
257  }
258 };
259 
261 class LiteralConverter : public Converter
262 {
263 public:
264  LiteralConverter(const String& literal)
265  : m_literal(literal)
266  {}
267 
268  virtual void convert(const LogMessage &message, StringBuffer &output) const
269  {
270  output += m_literal;
271  }
272 
273 private:
274  String m_literal;
275 };
276 
278 class ThreadConverter : public Converter
279 {
280 public:
281  ThreadConverter(const Formatting& formatting)
282  : Converter(formatting)
283  {}
284 
285  virtual void convert(const LogMessage &message, StringBuffer &output) const
286  {
288  }
289 };
290 
292 class PidConverter : public Converter
293 {
294 public:
295  PidConverter(const Formatting& formatting)
296  : Converter(formatting)
297  {}
298 
299  virtual void convert(const LogMessage &message, StringBuffer &output) const
300  {
301 #ifdef BLOCXX_WIN32
302  output += ::GetCurrentProcessId();
303 #else
304  output += ::getpid();
305 #endif
306  }
307 };
308 
310 class ComponentConverter : public Converter
311 {
312 public:
313  ComponentConverter(const Formatting& formatting, int precision)
314  : Converter(formatting)
315  , m_precision(precision)
316  {}
317 
318  virtual void convert(const LogMessage &message, StringBuffer &output) const
319  {
320  if (m_precision <= 0)
321  {
322  output += message.component;
323  }
324  else
325  {
326  const String& component(message.component);
327  size_t len(component.length());
328  size_t end(len - 1);
329  for (int i = m_precision; i > 0; --i)
330  {
331  end = component.lastIndexOf('.', end - 1);
332  if (end == String::npos)
333  {
334  output += component;
335  return;
336  }
337  }
338  output += component.substring(end + 1, len - (end + 1));
339  }
340  }
341 
342 private:
344 };
345 
347 class FileLocationConverter : public Converter
348 {
349 public:
350  FileLocationConverter(const Formatting& formatting)
351  : Converter(formatting)
352  {}
353 
354  virtual void convert(const LogMessage &message, StringBuffer &output) const
355  {
356  if (message.filename != 0)
357  {
358  output += message.filename;
359  }
360  }
361 };
362 
364 class FullLocationConverter : public Converter
365 {
366 public:
367  FullLocationConverter(const Formatting& formatting)
368  : Converter(formatting)
369  {}
370 
371  virtual void convert(const LogMessage &message, StringBuffer &output) const
372  {
373  if (message.filename != 0)
374  {
375  output += message.filename;
376  output += '(';
377  output += message.fileline;
378  output += ')';
379  }
380  }
381 };
382 
384 class LineLocationConverter : public Converter
385 {
386 public:
387  LineLocationConverter(const Formatting& formatting)
388  : Converter(formatting)
389  {}
390 
391  virtual void convert(const LogMessage &message, StringBuffer &output) const
392  {
393  output += message.fileline;
394  }
395 };
396 
398 class MethodLocationConverter : public Converter
399 {
400 public:
401  MethodLocationConverter(const Formatting& formatting)
402  : Converter(formatting)
403  {}
404 
405  virtual void convert(const LogMessage &message, StringBuffer &output) const
406  {
407  if (message.methodname != 0)
408  {
409  output += message.methodname;
410  }
411  }
412 };
413 
415 class CategoryConverter : public Converter
416 {
417 public:
418  CategoryConverter(const Formatting& formatting)
419  : Converter(formatting)
420  {}
421 
422  virtual void convert(const LogMessage &message, StringBuffer &output) const
423  {
424  output += message.category;
425  }
426 };
427 
429 class RelativeTimeConverter : public Converter
430 {
431 public:
432  RelativeTimeConverter(const Formatting& formatting)
433  : Converter(formatting)
434  {}
435 
436  virtual void convert(const LogMessage &message, StringBuffer &output) const
437  {
438  output += getRelativeTime();
439  }
440 
441 private:
442  static UInt64 getRelativeTime()
443  {
444  return getNowMillis() - startMillis;
445  }
446 
447  static UInt64 startMillis;
448 public:
449  static UInt64 getNowMillis()
450  {
451  DateTime now;
452  now.setToCurrent();
453  return UInt64(now.get()) * 1000 + (now.getMicrosecond() / 1000);
454  }
455 };
456 
457 UInt64 RelativeTimeConverter::startMillis(RelativeTimeConverter::getNowMillis());
458 
460 enum EParserState
461 {
462  E_LITERAL_STATE,
463  E_CONVERTER_STATE,
464  E_DOT_STATE,
465  E_MIN_STATE,
466  E_MAX_STATE
467 };
468 
470 class DateConverter : public Converter
471 {
472 public:
473  DateConverter(const Formatting& formatting, const String& format)
474  : Converter(formatting)
475  , m_format(format)
476  {
477  size_t pos = m_format.indexOf("%Q");
478  if (pos != String::npos)
479  {
480  // escape the %Q, since strftime doesn't know about it.
481  m_format = m_format.substring(0, pos) + '%' + m_format.substring(pos);
482  }
483  }
484 
485  virtual void convert(const LogMessage &message, StringBuffer &output) const
486  {
487  char buf[255];
488 
489  DateTime now;
490  now.setToCurrent();
491  struct tm nowTm;
492  now.toLocal(nowTm);
493 
494  size_t len = ::strftime(buf, sizeof(buf), m_format.c_str(), &nowTm);
495 
496  buf[len] = '\0';
497 
498  // handle %Q special case
499  char* p = strstr(buf, "%Q");
500  if (p != NULL)
501  {
502  *p = '\0';
503  output += buf;
504  long deciMillis = now.getMicrosecond() / 1000;
505  String strMillis(deciMillis);
506  // output 3 chars
507  switch (strMillis.length())
508  {
509  case 1:
510  output += '0';
511  case 2:
512  output += '0';
513  }
514  output += strMillis;
515  output += p+2;
516  }
517  else
518  {
519  output += buf;
520  }
521  }
522 
523  static const char* const ISO8601_DATE_FORMAT;
524  static const char* const ISO8601_PATTERN;
525  static const char* const ABSOLUTE_DATE_FORMAT;
526  static const char* const ABSOLUTE_PATTERN;
527  static const char* const DATE_DATE_FORMAT;
528  static const char* const DATE_PATTERN;
529 
530 private:
531  String m_format;
532 };
533 
534 const char* const DateConverter::ISO8601_DATE_FORMAT = "ISO8601";
535 const char* const DateConverter::ISO8601_PATTERN = "%Y-%m-%d %H:%M:%S,%Q";
536 const char* const DateConverter::ABSOLUTE_DATE_FORMAT = "ABSOLUTE";
537 const char* const DateConverter::ABSOLUTE_PATTERN = "%H:%M:%S,%Q";
538 const char* const DateConverter::DATE_DATE_FORMAT = "DATE";
539 const char* const DateConverter::DATE_PATTERN = "%d %b %Y %H:%M:%S,%Q";
540 
542 class Parser
543 {
544 public:
545  Parser(const String& pattern_)
546  : i(0)
547  , state(E_LITERAL_STATE)
548  , pattern(pattern_)
549  {}
550 
552  void parse(Array<ConverterRef>& converters)
553  {
554  char c;
555  size_t patternLength(pattern.length());
556 
557  while (i < patternLength)
558  {
559  c = pattern[i];
560  ++i;
561  switch (state)
562  {
563  case E_LITERAL_STATE:
564  {
565  if (i == patternLength)
566  {
567  literal += c;
568  continue;
569  }
570  // handle %% -> % and %n -> \n or move to the CONVERTER_STATE
571  else if (c == '%')
572  {
573  switch (pattern[i])
574  {
575  case '%':
576  literal += c;
577  ++i;
578  break;
579  case 'n':
580  literal += '\n';
581  ++i;
582  break;
583  default:
584  if (literal.length() > 0)
585  {
586  converters.push_back(ConverterRef(new LiteralConverter(literal.toString())));
587  literal.reset();
588  }
589  literal += c;
590  state = E_CONVERTER_STATE;
591  formatting = Formatting();
592  }
593  }
594  // handle \n, \\, \r, \t, \x<hexDigits>
595  else if (c == '\\')
596  {
597  switch (pattern[i])
598  {
599  case 'n':
600  literal += '\n';
601  ++i;
602  break;
603 
604  case '\\':
605  literal += '\\';
606  ++i;
607  break;
608 
609  case 'r':
610  literal += '\r';
611  ++i;
612  break;
613 
614  case 't':
615  literal += '\t';
616  ++i;
617  break;
618 
619  case 'x':
620  {
621  if (i + 1 > patternLength)
622  {
623  literal += "\\x";
624  ++i;
625  break;
626  }
627 
628  char* begin = &pattern[i+1];
629  char* end(0);
630  errno = 0;
631  int hexNumber = std::strtol(begin, &end, 16);
632  if (end == begin || errno == ERANGE || hexNumber > CHAR_MAX)
633  {
634  literal += "\\x";
635  ++i;
636  break;
637  }
638  literal += static_cast<char>(hexNumber);
639  i += (end - begin) + 1;
640  }
641  break;
642 
643  default:
644  literal += '\\';
645  break;
646  }
647  }
648  else
649  {
650  literal += c;
651  }
652  }
653  break;
654  // handle converter stuff after a %
655  case E_CONVERTER_STATE:
656  {
657  literal += c;
658  switch (c)
659  {
660  case '-':
661  formatting.justification = E_LEFT_JUSTIFY;
662  break;
663  case '.':
664  state = E_DOT_STATE;
665  break;
666  default:
667  if (isdigit(c))
668  {
669  formatting.minWidth = c - '0';
670  state = E_MIN_STATE;
671  }
672  else
673  {
674  converters.push_back(finalizeConverter(c));
675  }
676  }
677  }
678  break;
679  case E_MIN_STATE:
680  {
681  literal += c;
682  if (isdigit(c))
683  {
684  formatting.minWidth = formatting.minWidth * 10 + (c - '0');
685  }
686  else if (c == '.')
687  {
688  state = E_DOT_STATE;
689  }
690  else
691  {
692  converters.push_back(finalizeConverter(c));
693  }
694  }
695  break;
696  case E_DOT_STATE:
697  {
698  literal += c;
699  if (isdigit(c))
700  {
701  formatting.maxWidth = c - '0';
702  state = E_MAX_STATE;
703  }
704  else
705  {
706  BLOCXX_THROW_ERR(LogMessagePatternFormatterException,
707  Format("Invalid pattern \"%1\" in position %2. Was expecting a digit, instead got char %3.",
708  pattern, i, c).c_str(),
710  }
711  }
712  break;
713  case E_MAX_STATE:
714  {
715  literal += c;
716  if (isdigit(c))
717  {
718  formatting.maxWidth = formatting.maxWidth * 10 + (c - '0');
719  }
720  else
721  {
722  converters.push_back(finalizeConverter(c));
723  state = E_LITERAL_STATE;
724  }
725  }
726  break;
727  } // switch
728  } // while
729 
730  // hanlde whatever is left
731  if (literal.length() > 0)
732  {
733  converters.push_back(ConverterRef(new LiteralConverter(literal.toString())));
734  }
735  }
736 
738  String getOption()
739  {
740  // retrieves the contents of a { }, like in a %d{ISO8601}
741  if ((i < pattern.length()) && (pattern[i] == '{'))
742  {
743  size_t end = pattern.indexOf('}', i);
744  if (end > i)
745  {
746  String rv = pattern.substring(i + 1, end - (i + 1));
747  i = end + 1;
748  return rv;
749  }
750  }
751 
752  return String();
753  }
754 
756  int getPrecision()
757  {
758  // retrieves the numeric contents of a { }, like in a %c{2}
759  String opt = getOption();
760  int rv = 0;
761  if (!opt.empty())
762  {
763  try
764  {
765  rv = opt.toUInt32();
766  }
767  catch (StringConversionException& e)
768  {
769  BLOCXX_THROW_ERR(LogMessagePatternFormatterException,
770  Format("Invalid pattern \"%1\" in position %2. A positive integer is required for precision option (%3).",
771  pattern, i, opt).c_str(),
773  }
774  }
775  return rv;
776  }
777 
779  ConverterRef finalizeConverter(char c)
780  {
781  // handle the actual type of converter
782  ConverterRef rv;
783  switch (c)
784  {
785  case 'c':
786  {
787  rv = new ComponentConverter(formatting, getPrecision());
788  }
789  break;
790 
791  case 'd':
792  {
793  String dateFormat;
794  String dateOpt = getOption();
795  if (dateOpt.empty())
796  {
798  }
799  else
800  {
801  dateFormat = dateOpt;
802  }
803 
804  // take care of the predefined date formats
805  if (dateFormat.equalsIgnoreCase(DateConverter::ISO8601_DATE_FORMAT))
806  {
807  dateFormat = DateConverter::ISO8601_PATTERN;
808  }
809  else if (dateFormat.equalsIgnoreCase(DateConverter::ABSOLUTE_DATE_FORMAT))
810  {
811  dateFormat = DateConverter::ABSOLUTE_PATTERN;
812  }
813  else if (dateFormat.equalsIgnoreCase(DateConverter::DATE_DATE_FORMAT))
814  {
815  dateFormat = DateConverter::DATE_PATTERN;
816  }
817 
818  rv = new DateConverter(formatting, dateFormat);
819  }
820  break;
821 
822  case 'F':
823  {
824  rv = new FileLocationConverter(formatting);
825  }
826  break;
827 
828  case 'l':
829  {
830  rv = new FullLocationConverter(formatting);
831  }
832  break;
833 
834  case 'L':
835  {
836  rv = new LineLocationConverter(formatting);
837  }
838  break;
839 
840  case 'M':
841  {
842  rv = new MethodLocationConverter(formatting);
843  }
844  break;
845 
846  case 'm':
847  {
848  rv = new MessageConverter(formatting);
849  }
850  break;
851 
852  case 'e':
853  {
854  rv = new XMLMessageConverter(formatting);
855  }
856  break;
857 
858  case 'p':
859  {
860  rv = new CategoryConverter(formatting);
861  }
862  break;
863 
864  case 'r':
865  {
866  rv = new RelativeTimeConverter(formatting);
867  }
868  break;
869 
870  case 't':
871  {
872  rv = new ThreadConverter(formatting);
873  }
874  break;
875 
876  case 'P':
877  {
878  rv = new PidConverter(formatting);
879  }
880  break;
881 #if 0 // don't support these for now.
882  case 'x':
883  {
884 
885  }
886  break;
887 
888  case 'X':
889  {
890 
891  }
892  break;
893 #endif
894  default:
895  {
896  BLOCXX_THROW_ERR(LogMessagePatternFormatterException,
897  Format("Invalid pattern \"%1\" in position %2. Unsupported conversion (%3).",
898  pattern, i, c).c_str(),
900 
901  }
902  break;
903  }
904 
905  literal.reset();
906  state = E_LITERAL_STATE;
907  formatting = Formatting();
908  return rv;
909  }
910 
911 private:
912  size_t i;
913  EParserState state;
914  StringBuffer literal;
915  Formatting formatting;
916  String pattern;
917 };
918 
919 
920 } // end unnamed namespace
921 
924 {
925  Parser parser(pattern);
926  parser.parse(m_patternConverters);
927 }
928 
929 } // end namespace BLOCXX_NAMESPACE
930 
931 
932 
933 
934