SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDIN, service->stdin_path);\r
SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDOUT, service->stdout_path);\r
SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDERR, service->stderr_path);\r
+ if (service->timestamp_log) SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TIMESTAMP, BM_SETCHECK, BST_CHECKED, 0);\r
\r
/* Rotation tab. */\r
if (service->stdout_disposition == CREATE_ALWAYS) SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_SETCHECK, BST_CHECKED, 0);\r
check_io(window, _T("stdin"), service->stdin_path, _countof(service->stdin_path), IDC_STDIN);\r
check_io(window, _T("stdout"), service->stdout_path, _countof(service->stdout_path), IDC_STDOUT);\r
check_io(window, _T("stderr"), service->stderr_path, _countof(service->stderr_path), IDC_STDERR);\r
+ if (SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TIMESTAMP, BM_GETCHECK, 0, 0) & BST_CHECKED) service->timestamp_log = true;\r
+ else service->timestamp_log = false;\r
\r
/* Override stdout and/or stderr. */\r
if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
tablist[NSSM_TAB_IO] = dialog(MAKEINTRESOURCE(IDD_IO), window, tab_dlg);\r
ShowWindow(tablist[NSSM_TAB_IO], SW_HIDE);\r
\r
+ /* Set defaults. */\r
+ SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TIMESTAMP, BM_SETCHECK, BST_UNCHECKED, 0);\r
+\r
/* Rotation tab. */\r
tab.pszText = message_string(NSSM_GUI_TAB_ROTATION);\r
tab.cchTextMax = (int) _tcslen(tab.pszText) + 1;\r
#define COMPLAINED_READ (1 << 0)\r
#define COMPLAINED_WRITE (1 << 1)\r
#define COMPLAINED_ROTATE (1 << 2)\r
+#define TIMESTAMP_FORMAT "%04u-%02u-%02u %02u:%02u:%02u.%03u: "\r
+#define TIMESTAMP_LEN 25\r
\r
static int dup_handle(HANDLE source_handle, HANDLE *dest_handle_ptr, TCHAR *source_description, TCHAR *dest_description, unsigned long flags) {\r
if (! dest_handle_ptr) return 1;\r
pipe_handle: stdout of application\r
write_handle: to file\r
*/\r
-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) {\r
+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) {\r
*tid_ptr = 0;\r
\r
/* Pipe between application's stdout/stderr and our logging handle. */\r
logger->write_handle = *write_handle_ptr;\r
logger->size = (__int64) size.QuadPart;\r
logger->tid_ptr = tid_ptr;\r
+ logger->timestamp_log = timestamp_log;\r
+ logger->line_length = 0;\r
logger->rotate_online = rotate_online;\r
logger->rotate_delay = rotate_delay;\r
logger->copy_and_truncate = copy_and_truncate;\r
\r
if (service->use_stdout_pipe) {\r
service->stdout_pipe = si->hStdOutput = 0;\r
- 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);\r
+ 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);\r
if (! service->stdout_thread) {\r
CloseHandle(service->stdout_pipe);\r
CloseHandle(service->stdout_si);\r
\r
if (service->use_stderr_pipe) {\r
service->stderr_pipe = si->hStdError = 0;\r
- 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);\r
+ 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);\r
if (! service->stderr_thread) {\r
CloseHandle(service->stderr_pipe);\r
CloseHandle(service->stderr_si);\r
return ret;\r
}\r
\r
+/* Note that the timestamp is created in UTF-8. */\r
+static inline int write_timestamp(logger_t *logger, unsigned long charsize, unsigned long *out, int *complained) {\r
+ char timestamp[TIMESTAMP_LEN + 1];\r
+\r
+ SYSTEMTIME now;\r
+ GetSystemTime(&now);\r
+ _snprintf_s(timestamp, _countof(timestamp), _TRUNCATE, TIMESTAMP_FORMAT, now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond, now.wMilliseconds);\r
+\r
+ if (charsize == sizeof(char)) return try_write(logger, (void *) timestamp, TIMESTAMP_LEN, out, complained);\r
+\r
+ wchar_t *utf16;\r
+ unsigned long utf16len;\r
+ if (to_utf16(timestamp, &utf16, &utf16len)) return -1;\r
+ int ret = try_write(logger, (void *) *utf16, utf16len * sizeof(wchar_t), out, complained);\r
+ HeapFree(GetProcessHeap(), 0, utf16);\r
+ return ret;\r
+}\r
+\r
+static int write_with_timestamp(logger_t *logger, void *address, unsigned long bufsize, unsigned long *out, int *complained, unsigned long charsize) {\r
+ if (logger->timestamp_log) {\r
+ unsigned long log_out;\r
+ int log_complained;\r
+ unsigned long timestamp_out = 0;\r
+ int timestamp_complained;\r
+ if (! logger->line_length) {\r
+ write_timestamp(logger, charsize, ×tamp_out, ×tamp_complained);\r
+ logger->line_length += (__int64) timestamp_out;\r
+ *out += timestamp_out;\r
+ *complained |= timestamp_complained;\r
+ }\r
+\r
+ unsigned long i;\r
+ void *line = address;\r
+ unsigned long offset = 0;\r
+ int ret;\r
+ for (i = 0; i < bufsize; i++) {\r
+ if (((char *) address)[i] == '\n') {\r
+ ret = try_write(logger, line, i - offset + 1, &log_out, &log_complained);\r
+ line = (void *) ((char *) line + i - offset + 1);\r
+ logger->line_length = 0LL;\r
+ *out += log_out;\r
+ *complained |= log_complained;\r
+ offset = i + 1;\r
+ if (offset < bufsize) {\r
+ write_timestamp(logger, charsize, ×tamp_out, ×tamp_complained);\r
+ logger->line_length += (__int64) timestamp_out;\r
+ *out += timestamp_out;\r
+ *complained |= timestamp_complained;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (offset < bufsize) {\r
+ ret = try_write(logger, line, bufsize - offset, &log_out, &log_complained);\r
+ *out += log_out;\r
+ *complained |= log_complained;\r
+ }\r
+\r
+ return ret;\r
+ }\r
+ else return try_write(logger, address, bufsize, out, complained);\r
+}\r
+\r
/* Wrapper to be called in a new thread for logging. */\r
unsigned long WINAPI log_and_rotate(void *arg) {\r
logger_t *logger = (logger_t *) arg;\r
}\r
}\r
\r
+ if (! size || logger->timestamp_log) if (! charsize) charsize = guess_charsize(address, in);\r
if (! size) {\r
/* Write a BOM to the new file. */\r
- if (! charsize) charsize = guess_charsize(address, in);\r
if (charsize == sizeof(wchar_t)) write_bom(logger, &out);\r
size += (__int64) out;\r
}\r
/* Write the data, if any. */\r
if (! in) continue;\r
\r
- ret = try_write(logger, address, in, &out, &complained);\r
+ ret = write_with_timestamp(logger, address, in, &out, &complained, charsize);\r
size += (__int64) out;\r
if (ret < 0) {\r
close_handle(&logger->read_handle);\r
if (service->stderr_copy_and_truncate) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE, 1);\r
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE);\r
}\r
+ if (service->timestamp_log) set_number(key, NSSM_REG_TIMESTAMP_LOG, 1);\r
+ else if (editing) RegDeleteValue(key, NSSM_REG_TIMESTAMP_LOG);\r
if (service->hook_share_output_handles) set_number(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, 1);\r
else if (editing) RegDeleteValue(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES);\r
if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);\r
else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
}\r
else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
+ /* Log timestamping requires a logging thread.*/\r
+ unsigned long timestamp_log;\r
+ if (get_number(key, NSSM_REG_TIMESTAMP_LOG, ×tamp_log, false) == 1) {\r
+ if (timestamp_log) service->timestamp_log = true;\r
+ else service->timestamp_log = false;\r
+ }\r
+ else service->timestamp_log = false;\r
+\r
/* Hook I/O sharing and online rotation need a pipe. */\r
- service->use_stdout_pipe = service->rotate_stdout_online || hook_share_output_handles;\r
- service->use_stderr_pipe = service->rotate_stderr_online || hook_share_output_handles;\r
+ service->use_stdout_pipe = service->rotate_stdout_online || service->timestamp_log || hook_share_output_handles;\r
+ service->use_stderr_pipe = service->rotate_stderr_online || service->timestamp_log || hook_share_output_handles;\r
if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
{ NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number, 0 },\r
{ NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number, 0 },\r
{ NSSM_REG_ROTATE_DELAY, REG_DWORD, (void *) NSSM_ROTATE_DELAY, false, 0, setting_set_number, setting_get_number, 0 },\r
+ { NSSM_REG_TIMESTAMP_LOG, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number, 0 },\r
{ NSSM_NATIVE_DEPENDONGROUP, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependongroup, native_get_dependongroup, native_dump_dependongroup },\r
{ NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice, native_dump_dependonservice },\r
{ NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description, 0 },\r