From 48d92133b4963d7b17611725f7f09f5f7548b1dd Mon Sep 17 00:00:00 2001
From: Julian Rother <julian@jrother.eu>
Date: Wed, 17 Nov 2021 01:56:16 +0100
Subject: [PATCH] More compact INFO-level logging of SEARCH operations

---
 ldapserver/ldap.py   | 37 ++++++++++++++++++++++---------------
 ldapserver/server.py | 11 ++++++-----
 2 files changed, 28 insertions(+), 20 deletions(-)

diff --git a/ldapserver/ldap.py b/ldapserver/ldap.py
index 91d5ccd..238b9bc 100644
--- a/ldapserver/ldap.py
+++ b/ldapserver/ldap.py
@@ -42,7 +42,7 @@ class Filter(asn1.Choice, ABC):
 	'''Base class for filters in SEARCH operations'''
 
 	@abstractmethod
-	def get_filter_string(self):
+	def __str__(self):
 		raise NotImplementedError()
 
 class FilterAnd(asn1.Wrapper, Filter):
@@ -64,8 +64,8 @@ class FilterAnd(asn1.Wrapper, Filter):
 	def __init__(self, filters=None):
 		super().__init__(filters=filters)
 
-	def get_filter_string(self):
-		return '(&%s)'%(''.join([subfilter.get_filter_string() for subfilter in self.filters]))
+	def __str__(self):
+		return '(&%s)'%(''.join([str(subfilter) for subfilter in self.filters]))
 
 class FilterOr(asn1.Wrapper, Filter):
 	'''OR conjunction of multiple filters ``(|filters...)``
@@ -86,8 +86,8 @@ class FilterOr(asn1.Wrapper, Filter):
 	def __init__(self, filters=None):
 		super().__init__(filters=filters)
 
-	def get_filter_string(self):
-		return '(|%s)'%(''.join([subfilter.get_filter_string() for subfilter in self.filters]))
+	def __str__(self):
+		return '(|%s)'%(''.join([str(subfilter) for subfilter in self.filters]))
 
 class FilterNot(asn1.Sequence, Filter):
 	'''Negation of a filter ``(!filter)``
@@ -107,8 +107,8 @@ class FilterNot(asn1.Sequence, Filter):
 	def __init__(self, filter=None):
 		super().__init__(filter=filter)
 
-	def get_filter_string(self):
-		return '(!%s)'%self.filter.get_filter_string()
+	def __str__(self):
+		return '(!%s)'%str(self.filter)
 
 class FilterEqual(asn1.Sequence, Filter):
 	'''Attribute equal filter ``(attribute=value)``
@@ -130,7 +130,7 @@ class FilterEqual(asn1.Sequence, Filter):
 	def __init__(self, attribute=None, value=None):
 		super().__init__(attribute=attribute, value=value)
 
-	def get_filter_string(self):
+	def __str__(self):
 		return '(%s=%s)'%(self.attribute, escape_filter_assertionvalue(self.value))
 
 class Substring(asn1.Choice, ABC):
@@ -200,7 +200,7 @@ class FilterSubstrings(asn1.Sequence, Filter):
 			return None
 		return results[0]
 
-	def get_filter_string(self):
+	def __str__(self):
 		substrings = [self.initial_substring or b''] + self.any_substrings + [self.final_substring or b'']
 		value = '*'.join(map(escape_filter_assertionvalue, substrings))
 		return f'({self.attribute}={value})'
@@ -218,7 +218,7 @@ class FilterGreaterOrEqual(asn1.Sequence, Filter):
 	def __init__(self, attribute=None, value=None):
 		super().__init__(attribute=attribute, value=value)
 
-	def get_filter_string(self):
+	def __str__(self):
 		return '(%s>=%s)'%(self.attribute, escape_filter_assertionvalue(self.value))
 
 class FilterLessOrEqual(asn1.Sequence, Filter):
@@ -234,7 +234,7 @@ class FilterLessOrEqual(asn1.Sequence, Filter):
 	def __init__(self, attribute=None, value=None):
 		super().__init__(attribute=attribute, value=value)
 
-	def get_filter_string(self):
+	def __str__(self):
 		return '(%s<=%s)'%(self.attribute, escape_filter_assertionvalue(self.value))
 
 class FilterPresent(asn1.Wrapper, Filter):
@@ -253,7 +253,7 @@ class FilterPresent(asn1.Wrapper, Filter):
 	def __init__(self, attribute=None):
 		super().__init__(attribute=attribute)
 
-	def get_filter_string(self):
+	def __str__(self):
 		return '(%s=*)'%(self.attribute)
 
 class FilterApproxMatch(asn1.Sequence, Filter):
@@ -269,7 +269,7 @@ class FilterApproxMatch(asn1.Sequence, Filter):
 	def __init__(self, attribute=None, value=None):
 		super().__init__(attribute=attribute, value=value)
 
-	def get_filter_string(self):
+	def __str__(self):
 		return '(%s~=%s)'%(self.attribute, escape_filter_assertionvalue(self.value))
 
 class FilterExtensibleMatch(asn1.Sequence, Filter):
@@ -286,8 +286,15 @@ class FilterExtensibleMatch(asn1.Sequence, Filter):
 	matchValue: bytes
 	dnAttributes: bool
 
-	def get_filter_string(self):
-		return '(%s:%s:=%s)'%(self.matchingRule, self.type, escape_filter_assertionvalue(self.matchValue))
+	def __str__(self):
+		parts = []
+		if self.type is not None:
+			parts.append(self.type)
+		if self.dnAttributes:
+			parts.append('dn')
+		if self.matchingRule is not None:
+			parts.append(self.matchingRule)
+		return '(%s:=%s)'%(':'.join(parts), escape_filter_assertionvalue(self.matchValue))
 
 class SearchScope(enum.Enum):
 	''':any:`enum.Enum` of `scope` values in SEARCH operations'''
diff --git a/ldapserver/server.py b/ldapserver/server.py
index d653f71..e50c5e2 100644
--- a/ldapserver/server.py
+++ b/ldapserver/server.py
@@ -459,12 +459,12 @@ class LDAPRequestHandler(BaseLDAPRequestHandler):
 			self.__paged_searches[cookie] = iterator, op
 		yield ldap.SearchResultDone(ldap.LDAPResultCode.success), [build_control(cookie=cookie)]
 		time_end = time.perf_counter()
-		self.logger.info('SEARCH done page cookie=%r entries=%d duration_seconds=%.3f', cookie, entries, time_end - time_start)
+		self.logger.info('SEARCH dn=%r dn_scope=%s filter=%s attributes=%r page_cookie=%r entries=%d duration_seconds=%.3f',
+		                 op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), cookie, entries, time_end - time_start)
 
 	def handle_search(self, op, controls=None):
-		self.logger.info('SEARCH request dn=%r dn_scope=%s filter=%r, attributes=%r',
-		                 op.baseObject, op.scope.name, op.filter.get_filter_string(),
-		                 ' '.join(op.attributes))
+		self.logger.debug('SEARCH request dn=%r dn_scope=%s filter=%r attributes=%r',
+		                  op.baseObject, op.scope.name, str(op.filter), ' '.join(op.attributes))
 		paged_control = None
 		if self.supports_paged_results:
 			paged_control, controls = pop_control(controls, ldap.PAGED_RESULTS_OID)
@@ -482,7 +482,8 @@ class LDAPRequestHandler(BaseLDAPRequestHandler):
 				yield entry
 		yield ldap.SearchResultDone(ldap.LDAPResultCode.success)
 		time_end = time.perf_counter()
-		self.logger.info('SEARCH done entries=%d duration_seconds=%.3f', entries, time_end - time_start)
+		self.logger.info('SEARCH dn=%r dn_scope=%s filter=\'%s\' attributes=%r entries=%d duration_seconds=%.3f',
+		                 op.baseObject, op.scope.name, op.filter, ' '.join(op.attributes), entries, time_end - time_start)
 
 	def do_search(self, baseobj, scope, filterobj):
 		'''Do LDAP SEARCH operation
-- 
GitLab