Finalized the pgbouncemgr.Logger.
This commit is contained in:
parent
9697589318
commit
de5d75516d
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=no-self-use,too-few-public-methods
|
||||
"""This module encapsulates the configuration for pgbouncemgr.
|
||||
The configuration is read from a YAML file."""
|
||||
|
||||
|
@ -35,6 +36,8 @@ class InvalidConfigValue(ConfigException):
|
|||
|
||||
|
||||
class Config():
|
||||
"""This class is used to load the pgbouncemgr configuration from
|
||||
a yaml file and to enrich it with default values where needed."""
|
||||
def __init__(self, path):
|
||||
self._set_defaults()
|
||||
parser = self._read(path)
|
||||
|
@ -129,7 +132,7 @@ class Config():
|
|||
def _build_node(self, node_name, data):
|
||||
node = dict()
|
||||
if not data:
|
||||
return
|
||||
return
|
||||
for key in data:
|
||||
value = data[key]
|
||||
if key in ["port", "connect_timeout"]:
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=missing-docstring,global-statement,no-self-use
|
||||
|
||||
import syslog
|
||||
import re
|
||||
|
||||
|
||||
class LoggerException(Exception):
|
||||
"""Used for all exceptions that are raised from pgbouncemgr.logger."""
|
||||
|
||||
class SyslogLogTargetException(Exception):
|
||||
"""Used for all exceptions that are raised from the SyslogLogTarget."""
|
||||
|
||||
|
||||
class Logger(list):
|
||||
def debug(self, msg):
|
||||
for log_target in self:
|
||||
log_target.debug(msg)
|
||||
|
||||
def info(self, msg):
|
||||
for log_target in self:
|
||||
log_target.info(msg)
|
||||
|
||||
def warning(self, msg):
|
||||
for log_target in self:
|
||||
log_target.warning(msg)
|
||||
|
||||
def error(self, msg):
|
||||
for log_target in self:
|
||||
log_target.error(msg)
|
||||
|
||||
|
||||
def format_ex(exception):
|
||||
"""Takes an exception as its input and returns a message that can be used
|
||||
for logging purposes: single line, multiple spaces collapsed."""
|
||||
error = str(exception).strip()
|
||||
error = re.sub(r' +', ' ', error)
|
||||
error = re.sub(r'\r?\n+', ' ', error)
|
||||
error = re.sub(r'\s{2,}', ' / ', error)
|
||||
name = type(exception).__name__
|
||||
return "%s: %s" % (name, error)
|
||||
|
||||
|
||||
class MemoryLogTarget(list):
|
||||
"""MemoryTarget is used to collect log messages in memory."""
|
||||
def debug(self, msg):
|
||||
self.append(["DEBUG", msg])
|
||||
|
||||
def info(self, msg):
|
||||
self.append(["INFO", msg])
|
||||
|
||||
def warning(self, msg):
|
||||
self.append(["WARNING", msg])
|
||||
|
||||
def error(self, msg):
|
||||
self.append(["ERROR", msg])
|
||||
|
||||
|
||||
class ConsoleLogTarget():
|
||||
"""ConsoleTarget is used to send log messages to the console."""
|
||||
def __init__(self, verbose, debug):
|
||||
self.verbose_enabled = verbose or debug
|
||||
self.debug_enabled = debug
|
||||
|
||||
def debug(self, msg):
|
||||
if self.debug_enabled:
|
||||
print("[DEBUG] %s" % msg)
|
||||
|
||||
def info(self, msg):
|
||||
if self.verbose_enabled:
|
||||
print("[INFO] %s" % msg)
|
||||
|
||||
def warning(self, msg):
|
||||
if self.verbose_enabled:
|
||||
print("[WARNING] %s" % msg)
|
||||
|
||||
def error(self, msg):
|
||||
if self.verbose_enabled:
|
||||
print("[ERROR] %s" % msg)
|
||||
|
||||
|
||||
class SyslogLogTarget():
|
||||
"""Syslogtarget is used to send log messages to syslog."""
|
||||
def __init__(self, ident, facility):
|
||||
facility = self._resolve_facility(facility)
|
||||
syslog.openlog(ident=ident, facility=facility)
|
||||
|
||||
def debug(self, msg):
|
||||
syslog.syslog(syslog.LOG_DEBUG, msg)
|
||||
|
||||
def info(self, msg):
|
||||
syslog.syslog(syslog.LOG_INFO, msg)
|
||||
|
||||
def warning(self, msg):
|
||||
syslog.syslog(syslog.LOG_WARNING, msg)
|
||||
|
||||
def error(self, msg):
|
||||
syslog.syslog(syslog.LOG_ERR, msg)
|
||||
|
||||
def _resolve_facility(self, facility):
|
||||
"""Resolve the provided facility. When it is numeric, then use the
|
||||
number. Otherwise try to look it up in the syslog lib by name."""
|
||||
try:
|
||||
return int(facility)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return int(getattr(syslog, str.upper(facility)))
|
||||
except (AttributeError, ValueError):
|
||||
raise SyslogLogTargetException(
|
||||
"Invalid syslog facility provided (facility=%s)" %
|
||||
(facility if facility is None else repr(facility)))
|
|
@ -3,6 +3,8 @@
|
|||
PostgreSQL cluster and that reconfigures pgbouncer when needed."""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from pgbouncemgr.logger import Logger
|
||||
from pgbouncemgr.config import Config
|
||||
|
||||
|
||||
DEFAULT_CONFIG = "/etc/pgbouncer/pgbouncemgr.yaml"
|
||||
|
@ -12,7 +14,8 @@ DEFAULT_LOG_FACILITY = "LOG_LOCAL1"
|
|||
def main():
|
||||
"""Starts the pgbouncemgr main application."""
|
||||
args = _parse_arguments()
|
||||
print(repr(args))
|
||||
config = Config(args.config)
|
||||
log = Logger("pgbouncemgr", args.log_facility, args.debug, args.verbose)
|
||||
|
||||
|
||||
def _parse_arguments():
|
||||
|
|
|
@ -24,7 +24,7 @@ class DuplicateNodeAdded(StateException):
|
|||
already exists in the contained nodes."""
|
||||
def __init__(self, node_id):
|
||||
super().__init__(
|
||||
"Node already exists in state (duplicate node_id=%s)" % node_id)
|
||||
"Node already exists in state (duplicate node_id=%s)" % node_id)
|
||||
|
||||
class UnknownNodeRequested(StateException):
|
||||
"""Raised when a request is made for a node that is not contained
|
||||
|
@ -349,7 +349,7 @@ class NodeState():
|
|||
"config": self.config.export(),
|
||||
"system_id": self.system_id,
|
||||
"timeline_id": self.timeline_id,
|
||||
"is_leader": self.parent_state._leader_node_id == self.node_id,
|
||||
"is_leader": self.parent_state.leader_node_id == self.node_id,
|
||||
"status": self.status,
|
||||
"error": self.err,
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import json
|
|||
from datetime import datetime
|
||||
from os import rename
|
||||
from os.path import isfile
|
||||
from pgbouncemgr.log import format_exception_message
|
||||
from pgbouncemgr.logger import format_ex
|
||||
|
||||
STORE_ERROR = "error"
|
||||
STORE_UPDATED = "updated"
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import unittest
|
||||
from pgbouncemgr.logger import *
|
||||
|
||||
|
||||
class LoggerTests(unittest.TestCase):
|
||||
def test_Logger_CanBeEmpty(self):
|
||||
"""A NULL-logger is a logger that does sink all the messages that
|
||||
come in. No messages are written or collected whatsoever."""
|
||||
logger = Logger()
|
||||
send_logs(logger)
|
||||
|
||||
self.assertTrue(len(logger) == 0)
|
||||
|
||||
def test_Logger_WritesToAllTargets(self):
|
||||
logger = Logger()
|
||||
logger.append(MemoryLogTarget())
|
||||
logger.append(MemoryLogTarget())
|
||||
send_logs(logger)
|
||||
|
||||
self.assertEqual([
|
||||
[
|
||||
['DEBUG', 'Debug me'],
|
||||
['INFO', 'Inform me'],
|
||||
['WARNING', 'Warn me'],
|
||||
['ERROR', 'Break me']
|
||||
], [
|
||||
['DEBUG', 'Debug me'],
|
||||
['INFO', 'Inform me'],
|
||||
['WARNING', 'Warn me'],
|
||||
['ERROR', 'Break me']
|
||||
]
|
||||
], logger)
|
||||
|
||||
|
||||
class SyslogLargetTests(unittest.TestCase):
|
||||
def test_GivenInvalidFacility_ExceptionIsRaised(self):
|
||||
with self.assertRaises(SyslogLogTargetException) as context:
|
||||
SyslogLogTarget("my app", "LOG_WRONG")
|
||||
self.assertIn("Invalid syslog facility provided", str(context.exception))
|
||||
self.assertIn("'LOG_WRONG'", str(context.exception))
|
||||
|
||||
def test_GivenValidFacility_LogTargetIsCreated(self):
|
||||
SyslogLogTarget("my app", "LOG_LOCAL0")
|
||||
|
||||
|
||||
class ConsoleLogTargetTests(unittest.TestCase):
|
||||
def test_CanCreateSilentConsoleLogger(self):
|
||||
console = ConsoleLogTarget(False, False)
|
||||
|
||||
self.assertFalse(console.verbose_enabled)
|
||||
self.assertFalse(console.debug_enabled)
|
||||
|
||||
def test_CanCreateVerboseConsoleLogger(self):
|
||||
console = ConsoleLogTarget(True, False)
|
||||
|
||||
self.assertTrue(console.verbose_enabled)
|
||||
self.assertFalse(console.debug_enabled)
|
||||
|
||||
def test_CanCreateDebuggingConsoleLogger(self):
|
||||
console = ConsoleLogTarget(False, True)
|
||||
|
||||
self.assertTrue(console.verbose_enabled)
|
||||
self.assertTrue(console.debug_enabled)
|
||||
|
||||
def test_CanCreateVerboseDebuggingConsoleLogger(self):
|
||||
"""Basically the same as a debugging console logger,
|
||||
since the debug flag enables the verbose flag as well."""
|
||||
console = ConsoleLogTarget(True, True)
|
||||
|
||||
self.assertTrue(console.verbose_enabled)
|
||||
self.assertTrue(console.debug_enabled)
|
||||
|
||||
|
||||
class FormatExTests(unittest.TestCase):
|
||||
def test_SimpleException(self):
|
||||
exception = Exception("It crashed!")
|
||||
msg = format_ex(exception)
|
||||
|
||||
self.assertEqual("Exception: It crashed!", msg)
|
||||
|
||||
def test_ComplexException(self):
|
||||
exception = Exception(
|
||||
"It crashed!\n" +
|
||||
"The error was around line 10\n" +
|
||||
" \t A. memory broken? \r\n" +
|
||||
" \t B. cpu broken?\r\n" +
|
||||
"\n")
|
||||
msg = format_ex(exception)
|
||||
|
||||
self.assertEqual(
|
||||
"Exception: It crashed! The error was around line 10 / " +
|
||||
"A. memory broken? / B. cpu broken?", msg)
|
||||
|
||||
|
||||
def send_logs(logger):
|
||||
logger.debug("Debug me")
|
||||
logger.info("Inform me")
|
||||
logger.warning("Warn me")
|
||||
logger.error("Break me")
|
Loading…
Reference in New Issue