From: Iain Patterson Date: Tue, 6 Sep 2016 09:30:21 +0000 (+0100) Subject: Alow prefixing log output with a timestamp. X-Git-Url: http://git.iain.cx/?a=commitdiff_plain;h=cc956aacde93d93f5810ca4c0df7775d1bfe3458;p=nssm.git Alow prefixing log output with a timestamp. When AppTimestampLog is set to 1 all output to stdout and stderr will be prefixed with a millisecond-precision ISO8601 timestamp. Timestamp prefixing implies online rotation mode. It should be considered experimental as it requires splitting input lines by LF with no way of knowing for certain that the input stream actually is line-based. Thanks Nicolas Ducrocq. --- diff --git a/README.txt b/README.txt index 00440f9..fc5121e 100644 --- a/README.txt +++ b/README.txt @@ -371,6 +371,21 @@ error-prone than simply redirecting the I/O streams before launching the application. Therefore online rotation is not enabled by default. +Timestamping output +------------------- +When redirecting output, NSSM can prefix each line of output with a +millisecond-precision timestamp, for example: + + 2016-09-06 10:17:09.451 Pipeline main started + +To enable timestamp prefixing, set AppTimestampLog to a non-zero value. + +The prefix applies to both stdout and stderr. Prefixing requires +intercepting the application's I/O in the same way that online rotation +does. If log rotation and timestamp prefixing are both enabled, the +rotation will be online. + + Environment variables --------------------- NSSM can replace or append to the managed application's environment. Two @@ -1006,6 +1021,7 @@ Thanks to Nabil Redmann for suggesting redirecting hooks' output. Thanks to Bader Aldurai for suggesting the process tree. Thanks to Christian Long for suggesting virtual accounts. Thanks to Marcin Lewandowski for spotting a bug appending to large files. +Thanks to Nicolas Ducrocq for suggesting timestamping redirected output. Licence ------- diff --git a/gui.cpp b/gui.cpp index 0fa11f4..9c4d712 100644 --- a/gui.cpp +++ b/gui.cpp @@ -180,6 +180,7 @@ int nssm_gui(int resource, nssm_service_t *service) { SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDIN, service->stdin_path); SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDOUT, service->stdout_path); SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDERR, service->stderr_path); + if (service->timestamp_log) SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TIMESTAMP, BM_SETCHECK, BST_CHECKED, 0); /* Rotation tab. */ if (service->stdout_disposition == CREATE_ALWAYS) SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_SETCHECK, BST_CHECKED, 0); @@ -676,6 +677,8 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service check_io(window, _T("stdin"), service->stdin_path, _countof(service->stdin_path), IDC_STDIN); check_io(window, _T("stdout"), service->stdout_path, _countof(service->stdout_path), IDC_STDOUT); check_io(window, _T("stderr"), service->stderr_path, _countof(service->stderr_path), IDC_STDERR); + if (SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TIMESTAMP, BM_GETCHECK, 0, 0) & BST_CHECKED) service->timestamp_log = true; + else service->timestamp_log = false; /* Override stdout and/or stderr. */ if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) { @@ -1248,6 +1251,9 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) { tablist[NSSM_TAB_IO] = dialog(MAKEINTRESOURCE(IDD_IO), window, tab_dlg); ShowWindow(tablist[NSSM_TAB_IO], SW_HIDE); + /* Set defaults. */ + SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TIMESTAMP, BM_SETCHECK, BST_UNCHECKED, 0); + /* Rotation tab. */ tab.pszText = message_string(NSSM_GUI_TAB_ROTATION); tab.cchTextMax = (int) _tcslen(tab.pszText) + 1; diff --git a/io.cpp b/io.cpp index 48611b0..dd2641c 100644 --- a/io.cpp +++ b/io.cpp @@ -3,6 +3,8 @@ #define COMPLAINED_READ (1 << 0) #define COMPLAINED_WRITE (1 << 1) #define COMPLAINED_ROTATE (1 << 2) +#define TIMESTAMP_FORMAT "%04u-%02u-%02u %02u:%02u:%02u.%03u: " +#define TIMESTAMP_LEN 25 static int dup_handle(HANDLE source_handle, HANDLE *dest_handle_ptr, TCHAR *source_description, TCHAR *dest_description, unsigned long flags) { if (! dest_handle_ptr) return 1; @@ -23,7 +25,7 @@ static int dup_handle(HANDLE source_handle, HANDLE *dest_handle_ptr, TCHAR *sour pipe_handle: stdout of application write_handle: to file */ -static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned long sharing, unsigned long disposition, unsigned long flags, HANDLE *read_handle_ptr, HANDLE *pipe_handle_ptr, HANDLE *write_handle_ptr, unsigned long rotate_bytes_low, unsigned long rotate_bytes_high, unsigned long rotate_delay, unsigned long *tid_ptr, unsigned long *rotate_online, bool copy_and_truncate) { +static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned long sharing, unsigned long disposition, unsigned long flags, HANDLE *read_handle_ptr, HANDLE *pipe_handle_ptr, HANDLE *write_handle_ptr, unsigned long rotate_bytes_low, unsigned long rotate_bytes_high, unsigned long rotate_delay, unsigned long *tid_ptr, unsigned long *rotate_online, bool timestamp_log, bool copy_and_truncate) { *tid_ptr = 0; /* Pipe between application's stdout/stderr and our logging handle. */ @@ -58,6 +60,8 @@ static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned l logger->write_handle = *write_handle_ptr; logger->size = (__int64) size.QuadPart; logger->tid_ptr = tid_ptr; + logger->timestamp_log = timestamp_log; + logger->line_length = 0; logger->rotate_online = rotate_online; logger->rotate_delay = rotate_delay; logger->copy_and_truncate = copy_and_truncate; @@ -323,7 +327,7 @@ int get_output_handles(nssm_service_t *service, STARTUPINFO *si) { if (service->use_stdout_pipe) { service->stdout_pipe = si->hStdOutput = 0; - service->stdout_thread = create_logging_thread(service->name, service->stdout_path, service->stdout_sharing, service->stdout_disposition, service->stdout_flags, &service->stdout_pipe, &service->stdout_si, &stdout_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stdout_tid, &service->rotate_stdout_online, service->stdout_copy_and_truncate); + service->stdout_thread = create_logging_thread(service->name, service->stdout_path, service->stdout_sharing, service->stdout_disposition, service->stdout_flags, &service->stdout_pipe, &service->stdout_si, &stdout_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stdout_tid, &service->rotate_stdout_online, service->timestamp_log, service->stdout_copy_and_truncate); if (! service->stdout_thread) { CloseHandle(service->stdout_pipe); CloseHandle(service->stdout_si); @@ -360,7 +364,7 @@ int get_output_handles(nssm_service_t *service, STARTUPINFO *si) { if (service->use_stderr_pipe) { service->stderr_pipe = si->hStdError = 0; - service->stderr_thread = create_logging_thread(service->name, service->stderr_path, service->stderr_sharing, service->stderr_disposition, service->stderr_flags, &service->stderr_pipe, &service->stderr_si, &stderr_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stderr_tid, &service->rotate_stderr_online, service->stderr_copy_and_truncate); + service->stderr_thread = create_logging_thread(service->name, service->stderr_path, service->stderr_sharing, service->stderr_disposition, service->stderr_flags, &service->stderr_pipe, &service->stderr_si, &stderr_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stderr_tid, &service->rotate_stderr_online, service->timestamp_log, service->stderr_copy_and_truncate); if (! service->stderr_thread) { CloseHandle(service->stderr_pipe); CloseHandle(service->stderr_si); @@ -533,6 +537,69 @@ complain_write: return ret; } +/* Note that the timestamp is created in UTF-8. */ +static inline int write_timestamp(logger_t *logger, unsigned long charsize, unsigned long *out, int *complained) { + char timestamp[TIMESTAMP_LEN + 1]; + + SYSTEMTIME now; + GetSystemTime(&now); + _snprintf_s(timestamp, _countof(timestamp), _TRUNCATE, TIMESTAMP_FORMAT, now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond, now.wMilliseconds); + + if (charsize == sizeof(char)) return try_write(logger, (void *) timestamp, TIMESTAMP_LEN, out, complained); + + wchar_t *utf16; + unsigned long utf16len; + if (to_utf16(timestamp, &utf16, &utf16len)) return -1; + int ret = try_write(logger, (void *) *utf16, utf16len * sizeof(wchar_t), out, complained); + HeapFree(GetProcessHeap(), 0, utf16); + return ret; +} + +static int write_with_timestamp(logger_t *logger, void *address, unsigned long bufsize, unsigned long *out, int *complained, unsigned long charsize) { + if (logger->timestamp_log) { + unsigned long log_out; + int log_complained; + unsigned long timestamp_out = 0; + int timestamp_complained; + if (! logger->line_length) { + write_timestamp(logger, charsize, ×tamp_out, ×tamp_complained); + logger->line_length += (__int64) timestamp_out; + *out += timestamp_out; + *complained |= timestamp_complained; + } + + unsigned long i; + void *line = address; + unsigned long offset = 0; + int ret; + for (i = 0; i < bufsize; i++) { + if (((char *) address)[i] == '\n') { + ret = try_write(logger, line, i - offset + 1, &log_out, &log_complained); + line = (void *) ((char *) line + i - offset + 1); + logger->line_length = 0LL; + *out += log_out; + *complained |= log_complained; + offset = i + 1; + if (offset < bufsize) { + write_timestamp(logger, charsize, ×tamp_out, ×tamp_complained); + logger->line_length += (__int64) timestamp_out; + *out += timestamp_out; + *complained |= timestamp_complained; + } + } + } + + if (offset < bufsize) { + ret = try_write(logger, line, bufsize - offset, &log_out, &log_complained); + *out += log_out; + *complained |= log_complained; + } + + return ret; + } + else return try_write(logger, address, bufsize, out, complained); +} + /* Wrapper to be called in a new thread for logging. */ unsigned long WINAPI log_and_rotate(void *arg) { logger_t *logger = (logger_t *) arg; @@ -650,9 +717,9 @@ unsigned long WINAPI log_and_rotate(void *arg) { } } + if (! size || logger->timestamp_log) if (! charsize) charsize = guess_charsize(address, in); if (! size) { /* Write a BOM to the new file. */ - if (! charsize) charsize = guess_charsize(address, in); if (charsize == sizeof(wchar_t)) write_bom(logger, &out); size += (__int64) out; } @@ -660,7 +727,7 @@ unsigned long WINAPI log_and_rotate(void *arg) { /* Write the data, if any. */ if (! in) continue; - ret = try_write(logger, address, in, &out, &complained); + ret = write_with_timestamp(logger, address, in, &out, &complained, charsize); size += (__int64) out; if (ret < 0) { close_handle(&logger->read_handle); diff --git a/nssm.rc b/nssm.rc index 9f61384..8c0d377 100644 Binary files a/nssm.rc and b/nssm.rc differ diff --git a/registry.cpp b/registry.cpp index 93c034d..1b85af5 100644 --- a/registry.cpp +++ b/registry.cpp @@ -166,6 +166,8 @@ int create_parameters(nssm_service_t *service, bool editing) { if (service->stderr_copy_and_truncate) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE, 1); else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE); } + if (service->timestamp_log) set_number(key, NSSM_REG_TIMESTAMP_LOG, 1); + else if (editing) RegDeleteValue(key, NSSM_REG_TIMESTAMP_LOG); if (service->hook_share_output_handles) set_number(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, 1); else if (editing) RegDeleteValue(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES); if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1); @@ -828,9 +830,17 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) { else service->rotate_stdout_online = service->rotate_stderr_online = false; } else service->rotate_stdout_online = service->rotate_stderr_online = false; + /* Log timestamping requires a logging thread.*/ + unsigned long timestamp_log; + if (get_number(key, NSSM_REG_TIMESTAMP_LOG, ×tamp_log, false) == 1) { + if (timestamp_log) service->timestamp_log = true; + else service->timestamp_log = false; + } + else service->timestamp_log = false; + /* Hook I/O sharing and online rotation need a pipe. */ - service->use_stdout_pipe = service->rotate_stdout_online || hook_share_output_handles; - service->use_stderr_pipe = service->rotate_stderr_online || hook_share_output_handles; + service->use_stdout_pipe = service->rotate_stdout_online || service->timestamp_log || hook_share_output_handles; + service->use_stderr_pipe = service->rotate_stderr_online || service->timestamp_log || hook_share_output_handles; if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0; if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0; if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0; diff --git a/registry.h b/registry.h index ad7cd7a..25ba091 100644 --- a/registry.h +++ b/registry.h @@ -32,6 +32,7 @@ #define NSSM_REG_ROTATE_BYTES_LOW _T("AppRotateBytes") #define NSSM_REG_ROTATE_BYTES_HIGH _T("AppRotateBytesHigh") #define NSSM_REG_ROTATE_DELAY _T("AppRotateDelay") +#define NSSM_REG_TIMESTAMP_LOG _T("AppTimestampLog") #define NSSM_REG_PRIORITY _T("AppPriority") #define NSSM_REG_AFFINITY _T("AppAffinity") #define NSSM_REG_NO_CONSOLE _T("AppNoConsole") diff --git a/resource.h b/resource.h index 3dcf6db..f4dd3d5 100644 --- a/resource.h +++ b/resource.h @@ -72,6 +72,7 @@ #define IDC_BROWSE_HOOK 1051 #define IDC_REDIRECT_HOOK 1052 #define IDC_VIRTUAL_SERVICE 1053 +#define IDC_TIMESTAMP 1054 // Next default values for new objects // @@ -79,7 +80,7 @@ #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 117 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1054 +#define _APS_NEXT_CONTROL_VALUE 1055 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/service.h b/service.h index a8e0bcf..fb8a160 100644 --- a/service.h +++ b/service.h @@ -77,6 +77,7 @@ typedef struct { unsigned long stderr_tid; bool hook_share_output_handles; bool rotate_files; + bool timestamp_log; bool stdout_copy_and_truncate; bool stderr_copy_and_truncate; unsigned long rotate_stdout_online; diff --git a/settings.cpp b/settings.cpp index 9e5f666..e72430f 100644 --- a/settings.cpp +++ b/settings.cpp @@ -1435,6 +1435,7 @@ settings_t settings[] = { { NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number, 0 }, { NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number, 0 }, { NSSM_REG_ROTATE_DELAY, REG_DWORD, (void *) NSSM_ROTATE_DELAY, false, 0, setting_set_number, setting_get_number, 0 }, + { NSSM_REG_TIMESTAMP_LOG, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number, 0 }, { NSSM_NATIVE_DEPENDONGROUP, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependongroup, native_get_dependongroup, native_dump_dependongroup }, { NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice, native_dump_dependonservice }, { NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description, 0 },