Python’s syslog module writes to the Unix syslog daemon (Linux/macOS only). import syslog. openlog: syslog.openlog(ident, logoption, facility) — sets program name prefix; call once at startup. closelog: syslog.closelog() — closes syslog connection (auto-reopens on next call). syslog: syslog.syslog(priority, message) or syslog.syslog(message) (defaults to LOG_INFO). setlogmask: syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_WARNING)) — suppress DEBUG/INFO. Priority = facility | severity: syslog.LOG_USER | syslog.LOG_ERR. Severity constants (high→low): LOG_EMERG=0, LOG_ALERT=1, LOG_CRIT=2, LOG_ERR=3, LOG_WARNING=4, LOG_NOTICE=5, LOG_INFO=6, LOG_DEBUG=7. Facility constants: LOG_KERN, LOG_USER, LOG_MAIL, LOG_DAEMON, LOG_AUTH, LOG_SYSLOG, LOG_LOCAL0–LOG_LOCAL7. Option flags: LOG_PID (append PID), LOG_CONS (fall back to console), LOG_NDELAY (connect immediately), LOG_PERROR (also write to stderr). Messages appear in /var/log/syslog, /var/log/messages, or via journalctl -t ident on systemd systems. Claude Code generates daemon loggers, audit trail emitters, structured syslog handlers, and priority-routed log pipelines.
CLAUDE.md for syslog
## syslog Stack
- Stdlib: import syslog (Linux/macOS only)
- Init: syslog.openlog("myapp", syslog.LOG_PID, syslog.LOG_DAEMON)
- Write: syslog.syslog(syslog.LOG_ERR, "something failed")
- Filter: syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_WARNING))
- Close: syslog.closelog()
- Priority = facility | severity, or severity alone (uses facility from openlog)
syslog Unix Syslog Pipeline
# app/syslogutil.py — openlog wrapper, Python logging handler, audit emitter
from __future__ import annotations
import logging
import os
import platform
from dataclasses import dataclass, field
from typing import ClassVar
_SYSLOG_AVAILABLE = platform.system() != "Windows"
if _SYSLOG_AVAILABLE:
import syslog as _syslog
# ─────────────────────────────────────────────────────────────────────────────
# 1. Convenience open / write / close
# ─────────────────────────────────────────────────────────────────────────────
# Severity name → constant
_SEVERITY: dict[str, int] = {}
_FACILITY: dict[str, int] = {}
if _SYSLOG_AVAILABLE:
_SEVERITY = {
"EMERG": _syslog.LOG_EMERG,
"ALERT": _syslog.LOG_ALERT,
"CRIT": _syslog.LOG_CRIT,
"ERR": _syslog.LOG_ERR,
"WARNING": _syslog.LOG_WARNING,
"NOTICE": _syslog.LOG_NOTICE,
"INFO": _syslog.LOG_INFO,
"DEBUG": _syslog.LOG_DEBUG,
}
_FACILITY = {
"USER": _syslog.LOG_USER,
"DAEMON": _syslog.LOG_DAEMON,
"AUTH": _syslog.LOG_AUTH,
"SYSLOG": _syslog.LOG_SYSLOG,
"LOCAL0": _syslog.LOG_LOCAL0,
"LOCAL1": _syslog.LOG_LOCAL1,
"LOCAL2": _syslog.LOG_LOCAL2,
"LOCAL3": _syslog.LOG_LOCAL3,
"LOCAL4": _syslog.LOG_LOCAL4,
"LOCAL5": _syslog.LOG_LOCAL5,
"LOCAL6": _syslog.LOG_LOCAL6,
"LOCAL7": _syslog.LOG_LOCAL7,
}
def open_syslog(
ident: str,
facility: str = "USER",
options: int | None = None,
) -> None:
"""
Open a connection to syslog with the given program identity and facility.
Example:
open_syslog("myapp", facility="DAEMON")
"""
if not _SYSLOG_AVAILABLE:
return
if options is None:
options = _syslog.LOG_PID
fac = _FACILITY.get(facility.upper(), _syslog.LOG_USER)
_syslog.openlog(ident, options, fac)
def close_syslog() -> None:
"""Close the syslog connection."""
if _SYSLOG_AVAILABLE:
_syslog.closelog()
def log(severity: str | int, message: str) -> None:
"""
Write a message to syslog at the given severity.
severity can be a string ('ERR', 'INFO', etc.) or an int constant.
Example:
log("ERR", "disk write failed: /var/data")
log("INFO", "worker started")
"""
if not _SYSLOG_AVAILABLE:
print(f"[syslog/{severity}] {message}")
return
if isinstance(severity, str):
priority = _SEVERITY.get(severity.upper(), _syslog.LOG_INFO)
else:
priority = severity
_syslog.syslog(priority, message)
def set_min_level(min_severity: str) -> None:
"""
Suppress syslog messages below min_severity.
E.g. set_min_level("WARNING") hides DEBUG and INFO.
Example:
set_min_level("WARNING")
"""
if not _SYSLOG_AVAILABLE:
return
level = _SEVERITY.get(min_severity.upper(), _syslog.LOG_DEBUG)
_syslog.setlogmask(_syslog.LOG_UPTO(level))
# ─────────────────────────────────────────────────────────────────────────────
# 2. Python logging.Handler integration
# ─────────────────────────────────────────────────────────────────────────────
class SyslogHandler(logging.Handler):
"""
A logging.Handler that routes Python log records to Unix syslog.
Integrates with the standard logging hierarchy.
Example:
logger = logging.getLogger("myapp")
logger.addHandler(SyslogHandler("myapp", facility="DAEMON"))
logger.setLevel(logging.DEBUG)
logger.error("something failed")
"""
# Mapping from Python logging levels to syslog severities
_LEVEL_MAP: ClassVar[dict[int, int]] = {}
def __init__(
self,
ident: str = "python",
facility: str = "USER",
options: int | None = None,
level: int = logging.NOTSET,
) -> None:
super().__init__(level)
self._ident = ident
if _SYSLOG_AVAILABLE:
self.__class__._LEVEL_MAP = {
logging.DEBUG: _syslog.LOG_DEBUG,
logging.INFO: _syslog.LOG_INFO,
logging.WARNING: _syslog.LOG_WARNING,
logging.ERROR: _syslog.LOG_ERR,
logging.CRITICAL: _syslog.LOG_CRIT,
}
open_syslog(ident, facility=facility, options=options)
def emit(self, record: logging.LogRecord) -> None:
if not _SYSLOG_AVAILABLE:
return
try:
msg = self.format(record)
priority = self._LEVEL_MAP.get(record.levelno, _syslog.LOG_INFO)
_syslog.syslog(priority, msg)
except Exception:
self.handleError(record)
def close(self) -> None:
if _SYSLOG_AVAILABLE:
_syslog.closelog()
super().close()
def make_syslog_logger(
name: str,
ident: str | None = None,
facility: str = "USER",
min_level: int = logging.INFO,
) -> logging.Logger:
"""
Return a logger that writes to syslog (and stderr as fallback).
The logger writes only to syslog on POSIX; stderr fallback on Windows.
Example:
logger = make_syslog_logger("myapp.worker", facility="DAEMON")
logger.info("worker started pid=%d", os.getpid())
logger.error("task failed: %s", exc)
"""
logger = logging.getLogger(name)
logger.setLevel(min_level)
if not logger.handlers:
if _SYSLOG_AVAILABLE:
handler = SyslogHandler(ident or name, facility=facility)
else:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(name)s: %(levelname)s %(message)s"))
logger.addHandler(handler)
return logger
# ─────────────────────────────────────────────────────────────────────────────
# 3. Audit-trail emitter
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class AuditEvent:
"""Structured audit event for compliance / security logging."""
action: str
user: str = field(default_factory=lambda: str(os.getuid()))
resource: str = ""
result: str = "ok" # "ok" or "denied"
detail: str = ""
pid: int = field(default_factory=os.getpid)
def to_syslog_msg(self) -> str:
parts = [
f"action={self.action}",
f"user={self.user}",
f"pid={self.pid}",
f"result={self.result}",
]
if self.resource:
parts.append(f"resource={self.resource!r}")
if self.detail:
parts.append(f"detail={self.detail!r}")
return "AUDIT " + " ".join(parts)
def emit_audit(
action: str,
user: str | None = None,
resource: str = "",
result: str = "ok",
detail: str = "",
facility: str = "AUTH",
ident: str = "audit",
) -> AuditEvent:
"""
Write a structured audit event to syslog at LOG_NOTICE.
Uses LOG_AUTH facility by default (visible in /var/log/auth.log).
Example:
emit_audit("login", user="alice", result="ok")
emit_audit("delete", user="bob", resource="/etc/passwd", result="denied")
"""
if user is None:
try:
import pwd
user = pwd.getpwuid(os.getuid()).pw_name
except (ImportError, KeyError):
user = str(os.getuid())
event = AuditEvent(action=action, user=user,
resource=resource, result=result,
detail=detail)
msg = event.to_syslog_msg()
if _SYSLOG_AVAILABLE:
fac = _FACILITY.get(facility.upper(), _syslog.LOG_AUTH)
priority = fac | _syslog.LOG_NOTICE
_syslog.openlog(ident, _syslog.LOG_PID, fac)
_syslog.syslog(priority, msg)
else:
print(f"[{facility}/NOTICE] {msg}")
return event
# ─────────────────────────────────────────────────────────────────────────────
# 4. Priority routing
# ─────────────────────────────────────────────────────────────────────────────
class PriorityRouter:
"""
Routes messages to different syslog facilities based on severity.
Useful for daemons that want errors in LOG_DAEMON and audits in LOG_AUTH.
Example:
router = PriorityRouter("myapp")
router.log("ERR", "disk full")
router.audit("file_delete", resource="/tmp/x")
"""
def __init__(self, ident: str) -> None:
self._ident = ident
self._operational_logger = make_syslog_logger(
f"{ident}.ops", ident=ident, facility="DAEMON"
)
def log(self, severity: str, message: str) -> None:
"""Route an operational message to LOG_DAEMON."""
level_map = {
"DEBUG": logging.DEBUG,
"INFO": logging.INFO,
"NOTICE": logging.INFO,
"WARNING": logging.WARNING,
"ERR": logging.ERROR,
"CRIT": logging.CRITICAL,
"ALERT": logging.CRITICAL,
"EMERG": logging.CRITICAL,
}
level = level_map.get(severity.upper(), logging.INFO)
self._operational_logger.log(level, message)
def audit(
self,
action: str,
user: str | None = None,
resource: str = "",
result: str = "ok",
detail: str = "",
) -> AuditEvent:
"""Route an audit event to LOG_AUTH."""
return emit_audit(
action=action, user=user, resource=resource,
result=result, detail=detail,
facility="AUTH", ident=self._ident,
)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== syslog demo ===")
if not _SYSLOG_AVAILABLE:
print(" syslog not available on Windows; messages would go to stderr")
# ── basic open / log / close ───────────────────────────────────────────────
print("\n--- basic log ---")
open_syslog("syslog-demo", facility="USER")
log("INFO", "demo started")
log("WARNING", "example warning from syslog demo")
log("ERR", "example error from syslog demo")
log("DEBUG", "this debug message is visible by default")
close_syslog()
print(" logged INFO / WARNING / ERR / DEBUG to syslog")
print(" check: sudo journalctl -t syslog-demo OR /var/log/syslog")
# ── setlogmask ────────────────────────────────────────────────────────────
print("\n--- setlogmask ---")
open_syslog("syslog-demo", facility="USER")
set_min_level("WARNING")
log("DEBUG", "this DEBUG is suppressed by mask")
log("INFO", "this INFO is suppressed by mask")
log("WARNING", "this WARNING should appear")
close_syslog()
print(" only WARNING and above passed through setlogmask")
# ── Python logging integration ─────────────────────────────────────────────
print("\n--- Python logging handler ---")
logger = make_syslog_logger("syslog-demo.worker", facility="USER")
logger.info("worker started pid=%d", os.getpid())
logger.warning("high memory usage: %dMB", 512)
logger.error("task failed: division by zero")
print(" Python logging → SyslogHandler → syslogd")
# ── audit events ──────────────────────────────────────────────────────────
print("\n--- emit_audit ---")
ev1 = emit_audit("login", user="alice", result="ok", facility="AUTH")
ev2 = emit_audit("delete", user="bob", result="denied",
resource="/etc/shadow", facility="AUTH")
print(f" {ev1.to_syslog_msg()}")
print(f" {ev2.to_syslog_msg()}")
# ── PriorityRouter ────────────────────────────────────────────────────────
print("\n--- PriorityRouter ---")
router = PriorityRouter("syslog-demo")
router.log("INFO", "service started cleanly")
router.log("ERR", "upstream connection refused")
router.audit("config_change", resource="/etc/myapp.conf")
print(" operational messages → LOG_DAEMON, audits → LOG_AUTH")
print("\n=== done (messages written to syslog) ===")
For the logging alternative — Python’s logging module with logging.handlers.SysLogHandler(address='/dev/log') provides cross-platform structured log emission with formatters, filters, level hierarchy, and handler chaining — use logging.handlers.SysLogHandler when you need structured log formatting, multiple output destinations (file + syslog simultaneously), log rotation, or cross-platform code; use syslog directly when you want the simplest possible connection to the native syslog daemon with zero configuration, or when writing audit events that should use LOG_AUTH facility semantics that SysLogHandler can also target via the facility parameter. For the systemd journal alternative — on systemd systems, writing to stderr with the format <N>message (where N is the journal priority integer: <3> for ERR, <6> for INFO) or using systemd-python’s journal.send() sends structured entries with MESSAGE=, PRIORITY=, SYSLOG_IDENTIFIER=, and custom fields — use journal writing when deploying systemd services that benefit from journalctl -u service --output json queryability; use syslog when targeting legacy /var/log/syslog infrastructure or when LOG_LOCAL0–LOG_LOCAL7 facility routing to remote rsyslog/syslog-ng is required. The Claude Skills 360 bundle includes syslog skill sets covering open_syslog()/log()/set_min_level()/close_syslog() primitives, SyslogHandler Python logging integration with make_syslog_logger(), AuditEvent with emit_audit() compliance trail emitter, and PriorityRouter for multi-facility routing. Start with the free tier to try syslog daemon patterns and syslog pipeline code generation.