Skip to content

Commit b0c283d

Browse files
scripts: add set log level script (#10249)
1 parent d8507cd commit b0c283d

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed

scripts/set_log_level.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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 tunnel to",
55+
)
56+
57+
pf_group.add_argument(
58+
"--cluster_name",
59+
type=str,
60+
help="Optional k8s cluster name; if supplied, the script runs 'kubectx <cluster_name>' before port-forwarding",
61+
)
62+
63+
pf_group.add_argument(
64+
"--namespace",
65+
type=str,
66+
help="Optional k8s namespace; if supplied, the script runs 'kubens <namespace>' before port-forwarding",
67+
)
68+
69+
pf_group.add_argument(
70+
"--monitoring_port",
71+
type=int,
72+
default=8082,
73+
help="Monitoring endpoint port",
74+
)
75+
76+
77+
def port_forward(
78+
pod_name: str,
79+
local_port: int,
80+
remote_port: int,
81+
max_attempts: int = 5,
82+
) -> subprocess.Popen:
83+
"""Start a kubectl port-forward and wait until it is ready.
84+
85+
Returns the Popen handle so the caller can terminate it later.
86+
Raises RuntimeError if the local port is still unreachable after
87+
`max_attempts` connection checks.
88+
"""
89+
90+
cmd = ["kubectl", "port-forward", pod_name, f"{local_port}:{remote_port}"]
91+
print("Starting port-forward:", " ".join(cmd))
92+
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
93+
94+
for _attempt in range(1, max_attempts + 1):
95+
try:
96+
with socket.create_connection(("localhost", local_port), timeout=1):
97+
print(
98+
f"✅ Port-forward to {pod_name}:{remote_port} is ready on localhost:{local_port}"
99+
)
100+
return proc
101+
except OSError:
102+
time.sleep(SLEEP_INTERVAL)
103+
104+
proc.terminate()
105+
proc.wait(timeout=5)
106+
raise RuntimeError(
107+
f"❌ Port-forward to {pod_name}:{remote_port} failed after {max_attempts} attempts."
108+
)
109+
110+
111+
def format_log_levels(raw: str) -> str:
112+
"""Convert comma-separated log-level string to human-readable form.
113+
114+
If the last token has no "=", treat it as the global default level.
115+
Returns a newline-joined string ready for printing.
116+
"""
117+
118+
raw_entries = [item.strip() for item in raw.split(",") if item.strip()]
119+
120+
default_level = None
121+
if raw_entries and "=" not in raw_entries[-1]:
122+
default_level = raw_entries.pop()
123+
124+
lines: list[str] = []
125+
if default_level:
126+
lines.append(f"default={default_level}")
127+
128+
lines.extend(sorted(raw_entries))
129+
return "\n".join(lines)
130+
131+
132+
def main():
133+
args = parse_args(sys.argv[1:])
134+
135+
# If a pod name is supplied, establish a port-forward before making the request
136+
port_forward_proc = None
137+
138+
target_port = args.monitoring_port
139+
setup_port_forwarding = bool(args.pod_name)
140+
base_port = args.local_port if setup_port_forwarding else target_port
141+
142+
if setup_port_forwarding:
143+
# Switch context/namespace if requested
144+
try:
145+
if args.cluster_name:
146+
subprocess.check_call(["kubectx", args.cluster_name])
147+
if args.namespace:
148+
subprocess.check_call(["kubens", args.namespace])
149+
except subprocess.CalledProcessError as err:
150+
print(f"Failed to switch kubectl context/namespace: {err}")
151+
sys.exit(1)
152+
153+
try:
154+
port_forward_proc = port_forward(args.pod_name, args.local_port, args.monitoring_port)
155+
except RuntimeError as err:
156+
print(err)
157+
sys.exit(1)
158+
159+
try:
160+
if args.method == "get":
161+
full_url = f"http://localhost:{base_port}/monitoring/logLevel"
162+
print(f"Fetching current log level from {full_url}")
163+
response = requests.get(full_url, timeout=5)
164+
165+
if response.status_code != 200:
166+
print(f"Failed to fetch log level: {response.status_code} {response.text}")
167+
sys.exit(1)
168+
169+
print("Current log levels:\n", format_log_levels(response.text))
170+
elif args.method == "post":
171+
# Validate required arguments
172+
if not args.target or not args.log_level:
173+
print("--target and --log_level are required when --method=post")
174+
sys.exit(1)
175+
176+
base_url = f"http://localhost:{base_port}/monitoring/setLogLevel"
177+
full_url = f"{base_url}/{args.target}/{args.log_level}"
178+
179+
print(f"Setting log level for {args.target} to {args.log_level} at {full_url}")
180+
181+
response = requests.post(full_url, timeout=5)
182+
183+
if response.status_code != 200:
184+
print(
185+
f"❌ Failed to set log level for {args.target} to {args.log_level}: {response.text}"
186+
)
187+
sys.exit(1)
188+
189+
print(f"✅ Successfully set log level for {args.target} to {args.log_level}")
190+
191+
finally:
192+
# Clean up the port-forward process if we started one
193+
if port_forward_proc:
194+
port_forward_proc.send_signal(signal.SIGINT)
195+
try:
196+
port_forward_proc.wait(timeout=5)
197+
except subprocess.TimeoutExpired:
198+
port_forward_proc.kill()
199+
port_forward_proc.wait()
200+
201+
202+
if __name__ == "__main__":
203+
main()

0 commit comments

Comments
 (0)