00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00038 #include "blocxx/BLOCXX_config.h"
00039 #include "blocxx/LogMessagePatternFormatter.hpp"
00040 #include "blocxx/String.hpp"
00041 #include "blocxx/LogMessage.hpp"
00042 #include "blocxx/StringBuffer.hpp"
00043 #include "blocxx/IntrusiveCountableBase.hpp"
00044 #include "blocxx/Format.hpp"
00045 #include "blocxx/ExceptionIds.hpp"
00046 #include "blocxx/DateTime.hpp"
00047 #include "blocxx/ThreadImpl.hpp"
00048 #include "blocxx/GlobalString.hpp"
00049
00050 #include <vector>
00051 #include <cstdlib>
00052 #include <climits>
00053
00054 #ifdef BLOCXX_HAVE_UNISTD_H
00055 #include <unistd.h>
00056 #endif
00057
00058 extern "C"
00059 {
00060 #include <errno.h>
00061 }
00062
00063 namespace BLOCXX_NAMESPACE
00064 {
00065
00066 BLOCXX_DEFINE_EXCEPTION_WITH_ID(LogMessagePatternFormatter);
00067
00068 namespace LogMessagePatternFormatterImpl
00069 {
00070
00071 enum EJustificationFlag
00072 {
00073 E_RIGHT_JUSTIFY,
00074 E_LEFT_JUSTIFY
00075 };
00076
00077 struct Formatting
00078 {
00079 int minWidth;
00080 int maxWidth;
00081 EJustificationFlag justification;
00082
00083 static const int NO_MIN_WIDTH = -1;
00084 static const int NO_MAX_WIDTH = 0x7FFFFFFF;
00085
00086 Formatting()
00087 : minWidth(NO_MIN_WIDTH)
00088 , maxWidth(NO_MAX_WIDTH)
00089 , justification(E_RIGHT_JUSTIFY)
00090 {}
00091 };
00092
00093 }
00094
00095 using namespace LogMessagePatternFormatterImpl;
00096
00098 class LogMessagePatternFormatter::Converter : public IntrusiveCountableBase
00099 {
00100 public:
00101 Converter()
00102 {}
00103
00104 Converter(const Formatting& formatting)
00105 : m_formatting(formatting)
00106 {}
00107
00108 virtual ~Converter() {}
00109
00110 virtual void formatMessage(const LogMessage& message, StringBuffer& output) const
00111 {
00112 if ((m_formatting.minWidth == Formatting::NO_MIN_WIDTH) && (m_formatting.maxWidth == Formatting::NO_MAX_WIDTH))
00113 {
00114 convert(message, output);
00115 }
00116 else
00117 {
00118 StringBuffer buf;
00119 convert(message, buf);
00120
00121 if (buf.length() == 0)
00122 {
00123 if (m_formatting.minWidth > 0)
00124 {
00125 output.append(&(std::vector<char>(size_t(m_formatting.minWidth), ' ')[0]), m_formatting.minWidth);
00126 }
00127 return;
00128 }
00129
00130 int len = buf.length();
00131 if (len > m_formatting.maxWidth)
00132 {
00133 if (m_formatting.justification == E_LEFT_JUSTIFY)
00134 {
00135 buf.truncate(m_formatting.maxWidth);
00136 output += buf;
00137 }
00138 else
00139 {
00140 output += buf.releaseString().substring(len - m_formatting.maxWidth);
00141 }
00142 }
00143 else if (len < m_formatting.minWidth)
00144 {
00145 if (m_formatting.justification == E_LEFT_JUSTIFY)
00146 {
00147 output += buf;
00148 output.append(&(std::vector<char>(size_t(m_formatting.minWidth - len), ' ')[0]), m_formatting.minWidth - len);
00149 }
00150 else
00151 {
00152 output.append(&(std::vector<char>(size_t(m_formatting.minWidth - len), ' ')[0]), m_formatting.minWidth - len);
00153 output += buf;
00154 }
00155 }
00156 else
00157 {
00158 output += buf;
00159 }
00160 }
00161 }
00162
00163 virtual void convert(const LogMessage& message, StringBuffer& output) const = 0;
00164
00165 private:
00166 Formatting m_formatting;
00167
00168 };
00169
00171 const GlobalString LogMessagePatternFormatter::STR_DEFAULT_MESSAGE_PATTERN = BLOCXX_GLOBAL_STRING_INIT("%r [%t] %p %c - %m");
00172
00174 LogMessagePatternFormatter::~LogMessagePatternFormatter()
00175 {
00176 }
00177
00179 void
00180 LogMessagePatternFormatter::formatMessage(const LogMessage& message, StringBuffer& output) const
00181 {
00182 typedef Array<ConverterRef>::const_iterator iter_t;
00183 iter_t end(m_patternConverters.end());
00184 for (iter_t i(m_patternConverters.begin()); i != end; ++i)
00185 {
00186 (*i)->formatMessage(message, output);
00187 }
00188 }
00189
00191 namespace
00192 {
00193
00194 typedef LogMessagePatternFormatter::Converter Converter;
00195 typedef LogMessagePatternFormatter::ConverterRef ConverterRef;
00196
00198 class MessageConverter : public Converter
00199 {
00200 public:
00201 MessageConverter(const Formatting& formatting)
00202 : Converter(formatting)
00203 {}
00204
00205 virtual void convert(const LogMessage &message, StringBuffer &output) const
00206 {
00207 output += message.message;
00208 }
00209 };
00210
00211 #define CDATA_START_DEF "<![CDATA["
00212 #define CDATA_END_DEF "]]>"
00213 #define CDATA_PSEUDO_END_DEF "]]>"
00214
00215 GlobalString CDATA_START = BLOCXX_GLOBAL_STRING_INIT(CDATA_START_DEF);
00216 GlobalString CDATA_END = BLOCXX_GLOBAL_STRING_INIT(CDATA_END_DEF);
00217 GlobalString CDATA_PSEUDO_END = BLOCXX_GLOBAL_STRING_INIT(CDATA_PSEUDO_END_DEF);
00218 GlobalString CDATA_EMBEDDED_END = BLOCXX_GLOBAL_STRING_INIT(CDATA_END_DEF CDATA_PSEUDO_END_DEF CDATA_START_DEF);
00219
00221 class XMLMessageConverter : public Converter
00222 {
00223 public:
00224 XMLMessageConverter(const Formatting& formatting)
00225 : Converter(formatting)
00226 {}
00227
00228 virtual void convert(const LogMessage &message, StringBuffer &output) const
00229 {
00230 output += CDATA_START;
00231 const String& msg(message.message);
00232 if (!msg.empty())
00233 {
00234 size_t end = msg.indexOf(CDATA_END);
00235 if (end == String::npos)
00236 {
00237 output += msg;
00238 }
00239
00240 size_t start(0);
00241 while (end != String::npos)
00242 {
00243 output.append(&msg[start], end - start);
00244 output += CDATA_EMBEDDED_END;
00245 start = end + static_cast<String>(CDATA_END).length();
00246 if (start < msg.length())
00247 {
00248 end = msg.indexOf(CDATA_END, start);
00249 }
00250 else
00251 {
00252 break;
00253 }
00254 }
00255 }
00256 output += CDATA_END;
00257 }
00258 };
00259
00261 class LiteralConverter : public Converter
00262 {
00263 public:
00264 LiteralConverter(const String& literal)
00265 : m_literal(literal)
00266 {}
00267
00268 virtual void convert(const LogMessage &message, StringBuffer &output) const
00269 {
00270 output += m_literal;
00271 }
00272
00273 private:
00274 String m_literal;
00275 };
00276
00278 class ThreadConverter : public Converter
00279 {
00280 public:
00281 ThreadConverter(const Formatting& formatting)
00282 : Converter(formatting)
00283 {}
00284
00285 virtual void convert(const LogMessage &message, StringBuffer &output) const
00286 {
00287 output += ThreadImpl::thread_t_ToUInt64(ThreadImpl::currentThread());
00288 }
00289 };
00290
00292 class PidConverter : public Converter
00293 {
00294 public:
00295 PidConverter(const Formatting& formatting)
00296 : Converter(formatting)
00297 {}
00298
00299 virtual void convert(const LogMessage &message, StringBuffer &output) const
00300 {
00301 #ifdef BLOCXX_WIN32
00302 output += ::GetCurrentProcessId();
00303 #else
00304 output += ::getpid();
00305 #endif
00306 }
00307 };
00308
00310 class ComponentConverter : public Converter
00311 {
00312 public:
00313 ComponentConverter(const Formatting& formatting, int precision)
00314 : Converter(formatting)
00315 , m_precision(precision)
00316 {}
00317
00318 virtual void convert(const LogMessage &message, StringBuffer &output) const
00319 {
00320 if (m_precision <= 0)
00321 {
00322 output += message.component;
00323 }
00324 else
00325 {
00326 const String& component(message.component);
00327 size_t len(component.length());
00328 size_t end(len - 1);
00329 for (int i = m_precision; i > 0; --i)
00330 {
00331 end = component.lastIndexOf('.', end - 1);
00332 if (end == String::npos)
00333 {
00334 output += component;
00335 return;
00336 }
00337 }
00338 output += component.substring(end + 1, len - (end + 1));
00339 }
00340 }
00341
00342 private:
00343 int m_precision;
00344 };
00345
00347 class FileLocationConverter : public Converter
00348 {
00349 public:
00350 FileLocationConverter(const Formatting& formatting)
00351 : Converter(formatting)
00352 {}
00353
00354 virtual void convert(const LogMessage &message, StringBuffer &output) const
00355 {
00356 if (message.filename != 0)
00357 {
00358 output += message.filename;
00359 }
00360 }
00361 };
00362
00364 class FullLocationConverter : public Converter
00365 {
00366 public:
00367 FullLocationConverter(const Formatting& formatting)
00368 : Converter(formatting)
00369 {}
00370
00371 virtual void convert(const LogMessage &message, StringBuffer &output) const
00372 {
00373 if (message.filename != 0)
00374 {
00375 output += message.filename;
00376 output += '(';
00377 output += message.fileline;
00378 output += ')';
00379 }
00380 }
00381 };
00382
00384 class LineLocationConverter : public Converter
00385 {
00386 public:
00387 LineLocationConverter(const Formatting& formatting)
00388 : Converter(formatting)
00389 {}
00390
00391 virtual void convert(const LogMessage &message, StringBuffer &output) const
00392 {
00393 output += message.fileline;
00394 }
00395 };
00396
00398 class MethodLocationConverter : public Converter
00399 {
00400 public:
00401 MethodLocationConverter(const Formatting& formatting)
00402 : Converter(formatting)
00403 {}
00404
00405 virtual void convert(const LogMessage &message, StringBuffer &output) const
00406 {
00407 if (message.methodname != 0)
00408 {
00409 output += message.methodname;
00410 }
00411 }
00412 };
00413
00415 class CategoryConverter : public Converter
00416 {
00417 public:
00418 CategoryConverter(const Formatting& formatting)
00419 : Converter(formatting)
00420 {}
00421
00422 virtual void convert(const LogMessage &message, StringBuffer &output) const
00423 {
00424 output += message.category;
00425 }
00426 };
00427
00429 class RelativeTimeConverter : public Converter
00430 {
00431 public:
00432 RelativeTimeConverter(const Formatting& formatting)
00433 : Converter(formatting)
00434 {}
00435
00436 virtual void convert(const LogMessage &message, StringBuffer &output) const
00437 {
00438 output += getRelativeTime();
00439 }
00440
00441 private:
00442 static UInt64 getRelativeTime()
00443 {
00444 return getNowMillis() - startMillis;
00445 }
00446
00447 static UInt64 startMillis;
00448 public:
00449 static UInt64 getNowMillis()
00450 {
00451 DateTime now;
00452 now.setToCurrent();
00453 return UInt64(now.get()) * 1000 + (now.getMicrosecond() / 1000);
00454 }
00455 };
00456
00457 UInt64 RelativeTimeConverter::startMillis(RelativeTimeConverter::getNowMillis());
00458
00460 enum EParserState
00461 {
00462 E_LITERAL_STATE,
00463 E_CONVERTER_STATE,
00464 E_DOT_STATE,
00465 E_MIN_STATE,
00466 E_MAX_STATE
00467 };
00468
00470 class DateConverter : public Converter
00471 {
00472 public:
00473 DateConverter(const Formatting& formatting, const String& format)
00474 : Converter(formatting)
00475 , m_format(format)
00476 {
00477 size_t pos = m_format.indexOf("%Q");
00478 if (pos != String::npos)
00479 {
00480
00481 m_format = m_format.substring(0, pos) + '%' + m_format.substring(pos);
00482 }
00483 }
00484
00485 virtual void convert(const LogMessage &message, StringBuffer &output) const
00486 {
00487 char buf[255];
00488
00489 DateTime now;
00490 now.setToCurrent();
00491 struct tm nowTm;
00492 now.toLocal(nowTm);
00493
00494 size_t len = ::strftime(buf, sizeof(buf), m_format.c_str(), &nowTm);
00495
00496 buf[len] = '\0';
00497
00498
00499 char* p = strstr(buf, "%Q");
00500 if (p != NULL)
00501 {
00502 *p = '\0';
00503 output += buf;
00504 long deciMillis = now.getMicrosecond() / 1000;
00505 String strMillis(deciMillis);
00506
00507 switch (strMillis.length())
00508 {
00509 case 1:
00510 output += '0';
00511 case 2:
00512 output += '0';
00513 }
00514 output += strMillis;
00515 output += p+2;
00516 }
00517 else
00518 {
00519 output += buf;
00520 }
00521 }
00522
00523 static const char* const ISO8601_DATE_FORMAT;
00524 static const char* const ISO8601_PATTERN;
00525 static const char* const ABSOLUTE_DATE_FORMAT;
00526 static const char* const ABSOLUTE_PATTERN;
00527 static const char* const DATE_DATE_FORMAT;
00528 static const char* const DATE_PATTERN;
00529
00530 private:
00531 String m_format;
00532 };
00533
00534 const char* const DateConverter::ISO8601_DATE_FORMAT = "ISO8601";
00535 const char* const DateConverter::ISO8601_PATTERN = "%Y-%m-%d %H:%M:%S,%Q";
00536 const char* const DateConverter::ABSOLUTE_DATE_FORMAT = "ABSOLUTE";
00537 const char* const DateConverter::ABSOLUTE_PATTERN = "%H:%M:%S,%Q";
00538 const char* const DateConverter::DATE_DATE_FORMAT = "DATE";
00539 const char* const DateConverter::DATE_PATTERN = "%d %b %Y %H:%M:%S,%Q";
00540
00542 class Parser
00543 {
00544 public:
00545 Parser(const String& pattern_)
00546 : i(0)
00547 , state(E_LITERAL_STATE)
00548 , pattern(pattern_)
00549 {}
00550
00552 void parse(Array<ConverterRef>& converters)
00553 {
00554 char c;
00555 size_t patternLength(pattern.length());
00556
00557 while (i < patternLength)
00558 {
00559 c = pattern[i];
00560 ++i;
00561 switch (state)
00562 {
00563 case E_LITERAL_STATE:
00564 {
00565 if (i == patternLength)
00566 {
00567 literal += c;
00568 continue;
00569 }
00570
00571 else if (c == '%')
00572 {
00573 switch (pattern[i])
00574 {
00575 case '%':
00576 literal += c;
00577 ++i;
00578 break;
00579 case 'n':
00580 literal += '\n';
00581 ++i;
00582 break;
00583 default:
00584 if (literal.length() > 0)
00585 {
00586 converters.push_back(ConverterRef(new LiteralConverter(literal.toString())));
00587 literal.reset();
00588 }
00589 literal += c;
00590 state = E_CONVERTER_STATE;
00591 formatting = Formatting();
00592 }
00593 }
00594
00595 else if (c == '\\')
00596 {
00597 switch (pattern[i])
00598 {
00599 case 'n':
00600 literal += '\n';
00601 ++i;
00602 break;
00603
00604 case '\\':
00605 literal += '\\';
00606 ++i;
00607 break;
00608
00609 case 'r':
00610 literal += '\r';
00611 ++i;
00612 break;
00613
00614 case 't':
00615 literal += '\t';
00616 ++i;
00617 break;
00618
00619 case 'x':
00620 {
00621 if (i + 1 > patternLength)
00622 {
00623 literal += "\\x";
00624 ++i;
00625 break;
00626 }
00627
00628 char* begin = &pattern[i+1];
00629 char* end(0);
00630 errno = 0;
00631 int hexNumber = std::strtol(begin, &end, 16);
00632 if (end == begin || errno == ERANGE || hexNumber > CHAR_MAX)
00633 {
00634 literal += "\\x";
00635 ++i;
00636 break;
00637 }
00638 literal += static_cast<char>(hexNumber);
00639 i += (end - begin) + 1;
00640 }
00641 break;
00642
00643 default:
00644 literal += '\\';
00645 break;
00646 }
00647 }
00648 else
00649 {
00650 literal += c;
00651 }
00652 }
00653 break;
00654
00655 case E_CONVERTER_STATE:
00656 {
00657 literal += c;
00658 switch (c)
00659 {
00660 case '-':
00661 formatting.justification = E_LEFT_JUSTIFY;
00662 break;
00663 case '.':
00664 state = E_DOT_STATE;
00665 break;
00666 default:
00667 if (isdigit(c))
00668 {
00669 formatting.minWidth = c - '0';
00670 state = E_MIN_STATE;
00671 }
00672 else
00673 {
00674 converters.push_back(finalizeConverter(c));
00675 }
00676 }
00677 }
00678 break;
00679 case E_MIN_STATE:
00680 {
00681 literal += c;
00682 if (isdigit(c))
00683 {
00684 formatting.minWidth = formatting.minWidth * 10 + (c - '0');
00685 }
00686 else if (c == '.')
00687 {
00688 state = E_DOT_STATE;
00689 }
00690 else
00691 {
00692 converters.push_back(finalizeConverter(c));
00693 }
00694 }
00695 break;
00696 case E_DOT_STATE:
00697 {
00698 literal += c;
00699 if (isdigit(c))
00700 {
00701 formatting.maxWidth = c - '0';
00702 state = E_MAX_STATE;
00703 }
00704 else
00705 {
00706 BLOCXX_THROW_ERR(LogMessagePatternFormatterException,
00707 Format("Invalid pattern \"%1\" in position %2. Was expecting a digit, instead got char %3.",
00708 pattern, i, c).c_str(),
00709 LogMessagePatternFormatter::E_INVALID_PATTERN_NO_DIGIT_AFTER_DOT);
00710 }
00711 }
00712 break;
00713 case E_MAX_STATE:
00714 {
00715 literal += c;
00716 if (isdigit(c))
00717 {
00718 formatting.maxWidth = formatting.maxWidth * 10 + (c - '0');
00719 }
00720 else
00721 {
00722 converters.push_back(finalizeConverter(c));
00723 state = E_LITERAL_STATE;
00724 }
00725 }
00726 break;
00727 }
00728 }
00729
00730
00731 if (literal.length() > 0)
00732 {
00733 converters.push_back(ConverterRef(new LiteralConverter(literal.toString())));
00734 }
00735 }
00736
00738 String getOption()
00739 {
00740
00741 if ((i < pattern.length()) && (pattern[i] == '{'))
00742 {
00743 size_t end = pattern.indexOf('}', i);
00744 if (end > i)
00745 {
00746 String rv = pattern.substring(i + 1, end - (i + 1));
00747 i = end + 1;
00748 return rv;
00749 }
00750 }
00751
00752 return String();
00753 }
00754
00756 int getPrecision()
00757 {
00758
00759 String opt = getOption();
00760 int rv = 0;
00761 if (!opt.empty())
00762 {
00763 try
00764 {
00765 rv = opt.toUInt32();
00766 }
00767 catch (StringConversionException& e)
00768 {
00769 BLOCXX_THROW_ERR(LogMessagePatternFormatterException,
00770 Format("Invalid pattern \"%1\" in position %2. A positive integer is required for precision option (%3).",
00771 pattern, i, opt).c_str(),
00772 LogMessagePatternFormatter::E_INVALID_PATTERN_PRECISION_NOT_AN_INTEGER);
00773 }
00774 }
00775 return rv;
00776 }
00777
00779 ConverterRef finalizeConverter(char c)
00780 {
00781
00782 ConverterRef rv;
00783 switch (c)
00784 {
00785 case 'c':
00786 {
00787 rv = new ComponentConverter(formatting, getPrecision());
00788 }
00789 break;
00790
00791 case 'd':
00792 {
00793 String dateFormat;
00794 String dateOpt = getOption();
00795 if (dateOpt.empty())
00796 {
00797 dateFormat = DateConverter::ISO8601_DATE_FORMAT;
00798 }
00799 else
00800 {
00801 dateFormat = dateOpt;
00802 }
00803
00804
00805 if (dateFormat.equalsIgnoreCase(DateConverter::ISO8601_DATE_FORMAT))
00806 {
00807 dateFormat = DateConverter::ISO8601_PATTERN;
00808 }
00809 else if (dateFormat.equalsIgnoreCase(DateConverter::ABSOLUTE_DATE_FORMAT))
00810 {
00811 dateFormat = DateConverter::ABSOLUTE_PATTERN;
00812 }
00813 else if (dateFormat.equalsIgnoreCase(DateConverter::DATE_DATE_FORMAT))
00814 {
00815 dateFormat = DateConverter::DATE_PATTERN;
00816 }
00817
00818 rv = new DateConverter(formatting, dateFormat);
00819 }
00820 break;
00821
00822 case 'F':
00823 {
00824 rv = new FileLocationConverter(formatting);
00825 }
00826 break;
00827
00828 case 'l':
00829 {
00830 rv = new FullLocationConverter(formatting);
00831 }
00832 break;
00833
00834 case 'L':
00835 {
00836 rv = new LineLocationConverter(formatting);
00837 }
00838 break;
00839
00840 case 'M':
00841 {
00842 rv = new MethodLocationConverter(formatting);
00843 }
00844 break;
00845
00846 case 'm':
00847 {
00848 rv = new MessageConverter(formatting);
00849 }
00850 break;
00851
00852 case 'e':
00853 {
00854 rv = new XMLMessageConverter(formatting);
00855 }
00856 break;
00857
00858 case 'p':
00859 {
00860 rv = new CategoryConverter(formatting);
00861 }
00862 break;
00863
00864 case 'r':
00865 {
00866 rv = new RelativeTimeConverter(formatting);
00867 }
00868 break;
00869
00870 case 't':
00871 {
00872 rv = new ThreadConverter(formatting);
00873 }
00874 break;
00875
00876 case 'P':
00877 {
00878 rv = new PidConverter(formatting);
00879 }
00880 break;
00881 #if 0 // don't support these for now.
00882 case 'x':
00883 {
00884
00885 }
00886 break;
00887
00888 case 'X':
00889 {
00890
00891 }
00892 break;
00893 #endif
00894 default:
00895 {
00896 BLOCXX_THROW_ERR(LogMessagePatternFormatterException,
00897 Format("Invalid pattern \"%1\" in position %2. Unsupported conversion (%3).",
00898 pattern, i, c).c_str(),
00899 LogMessagePatternFormatter::E_INVALID_PATTERN_UNSUPPORTED_CONVERSION);
00900
00901 }
00902 break;
00903 }
00904
00905 literal.reset();
00906 state = E_LITERAL_STATE;
00907 formatting = Formatting();
00908 return rv;
00909 }
00910
00911 private:
00912 size_t i;
00913 EParserState state;
00914 StringBuffer literal;
00915 Formatting formatting;
00916 String pattern;
00917 };
00918
00919
00920 }
00921
00923 LogMessagePatternFormatter::LogMessagePatternFormatter(const String& pattern)
00924 {
00925 Parser parser(pattern);
00926 parser.parse(m_patternConverters);
00927 }
00928
00929 }
00930
00931
00932
00933
00934