Skip to content

Commit 6ce54f4

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 6ce54f4

File tree

2 files changed

+27
-8
lines changed

2 files changed

+27
-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: 25 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 {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} 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} 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}"):
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} 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} 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} 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} 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} -> {new_name}] deleted: {deleted}"
264+
with self._stats_updater("move", msg + f" [{nested_name} -> {nested_new_name}]"):
255265
self.backend.move(nested_name, nested_new_name)
256266

257267
def list(self, name: str, deleted: bool = False) -> Iterator[ItemInfo]:
@@ -264,8 +274,15 @@ 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} deleted: {deleted}")
267278
self._stats["list_calls"] += 1
268-
yield from self._list(name, deleted=deleted)
279+
count = 0
280+
for info in self._list(name, deleted=deleted):
281+
count += 1
282+
yield info
283+
# note: as this is a generator, we do not measure the execution time because
284+
# that would include the time needed by the caller to process the infos.
285+
logger.debug(f"borgstore list_end {name} deleted: {deleted} ({count})")
269286

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

0 commit comments

Comments
 (0)