Episode 1: Reading the Log

Find the session log a run wrote, and read a line of it: five columns, two of which earn their place.

A running pipeline cannot raise at you. Its nodes are driven from source threads that nobody is waiting on, so when one of them has something to say, a warning, a refusal, an exception, it is written to the session log instead. That file is the only channel a realtime run has, and a windowed application may have no console at all. Knowing where the log is, and how to read a line of it, is what makes the rest of this season usable.

Where it is

Each run writes a new file, named after the date and time it started, into a folder that depends on the platform: %APPDATA%\gtec\gPype on Windows, ~/Library/Application Support/gtec/gPype on macOS, and ~/.local/share/gtec/gPype on Linux, or under XDG_DATA_HOME where that is set.

11def log_directory() -> str:
12    """Where g.Pype writes its session log on this platform."""
13    if sys.platform == "win32":
14        return os.path.join(os.getenv("APPDATA", ""), "gtec", "gPype")
15    if sys.platform == "darwin":
16        return os.path.expanduser("~/Library/Application Support/gtec/gPype")
17    return os.path.expanduser("~/.local/share/gtec/gPype")
Two filters, named

Two Bandpass nodes of the same class, off one Generator, run for a second. They are named: without a name both are Bandpass in the log, and you cannot tell which one spoke.

28    p = gp.Pipeline()
29
30    # Two filters of the same class, named: without a name both are
31    # "Bandpass" in the log and you cannot tell which one spoke
32    source = gp.Generator(sampling_rate=250, channel_count=4,
33                          signal_amplitude=1.0)
34    alpha = gp.Bandpass(f_lo=8, f_hi=12, name="alpha")
35    beta = gp.Bandpass(f_lo=13, f_hi=30, name="beta")
36
37    p.connect(source, alpha)
38    p.connect(alpha, gp.Collector())
39    p.connect(source, beta)
40    p.connect(beta, gp.Collector())
41
42    p.start()
43    time.sleep(1.0)
44    p.stop()
45    p.close()
The newest file

The script prints the folder, the newest file in it, and the last lines this run wrote.

47    directory = log_directory()
48    path = newest_log(directory)
49
50    print("Log directory:", directory)
51    print("Newest file  :", os.path.basename(path) or "(none found)")
52    # A line has five columns: timestamp | type | source | thread | message
53    if path:
54        with open(path, encoding="utf-8", errors="replace") as handle:
55            lines = [line.rstrip("\n") for line in handle if line.strip()]
56        print()
57        print("The last few lines this run wrote:")
58        for line in lines[-8:]:
59            print("  " + line[:150])
Result

Run the script:

Log directory: <the platform's folder>
Newest file  : <date>_<time>.log

The last few lines this run wrote:
  <timestamp> | INFO | alpha: Bandpass.stop() [...] | 0x... | alpha stopped.
  ...

A line has five columns, timestamp, type, source, thread and message, and two of them earn their place. Source names the node instance where you gave one, so alpha and beta are told apart; leave them unnamed and three Bandpass nodes in one pipeline produce three identical lines. That is the whole argument for passing name= to anything you might later have to ask about. Thread is worth reading when the ordering surprises you: sources run on their own threads, so lines from different parts of the pipeline interleave, and two neighbouring entries are not necessarily related.

What the log is not

It is not somewhere to look afterwards and hope. A failure stops the run, as Episode 4 shows, so to find out whether a run was clean, ask the pipeline with get_condition() rather than reading the file later. The log tells you what went wrong; the condition tells you that something did.

Summary

In this episode, you learned how to:

  • find the session log on your platform

  • read a line’s five columns, and why the source and the thread matter

  • name nodes so the log can tell them apart

Proceed to Episode 2, where a pipeline refuses to start, and the message says why.

Nodes in this episode: Generator, Bandpass, Collector

File s6e1_reading_the_log.py – View file on GitHub

 1"""Season 6, Episode 1: where the session log is, and how to read a line."""
 2
 3import glob
 4import os
 5import sys
 6import time
 7
 8import gpype as gp
 9
10
11def log_directory() -> str:
12    """Where g.Pype writes its session log on this platform."""
13    if sys.platform == "win32":
14        return os.path.join(os.getenv("APPDATA", ""), "gtec", "gPype")
15    if sys.platform == "darwin":
16        return os.path.expanduser("~/Library/Application Support/gtec/gPype")
17    return os.path.expanduser("~/.local/share/gtec/gPype")
18
19
20def newest_log(directory: str) -> str:
21    """The log file this run most likely wrote."""
22    found = sorted(glob.glob(os.path.join(directory, "*.log")))
23    return found[-1] if found else ""
24
25
26if __name__ == "__main__":
27
28    p = gp.Pipeline()
29
30    # Two filters of the same class, named: without a name both are
31    # "Bandpass" in the log and you cannot tell which one spoke
32    source = gp.Generator(sampling_rate=250, channel_count=4,
33                          signal_amplitude=1.0)
34    alpha = gp.Bandpass(f_lo=8, f_hi=12, name="alpha")
35    beta = gp.Bandpass(f_lo=13, f_hi=30, name="beta")
36
37    p.connect(source, alpha)
38    p.connect(alpha, gp.Collector())
39    p.connect(source, beta)
40    p.connect(beta, gp.Collector())
41
42    p.start()
43    time.sleep(1.0)
44    p.stop()
45    p.close()
46
47    directory = log_directory()
48    path = newest_log(directory)
49
50    print("Log directory:", directory)
51    print("Newest file  :", os.path.basename(path) or "(none found)")
52    # A line has five columns: timestamp | type | source | thread | message
53    if path:
54        with open(path, encoding="utf-8", errors="replace") as handle:
55            lines = [line.rstrip("\n") for line in handle if line.strip()]
56        print()
57        print("The last few lines this run wrote:")
58        for line in lines[-8:]:
59            print("  " + line[:150])