/* A wrapper function to ouput an array of user agents for each host. */ static void process_host_agents (GJSON * json, GHolderItem * item, int iisp) { GAgents *agents = new_gagents (); int i, n = 0, iiisp = 0; /* use tabs to prettify output */ if (conf.json_pretty_print) iiisp = iisp + 1; if (set_host_agents (item->metrics->data, load_host_agents, agents) == 1) return; pjson (json, ",%.*s%.*s\"items\": [%.*s", nlines, NL, iisp, TAB, nlines, NL); n = agents->size > 10 ? 10 : agents->size; for (i = 0; i < n; ++i) { pjson (json, "%.*s\"", iiisp, TAB); escape_json_output (json, agents->items[i].agent); if (i == n - 1) pjson (json, "\""); else pjson (json, "\",%.*s", nlines, NL); } pclose_arr (json, iisp, 1); /* clean stuff up */ free_agents_array (agents); }
/* Entry point to generate a a json report writing it to the fp */ void output_json (GLog * logger, GHolder * holder) { FILE *fp = stdout; GModule module; GPercTotals totals = { .hits = logger->valid, .visitors = ht_get_size_uniqmap (VISITORS), .bw = logger->resp_size, }; /* use new lines to prettify output */ if (conf.json_pretty_print) nlines = 1; pjson (fp, "{%.*s", nlines, NL); print_json_summary (fp, logger); for (module = 0; module < TOTAL_MODULES; module++) { const GPanel *panel = panel_lookup (module); if (!panel) continue; if (ignore_panel (module)) continue; panel->render (fp, holder + module, totals, panel); pjson (fp, (module != TOTAL_MODULES - 1) ? ",%.*s" : "%.*s", nlines, NL); } pjson (fp, "}"); fclose (fp); }
/* A wrapper function to ouput children nodes. */ static void print_json_sub_items (FILE * fp, GSubList * sl, GPercTotals totals, int iisp) { GMetrics *nmetrics; GSubItem *iter; char comma = ' '; int i = 0, iiisp = 0, iiiisp = 0; /* use tabs to prettify output */ if (conf.json_pretty_print) iiisp = iisp + 1, iiiisp = iiisp + 1; if (sl == NULL) return; pjson (fp, ",%.*s%.*s\"items\": [%.*s", nlines, NL, iisp, TAB, nlines, NL); for (iter = sl->head; iter; iter = iter->next, i++) { set_data_metrics (iter->metrics, &nmetrics, totals); pjson (fp, "%.*s{%.*s", iiisp, TAB, nlines, NL); print_json_block (fp, nmetrics, iiiisp); comma = (i != sl->size - 1) ? ',' : ' '; pjson (fp, "%.*s%.*s}%c%.*s", nlines, NL, iiisp, TAB, comma, nlines, NL); free (nmetrics); } pjson (fp, "%.*s]", iisp, TAB); }
/* Close the data array. */ static void pclose_arr (GJSON * json, int sp, int last) { if (!last) pjson (json, "%.*s%.*s],%.*s", nlines, NL, sp, TAB, nlines, NL); else pjson (json, "%.*s%.*s]", nlines, NL, sp, TAB); }
/* Write to a buffer a JSON string key, uint64_t value pair. */ static void pskeyu64val (GJSON * json, const char *key, uint64_t val, int sp, int last) { if (!last) pjson (json, "%.*s\"%s\": %" PRIu64 ",%.*s", sp, TAB, key, val, nlines, NL); else pjson (json, "%.*s\"%s\": %" PRIu64 "", sp, TAB, key, val); }
/* Close JSON object. */ static void pclose_obj (GJSON * json, int iisp, int last) { if (!last) pjson (json, "%.*s%.*s},%.*s", nlines, NL, iisp, TAB, nlines, NL); else pjson (json, "%.*s%.*s}", nlines, NL, iisp, TAB); }
/* Write to a buffer a JSON string key, int value pair. */ static void pskeyfval (GJSON * json, const char *key, float val, int sp, int last) { if (!last) pjson (json, "%.*s\"%s\": \"%4.2f\",%.*s", sp, TAB, key, val, nlines, NL); else pjson (json, "%.*s\"%s\": \"%4.2f\"", sp, TAB, key, val); }
/* Write to a buffer a JSON string key, int value pair. */ static void pskeyival (GJSON * json, const char *key, int val, int sp, int last) { if (!last) pjson (json, "%.*s\"%s\": %d,%.*s", sp, TAB, key, val, nlines, NL); else pjson (json, "%.*s\"%s\": %d", sp, TAB, key, val); }
/* Output the path of the log being parsed under the overall object. * * On success, data is outputted. */ static void poverall_log (FILE * fp, int isp) { if (conf.ifile == NULL) conf.ifile = (char *) "STDIN"; pjson (fp, "%.*s\"%s\": \"", isp, TAB, OVERALL_LOG); escape_json_output (fp, conf.ifile); pjson (fp, "\"%.*s", nlines, NL); }
static void poverall_log_path (GJSON * json, int idx, int isp) { pjson (json, "%.*s\"", isp, TAB); if (conf.filenames[idx][0] == '-' && conf.filenames[idx][1] == '\0') pjson (json, "STDIN"); else escape_json_output (json, (char *) conf.filenames[idx]); pjson (json, conf.filenames_idx - 1 != idx ? "\",\n" : "\""); }
/* Output the visitors meta data object. * * If no metadata found, it simply returns. * On success, meta data is outputted. */ static void pmeta_data_visitors (FILE * fp, GModule module, int sp) { int isp = 0; uint64_t count = ht_get_meta_data (module, "visitors"); /* use tabs to prettify output */ if (conf.json_pretty_print) isp = sp + 1; pjson (fp, "%.*s\"visitors\": {%.*s", sp, TAB, nlines, NL); pjson (fp, "%.*s\"count\": %lld%.*s", isp, TAB, (long long) count, nlines, NL); pjson (fp, "%.*s},%.*s", sp, TAB, nlines, NL); }
/* Output the number of invalid requests under the overall object. * * On success, data is outputted. */ static void poverall_invalid_reqs (FILE * fp, GLog * logger, int isp) { int total = logger->invalid; pjson (fp, "%.*s\"%s\": %d,%.*s", isp, TAB, OVERALL_FAILED, total, nlines, NL); }
/* Output the number of static files (jpg, pdf, etc) under the overall * object. * * On success, data is outputted. */ static void poverall_static_files (FILE * fp, int isp) { int total = ht_get_size_datamap (REQUESTS_STATIC); pjson (fp, "%.*s\"%s\": %d,%.*s", isp, TAB, OVERALL_STATIC, total, nlines, NL); }
/* Output the number of not found (404s) under the overall object. * * On success, data is outputted. */ static void poverall_notfound (FILE * fp, int isp) { int total = ht_get_size_datamap (NOT_FOUND); pjson (fp, "%.*s\"%s\": %d,%.*s", isp, TAB, OVERALL_NOTFOUND, total, nlines, NL); }
/* Output the total number of excluded requests under the overall * object. * * On success, data is outputted. */ static void poverall_excluded (FILE * fp, GLog * logger, int isp) { int total = logger->excluded_ip; pjson (fp, "%.*s\"%s\": %d,%.*s", isp, TAB, OVERALL_EXCL_HITS, total, nlines, NL); }
/* Output the total number of unique visitors under the overall * object. * * On success, data is outputted. */ static void poverall_visitors (FILE * fp, int isp) { int total = ht_get_size_uniqmap (VISITORS); pjson (fp, "%.*s\"%s\": %d,%.*s", isp, TAB, OVERALL_VISITORS, total, nlines, NL); }
/* A wrapper function to ouput children nodes. */ static void print_json_sub_items (GJSON * json, GHolderItem * item, GPercTotals totals, int size, int iisp) { GMetrics *nmetrics; GSubItem *iter; GSubList *sl = item->sub_list; int i = 0, iiisp = 0, iiiisp = 0; /* no sub items, nothing to output */ if (size == 0) return; /* use tabs to prettify output */ if (conf.json_pretty_print) iiisp = iisp + 1, iiiisp = iiisp + 1; if (sl == NULL) return; pjson (json, ",%.*s%.*s\"items\": [%.*s", nlines, NL, iisp, TAB, nlines, NL); for (iter = sl->head; iter; iter = iter->next, i++) { set_data_metrics (iter->metrics, &nmetrics, totals); popen_obj (json, iiisp); print_json_block (json, nmetrics, iiiisp); pclose_obj (json, iiisp, (i == sl->size - 1)); free (nmetrics); } pclose_arr (json, iisp, 1); }
/* Output maximum time served data. * * On success, data is outputted. */ static void pmaxts (FILE * fp, GMetrics * nmetrics, int sp) { if (!conf.serve_usecs) return; pjson (fp, "%.*s\"maxts\": %lld,%.*s", sp, TAB, (long long) nmetrics->maxts.nts, nlines, NL); }
/* Output hits data. * * On success, data is outputted. */ static void phits (FILE * fp, GMetrics * nmetrics, int sp) { int isp = 0; /* use tabs to prettify output */ if (conf.json_pretty_print) isp = sp + 1; pjson (fp, "%.*s\"hits\": {%.*s", sp, TAB, nlines, NL); /* print hits */ pjson (fp, "%.*s\"count\": %d,%.*s", isp, TAB, nmetrics->hits, nlines, NL); /* print hits percent */ pjson (fp, "%.*s\"percent\": %4.2f%.*s", isp, TAB, nmetrics->hits_perc, nlines, NL); pjson (fp, "%.*s},%.*s", sp, TAB, nlines, NL); }
/* Output request method data. * * On success, data is outputted. */ static void pmethod (FILE * fp, GMetrics * nmetrics, int sp) { /* request method */ if (conf.append_method && nmetrics->method) { pjson (fp, "%.*s\"method\": \"%s\",%.*s", sp, TAB, nmetrics->method, nlines, NL); } }
/* Output protocol method data. * * On success, data is outputted. */ static void pprotocol (FILE * fp, GMetrics * nmetrics, int sp) { /* request protocol */ if (conf.append_protocol && nmetrics->protocol) { pjson (fp, "%.*s\"protocol\": \"%s\",%.*s", sp, TAB, nmetrics->protocol, nlines, NL); } }
/* Output the size of the log being parsed under the overall object. * * On success, data is outputted. */ static void poverall_log_size (FILE * fp, GLog * logger, int isp) { off_t log_size = 0; if (!logger->piping && conf.ifile) log_size = file_size (conf.ifile); pjson (fp, "%.*s\"%s\": %jd,%.*s", isp, TAB, OVERALL_LOGSIZE, (intmax_t) log_size, nlines, NL); }
/* Output the maximum time served meta data object. * * If no metadata found, it simply returns. * On success, meta data is outputted. */ static void pmeta_data_maxts (FILE * fp, GModule module, int sp) { int isp = 0; uint64_t count = 0; if (!conf.serve_usecs) return; /* use tabs to prettify output */ if (conf.json_pretty_print) isp = sp + 1; count = ht_get_meta_data (module, "maxts"); pjson (fp, "%.*s\"maxts\": {%.*s", sp, TAB, nlines, NL); pjson (fp, "%.*s\"count\": %lld%.*s", isp, TAB, (long long) count, nlines, NL); pjson (fp, "%.*s},%.*s", sp, TAB, nlines, NL); }
/* Output bandwidth data. * * On success, data is outputted. */ static void pbw (FILE * fp, GMetrics * nmetrics, int sp) { int isp = 0; /* use tabs to prettify output */ if (conf.json_pretty_print) isp = sp + 1; if (!conf.bandwidth) return; pjson (fp, "%.*s\"bytes\": {%.*s", sp, TAB, nlines, NL); /* print bandwidth */ pjson (fp, "%.*s\"count\": %d,%.*s", isp, TAB, nmetrics->bw.nbw, nlines, NL); /* print bandwidth percent */ pjson (fp, "%.*s\"percent\": %4.2f%.*s", isp, TAB, nmetrics->bw_perc, nlines, NL); pjson (fp, "%.*s},%.*s", sp, TAB, nlines, NL); }
/* Output date and time for the overall object. * * On success, data is outputted. */ static void poverall_datetime (FILE * fp, int isp) { char now[DATE_TIME]; generate_time (); strftime (now, DATE_TIME, "%Y-%m-%d %H:%M:%S", now_tm); pjson (fp, "%.*s\"%s\": \"%s\",%.*s", isp, TAB, OVERALL_DATETIME, now, nlines, NL); }
/* A wrapper function to ouput geolocation fields for the given host. */ static void print_json_host_geo (GJSON * json, GSubList * sl, int iisp) { GSubItem *iter; int i; static const char *key[] = { "country", "city", "hostname", }; pjson (json, ",%.*s", nlines, NL); /* Iterate over child properties (country, city, etc) and print them out */ for (i = 0, iter = sl->head; iter; iter = iter->next, i++) { pjson (json, "%.*s\"%s\": \"", iisp, TAB, key[iter->metrics->id]); escape_json_output (json, iter->metrics->data); pjson (json, (i != sl->size - 1) ? "\",%.*s" : "\"", nlines, NL); } }
/* Escape all other characters accordingly. */ static void escape_json_other (GJSON * json, char **s) { /* Since JSON data is bootstrapped into the HTML document of a report, * then we perform the following four translations in case weird stuff * is put into the document. * * Note: The following scenario assumes that the user manually makes * the HTML report a PHP file (GoAccess doesn't allow the creation of a * PHP file): * * /index.html<?php eval(base_decode('iZWNobyAiPGgxPkhFTExPPC9oMT4iOw=='));?> */ if (escape_html_output) { switch (**s) { case '\'': pjson (json, "'"); return; case '&': pjson (json, "&"); return; case '<': pjson (json, "<"); return; case '>': pjson (json, ">"); return; } } if ((uint8_t) ** s <= 0x1f) { /* Control characters (U+0000 through U+001F) */ char buf[8]; snprintf (buf, sizeof buf, "\\u%04x", **s); pjson (json, "%s", buf); } else if ((uint8_t) ** s == 0xe2 && (uint8_t) * (*s + 1) == 0x80 && (uint8_t) * (*s + 2) == 0xa8) { /* Line separator (U+2028) - 0xE2 0x80 0xA8 */ pjson (json, "\\u2028"); *s += 2; } else if ((uint8_t) ** s == 0xe2 && (uint8_t) * (*s + 1) == 0x80 && (uint8_t) * (*s + 2) == 0xa9) { /* Paragraph separator (U+2019) - 0xE2 0x80 0xA9 */ pjson (json, "\\u2029"); *s += 2; } else { char buf[2]; snprintf (buf, sizeof buf, "%c", **s); pjson (json, "%s", buf); } }
/* Output overall data. */ static void print_json_summary (FILE * fp, GLog * logger) { int sp = 0, isp = 0; /* use tabs to prettify output */ if (conf.json_pretty_print) sp = 1, isp = 2; pjson (fp, "%.*s\"%s\": {%.*s", sp, TAB, GENER_ID, nlines, NL); /* generated date time */ poverall_datetime (fp, isp); /* total requests */ poverall_requests (fp, logger, isp); /* valid requests */ poverall_valid_reqs (fp, logger, isp); /* invalid requests */ poverall_invalid_reqs (fp, logger, isp); /* generated time */ poverall_processed_time (fp, isp); /* visitors */ poverall_visitors (fp, isp); /* files */ poverall_files (fp, isp); /* excluded hits */ poverall_excluded (fp, logger, isp); /* referrers */ poverall_refs (fp, isp); /* not found */ poverall_notfound (fp, isp); /* static files */ poverall_static_files (fp, isp); /* log size */ poverall_log_size (fp, logger, isp); /* bandwidth */ poverall_bandwidth (fp, logger, isp); /* log path */ poverall_log (fp, isp); pjson (fp, "%.*s},%.*s", sp, TAB, nlines, NL); }
/* Output the average of the average time served meta data object. * * If no metadata found, it simply returns. * On success, meta data is outputted. */ static void pmeta_data_avgts (FILE * fp, GModule module, int sp) { int isp = 0; uint64_t avg = 0, hits = 0, cumts = 0; if (!conf.serve_usecs) return; /* use tabs to prettify output */ if (conf.json_pretty_print) isp = sp + 1; cumts = ht_get_meta_data (module, "cumts"); hits = ht_get_meta_data (module, "hits"); if (hits > 0) avg = cumts / hits; pjson (fp, "%.*s\"avgts\": {%.*s", sp, TAB, nlines, NL); pjson (fp, "%.*s\"avg\": %lld%.*s", isp, TAB, (long long) avg, nlines, NL); pjson (fp, "%.*s},%.*s", sp, TAB, nlines, NL); }
/* A wrapper function to ouput data metrics per panel. */ static void print_json_block (GJSON * json, GMetrics * nmetrics, int sp) { /* print hits */ phits (json, nmetrics, sp); /* print visitors */ pvisitors (json, nmetrics, sp); /* print bandwidth */ pbw (json, nmetrics, sp); /* print time served metrics */ pavgts (json, nmetrics, sp); pcumts (json, nmetrics, sp); pmaxts (json, nmetrics, sp); /* print protocol/method */ pmethod (json, nmetrics, sp); pprotocol (json, nmetrics, sp); /* data metric */ pjson (json, "%.*s\"data\": \"", sp, TAB); escape_json_output (json, nmetrics->data); pjson (json, "\""); }