Skip to content

Commit ea65d8e

Browse files
committed
Factorize and simplify some more things
Signed-off-by: Juanjo Alvarez <[email protected]>
1 parent 92de674 commit ea65d8e

File tree

1 file changed

+77
-73
lines changed

1 file changed

+77
-73
lines changed

ddtrace/internal/flare/flare.py

Lines changed: 77 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ def __init__(
5454
self.url: str = trace_agent_url
5555
self._api_key: Optional[str] = api_key
5656
self.ddconfig = ddconfig
57+
# Use a fixed boundary for consistency
58+
self._BOUNDARY = "83CAD6AA-8A24-462C-8B3D-FF9CC683B51B"
5759

5860
def prepare(self, log_level: str):
5961
"""
@@ -63,32 +65,15 @@ def prepare(self, log_level: str):
6365
try:
6466
self.flare_dir.mkdir(exist_ok=True)
6567
except Exception as e:
66-
log.error("Failed to create %s directory: %s", self.flare_dir, e)
68+
log.error("Flare prepare: failed to create %s directory: %s", self.flare_dir, e)
6769
return
6870

6971
flare_log_level_int = logging.getLevelName(log_level)
7072
if type(flare_log_level_int) != int:
71-
raise TypeError("Invalid log level provided: %s", log_level)
72-
73-
ddlogger = get_logger("ddtrace")
74-
pid = os.getpid()
75-
flare_file_path = self.flare_dir / f"tracer_python_{pid}.log"
76-
self.original_log_level = ddlogger.level
77-
78-
# Set the logger level to the more verbose between original and flare
79-
# We do this valid_original_level check because if the log level is NOTSET, the value is 0
80-
# which is the minimum value. In this case, we just want to use the flare level, but still
81-
# retain the original state as NOTSET/0
82-
valid_original_level = (
83-
logging.CRITICAL if self.original_log_level == logging.NOTSET else self.original_log_level
84-
)
85-
logger_level = min(valid_original_level, flare_log_level_int)
86-
ddlogger.setLevel(logger_level)
87-
self.file_handler = _add_file_handler(
88-
ddlogger, flare_file_path.__str__(), flare_log_level_int, TRACER_FLARE_FILE_HANDLER_NAME
89-
)
73+
raise TypeError("Flare prepare: Invalid log level provided: %s", log_level)
9074

91-
# Create and add config file
75+
# Setup logging and create config file
76+
pid = self._setup_flare_logging(flare_log_level_int)
9277
self._generate_config_file(pid)
9378

9479
def send(self, flare_send_req: FlareSendRequest):
@@ -101,18 +86,9 @@ def send(self, flare_send_req: FlareSendRequest):
10186
# Ensure the flare directory exists (it might have been deleted by clean_up_files)
10287
self.flare_dir.mkdir(exist_ok=True)
10388

104-
# # Validate case_id (must be a digit and cannot be 0 according to spec)
105-
if flare_send_req.case_id in ("0", 0):
106-
log.warning("Case ID cannot be 0, skipping flare send")
107-
self.clean_up_files()
108-
return
109-
110-
if not flare_send_req.case_id.isdigit():
111-
log.warning("Case ID string must contain a digit, skipping flare send")
112-
self.clean_up_files()
113-
return
114-
11589
try:
90+
if not self._validate_case_id(flare_send_req.case_id):
91+
return
11692
self._send_flare_request(flare_send_req)
11793
finally:
11894
self.clean_up_files()
@@ -149,69 +125,97 @@ def revert_configs(self):
149125
log.debug("Could not find %s to remove", TRACER_FLARE_FILE_HANDLER_NAME)
150126
ddlogger.setLevel(self.original_log_level)
151127

152-
def _generate_payload(self, flare_send_req):
128+
def _validate_case_id(self, case_id: str) -> bool:
153129
"""
154-
Generate the multipart form-data payload for the flare request.
130+
Validate case_id (must be a digit and cannot be 0 according to spec).
131+
Returns True if valid, False otherwise. Cleans up files if invalid.
155132
"""
156-
body = io.BytesIO()
157-
158-
# Use a fixed boundary for consistency
159-
boundary = "83CAD6AA-8A24-462C-8B3D-FF9CC683B51B"
133+
if case_id in ("0", 0):
134+
log.warning("Case ID cannot be 0, skipping flare send")
135+
return False
136+
137+
if not case_id.isdigit():
138+
log.warning("Case ID string must contain a digit, skipping flare send")
139+
return False
140+
141+
return True
160142

161-
# Create the multipart form data in the same order:
162-
# source, case_id, hostname, email, uuid, flare_file
143+
def _setup_flare_logging(self, flare_log_level_int: int) -> int:
144+
"""
145+
Setup flare logging configuration.
146+
Returns the process ID.
147+
"""
148+
ddlogger = get_logger("ddtrace")
149+
pid = os.getpid()
150+
flare_file_path = self.flare_dir / f"tracer_python_{pid}.log"
151+
self.original_log_level = ddlogger.level
163152

164-
# 1. source field
165-
body.write(f"--{boundary}\r\n".encode())
166-
body.write(b'Content-Disposition: form-data; name="source"\r\n\r\n')
167-
body.write(b"tracer_python\r\n")
153+
# Set the logger level to the more verbose between original and flare
154+
# We do this valid_original_level check because if the log level is NOTSET, the value is 0
155+
# which is the minimum value. In this case, we just want to use the flare level, but still
156+
# retain the original state as NOTSET/0
157+
valid_original_level = (
158+
logging.CRITICAL if self.original_log_level == logging.NOTSET else self.original_log_level
159+
)
160+
logger_level = min(valid_original_level, flare_log_level_int)
161+
ddlogger.setLevel(logger_level)
162+
self.file_handler = _add_file_handler(
163+
ddlogger, flare_file_path.__str__(), flare_log_level_int, TRACER_FLARE_FILE_HANDLER_NAME
164+
)
165+
return pid
168166

169-
# 2. case_id field
170-
body.write(f"--{boundary}\r\n".encode())
171-
body.write(b'Content-Disposition: form-data; name="case_id"\r\n\r\n')
172-
body.write(f"{flare_send_req.case_id}\r\n".encode())
167+
def _create_zip_content(self) -> bytes:
168+
"""
169+
Create ZIP file content containing all flare files.
170+
Returns the ZIP file content as bytes.
171+
"""
172+
zip_stream = io.BytesIO()
173+
with zipfile.ZipFile(zip_stream, mode="w", compression=zipfile.ZIP_DEFLATED) as zipf:
174+
for flare_file_name in self.flare_dir.iterdir():
175+
zipf.write(flare_file_name, arcname=flare_file_name.name)
176+
zip_stream.seek(0)
177+
return zip_stream.getvalue()
173178

174-
# 3. hostname field
175-
body.write(f"--{boundary}\r\n".encode())
176-
body.write(b'Content-Disposition: form-data; name="hostname"\r\n\r\n')
177-
body.write(f"{flare_send_req.hostname}\r\n".encode())
179+
def _write_body_field(self, body: io.BytesIO, name: str, value: str):
180+
"""Write a form field to the multipart body."""
181+
body.write(f"--{self._BOUNDARY}\r\n".encode())
182+
body.write(f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode())
183+
body.write(f"{value}\r\n".encode())
178184

179-
# 4. email field
180-
body.write(f"--{boundary}\r\n".encode())
181-
body.write(b'Content-Disposition: form-data; name="email"\r\n\r\n')
182-
body.write(f"{flare_send_req.email}\r\n".encode())
185+
def _generate_payload(self, flare_send_req):
186+
"""
187+
Generate the multipart form-data payload for the flare request.
188+
"""
183189

184-
# 5. uuid field (new, per spec)
185-
body.write(f"--{boundary}\r\n".encode())
186-
body.write(b'Content-Disposition: form-data; name="uuid"\r\n\r\n')
187-
body.write(f"{flare_send_req.uuid}\r\n".encode())
190+
# Create the multipart form data in the same order:
191+
# source, case_id, hostname, email, uuid, flare_file
192+
body = io.BytesIO()
193+
self._write_body_field(body, "source", "tracer_python")
194+
self._write_body_field(body, "case_id", flare_send_req.case_id)
195+
self._write_body_field(body, "hostname", flare_send_req.hostname)
196+
self._write_body_field(body, "email", flare_send_req.email)
197+
self._write_body_field(body, "uuid", flare_send_req.uuid)
188198

189-
# 6. flare_file field with descriptive filename
199+
# flare_file field with descriptive filename
190200
timestamp = int(time.time() * 1000)
191201
filename = f"tracer-python-{flare_send_req.case_id}-{timestamp}-debug.zip"
192-
body.write(f"--{boundary}\r\n".encode())
202+
body.write(f"--{self._BOUNDARY}\r\n".encode())
193203
body.write(f'Content-Disposition: form-data; name="flare_file"; filename="{filename}"\r\n'.encode())
194204
body.write(b"Content-Type: application/octet-stream\r\n\r\n")
195205

196206
# Create the zip file content separately
197-
zip_stream = io.BytesIO()
198-
with zipfile.ZipFile(zip_stream, mode="w", compression=zipfile.ZIP_DEFLATED) as zipf:
199-
for flare_file_name in self.flare_dir.iterdir():
200-
zipf.write(flare_file_name, arcname=flare_file_name.name)
201-
zip_stream.seek(0)
202-
body.write(zip_stream.getvalue())
203-
body.write(b"\r\n")
207+
body.write(self._create_zip_content() + b"\r\n")
204208

205-
# End boundary
206-
body.write(f"--{boundary}--\r\n".encode())
209+
# Ending boundary
210+
body.write(f"--{self._BOUNDARY}--\r\n".encode())
207211

208212
# Set headers
209213
headers = {
210-
"Content-Type": f"multipart/form-data; boundary={boundary}",
214+
"Content-Type": f"multipart/form-data; boundary={self._BOUNDARY}",
211215
"Content-Length": str(body.tell()),
212216
}
213217

214-
# Don't send DD-API-KEY or Host Header - the agent should add it when forwarding to backend
218+
# Note: don't send DD-API-KEY or Host Header - the agent should add it when forwarding to backend
215219
return headers, body.getvalue()
216220

217221
def _get_valid_logger_level(self, flare_log_level: int) -> int:

0 commit comments

Comments
 (0)