Skip to content

Commit 3dae21e

Browse files
store: add logging for store ops, fixes #104
logs: - operation - name(s) - params like deleted - size and timing note: - logging is done at debug level, so log output is not visible with a default logger. - borgstore does not configure logging, that is the task of the application that uses borgstore.
1 parent e97e664 commit 3dae21e

File tree

2 files changed

+29
-8
lines changed

2 files changed

+29
-8
lines changed

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ API can be much simpler:
5656
[us]) and lower bandwidth (via BORGSTORE_BANDWIDTH [bit/s]) than what is
5757
actually provided by the backend.
5858

59+
Store operations (and per-op timing and volume) are logged at DEBUG log level.
60+
5961
Automatic Nesting
6062
-----------------
6163

src/borgstore/store.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
- recursive .list method
99
- soft deletion
1010
"""
11+
1112
from binascii import hexlify
1213
from collections import Counter
1314
from contextlib import contextmanager
15+
import logging
1416
import os
1517
import time
1618
from typing import Iterator, Optional
@@ -24,6 +26,8 @@
2426
from .backends.s3 import get_s3_backend
2527
from .constants import DEL_SUFFIX
2628

29+
logger = logging.getLogger(__name__)
30+
2731

2832
def get_backend(url):
2933
"""parse backend URL and return a backend instance (or None)"""
@@ -118,7 +122,7 @@ def close(self) -> None:
118122
self.backend.close()
119123

120124
@contextmanager
121-
def _stats_updater(self, key):
125+
def _stats_updater(self, key, msg):
122126
"""update call counters and overall times, also emulate latency and bandwidth"""
123127
# do not use this in generators!
124128
volume_before = self._stats_get_volume(key)
@@ -132,8 +136,10 @@ def _stats_updater(self, key):
132136
if remaining_time > 0.0:
133137
time.sleep(remaining_time)
134138
end = time.perf_counter_ns()
139+
overall_time = end - start
135140
self._stats[f"{key}_calls"] += 1
136-
self._stats[f"{key}_time"] += end - start
141+
self._stats[f"{key}_time"] += overall_time
142+
logger.debug(f"borgstore: {msg} -> {volume}B in {overall_time / 1e6:0.1f}ms")
137143

138144
def _stats_update_volume(self, key, amount):
139145
self._stats[f"{key}_volume"] += amount
@@ -196,11 +202,11 @@ def find(self, name: str, *, deleted=False) -> str:
196202
return nested_name
197203

198204
def info(self, name: str, *, deleted=False) -> ItemInfo:
199-
with self._stats_updater("info"):
205+
with self._stats_updater("info", f"info({name!r}, deleted={deleted})"):
200206
return self.backend.info(self.find(name, deleted=deleted))
201207

202208
def load(self, name: str, *, size=None, offset=0, deleted=False) -> bytes:
203-
with self._stats_updater("load"):
209+
with self._stats_updater("load", f"load({name!r}, offset={offset}, size={size}, deleted={deleted})"):
204210
result = self.backend.load(self.find(name, deleted=deleted), size=size, offset=offset)
205211
self._stats_update_volume("load", len(result))
206212
return result
@@ -209,7 +215,7 @@ def store(self, name: str, value: bytes) -> None:
209215
# note: using .find here will:
210216
# - overwrite an existing item (level stays same)
211217
# - write to the last level if no existing item is found.
212-
with self._stats_updater("store"):
218+
with self._stats_updater("store", f"store({name!r})"):
213219
self.backend.store(self.find(name), value)
214220
self._stats_update_volume("store", len(value))
215221

@@ -219,7 +225,7 @@ def delete(self, name: str, *, deleted=False) -> None:
219225
220226
See also .move(name, delete=True) for "soft" deletion.
221227
"""
222-
with self._stats_updater("delete"):
228+
with self._stats_updater("delete", f"delete({name!r}, deleted={deleted})"):
223229
self.backend.delete(self.find(name, deleted=deleted))
224230

225231
def move(
@@ -236,22 +242,26 @@ def move(
236242
# use case: keep name, but soft "delete" the item
237243
nested_name = self.find(name, deleted=False)
238244
nested_new_name = nested_name + DEL_SUFFIX
245+
msg = f"soft_delete({name!r}, deleted={deleted})"
239246
elif undelete:
240247
# use case: keep name, undelete a previously soft "deleted" item
241248
nested_name = self.find(name, deleted=True)
242249
nested_new_name = nested_name.removesuffix(DEL_SUFFIX)
250+
msg = f"soft_undelete({name!r}, deleted={deleted})"
243251
elif change_level:
244252
# use case: keep name, changing to another nesting level
245253
suffix = DEL_SUFFIX if deleted else None
246254
nested_name = self.find(name, deleted=deleted)
247255
nested_new_name = nest(name, self._get_levels(name)[-1], add_suffix=suffix)
256+
msg = f"change_level({name!r}, deleted={deleted})"
248257
else:
249258
# generic use (be careful!)
250259
if not new_name:
251260
raise ValueError("generic move needs new_name to be given.")
252261
nested_name = self.find(name, deleted=deleted)
253262
nested_new_name = self.find(new_name, deleted=deleted)
254-
with self._stats_updater("move"):
263+
msg = f"rename({name!r}, {new_name!r}, deleted={deleted})"
264+
with self._stats_updater("move", msg + f" [{nested_name!r}, {nested_new_name!r}]"):
255265
self.backend.move(nested_name, nested_new_name)
256266

257267
def list(self, name: str, deleted: bool = False) -> Iterator[ItemInfo]:
@@ -264,8 +274,17 @@ def list(self, name: str, deleted: bool = False) -> Iterator[ItemInfo]:
264274
backend.list giving us sorted names implies store.list is also sorted, if all items are stored on same level.
265275
"""
266276
# we need this wrapper due to the recursion - we only want to increment list_calls once:
277+
logger.debug(f"borgstore: list_start({name!r}, deleted={deleted})")
267278
self._stats["list_calls"] += 1
268-
yield from self._list(name, deleted=deleted)
279+
count = 0
280+
try:
281+
for info in self._list(name, deleted=deleted):
282+
count += 1
283+
yield info
284+
finally:
285+
# note: as this is a generator, we do not measure the execution time because
286+
# that would include the time needed by the caller to process the infos.
287+
logger.debug(f"borgstore: list_end({name!r}, deleted={deleted}) -> {count}")
269288

270289
def _list(self, name: str, deleted: bool = False) -> Iterator[ItemInfo]:
271290
# as the backend.list method only supports non-recursive listing and

0 commit comments

Comments
 (0)