From 62b7a1b8a828446aa75ac138333a4fa019898fb1 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Fri, 16 Mar 2012 08:21:12 +0100 Subject: added "json" property replacer option --- ChangeLog | 4 +- doc/property_replacer.html | 6 +++ runtime/msg.c | 112 ++++++++++++++++++++++++++++++++++++++++++++- template.c | 17 ++++++- template.h | 3 +- 5 files changed, 137 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1c4b00f5..65040607 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,12 +1,14 @@ --------------------------------------------------------------------------- Version 6.3.8 [DEVEL] 2012-02-?? +- added "json" property replacer option to support JSON encoding on a + per-property basis - added mmjsonparse to support recognizing and parsing JSON enhanced syslog messages - upgraded more plugins to support the new v6 config format: - ommysql - omlibdbi - omsnmp -. added message property parsesuccess to indicate if the last run +- added message property parsesuccess to indicate if the last run higher-level parser could successfully parse the message or not (see property replacer html doc for details) - bugfix: abort during startup when rsyslog.conf v6+ format was used in diff --git a/doc/property_replacer.html b/doc/property_replacer.html index 394011a1..f0ac3c94 100644 --- a/doc/property_replacer.html +++ b/doc/property_replacer.html @@ -342,6 +342,12 @@ case-insensitive. Currently, the following options are defined: convert property text to uppercase only +json +encode the value so that it can be used inside a JSON field. This means +that several characters (according to the JSON spec) are being escaped, for +example US-ASCII LF is replaced by "\n". + + csv formats the resulting field (after all modifications) in CSV format as specified in RFC 4180. diff --git a/runtime/msg.c b/runtime/msg.c index 8f5fb080..548afb15 100644 --- a/runtime/msg.c +++ b/runtime/msg.c @@ -2334,6 +2334,112 @@ finalize_it: } } + +/* encode a property in JSON escaped format. This is a helper + * to MsgGetProp. It needs to update all provided parameters. + * Note: Code is borrowed from libee (my own code, so ASL 2.0 + * is fine with it); this function may later be replaced by + * some "better" and more complete implementation (maybe from + * libee or its helpers). + * For performance reasons, we begin to copy the string only + * when we recognice that we actually need to do some escaping. + * rgerhards, 2012-03-16 + */ +static rsRetVal +jsonEncode(uchar **ppRes, unsigned short *pbMustBeFreed, int *pBufLen) +{ + static char hexdigit[16] = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + unsigned char c; + es_size_t i; + char numbuf[4]; + int j; + unsigned buflen; + uchar *pSrc; + es_str_t *dst = NULL; + DEFiRet; + + pSrc = *ppRes; + buflen = (*pBufLen == -1) ? ustrlen(pSrc) : *pBufLen; + for(i = 0 ; i < buflen ; ++i) { + c = pSrc[i]; + if( (c >= 0x23 && c <= 0x5b) + || (c >= 0x5d /* && c <= 0x10FFFF*/) + || c == 0x20 || c == 0x21) { + /* no need to escape */ + if(dst != NULL) + es_addChar(&dst, c); + } else { + if(dst == NULL) { + if(i == 0) { + /* we hope we have only few escapes... */ + dst = es_newStr(buflen+10); + } else { + dst = es_newStrFromBuf((char*)pSrc, i-1); + } + if(dst == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + } + /* we must escape, try RFC4627-defined special sequences first */ + switch(c) { + case '\0': + es_addBuf(&dst, "\\u0000", 6); + break; + case '\"': + es_addBuf(&dst, "\\\"", 2); + break; + case '/': + es_addBuf(&dst, "\\/", 2); + break; + case '\\': + es_addBuf(&dst, "\\\\", 2); + break; + case '\010': + es_addBuf(&dst, "\\b", 2); + break; + case '\014': + es_addBuf(&dst, "\\f", 2); + break; + case '\n': + es_addBuf(&dst, "\\n", 2); + break; + case '\r': + es_addBuf(&dst, "\\r", 2); + break; + case '\t': + es_addBuf(&dst, "\\t", 2); + break; + default: + /* TODO : proper Unicode encoding (see header comment) */ + for(j = 0 ; j < 4 ; ++j) { + numbuf[3-j] = hexdigit[c % 16]; + c = c / 16; + } + es_addBuf(&dst, "\\u", 2); + es_addBuf(&dst, numbuf, 4); + break; + } + } + } + + if(dst != NULL) { + /* we updated the string and need to replace the + * previous data. + */ + if(*pbMustBeFreed) + free(*ppRes); + *ppRes = (uchar*)es_str2cstr(dst, NULL); + *pbMustBeFreed = 1; + es_deleteStr(dst); + } + +finalize_it: + RETiRet; +} + + /* This function returns a string-representation of the * requested message property. This is a generic function used * to abstract properties so that these can be easier @@ -3105,8 +3211,8 @@ dbgprintf("prop repl 4, pRes='%s', len %d\n", pRes, bufLen); } } - /* finally, we need to check if the property should be formatted in CSV - * format (we use RFC 4180, and always use double quotes). As of this writing, + /* finally, we need to check if the property should be formatted in CSV or JSON. + * For CSV we use RFC 4180, and always use double quotes. As of this writing, * this should be the last action carried out on the property, but in the * future there may be reasons to change that. -- rgerhards, 2009-04-02 */ @@ -3140,6 +3246,8 @@ dbgprintf("prop repl 4, pRes='%s', len %d\n", pRes, bufLen); pRes = pBStart; bufLen = -1; *pbMustBeFreed = 1; + } else if(pTpe->data.field.options.bJSON) { + jsonEncode(&pRes, pbMustBeFreed, &bufLen); } if(bufLen == -1) diff --git a/template.c b/template.c index ca1688f7..bcdcc367 100644 --- a/template.c +++ b/template.c @@ -542,7 +542,19 @@ static void doOptions(unsigned char **pp, struct templateEntry *pTpe) } else if(!strcmp((char*)Buf, "secpath-replace")) { pTpe->data.field.options.bSecPathReplace = 1; } else if(!strcmp((char*)Buf, "csv")) { - pTpe->data.field.options.bCSV = 1; + if(pTpe->data.field.options.bJSON) { + errmsg.LogError(0, NO_ERRCODE, "error: can not specify " + "both csv and json options - csv ignored"); + } else { + pTpe->data.field.options.bCSV = 1; + } + } else if(!strcmp((char*)Buf, "json")) { + if(pTpe->data.field.options.bCSV) { + errmsg.LogError(0, NO_ERRCODE, "error: can not specify " + "both csv and json options - json ignored"); + } else { + pTpe->data.field.options.bJSON = 1; + } } else { dbgprintf("Invalid field option '%s' specified - ignored.\n", Buf); } @@ -1280,6 +1292,9 @@ void tplPrintList(rsconf_t *conf) if(pTpe->data.field.options.bCSV) { dbgprintf("[format as CSV (RFC4180)]"); } + if(pTpe->data.field.options.bJSON) { + dbgprintf("[format as JSON"); + } if(pTpe->data.field.options.bDropLastLF) { dbgprintf("[drop last LF in msg] "); } diff --git a/template.h b/template.h index d394809b..b2cef271 100644 --- a/template.h +++ b/template.h @@ -2,7 +2,7 @@ * Please see syslogd.c for license information. * begun 2004-11-17 rgerhards * - * Copyright (C) 2004 by Rainer Gerhards and Adiscon GmbH + * Copyright (C) 2004-2012 by Rainer Gerhards and Adiscon GmbH * * This file is part of rsyslog. * @@ -103,6 +103,7 @@ struct templateEntry { unsigned bSecPathReplace: 1; /* replace slashes, replace dots, empty string */ unsigned bSPIffNo1stSP: 1; /* replace slashes, replace dots, empty string */ unsigned bCSV: 1; /* format field in CSV (RFC 4180) format */ + unsigned bJSON: 1; /* format field JSON escaped */ } options; /* options as bit fields */ } field; } data; -- cgit