Bash - 在标准输入上接收来自管道的数据时刻的日志时间戳

问题描述 投票:0回答:1

我有一个应用程序,它会不时输出到标准输出 - 随机间隔,随机输出持续时间,然后再次保持沉默。其输出(二进制数据)被捕获到文件中。

我希望能够记录输出开始时刻的时间戳,但前提是应用程序保持输出至少 3 秒,并且如果至少 5 秒没有收到输出,还记录输出停止的时间停止后的秒数(如果收到,则将其视为前一周期的一部分)。

Start: (date +'%d.%m - %H:%M:%S')
End: (date +'%d.%m - %H:%M:%S')

此后输出必须透明地重定向到文件。

类似于

tee
,但带有时间戳记录器,例如:

app | monitor > out

monitor
可以输出到日志文件或stderr,没关系。

有没有办法使用 shell 上可用的常用工具来做到这一点?

我可以编写一个 Python 脚本,但我不想无缘无故地使任务过于复杂。

bash logging pipe stdout stdin
1个回答
0
投票

不久前,当我想记录通过管道的数据以获取直方图时,我写了一些东西。这是一个相当快的肮脏的 C 程序,但它对我有用(tm)。您可以将其放在管道中的某个位置,然后通过它的数据就不会改变地传递。 JSON 输出到 stderror,或使用 -o 开关输出到文件。

这是一个例子:

{ echo -n A; sleep 3; echo -n B; sleep 6; echo -n C; } | ./pipe_histo2 -z 2>&1 >/dev/null | jq .


{
  "measurements": [
     [ 0, 1 ],
     [ 2, 1 ],
     [ 8, 1 ]
  ],
  "min": 0,
  "max": 1,
  "avg": 0.333336,
  "total": 3,
  "start_time": "2024-06-28 23:31:34.338997847",
  "start_epoch": "1719610294",
  "end_time": "2024-06-28 23:31:43.338917382",
  "end_epoch": "1719610303",
  "elapsed": 8,
  "total_readable": "3B",
  "elapsed_readable": "08s"
}

这是 C 代码。将其复制到名为 pipeline_histo2.c 的文件中

/*

To compile: gcc -Wall -o pipe_histo2 pipe_histo2.c

Examples:

{ echo -n A; sleep 3; echo -n B; sleep 3; echo -n C; } | ./pipe_histo 2>&1 >/dev/null | jq .
ls -als /dev | ./pipe_histo2

*/



/***************************************************************************/
/** Includes                                                              **/

#include <stdio.h>
#include <stdlib.h>         // atexit

#include <sys/time.h>       // select
#include <sys/types.h>      // select
#include <unistd.h>         // select

#include <time.h>           // clock_gettime

#include <string.h>         // strlen

char *output_file = NULL;
FILE *output_fd;

unsigned int omit_zero_measurements = 0;
unsigned int omit_seconds = 0;
unsigned int only_output_json = 0;
unsigned int print_graph = 0;


void die(char *S)
{
printf("%s\n", S);
exit(1);
}


void Usage(char *Argv0)
{
printf("\n"
       "Usage:     <some process> | %s [options] [| <some process>]\n"
       "\n"
       " -o <output file>    Output JSON to a file\n"
       " -z                  Omit zero measurements. If value == 0, do not print\n"
       " -s                  Omit seconds. Only print values in the JSON\n"
       " -j                  Only print JSON. Does not output what has been read, only prints JSON\n"
       " -g                  Output an ASCII art graph\n"
       " -h                  This help text\n"
       "\n"
       "Example:   { echo -n A; sleep 3; echo -n B; sleep 6; echo -n C; } | %s -z 2>&1 >/dev/null | jq .\n"
       "\n"
       "           stdbuf -i0 -o0 -e0 find / -xdev | %s -o result.json | wc -l\n"
       "\n"
       ,Argv0
       ,Argv0
       ,Argv0
       );
exit(0);
}


void parse_arg(int argc, char *argv[])
{
int c;

while((c = getopt(argc, argv, "ho:zsjg")) != -1)
  switch(c)
      {
      case 'o':    output_file = optarg;
                   break;
      case 'z':    omit_zero_measurements = 1;
                   break;
      case 's':    omit_seconds = 1;
                   break;
      case 'j':    only_output_json = 1;
                   break;
      case 'g':    print_graph = 1;
                   break;
      case 'h':    Usage(argv[0]);
                   break;
      case '?':    die("getopt: unknown option");
      default:     die("getopt: default reached. Internal error");
      }

output_fd = stderr;

if(only_output_json)
  output_fd = stdout;

if(output_file)
  if((output_fd = fopen(output_file, "w")) == NULL)
    die("fopen");
}

static void timespec_diff(struct timespec *end, struct timespec *start, struct timespec *result)
{
if(end->tv_nsec < start->tv_nsec)
  {
  result->tv_sec  = end->tv_sec - start->tv_sec - 1;
  result->tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
  }
  else
  {
  result->tv_sec  = end->tv_sec  - start->tv_sec;
  result->tv_nsec = end->tv_nsec - start->tv_nsec;
  }
}



// https://stackoverflow.com/questions/8304259/formatting-struct-timespec/14746954

static char *timespec2str(struct timespec *TS)
{
static char   ret[] = "2012-12-31 12:59:59.123456789";   // Maximum size
struct tm     t;

tzset();
if(localtime_r(&(TS->tv_sec), &t) == NULL)
  die("localtime_r");

if(strftime(ret, strlen(ret), "%F %T", &t) == 0)
  die("strftime");

sprintf(&ret[strlen(ret)], ".%09ld", TS->tv_nsec);

return ret;
}


double timespec2double(struct timespec *TS)
{
double ret = TS->tv_nsec;

while(ret > 1)
  ret = ret / 10;

return ret + TS->tv_sec;
}



char *printReadableSize(size_t Size)
{
unsigned long long int scale = 1;
char *postfix = "BKMGTPEZY";
static char result[16];

for(; Size / (scale * 1024); scale = scale * 1024, postfix++);

if(Size % scale)
  sprintf(result, "%.02f%.1s", (float) Size / scale, postfix);
  else
  sprintf(result, "%u%.1s", (unsigned int) (Size / scale), postfix);

return result;
}



char *printReadableTime(unsigned long int Seconds)
{
static char       ret[22];
unsigned int      ret_len = 0;

if(Seconds > 31536000) // years
  {
  ret_len = ret_len + sprintf(&ret[ret_len], "%luy", Seconds / 31536000);
  Seconds = Seconds % 31536000;
  }

if(Seconds > 604800) // weeks
  {
  ret_len = ret_len + sprintf(&ret[ret_len], "%luw", Seconds / 604800);
  Seconds = Seconds % 604800;
  }

if(Seconds > 86400) // days
  {
  ret_len = ret_len + sprintf(&ret[ret_len], "%lud", Seconds / 86400);
  Seconds = Seconds % 86400;
  }

if(Seconds > 3600) // hours
  {
  ret_len = ret_len + sprintf(&ret[ret_len], "%luh", Seconds / 3600);
  Seconds = Seconds % 3600;
  }

if(Seconds > 60) // minutes
  {
  ret_len = ret_len + sprintf(&ret[ret_len], "%lum", Seconds / 60);
  Seconds = Seconds % 60;
  }

ret_len = ret_len + sprintf(&ret[ret_len], "%02lus", Seconds);

ret[ret_len] = '\0';
return ret;
}




int main(int argc, char *argv[])
{
char             buf[4096];
unsigned int     buf_len=0;

fd_set               read_set;
fd_set               error_set;
unsigned long int    t;

struct timespec   start;
struct timespec   end;
struct timespec   tmp;


size_t histo[604800] = { 0 };   // A weeks worth of seconds
unsigned long int histo_len = 0;

size_t value_min;
size_t value_max;
size_t value_total;

unsigned int first = 1;


parse_arg(argc, argv);



FD_ZERO(&read_set);
FD_SET(STDIN_FILENO, &read_set);
FD_ZERO(&error_set);
FD_SET(STDIN_FILENO, &error_set);


if(clock_gettime(CLOCK_REALTIME, &start) == -1)
  die("CLOCK_REALTIME");

while(1)
  {
  t = select(STDIN_FILENO+1, &read_set, NULL, &error_set, NULL);

  if(FD_ISSET(STDIN_FILENO, &error_set))
    die("error_set\n");

  switch(t)
    {
    case -1:  die("Getch_timeout: select returned -1\n");

    case  0:  die("0 - on blocking call\n");

    default:  buf_len = read(STDIN_FILENO, buf, 4096);
              switch(buf_len)
                {
                case -1:   die("read");
                case  0:   goto exit_copy;          // EOF
                }

              if(clock_gettime(CLOCK_REALTIME, &end) == -1)
                die("CLOCK_REALTIME");
              timespec_diff(&end, &start, &tmp);
              histo[tmp.tv_sec] += buf_len;
              histo_len = tmp.tv_sec;

              if(!only_output_json)
                {
                t = write(STDOUT_FILENO, buf, buf_len);
                if(t != buf_len)
                  die("write != read\n");
                }
              break;

    } // switch
  } // while

exit_copy:


if(clock_gettime(CLOCK_REALTIME, &end) == -1)
  die("CLOCK_REALTIME");

// Display results

fprintf(output_fd, "{\"measurements\":[");

value_min = histo[0];
value_max = 0;
value_total = 0;

for(t=0; t <= histo_len; t++)
  {
  if(!omit_zero_measurements || histo[t])
    {
    if(first)
      first = 0;
      else
      fprintf(output_fd, ",");
    if(omit_seconds)
      fprintf(output_fd, "%lu", histo[t]);
      else
      fprintf(output_fd, "[%lu,%lu]", t, histo[t]);
    }
  if(histo[t] < value_min)
    value_min = histo[t];
  if(histo[t] > value_max)
    value_max = histo[t];
  value_total += histo[t];
  }


timespec_diff(&end, &start, &tmp);

fprintf(output_fd, "],\"min\":%lu,\"max\":%lu,\"avg\":%f,\"total\":%lu", value_min, value_max, (double) value_total / timespec2double(&tmp), value_total);
fprintf(output_fd, ",\"start_time\":\"%s\",\"start_epoch\":\"%lu\"", timespec2str(&start), start.tv_sec);
fprintf(output_fd, ",\"end_time\":\"%s\",\"end_epoch\":\"%lu\"", timespec2str(&end), end.tv_sec);
fprintf(output_fd, ",\"elapsed\":%lu", tmp.tv_sec);
fprintf(output_fd, ",\"total_readable\":\"%s\",\"elapsed_readable\":\"%s\"", printReadableSize(value_total), printReadableTime(tmp.tv_sec));

if(print_graph)
  {
  fprintf(output_fd, ",\"graph\":\"xx TODO xx\"");
  }

fprintf(output_fd, "}\n");

fclose(output_fd);
return 0;
}

// EOF
© www.soinside.com 2019 - 2024. All rights reserved.