Browse Source

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.
Iain Patterson 5 years ago
parent
commit
cc956aacde
9 changed files with 111 additions and 8 deletions
  1. 16 0
      README.txt
  2. 6 0
      gui.cpp
  3. 72 5
      io.cpp
  4. BIN
      nssm.rc
  5. 12 2
      registry.cpp
  6. 1 0
      registry.h
  7. 2 1
      resource.h
  8. 1 0
      service.h
  9. 1 0
      settings.cpp

+ 16 - 0
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
 -------

+ 6 - 0
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;

+ 72 - 5
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, &timestamp_out, &timestamp_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, &timestamp_out, &timestamp_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);

BIN
nssm.rc


+ 12 - 2
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, &timestamp_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;

+ 1 - 0
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")

+ 2 - 1
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

+ 1 - 0
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;

+ 1 - 0
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 },