# # log4j_parser snapon # # This snapon implements log4j parsing functionality, based on a specified PatternLayout directive. # log4j_parser = { version = "1.3.1" # 2012-02-08 - GMF - 1.1 - Fixed date format (%d) when not specified # 2012-02-10 - GMF - 1.2 - Added milliseconds_from_layout_construction (%r) # 2012-02-13 - GMF - 1.3 - Fixed support for right padded fields (e.g. %-5p) # 2012-02-16 - GMF - 1.3.1 - Fixed spaces in *_diagnostic_content fields. Allowed %x to be empty. # 2012-02-24 - GMF - 1.3.2 - Added line breaks with
to the message field, and turned on skip_escaping for that field so it will render them. label = "$lang_admin.snapons.log4j_parser.label" comment = "$lang_admin.snapons.log4j_parser.comment" config_snapon_category = "" parameters = { pattern_layout = { parameter_value = "" validation_type = "string" form_element_label = "$lang_admin.snapons.log4j_parser.parameters.pattern_layout.form_element_label" form_element_type = "text" form_element_width = "500" description = "" } # pattern_layout } # parameters parameters_form = { group_1 = { description = "$lang_admin.snapons.log4j_parser.parameters_form.group_1.description" parameters = { pattern_layout = true } # parameters } # group 1 } # parameters_form attach_operations = { configure_parsing = { type = "execute_expression" expression = ` #echo("profile: " . profile); #echo("profile: " . node_as_string(profile)); string pattern = @profile{"create_profile_wizard_info"}{"snapons"}{"parser"}{"parameters"}{"pattern_layout"}{"parameter_value"}; #echo("pattern: " . pattern); # This subroutine creates a database field subroutine(create_database_field(node profile, string fieldname), ( #echo("create_database_field: " . fieldname); node database_field = profile{"database"}{"fields"}{fieldname}; if (fieldname eq 'message') then ( @database_field{'sql_field_length'} = 10000; @database_field{'skip_escaping'} = true; ); )); # create_database_field() # This subroutine creates an aggregating database field subroutine(create_aggregating_database_field(node profile, string fieldname, string type, string display_format_type), ( #echo("create_aggregating_database_field: " . fieldname); node database_field = profile{"database"}{"fields"}{fieldname}; @database_field{"type"} = type; # @database_field{"integer_bits"} = 64; # if ('lang_stats.field_labels.tomcat_pattern'?{fieldname}) then # @database_field{"label"} = "{" . "=capitalize(expand(lang_stats.field_labels.tomcat_pattern." . fieldname . "))=" . "}"; # else @database_field{"label"} = "{" . "=capitalize(expand(lang_stats.field_labels." . fieldname . "))=" . "}"; @database_field{"display_format_type"} = display_format_type; @database_field{"aggregation_method"} = "sum"; )); # create_database_field() subroutine(create_log_field(node profile, string fieldname, string type), ( #echo("create_log_field: fieldname=" . fieldname); #echo("profile: " . profile); node logfield = profile{"log"}{"fields"}{fieldname}; #echo("logfield: " . logfield); @logfield{"type"} = type; )); ##=== create_log_field() ===## node log_fields_at_end = new_node(); subroutine(create_log_field_at_end(node log_fields_at_end, string fieldname, string type), ( log_fields_at_end{fieldname}{"type"} = type; )); # Delete any existing log fields delete_node(profile{"log"}{"fields"}); # Parse the string, building the regular expression from it, and the fields string regexp = "^"; string field_regexp; string timestamp_regexp; string field_name; string log_field_type; string c; string parsing_filter_set_collected_fields = "\n"; int fieldnum = 1; bool final_field_is_m = false; for (int i = 0; i < length(pattern); i++) ( # echo("i: " . i); c = substr(pattern, i, 1); # echo("c: " . c); # Handle % variables if (c eq '%') then ( i++; c = substr(pattern, i, 1); log_field_type = "flat"; # Handle leading integer string leading_integer = ""; bool right_padded = false; if (c eq '-') then right_padded = true; if (matches_regular_expression(c, '^[0-9-]$')) then ( while (matches_regular_expression(c, '^[0-9-]$')) ( leading_integer .= c; i++; c = substr(pattern, i, 1); ); # while integer ); # if integer #echo("leading integer: " . leading_integer); # Remember if the field field is %m, possibly followed by %n. if (c eq 'm') then final_field_is_m = true; else if (c eq 'n') then ( ); else final_field_is_m = false; if (false) then ( ) else if (c eq 'c') then ( field_regexp = '([^ ]+)'; field_name = "logging_category"; # Handle {} (by ignoring it) if (substr(pattern, i+1, 1) eq '{') then ( i += 2; c = substr(pattern, i, 1); string curly_section = ""; while ((c ne '}') and (i != length(pattern))) ( curly_section .= c; i++; c = substr(pattern, i, 1); ); # while not } ); # if {} ); # c else if (c eq 'C') then ( field_regexp = '([^ ]+)'; field_name = "caller_class"; ); # C # Need to support format variants, at least the common ones. else if (c eq 'd') then ( field_regexp = '([^ ]+)'; field_name = "time"; log_field_type = "time"; # Handle {} if (substr(pattern, i+1, 1) eq '{') then ( i += 2; c = substr(pattern, i, 1); string curly_section = ""; #echo("i: " . i); #echo("length(pattern): " . length(pattern)); while ((c ne '}') and (i != length(pattern))) ( #echo(" c=" . c); curly_section .= c; i++; c = substr(pattern, i, 1); ); # while not } #echo("Curly section: {" . curly_section . "}"); ); # if {} if ((curly_section eq "") or (curly_section eq "ISO8601") or (curly_section eq "yyyy-MM-dd HH:mm:ss,SSS")) then ( timestamp_regexp = '([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ([0-9][0-9]:[0-9][0-9]:[0-9][0-9]),[0-9][0-9][0-9]'; create_log_field(profile, 'date', 'date'); field_regexp = timestamp_regexp; ); # ISO8601 # Handle standard date format else if ((curly_section eq "DATE") or (curly_section eq "dd MMM yyyy HH:mm:ss,SSS")) then ( timestamp_regexp = '([0-9][0-9] [A-Z][a-z][a-z] [0-9][0-9][0-9][0-9]) ([0-9][0-9]:[0-9][0-9]:[0-9][0-9]),[0-9][0-9][0-9]'; create_log_field(profile, 'date', 'date'); field_regexp = timestamp_regexp; ); # DATE else if (curly_section eq "dd MMM yyyy HH:mm:ss:SSS") then ( timestamp_regexp = '([0-9][0-9] [A-Z][a-z][a-z] [0-9][0-9][0-9][0-9]) ([0-9][0-9]:[0-9][0-9]:[0-9][0-9]):[0-9][0-9][0-9]'; create_log_field(profile, 'date', 'date'); field_regexp = timestamp_regexp; ); # dd MMM yyyy HH:mm:ss:SSS # New date format from ThreadID:1285254 else if (curly_section eq "yyyy-MM-dd HH:mm:ss") then ( timestamp_regexp = '([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ([0-9][0-9]:[0-9][0-9]:[0-9][0-9])'; create_log_field(profile, 'date', 'date'); field_regexp = timestamp_regexp; ); # yyyy-MM-dd HH:mm:ss else error("Unsupported date format %d{" . curly_section . "}. Please contact Sawmill technical support if you need support for this format. Or use %d{DATTE}"); #LM ); # d else if (c eq 'F') then ( field_regexp = '([^ ]+)'; field_name = "request_file_name"; ); # F else if (c eq 'l') then ( field_regexp = '([^ ]+)'; field_name = "caller_location"; ); # l else if (c eq 'L') then ( field_regexp = '([^ ]+)'; field_name = "request_line_number"; ); # L else if (c eq 'm') then ( field_regexp = '(.*)'; field_name = "message"; ); # m else if (c eq 'n') then ( field_regexp = ''; field_name = "line_separator"; ); # n else if (c eq 'p') then ( field_regexp = '([^ ]+)'; field_name = "priority"; ); # p else if (c eq 'm') then ( field_regexp = '([^ ]+)'; field_name = "time_elapsed"; ); # m else if (c eq 'M') then ( field_regexp = '([^ ]+)'; field_name = "method_name"; ); # M else if (c eq 'r') then ( field_regexp = '([^ ]+)'; field_name = "milliseconds_from_layout_construction"; ); # r else if (c eq 't') then ( field_regexp = '([^ ]+)'; field_name = "thread_name"; ); # t else if (c eq 'x') then ( field_regexp = '([^ ]*)'; field_name = "nested_diagnostic_content"; ); # x else if (c eq 'X') then ( field_regexp = '([^ ]+)'; field_name = "mapped_diagnostic_content"; ); # X else error("Invalid pattern; unknown % directive %" . c); # LM if (right_padded) then field_regexp .= " *"; # echo("found field_name=" . field_name . "; field_regexp=" . field_regexp); # Don't create fields for line_separator if (field_name ne "line_separator") then ( # Add this field to the regular expression regexp .= field_regexp; create_log_field(profile, field_name, log_field_type); # echo("NEED TO ADD FIELDS HERE (" . field_name . ")"); if (field_name eq "time") then ( create_log_field_at_end(log_fields_at_end, 'date_time', 'date_time'); create_log_field_at_end(log_fields_at_end, 'day_of_week', 'flat'); create_log_field_at_end(log_fields_at_end, 'hour_of_day', 'flat'); #echo("SCHEDULING date_time log field"); create_database_field(profile, 'date_time'); create_database_field(profile, 'day_of_week'); create_database_field(profile, 'hour_of_day'); ); # Handle aggregating fields else if (field_name eq "time_elapsed") then create_aggregating_database_field(profile, field_name, "int", "duration_millis"); # Handle aggregating fields else if (field_name eq "milliseconds_from_layout_construction") then create_aggregating_database_field(profile, field_name, "int", "duration_millis"); # Create a normal database field else create_database_field(profile, field_name); if (field_name eq "time") then ( parsing_filter_set_collected_fields .= " set_collected_field('', 'date', $" . fieldnum . ");\n"; fieldnum++; ); parsing_filter_set_collected_fields .= " set_collected_field('', '" . field_name . "', $" . fieldnum . ");\n"; fieldnum++; ); # if not line_separator ); # if % # If it's not a variable, add it literally to the regular expression. else ( if ((c eq '[') or (c eq ']') or (c eq '.') or (c eq '|') or (c eq '(') or (c eq ')')) then ( regexp .= '\\\\'; ); regexp .= c; ); # if not % ); # for i regexp .= "$"; # If the final field is %m, there might be a stack trace on multiple lines after the first line. So, we need # to use collect/accept to collect those into the message, and finally accept on the next YYYY-MM-DD HH-MM-S,mmm line. #echo("Final regular expression: " . regexp); if (final_field_is_m) then ( string parsing_filter = " # Accept each line on the following line if (matches_regular_expression(current_log_line(), '" . timestamp_regexp . "')) then ( set_collected_field('', 'message', replace_all(get_collected_field('', 'message'), '$', '_')); accept_collected_entry('', false); ); # If this is a line with all the first fields, collect them if (matches_regular_expression(current_log_line(), '" . regexp . "')) then (" . parsing_filter_set_collected_fields . "); # Otherwise, it must be a continuation of the previous message line; add it to the message field else ( set_collected_field('', 'message', get_collected_field('', 'message') . '
' . current_log_line()); ); "; @profile{"log"}{"parsing_filters"}{"parse"} = parsing_filter; @profile{"log"}{"format"}{"parse_only_with_filters"} = true; ); # if final_field_is_m # If the final field isn't %m, just use a parsing regular expression else @profile{"log"}{"format"}{"parsing_regular_expression"} = regexp; #echo("numerical fields: " . node_as_string(profile{"database"}{"numerical_fields"})); # Create any final log fields node lfae; foreach lfae log_fields_at_end ( #echo("Final log field creation: " . node_name(lfae)); create_log_field(profile, node_name(lfae), @lfae{"type"}); ); create_log_field(profile, "events", "flat"); #echo("Final log fields: " . node_as_string(profile{"log"}{"fields"})); #echo("Final database fields: " . node_as_string(profile{"database"}{"fields"})); ` } # configure_parsing } # attach_operations } # double_hits