Skip to content

Commit 2230db9

Browse files
Remove ~log table feature in favor of Python logging
Closes #1298 The ~log table is a client-side audit logging mechanism that has been superseded by modern alternatives including: - Server-side database audit logging - Infrastructure logging platforms (Datadog, CloudWatch) - Python's standard logging module Changes: - Remove Log class from table.py - Remove log property from Schema class - Replace self._log() calls with logger.info/debug - Remove special ~log handling from heading.py - Delete test_log.py Existing ~log tables in databases remain but will no longer be written to. All operations now emit through Python's logging module instead. Co-authored-by: dimitri-yatsenko <dimitri@datajoint.com>
1 parent 4eb8202 commit 2230db9

File tree

4 files changed

+8
-109
lines changed

4 files changed

+8
-109
lines changed

src/datajoint/heading.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,6 @@ def _init_from_database(self):
243243
as_dict=True,
244244
).fetchone()
245245
if info is None:
246-
if table_name == "~log":
247-
logger.warning("Could not create the ~log table")
248-
return
249246
raise DataJointError(
250247
"The table `{database}`.`{table_name}` is not defined.".format(table_name=table_name, database=database)
251248
)

src/datajoint/schemas.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .external import ExternalMapping
1212
from .heading import Heading
1313
from .settings import config
14-
from .table import FreeTable, Log, lookup_class_name
14+
from .table import FreeTable, lookup_class_name
1515
from .user_tables import Computed, Imported, Lookup, Manual, Part, _get_tier
1616
from .utils import to_camel_case, user_choice
1717

@@ -63,7 +63,6 @@ def __init__(
6363
:param add_objects: a mapping with additional objects to make available to the context in which table classes
6464
are declared.
6565
"""
66-
self._log = None
6766
self.connection = connection
6867
self.database = None
6968
self.context = context
@@ -136,7 +135,7 @@ def activate(
136135
"Schema `{name}` does not exist and could not be created. Check permissions.".format(name=schema_name)
137136
)
138137
else:
139-
self.log("created")
138+
logger.info("Created schema `%s`", schema_name)
140139
self.connection.register(self)
141140

142141
# decorate all tables already decorated
@@ -231,13 +230,6 @@ def _decorate_table(self, table_class, context, assert_declared=False):
231230
if table_class not in self._auto_populated_tables:
232231
self._auto_populated_tables.append(table_class)
233232

234-
@property
235-
def log(self):
236-
self._assert_exists()
237-
if self._log is None:
238-
self._log = Log(self.connection, self.database)
239-
return self._log
240-
241233
def __repr__(self):
242234
return "Schema `{name}`\n".format(name=self.database)
243235

@@ -420,7 +412,7 @@ def replace(s):
420412
def list_tables(self):
421413
"""
422414
Return a list of all tables in the schema except tables with ~ in first character such
423-
as ~logs and ~job
415+
as ~job
424416
425417
:return: A list of table names from the database schema.
426418
"""

src/datajoint/table.py

Lines changed: 5 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import json
66
import logging
77
import mimetypes
8-
import platform
98
import re
109
import uuid
1110
from datetime import datetime, timezone
@@ -34,7 +33,6 @@
3433
from .staged_insert import staged_insert1 as _staged_insert1
3534
from .storage import StorageBackend, build_object_path, verify_or_create_store_metadata
3635
from .utils import get_master, is_camel_case, user_choice
37-
from .version import __version__ as version
3836

3937
logger = logging.getLogger(__name__.split(".")[0])
4038

@@ -73,7 +71,6 @@ class Table(QueryExpression):
7371
"""
7472

7573
_table_name = None # must be defined in subclass
76-
_log_ = None # placeholder for the Log table object
7774

7875
# These properties must be set by the schema decorator (schemas.py) at class level
7976
# or by FreeTable at instance level
@@ -118,7 +115,7 @@ def declare(self, context=None):
118115
# skip if no create privilege
119116
pass
120117
else:
121-
self._log("Declared " + self.full_table_name)
118+
logger.info("Declared %s", self.full_table_name)
122119
# Populate lineage entries for semantic matching
123120
self._populate_lineage()
124121

@@ -153,7 +150,7 @@ def alter(self, prompt=True, context=None):
153150
self.__class__._heading = Heading(table_info=self.heading.table_info)
154151
if prompt:
155152
logger.info("Table altered")
156-
self._log("Altered " + self.full_table_name)
153+
logger.info("Altered %s", self.full_table_name)
157154

158155
def _populate_lineage(self):
159156
"""
@@ -293,16 +290,6 @@ def full_table_name(self):
293290
"""
294291
return r"`{0:s}`.`{1:s}`".format(self.database, self.table_name)
295292

296-
@property
297-
def _log(self):
298-
if self._log_ is None:
299-
self._log_ = Log(
300-
self.connection,
301-
database=self.database,
302-
skip_logging=self.table_name.startswith("~"),
303-
)
304-
return self._log_
305-
306293
@property
307294
def external(self):
308295
return self.connection.schemas[self.database].external
@@ -609,7 +596,7 @@ def delete_quick(self, get_count=False):
609596
query = "DELETE FROM " + self.full_table_name + self.where_clause()
610597
self.connection.query(query)
611598
count = self.connection.query("SELECT ROW_COUNT()").fetchone()[0] if get_count else None
612-
self._log(query[:255])
599+
logger.debug("Deleted from %s", self.full_table_name)
613600
return count
614601

615602
def delete(
@@ -787,10 +774,9 @@ def drop_quick(self):
787774
self.connection.query(query)
788775
# Clean up lineage entries
789776
delete_lineage_entries(self.connection, self.database, self.table_name)
790-
logger.info("Dropped table %s" % self.full_table_name)
791-
self._log(query[:255])
777+
logger.info("Dropped table %s", self.full_table_name)
792778
else:
793-
logger.info("Nothing to drop: table %s is not declared" % self.full_table_name)
779+
logger.info("Nothing to drop: table %s is not declared", self.full_table_name)
794780

795781
def drop(self):
796782
"""
@@ -1097,76 +1083,3 @@ def __init__(self, conn, full_table_name):
10971083

10981084
def __repr__(self):
10991085
return "FreeTable(`%s`.`%s`)\n" % (self.database, self._table_name) + super().__repr__()
1100-
1101-
1102-
class Log(Table):
1103-
"""
1104-
The log table for each schema.
1105-
Instances are callable. Calls log the time and identifying information along with the event.
1106-
1107-
:param skip_logging: if True, then log entry is skipped by default. See __call__
1108-
"""
1109-
1110-
_table_name = "~log"
1111-
1112-
def __init__(self, conn, database, skip_logging=False):
1113-
self.database = database
1114-
self.skip_logging = skip_logging
1115-
self._connection = conn
1116-
self._heading = Heading(table_info=dict(conn=conn, database=database, table_name=self.table_name, context=None))
1117-
self._support = [self.full_table_name]
1118-
1119-
self._definition = """ # event logging table for `{database}`
1120-
id :int unsigned auto_increment # event order id
1121-
---
1122-
timestamp = CURRENT_TIMESTAMP : timestamp # event timestamp
1123-
version :varchar(12) # datajoint version
1124-
user :varchar(255) # user@host
1125-
host="" :varchar(255) # system hostname
1126-
event="" :varchar(255) # event message
1127-
""".format(database=database)
1128-
1129-
super().__init__()
1130-
1131-
if not self.is_declared:
1132-
self.declare()
1133-
self.connection.dependencies.clear()
1134-
self._user = self.connection.get_user()
1135-
1136-
@property
1137-
def definition(self):
1138-
return self._definition
1139-
1140-
def __call__(self, event, skip_logging=None):
1141-
"""
1142-
1143-
:param event: string to write into the log table
1144-
:param skip_logging: If True then do not log. If None, then use self.skip_logging
1145-
"""
1146-
skip_logging = self.skip_logging if skip_logging is None else skip_logging
1147-
if not skip_logging:
1148-
try:
1149-
self.insert1(
1150-
dict(
1151-
user=self._user,
1152-
version=version + "py",
1153-
host=platform.uname().node,
1154-
event=event,
1155-
),
1156-
skip_duplicates=True,
1157-
ignore_extra_fields=True,
1158-
)
1159-
except DataJointError:
1160-
logger.info("could not log event in table ~log")
1161-
1162-
def delete(self):
1163-
"""
1164-
bypass interactive prompts and cascading dependencies
1165-
1166-
:return: number of deleted items
1167-
"""
1168-
return self.delete_quick(get_count=True)
1169-
1170-
def drop(self):
1171-
"""bypass interactive prompts and cascading dependencies"""
1172-
self.drop_quick()

tests/test_log.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)