在我的虛擬機應用程式啟動腳本中,我有:
exec /usr/java/latest/bin/java $JAVA_OPTS -jar $JAR_FILE >> /logs/$APP_NAME/startup.out 2>&1 &
這會導致 startup.out 檔案的大小出現問題,因為它會重定向整個 stdout 和 stderr。我不想要它,因為我的應用程式每天都會創建一個日志檔案并截斷??它。這意味著我的 startup.out 檔案復制了應用程式中“啟動”階段之外的所有內容。
我只想將啟動日志(比如說 3M)重定向到我腳本中的指定檔案。我正在嘗試類似的東西:
exec /usr/java/latest/bin/java $JAVA_OPTS -jar $JAR_FILE 2>&1 | head -c3M >> /logs/$APP_NAME/startup.out &
但是head直到它有 3 兆位元組才會將日志重定向到檔案。
如何立即保存應用程式的前 3 兆位元組日志以歸檔?
uj5u.com熱心網友回復:
嘗試使用將應用程式的輸出重定向到的管道的最大問題head是,head在讀取請求的資料量后退出時,下次應用程式嘗試寫入所述輸出時,您將獲得(通常是致命的)SIGPIPE -因為沒有什么在聽了。
我的想法是創建一個可以在管道中使用而不是head在管道中使用的小程式,從其標準輸入讀取并寫入真實的日志檔案,直到傳輸了請求的資料量。此時它繼續讀取,但只是處理輸入而不是繼續將其添加到真實日志中。這樣,您的應用程式就永遠不會過早地獲得 SIGPIPE。
以下特定于 Linux 的 C 程式將有效地做到這一點:
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv) {
if (argc != 3) {
fprintf(stderr, "Usage: %s SIZE-IN-MB LOGFILE\n", argv[0]);
return EXIT_FAILURE;
}
// Make sure standard input is a pipe
struct stat sb;
if (fstat(STDIN_FILENO, &sb) < 0) {
fprintf(stderr, "Unable to stat standard input: %s\n", strerror(errno));
return EXIT_FAILURE;
}
if (!S_ISFIFO(sb.st_mode)) {
fprintf(stderr, "Standard input must be a pipe!\n");
return EXIT_FAILURE;
}
// Calculate how many bytes to log
size_t bytes = strtoul(argv[1], NULL, 10) * 1024 * 1024;
int null_fd = open("/dev/null", O_WRONLY);
if (null_fd < 0) {
fprintf(stderr, "Unable to open /dev/null: %s\n", strerror(errno));
return EXIT_FAILURE;
}
// The real log file
int log_fd = open(argv[2], O_WRONLY | O_CREAT, 0644);
if (log_fd < 0) {
fprintf(stderr, "Unable to open log file '%s': %s\b", argv[2], strerror(errno));
return EXIT_FAILURE;
}
if (lseek(log_fd, 0, SEEK_END) < 0) {
fprintf(stderr, "Unable to seek to end of log file: %s\n", strerror(errno));
return EXIT_FAILURE;
}
// Use splice(2) to efficiently move data from the stdin pipe to the log
while (bytes > 0) {
ssize_t transferred = splice(STDIN_FILENO, NULL, log_fd, NULL, bytes, SPLICE_F_MORE);
if (transferred < 0) {
fprintf(stderr, "Unable to copy data to log: %s\n", strerror(errno));
break;
} else if (transferred == 0) { // End of file
return 0;
} else {
bytes -= transferred;
}
}
close(log_fd); // Don't need this any more
// Now loop until the main app exits, moving data it writes to /dev/null
// to get rid of it.
while (1) {
ssize_t transferred = splice(STDIN_FILENO, NULL, null_fd, NULL, 4096, SPLICE_F_MORE);
if (transferred < 0) {
fprintf(stderr, "Unable to dump excess log data: %s\n", strerror(errno));
return EXIT_FAILURE;
} else if (transferred == 0) {
// The writer exited, so shall we.
return 0;
}
}
}
示例用法:
$ gcc -o logcopy -O -Wall -Wextra logcopy.c
# real.log will grow to around 1mb and stop no matter how long you let the
# below command run
$ yes | ./logcopy 1 real.log
您的用法可能類似于
exec /usr/java/latest/bin/java "$JAVA_OPTS" -jar "$JAR_FILE" 2>&1 | ./logcopy 3 "/logs/$APP_NAME/startup.out" &
uj5u.com熱心網友回復:
假設我們的程式是a.out.
粗略的想法是將其通過管道傳輸到head,然后通過管道傳輸到tee(使用-aif 附加到日志)。
例如,以下將寫入前 2 行:
./a.out | head -n 2 | tee startup.out
正如@Shawn 所指出的,這個簡單解決方案的問題是,當head終止管道中的所有先前步驟(包括a.out)時,將收到 aSIGPIPE并過早終止。
因此,一種解決方法是強制運行的 shellhead保持活動狀態,直到第一個行程終止。讓我們創建一個 FIFOcomplete.fifo并在我們終止時在那里寫入一個日期戳。子shell將head等待(阻止讀取)直到寫入日期戳。
mkfifo complete.fifo
(./a.out && date >complete.fifo) | \
(head -n 2 && exec 5<complete.fifo && read -u 5 completed-at) | \
tee startup.out
uj5u.com熱心網友回復:
exec stdbuf -oL /usr/java/latest/bin/java $JAVA_OPTS -jar $JAR_FILE 2>&1 |
{ head -c 3M >> /logs/$APP_NAME/startup.out; cat > /dev/null; }
有兩個問題需要克服:
當
stdout被重定向到檔案時glibc,從行緩沖切換到完全緩沖,輸出被緩沖并以 4KB 塊的形式寫出。這樣做是為了提高效率,但這意味著如果輸出生成緩慢,您將看不到列印的行。您可以使用
stdbuf從外部更改此行為。stdbuf -oL覆寫默認行為并強制行緩沖。完成后,
head您的程式將收到一個SIGPIPE指示管道已關閉的指示。通常這很有幫助。如果沒有人在讀取它們的輸出,它會告訴程式停止。但是,如果您希望程式在head退出后繼續運行,則需要忽略或阻止SIGPIPE.cat您可以通過呼叫after來保持管道運行head。cat將吸收多余的輸出,在head退出后保持管道運行。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/478751.html
