Skip to content

Commit 4683ddf

Browse files
scripts: add set log level script
1 parent a109ad6 commit 4683ddf

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

scripts/set_log_level.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import argparse
2+
import subprocess
3+
import sys
4+
import time
5+
from typing import List
6+
7+
import requests
8+
import signal
9+
import socket
10+
11+
SLEEP_INTERVAL = 0.4
12+
13+
14+
def parse_args(args: List[str]) -> argparse.Namespace:
15+
parser = argparse.ArgumentParser(
16+
description="Set the log level for a module or crate",
17+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
18+
)
19+
20+
# Add port-forwarding arguments
21+
add_port_forward_args(parser)
22+
23+
parser.add_argument(
24+
"--target",
25+
type=str,
26+
help="Crate or module name whose log level should be inspected or updated",
27+
)
28+
parser.add_argument("--log_level", type=str, help="The log level to set for the module/crate")
29+
parser.add_argument(
30+
"--method",
31+
type=str,
32+
choices=["get", "post"],
33+
default="post",
34+
help="HTTP method to use: 'get' to read current log level, 'post' to set a log level",
35+
)
36+
return parser.parse_args(args)
37+
38+
39+
def add_port_forward_args(parser: argparse.ArgumentParser) -> None:
40+
"""Add port-forwarding related CLI options to the parser."""
41+
42+
pf_group = parser.add_argument_group("port-forwarding options")
43+
44+
pf_group.add_argument(
45+
"--pod_name",
46+
type=str,
47+
help="Pod to port-forward to; omit when no port forwarding is needed",
48+
)
49+
50+
pf_group.add_argument(
51+
"--local_port",
52+
type=int,
53+
default=8082,
54+
help="Local port to bind the port-forward to",
55+
)
56+
57+
pf_group.add_argument(
58+
"--monitoring_port",
59+
type=int,
60+
default=8082,
61+
help="Monitoring endpoint port",
62+
)
63+
64+
65+
def port_forward(
66+
pod_name: str,
67+
local_port: int,
68+
remote_port: int,
69+
max_attempts: int = 5,
70+
) -> subprocess.Popen:
71+
"""Start a kubectl port-forward and wait until it is ready.
72+
73+
Returns the Popen handle so the caller can terminate it later.
74+
Raises RuntimeError if the local port is still unreachable after
75+
`max_attempts` connection checks.
76+
"""
77+
78+
cmd = ["kubectl", "port-forward", pod_name, f"{local_port}:{remote_port}"]
79+
print("Starting port-forward:", " ".join(cmd))
80+
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
81+
82+
for _attempt in range(1, max_attempts + 1):
83+
try:
84+
with socket.create_connection(("localhost", local_port), timeout=1):
85+
print(
86+
f"✅ Port-forward to {pod_name}:{remote_port} is ready on localhost:{local_port}"
87+
)
88+
return proc
89+
except OSError:
90+
time.sleep(SLEEP_INTERVAL)
91+
92+
proc.terminate()
93+
proc.wait(timeout=5)
94+
raise RuntimeError(
95+
f"❌ Port-forward to {pod_name}:{remote_port} failed after {max_attempts} attempts."
96+
)
97+
98+
99+
def format_log_levels(raw: str) -> str:
100+
"""Convert comma-separated log-level string to human-readable form.
101+
102+
If the last token has no "=", treat it as the global default level.
103+
Returns a newline-joined string ready for printing.
104+
"""
105+
106+
raw_entries = [item.strip() for item in raw.split(",") if item.strip()]
107+
108+
default_level = None
109+
if raw_entries and "=" not in raw_entries[-1]:
110+
default_level = raw_entries.pop()
111+
112+
lines: list[str] = []
113+
if default_level:
114+
lines.append(f"default={default_level}")
115+
116+
lines.extend(sorted(raw_entries))
117+
return "\n".join(lines)
118+
119+
120+
def main():
121+
args = parse_args(sys.argv[1:])
122+
123+
# If a pod name is supplied, establish a port-forward before making the request
124+
port_forward_proc = None
125+
126+
target_port = args.monitoring_port
127+
setup_port_forwarding = bool(args.pod_name)
128+
base_port = args.local_port if setup_port_forwarding else target_port
129+
130+
if setup_port_forwarding:
131+
try:
132+
port_forward_proc = port_forward(args.pod_name, args.local_port, args.monitoring_port)
133+
except RuntimeError as err:
134+
print(err)
135+
sys.exit(1)
136+
137+
try:
138+
if args.method == "get":
139+
full_url = f"http://localhost:{base_port}/monitoring/logLevel"
140+
print(f"Fetching current log level from {full_url}")
141+
response = requests.get(full_url, timeout=5)
142+
143+
if response.status_code != 200:
144+
print(f"Failed to fetch log level: {response.status_code} {response.text}")
145+
sys.exit(1)
146+
147+
print("Current log levels:\n", format_log_levels(response.text))
148+
elif args.method == "post":
149+
# Validate required arguments
150+
if not args.target or not args.log_level:
151+
print("--target and --log_level are required when --method=post")
152+
sys.exit(1)
153+
154+
base_url = f"http://localhost:{base_port}/monitoring/setLogLevel"
155+
full_url = f"{base_url}/{args.target}/{args.log_level}"
156+
157+
print(f"Setting log level for {args.target} to {args.log_level} at {full_url}")
158+
159+
response = requests.post(full_url, timeout=5)
160+
161+
if response.status_code != 200:
162+
print(
163+
f"❌ Failed to set log level for {args.target} to {args.log_level}: {response.text}"
164+
)
165+
sys.exit(1)
166+
167+
print(f"✅ Successfully set log level for {args.target} to {args.log_level}")
168+
169+
finally:
170+
# Clean up the port-forward process if we started one
171+
if port_forward_proc:
172+
port_forward_proc.send_signal(signal.SIGINT)
173+
try:
174+
port_forward_proc.wait(timeout=5)
175+
except subprocess.TimeoutExpired:
176+
port_forward_proc.kill()
177+
port_forward_proc.wait()
178+
179+
180+
if __name__ == "__main__":
181+
main()

0 commit comments

Comments
 (0)