From a78d04984240a3d04045402c964d9d8c5be463ef Mon Sep 17 00:00:00 2001 From: Dmitri Pal Date: Sun, 27 Sep 2009 22:16:17 -0400 Subject: ELAPI Resolving message attribute This patch continues work started with the previous patch. It resolves message attribute. Message attribute is a special attribute in the event that may contain references to other attributes in the event. When message is resolved the references are replaced with actual values of the referenced attributes. --- common/elapi/Makefile.am | 1 + common/elapi/elapi_event.h | 18 +- common/elapi/elapi_priv.h | 11 +- common/elapi/elapi_resolve.c | 131 ++++++---- common/elapi/elapi_subst.c | 395 +++++++++++++++++++++++++++++ common/elapi/elapi_test/Makefile.am | 1 + common/elapi/elapi_test/elapi_ut.c | 30 ++- common/elapi/providers/file/file_fmt_csv.c | 6 +- 8 files changed, 536 insertions(+), 57 deletions(-) create mode 100644 common/elapi/elapi_subst.c (limited to 'common') diff --git a/common/elapi/Makefile.am b/common/elapi/Makefile.am index 08fd2d773..53f2c3eff 100644 --- a/common/elapi/Makefile.am +++ b/common/elapi/Makefile.am @@ -54,6 +54,7 @@ libelapi_la_SOURCES = \ elapi_sink.c \ elapi_resolve.c \ elapi_async.c \ + elapi_subst.c \ elapi_event.h \ elapi_priv.h \ elapi_sink.h \ diff --git a/common/elapi/elapi_event.h b/common/elapi/elapi_event.h index 6a5fe044e..3e52dfdb6 100644 --- a/common/elapi/elapi_event.h +++ b/common/elapi/elapi_event.h @@ -22,11 +22,15 @@ #include "collection.h" -/* Possible predefined elements of the event */ -#define E_TIMESTAMP "__stamp__" /* string - the value is the format for strftime() +/* Possible predefined elements of the event. + * First letter R means that it is a property + * resolvable at the logging time. + */ + +#define E_TIMESTAMP "R_stamp__" /* string - the value is the format for strftime() * default is standard format for current locale. */ -#define E_UTCTIME "__time__" /* int - UTC time as unix time in seconds since 1970 */ -#define E_OFFSET "__loco__" /* int - local time displacement */ +#define E_UTCTIME "R_time__" /* int - UTC time as unix time in seconds since 1970 */ +#define E_OFFSET "R_loco__" /* int - local time displacement */ #define E_PID "__pid__" /* int - Process ID of the current process */ #define E_APPNAME "__appnm__" /* string - Name of the current application */ #define E_HOSTNAME "__host__" /* string - Name of the current host */ @@ -44,11 +48,11 @@ * The token %(server) will be replaced by value * in the attribute "server" in the event. */ -#define E_MESSAGE "__message__" +#define E_MESSAGE "R_message__" -/* Standard prefix for internal attributes */ -#define E_PREFIX "__" +/* Standard prefix for internal resolvable attributes */ +#define E_PREFIX "R_" #define E_PREFIX_LEN 2 /* Base argument in the template creation function is a bit mask. diff --git a/common/elapi/elapi_priv.h b/common/elapi/elapi_priv.h index 27b0079ff..e7480f90b 100644 --- a/common/elapi/elapi_priv.h +++ b/common/elapi/elapi_priv.h @@ -24,6 +24,7 @@ #include #include "collection.h" +#include "elapi_basic.h" #include "elapi_async.h" #include "elapi_sink.h" @@ -178,8 +179,8 @@ struct elapi_sink_ctx { * needed to resolve the event. */ struct elapi_resolve_data { - /* Reference to the event */ - struct collection_item *event; + /* Reference to the message item inside event */ + struct collection_item *message; /* Reference back to dispatcher */ struct elapi_dispatcher *handle; /* Time related data */ @@ -321,6 +322,12 @@ int elapi_resolve_event(struct collection_item **final_event, struct collection_item *event, struct elapi_dispatcher *handle); +/* Function to place the event items into a formatted string */ +int elapi_sprintf(struct elapi_data_out *out_data, + const char *format_str, + struct collection_item *event); + + /* Send ELAPI config errors into a file */ void elapi_dump_ini_err(struct collection_item *error_list); diff --git a/common/elapi/elapi_resolve.c b/common/elapi/elapi_resolve.c index 5570eee0c..ca2601b70 100644 --- a/common/elapi/elapi_resolve.c +++ b/common/elapi/elapi_resolve.c @@ -23,17 +23,17 @@ #include "elapi_priv.h" #include "elapi_event.h" -/* #include "elapi_subst.h" */ +#include "elapi_basic.h" #include "trace.h" #include "config.h" /*****************************************/ /* Individual callbacks are defined here */ /*****************************************/ -/* Timestamp resoltion callback */ -int elapi_timestamp_cb(struct elapi_resolve_data *resolver, - struct collection_item *item, - int *skip) +/* Timestamp resolution callback */ +static int elapi_timestamp_cb(struct elapi_resolve_data *resolver, + struct collection_item *item, + int *skip) { int error = EOK; char timestamp[TIME_ARRAY_SIZE + 1]; @@ -58,9 +58,9 @@ int elapi_timestamp_cb(struct elapi_resolve_data *resolver, } /* UTC time resolution callback */ -int elapi_utctime_cb(struct elapi_resolve_data *resolver, - struct collection_item *item, - int *skip) +static int elapi_utctime_cb(struct elapi_resolve_data *resolver, + struct collection_item *item, + int *skip) { int error = EOK; @@ -76,9 +76,9 @@ int elapi_utctime_cb(struct elapi_resolve_data *resolver, } /* Offset resolution callback */ -int elapi_offset_cb(struct elapi_resolve_data *resolver, - struct collection_item *item, - int *skip) +static int elapi_offset_cb(struct elapi_resolve_data *resolver, + struct collection_item *item, + int *skip) { int error = EOK; @@ -93,45 +93,23 @@ int elapi_offset_cb(struct elapi_resolve_data *resolver, return error; } - /* Message resolution callback */ -int elapi_message_cb(struct elapi_resolve_data *resolver, - struct collection_item *item, - int *skip) +static int elapi_message_cb(struct elapi_resolve_data *resolver, + struct collection_item *item, + int *skip) { - int error = EOK; - /* int length; */ - /* char *result; */ TRACE_FLOW_STRING("elapi_message_cb", "Entry"); - /* FIXME: Resolve message here */ - /* Function is not yet implemented ... - error = elapi_sprintf(&result, - &length, - (const char *)col_get_item_data(item), - resolver->event); - if (error) { - TRACE_ERROR_NUMBER("Failed to build message", error); - return error; - } + /* Save pointer to message item */ + resolver->message = item; - error = col_modify_str_item(item, - NULL, - result; - length + 1); - free(result); - if (error) { - TRACE_ERROR_NUMBER("Failed to modify message item", error); - return error; - } - */ - - TRACE_FLOW_NUMBER("elapi_message_cb. Exit. Returning", error); - return error; + TRACE_FLOW_NUMBER("elapi_message_cb.", "Exit"); + return EOK; } + /*****************************************/ /* Array of structures for resolution of * the different event properties. @@ -172,6 +150,8 @@ static int elapi_resolve_item(struct collection_item *item, return EOK; } + TRACE_FLOW_STRING("Item to resolve: ", col_get_item_property(item, NULL)); + /* This is an internal field that might need resolution */ resolver = (struct elapi_resolve_data *)ext_data; @@ -224,6 +204,53 @@ static int elapi_resolve_item(struct collection_item *item, return error; } +/* Message resolution function */ +int elapi_resolve_message(struct collection_item *item, + struct collection_item *event) +{ + int error = EOK; + struct elapi_data_out *serialized; + + TRACE_FLOW_STRING("elapi_resolve_message", "Entry"); + + /* I prefer to allocate it rather than do it on stack. + * The main reason is that I would have to memset + * the struct to init it. + * So why not just use the interface we already have. + */ + error = elapi_alloc_serialized_data(&serialized); + if (error) { + TRACE_ERROR_NUMBER("Failed to allocate serialized data", error); + return error; + } + + /* Call string substitution */ + error = elapi_sprintf(serialized, + (const char *)col_get_item_data(item), + event); + if (error) { + TRACE_ERROR_NUMBER("Failed to build message", error); + elapi_free_serialized_data(serialized); + return error; + } + + /* Put the resolved value into the item */ + error = col_modify_str_item(item, + NULL, + (char *)serialized->buffer, + serialized->length + 1); + + /* Clean data regardless of the result */ + elapi_free_serialized_data(serialized); + + if (error) { + TRACE_ERROR_NUMBER("Failed to modify message item", error); + return error; + } + + TRACE_FLOW_NUMBER("elapi_resolve_message. Exit. Returning", error); + return error; +} /* Resolve event */ int elapi_resolve_event(struct collection_item **final_event, @@ -238,8 +265,8 @@ int elapi_resolve_event(struct collection_item **final_event, TRACE_FLOW_STRING("elapi_create_event_ctx", "Entry"); - /* Prepeare the resolver */ - resolver.event = event; + /* Prepare the resolver */ + resolver.message = NULL; resolver.handle = handle; /* Get seconds */ resolver.tm = time(NULL); @@ -267,6 +294,22 @@ int elapi_resolve_event(struct collection_item **final_event, return error; } + if (resolver.message) { + /* Now resolve message. We need to do it last since + * we do not know the order of the properties + * and message can be referencing properties + * that are later than message in the list + * and have not been resolved yet. + */ + error = elapi_resolve_message(resolver.message, + new_event); + if (error) { + TRACE_ERROR_NUMBER("Failed to resolve the event", error); + col_destroy_collection(new_event); + return error; + } + } + *final_event = new_event; TRACE_FLOW_STRING("elapi_create_event_ctx", "Exit"); @@ -279,7 +322,7 @@ int elapi_init_resolve_list(struct collection_iterator **list) int error = EOK; struct elapi_resolve_list *current; struct collection_item *col = NULL; - struct collection_iterator *iterator; + struct collection_iterator *iterator = NULL; struct elapi_rslv_item_data *bin_data; TRACE_FLOW_STRING("elapi_init_resolve_list", "Entry"); diff --git a/common/elapi/elapi_subst.c b/common/elapi/elapi_subst.c new file mode 100644 index 000000000..da507aa5b --- /dev/null +++ b/common/elapi/elapi_subst.c @@ -0,0 +1,395 @@ +/* + ELAPI + + Module contains functions related to format substitution + + Copyright (C) Dmitri Pal 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#define _GNU_SOURCE +#include +#include +#include "elapi_priv.h" +#include "trace.h" +#include "config.h" + +/* Reasonable size for one event */ +/* FIXME: may be it would make sense to make it configurable ? */ +#define ELAPI_SUBST_BLOCK 256 + +/* Calculate the potential size of the item */ +static unsigned elapi_get_item_len(int type, int raw_len) +{ + int serialized_len = 0; + + TRACE_FLOW_STRING("elapi_get_item_len", "Entry point"); + + switch (type) { + case COL_TYPE_INTEGER: + case COL_TYPE_UNSIGNED: + case COL_TYPE_LONG: + case COL_TYPE_ULONG: + serialized_len = MAX_LONG_STRING_LEN; + break; + + case COL_TYPE_STRING: + serialized_len = raw_len; + break; + + case COL_TYPE_BINARY: + serialized_len = raw_len * 2; + break; + + case COL_TYPE_DOUBLE: + serialized_len = MAX_DOUBLE_STRING_LEN; + break; + + case COL_TYPE_BOOL: + serialized_len = MAX_BOOL_STRING_LEN; + break; + + default: + serialized_len = 0; + break; + } + + TRACE_FLOW_STRING("elapi_get_item_len", "Exit point"); + return (uint32_t)serialized_len; +} + + +/* Function to serialize one item */ +static int elapi_sprintf_item(struct elapi_data_out *out_data, + struct collection_item *item) +{ + int error = EOK; + uint32_t projected_len; + uint32_t used_len; + uint32_t item_len; + void *data; + int type; + int i; + + TRACE_FLOW_STRING("elapi_sprintf_item", "Entry"); + + /* Get projected length of the item */ + item_len = col_get_item_length(item); + type = col_get_item_type(item); + projected_len = elapi_get_item_len(type, item_len); + + TRACE_INFO_NUMBER("Expected data length: ", projected_len); + + /* Make sure we have enough space */ + if (out_data->buffer == NULL) { + TRACE_INFO_STRING("First time use.", ""); + /* Add null terminating zero */ + projected_len++; + } + + /* Grow buffer if needed */ + error = elapi_grow_data(out_data, + projected_len, + ELAPI_SUBST_BLOCK); + if (error) { + TRACE_ERROR_NUMBER("Error. Failed to allocate memory.", error); + return error; + } + + data = col_get_item_data(item); + + /* Add the value */ + switch (type) { + case COL_TYPE_STRING: + + /* Item's length includes trailing 0 for data items */ + used_len = item_len - 1; + memcpy(&out_data->buffer[out_data->length], + (const char *)(data), + used_len); + out_data->buffer[out_data->length + used_len] = '\0'; + break; + + case COL_TYPE_BINARY: + + for (i = 0; i < item_len; i++) { + sprintf((char *)&out_data->buffer[out_data->length + i * 2], + "%02X", (unsigned int)(((const unsigned char *)(data))[i])); + } + used_len = item_len * 2; + /* We need it here for the case item_len = 0 */ + out_data->buffer[out_data->length + used_len] = '\0'; + break; + + case COL_TYPE_INTEGER: + used_len = sprintf((char *)&out_data->buffer[out_data->length], + "%d", *((const int *)(data))); + break; + + case COL_TYPE_UNSIGNED: + used_len = sprintf((char *)&out_data->buffer[out_data->length], + "%u", *((const unsigned int *)(data))); + break; + + case COL_TYPE_LONG: + used_len = sprintf((char *)&out_data->buffer[out_data->length], + "%ld", *((const long *)(data))); + break; + + case COL_TYPE_ULONG: + used_len = sprintf((char *)&out_data->buffer[out_data->length], + "%lu", *((const unsigned long *)(data))); + break; + + case COL_TYPE_DOUBLE: + used_len = sprintf((char *)&out_data->buffer[out_data->length], + "%.4f", *((const double *)(data))); + break; + + case COL_TYPE_BOOL: + used_len = sprintf((char *)&out_data->buffer[out_data->length], + "%s", + (*((const unsigned char *)(data))) ? "true" : "false"); + break; + + default: + out_data->buffer[out_data->length] = '\0'; + used_len = 0; + break; + } + + /* Adjust length */ + out_data->length += used_len; + + TRACE_INFO_STRING("Data: ", (char *)out_data->buffer); + + TRACE_FLOW_STRING("elapi_sprintf_item", "Exit"); + return error; + +} + +/* Lookup item hoping that items in message are somewhat ordered. + * If there is some ordering we will save on performance. + * If not we will not loos against a standard lookup. + */ +static struct collection_item *elapi_lookup_item(const char *start, + int length, + struct collection_iterator *iterator) +{ + int error = EOK; + struct collection_item *found = NULL; + const char *property; + int property_len; + uint64_t hash; + + TRACE_FLOW_STRING("elapi_lookup_item", "Entry"); + + /* Prepare hash */ + hash = col_make_hash(start, length, NULL); + + /* NOTE: This iteration loop uses advanced iterator + * capabilities. Read more about it before you decide + * to use this code as an example. + */ + while (1) { + + /* Advance to next item in the list */ + error = col_iterate_collection(iterator, + &found); + if (error) { + TRACE_ERROR_NUMBER("Failed to iterate collection", error); + return NULL; + } + + /* Are we done ? This means we looped and did not find + * the item. */ + if (found == NULL) break; + + property = col_get_item_property(found, &property_len); + + /* Compare item and the property */ + if ((hash == col_get_item_hash(found)) && + (length == property_len) && + (strncasecmp(start, property, length) == 0)) { + /* This is our item !!! */ + /* Pin down the iterator here */ + col_pin_iterator(iterator); + + /* Break out of loop */ + break; + } + } + + TRACE_FLOW_STRING("elapi_lookup_item", "Exit"); + return found; +} + + +/* Function to parse format string */ +static const char *elapi_parse_format(const char *start, + int *length, + struct collection_item **item, + struct collection_iterator *iterator) +{ + const char *runner; + const char *bracket; + const char *name_start; + + TRACE_FLOW_STRING("elapi_parse_format", "Entry"); + if ((start == NULL) || (*start == '\0')) { + TRACE_FLOW_STRING("String is empty", "Return"); + return NULL; + } + + runner = start; + + while (1) { + /* First check for end of the string */ + if (*runner == '\0') { + TRACE_FLOW_STRING("Found last token", start); + *length = runner - start; + return runner; + } + + /* Is it the beginning of the field substitution? */ + if (*runner == '%') { + /* Check for bracket */ + if (*(runner + 1) == '(') { + /* Search for closing one */ + name_start = runner + 2; + bracket = name_start; + while (1) { + /* Check for the end */ + if (*bracket == '\0') { + TRACE_FLOW_STRING("No closing bracket", start); + *length = bracket - start; + return bracket; + } + /* Did we find closing backet? */ + if (*bracket == ')') { + TRACE_FLOW_STRING("Bracket is found: ", name_start); + /* There might be specific format specifiers */ + if (*name_start == '!') { + /* Force rewind of the + * iterator */ + col_rewind_iterator(iterator); + name_start++; + } + + /* FIXME: Add other specifiers here... + * Specifier that can be supported in future + * might expand multi value property + * to a list of values separated by + * provided symbol. + */ + + *item = elapi_lookup_item(name_start, + bracket - name_start, + iterator); + bracket++; + if (*item == NULL) { + /* The item is not known (or error) */ + TRACE_FLOW_STRING("No item in event", name_start); + *length = bracket - start; + return bracket; + } + + /* Item is found */ + TRACE_FLOW_STRING("Item found: ", name_start); + *length = runner - start; + return bracket; + } + bracket++; + } + } + } + runner++; + } + /* This point is unreachable */ +} + +/* Function to place the event items into the formatted string */ +int elapi_sprintf(struct elapi_data_out *out_data, + const char *format_str, + struct collection_item *event) +{ + const char *start; + int length; + struct collection_item *item; + struct collection_iterator *iterator = NULL; + const char *result; + int error; + + TRACE_FLOW_STRING("elapi_sprintf", "Entry"); + + /* Create iterator - by thus time cevent is resolved and should + * be a flattened collection. At least this is the assumption. + */ + error = col_bind_iterator(&iterator, event, COL_TRAVERSE_IGNORE); + if (error) { + TRACE_ERROR_NUMBER("Failed to bind iterator", error); + return error; + } + + start = format_str; + + while(1) { + + item = NULL; + length = 0; + + /* Parse format definition */ + result = elapi_parse_format(start, &length, &item, iterator); + if (result == NULL) { + TRACE_INFO_STRING("Done parsing string", ""); + break; + } + + /* Apply parsed data */ + if (length > 0) { + error = elapi_grow_data(out_data, + length + 1, + ELAPI_SUBST_BLOCK); + if (error) { + TRACE_ERROR_NUMBER("Error. Failed to allocate memory.", error); + col_unbind_iterator(iterator); + return error; + } + + memcpy(&out_data->buffer[out_data->length], + (const char *)(start), + length); + + out_data->length += length; + /* We asked for this one extra byte above */ + out_data->buffer[out_data->length] = '\0'; + } + + if (item != NULL) { + TRACE_INFO_NUMBER("Need to output item", error); + error = elapi_sprintf_item(out_data, item); + if (error) { + TRACE_ERROR_NUMBER("Error. Failed to allocate memory.", error); + col_unbind_iterator(iterator); + return error; + } + } + + start = result; + } + + col_unbind_iterator(iterator); + + TRACE_FLOW_STRING("elapi_sprintf", "Exit"); + return error; +} diff --git a/common/elapi/elapi_test/Makefile.am b/common/elapi/elapi_test/Makefile.am index dcf1707cc..c0eac8a5d 100644 --- a/common/elapi/elapi_test/Makefile.am +++ b/common/elapi/elapi_test/Makefile.am @@ -32,6 +32,7 @@ libelapi_test_la_SOURCES = \ ../elapi_basic.h \ ../elapi_resolve.c \ ../elapi_async.c \ + ../elapi_subst.c \ ../elapi_event.h \ ../elapi_priv.h \ ../elapi_sink.h \ diff --git a/common/elapi/elapi_test/elapi_ut.c b/common/elapi/elapi_test/elapi_ut.c index 1046ed0e6..ab0f88d14 100644 --- a/common/elapi/elapi_test/elapi_ut.c +++ b/common/elapi/elapi_test/elapi_ut.c @@ -76,7 +76,7 @@ int simple_event_test(void) printf("Simple test START:\n"); error = elapi_set_default_template( - E_BASE_DEFV1 | E_BASE_HOSTEXT, + E_BASE_DEFV1 | E_BASE_HOSTEXT /* FIXME Ticket #207 */, "%n( bin )", bin, 8, " %sb( logical1 )", "false", "%sb( logical2 )", "YES", @@ -391,6 +391,34 @@ int complex_event_test(void) return error; } + error = elapi_dsp_msg(E_TARGET_DEBUG, + dispatcher, + template, + E_MESSAGE, + "date = %(R_stamp__), pid = %(__pid__), " + "hostname = %(__host__), %(__halias__), " + "ip = %(__ip__), [%(__iplist__);%(!__iplist__);%(__iplist__)]" , + E_EOARG); + if (error) { + elapi_destroy_event_template(template); + printf("Failed to log event! %d\n", error); + return error; + } + + error = elapi_dsp_msg(E_TARGET_DEBUG, + dispatcher, + template, + E_MESSAGE, + "date = %(R_stamp__), pid = %(__pid__), " + "hostname = %(__host__), %(__halias__), " + "ip = %(__ip__), [%(__iplist__);%(__iplist__);%(__iplist__)]" , + E_EOARG); + if (error) { + elapi_destroy_event_template(template); + printf("Failed to log event! %d\n", error); + return error; + } + elapi_destroy_event_template(template); elapi_print_dispatcher(dispatcher); diff --git a/common/elapi/providers/file/file_fmt_csv.c b/common/elapi/providers/file/file_fmt_csv.c index e55d0b1c1..1a198711e 100644 --- a/common/elapi/providers/file/file_fmt_csv.c +++ b/common/elapi/providers/file/file_fmt_csv.c @@ -40,7 +40,7 @@ static unsigned file_csv_data_len(struct file_csv_cfg *cfg, { int serialized_len = 0; - TRACE_FLOW_STRING("col_get_data_len", "Entry point"); + TRACE_FLOW_STRING("file_csv_data_len", "Entry point"); switch (type) { case COL_TYPE_INTEGER: @@ -75,7 +75,7 @@ static unsigned file_csv_data_len(struct file_csv_cfg *cfg, if (cfg->csvqualifier) serialized_len += 2; - TRACE_FLOW_STRING("col_get_data_len","Exit point"); + TRACE_FLOW_STRING("file_csv_data_len", "Exit point"); return (uint32_t)serialized_len; } @@ -249,7 +249,7 @@ int file_serialize_csv(struct elapi_data_out *out_data, */ out_data->buffer[out_data->length] = '\0'; - TRACE_INFO_STRING("Data: ", out_data->buffer); + TRACE_INFO_STRING("Data: ", (char *)out_data->buffer); TRACE_FLOW_STRING("file_serialize_csv.", "Exit"); return error; -- cgit