log-foundry 0.10.2.dev55__tar.gz → 0.10.2.dev56__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/PKG-INFO +1 -1
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/_fork.py +91 -10
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/base.py +27 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/file.py +97 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/LICENSE +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/README.md +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/_diag.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/_lifecycle.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/decorator.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/results.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/_retry.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/memory.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/null.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/worker.py +0 -0
|
@@ -330,7 +330,43 @@ def _fresh_primitive(value: Any, memo: dict[int, Any], keepalive: list[Any]) ->
|
|
|
330
330
|
return fresh
|
|
331
331
|
|
|
332
332
|
|
|
333
|
-
|
|
333
|
+
_DISCARD_HOOK = "discard_buffered_after_fork"
|
|
334
|
+
"""The optional sink member a forked child asks to strand the parent's pending bytes.
|
|
335
|
+
|
|
336
|
+
Probed by name, as ``losses()`` and ``log_foundry_stop_signal`` are, so no existing sink stops
|
|
337
|
+
satisfying ``Sink`` by not having it. ``sinks/base.py`` states the contract; this is the only
|
|
338
|
+
place the name is read.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _offers_discard(holder: object) -> bool:
|
|
343
|
+
"""Whether this object carries FR-004's buffer hook, without letting the question raise.
|
|
344
|
+
|
|
345
|
+
Every other read the walk makes is guarded individually, and this one has to be too: an
|
|
346
|
+
owned ``__getattr__`` raising anything but ``AttributeError`` would abort the walk, and
|
|
347
|
+
what is lost then is not a buffer but the **lock repair** for everything the walk had not
|
|
348
|
+
reached yet — the hang this module exists to remove, arrived at through the probe for a
|
|
349
|
+
different hazard. Nothing owned defines ``__getattr__`` today.
|
|
350
|
+
|
|
351
|
+
Callability is part of the question, so an attribute of that name which is not a method is
|
|
352
|
+
simply not the hook.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
holder: Any object the walk has entered.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Whether it offers the hook.
|
|
359
|
+
|
|
360
|
+
Raises:
|
|
361
|
+
None.
|
|
362
|
+
"""
|
|
363
|
+
try:
|
|
364
|
+
return callable(getattr(holder, _DISCARD_HOOK, None))
|
|
365
|
+
except Exception:
|
|
366
|
+
return False
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _reinit_primitives() -> list[Any]:
|
|
334
370
|
"""Replaces every lock and event this package owns, wherever the walk reaches one.
|
|
335
371
|
|
|
336
372
|
An inherited ``Lock`` stays locked with no owner — measured, ``acquire(timeout=1)`` returns
|
|
@@ -345,11 +381,17 @@ def _reinit_primitives() -> None:
|
|
|
345
381
|
events measured 202 ms, against 0.45 ms idle. That is accepted rather than bounded: a cap
|
|
346
382
|
would be a lock this cannot promise to find, and the alternative to finding it is a hang.
|
|
347
383
|
|
|
384
|
+
**The sinks carrying FR-004's buffer hook are collected on this same walk** rather than by
|
|
385
|
+
a second one, because the traversal is the expensive part and its cost is proportional to
|
|
386
|
+
caller data — paying 202 ms twice on a process that forks is a worse trade than one function
|
|
387
|
+
reporting what it passed. Collected, not called: a hook may take a lock, and the contract is
|
|
388
|
+
that every lock is the child's own before any of them runs (FR-001 AC-2).
|
|
389
|
+
|
|
348
390
|
Args:
|
|
349
391
|
None.
|
|
350
392
|
|
|
351
393
|
Returns:
|
|
352
|
-
|
|
394
|
+
The owned instances carrying :data:`_DISCARD_HOOK`, in the order the walk reached them.
|
|
353
395
|
|
|
354
396
|
Raises:
|
|
355
397
|
None.
|
|
@@ -357,6 +399,7 @@ def _reinit_primitives() -> None:
|
|
|
357
399
|
memo: dict[int, Any] = {}
|
|
358
400
|
keepalive: list[Any] = []
|
|
359
401
|
seen: set[int] = set()
|
|
402
|
+
buffered: list[Any] = []
|
|
360
403
|
stack: list[Any] = [
|
|
361
404
|
module
|
|
362
405
|
for name, module in list(sys.modules.items())
|
|
@@ -369,14 +412,49 @@ def _reinit_primitives() -> None:
|
|
|
369
412
|
seen.add(id(holder))
|
|
370
413
|
if _is_container(holder):
|
|
371
414
|
stack.extend(child for child in _container_children(holder) if _is_traversable(child))
|
|
372
|
-
if not isinstance(holder, types.ModuleType | type)
|
|
373
|
-
|
|
415
|
+
if not isinstance(holder, types.ModuleType | type):
|
|
416
|
+
if not _is_owned(holder):
|
|
417
|
+
continue
|
|
418
|
+
if _offers_discard(holder):
|
|
419
|
+
buffered.append(holder)
|
|
374
420
|
for name, value in _namespace_items(holder):
|
|
375
421
|
fresh = _fresh_primitive(value, memo, keepalive)
|
|
376
422
|
if fresh is not None:
|
|
377
423
|
_assign(holder, name, fresh)
|
|
378
424
|
elif _is_traversable(value):
|
|
379
425
|
stack.append(value)
|
|
426
|
+
return buffered
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _discard_buffers(holders: list[Any]) -> None:
|
|
430
|
+
"""Asks each sink that owns a buffered stream to strand what it inherited (FR-004).
|
|
431
|
+
|
|
432
|
+
A fork landing inside ``emit`` — after the write loop, before the flush — leaves the child
|
|
433
|
+
holding the parent's unflushed bytes, which both processes then write: measured, the event
|
|
434
|
+
at the fork point appeared on disk twice. Without a ``before`` handler there is nowhere to
|
|
435
|
+
empty the buffer from (FR-001), so the child throws its copy away instead.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
holders: What :func:`_reinit_primitives` collected on its walk.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
None.
|
|
442
|
+
|
|
443
|
+
Raises:
|
|
444
|
+
None. One sink's failure is absorbed separately from the rest, so a hook that cannot
|
|
445
|
+
strand its buffer costs that sink a duplicated batch rather than costing every other
|
|
446
|
+
sink its repair — and the consequence is named, because an absorbed failure is
|
|
447
|
+
invisible apart from that line.
|
|
448
|
+
"""
|
|
449
|
+
for holder in holders:
|
|
450
|
+
try:
|
|
451
|
+
getattr(holder, _DISCARD_HOOK)()
|
|
452
|
+
except Exception as exc:
|
|
453
|
+
_diag.absorbed(
|
|
454
|
+
"discarding an inherited buffer after a fork",
|
|
455
|
+
exc,
|
|
456
|
+
f"{type(holder).__name__} may write the parent's pending bytes again",
|
|
457
|
+
)
|
|
380
458
|
|
|
381
459
|
|
|
382
460
|
def register_child_handler(fn: Callable[[], None]) -> None:
|
|
@@ -414,11 +492,12 @@ def _reinit_after_fork() -> None:
|
|
|
414
492
|
"""Repairs the library in a child that has just returned from ``fork``.
|
|
415
493
|
|
|
416
494
|
**The order of work here is the contract** (FR-001 AC-2): locks and events first, then the
|
|
417
|
-
registered handlers. A lock re-initialised *after* a handler that
|
|
418
|
-
that hangs, and it hangs on the child's only thread with nothing to
|
|
419
|
-
must happen before *any* handler
|
|
420
|
-
— the
|
|
421
|
-
``decorator``'s rebuild, which has started a live drain thread by then
|
|
495
|
+
buffer discard, then the registered handlers. A lock re-initialised *after* a handler that
|
|
496
|
+
takes it is a handler that hangs, and it hangs on the child's only thread with nothing to
|
|
497
|
+
interrupt it. Work that must happen before *any* handler stays inline between the two steps
|
|
498
|
+
rather than being registered — the discard is the case, and registering it would put it
|
|
499
|
+
after ``decorator``'s rebuild, which has started a live drain thread by then, emitting into
|
|
500
|
+
the very sink whose buffer is still the parent's.
|
|
422
501
|
|
|
423
502
|
Args:
|
|
424
503
|
None.
|
|
@@ -436,10 +515,12 @@ def _reinit_after_fork() -> None:
|
|
|
436
515
|
registers another simply causes it to run — and :func:`register_child_handler` is the
|
|
437
516
|
only writer, appending only.
|
|
438
517
|
"""
|
|
518
|
+
buffered: list[Any] = []
|
|
439
519
|
try:
|
|
440
|
-
_reinit_primitives()
|
|
520
|
+
buffered = _reinit_primitives()
|
|
441
521
|
except Exception as exc:
|
|
442
522
|
_diag.absorbed("repairing the library after a fork", exc, "this child may block or lose")
|
|
523
|
+
_discard_buffers(buffered)
|
|
443
524
|
for handler in _child_handlers:
|
|
444
525
|
try:
|
|
445
526
|
handler()
|
|
@@ -89,6 +89,33 @@ class Sink(Protocol):
|
|
|
89
89
|
already used, with a ``threading.Event``. Reproduced. A prefixed name cannot collide by
|
|
90
90
|
accident, and the cost is one verbose attribute on the sinks that want interruptibility.
|
|
91
91
|
|
|
92
|
+
A fifth is optional in the same way and exists for one event only:
|
|
93
|
+
``discard_buffered_after_fork() -> None``. A sink that owns a **buffered** stream — one it
|
|
94
|
+
opened itself, rather than the process's ``sys.stdout`` — inherits the parent's unflushed
|
|
95
|
+
bytes in a forked child, and both processes then write them (SPEC-039 FR-004). Defining this
|
|
96
|
+
method is how a sink says it can strand what it inherited; a sink holding no buffer of its
|
|
97
|
+
own needs nothing here. It is *called* rather than assigned, which is why it carries no
|
|
98
|
+
``log_foundry_`` prefix: the collision the fourth member's name guards against is an
|
|
99
|
+
attribute the library writes onto an object it does not own.
|
|
100
|
+
|
|
101
|
+
**Which sinks are actually asked is narrower than "whichever define it", and the boundary is
|
|
102
|
+
the same one every fork repair has.** The child's repair walks this package's own objects —
|
|
103
|
+
a sink whose class is defined here or **inherits** one that is, ``Sink`` included, and the
|
|
104
|
+
**owned** sinks such an object holds, ``MultiSink``'s children among them. A third-party
|
|
105
|
+
sink satisfying this Protocol *structurally*, which is how every shipped sink satisfies it,
|
|
106
|
+
is outside that walk exactly as its locks are (SPEC-039 FR-005): its hook is never called,
|
|
107
|
+
and a fork mid-batch there can still duplicate — including when a wrapper this library owns
|
|
108
|
+
is holding it. Inheriting from ``Sink`` or from a shipped sink is what brings it inside. A
|
|
109
|
+
**wrapper** sink the walk enters need do nothing itself, because the owned sinks it holds
|
|
110
|
+
are reached directly rather than through it.
|
|
111
|
+
|
|
112
|
+
**It runs in a child that has not yet returned from ``fork``, on that child's only thread,
|
|
113
|
+
and must not block.** Nothing else is running there, so there is no holder a lock could be
|
|
114
|
+
waiting for — and no watchdog to end a wait taken anyway, since a pending alarm does not
|
|
115
|
+
survive the fork and this runs before any application code can arm one. It may raise: the
|
|
116
|
+
library absorbs and announces that, and the cost is a child that may write the parent's
|
|
117
|
+
pending bytes a second time.
|
|
118
|
+
|
|
92
119
|
"Safe to call during an emit" is a concurrency requirement once :meth:`emit` is (SPEC-028
|
|
93
120
|
FR-003). The shipped sinks keep their loss counters under a **dedicated** lock, separate
|
|
94
121
|
from whatever guards their transport: an increment is a read-modify-write that Python does
|
|
@@ -20,6 +20,46 @@ _WHEN_SECONDS = {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
def _reopen_discarding(stream: TextIO, path: str, encoding: str) -> TextIO:
|
|
24
|
+
"""Strands an inherited stream's pending bytes and returns a fresh one on the same path.
|
|
25
|
+
|
|
26
|
+
``os.dup2`` points the inherited descriptor at ``os.devnull``, so the buffer this process
|
|
27
|
+
inherited can only ever reach the null device — whether it is flushed deliberately, by the
|
|
28
|
+
interpreter at exit, or by the garbage collector when the old object is dropped. The two
|
|
29
|
+
steps are written in this order for readability and **not** because the order is what makes
|
|
30
|
+
it safe: the replacement takes a different descriptor either way, since the inherited one is
|
|
31
|
+
still occupied, and nothing else is running to flush anything in between. Reopening rather
|
|
32
|
+
than reusing the descriptor is what gives the child a stream of its own, and it picks up the
|
|
33
|
+
currently active file if the parent rotated.
|
|
34
|
+
|
|
35
|
+
``dup2`` leaves the redirected descriptor **inheritable** across an ``exec``, where the one
|
|
36
|
+
``open`` produced carried ``O_CLOEXEC``. That is a behaviour change and it is harmless: the
|
|
37
|
+
descriptor names the null device, so what an exec'd process inherits is a handle on nothing.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
stream: The inherited stream, still holding whatever the parent had not flushed.
|
|
41
|
+
path: The file to reopen. **Append mode**, never write mode: the child shares this file
|
|
42
|
+
with the parent and with whatever was written before either existed, so truncating here
|
|
43
|
+
would destroy a log to protect it — strictly worse than the duplication this prevents.
|
|
44
|
+
encoding: The text encoding to open it in, carried across so a child does not start
|
|
45
|
+
writing a differently-encoded second half into the parent's file.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The replacement stream.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
OSError: If the descriptor cannot be redirected or the path cannot be reopened.
|
|
52
|
+
ValueError: If the stream has no usable descriptor, which means there is no inherited
|
|
53
|
+
buffer this can strand and the caller must not carry on as though there were.
|
|
54
|
+
"""
|
|
55
|
+
devnull = os.open(os.devnull, os.O_WRONLY)
|
|
56
|
+
try:
|
|
57
|
+
os.dup2(devnull, stream.fileno())
|
|
58
|
+
finally:
|
|
59
|
+
os.close(devnull)
|
|
60
|
+
return open(path, "a", encoding=encoding)
|
|
61
|
+
|
|
62
|
+
|
|
23
63
|
class FileSink:
|
|
24
64
|
"""A :class:`~log_foundry.sinks.base.Sink` that appends events as NDJSON to one file.
|
|
25
65
|
|
|
@@ -84,6 +124,35 @@ class FileSink:
|
|
|
84
124
|
self._stream.write(json.dumps(event) + "\n")
|
|
85
125
|
self._stream.flush()
|
|
86
126
|
|
|
127
|
+
def discard_buffered_after_fork(self) -> None:
|
|
128
|
+
"""Throws away the parent's unflushed bytes in a forked child (SPEC-039 FR-004).
|
|
129
|
+
|
|
130
|
+
``emit`` writes a whole batch into a **buffered** stream and flushes once at the end, so
|
|
131
|
+
a fork landing inside it leaves both processes holding the same pending bytes and both
|
|
132
|
+
writing them: measured, the event at the fork point appeared on disk twice. Without a
|
|
133
|
+
``before`` handler there is nowhere to empty the buffer from (FR-001), so the child
|
|
134
|
+
strands it instead — the parent's copy is untouched and still reaches disk exactly once.
|
|
135
|
+
|
|
136
|
+
No lock is taken, for the reason ``decorator._rebuild_worker_after_fork`` gives: there is
|
|
137
|
+
one thread here by construction, and the lock was re-initialised moments earlier, so
|
|
138
|
+
taking it could only wait on a holder that cannot exist. A hook that blocks here blocks a
|
|
139
|
+
child that has not yet returned from ``fork``, where no watchdog can reach it.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
None.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
None.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
OSError: If the descriptor cannot be redirected or the file cannot be reopened.
|
|
149
|
+
ValueError: If the stream has no usable descriptor. ``_fork`` absorbs and announces
|
|
150
|
+
either, since a child that cannot strand its buffer still has working locks.
|
|
151
|
+
"""
|
|
152
|
+
if self._closed:
|
|
153
|
+
return
|
|
154
|
+
self._stream = _reopen_discarding(self._stream, self._path, self._encoding)
|
|
155
|
+
|
|
87
156
|
def close(self) -> None:
|
|
88
157
|
"""Flushes and closes the file handle, with a second call a no-op (FR-001).
|
|
89
158
|
|
|
@@ -221,6 +290,34 @@ class RotatingFileSink:
|
|
|
221
290
|
self._size += data
|
|
222
291
|
self._stream.flush()
|
|
223
292
|
|
|
293
|
+
def discard_buffered_after_fork(self) -> None:
|
|
294
|
+
"""Throws away the parent's unflushed bytes in a forked child (SPEC-039 FR-004).
|
|
295
|
+
|
|
296
|
+
Identical to :meth:`FileSink.discard_buffered_after_fork` and measured on this class
|
|
297
|
+
too, because the window is the same one: a whole batch written into a buffered stream
|
|
298
|
+
and flushed once at the end.
|
|
299
|
+
|
|
300
|
+
``_size`` is deliberately left as the parent set it. It counts bytes this sink believes
|
|
301
|
+
are in the active file, and the moment two processes append to one path that is an
|
|
302
|
+
approximation whichever way it is computed (FR-005 AC-1) — re-reading the file's size
|
|
303
|
+
here would claim a precision a shared handle cannot support, and the only consequence of
|
|
304
|
+
the stale count is a rotation that fires marginally early.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
None.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
None.
|
|
311
|
+
|
|
312
|
+
Raises:
|
|
313
|
+
OSError: If the descriptor cannot be redirected or the file cannot be reopened.
|
|
314
|
+
ValueError: If the stream has no usable descriptor. ``_fork`` absorbs and announces
|
|
315
|
+
either, since a child that cannot strand its buffer still has working locks.
|
|
316
|
+
"""
|
|
317
|
+
if self._closed:
|
|
318
|
+
return
|
|
319
|
+
self._stream = _reopen_discarding(self._stream, self._path, self._encoding)
|
|
320
|
+
|
|
224
321
|
def close(self) -> None:
|
|
225
322
|
"""Flushes and closes the active handle, with a second call a no-op (FR-002).
|
|
226
323
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{log_foundry-0.10.2.dev55 → log_foundry-0.10.2.dev56}/src/log_foundry/sinks/elasticsearch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|