ecbff1
From 32244f8d21a3e06f6519c47234289da696f6b151 Mon Sep 17 00:00:00 2001
ecbff1
From: Lennart Poettering <lennart@poettering.net>
ecbff1
Date: Sun, 8 Oct 2017 09:05:59 +0200
ecbff1
Subject: [PATCH] journald: make maximum size of stream log lines configurable
ecbff1
 and bump it to 48K (#6838)
ecbff1
ecbff1
This adds a new setting LineMax= to journald.conf, and sets it by
ecbff1
default to 48K. When we convert stream-based stdout/stderr logging into
ecbff1
record-based log entries, read up to the specified amount of bytes
ecbff1
before forcing a line-break.
ecbff1
ecbff1
This also makes three related changes:
ecbff1
ecbff1
- When a NUL byte is read we'll not recognize this as alternative line
ecbff1
  break, instead of silently dropping everything after it. (see #4863)
ecbff1
ecbff1
- The reason for a line-break is now encoded in the log record, if it
ecbff1
  wasn't a plain newline. Specifically, we distuingish "nul",
ecbff1
  "line-max" and "eof", for line breaks due to NUL byte, due to the
ecbff1
  maximum line length as configured with LineMax= or due to end of
ecbff1
  stream. This data is stored in the new implicit _LINE_BREAK= field.
ecbff1
  It's not synthesized for plain \n line breaks.
ecbff1
ecbff1
- A randomized 128bit ID is assigned to each log stream.
ecbff1
ecbff1
With these three changes in place it's (mostly) possible to reconstruct
ecbff1
the original byte streams from log data, as (most) of the context of
ecbff1
the conversion from the byte stream to log records is saved now. (So,
ecbff1
the only bits we still drop are empty lines. Which might be something to
ecbff1
look into in a future change, and which is outside of the scope of this
ecbff1
work)
ecbff1
ecbff1
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=86465
ecbff1
See: #4863
ecbff1
Replaces: #4875
ecbff1
ecbff1
(cherry picked from commit ec20fe5ffb8a00469bab209fff6c069bb93c6db2)
ecbff1
ecbff1
Resolves: #1442262
ecbff1
ecbff1
[msekleta: I had to manually rewrite upstream commit, because git
ecbff1
did very poor job merging old and new code and identified a lot of merge
ecbff1
conflicts in a code that was totally unrelated.]
ecbff1
---
de8967
 man/journald.conf.xml            |  18 +++++
de8967
 man/systemd.journal-fields.xml   |  22 ++++++
ecbff1
 src/journal/journald-gperf.gperf |   1 +
de8967
 src/journal/journald-server.c    |  68 ++++++++++++++++++
ecbff1
 src/journal/journald-server.h    |   3 +
de8967
 src/journal/journald-stream.c    | 116 +++++++++++++++++++++++++------
ecbff1
 src/journal/journald.conf        |   1 +
ecbff1
 7 files changed, 209 insertions(+), 20 deletions(-)
ecbff1
ecbff1
diff --git a/man/journald.conf.xml b/man/journald.conf.xml
ecbff1
index 46a498b67..e2d6a1225 100644
ecbff1
--- a/man/journald.conf.xml
ecbff1
+++ b/man/journald.conf.xml
ecbff1
@@ -354,6 +354,24 @@
ecbff1
         <filename>/dev/console</filename>.</para></listitem>
ecbff1
       </varlistentry>
ecbff1
 
ecbff1
+      <varlistentry>
ecbff1
+        <term><varname>LineMax=</varname></term>
ecbff1
+
ecbff1
+        <listitem><para>The maximum line length to permit when converting stream logs into record logs. When a systemd
ecbff1
+        unit's standard output/error are connected to the journal via a stream socket, the data read is split into
ecbff1
+        individual log records at newline (<literal>\n</literal>, ASCII 10) and NUL characters. If no such delimiter is
ecbff1
+        read for the specified number of bytes a hard log record boundary is artifically inserted, breaking up overly
ecbff1
+        long lines into multiple log records. Selecting overly large values increases the possible memory usage of the
ecbff1
+        Journal daemon for each stream client, as in the worst case the journal daemon needs to buffer the specified
ecbff1
+        number of bytes in memory before it can flush a new log record to disk. Also note that permitting overly large
ecbff1
+        line maximum line lengths affects compatibility with traditional log protocols as log records might not fit
ecbff1
+        anymore into a single <constant>AF_UNIX</constant> or <constant>AF_INET</constant> datagram. Takes a size in
ecbff1
+        bytes. If the value is suffixed with K, M, G or T, the specified size is parsed as Kilobytes, Megabytes,
ecbff1
+        Gigabytes, or Terabytes (with the base 1024), respectively. Defaults to 48K, which is relatively large but
ecbff1
+        still small enough so that log records likely fit into network datagrams along with extra room for
ecbff1
+        metadata. Note that values below 79 are not accepted and will be bumped to 79.</para></listitem>
ecbff1
+      </varlistentry>
ecbff1
+
ecbff1
     </variablelist>
ecbff1
 
ecbff1
   </refsect1>
ecbff1
diff --git a/man/systemd.journal-fields.xml b/man/systemd.journal-fields.xml
ecbff1
index 7d6c5c715..a53f8def2 100644
ecbff1
--- a/man/systemd.journal-fields.xml
ecbff1
+++ b/man/systemd.journal-fields.xml
ecbff1
@@ -311,6 +311,28 @@
ecbff1
           </variablelist>
ecbff1
         </listitem>
ecbff1
       </varlistentry>
ecbff1
+      <varlistentry>
ecbff1
+        <term><varname>_STREAM_ID=</varname></term>
ecbff1
+        <listitem>
ecbff1
+          <para>Only applies to <literal>_TRANSPORT=stream</literal> records: specifies a randomized 128bit ID assigned
ecbff1
+          to the stream connection when it was first created. This ID is useful to reconstruct individual log streams
ecbff1
+          from the log records: all log records carrying the same stream ID originate from the same stream.</para>
ecbff1
+        </listitem>
ecbff1
+      </varlistentry>
ecbff1
+      <varlistentry>
ecbff1
+        <term><varname>_LINE_BREAK=</varname></term>
ecbff1
+        <listitem>
ecbff1
+          <para>Only applies to <literal>_TRANSPORT=stream</literal> records: indicates that the log message in the
ecbff1
+          standard output/error stream was not terminated with a normal newline character (<literal>\n</literal>,
ecbff1
+          i.e. ASCII 10). Specifically, when set this field is one of <option>nul</option> (in case the line was
ecbff1
+          terminated by a NUL byte), <option>line-max</option> (in case the maximum log line length was reached, as
ecbff1
+          configured with <varname>LineMax=</varname> in
ecbff1
+          <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>) or
ecbff1
+          <option>eof</option> (if this was the last log record of a stream and the stream ended without a final
ecbff1
+          newline character). Note that this record is not generated when a normal newline character was used for
ecbff1
+          marking the log line end.</para>
ecbff1
+        </listitem>
ecbff1
+      </varlistentry>
ecbff1
     </variablelist>
ecbff1
   </refsect1>
ecbff1
 
ecbff1
diff --git a/src/journal/journald-gperf.gperf b/src/journal/journald-gperf.gperf
ecbff1
index 74554c1c3..73327c10e 100644
ecbff1
--- a/src/journal/journald-gperf.gperf
ecbff1
+++ b/src/journal/journald-gperf.gperf
ecbff1
@@ -40,3 +40,4 @@ Journal.MaxLevelKMsg,       config_parse_log_level,  0, offsetof(Server, max_lev
ecbff1
 Journal.MaxLevelConsole,    config_parse_log_level,  0, offsetof(Server, max_level_console)
ecbff1
 Journal.MaxLevelWall,       config_parse_log_level,  0, offsetof(Server, max_level_wall)
ecbff1
 Journal.SplitMode,          config_parse_split_mode, 0, offsetof(Server, split_mode)
ecbff1
+Journal.LineMax,            config_parse_line_max,   0, offsetof(Server, line_max)
ecbff1
\ No newline at end of file
ecbff1
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
ecbff1
index f6f8c30eb..daeecd519 100644
ecbff1
--- a/src/journal/journald-server.c
ecbff1
+++ b/src/journal/journald-server.c
ecbff1
@@ -67,6 +67,10 @@
ecbff1
 
ecbff1
 #define RECHECK_AVAILABLE_SPACE_USEC (30*USEC_PER_SEC)
ecbff1
 
ecbff1
+/* Pick a good default that is likely to fit into AF_UNIX and AF_INET SOCK_DGRAM datagrams, and even leaves some room
ecbff1
++ * for a bit of additional metadata. */
ecbff1
+#define DEFAULT_LINE_MAX (48*1024)
ecbff1
+
ecbff1
 static const char* const storage_table[_STORAGE_MAX] = {
ecbff1
         [STORAGE_AUTO] = "auto",
ecbff1
         [STORAGE_VOLATILE] = "volatile",
ecbff1
@@ -83,9 +87,71 @@ static const char* const split_mode_table[_SPLIT_MAX] = {
ecbff1
         [SPLIT_NONE] = "none",
ecbff1
 };
ecbff1
 
ecbff1
+
ecbff1
 DEFINE_STRING_TABLE_LOOKUP(split_mode, SplitMode);
ecbff1
 DEFINE_CONFIG_PARSE_ENUM(config_parse_split_mode, split_mode, SplitMode, "Failed to parse split mode setting");
ecbff1
 
ecbff1
+int config_parse_line_max(
ecbff1
+                const char* unit,
ecbff1
+                const char *filename,
ecbff1
+                unsigned line,
ecbff1
+                const char *section,
ecbff1
+                unsigned section_line,
ecbff1
+                const char *lvalue,
ecbff1
+                int ltype,
ecbff1
+                const char *rvalue,
ecbff1
+                void *data,
ecbff1
+                void *userdata) {
ecbff1
+
ecbff1
+        size_t *sz = data;
ecbff1
+        int r;
ecbff1
+
ecbff1
+        assert(filename);
ecbff1
+        assert(lvalue);
ecbff1
+        assert(rvalue);
ecbff1
+        assert(data);
ecbff1
+
ecbff1
+        if (isempty(rvalue))
ecbff1
+                /* Empty assignment means default */
ecbff1
+                *sz = DEFAULT_LINE_MAX;
ecbff1
+        else {
ecbff1
+                uint64_t v;
ecbff1
+                off_t u;
ecbff1
+
ecbff1
+                r = parse_size(rvalue, 1024, &u);
ecbff1
+                if (r < 0) {
ecbff1
+                        log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse LineMax= value, ignoring: %s", rvalue);
ecbff1
+                        return 0;
ecbff1
+                }
ecbff1
+
ecbff1
+                /* Backport note */
ecbff1
+                /* Upstream ditched use of off_t however our parse_size implementation still takes off_t*
ecbff1
+                 * as an argument. Since we compile with -Werror, we have two choices, either disable sign-compare
ecbff1
+                 * warning or do this casting so we don't have to change rest of the code. I think it is
ecbff1
+                 * better to do cast here instead of rewriting the code so it deals with off_t instead of
ecbff1
+                 * uint64_t. Doing conversion off_t -> uint64_t is something that we should think about. */
ecbff1
+                v = (uint64_t) u;
ecbff1
+
ecbff1
+                if (v < 79) {
ecbff1
+                        /* Why specify 79 here as minimum line length? Simply, because the most common traditional
ecbff1
+                         * terminal size is 80ch, and it might make sense to break one character before the natural
ecbff1
+                         * line break would occur on that. */
ecbff1
+                        log_syntax(unit, LOG_WARNING, filename, line, 0, "LineMax= too small, clamping to 79: %s", rvalue);
ecbff1
+                        *sz = 79;
ecbff1
+                } else if (v > (uint64_t) (SSIZE_MAX-1)) {
ecbff1
+                        /* So, why specify SSIZE_MAX-1 here? Because that's one below the largest size value read()
ecbff1
+                         * can return, and we need one extra byte for the trailing NUL byte. Of course IRL such large
ecbff1
+                         * memory allocations will fail anyway, hence this limit is mostly theoretical anyway, as we'll
ecbff1
+                         * fail much earlier anyway. */
ecbff1
+                        log_syntax(unit, LOG_WARNING, filename, line, 0, "LineMax= too large, clamping to %" PRIu64 ": %s", (uint64_t) (SSIZE_MAX-1), rvalue);
ecbff1
+                        *sz = SSIZE_MAX-1;
ecbff1
+                } else
ecbff1
+                        *sz = (size_t) v;
ecbff1
+        }
ecbff1
+
ecbff1
+        return 0;
ecbff1
+}
ecbff1
+
ecbff1
 static uint64_t available_space(Server *s, bool verbose) {
ecbff1
         char ids[33];
ecbff1
         _cleanup_free_ char *p = NULL;
ecbff1
@@ -1518,6 +1584,8 @@ int server_init(Server *s) {
ecbff1
         s->max_level_console = LOG_INFO;
ecbff1
         s->max_level_wall = LOG_EMERG;
ecbff1
 
ecbff1
+        s->line_max = DEFAULT_LINE_MAX;
ecbff1
+
ecbff1
         memset(&s->system_metrics, 0xFF, sizeof(s->system_metrics));
ecbff1
         memset(&s->runtime_metrics, 0xFF, sizeof(s->runtime_metrics));
ecbff1
 
ecbff1
diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h
ecbff1
index 7a456c2d5..b29410778 100644
ecbff1
--- a/src/journal/journald-server.h
ecbff1
+++ b/src/journal/journald-server.h
ecbff1
@@ -143,6 +143,8 @@ typedef struct Server {
ecbff1
 
ecbff1
         /* Cached cgroup root, so that we don't have to query that all the time */
ecbff1
         char *cgroup_root;
ecbff1
+
ecbff1
+        size_t line_max;
ecbff1
 } Server;
ecbff1
 
ecbff1
 #define N_IOVEC_META_FIELDS 20
ecbff1
@@ -157,6 +159,7 @@ void server_driver_message(Server *s, sd_id128_t message_id, const char *format,
ecbff1
 const struct ConfigPerfItem* journald_gperf_lookup(const char *key, unsigned length);
ecbff1
 
ecbff1
 int config_parse_storage(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
ecbff1
+int config_parse_line_max(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
ecbff1
 
ecbff1
 const char *storage_to_string(Storage s) _const_;
ecbff1
 Storage storage_from_string(const char *s) _pure_;
ecbff1
diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c
ecbff1
index 15c9110c0..4d6b7ad18 100644
ecbff1
--- a/src/journal/journald-stream.c
ecbff1
+++ b/src/journal/journald-stream.c
ecbff1
@@ -53,6 +53,16 @@ typedef enum StdoutStreamState {
ecbff1
         STDOUT_STREAM_RUNNING
ecbff1
 } StdoutStreamState;
ecbff1
 
ecbff1
+/* The different types of log record terminators: a real \n was read, a NUL character was read, the maximum line length
ecbff1
+ * was reached, or the end of the stream was reached */
ecbff1
+
ecbff1
+typedef enum LineBreak {
ecbff1
+        LINE_BREAK_NEWLINE,
ecbff1
+        LINE_BREAK_NUL,
ecbff1
+        LINE_BREAK_LINE_MAX,
ecbff1
+        LINE_BREAK_EOF,
ecbff1
+} LineBreak;
ecbff1
+
ecbff1
 struct StdoutStream {
ecbff1
         Server *server;
ecbff1
         StdoutStreamState state;
ecbff1
@@ -71,14 +81,17 @@ struct StdoutStream {
ecbff1
 
ecbff1
         bool fdstore:1;
ecbff1
 
ecbff1
-        char buffer[LINE_MAX+1];
ecbff1
+        char *buffer;
ecbff1
         size_t length;
ecbff1
+        size_t allocated;
ecbff1
 
ecbff1
         sd_event_source *event_source;
ecbff1
 
ecbff1
         char *state_file;
ecbff1
 
ecbff1
         LIST_FIELDS(StdoutStream, stdout_stream);
ecbff1
+
ecbff1
+        char id_field[sizeof("_STREAM_ID=")-1 + SD_ID128_STRING_MAX];
ecbff1
 };
ecbff1
 
ecbff1
 void stdout_stream_free(StdoutStream *s) {
ecbff1
@@ -101,6 +114,7 @@ void stdout_stream_free(StdoutStream *s) {
ecbff1
         free(s->identifier);
ecbff1
         free(s->unit_id);
ecbff1
         free(s->state_file);
ecbff1
+        free(s->buffer);
ecbff1
 
ecbff1
         free(s);
ecbff1
 }
ecbff1
@@ -151,12 +165,14 @@ static int stdout_stream_save(StdoutStream *s) {
ecbff1
                 "LEVEL_PREFIX=%i\n"
ecbff1
                 "FORWARD_TO_SYSLOG=%i\n"
ecbff1
                 "FORWARD_TO_KMSG=%i\n"
ecbff1
-                "FORWARD_TO_CONSOLE=%i\n",
ecbff1
+                "FORWARD_TO_CONSOLE=%i\n"
ecbff1
+                "STREAM_ID=%s\n",
ecbff1
                 s->priority,
ecbff1
                 s->level_prefix,
ecbff1
                 s->forward_to_syslog,
ecbff1
                 s->forward_to_kmsg,
ecbff1
-                s->forward_to_console);
ecbff1
+                s->forward_to_console,
ecbff1
+                s->id_field + strlen("_STREAM_ID="));
ecbff1
 
ecbff1
         if (!isempty(s->identifier)) {
ecbff1
                 _cleanup_free_ char *escaped;
ecbff1
@@ -211,8 +227,8 @@ finish:
ecbff1
         return r;
ecbff1
 }
ecbff1
 
ecbff1
-static int stdout_stream_log(StdoutStream *s, const char *p) {
ecbff1
-        struct iovec iovec[N_IOVEC_META_FIELDS + 5];
ecbff1
+static int stdout_stream_log(StdoutStream *s, const char *p, LineBreak line_break) {
ecbff1
+        struct iovec iovec[N_IOVEC_META_FIELDS + 7];
ecbff1
         int priority;
ecbff1
         char syslog_priority[] = "PRIORITY=\0";
ecbff1
         char syslog_facility[sizeof("SYSLOG_FACILITY=")-1 + DECIMAL_STR_MAX(int) + 1];
ecbff1
@@ -245,6 +261,8 @@ static int stdout_stream_log(StdoutStream *s, const char *p) {
ecbff1
 
ecbff1
         IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout");
ecbff1
 
ecbff1
+        IOVEC_SET_STRING(iovec[n++], s->id_field);
ecbff1
+
ecbff1
         syslog_priority[strlen("PRIORITY=")] = '0' + LOG_PRI(priority);
ecbff1
         IOVEC_SET_STRING(iovec[n++], syslog_priority);
ecbff1
 
ecbff1
@@ -259,6 +277,18 @@ static int stdout_stream_log(StdoutStream *s, const char *p) {
ecbff1
                         IOVEC_SET_STRING(iovec[n++], syslog_identifier);
ecbff1
         }
ecbff1
 
ecbff1
+        if (line_break != LINE_BREAK_NEWLINE) {
ecbff1
+                const char *c;
ecbff1
+
ecbff1
+                /* If this log message was generated due to an uncommon line break then mention this in the log
ecbff1
+                 * entry */
ecbff1
+
ecbff1
+                c =     line_break == LINE_BREAK_NUL ?      "_LINE_BREAK=nul" :
ecbff1
+                        line_break == LINE_BREAK_LINE_MAX ? "_LINE_BREAK=line-max" :
ecbff1
+                                                            "_LINE_BREAK=eof";
ecbff1
+                IOVEC_SET_STRING(iovec[n++], c);
ecbff1
+        }
ecbff1
+
ecbff1
         message = strappend("MESSAGE=", p);
ecbff1
         if (message)
ecbff1
                 IOVEC_SET_STRING(iovec[n++], message);
ecbff1
@@ -268,12 +298,18 @@ static int stdout_stream_log(StdoutStream *s, const char *p) {
ecbff1
         return 0;
ecbff1
 }
ecbff1
 
ecbff1
-static int stdout_stream_line(StdoutStream *s, char *p) {
ecbff1
+static int stdout_stream_line(StdoutStream *s, char *p, LineBreak line_break) {
ecbff1
         int r;
ecbff1
 
ecbff1
         assert(s);
ecbff1
         assert(p);
ecbff1
 
ecbff1
+        /* line breaks by NUL, line max length or EOF are not permissible during the negotiation part of the protocol */
ecbff1
+        if (line_break != LINE_BREAK_NEWLINE && s->state != STDOUT_STREAM_RUNNING) {
ecbff1
+                log_warning("Control protocol line not properly terminated.");
ecbff1
+                return -EINVAL;
ecbff1
+        }
ecbff1
+
ecbff1
         p = strstrip(p);
ecbff1
 
ecbff1
         switch (s->state) {
ecbff1
@@ -362,7 +398,7 @@ static int stdout_stream_line(StdoutStream *s, char *p) {
ecbff1
                 return 0;
ecbff1
 
ecbff1
         case STDOUT_STREAM_RUNNING:
ecbff1
-                return stdout_stream_log(s, p);
ecbff1
+                return stdout_stream_log(s, p, line_break);
ecbff1
         }
ecbff1
 
ecbff1
         assert_not_reached("Unknown stream state");
ecbff1
@@ -378,21 +414,32 @@ static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
ecbff1
         p = s->buffer;
ecbff1
         remaining = s->length;
ecbff1
         for (;;) {
ecbff1
-                char *end;
ecbff1
+                LineBreak line_break;
ecbff1
                 size_t skip;
ecbff1
 
ecbff1
-                end = memchr(p, '\n', remaining);
ecbff1
-                if (end)
ecbff1
-                        skip = end - p + 1;
ecbff1
-                else if (remaining >= sizeof(s->buffer) - 1) {
ecbff1
-                        end = p + sizeof(s->buffer) - 1;
ecbff1
+                char *end1, *end2;
ecbff1
+
ecbff1
+                end1 = memchr(p, '\n', remaining);
ecbff1
+                end2 = memchr(p, 0, end1 ? (size_t) (end1 - p) : remaining);
ecbff1
+
ecbff1
+                if (end2) {
ecbff1
+                        /* We found a NUL terminator */
ecbff1
+                        skip = end2 - p + 1;
ecbff1
+                        line_break = LINE_BREAK_NUL;
ecbff1
+                } else if (end1) {
ecbff1
+                        /* We found a \n terminator */
ecbff1
+                        *end1 = 0;
ecbff1
+                        skip = end1 - p + 1;
ecbff1
+                        line_break = LINE_BREAK_NEWLINE;
ecbff1
+                } else if (remaining >= s->server->line_max) {
ecbff1
+                        /* Force a line break after the maximum line length */
ecbff1
+                        *(p + s->server->line_max) = 0;
ecbff1
                         skip = remaining;
ecbff1
+                        line_break = LINE_BREAK_LINE_MAX;
ecbff1
                 } else
ecbff1
                         break;
ecbff1
 
ecbff1
-                *end = 0;
ecbff1
-
ecbff1
-                r = stdout_stream_line(s, p);
ecbff1
+                r = stdout_stream_line(s, p, line_break);
ecbff1
                 if (r < 0)
ecbff1
                         return r;
ecbff1
 
ecbff1
@@ -402,7 +449,7 @@ static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
ecbff1
 
ecbff1
         if (force_flush && remaining > 0) {
ecbff1
                 p[remaining] = 0;
ecbff1
-                r = stdout_stream_line(s, p);
ecbff1
+                r = stdout_stream_line(s, p, LINE_BREAK_EOF);
ecbff1
                 if (r < 0)
ecbff1
                         return r;
ecbff1
 
ecbff1
@@ -420,6 +467,7 @@ static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
ecbff1
 
ecbff1
 static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
ecbff1
         StdoutStream *s = userdata;
ecbff1
+        size_t limit;
ecbff1
         ssize_t l;
ecbff1
         int r;
ecbff1
 
ecbff1
@@ -430,9 +478,20 @@ static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents,
ecbff1
                 goto terminate;
ecbff1
         }
ecbff1
 
ecbff1
-        l = read(s->fd, s->buffer+s->length, sizeof(s->buffer)-1-s->length);
ecbff1
-        if (l < 0) {
ecbff1
+        /* If the buffer is full already (discounting the extra NUL we need), add room for another 1K */
ecbff1
+        if (s->length + 1 >= s->allocated) {
ecbff1
+                if (!GREEDY_REALLOC(s->buffer, s->allocated, s->length + 1 + 1024)) {
ecbff1
+                        log_oom();
ecbff1
+                        goto terminate;
ecbff1
+                }
ecbff1
+        }
ecbff1
+
ecbff1
+        /* Try to make use of the allocated buffer in full, but never read more than the configured line size. Also,
ecbff1
+         * always leave room for a terminating NUL we might need to add. */
ecbff1
+        limit = MIN(s->allocated - 1, s->server->line_max);
ecbff1
 
ecbff1
+        l = read(s->fd, s->buffer + s->length, limit - s->length);
ecbff1
+        if (l < 0) {
ecbff1
                 if (errno == EAGAIN)
ecbff1
                         return 0;
ecbff1
 
ecbff1
@@ -459,11 +518,16 @@ terminate:
ecbff1
 
ecbff1
 static int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
ecbff1
         _cleanup_(stdout_stream_freep) StdoutStream *stream = NULL;
ecbff1
+        sd_id128_t id;
ecbff1
         int r;
ecbff1
 
ecbff1
         assert(s);
ecbff1
         assert(fd >= 0);
ecbff1
 
ecbff1
+        r = sd_id128_randomize(&id;;
ecbff1
+        if (r < 0)
ecbff1
+                return log_error_errno(r, "Failed to generate stream ID: %m");
ecbff1
+
ecbff1
         stream = new0(StdoutStream, 1);
ecbff1
         if (!stream)
ecbff1
                 return log_oom();
ecbff1
@@ -471,6 +535,8 @@ static int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
ecbff1
         stream->fd = -1;
ecbff1
         stream->priority = LOG_INFO;
ecbff1
 
ecbff1
+        xsprintf(stream->id_field, "_STREAM_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
ecbff1
+
ecbff1
         r = getpeercred(fd, &stream->ucred);
ecbff1
         if (r < 0)
ecbff1
                 return log_error_errno(r, "Failed to determine peer credentials: %m");
ecbff1
@@ -545,7 +611,8 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) {
ecbff1
                 *level_prefix = NULL,
ecbff1
                 *forward_to_syslog = NULL,
ecbff1
                 *forward_to_kmsg = NULL,
ecbff1
-                *forward_to_console = NULL;
ecbff1
+                *forward_to_console = NULL,
ecbff1
+                *stream_id = NULL;
ecbff1
         int r;
ecbff1
 
ecbff1
         assert(stream);
ecbff1
@@ -565,6 +632,7 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) {
ecbff1
                            "FORWARD_TO_CONSOLE", &forward_to_console,
ecbff1
                            "IDENTIFIER", &stream->identifier,
ecbff1
                            "UNIT", &stream->unit_id,
ecbff1
+                           "STREAM_ID", &stream_id,
ecbff1
                            NULL);
ecbff1
         if (r < 0)
ecbff1
                 return log_error_errno(r, "Failed to read: %s", stream->state_file);
ecbff1
@@ -601,6 +669,14 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) {
ecbff1
                         stream->forward_to_console = r;
ecbff1
         }
ecbff1
 
ecbff1
+        if (stream_id) {
ecbff1
+                sd_id128_t id;
ecbff1
+
ecbff1
+                r = sd_id128_from_string(stream_id, &id;;
ecbff1
+                if (r >= 0)
ecbff1
+                        xsprintf(stream->id_field, "_STREAM_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
ecbff1
+        }
ecbff1
+
ecbff1
         return 0;
ecbff1
 }
ecbff1
 
ecbff1
diff --git a/src/journal/journald.conf b/src/journal/journald.conf
ecbff1
index 3907dfb7f..5355ec2b2 100644
ecbff1
--- a/src/journal/journald.conf
ecbff1
+++ b/src/journal/journald.conf
ecbff1
@@ -37,3 +37,4 @@
ecbff1
 #MaxLevelKMsg=notice
ecbff1
 #MaxLevelConsole=info
ecbff1
 #MaxLevelWall=emerg
ecbff1
+#LineMax=48K