Linux non-blocking fifo (on demand logging)

前端 未结 10 2274
你的背包
你的背包 2020-11-29 19:20

I like to log a programs output \'on demand\'. Eg. the output is logged to the terminal, but another process can hook on the current output at any time.

The classic

相关标签:
10条回答
  • 2020-11-29 19:50

    BusyBox often used on embedded devices can create a ram buffered log by

    syslogd -C
    

    which can be filled by

    logger
    

    and read by

    logread
    

    Works quite well, but only provides one global log.

    0 讨论(0)
  • 2020-11-29 19:51

    The problem with the given fifo approach is that the whole thing will hang when the pipe buffer is getting filled up and no reading process is taking place.

    For the fifo approach to work I think you would have to implement a named pipe client-server model similar to the one mentioned in BASH: Best architecture for reading from two input streams (see slightly modified code below, sample code 2).

    For a workaround you could also use a while ... read construct instead of teeing stdout to a named pipe by implementing a counting mechanism inside the while ... read loop that will overwrite the log file periodically by a specified number of lines. This would prevent an ever growing log file (sample code 1).

    # sample code 1
    
    # terminal window 1
    rm -f /tmp/mylog
    touch /tmp/mylog
    while sleep 2; do date '+%Y-%m-%d_%H.%M.%S'; done 2>&1 | while IFS="" read -r line; do 
      lno=$((lno+1))
      #echo $lno
      array[${lno}]="${line}"
      if [[ $lno -eq 10 ]]; then
        lno=$((lno+1))
        array[${lno}]="-------------"
        printf '%s\n' "${array[@]}" > /tmp/mylog
        unset lno array
      fi
      printf '%s\n' "${line}"
    done
    
    # terminal window 2
    tail -f /tmp/mylog
    
    
    #------------------------
    
    
    # sample code 2
    
    # code taken from: 
    # https://stackoverflow.com/questions/6702474/bash-best-architecture-for-reading-from-two-input-streams
    # terminal window 1
    
    # server
    (
    rm -f /tmp/to /tmp/from
    mkfifo /tmp/to /tmp/from
    while true; do 
      while IFS="" read -r -d $'\n' line; do 
        printf '%s\n' "${line}"
      done </tmp/to >/tmp/from &
      bgpid=$!
      exec 3>/tmp/to
      exec 4</tmp/from
      trap "kill -TERM $bgpid; exit" 0 1 2 3 13 15
      wait "$bgpid"
      echo "restarting..."
    done
    ) &
    serverpid=$!
    #kill -TERM $serverpid
    
    # client
    (
    exec 3>/tmp/to;
    exec 4</tmp/from;
    while IFS="" read -r -d $'\n' <&4 line; do
      if [[ "${line:0:1}" == $'\177' ]]; then 
        printf 'line from stdin: %s\n' "${line:1}"  > /dev/null
      else       
        printf 'line from fifo: %s\n' "$line"       > /dev/null
      fi
    done &
    trap "kill -TERM $"'!; exit' 1 2 3 13 15
    while IFS="" read -r -d $'\n' line; do
      # can we make it atomic?
      # sleep 0.5
      # dd if=/tmp/to iflag=nonblock of=/dev/null  # flush fifo
      printf '\177%s\n' "${line}"
    done >&3
    ) &
    # kill -TERM $!
    
    
    # terminal window 2
    # tests
    echo hello > /tmp/to
    yes 1 | nl > /tmp/to
    yes 1 | nl | tee /tmp/to
    while sleep 2; do date '+%Y-%m-%d_%H.%M.%S'; done 2>&1 | tee -a /tmp/to
    
    
    # terminal window 3
    cat /tmp/to | head -n 10
    
    0 讨论(0)
  • 2020-11-29 19:54

    The logging could be directed to a UDP socket. Since UDP is connection-less, it won't block the sending program. Of course logs will be lost if the receiver or network can't keep up.

    myprogram 2>&1 | socat - udp-datagram:localhost:3333
    

    Then when you want to observe the logging:

    socat udp-recv:3333 -
    

    There are some other cool benefits like being able to attach multiple listeners at the same time or broadcast to multiple devices.

    0 讨论(0)
  • 2020-11-29 19:58

    To follow in Fabraxias foot steps I'm going to share my small modification of racic's code. In one of my use cases I needed to suppress the writes to STDOUT, so I've added another parameter: swallow_stdout. If that is not 0, then output to STDOUT will be turned off.

    Since I'm no C coder I've added comments while reading the code, maybe they are useful for others.

    /* ftee - clone stdin to stdout and to a named pipe 
    (c) racic@stackoverflow
    WTFPL Licence */
    
    // gcc /tmp/ftee.c -o /usr/local/bin/ftee
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <signal.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        int readfd, writefd;        // read & write file descriptors
        struct stat status;         // read file descriptor status
        char *fifonam;              // name of the pipe
        int swallow_stdout;         // 0 = write to STDOUT
        char buffer[BUFSIZ];        // read/write buffer
        ssize_t bytes;              // bytes read/written
    
        signal(SIGPIPE, SIG_IGN);   
    
        if(3!=argc)
        {
            printf("Usage:\n someprog 2>&1 | %s [FIFO] [swallow_stdout] \n" 
                "FIFO           - path to a named pipe (created beforehand with mkfifo), required argument\n"
                "swallow_stdout - 0 = output to PIPE and STDOUT, 1 = output to PIPE only, required argument\n", argv[0]);
            exit(EXIT_FAILURE);
        }
        fifonam = argv[1];
        swallow_stdout = atoi(argv[2]);
    
        readfd = open(fifonam, O_RDONLY | O_NONBLOCK);  // open read file descriptor in non-blocking mode
    
        if(-1==readfd)  // read descriptor error!
        {
            perror("ftee: readfd: open()");
            exit(EXIT_FAILURE);
        }
    
        if(-1==fstat(readfd, &status)) // read descriptor status error! (?)
        {
            perror("ftee: fstat");
            close(readfd);
            exit(EXIT_FAILURE);
        }
    
        if(!S_ISFIFO(status.st_mode)) // read descriptor is not a FIFO error!
        {
            printf("ftee: %s in not a fifo!\n", fifonam);
            close(readfd);
            exit(EXIT_FAILURE);
        }
    
        writefd = open(fifonam, O_WRONLY | O_NONBLOCK); // open write file descriptor non-blocking
        if(-1==writefd) // write file descriptor error!
        {
            perror("ftee: writefd: open()");
            close(readfd);
            exit(EXIT_FAILURE);
        }
    
        close(readfd); // reading complete, close read file descriptor
    
        while(1) // infinite loop
        {
            bytes = read(STDIN_FILENO, buffer, sizeof(buffer)); // read STDIN into buffer
            if (bytes < 0 && errno == EINTR)
                continue;   // skip over errors
    
            if (bytes <= 0) 
                break; // no more data coming in or uncaught error, let's quit since we can't write anything
    
            if (swallow_stdout == 0)
                bytes = write(STDOUT_FILENO, buffer, bytes); // write buffer to STDOUT
            if(-1==bytes) // write error!
                perror("ftee: writing to stdout");
            bytes = write(writefd, buffer, bytes); // write a copy of the buffer to the write file descriptor
            if(-1==bytes);// ignore errors
        }
        close(writefd); // close write file descriptor
        return(0); // return exit code 0
    }
    
    0 讨论(0)
  • 2020-11-29 20:03

    If you can install screen on the embedded device then you can run 'myprogram' in it and detach it, and reattach it anytime you want to see the log. Something like:

    $ screen -t sometitle myprogram
    Hit Ctrl+A, then d to detach it.
    

    Whenever you want to see the output, reattach it:

    $ screen -DR sometitle
    Hit Ctrl-A, then d to detach it again.
    

    This way you won't have to worry about the program output using disk space at all.

    0 讨论(0)
  • 2020-11-29 20:07

    Inspired by your question I've written a simple program that will let you do this:

    $ myprogram 2>&1 | ftee /tmp/mylog

    It behaves similarly to tee but clones the stdin to stdout and to a named pipe (a requirement for now) without blocking. This means that if you want to log this way it may happen that you're gonna lose your log data, but I guess it's acceptable in your scenario. The trick is to block SIGPIPE signal and to ignore error on writing to a broken fifo. This sample may be optimized in various ways of course, but so far, it does the job I guess.

    /* ftee - clone stdin to stdout and to a named pipe 
    (c) racic@stackoverflow
    WTFPL Licence */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <signal.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        int readfd, writefd;
        struct stat status;
        char *fifonam;
        char buffer[BUFSIZ];
        ssize_t bytes;
        
        signal(SIGPIPE, SIG_IGN);
    
        if(2!=argc)
        {
            printf("Usage:\n someprog 2>&1 | %s FIFO\n FIFO - path to a"
                " named pipe, required argument\n", argv[0]);
            exit(EXIT_FAILURE);
        }
        fifonam = argv[1];
    
        readfd = open(fifonam, O_RDONLY | O_NONBLOCK);
        if(-1==readfd)
        {
            perror("ftee: readfd: open()");
            exit(EXIT_FAILURE);
        }
    
        if(-1==fstat(readfd, &status))
        {
            perror("ftee: fstat");
            close(readfd);
            exit(EXIT_FAILURE);
        }
    
        if(!S_ISFIFO(status.st_mode))
        {
            printf("ftee: %s in not a fifo!\n", fifonam);
            close(readfd);
            exit(EXIT_FAILURE);
        }
    
        writefd = open(fifonam, O_WRONLY | O_NONBLOCK);
        if(-1==writefd)
        {
            perror("ftee: writefd: open()");
            close(readfd);
            exit(EXIT_FAILURE);
        }
    
        close(readfd);
    
        while(1)
        {
            bytes = read(STDIN_FILENO, buffer, sizeof(buffer));
            if (bytes < 0 && errno == EINTR)
                continue;
            if (bytes <= 0)
                break;
    
            bytes = write(STDOUT_FILENO, buffer, bytes);
            if(-1==bytes)
                perror("ftee: writing to stdout");
            bytes = write(writefd, buffer, bytes);
            if(-1==bytes);//Ignoring the errors
        }
        close(writefd); 
        return(0);
    }
    

    You can compile it with this standard command:

    $ gcc ftee.c -o ftee

    You can quickly verify it by running e.g.:

    $ ping www.google.com | ftee /tmp/mylog

    $ cat /tmp/mylog

    Also note - this is no multiplexer. You can only have one process doing $ cat /tmp/mylog at a time.

    0 讨论(0)
提交回复
热议问题