Skip to content

Commit 57547c0

Browse files
authored
Merge pull request #60 from alan-turing-institute/better-logging
Better logging
2 parents dc1a7f5 + df32e4b commit 57547c0

10 files changed

+224
-171
lines changed

apricot/apricot_server.py

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from __future__ import annotations
22

33
import inspect
4-
import sys
4+
import logging
55
from typing import TYPE_CHECKING, Any, Self, cast
66

77
from twisted.internet import reactor, task
88
from twisted.internet.endpoints import quoteStringArgument, serverFromString
9+
from twisted.logger import Logger
910
from twisted.python import log
1011

1112
from apricot.cache import LocalCache, RedisCache, UidCache
@@ -59,44 +60,58 @@ def __init__(
5960
@param tls_certificate: TLS certificate for LDAPS
6061
@param tls_private_key: TLS private key for LDAPS
6162
"""
62-
self.debug = debug
63+
# Set up Python root logger
64+
logging.basicConfig(
65+
level=logging.INFO,
66+
datefmt=r"%Y-%m-%d %H:%M:%S",
67+
format=r"%(asctime)s [%(levelname)-8s] %(message)s",
68+
)
69+
if debug:
70+
logging.getLogger("apricot").setLevel(logging.DEBUG)
6371

64-
# Log to stdout
65-
log.startLogging(sys.stdout)
72+
# Configure Twisted loggers to write to Python logging
73+
observer = log.PythonLoggingObserver("apricot")
74+
observer.start()
75+
self.logger = Logger()
76+
77+
# Load the Twisted reactor
78+
self.reactor = cast("IReactorCore", reactor)
6679

6780
# Initialise the UID cache
6881
uid_cache: UidCache
6982
if redis_host and redis_port:
70-
log.msg(
71-
f"Using a Redis user-id cache at host '{redis_host}' on port '{redis_port}'.",
83+
self.logger.info(
84+
"Using a Redis user-id cache at host '{host}' on port '{port}'.",
85+
host=redis_host,
86+
port=redis_port,
7287
)
7388
uid_cache = RedisCache(redis_host=redis_host, redis_port=redis_port)
7489
else:
75-
log.msg("Using a local user-id cache.")
90+
self.logger.info("Using a local user-id cache.")
7691
uid_cache = LocalCache()
7792

7893
# Initialise the appropriate OAuth client
7994
try:
80-
if self.debug:
81-
log.msg(f"Creating an OAuthClient for {backend}.")
95+
self.logger.debug(
96+
"Creating an OAuthClient for the {backend} backend.",
97+
backend=backend.value,
98+
)
8299
oauth_backend = OAuthClientMap[backend]
83100
oauth_backend_args = inspect.getfullargspec(
84101
oauth_backend.__init__, # type: ignore[misc]
85102
).args
86103
oauth_client = oauth_backend(
87104
client_id=client_id,
88105
client_secret=client_secret,
89-
debug=debug,
90106
uid_cache=uid_cache,
91107
**{k: v for k, v in kwargs.items() if k in oauth_backend_args},
92108
)
93109
except Exception as exc:
94-
msg = f"Could not construct an OAuth client for the '{backend}' backend.\n{exc!s}"
110+
msg = f"Could not construct an OAuth client for the {backend.value} backend.\n{exc!s}"
95111
raise ValueError(msg) from exc
96112

97113
# Initialise the OAuth data adaptor
98-
if self.debug:
99-
log.msg("Creating an OAuthDataAdaptor.")
114+
self.logger.debug("Creating an OAuthDataAdaptor.")
100115
oauth_adaptor = OAuthDataAdaptor(
101116
domain,
102117
oauth_client,
@@ -105,9 +120,8 @@ def __init__(
105120
enable_user_domain_verification=enable_user_domain_verification,
106121
)
107122

108-
# Create an LDAPServerFactory
109-
if self.debug:
110-
log.msg("Creating an LDAPServerFactory.")
123+
# Create an OAuthLDAPServerFactory
124+
self.logger.debug("Creating an OAuthLDAPServerFactory.")
111125
factory = OAuthLDAPServerFactory(
112126
oauth_adaptor,
113127
oauth_client,
@@ -116,17 +130,16 @@ def __init__(
116130
)
117131

118132
if background_refresh:
119-
if self.debug:
120-
log.msg(
121-
f"Starting background refresh (interval={refresh_interval})",
122-
)
133+
self.logger.info(
134+
"Starting background refresh (interval={interval})",
135+
interval=refresh_interval,
136+
)
123137
loop = task.LoopingCall(factory.adaptor.refresh)
124138
loop.start(refresh_interval)
125139

126140
# Attach a listening endpoint
127-
if self.debug:
128-
log.msg("Attaching a listening endpoint (plain).")
129-
endpoint: IStreamServerEndpoint = serverFromString(reactor, f"tcp:{port}")
141+
self.logger.info("Listening for LDAP requests on port {port}.", port=port)
142+
endpoint: IStreamServerEndpoint = serverFromString(self.reactor, f"tcp:{port}")
130143
endpoint.listen(factory)
131144

132145
# Attach a listening endpoint
@@ -137,19 +150,16 @@ def __init__(
137150
if not tls_private_key:
138151
msg = "No TLS private key provided. Please provide one with --tls-private-key or disable TLS."
139152
raise ValueError(msg)
140-
if self.debug:
141-
log.msg("Attaching a listening endpoint (TLS).")
153+
self.logger.info(
154+
"Listening for LDAPS requests on port {port}.",
155+
port=tls_port,
156+
)
142157
ssl_endpoint: IStreamServerEndpoint = serverFromString(
143-
reactor,
158+
self.reactor,
144159
f"ssl:{tls_port}:privateKey={quoteStringArgument(tls_private_key)}:certKey={quoteStringArgument(tls_certificate)}",
145160
)
146161
ssl_endpoint.listen(factory)
147162

148-
# Load the Twisted reactor
149-
self.reactor = cast("IReactorCore", reactor)
150-
151163
def run(self: Self) -> None:
152164
"""Start the Twisted reactor."""
153-
if self.debug:
154-
log.msg("Starting the Twisted reactor.")
155165
self.reactor.run()

apricot/ldap/oauth_ldap_entry.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
LDAPInvalidCredentials,
1313
)
1414
from twisted.internet import defer
15-
from twisted.python import log
15+
from twisted.logger import Logger
1616

1717
from apricot.oauth import LDAPAttributeDict, OAuthClient
1818

@@ -35,6 +35,7 @@ def __init__(
3535
@param attributes: Attributes of the object.
3636
@param oauth_client: An OAuth client used for binding
3737
"""
38+
self.logger = Logger()
3839
self.oauth_client_ = oauth_client
3940
if not isinstance(dn, DistinguishedName):
4041
dn = DistinguishedName(stringValue=dn)
@@ -73,7 +74,10 @@ def add_child(
7374
try:
7475
output = self.addChild(rdn, attributes)
7576
except LDAPEntryAlreadyExists:
76-
log.msg(f"Refusing to add child '{rdn.getText()}' as it already exists.")
77+
self.logger.warn(
78+
"Refusing to add child '{child}' as it already exists.",
79+
child=rdn.getText(),
80+
)
7781
output = self._children[rdn.getText()]
7882
return cast("OAuthLDAPEntry", output)
7983

@@ -84,6 +88,7 @@ def _bind(password: bytes) -> OAuthLDAPEntry:
8488
if self.oauth_client.verify(username=oauth_username, password=s_password):
8589
return self
8690
msg = f"Invalid password for user '{oauth_username}'."
91+
self.logger.error(msg)
8792
raise LDAPInvalidCredentials(msg)
8893

8994
return defer.maybeDeferred(_bind, password)

apricot/ldap/oauth_ldap_server_factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ def buildProtocol(self: Self, addr: IAddress) -> Protocol: # noqa: N802
4646
@param addr: an object implementing L{IAddress}
4747
"""
4848
id(addr) # ignore unused arguments
49-
proto = ReadOnlyLDAPServer(debug=self.adaptor.debug)
49+
proto = ReadOnlyLDAPServer()
5050
proto.factory = self.adaptor
5151
return proto

apricot/ldap/oauth_ldap_tree.py

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55

66
from ldaptor.interfaces import IConnectedLDAPEntry, ILDAPEntry
77
from ldaptor.protocols.ldap.distinguishedname import DistinguishedName
8-
from twisted.python import log
8+
from twisted.logger import Logger
99
from zope.interface import implementer
1010

1111
from apricot.ldap.oauth_ldap_entry import OAuthLDAPEntry
1212

1313
if TYPE_CHECKING:
1414
from twisted.internet import defer
15+
from twisted.python.failure import Failure
1516

1617
from apricot.oauth import OAuthClient, OAuthDataAdaptor
1718

@@ -36,8 +37,8 @@ def __init__(
3637
@param refresh_interval: Interval in seconds after which the tree must be refreshed
3738
"""
3839
self.background_refresh = background_refresh
39-
self.debug = oauth_client.debug
4040
self.last_update = time.monotonic()
41+
self.logger = Logger()
4142
self.oauth_adaptor = oauth_adaptor
4243
self.oauth_client = oauth_client
4344
self.refresh_interval = refresh_interval
@@ -66,15 +67,37 @@ def __repr__(self: Self) -> str:
6667
return f"{self.__class__.__name__} with backend {self.oauth_client.__class__.__name__}"
6768

6869
def lookup(self: Self, dn: DistinguishedName | str) -> defer.Deferred[ILDAPEntry]:
69-
"""Lookup the referred to by dn.
70+
"""Lookup a DistinguishedName in the LDAP tree.
7071
7172
@return: A Deferred returning an ILDAPEntry.
7273
"""
74+
75+
def result_callback(ldap_entry: OAuthLDAPEntry | None) -> OAuthLDAPEntry | None:
76+
if ldap_entry:
77+
self.logger.debug(
78+
"LDAP lookup succeeded: found {dn}",
79+
dn=ldap_entry.dn.getText(),
80+
)
81+
return ldap_entry
82+
83+
def failure_callback(failure: Failure) -> Failure:
84+
self.logger.debug(
85+
"LDAP lookup failed: {error}",
86+
error=failure.getErrorMessage(),
87+
)
88+
return failure
89+
90+
# Construct a complete DN
7391
if not isinstance(dn, DistinguishedName):
7492
dn = DistinguishedName(stringValue=dn)
75-
if self.debug:
76-
log.msg(f"Starting an LDAP lookup for '{dn.getText()}'.")
77-
return self.root.lookup(dn)
93+
self.logger.info("Starting an LDAP lookup for '{dn}'.", dn=dn.getText())
94+
95+
# Attach debug callbacks to the lookup and return
96+
return (
97+
self.root.lookup(dn)
98+
.addErrback(failure_callback)
99+
.addCallback(result_callback)
100+
)
78101

79102
def refresh(self: Self) -> None:
80103
"""Refresh the LDAP tree."""
@@ -83,11 +106,11 @@ def refresh(self: Self) -> None:
83106
or (time.monotonic() - self.last_update) > self.refresh_interval
84107
):
85108
# Update users and groups from the OAuth server
86-
log.msg("Retrieving OAuth data.")
109+
self.logger.info("Retrieving OAuth data.")
87110
oauth_groups, oauth_users = self.oauth_adaptor.retrieve_all()
88111

89112
# Create a root node for the tree
90-
log.msg("Rebuilding LDAP tree.")
113+
self.logger.info("Rebuilding LDAP tree.")
91114
self.root_ = OAuthLDAPEntry(
92115
dn=self.oauth_adaptor.root_dn,
93116
attributes={"objectClass": ["dcObject"]},
@@ -105,31 +128,38 @@ def refresh(self: Self) -> None:
105128
)
106129

107130
# Add groups to the groups OU
108-
if self.debug:
109-
log.msg(
110-
f"Attempting to add {len(oauth_groups)} groups to the LDAP tree.",
111-
)
131+
self.logger.debug(
132+
"Attempting to add {n_groups} groups to the LDAP tree.",
133+
n_groups=len(oauth_groups),
134+
)
112135
for group_attrs in oauth_groups:
113136
groups_ou.add_child(f"CN={group_attrs.cn}", group_attrs.to_dict())
114-
if self.debug:
115-
children = groups_ou.list_children()
116-
for child in children:
117-
log.msg(f"... {child.dn.getText()}")
118-
log.msg(f"There are {len(children)} groups in the LDAP tree.")
137+
ldap_groups = groups_ou.list_children()
138+
self.logger.info(
139+
"There are {n_groups} groups in the LDAP tree.",
140+
n_groups=len(ldap_groups),
141+
)
142+
for ldap_group in ldap_groups:
143+
self.logger.debug(
144+
"... {ldap_group}",
145+
ldap_group=ldap_group.dn.getText(),
146+
)
119147

120148
# Add users to the users OU
121-
if self.debug:
122-
log.msg(
123-
f"Attempting to add {len(oauth_users)} users to the LDAP tree.",
124-
)
149+
self.logger.debug(
150+
"Attempting to add {n_users} users to the LDAP tree.",
151+
n_users=len(oauth_users),
152+
)
125153
for user_attrs in oauth_users:
126154
users_ou.add_child(f"CN={user_attrs.cn}", user_attrs.to_dict())
127-
if self.debug:
128-
children = users_ou.list_children()
129-
for child in children:
130-
log.msg(f"... {child.dn.getText()}")
131-
log.msg(f"There are {len(children)} users in the LDAP tree.")
155+
ldap_users = users_ou.list_children()
156+
self.logger.info(
157+
"There are {n_users} users in the LDAP tree.",
158+
n_users=len(ldap_users),
159+
)
160+
for ldap_user in ldap_users:
161+
self.logger.debug("... {ldap_user}", ldap_user=ldap_user.dn.getText())
132162

133163
# Set last updated time
134-
log.msg("Finished building LDAP tree.")
164+
self.logger.info("Finished building LDAP tree.")
135165
self.last_update = time.monotonic()

0 commit comments

Comments
 (0)