The big problem with trying to use a pipeline where your application's output is redirected to head is that when head exits after reading the requested amount of data, you'll get a (usually fatal) SIGPIPE the next time your app tries to write to said output - because there's nothing listening any more.
My idea is to whip up a small program that can be used instead of head in the pipeline, reading from its standard input and writing to the real log file until the requested amount of data has been transferred. At that point it continues to read, but just disposes of the input instead of continuing to add it to the real log. That way your application never gets a premature SIGPIPE.
The following Linux-specific C program will do that efficiently:
#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;
}
}
}
Example usage:
$ 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
Your usage might be something like
exec /usr/java/latest/bin/java "$JAVA_OPTS" -jar "$JAR_FILE" 2>&1 | ./logcopy 3 "/logs/$APP_NAME/startup.out" &