@@ -24,9 +24,11 @@ static inline int sd_journal_sendv(G_GNUC_UNUSED const struct iovec *iov, G_GNUC
24
24
/* Different types of container logging */
25
25
static gboolean use_journald_logging = FALSE;
26
26
static gboolean use_k8s_logging = FALSE;
27
+ static gboolean use_k8s_stream_logging = FALSE;
27
28
28
29
/* Value the user must input for each log driver */
29
30
static const char * const K8S_FILE_STRING = "k8s-file" ;
31
+ static const char * const K8S_STREAM_FILE_STRING = "k8s-stream-file" ;
30
32
static const char * const JOURNALD_FILE_STRING = "journald" ;
31
33
32
34
/* Max log size for any log file types */
@@ -64,6 +66,7 @@ static void parse_log_path(char *log_config);
64
66
static const char * stdpipe_name (stdpipe_t pipe );
65
67
static int write_journald (int pipe , char * buf , ssize_t num_read );
66
68
static int write_k8s_log (stdpipe_t pipe , const char * buf , ssize_t buflen );
69
+ static int write_k8s_stream_log (stdpipe_t pipe , const char * buf , ssize_t buflen );
67
70
static bool get_line_len (ptrdiff_t * line_len , const char * buf , ssize_t buflen );
68
71
static ssize_t writev_buffer_append_segment (int fd , writev_buffer_t * buf , const void * data , ssize_t len );
69
72
static ssize_t writev_buffer_flush (int fd , writev_buffer_t * buf );
@@ -134,9 +137,10 @@ void configure_log_drivers(gchar **log_drivers, int64_t log_size_max_, char *cuu
134
137
* parse_log_path branches on log driver type the user inputted.
135
138
* log_config will either be a ':' delimited string containing:
136
139
* <DRIVER_NAME>:<PATH_NAME> or <PATH_NAME>
137
- * in the case of no colon, the driver will be kubernetes-log-file,
140
+ * in the case the log driver is 'k8s-stream-file', the <PATH_NAME> must be present.
141
+ * in the case of no colon, the driver will be k8s-file,
138
142
* in the case the log driver is 'journald', the <PATH_NAME> is ignored.
139
- * exits with error if <DRIVER_NAME> isn't 'journald' or 'kubernetes-log -file'
143
+ * exits with error if <DRIVER_NAME> isn't 'journald', 'k8s-file', or 'k8s-stream -file'.
140
144
*/
141
145
static void parse_log_path (char * log_config )
142
146
{
@@ -165,6 +169,17 @@ static void parse_log_path(char *log_config)
165
169
return ;
166
170
}
167
171
172
+ // Driver is k8s-file, k8s-stream-file, or empty
173
+ if (!strcmp (driver , K8S_STREAM_FILE_STRING )) {
174
+ if (path == NULL ) {
175
+ nexitf ("k8s-stream-file requires a filename" );
176
+ }
177
+ use_k8s_logging = TRUE;
178
+ use_k8s_stream_logging = TRUE;
179
+ k8s_log_path = path ;
180
+ return ;
181
+ }
182
+
168
183
// Driver is k8s-file or empty
169
184
if (!strcmp (driver , K8S_FILE_STRING )) {
170
185
if (path == NULL ) {
@@ -188,9 +203,18 @@ static void parse_log_path(char *log_config)
188
203
/* write container output to all logs the user defined */
189
204
bool write_to_logs (stdpipe_t pipe , char * buf , ssize_t num_read )
190
205
{
191
- if (use_k8s_logging && write_k8s_log (pipe , buf , num_read ) < 0 ) {
192
- nwarn ("write_k8s_log failed" );
193
- return G_SOURCE_CONTINUE ;
206
+ if (use_k8s_logging ) {
207
+ if (use_k8s_stream_logging ) {
208
+ if (write_k8s_log (pipe , buf , num_read ) < 0 ) {
209
+ nwarn ("write_k8s_log failed" );
210
+ return G_SOURCE_CONTINUE ;
211
+ }
212
+ } else {
213
+ if (write_k8s_stream_log (pipe , buf , num_read ) < 0 ) {
214
+ nwarn ("write_k8s_stream_log failed" );
215
+ return G_SOURCE_CONTINUE ;
216
+ }
217
+ }
194
218
}
195
219
if (use_journald_logging && write_journald (pipe , buf , num_read ) < 0 ) {
196
220
nwarn ("write_journald failed" );
@@ -540,3 +564,110 @@ void sync_logs(void)
540
564
if (fsync (k8s_log_fd ) < 0 )
541
565
pwarn ("Failed to sync log file before exit" );
542
566
}
567
+
568
+
569
+ /* strlen("1997-03-25T13:20:42.999999999+01:00 stdout 9999999999 999999999 ") + 1 */
570
+ #define TSSTREAMBUFLEN 128
571
+
572
+ /*
573
+ * PROPOSED: CRI Stream Format, variable length file format
574
+ */
575
+ static int set_k8s_stream_timestamp (char * buf , ssize_t bufsiz , ssize_t * tsbuflen , const char * pipename , uint64_t offset , ssize_t buflen ,
576
+ ssize_t * btbw )
577
+ {
578
+ char off_sign = '+' ;
579
+ int off , len , err = -1 ;
580
+
581
+ struct timespec ts ;
582
+ if (clock_gettime (CLOCK_REALTIME , & ts ) < 0 ) {
583
+ /* If CLOCK_REALTIME is not supported, we set nano seconds to 0 */
584
+ if (errno == EINVAL ) {
585
+ ts .tv_nsec = 0 ;
586
+ } else {
587
+ return err ;
588
+ }
589
+ }
590
+
591
+ struct tm current_tm ;
592
+ if (localtime_r (& ts .tv_sec , & current_tm ) == NULL )
593
+ return err ;
594
+
595
+ off = (int )current_tm .tm_gmtoff ;
596
+ if (current_tm .tm_gmtoff < 0 ) {
597
+ off_sign = '-' ;
598
+ off = - off ;
599
+ }
600
+
601
+ len = snprintf (buf , bufsiz , "%d-%02d-%02dT%02d:%02d:%02d.%09ld%c%02d:%02d %s %lud %ld " , current_tm .tm_year + 1900 ,
602
+ current_tm .tm_mon + 1 , current_tm .tm_mday , current_tm .tm_hour , current_tm .tm_min , current_tm .tm_sec , ts .tv_nsec ,
603
+ off_sign , off / 3600 , off % 3600 , pipename , offset , buflen );
604
+
605
+ if (len < bufsiz )
606
+ err = 0 ;
607
+
608
+ * tsbuflen = len ;
609
+ * btbw = len + buflen ;
610
+ return err ;
611
+ }
612
+
613
+
614
+ /*
615
+ * PROPOSED: CRI Stream Format, variable length file format
616
+ *
617
+ * %d-%02d-%02dT%02d:%02d:%02d.%09ld%c%02d:%02d %(stream)s %(offset)lud %(buflen)ld %(buf)s
618
+ *
619
+ * The CRI stream fromat requires us to write each buffer read with a
620
+ * timestamp, stream, length (human readable ascii), and the buffer contents
621
+ * read (with a space character separating the buffer length string from the
622
+ * buffer.
623
+ */
624
+ static int write_k8s_stream_log (stdpipe_t pipe , const char * buf , ssize_t buflen )
625
+ {
626
+ writev_buffer_t bufv = {0 };
627
+ char tsbuf [TSSTREAMBUFLEN ];
628
+ static ssize_t bytes_written = 0 ;
629
+ static uint64_t offset = 0 ;
630
+ ssize_t bytes_to_be_written = 0 ;
631
+ ssize_t tsbuflen = 0 ;
632
+
633
+ /*
634
+ * Use the same timestamp for every line of the log in this buffer.
635
+ * There is no practical difference in the output since write(2) is
636
+ * fast.
637
+ */
638
+ if (set_k8s_stream_timestamp (tsbuf , sizeof tsbuf , & tsbuflen , stdpipe_name (pipe ), buflen , offset , & bytes_to_be_written ))
639
+ /* TODO: We should handle failures much more cleanly than this. */
640
+ return -1 ;
641
+
642
+ /*
643
+ * We re-open the log file if writing out the bytes will exceed the max
644
+ * log size. We also reset the state so that the new file is started with
645
+ * a timestamp.
646
+ */
647
+ if ((opt_log_size_max > 0 ) && (bytes_written + bytes_to_be_written ) > opt_log_size_max ) {
648
+ bytes_written = 0 ;
649
+
650
+ reopen_k8s_file ();
651
+ }
652
+
653
+ /* Output the timestamp, stream, and length */
654
+ if (writev_buffer_append_segment (k8s_log_fd , & bufv , tsbuf , tsbuflen ) < 0 ) {
655
+ nwarn ("failed to write (timestamp, stream) to log" );
656
+ goto stream_next ;
657
+ }
658
+
659
+ /* Output the actual contents. */
660
+ if (writev_buffer_append_segment (k8s_log_fd , & bufv , buf , buflen ) < 0 ) {
661
+ nwarn ("failed to write buffer to log" );
662
+ }
663
+
664
+ stream_next :
665
+ bytes_written += bytes_to_be_written ;
666
+ offset += (uint64_t )bytes_to_be_written ;
667
+
668
+ if (writev_buffer_flush (k8s_log_fd , & bufv ) < 0 ) {
669
+ nwarn ("failed to flush buffer to log" );
670
+ }
671
+
672
+ return 0 ;
673
+ }
0 commit comments