Skip to content

Commit 7f3800f

Browse files
authored
Allow parsing of escape sequence in log format (#443)
Signed-off-by: Marc Bestmann <marc.bestmann@dlr.de>
1 parent 477b24d commit 7f3800f

File tree

3 files changed

+72
-11
lines changed

3 files changed

+72
-11
lines changed

src/logging.c

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ bool g_rcutils_logging_initialized = false;
8787
static char g_rcutils_logging_output_format_string[RCUTILS_LOGGING_MAX_OUTPUT_FORMAT_LEN];
8888
static const char * g_rcutils_logging_default_output_format =
8989
"[{severity}] [{time}] [{name}]: {message}";
90+
#ifdef _WIN32
91+
static DWORD g_original_console_mode = 0;
92+
static bool g_consol_mode_modified = false;
93+
#endif
9094

9195
static rcutils_allocator_t g_rcutils_logging_allocator;
9296

@@ -415,6 +419,30 @@ static const char * copy_from_orig(
415419
return logging_output->buffer;
416420
}
417421

422+
#ifdef _WIN32
423+
#define ACTIVATE_VIRTUAL_TERMINAL_PROCESSING() \
424+
{ \
425+
HANDLE std_error_handle = GetStdHandle(STD_ERROR_HANDLE); \
426+
if (std_error_handle == INVALID_HANDLE_VALUE) { \
427+
RCUTILS_SET_ERROR_MSG("Could not get error handle to activating virtual terminal."); \
428+
return; \
429+
} \
430+
if (!GetConsoleMode(std_error_handle, &g_original_console_mode)) { \
431+
RCUTILS_SET_ERROR_MSG("Could not get consol mode to activating virtual terminal."); \
432+
return; \
433+
} \
434+
DWORD newDwMode = g_original_console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; \
435+
if (!SetConsoleMode(std_error_handle, newDwMode)) { \
436+
RCUTILS_SET_ERROR_MSG("Could not set consol mode to activating virtual terminal."); \
437+
return; \
438+
} \
439+
g_consol_mode_modified = true; \
440+
}
441+
#else
442+
// nothing todo for non-windows platform
443+
#define ACTIVATE_VIRTUAL_TERMINAL_PROCESSING()
444+
#endif
445+
418446
// copy buffers and decode escape characters if they exist
419447
static void create_format_string(
420448
const char * logging_output_format_string)
@@ -440,14 +468,28 @@ static void create_format_string(
440468
break;
441469
} else {
442470
const char * expected_char = NULL;
443-
switch (logging_output_format_string[i + back_slash_index + 1]) {
444-
case 'a': expected_char = "\a"; break; // alert
445-
case 'b': expected_char = "\b"; break; // backspace
446-
case 'n': expected_char = "\n"; break; // new line
447-
case 'r': expected_char = "\r"; break; // carriage return
448-
case 't': expected_char = "\t"; break; // horizontal tab
449-
default:
450-
break;
471+
int skip_chars = 0;
472+
473+
if (logging_output_format_string[i + back_slash_index + 1] == 'x' &&
474+
logging_output_format_string[i + back_slash_index + 2] == '1' &&
475+
logging_output_format_string[i + back_slash_index + 3] == 'b')
476+
{
477+
// detect escape sequence
478+
ACTIVATE_VIRTUAL_TERMINAL_PROCESSING();
479+
expected_char = "\x1b";
480+
// the 4 char long "\x1b" string literal will become a 2 char long \x1b escape sequence
481+
// therefore we need to skip forward in parsing the output format string
482+
skip_chars = 2;
483+
} else {
484+
switch (logging_output_format_string[i + back_slash_index + 1]) {
485+
case 'a': expected_char = "\a"; break; // alert
486+
case 'b': expected_char = "\b"; break; // backspace
487+
case 'n': expected_char = "\n"; break; // new line
488+
case 'r': expected_char = "\r"; break; // carriage return
489+
case 't': expected_char = "\t"; break; // horizontal tab
490+
default:
491+
break;
492+
}
451493
}
452494

453495
if (expected_char) {
@@ -466,12 +508,12 @@ static void create_format_string(
466508
// copy the decoded character
467509
g_rcutils_logging_output_format_string[dest_buffer_index] = expected_char[0];
468510
dest_buffer_index += 1;
469-
start_offset += 2;
511+
start_offset += 2 + skip_chars;
470512
} else {
471513
start_offset_previous_not_copy += (back_slash_index + 2);
472514
}
473515

474-
i += (back_slash_index + 2);
516+
i += (back_slash_index + 2 + skip_chars);
475517
}
476518
}
477519
}
@@ -749,6 +791,12 @@ rcutils_ret_t rcutils_logging_shutdown(void)
749791
}
750792
g_num_log_msg_handlers = 0;
751793
g_rcutils_logging_initialized = false;
794+
795+
#ifdef _WIN32
796+
if (g_consol_mode_modified) {
797+
SetConsoleMode(GetStdHandle(STD_ERROR_HANDLE), g_original_console_mode);
798+
}
799+
#endif
752800
return ret;
753801
}
754802

test/test_logging_output_format.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ def generate_test_description():
9191
))
9292
processes_to_test.append(name)
9393

94+
env_escape_sequence = dict(os.environ)
95+
# This custom output is to check that escape characters work correctly.
96+
env_escape_sequence['RCUTILS_CONSOLE_OUTPUT_FORMAT'] = \
97+
'{name} \x1b[0m {message}'
98+
name = 'test_logging_output_format_escape_sequence'
99+
launch_description.add_action(ExecuteProcess(
100+
cmd=[executable], env=env_escape_sequence, name=name, output='screen'
101+
))
102+
processes_to_test.append(name)
103+
94104
launch_description.add_action(
95105
launch_testing.actions.ReadyToTest()
96106
)
@@ -117,7 +127,8 @@ def test_logging_output(self, proc_output, processes_to_test):
117127
path=os.path.join(os.path.dirname(__file__), process_name),
118128
encoding='unicode_escape'
119129
),
120-
process=process_name
130+
process=process_name,
131+
strip_ansi_escape_sequences=False
121132
)
122133

123134
def test_processes_exit_codes(self, proc_info):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
name1 \x1b\[0m Xx{2045}X
2+
name2 \x1b\[0m X42x{2043}X

0 commit comments

Comments
 (0)