From bc559ab22aa8c146a35dc6bd503ba1a3c02ff186 Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Mon, 27 Apr 2020 06:16:55 +0200 Subject: idevicesyslog: Add color support and filtering options --- docs/idevicesyslog.1 | 104 ++++++++++- tools/idevicesyslog.c | 506 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 594 insertions(+), 16 deletions(-) diff --git a/docs/idevicesyslog.1 b/docs/idevicesyslog.1 index aa24339..fce1ff5 100644 --- a/docs/idevicesyslog.1 +++ b/docs/idevicesyslog.1 @@ -10,20 +10,106 @@ idevicesyslog \- Relay syslog of a connected device. Relay syslog of a connected device. .SH OPTIONS -.TP -.B \-d, \-\-debug -enable communication debugging. .TP .B \-u, \-\-udid UDID -target specific device by UDID. -.TP +target specific device by UDID +.TP +.B \-n, \-\-network +connect to network device even if available via USB +.TP +.B \-x, \-\-exit +exit when device disconnects +.TP +.B \-d, \-\-debug +enable communication debugging +.TP .B \-h, \-\-help -prints usage information. +prints usage information + +.SH FILTER OPTIONS +.TP +.B \-m, \-\-match STRING +only print messages that contain STRING + +This option will set a filter to only printed log messages that contain the given string. +.TP +.B \-t, \-\-trigger STRING +start logging when matching STRING + +When specified, logging will start as soon as a log messages is encountered that contains the given string. See also +\f[B]\-T, \-\-untrigger\f[]. Other filters are still applied but obviously filtered messages are only printed after logging has started. +.TP +.B \-T, \-\-untrigger STRING +stop logging when matching STRING + +When specified logging will halt as soon as a log message is encountered that contains the given string. See also +\f[B]\-t, \-\-trigger\f[]. Other filters are still applied but obviously filtered messages are only printed before logging stops. + +NOTE: If no \f[B]\-\-trigger\f[] is given, idevicesyslog will exit after a matching log message was encountered. +.TP +.B \-p, \-\-process PROCESS +only print messages from matching process(es) + +PROCESS is a string that can either be a numeric pid or a process name. It also supports multiple process names or pids in one string, separated by | (make sure to use quotes!). +.TP +.B \-e, \-\-exclude PROCESS +print all messages except matching process(es) + +PROCESS is a string that can either be a numeric pid or a process name. It also supports multiple process names or pids in one string, separated by | (make sure to use quotes!). +.TP +.B \-q, \-\-quiet +set a filter to exclude common noisy processes + +Since the syslog can be quite noisy, this quick command line switch allows to silence out a predefined set of commonly known processes. The list of processes that are silenced can be retrieved with \f[B]\-\-quiet\-list\f[]. +.TP +.B \-\-quiet\-list +prints the list of processes for \f[B]\-\-quiet\f[] and exits +.TP +.B \-k, \-\-kernel +only print kernel messages + +This is actually equivalent to passing \f[B]\-\-process kernel\f[] with the exception that it can be used with \f[B]\-\-quiet\f[] to silence out the noisy process but still get all the kernel log messages. +.TP +.B \-K, \-\-no\-kernel +suppress kernel messages + +This is equivalent to passing \f[B]\-\-exclude kernel\f[]. + +.SH EXAMPLES +.TP +.B idevicesyslog \-u 00008030\-0000111ABC000DEF +Relay syslog of device with UDID 00008030-0000111ABC000DEF. +.TP +.B idevicesyslog \-x +Relay syslog of device and exit when the device is unplugged. +.TP +.B idevicesyslog \-m '####' \-e 'identityservicesd' \-K +Only print log messages that contain the string #### and do NOT originate from identityservicesd or the kernel. +.TP +.B idevicesyslog \-p MyApp \-p ReportCrash +Only print log messages from the process named 'MyApp' and 'ReportCrash'. +.TP +.B idevicesyslog \-p 'MyApp|ReportCrash' +Same as previous example with different syntax. +.TP +.B idevicesyslog \-e 'backboardd|CommCenter|mDNSResponder' +Suppress log messages from backboardd, CommCenter, and mDNSResponder. +.TP +.B idevicesyslog \-q \-k +Suppress log messages from common noisy processes, but DO print kernel log messages. +.TP +.B idevicesyslog \-K +Suppress log messages from kernel, but print everything else +.TP +.B idevicesyslog \-t 'backlight on' \-T 'backlight off' \-q +Start logging when the device turns on backlight and stop logging when it turns backlight off, and suppress noisy processes -.SH AUTHOR -Martin Szulecki +.SH AUTHORS +Nikias Bassen, Martin Szulecki Man page written to conform with Debian by Julien Lavergne. .SH ON THE WEB -http://libimobiledevice.org +https://github.com/libimobiledevice/libimobiledevice + +https://libimobiledevice.org diff --git a/tools/idevicesyslog.c b/tools/idevicesyslog.c index 619b51b..b3a754f 100644 --- a/tools/idevicesyslog.c +++ b/tools/idevicesyslog.c @@ -2,7 +2,7 @@ * idevicesyslog.c * Relay the syslog of a device to stdout * - * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2020 Nikias Bassen, All Rights Reserved. * Copyright (c) 2009 Martin Szulecki All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -42,19 +42,325 @@ static int quit_flag = 0; static int exit_on_disconnect = 0; +static int use_colors = 0; +static int show_device_name = 0; static char* udid = NULL; +static char** proc_filters = NULL; +static int num_proc_filters = 0; +static int proc_filter_excluding = 0; + +static int* pid_filters = NULL; +static int num_pid_filters = 0; + +static char** msg_filters = NULL; +static int num_msg_filters = 0; + +static char** trigger_filters = NULL; +static int num_trigger_filters = 0; +static char** untrigger_filters = NULL; +static int num_untrigger_filters = 0; +static int triggered = 0; static idevice_t device = NULL; static syslog_relay_client_t syslog = NULL; +static const char QUIET_FILTER[] = "CommCenter|SpringBoard|UserEventAgent|WirelessRadioManagerd|aggregated|appstored|backboardd|biometrickitd|bluetoothd|callservicesd|contextstored|corespeechd|dasd|gpsd|homed|identityservicesd|itunesstored|kernel|locationd|mDNSResponder|mediaremoted|mediaserverd|navd|nsurlsessiond|powerd|rapportd|routined|runningboardd|sharingd|symptomsd|thermalmonitord|useractivityd|wifid"; + enum idevice_options lookup_opts = IDEVICE_LOOKUP_USBMUX | IDEVICE_LOOKUP_NETWORK; +static char *line = NULL; +static int line_buffer_size = 0; +static int lp = 0; + +#define COLOR_RESET "\e[m" +#define COLOR_NORMAL "\e[0m" +#define COLOR_DARK "\e[2m" +#define COLOR_RED "\e[0;31m" +#define COLOR_DARK_RED "\e[2;31m" +#define COLOR_GREEN "\e[0;32m" +#define COLOR_DARK_GREEN "\e[2;32m" +#define COLOR_YELLOW "\e[0;33m" +#define COLOR_DARK_YELLOW "\e[2;33m" +#define COLOR_BLUE "\e[0;34m" +#define COLOR_DARK_BLUE "\e[2;34m" +#define COLOR_MAGENTA "\e[0;35m" +#define COLOR_DARK_MAGENTA "\e[2;35m" +#define COLOR_CYAN "\e[0;36m" +#define COLOR_BRIGHT_CYAN "\e[1;36m" +#define COLOR_DARK_CYAN "\e[2;36m" +#define COLOR_WHITE "\e[1;37m" +#define COLOR_DARK_WHITE "\e[0;37m" + +#define TEXT_COLOR(x) if (use_colors) { fwrite(x, 1, sizeof(x), stdout); } + +static void add_filter(const char* filterstr) +{ + int filter_len = strlen(filterstr); + const char* start = filterstr; + const char* end = filterstr + filter_len; + const char* p = start; + while (p <= end) { + if ((*p == '|') || (*p == '\0')) { + if (p-start > 0) { + char* procn = malloc(p-start+1); + if (!procn) { + fprintf(stderr, "ERROR: malloc() failed\n"); + exit(EXIT_FAILURE); + } + memcpy(procn, start, p-start); + procn[p-start] = '\0'; + char* endp = NULL; + int pid_value = (int)strtol(procn, &endp, 10); + if (!endp || *endp == 0) { + int *new_pid_filters = realloc(pid_filters, sizeof(int) * (num_pid_filters+1)); + if (!new_pid_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + pid_filters = new_pid_filters; + pid_filters[num_pid_filters] = pid_value; + num_pid_filters++; + } else { + char **new_proc_filters = realloc(proc_filters, sizeof(char*) * (num_proc_filters+1)); + if (!new_proc_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + proc_filters = new_proc_filters; + proc_filters[num_proc_filters] = procn; + num_proc_filters++; + } + } + start = p+1; + } + p++; + } +} + +static int find_char(char c, char** p, char* end) +{ + while ((**p != c) && (*p < end)) { + (*p)++; + } + return (**p == c); +} + +static void stop_logging(void); + static void syslog_callback(char c, void *user_data) { - putchar(c); - if (c == '\n') { - fflush(stdout); + if (lp >= line_buffer_size-1) { + line_buffer_size+=1024; + char* _line = realloc(line, line_buffer_size); + if (!_line) { + fprintf(stderr, "ERROR: realloc failed\n"); + exit(EXIT_FAILURE); + } + line = _line; + } + line[lp++] = c; + if (c == '\0') { + int shall_print = 0; + int trigger_off = 0; + lp--; + char* linep = &line[0]; + do { + if (lp < 16) { + shall_print = 1; + TEXT_COLOR(COLOR_WHITE); + break; + } else if (line[3] == ' ' && line[6] == ' ' && line[15] == ' ') { + char* end = &line[lp]; + char* p = &line[16]; + + /* device name */ + char* device_name_start = p; + char* device_name_end = p; + if (!find_char(' ', &p, end)) break; + device_name_end = p; + p++; + + /* check if we have any triggers/untriggers */ + if (num_untrigger_filters > 0 && triggered) { + int found = 0; + int i; + for (i = 0; i < num_untrigger_filters; i++) { + if (strstr(device_name_end+1, untrigger_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 1; + } else { + shall_print = 1; + trigger_off = 1; + } + } else if (num_trigger_filters > 0 && !triggered) { + int found = 0; + int i; + for (i = 0; i < num_trigger_filters; i++) { + if (strstr(device_name_end+1, trigger_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 0; + break; + } else { + triggered = 1; + shall_print = 1; + } + } else if (num_trigger_filters == 0 && num_untrigger_filters > 0 && !triggered) { + shall_print = 0; + quit_flag++; + break; + } + + /* check message filters */ + if (num_msg_filters > 0) { + int found = 0; + int i; + for (i = 0; i < num_msg_filters; i++) { + if (strstr(device_name_end+1, msg_filters[i])) { + found = 1; + break; + } + } + if (!found) { + shall_print = 0; + break; + } else { + shall_print = 1; + } + } + + /* process name */ + char* proc_name_start = p; + char* proc_name_end = p; + if (!find_char('[', &p, end)) break; + char* process_name_start = proc_name_start; + char* process_name_end = p; + char* pid_start = p+1; + char* pp = process_name_start; + if (find_char('(', &pp, p)) { + process_name_end = pp; + } + if (!find_char(']', &p, end)) break; + p++; + if (*p != ' ') break; + proc_name_end = p; + p++; + + int proc_matched = 0; + if (num_pid_filters > 0) { + char* endp = NULL; + int pid_value = (int)strtol(pid_start, &endp, 10); + if (endp && (*endp == ']')) { + int found = proc_filter_excluding; + int i = 0; + for (i = 0; i < num_pid_filters; i++) { + if (pid_value == pid_filters[i]) { + found = !proc_filter_excluding; + break; + } + } + if (found) { + proc_matched = 1; + } + } + } + if (num_proc_filters > 0 && !proc_matched) { + int found = proc_filter_excluding; + int i = 0; + for (i = 0; i < num_proc_filters; i++) { + if (!proc_filters[i]) continue; + if (strncmp(proc_filters[i], process_name_start, process_name_end-process_name_start) == 0) { + found = !proc_filter_excluding; + break; + } + } + if (found) { + proc_matched = 1; + } + } + if (proc_matched) { + shall_print = 1; + } else { + if (num_pid_filters > 0 || num_proc_filters > 0) { + shall_print = 0; + break; + } + } + + /* log level */ + char* level_start = p; + char* level_end = p; + const char* level_color = NULL; + if (!strncmp(p, ":", 9)) { + level_end += 9; + level_color = COLOR_GREEN; + } else if (!strncmp(p, ":", 8)) { + level_end += 8; + level_color = COLOR_RED; + } else if (!strncmp(p, ":", 10)) { + level_end += 10; + level_color = COLOR_YELLOW; + } else if (!strncmp(p, ":", 8)) { + level_end += 8; + level_color = COLOR_MAGENTA; + } else { + level_color = COLOR_WHITE; + } + + /* write date and time */ + TEXT_COLOR(COLOR_DARK_WHITE); + fwrite(line, 1, 16, stdout); + + if (show_device_name) { + /* write device name */ + TEXT_COLOR(COLOR_DARK_YELLOW); + fwrite(device_name_start, 1, device_name_end-device_name_start+1, stdout); + TEXT_COLOR(COLOR_RESET); + } + + /* write process name */ + TEXT_COLOR(COLOR_BRIGHT_CYAN); + fwrite(process_name_start, 1, process_name_end-process_name_start, stdout); + TEXT_COLOR(COLOR_CYAN); + fwrite(process_name_end, 1, proc_name_end-process_name_end+1, stdout); + + /* write log level */ + TEXT_COLOR(level_color); + if (level_end > level_start) { + fwrite(level_start, 1, level_end-level_start, stdout); + p = level_end; + } + + lp -= p - linep; + linep = p; + + TEXT_COLOR(COLOR_WHITE); + + } else { + shall_print = 1; + TEXT_COLOR(COLOR_WHITE); + } + } while (0); + + if ((num_msg_filters == 0 && num_proc_filters == 0 && num_pid_filters == 0 && num_trigger_filters == 0 && num_untrigger_filters == 0) || shall_print) { + fwrite(linep, 1, lp, stdout); + TEXT_COLOR(COLOR_RESET); + fflush(stdout); + if (trigger_off) { + triggered = 0; + } + } + line[0] = '\0'; + lp = 0; + return; } } @@ -108,7 +414,7 @@ static int start_logging(void) } /* start capturing syslog */ - serr = syslog_relay_start_capture(syslog, syslog_callback, NULL); + serr = syslog_relay_start_capture_raw(syslog, syslog_callback, NULL); if (serr != SYSLOG_RELAY_E_SUCCESS) { fprintf(stderr, "ERROR: Unable tot start capturing syslog.\n"); syslog_relay_client_free(syslog); @@ -179,11 +485,25 @@ static void print_usage(int argc, char **argv, int is_error) fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0])); fprintf(is_error ? stderr : stdout, "Relay syslog of a connected device.\n\n" \ + "OPTIONS:\n" \ " -u, --udid UDID target specific device by UDID\n" \ " -n, --network connect to network device even if available via USB\n" \ + " -x, --exit exit when device disconnects\n" \ " -h, --help prints usage information\n" \ " -d, --debug enable communication debugging\n" \ - " -x, --exit exit when device disconnects\n" \ + "\n" \ + "FILTER OPTIONS:\n" \ + " -m, --match STRING only print messages that contain STRING\n" \ + " -t, --trigger STRING start logging when matching STRING\n" \ + " -T, --untrigger STRING stop logging when matching STRING\n" \ + " -p, --process PROCESS only print messages from matching process(es)\n" \ + " -e, --exclude PROCESS print all messages except matching process(es)\n" \ + " PROCESS is a process name or multiple process names separated by \"|\".\n" \ + " -q, --quiet set a filter to exclude common noisy processes\n" \ + " --quiet-list prints the list of processes for --quiet and exits\n" \ + " -k, --kernel only print kernel messages\n" \ + " -K, --no-kernel suppress kernel messages\n" \ + "For filter example usage consult idevicesyslog(1) man page.\n" \ "\n" \ "Homepage: <" PACKAGE_URL ">\n" ); @@ -191,6 +511,10 @@ static void print_usage(int argc, char **argv, int is_error) int main(int argc, char *argv[]) { + int include_filter = 0; + int exclude_filter = 0; + int include_kernel = 0; + int exclude_kernel = 0; int c = 0; const struct option longopts[] = { { "debug", no_argument, NULL, 'd' }, @@ -198,6 +522,15 @@ int main(int argc, char *argv[]) { "udid", required_argument, NULL, 'u' }, { "network", no_argument, NULL, 'n' }, { "exit", no_argument, NULL, 'x' }, + { "trigger", required_argument, NULL, 't' }, + { "untrigger", required_argument, NULL, 'T' }, + { "match", required_argument, NULL, 'm' }, + { "process", required_argument, NULL, 'p' }, + { "exclude", required_argument, NULL, 'e' }, + { "quiet", no_argument, NULL, 'q' }, + { "kernel", no_argument, NULL, 'k' }, + { "no-kernel", no_argument, NULL, 'K' }, + { "quiet-list", no_argument, NULL, 1 }, { NULL, 0, NULL, 0} }; @@ -208,7 +541,7 @@ int main(int argc, char *argv[]) signal(SIGPIPE, SIG_IGN); #endif - while ((c = getopt_long(argc, argv, "dhu:nx", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "dhu:nxt:T:m:e:p:qkK", longopts, NULL)) != -1) { switch (c) { case 'd': idevice_set_debug_level(1); @@ -225,21 +558,143 @@ int main(int argc, char *argv[]) case 'n': lookup_opts |= IDEVICE_LOOKUP_PREFER_NETWORK; break; + case 'q': + exclude_filter++; + add_filter(QUIET_FILTER); + break; + case 'p': + case 'e': + if (c == 'p') { + include_filter++; + } else if (c == 'e') { + exclude_filter++; + } + if (!*optarg) { + fprintf(stderr, "ERROR: filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + add_filter(optarg); + break; + case 'm': + if (!*optarg) { + fprintf(stderr, "ERROR: message filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_msg_filters = realloc(msg_filters, sizeof(char*) * (num_msg_filters+1)); + if (!new_msg_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + msg_filters = new_msg_filters; + msg_filters[num_msg_filters] = strdup(optarg); + num_msg_filters++; + } + break; + case 't': + if (!*optarg) { + fprintf(stderr, "ERROR: trigger filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_trigger_filters = realloc(trigger_filters, sizeof(char*) * (num_trigger_filters+1)); + if (!new_trigger_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + trigger_filters = new_trigger_filters; + trigger_filters[num_trigger_filters] = strdup(optarg); + num_trigger_filters++; + } + break; + case 'T': + if (!*optarg) { + fprintf(stderr, "ERROR: untrigger filter string must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } else { + char **new_untrigger_filters = realloc(untrigger_filters, sizeof(char*) * (num_untrigger_filters+1)); + if (!new_untrigger_filters) { + fprintf(stderr, "ERROR: realloc() failed\n"); + exit(EXIT_FAILURE); + } + untrigger_filters = new_untrigger_filters; + untrigger_filters[num_untrigger_filters] = strdup(optarg); + num_untrigger_filters++; + } + break; + case 'k': + include_kernel++; + break; + case 'K': + exclude_kernel++; + break; case 'x': exit_on_disconnect = 1; break; case 'h': print_usage(argc, argv, 0); return 0; + case 1: { + printf("%s\n", QUIET_FILTER); + return 0; + } default: print_usage(argc, argv, 1); return 2; } } + if (include_kernel > 0 && exclude_kernel > 0) { + fprintf(stderr, "ERROR: -k and -K cannot be used together.\n"); + print_usage(argc, argv, 1); + return 2; + } + + if (include_filter > 0 && exclude_filter > 0) { + fprintf(stderr, "ERROR: -p and -e/-q cannot be used together.\n"); + print_usage(argc, argv, 1); + return 2; + } else if (include_filter > 0 && exclude_kernel > 0) { + fprintf(stderr, "ERROR: -p and -K cannot be used together.\n"); + print_usage(argc, argv, 1); + return 2; + } + + if (exclude_filter > 0) { + proc_filter_excluding = 1; + if (include_kernel) { + int i = 0; + for (i = 0; i < num_proc_filters; i++) { + if (!strcmp(proc_filters[i], "kernel")) { + free(proc_filters[i]); + proc_filters[i] = NULL; + } + } + } else if (exclude_kernel) { + add_filter("kernel"); + } + } else { + if (include_kernel) { + add_filter("kernel"); + } else if (exclude_kernel) { + proc_filter_excluding = 1; + add_filter("kernel"); + } + } + + if (num_untrigger_filters > 0 && num_trigger_filters == 0) { + triggered = 1; + } + argc -= optind; argv += optind; + if (isatty(1)) { + use_colors = 1; + } + int num = 0; idevice_info_t *devices = NULL; idevice_get_device_list_extended(&devices, &num); @@ -253,6 +708,9 @@ int main(int argc, char *argv[]) } } + line_buffer_size = 1024; + line = malloc(line_buffer_size); + idevice_event_subscribe(device_event_cb, NULL); while (!quit_flag) { @@ -261,6 +719,40 @@ int main(int argc, char *argv[]) idevice_event_unsubscribe(); stop_logging(); + if (num_proc_filters > 0) { + int i; + for (i = 0; i < num_proc_filters; i++) { + free(proc_filters[i]); + } + free(proc_filters); + } + if (num_pid_filters > 0) { + free(pid_filters); + } + if (num_msg_filters > 0) { + int i; + for (i = 0; i < num_msg_filters; i++) { + free(msg_filters[i]); + } + free(msg_filters); + } + if (num_trigger_filters > 0) { + int i; + for (i = 0; i < num_trigger_filters; i++) { + free(trigger_filters[i]); + } + free(trigger_filters); + } + if (num_untrigger_filters > 0) { + int i; + for (i = 0; i < num_untrigger_filters; i++) { + free(untrigger_filters[i]); + } + free(untrigger_filters); + } + + free(line); + free(udid); return 0; -- cgit v1.1-32-gdbae