diff --git a/misc/Makefile b/misc/Makefile
index 38dad737f2..63812124f2 100644
--- a/misc/Makefile
+++ b/misc/Makefile
@@ -107,6 +107,8 @@ tests-special += $(objpfx)tst-error1-mem.out \
   $(objpfx)tst-allocate_once-mem.out
 endif
 
+tests-container := tst-syslog
+
 CFLAGS-select.c += -fexceptions -fasynchronous-unwind-tables
 CFLAGS-tsearch.c += $(uses-callbacks)
 CFLAGS-lsearch.c += $(uses-callbacks)
diff --git a/misc/tst-syslog.c b/misc/tst-syslog.c
new file mode 100644
index 0000000000..a3ff983c9a
--- /dev/null
+++ b/misc/tst-syslog.c
@@ -0,0 +1,450 @@
+/* Basic tests for syslog interfaces.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <netinet/in.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xsocket.h>
+#include <support/xunistd.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <syslog.h>
+#include <sys/un.h>
+
+static const int facilities[] =
+  {
+    LOG_KERN,
+    LOG_USER,
+    LOG_MAIL,
+    LOG_DAEMON,
+    LOG_AUTH,
+    LOG_SYSLOG,
+    LOG_LPR,
+    LOG_NEWS,
+    LOG_UUCP,
+    LOG_CRON,
+    LOG_AUTHPRIV,
+    LOG_FTP,
+    LOG_LOCAL0,
+    LOG_LOCAL1,
+    LOG_LOCAL2,
+    LOG_LOCAL3,
+    LOG_LOCAL4,
+    LOG_LOCAL5,
+    LOG_LOCAL6,
+    LOG_LOCAL7,
+  };
+
+static const int priorities[] =
+  {
+    LOG_EMERG,
+    LOG_ALERT,
+    LOG_CRIT,
+    LOG_ERR,
+    LOG_WARNING,
+    LOG_NOTICE,
+    LOG_INFO,
+    LOG_DEBUG
+  };
+
+enum
+  {
+    ident_length = 32,
+    msg_length = 64
+  };
+
+#define SYSLOG_MSG_BASE "syslog_message"
+#define OPENLOG_IDENT   "openlog_ident"
+
+struct msg_t
+  {
+    int priority;
+    int facility;
+    char ident[ident_length];
+    char msg[msg_length];
+  };
+
+static void
+call_vsyslog (int priority, const char *format, ...)
+{
+  va_list ap;
+  va_start (ap, format);
+  vsyslog (priority, format, ap);
+  va_end (ap);
+}
+
+static void
+send_vsyslog (void *clousure)
+{
+  for (size_t i = 0; i < array_length (facilities); i++)
+    {
+      for (size_t j = 0; j < array_length (priorities); j++)
+        {
+          int facility = facilities[i];
+          int priority = priorities[j];
+          call_vsyslog (facility | priority, "%s %d %d", SYSLOG_MSG_BASE,
+                        facility, priority);
+        }
+    }
+}
+
+static void
+send_syslog (void *clousure)
+{
+  for (size_t i = 0; i < array_length (facilities); i++)
+    {
+      for (size_t j = 0; j < array_length (priorities); j++)
+        {
+          int facility = facilities[i];
+          int priority = priorities[j];
+          syslog (facility | priority, "%s %d %d", SYSLOG_MSG_BASE, facility,
+                  priority);
+        }
+    }
+}
+
+static bool
+check_syslog_message (const struct msg_t *msg, int msgnum)
+{
+  if (msgnum == array_length (facilities) * array_length (priorities) - 1)
+    return false;
+
+  int i = msgnum / array_length (priorities);
+  int j = msgnum % array_length (priorities);
+
+  int expected_facility = facilities[i];
+  /* With no preceding openlog, syslog default to LOG_USER.  */
+  if (expected_facility == LOG_KERN)
+      expected_facility = LOG_USER;
+  int expected_priority = priorities[j];
+
+  TEST_COMPARE (msg->facility, expected_facility);
+  TEST_COMPARE (msg->priority, expected_priority);
+
+  return true;
+}
+
+static void
+send_openlog (void *clousure)
+{
+  int options = *(int *) clousure;
+
+  /* Define a non-default IDENT and a not default facility.  */
+  openlog (OPENLOG_IDENT, options, LOG_LOCAL0);
+  for (size_t j = 0; j < array_length (priorities); j++)
+    {
+      int priority = priorities[j];
+      syslog (priority, "%s %d %d", SYSLOG_MSG_BASE, LOG_LOCAL0, priority);
+    }
+  closelog ();
+
+  /* Back to the default IDENT with a non default facility.  */
+  openlog (NULL, 0, LOG_LOCAL6);
+  for (size_t j = 0; j < array_length (priorities); j++)
+    {
+      int priority = priorities[j];
+      syslog (LOG_LOCAL7 | priority, "%s %d %d", SYSLOG_MSG_BASE, LOG_LOCAL7,
+        priority);
+    }
+  closelog ();
+
+  /* LOG_KERN does not change the internal default facility.  */
+  openlog (NULL, 0, LOG_KERN);
+  for (size_t j = 0; j < array_length (priorities); j++)
+    {
+      int priority = priorities[j];
+      syslog (priority, "%s %d %d", SYSLOG_MSG_BASE, LOG_KERN, priority);
+    }
+  closelog ();
+}
+
+static bool
+check_openlog_message (const struct msg_t *msg, int msgnum)
+{
+  if (msgnum == 3 * array_length (priorities) - 1)
+    return false;
+
+  int expected_priority = priorities[msgnum % array_length (priorities)];
+  TEST_COMPARE (msg->priority, expected_priority);
+
+  if (msgnum < array_length (priorities))
+    {
+      TEST_COMPARE_STRING (msg->ident, OPENLOG_IDENT ":");
+      TEST_COMPARE (msg->facility, LOG_LOCAL0);
+    }
+  else if (msgnum < 2 * array_length (priorities))
+    {
+      TEST_COMPARE (msg->facility, LOG_LOCAL7);
+    }
+  /* Since openlog (LOG_KERN) does not change the facility, we need to check
+     against the previous valid openlog.  */
+  else if (msgnum < 3 * array_length (priorities))
+    {
+      TEST_COMPARE (msg->facility, LOG_LOCAL6);
+    }
+
+  return true;
+}
+
+static struct msg_t
+parse_syslog_msg (const char *msg)
+{
+  int number;
+  struct msg_t r;
+  int priority;
+  int facility;
+
+  /* The message in the form:
+     <179>Apr  8 14:51:19 tst-syslog: syslog message 176 3  */
+  int n = sscanf (msg, "<%3d>%*s %*d %*d:%*d:%*d %32s %64s %d %d",
+      &number, r.ident, r.msg, &facility, &priority);
+  TEST_COMPARE (n, 5);
+
+  r.facility = number & LOG_FACMASK;
+  r.priority = number & LOG_PRIMASK;
+
+  return r;
+}
+
+static struct msg_t
+parse_syslog_console (const char *msg)
+{
+  int priority;
+  int facility;
+  struct msg_t r;
+
+  /* The message in the form:
+     openlog_ident: syslog_message 128 0  */
+  int n = sscanf (msg, "%32s %64s %d %d",
+      r.ident, r.msg, &facility, &priority);
+  TEST_COMPARE (n, 4);
+
+  r.facility = facility;
+  r.priority = priority;
+
+  return r;
+}
+
+static void
+check_syslog_udp (void (*syslog_send)(void*), int options,
+                  bool (*syslog_check)(const struct msg_t *msg, int msgnum))
+{
+  struct sockaddr_un addr =
+    {
+      .sun_family = AF_UNIX,
+      .sun_path = _PATH_LOG
+    };
+
+  socklen_t addrlen = sizeof (addr);
+  int server_udp = xsocket (AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+  xbind (server_udp, (struct sockaddr *) &addr, addrlen);
+
+  {
+    struct support_capture_subprocess result;
+    result = support_capture_subprocess (syslog_send, &options);
+    support_capture_subprocess_check (&result, "tst-syslog-child", 0,
+              sc_allow_none);
+    support_capture_subprocess_free (&result);
+  }
+
+  int msgnum = 0;
+  while (1)
+    {
+      char buf[512];
+      size_t l = xrecvfrom (server_udp, buf, sizeof (buf), 0,
+                            (struct sockaddr *) &addr, &addrlen);
+      buf[l] = '\0';
+
+      struct msg_t msg = parse_syslog_msg (buf);
+      if (!syslog_check (&msg, msgnum++))
+        break;
+     }
+
+  xclose (server_udp);
+
+  unlink (_PATH_LOG);
+}
+
+static void
+check_syslog_tcp (void (*syslog_send)(void*), int options,
+                  bool (*syslog_check)(const struct msg_t *msg, int msgnum))
+{
+  struct sockaddr_un addr =
+    {
+      .sun_family = AF_UNIX,
+      .sun_path = _PATH_LOG
+    };
+  socklen_t addrlen = sizeof (addr);
+
+  int server_tcp = xsocket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  xbind (server_tcp, (struct sockaddr *) &addr, addrlen);
+  xlisten (server_tcp, 5);
+
+  {
+    struct support_capture_subprocess result;
+    result = support_capture_subprocess (syslog_send, &options);
+    support_capture_subprocess_check (&result, "tst-syslog-child", 0,
+              sc_allow_none);
+    support_capture_subprocess_free (&result);
+  }
+
+  struct pollfd fds[] =
+    {
+      { .fd = server_tcp, POLLIN }
+    };
+  xpoll (fds, array_length (fds), -1);
+  TEST_VERIFY_EXIT (fds[0].revents > 0);
+  int client_tcp = xaccept (fds[0].fd, NULL, NULL);
+
+  char buf[512], *rb = buf;
+  size_t rbl = sizeof (buf);
+  int msgnum = 0;
+
+  while (1)
+    {
+      size_t l = xrecvfrom (client_tcp, rb, rbl, 0, NULL, NULL);
+      if (l == 0)
+        break;
+
+      char *b = buf;
+      while (1)
+        {
+          char *e = memchr (b, '\0', l);
+          if (e == NULL)
+            {
+              ptrdiff_t diff = buf + sizeof (buf) - b;
+               memmove (buf, b, diff);
+              rb = buf + diff;
+              rbl = sizeof (buf) - diff;
+               break;
+            }
+
+          struct msg_t msg = parse_syslog_msg (b);
+          if (!syslog_check (&msg, msgnum++))
+            break;
+
+          l -= (e - b) + 1;
+          b = e + 1;
+        }
+    }
+
+  xclose (client_tcp);
+  xclose (server_tcp);
+
+  unlink (_PATH_LOG);
+}
+
+_Noreturn static void
+check_syslog_console_read (FILE *fp)
+{
+  char buf[512];
+  int msgnum = 0;
+  while (fgets (buf, sizeof (buf), fp) != NULL)
+    {
+      struct msg_t msg = parse_syslog_console (buf);
+      TEST_COMPARE_STRING (msg.ident, OPENLOG_IDENT ":");
+      TEST_COMPARE (msg.priority, priorities[msgnum]);
+      TEST_COMPARE (msg.facility, LOG_LOCAL0);
+
+      if (++msgnum == array_length (priorities))
+        break;
+    }
+  xfclose (fp);
+  _exit (0);
+}
+
+static void
+check_syslog_console (void)
+{
+  xmkfifo (_PATH_CONSOLE, 0666);
+
+  pid_t pid = xfork ();
+  if (pid == 0)
+    {
+      FILE *fp = xfopen (_PATH_CONSOLE, "r+");
+      check_syslog_console_read (fp);
+    }
+
+  {
+    struct support_capture_subprocess result;
+    result = support_capture_subprocess (send_openlog, &(int){LOG_CONS}); 
+    support_capture_subprocess_check (&result, "tst-openlog-child", 0,
+                                      sc_allow_none);
+    support_capture_subprocess_free (&result);
+  }
+
+  int status;
+  xwaitpid (pid, &status, 0);
+  TEST_COMPARE (status, 0);
+
+  unlink (_PATH_CONSOLE);
+}
+
+static void
+check_syslog_perror (void)
+{
+  {
+    struct support_capture_subprocess result;
+    result = support_capture_subprocess (send_openlog, &(int){LOG_PERROR});
+
+    FILE *mfp = fmemopen (result.err.buffer, result.err.length, "r");
+    if (mfp == NULL)
+      FAIL_EXIT1 ("fmemopen: %m");
+    check_syslog_console_read (mfp);
+    xfclose (mfp);
+
+    support_capture_subprocess_check (&result, "tst-openlog-child", 0,
+                                      sc_allow_stderr);
+    support_capture_subprocess_free (&result);
+  }
+}
+
+static int
+do_test (void)
+{
+  add_temp_file (_PATH_LOG);
+  add_temp_file (_PATH_CONSOLE);
+
+  /* Send every combination of facility/priority over UDP and TCP.  */
+  check_syslog_udp (send_syslog, 0, check_syslog_message);
+  check_syslog_tcp (send_syslog, 0, check_syslog_message);
+
+  /* Also check vsyslog.  */
+  check_syslog_udp (send_vsyslog, 0, check_syslog_message);
+  check_syslog_tcp (send_vsyslog, 0, check_syslog_message);
+
+  /* Run some openlog/syslog/closelog combinations.  */
+  check_syslog_udp (send_openlog, 0, check_openlog_message);
+  check_syslog_tcp (send_openlog, 0, check_openlog_message);
+
+  /* Check the LOG_CONS option.  */
+  check_syslog_console ();
+
+  /* Check the LOG_PERROR option.  */
+  check_syslog_perror ();
+
+  return 0;
+}
+
+#include <support/test-driver.c>
