logging: Add container labels to log entries on journald

At present it's not possible to ship properly labeled logs when using
podman and tools like podman-compose. Container labels are lost which
makes it much harder to understand where a particular log line
originated from. Log processing and analysis is significantly more
inconvenient as well, because it's hard to group related logs, e.g.
coming from the same compose project.

This commit implements the parts necessary to annotate log messages with
container labels. Each label and value pair is specified via --log-label
LABEL=VALUE arguments.

Co-authored-by: OZoneGuy <oalkersh@protonmail.com>
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
This commit is contained in:
Povilas Kanapickas 2025-05-26 21:08:03 +03:00
parent de270e6eb9
commit f37e9e795c
4 changed files with 75 additions and 5 deletions

View File

@ -69,6 +69,10 @@ Maximum size of the log file (in bytes).
**--log-tag**
Additional tag to use for logging.
**--log-label**
Additional label to use for logging. The accepted format is LABEL=VALUE. Can be specified multiple times.
Note that LABEL must contain only uppercase letters, numbers and underscore character.
**-n**, **--name**
Container name.

View File

@ -49,6 +49,7 @@ int opt_exit_delay = 0;
gboolean opt_replace_listen_pid = FALSE;
char *opt_log_level = NULL;
char *opt_log_tag = NULL;
gchar **opt_log_labels = NULL;
gboolean opt_sync = FALSE;
gboolean opt_no_sync_log = FALSE;
char *opt_sdnotify_socket = NULL;
@ -78,6 +79,8 @@ GOptionEntry opt_entries[] = {
{"log-size-max", 0, 0, G_OPTION_ARG_INT64, &opt_log_size_max, "Maximum size of log file", NULL},
{"log-global-size-max", 0, 0, G_OPTION_ARG_INT64, &opt_log_global_size_max, "Maximum size of all log files", NULL},
{"log-tag", 0, 0, G_OPTION_ARG_STRING, &opt_log_tag, "Additional tag to use for logging", NULL},
{"log-label", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_log_labels,
"Additional label to include in logs. Can be specified multiple times", NULL},
{"name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Container name", NULL},
{"no-new-keyring", 0, 0, G_OPTION_ARG_NONE, &opt_no_new_keyring, "Do not create a new session keyring for the container", NULL},
{"no-pivot", 0, 0, G_OPTION_ARG_NONE, &opt_no_pivot, "Do not use pivot_root", NULL},
@ -194,5 +197,5 @@ void process_cli()
if (opt_container_pid_file == NULL)
opt_container_pid_file = g_strdup_printf("%s/pidfile-%s", cwd, opt_cid);
configure_log_drivers(opt_log_path, opt_log_size_max, opt_log_global_size_max, opt_cid, opt_name, opt_log_tag);
configure_log_drivers(opt_log_path, opt_log_size_max, opt_log_global_size_max, opt_cid, opt_name, opt_log_tag, opt_log_labels);
}

View File

@ -2,6 +2,7 @@
#include "ctr_logging.h"
#include "cli.h"
#include "config.h"
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>
@ -70,6 +71,7 @@ static char *container_id_full = NULL;
static char *container_id = NULL;
static char *container_name = NULL;
static char *container_tag = NULL;
static gchar **container_labels = NULL;
static size_t container_tag_len;
static char *syslog_identifier = NULL;
static size_t syslog_identifier_len;
@ -96,13 +98,43 @@ gboolean logging_is_passthrough(void)
return use_logging_passthrough;
}
static int count_chars_in_string(const char *str, char ch)
{
int count = 0;
while (str) {
str = strchr(str, ch);
if (str == NULL)
break;
count++;
str++;
}
return count;
}
static int is_valid_label_name(const char *str)
{
while (*str) {
if (*str == '=') {
return 1;
}
if (!isupper(*str) && !isdigit(*str) && *str != '_') {
return 0;
}
str++;
}
return 1;
}
/*
* configures container log specific information, such as the drivers the user
* called with and the max log size for log file types. For the log file types
* (currently just k8s log file), it will also open the log_fd for that specific
* log file.
*/
void configure_log_drivers(gchar **log_drivers, int64_t log_size_max_, int64_t log_global_size_max_, char *cuuid_, char *name_, char *tag)
void configure_log_drivers(gchar **log_drivers, int64_t log_size_max_, int64_t log_global_size_max_, char *cuuid_, char *name_, char *tag,
gchar **log_labels)
{
log_size_max = log_size_max_;
log_global_size_max = log_global_size_max_;
@ -126,8 +158,14 @@ void configure_log_drivers(gchar **log_drivers, int64_t log_size_max_, int64_t l
}
k8s_total_bytes_written = k8s_bytes_written;
if (!use_journald_logging && tag)
nexit("k8s-file doesn't support --log-tag");
if (!use_journald_logging) {
if (!tag) {
nexit("k8s-file doesn't support --log-tag");
}
if (!log_labels) {
nexit("k8s-file doesn't support --log-label");
}
}
}
if (use_journald_logging) {
@ -169,6 +207,24 @@ void configure_log_drivers(gchar **log_drivers, int64_t log_size_max_, int64_t l
syslog_identifier = g_strdup_printf("SYSLOG_IDENTIFIER=%s", tag);
syslog_identifier_len = strlen(syslog_identifier);
}
if (log_labels) {
container_labels = log_labels;
/* Ensure that valid LABEL=VALUE pairs have been passed */
for (char **ptr = log_labels; *ptr; ptr++) {
if (**ptr == '=') {
nexitf("Container labels must be in format LABEL=VALUE (no LABEL present in '%s')", *ptr);
}
if (count_chars_in_string(*ptr, '=') != 1) {
nexitf("Container labels must be in format LABEL=VALUE (none or more than one '=' present in '%s')",
*ptr);
}
if (!is_valid_label_name(*ptr)) {
nexitf("Container label names must contain only uppercase letters, numbers and underscore (in '%s')",
*ptr);
}
}
}
}
}
@ -325,6 +381,12 @@ static int write_journald(int pipe, char *buf, ssize_t buflen)
/* per docker journald logging format, CONTAINER_PARTIAL_MESSAGE is set to true if it's partial, but otherwise not set. */
if (partial && writev_buffer_append_segment_no_flush(&bufv, "CONTAINER_PARTIAL_MESSAGE=true", PARTIAL_MESSAGE_EQ_LEN) < 0)
return -1;
if (container_labels) {
for (gchar **label = container_labels; *label; ++label) {
if (writev_buffer_append_segment_no_flush(&bufv, *label, strlen(*label)) < 0)
return -1;
}
}
int err = sd_journal_sendv(bufv.iov, bufv.iovcnt);
if (err < 0) {

View File

@ -7,7 +7,8 @@
void reopen_log_files(void);
bool write_to_logs(stdpipe_t pipe, char *buf, ssize_t num_read);
void configure_log_drivers(gchar **log_drivers, int64_t log_size_max_, int64_t log_global_size_max_, char *cuuid_, char *name_, char *tag);
void configure_log_drivers(gchar **log_drivers, int64_t log_size_max_, int64_t log_global_size_max_, char *cuuid_, char *name_, char *tag,
gchar **labels);
void sync_logs(void);
gboolean logging_is_passthrough(void);
void close_logging_fds(void);