changeset 936:6866d0d0e360

irccd: add log.h
author David Demelier <markand@malikania.fr>
date Sun, 10 Jan 2021 18:10:52 +0100
parents b0451fc0a17d
children ffe985308567
files .hgignore Makefile irccd/irccd irccd/log.c irccd/log.h tests/test-log.c
diffstat 6 files changed, 416 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sun Jan 10 17:29:30 2021 +0100
+++ b/.hgignore	Sun Jan 10 18:10:52 2021 +0100
@@ -17,12 +17,16 @@
 ^extern/libcompat/tryinclude$
 ^extern/libcompat/trylib$
 
+# executables.
+irccd/irccd
+
 # temporary files.
 \.a$
 \.o$
 \.d$
 
 # tests.
+^tests/test-log$
 ^tests/test-subst$
 ^tests/test-util$
 
--- a/Makefile	Sun Jan 10 17:29:30 2021 +0100
+++ b/Makefile	Sun Jan 10 18:10:52 2021 +0100
@@ -25,12 +25,14 @@
 
 IRCCD=          irccd/irccd
 IRCCD_SRCS=     extern/libduktape/duktape.c     \
+                irccd/log.c                     \
                 irccd/subst.c                   \
                 irccd/util.c
 IRCCD_OBJS=     ${IRCCD_SRCS:.c=.o}
 IRCCD_DEPS=     ${IRCCD_SRCS:.c=.d}
 
-TESTS=          tests/test-util.c               \
+TESTS=          tests/test-log.c                \
+                tests/test-util.c               \
                 tests/test-subst.c
 TESTS_OBJS=     ${TESTS:.c=}
 
Binary file irccd/irccd has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccd/log.c	Sun Jan 10 18:10:52 2021 +0100
@@ -0,0 +1,194 @@
+/*
+ * log.c -- loggers
+ *
+ * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "log.h"
+
+enum level {
+	LEVEL_INFO,
+	LEVEL_WARN,
+	LEVEL_DEBUG
+};
+
+static FILE *out, *err;
+static bool verbosity;
+
+static void
+handler_files(enum level level, const char *fmt, va_list ap)
+{
+	switch (level) {
+	case LEVEL_WARN:
+		vfprintf(err, fmt, ap);
+		putc('\n', err);
+		fflush(err);
+		break;
+	default:
+		vfprintf(out, fmt, ap);
+		putc('\n', out);
+		fflush(out);
+		break;
+	}
+}
+
+static void
+finalizer_files(void)
+{
+	if (out)
+		fclose(out);
+	if (err)
+		fclose(err);
+}
+
+static void
+handler_syslog(enum level level, const char *fmt, va_list ap)
+{
+	static const int table[] = {
+		[LEVEL_INFO] = LOG_INFO,
+		[LEVEL_WARN] = LOG_WARNING,
+		[LEVEL_DEBUG] = LOG_DEBUG
+	};
+
+	/* TODO: add compatibility shim for vsyslog. */
+	vsyslog(table[level], fmt, ap);
+}
+
+static void
+finalizer_syslog(void)
+{
+	closelog();
+}
+
+static void (*handler)(enum level, const char *, va_list);
+static void (*finalizer)(void);
+
+void
+irc_log_to_syslog(void)
+{
+	irc_log_finish();
+
+	openlog("irccd", 0, LOG_DAEMON);
+
+	handler = handler_syslog;
+	finalizer = finalizer_syslog;
+}
+
+void
+irc_log_to_console(void)
+{
+	irc_log_finish();
+
+	out = stdout;
+	err = stderr;
+
+	handler = handler_files;
+	finalizer = NULL;
+}
+
+void
+irc_log_to_files(const char *stdout, const char *stderr)
+{
+	irc_log_finish();
+
+	if (!(out = fopen(stdout, "a")))
+		irc_log_warn("%s: %s", stdout, strerror(errno));
+	if (!(err = fopen(stderr, "a")))
+		irc_log_warn("%s: %s", stdout, strerror(errno));
+
+	handler = handler_files;
+	finalizer = finalizer_files;
+}
+
+void
+irc_log_to_null(void)
+{
+	irc_log_finish();
+
+	handler = NULL;
+	finalizer = NULL;
+}
+
+void
+irc_log_set_verbose(bool mode)
+{
+	verbosity = mode;
+}
+
+void
+irc_log_info(const char *fmt, ...)
+{
+	assert(fmt);
+
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	if (verbosity && handler)
+		handler(LEVEL_INFO, fmt, ap);
+
+	va_end(ap);
+}
+
+void
+irc_log_warn(const char *fmt, ...)
+{
+	assert(fmt);
+
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	if (handler)
+		handler(LEVEL_WARN, fmt, ap);
+
+	va_end(ap);
+}
+
+void
+irc_log_debug(const char *fmt, ...)
+{
+#if !defined(NDBEUG)
+	assert(fmt);
+
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	if (handler)
+		handler(LEVEL_DEBUG, fmt, ap);
+
+	va_end(ap);
+#else
+	(void)fmt;
+#endif
+}
+
+void
+irc_log_finish(void)
+{
+	if (finalizer)
+		finalizer();
+
+	handler = NULL;
+	finalizer = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/irccd/log.h	Sun Jan 10 18:10:52 2021 +0100
@@ -0,0 +1,51 @@
+/*
+ * log.h -- loggers
+ *
+ * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_LOG_H
+#define IRCCD_LOG_H
+
+#include <stdbool.h>
+
+void
+irc_log_to_syslog(void);
+
+void
+irc_log_to_console(void);
+
+void
+irc_log_to_files(const char *, const char *);
+
+void
+irc_log_to_null(void);
+
+void
+irc_log_set_verbose(bool);
+
+void
+irc_log_info(const char *, ...);
+
+void
+irc_log_warn(const char *, ...);
+
+void
+irc_log_debug(const char *, ...);
+
+void
+irc_log_finish(void);
+
+#endif /* !IRCCD_LOG_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-log.c	Sun Jan 10 18:10:52 2021 +0100
@@ -0,0 +1,164 @@
+/*
+ * test-log.c -- test log.h functions
+ *
+ * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define GREATEST_USE_ABBREVS 0
+#include <greatest.h>
+
+#include <irccd/log.h>
+
+/*
+ * Note: we can't really test logs to console and syslog, so use files as best effort. Write some
+ * data and check that it is correctly written.
+ */
+
+static void
+clean(void *udata)
+{
+	(void)udata;
+
+	remove("stdout.txt");
+	remove("stderr.txt");
+}
+
+GREATEST_TEST
+basics_info_verbose_off(void)
+{
+	FILE *fpout, *fperr;
+	char out[128] = {0}, err[128] = {0};
+
+	/* Default is quiet, should not log. */
+	irc_log_to_files("stdout.txt", "stderr.txt");
+	irc_log_info("hello world!");
+
+	if (!(fpout = fopen("stdout.txt", "r")) || !(fperr = fopen("stderr.txt", "r")))
+		GREATEST_FAIL();
+
+	fgets(out, sizeof (out), fpout);
+	fgets(err, sizeof (err), fperr);
+
+	GREATEST_ASSERT_STR_EQ("", out);
+	GREATEST_ASSERT_STR_EQ("", err);
+
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+basics_info_verbose_on(void)
+{
+	FILE *fpout, *fperr;
+	char out[128] = {0}, err[128] = {0};
+
+	irc_log_set_verbose(true);
+	irc_log_to_files("stdout.txt", "stderr.txt");
+	irc_log_info("hello world!");
+	irc_log_info("what's up?");
+
+	if (!(fpout = fopen("stdout.txt", "r")) || !(fperr = fopen("stderr.txt", "r")))
+		GREATEST_FAIL();
+
+	GREATEST_ASSERT(fgets(out, sizeof (out), fpout));
+	GREATEST_ASSERT_STR_EQ("hello world!\n", out);
+	GREATEST_ASSERT(fgets(out, sizeof (out), fpout));
+	GREATEST_ASSERT_STR_EQ("what's up?\n", out);
+
+	GREATEST_ASSERT(!fgets(err, sizeof (err), fperr));
+	GREATEST_ASSERT_STR_EQ("", err);
+	GREATEST_ASSERT(!fgets(err, sizeof (err), fperr));
+	GREATEST_ASSERT_STR_EQ("", err);
+
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+basics_warn(void)
+{
+	FILE *fpout, *fperr;
+	char out[128] = {0}, err[128] = {0};
+
+	/* Warning messages are printed even without verbosity. */
+	irc_log_set_verbose(false);
+	irc_log_to_files("stdout.txt", "stderr.txt");
+	irc_log_warn("error line 1");
+	irc_log_warn("error line 2");
+
+	if (!(fpout = fopen("stdout.txt", "r")) || !(fperr = fopen("stderr.txt", "r")))
+		GREATEST_FAIL();
+
+	GREATEST_ASSERT(!fgets(out, sizeof (out), fpout));
+	GREATEST_ASSERT_STR_EQ("", out);
+	GREATEST_ASSERT(!fgets(out, sizeof (out), fpout));
+	GREATEST_ASSERT_STR_EQ("", out);
+
+	GREATEST_ASSERT(fgets(err, sizeof (err), fperr));
+	GREATEST_ASSERT_STR_EQ("error line 1\n", err);
+	GREATEST_ASSERT(fgets(err, sizeof (err), fperr));
+	GREATEST_ASSERT_STR_EQ("error line 2\n", err);
+
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+basics_debug(void)
+{
+#if !defined(NDEBUG)
+	FILE *fpout, *fperr;
+	char out[128] = {0}, err[128] = {0};
+
+	/* Debug messages are printed even without verbosity but requires to be built in debug. */
+	irc_log_set_verbose(false);
+	irc_log_to_files("stdout.txt", "stderr.txt");
+	irc_log_debug("startup!");
+	irc_log_debug("shutdown!");
+
+	if (!(fpout = fopen("stdout.txt", "r")) || !(fperr = fopen("stderr.txt", "r")))
+		GREATEST_FAIL();
+
+	GREATEST_ASSERT(fgets(out, sizeof (out), fpout));
+	GREATEST_ASSERT_STR_EQ("startup!\n", out);
+	GREATEST_ASSERT(fgets(out, sizeof (out), fpout));
+	GREATEST_ASSERT_STR_EQ("shutdown!\n", out);
+
+	GREATEST_ASSERT(!fgets(err, sizeof (err), fperr));
+	GREATEST_ASSERT_STR_EQ("", err);
+	GREATEST_ASSERT(!fgets(err, sizeof (err), fperr));
+	GREATEST_ASSERT_STR_EQ("", err);
+#endif
+	GREATEST_PASS();
+}
+
+GREATEST_SUITE(suite_basics)
+{
+	GREATEST_SET_SETUP_CB(clean, NULL);
+	GREATEST_SET_TEARDOWN_CB(clean, NULL);
+	GREATEST_RUN_TEST(basics_info_verbose_off);
+	GREATEST_RUN_TEST(basics_info_verbose_on);
+	GREATEST_RUN_TEST(basics_warn);
+	GREATEST_RUN_TEST(basics_debug);
+}
+
+GREATEST_MAIN_DEFS();
+
+int
+main(int argc, char **argv)
+{
+	GREATEST_MAIN_BEGIN();
+	GREATEST_RUN_SUITE(suite_basics);
+	GREATEST_MAIN_END();
+
+	return 0;
+}