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.
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 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 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])
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.
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.
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])