---
title: session_logger
---

<div class="python-reference">
<a id="fastvideo.entrypoints.streaming.session_logger-classes"></a>
<a id="fastvideo.entrypoints.streaming.session_logger"></a>
<p>Per-session JSONL event logger.</p>
<p>Each session gets its own JSONL file under the configured log root so
post-hoc analytics (enhancer latency, GPU assignment, segment timings)
can be recovered without a tracing backend. The internal UI uses this
format; keeping the same shape makes log tooling portable.</p>
<h2 id="fastvideo.entrypoints.streaming.session_logger.SessionLogEvent"><code>fastvideo.entrypoints.streaming.session_logger.SessionLogEvent</code></h2>
<pre class="doc-signature"><code>SessionLogEvent(session_id: str, event: str, payload: dict&#91;str, Any&#93; = dict(), ts: float = time.time())</code></pre>
<p>One line in the session JSONL file.</p>

<details><summary>Source</summary><pre><code>@dataclass
class SessionLogEvent:
    &quot;&quot;&quot;One line in the session JSONL file.&quot;&quot;&quot;

    session_id: str
    event: str
    payload: dict&#91;str, Any&#93; = field(default_factory=dict)
    ts: float = field(default_factory=time.time)</code></pre></details>
<h2 id="fastvideo.entrypoints.streaming.session_logger.SessionLogger"><code>fastvideo.entrypoints.streaming.session_logger.SessionLogger</code></h2>
<pre class="doc-signature"><code>SessionLogger(log_dir: str | None)</code></pre>
<p>Append-only JSONL logger keyed by session id.</p>
<p>Thread-safe; the server may be writing from multiple asyncio tasks
(fMP4 encoder thread + control-frame handler) for the same session.</p>

<details><summary>Source</summary><pre><code>class SessionLogger:
    &quot;&quot;&quot;Append-only JSONL logger keyed by session id.

    Thread-safe; the server may be writing from multiple asyncio tasks
    (fMP4 encoder thread + control-frame handler) for the same session.
    &quot;&quot;&quot;

    def __init__(self, log_dir: str | None) -&gt; None:
        self._log_dir = log_dir
        self._files: dict&#91;str, TextIO&#93; = &#123;&#125;
        self._locks: dict&#91;str, threading.Lock&#93; = &#123;&#125;
        self._registry_lock = threading.Lock()
        self._ensure_dir()

    def log(self, event: SessionLogEvent) -&gt; None:
        if self._log_dir is None:
            return
        opened = self._get_file(event.session_id)
        if opened is None:
            return
        handle, lock = opened
        line = json.dumps(&#123;
            &quot;session_id&quot;: event.session_id,
            &quot;event&quot;: event.event,
            &quot;ts&quot;: event.ts,
            &quot;payload&quot;: event.payload,
        &#125;)
        with lock, contextlib.suppress(ValueError):
            handle.write(line + &quot;\n&quot;)
            handle.flush()

    def close(self, session_id: str) -&gt; None:
        with self._registry_lock:
            handle = self._files.pop(session_id, None)
            lock = self._locks.pop(session_id, None)
        if handle is None or lock is None:
            return
        with lock, contextlib.suppress(Exception):
            handle.close()

    def close_all(self) -&gt; None:
        with self._registry_lock:
            sids = list(self._files)
        for sid in sids:
            self.close(sid)

    def _ensure_dir(self) -&gt; None:
        if self._log_dir is None:
            return
        os.makedirs(self._log_dir, exist_ok=True)

    def _get_file(self, session_id: str) -&gt; tuple&#91;TextIO, threading.Lock&#93; | None:
        if self._log_dir is None:
            return None
        with self._registry_lock:
            handle = self._files.get(session_id)
            lock = self._locks.get(session_id)
            if handle is not None and lock is not None:
                return handle, lock
            # Defense-in-depth: session_id is server-generated UUID today,
            # but sanitize against path traversal in case future code paths
            # allow client-supplied ids.
            safe_id = _FILENAME_SANITIZE_RE.sub(&quot;_&quot;, session_id) or &quot;unknown&quot;
            path = os.path.join(
                self._log_dir,
                f&quot;session-&#123;safe_id&#125;.jsonl&quot;,
            )
            try:
                handle = open(path, &quot;a&quot;, encoding=&quot;utf-8&quot;)  # noqa: SIM115
            except OSError:
                return None
            lock = threading.Lock()
            self._files&#91;session_id&#93; = handle
            self._locks&#91;session_id&#93; = lock
            return handle, lock</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.session_logger.SessionLogger.log"><code>fastvideo.entrypoints.streaming.session_logger.SessionLogger.log</code></h3>
<pre class="doc-signature"><code>log(event: SessionLogEvent) -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def log(self, event: SessionLogEvent) -&gt; None:
    if self._log_dir is None:
        return
    opened = self._get_file(event.session_id)
    if opened is None:
        return
    handle, lock = opened
    line = json.dumps(&#123;
        &quot;session_id&quot;: event.session_id,
        &quot;event&quot;: event.event,
        &quot;ts&quot;: event.ts,
        &quot;payload&quot;: event.payload,
    &#125;)
    with lock, contextlib.suppress(ValueError):
        handle.write(line + &quot;\n&quot;)
        handle.flush()</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.session_logger.SessionLogger.close"><code>fastvideo.entrypoints.streaming.session_logger.SessionLogger.close</code></h3>
<pre class="doc-signature"><code>close(session_id: str) -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def close(self, session_id: str) -&gt; None:
    with self._registry_lock:
        handle = self._files.pop(session_id, None)
        lock = self._locks.pop(session_id, None)
    if handle is None or lock is None:
        return
    with lock, contextlib.suppress(Exception):
        handle.close()</code></pre></details>
<h3 id="fastvideo.entrypoints.streaming.session_logger.SessionLogger.close_all"><code>fastvideo.entrypoints.streaming.session_logger.SessionLogger.close_all</code></h3>
<pre class="doc-signature"><code>close_all() -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def close_all(self) -&gt; None:
    with self._registry_lock:
        sids = list(self._files)
    for sid in sids:
        self.close(sid)</code></pre></details>
</div>
