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
37
38#include "blocxx/BLOCXX_config.h"
40#include "blocxx/String.hpp"
41#include "blocxx/LogMessage.hpp"
44#include "blocxx/Format.hpp"
46#include "blocxx/DateTime.hpp"
47#include "blocxx/ThreadImpl.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
58extern "C"
59{
60#include <errno.h>
61}
62
63namespace BLOCXX_NAMESPACE
64{
65
67
68namespace LogMessagePatternFormatterImpl // if this is anonymous, gcc 4.1.1 spits out a warning about using a data member from an unnamed namespace.
69{
70
76
78{
82
83 static const int NO_MIN_WIDTH = -1;
84 static const int NO_MAX_WIDTH = 0x7FFFFFFF;
85
91};
92
93} // end namespace LogMessagePatternFormatterImpl
94
95using namespace LogMessagePatternFormatterImpl;
96
99{
100public:
102 {}
103
104 Converter(const Formatting& formatting)
105 : m_formatting(formatting)
106 {}
107
108 virtual ~Converter() {}
109
110 virtual void formatMessage(const LogMessage& message, StringBuffer& output) const
111 {
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
165private:
167
168};
169
172
177
179void
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
191namespace
192{
193
196
198class MessageConverter : public Converter
199{
200public:
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
221class XMLMessageConverter : public Converter
222{
223public:
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
261class LiteralConverter : public Converter
262{
263public:
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
273private:
274 String m_literal;
275};
276
278class ThreadConverter : public Converter
279{
280public:
281 ThreadConverter(const Formatting& formatting)
282 : Converter(formatting)
283 {}
284
285 virtual void convert(const LogMessage &message, StringBuffer &output) const
286 {
288 }
289};
290
292class PidConverter : public Converter
293{
294public:
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
310class ComponentConverter : public Converter
311{
312public:
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
342private:
343 int m_precision;
344};
345
347class FileLocationConverter : public Converter
348{
349public:
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
364class FullLocationConverter : public Converter
365{
366public:
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
384class LineLocationConverter : public Converter
385{
386public:
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
398class MethodLocationConverter : public Converter
399{
400public:
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
415class CategoryConverter : public Converter
416{
417public:
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
429class RelativeTimeConverter : public Converter
430{
431public:
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
441private:
442 static UInt64 getRelativeTime()
443 {
444 return getNowMillis() - startMillis;
445 }
446
447 static UInt64 startMillis;
448public:
449 static UInt64 getNowMillis()
450 {
451 DateTime now;
452 now.setToCurrent();
453 return UInt64(now.get()) * 1000 + (now.getMicrosecond() / 1000);
454 }
455};
456
457UInt64 RelativeTimeConverter::startMillis(RelativeTimeConverter::getNowMillis());
458
460enum EParserState
461{
462 E_LITERAL_STATE,
463 E_CONVERTER_STATE,
464 E_DOT_STATE,
465 E_MIN_STATE,
466 E_MAX_STATE
467};
468
470class DateConverter : public Converter
471{
472public:
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
530private:
531 String m_format;
532};
533
534const char* const DateConverter::ISO8601_DATE_FORMAT = "ISO8601";
535const char* const DateConverter::ISO8601_PATTERN = "%Y-%m-%d %H:%M:%S,%Q";
536const char* const DateConverter::ABSOLUTE_DATE_FORMAT = "ABSOLUTE";
537const char* const DateConverter::ABSOLUTE_PATTERN = "%H:%M:%S,%Q";
538const char* const DateConverter::DATE_DATE_FORMAT = "DATE";
539const char* const DateConverter::DATE_PATTERN = "%d %b %Y %H:%M:%S,%Q";
540
542class Parser
543{
544public:
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 {
797 dateFormat = DateConverter::ISO8601_DATE_FORMAT;
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
911private:
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
#define BLOCXX_DEFINE_EXCEPTION_WITH_ID(NAME)
Define a new exception class named <NAME>Exception that derives from Exception.
#define BLOCXX_THROW_ERR(exType, msg, err)
Throw an exception using FILE and LINE.
#define BLOCXX_GLOBAL_STRING_INIT(str)
#define CDATA_END_DEF
#define CDATA_START_DEF
#define CDATA_PSEUDO_END_DEF
V::const_iterator const_iterator
Definition Array.hpp:85
virtual void formatMessage(const LogMessage &message, StringBuffer &output) const
virtual void convert(const LogMessage &message, StringBuffer &output) const =0
void formatMessage(const LogMessage &message, StringBuffer &output) const
StringBuffer & append(char c)
void truncate(size_t index)
Truncate the string at the given index.
This String class is an abstract data type that represents as NULL terminated string of characters.
Definition String.hpp:67
String substring(size_t beginIndex, size_t length=npos) const
Create another String object that is comprised of a substring of this String object.
Definition String.cpp:698
static const size_t npos
Definition String.hpp:742
BLOCXX_COMMON_API UInt64 thread_t_ToUInt64(Thread_t thr)
Convert a Thread_t to an UInt64.
Taken from RFC 1321.
LazyGlobal< String, char const *const > GlobalString