Skip to content

Commit a1e2fc0

Browse files
authored
Fix multiple threads share one logger (#720)
The original code ([_fuzzing_pipelines](https://github.com/google/oss-fuzz-gen/blob/c9f87defe821ab66f123b7730b59eb2a93c6608b/run_one_experiment.py#L336), [_fuzzing_pipeline](https://github.com/google/oss-fuzz-gen/blob/c9f87defe821ab66f123b7730b59eb2a93c6608b/run_one_experiment.py#L321), [get_trial_logger](https://github.com/google/oss-fuzz-gen/blob/c9f87defe821ab66f123b7730b59eb2a93c6608b/logger.py#L126)) will cause multiple threads in the thread pool to share a logger. Now use threading.local() to create a separate logger for each thread.
1 parent cd048ba commit a1e2fc0

File tree

6 files changed

+128
-78
lines changed

6 files changed

+128
-78
lines changed

agent/base_agent.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,20 @@ def get_tool(self, tool_name: str) -> Optional[BaseTool]:
4242
return tool
4343
return None
4444

45-
def chat_llm(self, cur_round: int, client: Any, prompt: Prompt) -> str:
45+
def chat_llm(self, cur_round: int, client: Any, prompt: Prompt,
46+
trial: int) -> str:
4647
"""Chat with LLM."""
4748
logger.info('<CHAT PROMPT:ROUND %02d>%s</CHAT PROMPT:ROUND %02d>',
48-
cur_round, prompt.get(), cur_round)
49+
cur_round,
50+
prompt.get(),
51+
cur_round,
52+
trial=trial)
4953
response = self.llm.chat_llm(client=client, prompt=prompt)
5054
logger.info('<CHAT RESPONSE:ROUND %02d>%s</CHAT RESPONSE:ROUND %02d>',
51-
cur_round, response, cur_round)
55+
cur_round,
56+
response,
57+
cur_round,
58+
trial=trial)
5259
return response
5360

5461
def _parse_tag(self, response: str, tag: str) -> str:
@@ -89,11 +96,16 @@ def _container_handle_invalid_tool_usage(self, tool: BaseTool) -> Prompt:
8996
f'interaction protocols:\n{tool.tutorial()}')
9097
return DefaultTemplateBuilder(self.llm, None, initial=prompt_text).build([])
9198

92-
def _sleep_random_duration(self, min_sec: int = 1, max_sec: int = 60) -> None:
99+
def _sleep_random_duration(
100+
self,
101+
trial: int,
102+
min_sec: int = 1,
103+
max_sec: int = 60,
104+
) -> None:
93105
"""Sleeps for a random duration between min_sec and max_sec. Agents uses
94106
this to avoid exceeding quota limit (e.g., LLM query frequency)."""
95107
duration = random.randint(min_sec, max_sec)
96-
logger.debug('Sleeping for %d before the next query', duration)
108+
logger.debug('Sleeping for %d before the next query', duration, trial=trial)
97109
time.sleep(duration)
98110

99111
@classmethod

agent/prototyper.py

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from agent.base_agent import BaseAgent
1111
from data_prep.project_context.context_introspector import ContextRetriever
1212
from experiment.benchmark import Benchmark
13-
from llm_toolkit.prompt_builder import EXAMPLES as EXAMPLE_FUZZ_TARGETS
1413
from llm_toolkit.prompt_builder import (DefaultTemplateBuilder,
1514
PrototyperTemplateBuilder)
1615
from llm_toolkit.prompts import Prompt
@@ -48,23 +47,31 @@ def _update_fuzz_target_and_build_script(self, cur_round: int, response: str,
4847
self._parse_tag(response, 'fuzz target'))
4948
build_result.fuzz_target_source = fuzz_target_source
5049
if fuzz_target_source:
51-
logger.debug('ROUND %02d Parsed fuzz target from LLM: %s', cur_round,
52-
fuzz_target_source)
50+
logger.debug('ROUND %02d Parsed fuzz target from LLM: %s',
51+
cur_round,
52+
fuzz_target_source,
53+
trial=build_result.trial)
5354
else:
5455
logger.error('ROUND %02d No fuzz target source code in conclusion: %s',
55-
cur_round, response)
56+
cur_round,
57+
response,
58+
trial=build_result.trial)
5659

5760
build_script_source = self._filter_code(
5861
self._parse_tag(response, 'build script'))
5962
# Sometimes LLM adds chronos, which makes no sense for new build scripts.
6063
build_result.build_script_source = build_script_source.replace(
6164
'source /src/chronos.sh', '')
6265
if build_script_source:
63-
logger.debug('ROUND %02d Parsed build script from LLM: %s', cur_round,
64-
build_script_source)
66+
logger.debug('ROUND %02d Parsed build script from LLM: %s',
67+
cur_round,
68+
build_script_source,
69+
trial=build_result.trial)
6570
else:
66-
logger.debug('ROUND %02d No build script in conclusion: %s', cur_round,
67-
response)
71+
logger.debug('ROUND %02d No build script in conclusion: %s',
72+
cur_round,
73+
response,
74+
trial=build_result.trial)
6875

6976
def _update_build_result(self, build_result: BuildResult,
7077
compile_process: sp.CompletedProcess, status: bool,
@@ -84,20 +91,22 @@ def _validate_fuzz_target_and_build_script(self, cur_round: int,
8491
# 2. Recompile with the modified build script, if any.
8592
build_script_source = build_result.build_script_source
8693

87-
logger.info('First compile fuzz target without modifying build script.')
94+
logger.info('First compile fuzz target without modifying build script.',
95+
trial=build_result.trial)
8896
build_result.build_script_source = ''
8997
self._validate_fuzz_target_and_build_script_via_compile(
9098
cur_round, build_result)
9199

92100
if not build_result.success and build_script_source:
93-
logger.info('Then compile fuzz target with modified build script.')
101+
logger.info('Then compile fuzz target with modified build script.',
102+
trial=build_result.trial)
94103
build_result.build_script_source = build_script_source
95104
self._validate_fuzz_target_and_build_script_via_compile(
96105
cur_round, build_result)
97106

98107
def _validate_fuzz_target_references_function(
99108
self, compilation_tool: ProjectContainerTool, benchmark: Benchmark,
100-
cur_round: int) -> bool:
109+
cur_round: int, trial: int) -> bool:
101110
"""Validates if the LLM generated fuzz target assembly code references
102111
function-under-test."""
103112
disassemble_result = compilation_tool.execute(
@@ -106,10 +115,13 @@ def _validate_fuzz_target_references_function(
106115
function_referenced = (disassemble_result.returncode == 0 and
107116
benchmark.function_name in disassemble_result.stdout)
108117
logger.debug('ROUND %02d Final fuzz target function referenced: %s',
109-
cur_round, function_referenced)
118+
cur_round,
119+
function_referenced,
120+
trial=trial)
110121
if not function_referenced:
111122
logger.debug('ROUND %02d Final fuzz target function not referenced',
112-
cur_round)
123+
cur_round,
124+
trial=trial)
113125
return function_referenced
114126

115127
def _validate_fuzz_target_and_build_script_via_compile(
@@ -133,25 +145,33 @@ def _validate_fuzz_target_and_build_script_via_compile(
133145
file_content=build_result.build_script_source))
134146

135147
# Recompile.
136-
logger.info('===== ROUND %02d Recompile =====', cur_round)
148+
logger.info('===== ROUND %02d Recompile =====',
149+
cur_round,
150+
trial=build_result.trial)
137151
start_time = time.time()
138152
compile_process = compilation_tool.compile()
139153
end_time = time.time()
140-
logger.debug('ROUND %02d compilation time: %s', cur_round,
141-
timedelta(seconds=end_time - start_time))
154+
logger.debug('ROUND %02d compilation time: %s',
155+
cur_round,
156+
timedelta(seconds=end_time - start_time),
157+
trial=build_result.trial)
142158
compile_succeed = compile_process.returncode == 0
143-
logger.debug('ROUND %02d Fuzz target compiles: %s', cur_round,
144-
compile_succeed)
159+
logger.debug('ROUND %02d Fuzz target compiles: %s',
160+
cur_round,
161+
compile_succeed,
162+
trial=build_result.trial)
145163

146164
# Double-check binary.
147165
ls_result = compilation_tool.execute(f'ls /out/{benchmark.target_name}')
148166
binary_exists = ls_result.returncode == 0
149-
logger.debug('ROUND %02d Final fuzz target binary exists: %s', cur_round,
150-
binary_exists)
167+
logger.debug('ROUND %02d Final fuzz target binary exists: %s',
168+
cur_round,
169+
binary_exists,
170+
trial=build_result.trial)
151171

152172
# Validate if function-under-test is referenced by the fuzz target.
153173
function_referenced = self._validate_fuzz_target_references_function(
154-
compilation_tool, benchmark, cur_round)
174+
compilation_tool, benchmark, cur_round, build_result.trial)
155175

156176
compilation_tool.terminate()
157177
self._update_build_result(build_result,
@@ -164,18 +184,24 @@ def _container_handle_conclusion(
164184
build_result: BuildResult) -> Optional[Prompt]:
165185
"""Runs a compilation tool to validate the new fuzz target and build script
166186
from LLM."""
167-
logger.info('----- ROUND %02d Received conclusion -----', cur_round)
187+
logger.info('----- ROUND %02d Received conclusion -----',
188+
cur_round,
189+
trial=build_result.trial)
168190

169191
self._update_fuzz_target_and_build_script(cur_round, response, build_result)
170192

171193
self._validate_fuzz_target_and_build_script(cur_round, build_result)
172194
if build_result.success:
173-
logger.info('***** Prototyper succeded in %02d rounds *****', cur_round)
195+
logger.info('***** Prototyper succeded in %02d rounds *****',
196+
cur_round,
197+
trial=build_result.trial)
174198
return None
175199

176200
if not build_result.compiles:
177201
compile_log = self.llm.truncate_prompt(build_result.compile_log)
178-
logger.info('***** Failed to recompile in %02d rounds *****', cur_round)
202+
logger.info('***** Failed to recompile in %02d rounds *****',
203+
cur_round,
204+
trial=build_result.trial)
179205
prompt_text = (
180206
'Failed to build fuzz target. Here is the fuzz target, build script, '
181207
'compliation command, and other compilation runtime output. Analyze '
@@ -205,7 +231,9 @@ def _container_handle_conclusion(
205231
elif not build_result.is_function_referenced:
206232
logger.info(
207233
'***** Fuzz target does not reference function-under-test in %02d '
208-
'rounds *****', cur_round)
234+
'rounds *****',
235+
cur_round,
236+
trial=build_result.trial)
209237
prompt_text = (
210238
'The fuzz target builds successfully, but the target function '
211239
f'`{build_result.benchmark.function_signature}` was not used by '
@@ -229,14 +257,16 @@ def _container_tool_reaction(self, cur_round: int, response: str,
229257
return self._container_handle_conclusion(cur_round, response,
230258
build_result)
231259
# Other responses are invalid.
232-
logger.warning('ROUND %02d Invalid response from LLM: %s', cur_round,
233-
response)
260+
logger.warning('ROUND %02d Invalid response from LLM: %s',
261+
cur_round,
262+
response,
263+
trial=build_result.trial)
234264
return self._container_handle_invalid_tool_usage(self.inspect_tool)
235265

236266
def execute(self, result_history: list[Result]) -> BuildResult:
237267
"""Executes the agent based on previous result."""
238-
logger.info('Executing Prototyper')
239268
last_result = result_history[-1]
269+
logger.info('Executing Prototyper', trial=last_result.trial)
240270
benchmark = last_result.benchmark
241271
self.inspect_tool = ProjectContainerTool(benchmark, name='inspect')
242272
self.inspect_tool.compile(extra_commands=' && rm -rf /out/* > /dev/null')
@@ -250,13 +280,17 @@ def execute(self, result_history: list[Result]) -> BuildResult:
250280
try:
251281
client = self.llm.get_chat_client(model=self.llm.get_model())
252282
while prompt and cur_round < MAX_ROUND:
253-
response = self.chat_llm(cur_round, client=client, prompt=prompt)
283+
response = self.chat_llm(cur_round,
284+
client=client,
285+
prompt=prompt,
286+
trial=last_result.trial)
254287
prompt = self._container_tool_reaction(cur_round, response,
255288
build_result)
256289
cur_round += 1
257290
finally:
258291
# Cleanup: stop and remove the container
259292
logger.debug('Stopping and removing the inspect container %s',
260-
self.inspect_tool.container_id)
293+
self.inspect_tool.container_id,
294+
trial=last_result.trial)
261295
self.inspect_tool.terminate()
262296
return build_result

logger.py

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
FINAL_RESULT_JSON = 'result.json'
1212

13-
_trial_logger = None
14-
1513

1614
class CustomLoggerAdapter(logging.LoggerAdapter):
1715
"""A note-taker to log and record experiment status, key info, and final
@@ -61,76 +59,76 @@ def write_chat_history(self, result: Result) -> None:
6159

6260
def debug(msg: object,
6361
*args: object,
62+
trial: int,
6463
exc_info=None,
6564
stack_info: bool = False,
6665
stacklevel: int = 1,
6766
extra: Mapping[str, object] | None = None,
6867
**kwargs: object) -> None:
69-
return get_trial_logger().debug(msg,
70-
*args,
71-
exc_info=exc_info,
72-
stack_info=stack_info,
73-
stacklevel=stacklevel,
74-
extra=extra,
75-
**kwargs)
68+
return get_trial_logger(trial=trial).debug(msg,
69+
*args,
70+
exc_info=exc_info,
71+
stack_info=stack_info,
72+
stacklevel=stacklevel,
73+
extra=extra,
74+
**kwargs)
7675

7776

7877
def info(msg: object,
7978
*args: object,
79+
trial: int,
8080
exc_info=None,
8181
stack_info: bool = False,
8282
stacklevel: int = 1,
8383
extra: Mapping[str, object] | None = None,
8484
**kwargs: object) -> None:
85-
return get_trial_logger().info(msg,
86-
*args,
87-
exc_info=exc_info,
88-
stack_info=stack_info,
89-
stacklevel=stacklevel,
90-
extra=extra,
91-
**kwargs)
85+
return get_trial_logger(trial=trial).info(msg,
86+
*args,
87+
exc_info=exc_info,
88+
stack_info=stack_info,
89+
stacklevel=stacklevel,
90+
extra=extra,
91+
**kwargs)
9292

9393

9494
def warning(msg: object,
9595
*args: object,
96+
trial: int,
9697
exc_info=None,
9798
stack_info: bool = False,
9899
stacklevel: int = 1,
99100
extra: Mapping[str, object] | None = None,
100101
**kwargs: object) -> None:
101-
return get_trial_logger().warning(msg,
102-
*args,
103-
exc_info=exc_info,
104-
stack_info=stack_info,
105-
stacklevel=stacklevel,
106-
extra=extra,
107-
**kwargs)
102+
return get_trial_logger(trial=trial).warning(msg,
103+
*args,
104+
exc_info=exc_info,
105+
stack_info=stack_info,
106+
stacklevel=stacklevel,
107+
extra=extra,
108+
**kwargs)
108109

109110

110111
def error(msg: object,
111112
*args: object,
113+
trial: int,
112114
exc_info=None,
113115
stack_info: bool = False,
114116
stacklevel: int = 1,
115117
extra: Mapping[str, object] | None = None,
116118
**kwargs: object) -> None:
117-
return get_trial_logger().error(msg,
118-
*args,
119-
exc_info=exc_info,
120-
stack_info=stack_info,
121-
stacklevel=stacklevel,
122-
extra=extra,
123-
**kwargs)
119+
return get_trial_logger(trial=trial).error(msg,
120+
*args,
121+
exc_info=exc_info,
122+
stack_info=stack_info,
123+
stacklevel=stacklevel,
124+
extra=extra,
125+
**kwargs)
124126

125127

126128
def get_trial_logger(name: str = __name__,
127129
trial: int = 0,
128130
level=logging.DEBUG) -> CustomLoggerAdapter:
129-
"""Sets up or retrieves the singleton instance of CustomLoggerAdapter."""
130-
global _trial_logger
131-
if _trial_logger:
132-
return _trial_logger
133-
131+
"""Sets up or retrieves a thread-local CustomLoggerAdapter for each thread."""
134132
logger = logging.getLogger(name)
135133
if not logger.handlers:
136134
formatter = logging.Formatter(
@@ -143,5 +141,4 @@ def get_trial_logger(name: str = __name__,
143141
logger.setLevel(level)
144142
logger.propagate = False
145143

146-
_trial_logger = CustomLoggerAdapter(logger, {'trial': trial})
147-
return _trial_logger
144+
return CustomLoggerAdapter(logger, {'trial': trial})

0 commit comments

Comments
 (0)