openlog(), syslog(), closelog(), setlogmask()

 

The syslog API
Chapter 37 โ€“ Part 6: openlog(), syslog(), closelog(), setlogmask()
๐Ÿ”Œ openlog()
๐Ÿ“ syslog()
๐Ÿ” setlogmask()

What Will You Learn?

The syslog C API has only four functions. But each one has important parameters and options that control exactly how your daemon’s log messages are formatted, where they go, and which ones are actually recorded. This tutorial covers all four functions in depth with every flag explained and practical code examples.

Key Concepts

openlog(ident, option, facility) syslog(priority, format, …) vsyslog() closelog() setlogmask() LOG_PID LOG_CONS LOG_UPTO() LOG_MASK()

1. openlog() โ€” Establish Connection to syslogd

Before calling syslog(), you should call openlog() to set up the logging connection and configure how messages will appear. It is optional โ€” if you skip it, syslog() works anyway with default settings โ€” but calling it is strongly recommended for any daemon.

#include <syslog.h>

void openlog(const char *ident, int options, int facility);

Parameter 1: ident (Identity String)

A string prepended to every log message to identify which program sent it. Typically the program name. This pointer must remain valid for the lifetime of logging โ€” do not pass a stack-allocated string.

openlog("sshd",    LOG_PID, LOG_AUTH);   /* SSH daemon */
openlog("cron",    LOG_PID, LOG_CRON);   /* cron daemon */
openlog("myapp",   LOG_PID, LOG_LOCAL0); /* Your daemon */

/* Log output will look like:
 * Jun  5 09:15:43 hostname myapp[12345]: Message here
 *                          ^^^^^^^^^^^^^
 *                          ident[PID] */

Parameter 2: options (Bit Mask of Flags)

Flag Meaning When to Use
LOG_PID Include process ID in every message: myapp[12345]: Always โ€” makes it easy to trace messages from a specific daemon instance
LOG_CONS If writing to syslogd fails, fall back to the system console (/dev/console) Critical daemons (like init) where losing log messages is unacceptable
LOG_NDELAY Open the /dev/log socket immediately (don’t wait for first syslog() call) Daemons that fork children โ€” ensures socket is opened before fork so children inherit it
LOG_ODELAY Delay opening /dev/log until first syslog() call (this is the default) Default behavior โ€” no need to specify explicitly
LOG_PERROR Also print log messages to stderr (in addition to syslogd) Testing/debugging only โ€” remove for production (stderr goes to /dev/null in daemon)
LOG_NOWAIT Do not wait() for children created by syslog implementation Rarely needed; for compatibility with older BSD implementations

Parameter 3: facility (Default Facility)

Sets the default facility for messages that do not specify one explicitly in the syslog() call. If you always call syslog() with just a priority level (e.g., LOG_INFO), this facility is used automatically.

/* Example: openlog sets default facility to LOG_DAEMON */
openlog("my_service", LOG_PID | LOG_NDELAY, LOG_DAEMON);

syslog(LOG_INFO, "Started");   /* Uses LOG_DAEMON|LOG_INFO */
syslog(LOG_ERR,  "Failed");    /* Uses LOG_DAEMON|LOG_ERR  */

/* Override facility for a specific message: */
syslog(LOG_AUTH | LOG_INFO, "User logged in");  /* Uses LOG_AUTH */

2. syslog() โ€” Send a Log Message
void syslog(int priority, const char *format, …);

The priority is the OR combination of a facility and a severity level. If only a severity level is given, the default facility from openlog() is used.

The format is a printf()-style format string with one important extension: %m is replaced by strerror(errno).

The %m Format Specifier

/* Standard C approach โ€” verbose */
syslog(LOG_ERR, "open(%s) failed: %s", path, strerror(errno));

/* syslog extension โ€” cleaner */
syslog(LOG_ERR, "open(%s) failed: %m", path);
/* Both produce identical output:
 * "open(/etc/missing) failed: No such file or directory" */

/* Using %m without checking errno first โ€” COMMON MISTAKE */
int fd = open("/some/file", O_RDONLY);
/* Do NOT call other functions between open() and syslog() */
/* They could change errno! */
if (fd == -1)
    syslog(LOG_ERR, "Cannot open file: %m");  /* Correct โ€” errno still set */

vsyslog() โ€” Variable Argument Version

For wrapper functions that accept variable arguments (va_list), use vsyslog() โ€” same as syslog() but takes a va_list instead of …:

#include <stdarg.h>
#include <syslog.h>

/*
 * Custom logging wrapper โ€” logs to syslog with a fixed prefix
 * and also records messages to an application-level buffer.
 */
void app_log(int priority, const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vsyslog(priority, fmt, args);  /* Forward to syslog */
    va_end(args);
}

/* Usage: */
app_log(LOG_INFO,  "Server started on port %d", 8080);
app_log(LOG_ERR,   "Request failed: %m");
app_log(LOG_DEBUG, "Buffer state: used=%zu free=%zu", used, free);

3. closelog() โ€” Close the syslog Connection
void closelog(void);

closelog() closes the file descriptor used to write to /dev/log. Calling it is optional since the connection is automatically closed when the process exits, but it is good practice to call it:

Good Practice: Call closelog() when you are completely done logging (e.g., at daemon shutdown in the SIGTERM handler).
Resource Management: Frees the file descriptor used for /dev/log. Useful if the daemon has a tight file descriptor limit.
/* Correct pattern for daemon lifecycle */
int main(void)
{
    /* Step 1: Open syslog (before daemonizing) */
    openlog("myapp", LOG_PID | LOG_NDELAY, LOG_DAEMON);
    syslog(LOG_INFO, "Daemon starting...");

    /* Step 2: Daemonize */
    /* becomeDaemon(0); */

    /* Step 3: Main work loop */
    syslog(LOG_INFO, "Daemon running");
    while (!terminate) {
        /* ... do work ... */
        sleep(1);
    }

    /* Step 4: Cleanup and close syslog */
    syslog(LOG_INFO, "Daemon shutting down");
    closelog();   /* Free /dev/log file descriptor */

    return 0;
}

4. setlogmask() โ€” Filter Messages by Priority
int setlogmask(int mask);

/* Returns the previous mask value */

setlogmask() sets a per-process filter that controls which log messages are actually sent to syslogd. A message is sent only if its priority is set in the mask. Messages with priorities NOT in the mask are silently discarded โ€” they never reach syslogd.

Two macros create masks:

LOG_MASK(priority)

Creates a mask with just one priority level set. Use with | to combine multiple individual levels.

LOG_MASK(LOG_ERR) | LOG_MASK(LOG_CRIT)

LOG_UPTO(priority)

Creates a mask with all priorities from EMERG up to (and including) the specified level. Most commonly used.

LOG_UPTO(LOG_INFO) // EMERG..INFO, excludes DEBUG

#include <syslog.h>
#include <stdio.h>

void demonstrate_setlogmask(void)
{
    openlog("mask_demo", LOG_PID | LOG_PERROR, LOG_DAEMON);

    /* --- Default mask: all messages pass through --- */
    printf("=== Default mask (all levels) ===\n");
    syslog(LOG_DEBUG,   "DEBUG   - you see this");
    syslog(LOG_INFO,    "INFO    - you see this");
    syslog(LOG_WARNING, "WARNING - you see this");
    syslog(LOG_ERR,     "ERR     - you see this");

    /* --- LOG_UPTO(LOG_WARNING): suppress DEBUG and INFO --- */
    printf("\n=== LOG_UPTO(LOG_WARNING): only WARNING and above ===\n");
    setlogmask(LOG_UPTO(LOG_WARNING));

    syslog(LOG_DEBUG,   "DEBUG   - SUPPRESSED, never reaches syslogd");
    syslog(LOG_INFO,    "INFO    - SUPPRESSED");
    syslog(LOG_WARNING, "WARNING - PASSES through");
    syslog(LOG_ERR,     "ERR     - PASSES through");
    syslog(LOG_EMERG,   "EMERG   - PASSES through");

    /* --- Enable only specific levels --- */
    printf("\n=== Only LOG_ERR and LOG_CRIT ===\n");
    setlogmask(LOG_MASK(LOG_ERR) | LOG_MASK(LOG_CRIT));

    syslog(LOG_DEBUG,   "DEBUG   - suppressed");
    syslog(LOG_INFO,    "INFO    - suppressed");
    syslog(LOG_WARNING, "WARNING - suppressed");
    syslog(LOG_ERR,     "ERR     - PASSES");
    syslog(LOG_CRIT,    "CRIT    - PASSES");
    syslog(LOG_EMERG,   "EMERG   - suppressed! (not in mask)");

    /* --- Re-enable all messages --- */
    setlogmask(LOG_UPTO(LOG_DEBUG));  /* All levels from EMERG to DEBUG */
    syslog(LOG_DEBUG, "DEBUG is back - mask restored");

    closelog();
}

int main(void)
{
    demonstrate_setlogmask();
    return 0;
}
Important: setlogmask() works at the process level โ€” it filters messages BEFORE they are sent to syslogd. This is different from syslog.conf filtering, which happens inside syslogd. Both can filter independently. Your setlogmask() can be more restrictive than syslog.conf.

Common production pattern: runtime debug toggle via SIGHUP

static volatile sig_atomic_t debug_mode = 0;

void handle_sigusr1(int sig)
{
    debug_mode = !debug_mode;  /* Toggle */
}

/* In main loop: */
if (debug_mode) {
    setlogmask(LOG_UPTO(LOG_DEBUG));   /* Enable all levels */
    syslog(LOG_NOTICE, "Debug logging ENABLED");
} else {
    setlogmask(LOG_UPTO(LOG_INFO));    /* Suppress DEBUG messages */
    syslog(LOG_NOTICE, "Debug logging DISABLED");
}
/* Now: kill -USR1 PID toggles debug mode without restart */

๐Ÿ’ป Code Example 1: Complete Daemon Logging Framework

A reusable logging module used by daemons โ€” wraps syslog with severity filtering, debug toggle, and structured message format.

/* daemon_log.h */
#ifndef DAEMON_LOG_H
#define DAEMON_LOG_H

#include <syslog.h>
#include <stdarg.h>

void dlog_init(const char *name, int facility, int debug_enabled);
void dlog_set_debug(int enable);
void dlog_close(void);
void dlog_msg(int level, const char *fmt, ...);

#define DLOG_EMERG(fmt, ...)   dlog_msg(LOG_EMERG,   fmt, ##__VA_ARGS__)
#define DLOG_ALERT(fmt, ...)   dlog_msg(LOG_ALERT,   fmt, ##__VA_ARGS__)
#define DLOG_CRIT(fmt, ...)    dlog_msg(LOG_CRIT,    fmt, ##__VA_ARGS__)
#define DLOG_ERR(fmt, ...)     dlog_msg(LOG_ERR,     fmt, ##__VA_ARGS__)
#define DLOG_WARN(fmt, ...)    dlog_msg(LOG_WARNING, fmt, ##__VA_ARGS__)
#define DLOG_INFO(fmt, ...)    dlog_msg(LOG_INFO,    fmt, ##__VA_ARGS__)
#define DLOG_DEBUG(fmt, ...)   dlog_msg(LOG_DEBUG,   fmt, ##__VA_ARGS__)

#endif
/* daemon_log.c */
#include "daemon_log.h"
#include <stdio.h>
#include <stdlib.h>

static int current_facility = LOG_DAEMON;
static int debug_enabled    = 0;

void dlog_init(const char *name, int facility, int debug)
{
    current_facility = facility;
    debug_enabled    = debug;

    openlog(name, LOG_PID | LOG_NDELAY, facility);
    setlogmask(debug ? LOG_UPTO(LOG_DEBUG) : LOG_UPTO(LOG_INFO));
    syslog(LOG_INFO, "Logging initialized (debug=%s)", debug ? "on" : "off");
}

void dlog_set_debug(int enable)
{
    debug_enabled = enable;
    setlogmask(enable ? LOG_UPTO(LOG_DEBUG) : LOG_UPTO(LOG_INFO));
    syslog(LOG_NOTICE, "Debug logging %s", enable ? "enabled" : "disabled");
}

void dlog_close(void)
{
    syslog(LOG_INFO, "Logging shutting down");
    closelog();
}

void dlog_msg(int level, const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vsyslog(level, fmt, args);
    va_end(args);
}

/* --- Main program using the logging framework --- */
int main(void)
{
    dlog_init("myservice", LOG_LOCAL0, 0);  /* No debug initially */

    DLOG_INFO("Service started");
    DLOG_WARN("Config file missing, using defaults");
    DLOG_DEBUG("This is suppressed (debug=off)");
    DLOG_ERR("Simulated error: %m");

    /* Enable debug */
    dlog_set_debug(1);
    DLOG_DEBUG("Now debug messages appear");
    DLOG_INFO("Still logging info messages");

    dlog_close();
    return 0;
}
gcc -o daemon_log daemon_log.c
./daemon_log
sudo grep "myservice" /var/log/syslog | tail -10

๐Ÿ’ป Code Example 2: Log Message Rate Limiting

A critical daemon pattern: prevent log flooding. If a daemon encounters 10,000 errors per second, it should not write 10,000 messages per second to syslog. Here is a rate limiter:

#include <stdio.h>
#include <time.h>
#include <syslog.h>
#include <stdarg.h>

#define RATE_LIMIT_WINDOW  5   /* seconds */
#define RATE_LIMIT_MAX    10   /* max messages per window */

/*
 * rate_limited_syslog() - Log a message, but at most MAX times per window.
 * After the limit is reached, counts suppressed messages and
 * reports the count in the next window.
 */
void rate_limited_syslog(int priority, const char *fmt, ...)
{
    static time_t window_start    = 0;
    static int    count_this_window = 0;
    static int    suppressed      = 0;

    time_t now = time(NULL);

    /* Start a new window */
    if (now - window_start >= RATE_LIMIT_WINDOW) {
        if (suppressed > 0) {
            syslog(LOG_WARNING,
                   "Rate limit: %d messages suppressed in last %d seconds",
                   suppressed, RATE_LIMIT_WINDOW);
            suppressed = 0;
        }
        window_start     = now;
        count_this_window = 0;
    }

    if (count_this_window < RATE_LIMIT_MAX) {
        va_list args;
        va_start(args, fmt);
        vsyslog(priority, fmt, args);
        va_end(args);
        count_this_window++;
    } else {
        suppressed++;
    }
}

int main(void)
{
    openlog("rate_limit_demo", LOG_PID | LOG_PERROR, LOG_DAEMON);

    /* Simulate a burst of 50 error messages in quick succession */
    printf("Sending 50 rapid error messages...\n");
    for (int i = 0; i < 50; i++) {
        rate_limited_syslog(LOG_ERR, "Error occurred: item=%d errno=%m", i);
    }

    printf("Only %d of 50 messages were logged (+ 1 suppression notice)\n",
           RATE_LIMIT_MAX);

    closelog();
    return 0;
}
gcc -o rate_limit_demo rate_limit_demo.c
./rate_limit_demo
sudo grep "rate_limit_demo" /var/log/syslog | tail -15

๐ŸŽฏ Interview Questions โ€” syslog API
Q1. What are the three parameters of openlog() and what does each control?
Answer: (1) ident: A string prepended to each log message to identify the program (e.g., “sshd”). (2) options: A bitmask of flags controlling behavior โ€” LOG_PID adds the PID, LOG_CONS falls back to console, LOG_NDELAY opens /dev/log immediately, LOG_PERROR also prints to stderr. (3) facility: The default facility code used for messages that don’t specify one (e.g., LOG_DAEMON, LOG_AUTH, LOG_LOCAL0).
Q2. What is the difference between LOG_MASK() and LOG_UPTO()?
Answer: LOG_MASK(p) creates a bitmask with only the single priority p set. LOG_UPTO(p) creates a bitmask with all priority levels from LOG_EMERG (0) up to and including p. In practice: LOG_UPTO(LOG_INFO) enables EMERG, ALERT, CRIT, ERR, WARNING, NOTICE, and INFO โ€” but not DEBUG. LOG_MASK() is used when you want to enable a specific non-contiguous set of levels.
Q3. What does LOG_NDELAY do and when should you use it?
Answer: LOG_NDELAY tells openlog() to immediately open the connection to /dev/log (the Unix domain socket to syslogd) rather than waiting until the first syslog() call. You should use it when the process will fork children โ€” the socket is opened before the fork, so all children inherit the already-open connection and don’t race to open it themselves. Without LOG_NDELAY, the first syslog() call after a fork might fail or behave unexpectedly.
Q4. How does setlogmask() differ from syslog.conf filtering?
Answer: setlogmask() is a process-level filter โ€” it runs in the calling process’s C library and silently discards messages that don’t match the mask before they are even sent to syslogd. syslog.conf filtering is performed inside syslogd after receiving the message. setlogmask() is cheaper (no socket write) and can be changed at runtime in your code. syslog.conf requires the administrator to edit the file and send SIGHUP to syslogd.
Q5. What is vsyslog() and when would you use it?
Answer: vsyslog() is the va_list version of syslog(). You use it when writing a wrapper function that itself accepts variable arguments (using … and va_start/va_end). Without vsyslog(), you would have no way to pass the variable arguments through from your wrapper to syslog(). Example: a custom log function void mylog(int level, const char *fmt, …) would call vsyslog(level, fmt, args) internally.
Q6. What happens if you call syslog() without calling openlog() first?
Answer: syslog() still works. If openlog() was not called, the first syslog() call implicitly calls openlog() with: ident = NULL (which defaults to argv[0] or the program name from /proc), options = 0 (no special flags), facility = LOG_USER. The LOG_PID flag is NOT set, so the PID will not appear in log messages. For any production daemon, calling openlog() explicitly is strongly recommended.

Chapter Summary

The syslog API has four functions: openlog() sets the program identity, options, and default facility; syslog() sends a formatted message with facility|priority; closelog() releases the /dev/log connection; setlogmask() provides process-level filtering using LOG_MASK() and LOG_UPTO() macros. Always use LOG_PID in options and call closelog() at daemon shutdown.

โ† syslog Overview Next: /etc/syslog.conf โ†’

Leave a Reply

Your email address will not be published. Required fields are marked *