kollabor 0.4.9__py3-none-any.whl → 0.4.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/__init__.py +2 -0
- agents/coder/__init__.py +0 -0
- agents/coder/agent.json +4 -0
- agents/coder/api-integration.md +2150 -0
- agents/coder/cli-pretty.md +765 -0
- agents/coder/code-review.md +1092 -0
- agents/coder/database-design.md +1525 -0
- agents/coder/debugging.md +1102 -0
- agents/coder/dependency-management.md +1397 -0
- agents/coder/git-workflow.md +1099 -0
- agents/coder/refactoring.md +1454 -0
- agents/coder/security-hardening.md +1732 -0
- agents/coder/system_prompt.md +1448 -0
- agents/coder/tdd.md +1367 -0
- agents/creative-writer/__init__.py +0 -0
- agents/creative-writer/agent.json +4 -0
- agents/creative-writer/character-development.md +1852 -0
- agents/creative-writer/dialogue-craft.md +1122 -0
- agents/creative-writer/plot-structure.md +1073 -0
- agents/creative-writer/revision-editing.md +1484 -0
- agents/creative-writer/system_prompt.md +690 -0
- agents/creative-writer/worldbuilding.md +2049 -0
- agents/data-analyst/__init__.py +30 -0
- agents/data-analyst/agent.json +4 -0
- agents/data-analyst/data-visualization.md +992 -0
- agents/data-analyst/exploratory-data-analysis.md +1110 -0
- agents/data-analyst/pandas-data-manipulation.md +1081 -0
- agents/data-analyst/sql-query-optimization.md +881 -0
- agents/data-analyst/statistical-analysis.md +1118 -0
- agents/data-analyst/system_prompt.md +928 -0
- agents/default/__init__.py +0 -0
- agents/default/agent.json +4 -0
- agents/default/dead-code.md +794 -0
- agents/default/explore-agent-system.md +585 -0
- agents/default/system_prompt.md +1448 -0
- agents/kollabor/__init__.py +0 -0
- agents/kollabor/analyze-plugin-lifecycle.md +175 -0
- agents/kollabor/analyze-terminal-rendering.md +388 -0
- agents/kollabor/code-review.md +1092 -0
- agents/kollabor/debug-mcp-integration.md +521 -0
- agents/kollabor/debug-plugin-hooks.md +547 -0
- agents/kollabor/debugging.md +1102 -0
- agents/kollabor/dependency-management.md +1397 -0
- agents/kollabor/git-workflow.md +1099 -0
- agents/kollabor/inspect-llm-conversation.md +148 -0
- agents/kollabor/monitor-event-bus.md +558 -0
- agents/kollabor/profile-performance.md +576 -0
- agents/kollabor/refactoring.md +1454 -0
- agents/kollabor/system_prompt copy.md +1448 -0
- agents/kollabor/system_prompt.md +757 -0
- agents/kollabor/trace-command-execution.md +178 -0
- agents/kollabor/validate-config.md +879 -0
- agents/research/__init__.py +0 -0
- agents/research/agent.json +4 -0
- agents/research/architecture-mapping.md +1099 -0
- agents/research/codebase-analysis.md +1077 -0
- agents/research/dependency-audit.md +1027 -0
- agents/research/performance-profiling.md +1047 -0
- agents/research/security-review.md +1359 -0
- agents/research/system_prompt.md +492 -0
- agents/technical-writer/__init__.py +0 -0
- agents/technical-writer/agent.json +4 -0
- agents/technical-writer/api-documentation.md +2328 -0
- agents/technical-writer/changelog-management.md +1181 -0
- agents/technical-writer/readme-writing.md +1360 -0
- agents/technical-writer/style-guide.md +1410 -0
- agents/technical-writer/system_prompt.md +653 -0
- agents/technical-writer/tutorial-creation.md +1448 -0
- core/__init__.py +0 -2
- core/application.py +343 -88
- core/cli.py +229 -10
- core/commands/menu_renderer.py +463 -59
- core/commands/registry.py +14 -9
- core/commands/system_commands.py +2461 -14
- core/config/loader.py +151 -37
- core/config/service.py +18 -6
- core/events/bus.py +29 -9
- core/events/executor.py +205 -75
- core/events/models.py +27 -8
- core/fullscreen/command_integration.py +20 -24
- core/fullscreen/components/__init__.py +10 -1
- core/fullscreen/components/matrix_components.py +1 -2
- core/fullscreen/components/space_shooter_components.py +654 -0
- core/fullscreen/plugin.py +5 -0
- core/fullscreen/renderer.py +52 -13
- core/fullscreen/session.py +52 -15
- core/io/__init__.py +29 -5
- core/io/buffer_manager.py +6 -1
- core/io/config_status_view.py +7 -29
- core/io/core_status_views.py +267 -347
- core/io/input/__init__.py +25 -0
- core/io/input/command_mode_handler.py +711 -0
- core/io/input/display_controller.py +128 -0
- core/io/input/hook_registrar.py +286 -0
- core/io/input/input_loop_manager.py +421 -0
- core/io/input/key_press_handler.py +502 -0
- core/io/input/modal_controller.py +1011 -0
- core/io/input/paste_processor.py +339 -0
- core/io/input/status_modal_renderer.py +184 -0
- core/io/input_errors.py +5 -1
- core/io/input_handler.py +211 -2452
- core/io/key_parser.py +7 -0
- core/io/layout.py +15 -3
- core/io/message_coordinator.py +111 -2
- core/io/message_renderer.py +129 -4
- core/io/status_renderer.py +147 -607
- core/io/terminal_renderer.py +97 -51
- core/io/terminal_state.py +21 -4
- core/io/visual_effects.py +816 -165
- core/llm/agent_manager.py +1063 -0
- core/llm/api_adapters/__init__.py +44 -0
- core/llm/api_adapters/anthropic_adapter.py +432 -0
- core/llm/api_adapters/base.py +241 -0
- core/llm/api_adapters/openai_adapter.py +326 -0
- core/llm/api_communication_service.py +167 -113
- core/llm/conversation_logger.py +322 -16
- core/llm/conversation_manager.py +556 -30
- core/llm/file_operations_executor.py +84 -32
- core/llm/llm_service.py +934 -103
- core/llm/mcp_integration.py +541 -57
- core/llm/message_display_service.py +135 -18
- core/llm/plugin_sdk.py +1 -2
- core/llm/profile_manager.py +1183 -0
- core/llm/response_parser.py +274 -56
- core/llm/response_processor.py +16 -3
- core/llm/tool_executor.py +6 -1
- core/logging/__init__.py +2 -0
- core/logging/setup.py +34 -6
- core/models/resume.py +54 -0
- core/plugins/__init__.py +4 -2
- core/plugins/base.py +127 -0
- core/plugins/collector.py +23 -161
- core/plugins/discovery.py +37 -3
- core/plugins/factory.py +6 -12
- core/plugins/registry.py +5 -17
- core/ui/config_widgets.py +128 -28
- core/ui/live_modal_renderer.py +2 -1
- core/ui/modal_actions.py +5 -0
- core/ui/modal_overlay_renderer.py +0 -60
- core/ui/modal_renderer.py +268 -7
- core/ui/modal_state_manager.py +29 -4
- core/ui/widgets/base_widget.py +7 -0
- core/updates/__init__.py +10 -0
- core/updates/version_check_service.py +348 -0
- core/updates/version_comparator.py +103 -0
- core/utils/config_utils.py +685 -526
- core/utils/plugin_utils.py +1 -1
- core/utils/session_naming.py +111 -0
- fonts/LICENSE +21 -0
- fonts/README.md +46 -0
- fonts/SymbolsNerdFont-Regular.ttf +0 -0
- fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
- fonts/__init__.py +44 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
- kollabor-0.4.15.dist-info/RECORD +228 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
- plugins/agent_orchestrator/__init__.py +39 -0
- plugins/agent_orchestrator/activity_monitor.py +181 -0
- plugins/agent_orchestrator/file_attacher.py +77 -0
- plugins/agent_orchestrator/message_injector.py +135 -0
- plugins/agent_orchestrator/models.py +48 -0
- plugins/agent_orchestrator/orchestrator.py +403 -0
- plugins/agent_orchestrator/plugin.py +976 -0
- plugins/agent_orchestrator/xml_parser.py +191 -0
- plugins/agent_orchestrator_plugin.py +9 -0
- plugins/enhanced_input/box_styles.py +1 -0
- plugins/enhanced_input/color_engine.py +19 -4
- plugins/enhanced_input/config.py +2 -2
- plugins/enhanced_input_plugin.py +61 -11
- plugins/fullscreen/__init__.py +6 -2
- plugins/fullscreen/example_plugin.py +1035 -222
- plugins/fullscreen/setup_wizard_plugin.py +592 -0
- plugins/fullscreen/space_shooter_plugin.py +131 -0
- plugins/hook_monitoring_plugin.py +436 -78
- plugins/query_enhancer_plugin.py +66 -30
- plugins/resume_conversation_plugin.py +1494 -0
- plugins/save_conversation_plugin.py +98 -32
- plugins/system_commands_plugin.py +70 -56
- plugins/tmux_plugin.py +154 -78
- plugins/workflow_enforcement_plugin.py +94 -92
- system_prompt/default.md +952 -886
- core/io/input_mode_manager.py +0 -402
- core/io/modal_interaction_handler.py +0 -315
- core/io/raw_input_processor.py +0 -946
- core/storage/__init__.py +0 -5
- core/storage/state_manager.py +0 -84
- core/ui/widget_integration.py +0 -222
- core/utils/key_reader.py +0 -171
- kollabor-0.4.9.dist-info/RECORD +0 -128
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,1102 @@
|
|
|
1
|
+
<!-- Systematic Debugging skill - find and fix bugs efficiently using proven methodologies -->
|
|
2
|
+
|
|
3
|
+
debugging mode: SYSTEMATIC ELIMINATION
|
|
4
|
+
|
|
5
|
+
when this skill is active, you follow methodical debugging discipline.
|
|
6
|
+
this is a comprehensive guide to finding and fixing bugs systematically.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
PHASE 0: DEBUGGING ENVIRONMENT VERIFICATION
|
|
10
|
+
|
|
11
|
+
before attempting ANY debugging, verify your tools are ready.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
check python debugger availability
|
|
15
|
+
|
|
16
|
+
<terminal>python -c "import pdb; print('pdb available')"</terminal>
|
|
17
|
+
|
|
18
|
+
if pdb not available:
|
|
19
|
+
<terminal>pip install --upgrade pdb</terminal>
|
|
20
|
+
|
|
21
|
+
verify enhanced debugger:
|
|
22
|
+
<terminal>python -c "import ipdb; print('ipdb available')" 2>/dev/null || echo "ipdb not installed"</terminal>
|
|
23
|
+
|
|
24
|
+
if ipdb not installed (recommended but optional):
|
|
25
|
+
<terminal>pip install ipdb</terminal>
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
check logging configuration
|
|
29
|
+
|
|
30
|
+
<terminal>python -c "import logging; print('logging module ready')"</terminal>
|
|
31
|
+
|
|
32
|
+
verify log file locations:
|
|
33
|
+
<terminal>ls -la logs/ 2>/dev/null || ls -la .kollabor-cli/logs/ 2>/dev/null || echo "no logs directory"</terminal>
|
|
34
|
+
|
|
35
|
+
verify log levels:
|
|
36
|
+
<terminal>grep -r "logging.setLevel\|LOG_LEVEL" . --include="*.py" 2>/dev/null | head -5</terminal>
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
check git for bisecting
|
|
40
|
+
|
|
41
|
+
<terminal>git --version</terminal>
|
|
42
|
+
|
|
43
|
+
verify git repo:
|
|
44
|
+
<terminal>git status</terminal>
|
|
45
|
+
|
|
46
|
+
check for clean bisect state:
|
|
47
|
+
<terminal>git bisect reset 2>/dev/null || echo "no bisect in progress"</terminal>
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
check project structure
|
|
51
|
+
|
|
52
|
+
<terminal>ls -la</terminal>
|
|
53
|
+
<terminal>find . -name "*.py" -type f | head -20</terminal>
|
|
54
|
+
|
|
55
|
+
identify entry points:
|
|
56
|
+
<terminal>cat main.py 2>/dev/null | head -30</terminal>
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
check test coverage for regression tests
|
|
60
|
+
|
|
61
|
+
<terminal>python -m pytest --collect-only 2>&1 | head -20</terminal>
|
|
62
|
+
|
|
63
|
+
verify tests can run:
|
|
64
|
+
<terminal>python -m pytest tests/ --collect-only -q 2>&1 | tail -5</terminal>
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
check for existing error logs
|
|
68
|
+
|
|
69
|
+
<terminal>tail -50 .kollabor-cli/logs/kollabor.log 2>/dev/null || tail -50 logs/*.log 2>/dev/null || echo "no recent logs"</terminal>
|
|
70
|
+
|
|
71
|
+
check for crash reports:
|
|
72
|
+
<terminal>find . -name "*.crash" -o -name "*.stackdump" -o -name "core.*" 2>/dev/null</terminal>
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
verify strace/ltrace for system call tracing (linux)
|
|
76
|
+
|
|
77
|
+
<terminal>which strace ltrace 2>/dev/null || echo "system tracing not available"</terminal>
|
|
78
|
+
|
|
79
|
+
macos alternative:
|
|
80
|
+
<terminal>which dtruss 2>/dev/null || echo "dtruss not available (requires sudo)"</terminal>
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
PHASE 1: THE DEBUGGING MINDSET
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
understand before fixing
|
|
87
|
+
|
|
88
|
+
debugging is about understanding, not changing code.
|
|
89
|
+
every bug teaches you something about the system.
|
|
90
|
+
rush to fix = introduce more bugs.
|
|
91
|
+
|
|
92
|
+
the scientific method:
|
|
93
|
+
|
|
94
|
+
[1] observe - what is actually happening
|
|
95
|
+
[2] hypothesize - what could cause this
|
|
96
|
+
[3] predict - if hypothesis is true, then X should happen
|
|
97
|
+
[4] test - run experiment to verify prediction
|
|
98
|
+
[5] refine - update hypothesis based on results
|
|
99
|
+
|
|
100
|
+
repeat until you understand the root cause.
|
|
101
|
+
only then fix the code.
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
reproduce first
|
|
105
|
+
|
|
106
|
+
before touching any code:
|
|
107
|
+
|
|
108
|
+
[ ] can you reproduce the bug consistently?
|
|
109
|
+
[ ] what are the exact steps to reproduce?
|
|
110
|
+
[ ] what is the expected behavior?
|
|
111
|
+
[ ] what is the actual behavior?
|
|
112
|
+
[ ] what error messages appear?
|
|
113
|
+
[ ] when did this start happening?
|
|
114
|
+
|
|
115
|
+
if you cannot reproduce it:
|
|
116
|
+
- gather more information from user
|
|
117
|
+
- check logs for patterns
|
|
118
|
+
- identify conditions that might trigger it
|
|
119
|
+
- create hypothesis and test it
|
|
120
|
+
|
|
121
|
+
a bug you cannot reproduce is a bug you cannot verify is fixed.
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
isolate the problem
|
|
125
|
+
|
|
126
|
+
narrow down the scope:
|
|
127
|
+
|
|
128
|
+
is it in production or development only?
|
|
129
|
+
is it specific to certain data?
|
|
130
|
+
is it timing-related?
|
|
131
|
+
is it platform-specific?
|
|
132
|
+
is it configuration-dependent?
|
|
133
|
+
|
|
134
|
+
use binary search thinking:
|
|
135
|
+
- half the code, test, still broken?
|
|
136
|
+
- yes: bug is in that half
|
|
137
|
+
- no: bug is in other half
|
|
138
|
+
- repeat
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
PHASE 2: PRINT DEBUGGING
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
when to use print debugging
|
|
145
|
+
|
|
146
|
+
print debugging is appropriate for:
|
|
147
|
+
[ok] quick investigations
|
|
148
|
+
[ok] understanding control flow
|
|
149
|
+
[ok] verifying variable values
|
|
150
|
+
[ok] one-off debugging
|
|
151
|
+
[ok] situations without debugger access
|
|
152
|
+
|
|
153
|
+
not appropriate for:
|
|
154
|
+
[x] complex async code
|
|
155
|
+
[x] production debugging
|
|
156
|
+
[x] performance issues
|
|
157
|
+
[x] race conditions
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
effective print statements
|
|
161
|
+
|
|
162
|
+
bad prints:
|
|
163
|
+
print("here")
|
|
164
|
+
print("debug")
|
|
165
|
+
print(x)
|
|
166
|
+
|
|
167
|
+
good prints:
|
|
168
|
+
print(f"[DEBUG] process_data: input={input_data}, length={len(input_data)}")
|
|
169
|
+
print(f"[DEBUG] process_data: step 1 complete, processed={count} items")
|
|
170
|
+
print(f"[DEBUG] process_data: result={result}, errors={len(errors)}")
|
|
171
|
+
|
|
172
|
+
include:
|
|
173
|
+
- function name
|
|
174
|
+
- step or phase
|
|
175
|
+
- relevant variable values
|
|
176
|
+
- context about what you expect
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
structured logging instead of prints
|
|
180
|
+
|
|
181
|
+
use logging module:
|
|
182
|
+
import logging
|
|
183
|
+
|
|
184
|
+
logger = logging.getLogger(__name__)
|
|
185
|
+
|
|
186
|
+
def process_data(data):
|
|
187
|
+
logger.debug("process_data called with %d items", len(data))
|
|
188
|
+
logger.info("starting data processing")
|
|
189
|
+
|
|
190
|
+
for item in data:
|
|
191
|
+
logger.debug("processing item: %s", item)
|
|
192
|
+
|
|
193
|
+
logger.info("completed processing, result=%s", result)
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
configure logging level:
|
|
197
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
198
|
+
|
|
199
|
+
advantages over print:
|
|
200
|
+
- can be turned on/off globally
|
|
201
|
+
- includes timestamps
|
|
202
|
+
- includes file/line information
|
|
203
|
+
- can log to file
|
|
204
|
+
- different levels for different purposes
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
log levels guide
|
|
208
|
+
|
|
209
|
+
DEBUG: detailed diagnostic information
|
|
210
|
+
- variable values
|
|
211
|
+
- function entry/exit
|
|
212
|
+
- loop iterations
|
|
213
|
+
|
|
214
|
+
INFO: confirmation of expected progress
|
|
215
|
+
- application startup
|
|
216
|
+
- major milestones
|
|
217
|
+
- successful completions
|
|
218
|
+
|
|
219
|
+
WARNING: unexpected but recoverable
|
|
220
|
+
- missing optional data
|
|
221
|
+
- retries happening
|
|
222
|
+
- fallback behavior used
|
|
223
|
+
|
|
224
|
+
ERROR: serious failure
|
|
225
|
+
- exceptions caught
|
|
226
|
+
- failed operations
|
|
227
|
+
- data corruption
|
|
228
|
+
|
|
229
|
+
CRITICAL: severe failure
|
|
230
|
+
- application crash
|
|
231
|
+
- data loss
|
|
232
|
+
- cannot continue
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
log viewing techniques
|
|
236
|
+
|
|
237
|
+
view last N lines:
|
|
238
|
+
<terminal>tail -100 logs/app.log</terminal>
|
|
239
|
+
|
|
240
|
+
follow log in real-time:
|
|
241
|
+
<terminal>tail -f logs/app.log</terminal>
|
|
242
|
+
|
|
243
|
+
filter for errors:
|
|
244
|
+
<terminal>grep -i error logs/app.log</terminal>
|
|
245
|
+
|
|
246
|
+
filter for specific component:
|
|
247
|
+
<terminal>grep "process_data" logs/app.log</terminal>
|
|
248
|
+
|
|
249
|
+
view context around match:
|
|
250
|
+
<terminal>grep -C 5 "ERROR" logs/app.log</terminal>
|
|
251
|
+
|
|
252
|
+
count occurrences:
|
|
253
|
+
<terminal>grep -c "ERROR" logs/app.log</terminal>
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
PHASE 3: USING PDB DEBUGGER
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
basic pdb commands
|
|
260
|
+
|
|
261
|
+
start pdb:
|
|
262
|
+
<terminal>python -m pdb script.py</terminal>
|
|
263
|
+
|
|
264
|
+
or insert in code:
|
|
265
|
+
import pdb; pdb.set_trace()
|
|
266
|
+
|
|
267
|
+
essential commands:
|
|
268
|
+
l(ist) - show current code context
|
|
269
|
+
n(ext) - execute current line, move to next
|
|
270
|
+
s(tep) - step into function calls
|
|
271
|
+
c(ontinue) - run until next breakpoint
|
|
272
|
+
p(rint) - print expression
|
|
273
|
+
pp - pretty print expression
|
|
274
|
+
w(here) - show current stack frame
|
|
275
|
+
u(p) - move up stack frame
|
|
276
|
+
d(own) - move down stack frame
|
|
277
|
+
b(reak) - set breakpoint
|
|
278
|
+
cl(ear) - clear breakpoint
|
|
279
|
+
h(elp) - show help
|
|
280
|
+
q(uit) - quit debugger
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
setting breakpoints
|
|
284
|
+
|
|
285
|
+
set by line number:
|
|
286
|
+
(Pdb) break script.py:42
|
|
287
|
+
|
|
288
|
+
set by function:
|
|
289
|
+
(Pdb) break my_function
|
|
290
|
+
|
|
291
|
+
set conditional breakpoint:
|
|
292
|
+
(Pdb) break script.py:42, x > 100
|
|
293
|
+
|
|
294
|
+
view breakpoints:
|
|
295
|
+
(Pdb) break
|
|
296
|
+
|
|
297
|
+
clear breakpoint:
|
|
298
|
+
(Pdb) clear 1
|
|
299
|
+
|
|
300
|
+
clear all breakpoints:
|
|
301
|
+
(Pdb) clear
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
breakpoint() builtin (python 3.7+)
|
|
305
|
+
|
|
306
|
+
modern replacement for pdb.set_trace():
|
|
307
|
+
|
|
308
|
+
def complex_function(data):
|
|
309
|
+
result = process(data)
|
|
310
|
+
breakpoint() # drops into debugger
|
|
311
|
+
return analyze(result)
|
|
312
|
+
|
|
313
|
+
advantages:
|
|
314
|
+
- cleaner syntax
|
|
315
|
+
- can be disabled via PYTHONBREAKPOINT=0 env var
|
|
316
|
+
- works with custom debugger
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
inspecting variables
|
|
320
|
+
|
|
321
|
+
print variable:
|
|
322
|
+
(Pdb) p variable_name
|
|
323
|
+
|
|
324
|
+
pretty print:
|
|
325
|
+
(Pdb) pp complex_dict
|
|
326
|
+
|
|
327
|
+
examine attributes:
|
|
328
|
+
(Pdb) p object.__dict__
|
|
329
|
+
|
|
330
|
+
get type:
|
|
331
|
+
(Pdb) p type(variable)
|
|
332
|
+
|
|
333
|
+
get length:
|
|
334
|
+
(Pdb) p len(my_list)
|
|
335
|
+
|
|
336
|
+
call methods:
|
|
337
|
+
(Pdb) p my_dict.keys()
|
|
338
|
+
|
|
339
|
+
examine expression:
|
|
340
|
+
(Pdb) p x + y * 2
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
navigating the stack
|
|
344
|
+
|
|
345
|
+
show stack trace:
|
|
346
|
+
(Pdb) where
|
|
347
|
+
|
|
348
|
+
move to calling frame:
|
|
349
|
+
(Pdb) up
|
|
350
|
+
|
|
351
|
+
move back to callee:
|
|
352
|
+
(Pdb) down
|
|
353
|
+
|
|
354
|
+
view frame at level:
|
|
355
|
+
(Pdb) frame 2
|
|
356
|
+
|
|
357
|
+
examine variables in different frame:
|
|
358
|
+
(Pdb) up
|
|
359
|
+
(Pdb) p local_variable
|
|
360
|
+
(Pdb) down
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
modifying state
|
|
364
|
+
|
|
365
|
+
change variable value:
|
|
366
|
+
(Pdb) variable_name = new_value
|
|
367
|
+
|
|
368
|
+
execute statement:
|
|
369
|
+
(Pdb) import math; x = math.sqrt(16)
|
|
370
|
+
|
|
371
|
+
call function:
|
|
372
|
+
(Pdb) result = helper_function(x)
|
|
373
|
+
|
|
374
|
+
return from function early:
|
|
375
|
+
(Pdb) return value
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
post-mortem debugging
|
|
379
|
+
|
|
380
|
+
debug after crash:
|
|
381
|
+
import pdb
|
|
382
|
+
import sys
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
risky_operation()
|
|
386
|
+
except Exception:
|
|
387
|
+
pdb.post_mortem()
|
|
388
|
+
|
|
389
|
+
or from command line:
|
|
390
|
+
<terminal>python -m pdb script.py</terminal>
|
|
391
|
+
# let it crash
|
|
392
|
+
(Pdb) where # shows crash location
|
|
393
|
+
|
|
394
|
+
or with exception:
|
|
395
|
+
<terminal>python -m pdb -c continue script.py</terminal>
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
PHASE 4: USING IPDB (ENHANCED DEBUGGER)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
install and configure ipdb
|
|
402
|
+
|
|
403
|
+
<terminal>pip install ipdb</terminal>
|
|
404
|
+
|
|
405
|
+
set as default:
|
|
406
|
+
export PYTHONBREAKPOINT=ipdb.set_trace
|
|
407
|
+
|
|
408
|
+
use in code:
|
|
409
|
+
import ipdb; ipdb.set_trace()
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
ipdb advantages over pdb
|
|
413
|
+
|
|
414
|
+
- syntax highlighting
|
|
415
|
+
- tab completion
|
|
416
|
+
- better stack trace display
|
|
417
|
+
- context aware code display
|
|
418
|
+
- easier to read
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
ipdb-specific commands
|
|
422
|
+
|
|
423
|
+
h(elp) - show help with categories
|
|
424
|
+
ll - show more source code context
|
|
425
|
+
st(ep) - step into, showing code
|
|
426
|
+
n(ext) - next, skipping function calls
|
|
427
|
+
c(ontinue) - continue to next breakpoint
|
|
428
|
+
r(eturn) - continue until function returns
|
|
429
|
+
j(ump) lineno - jump to line number
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
ipdb configuration
|
|
433
|
+
|
|
434
|
+
create ~/.ipdbrc:
|
|
435
|
+
# ipdb configuration
|
|
436
|
+
alias st step
|
|
437
|
+
alias n next
|
|
438
|
+
alias c continue
|
|
439
|
+
alias list ll 50
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
PHASE 5: LOG ANALYSIS
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
reading log files effectively
|
|
446
|
+
|
|
447
|
+
start from the end:
|
|
448
|
+
<terminal>tail -500 logs/app.log | less</terminal>
|
|
449
|
+
|
|
450
|
+
search backward in less:
|
|
451
|
+
- use ? to search backward
|
|
452
|
+
- use n for next match
|
|
453
|
+
- use N for previous match
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
correlating timestamps
|
|
457
|
+
|
|
458
|
+
grep specific time range:
|
|
459
|
+
<terminal>grep "2024-01-15 10:2[3-4]" logs/app.log</terminal>
|
|
460
|
+
|
|
461
|
+
find time-adjacent entries:
|
|
462
|
+
<terminal>grep -A 10 -B 10 "ERROR" logs/app.log | head -30</terminal>
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
log pattern analysis
|
|
466
|
+
|
|
467
|
+
find unique error types:
|
|
468
|
+
<terminal>grep -o "ERROR: [A-Z].*" logs/app.log | sort -u</terminal>
|
|
469
|
+
|
|
470
|
+
count error frequency:
|
|
471
|
+
<terminal>grep "ERROR" logs/app.log | sort | uniq -c | sort -rn</terminal>
|
|
472
|
+
|
|
473
|
+
find exceptions:
|
|
474
|
+
<terminal>grep -i "exception\|traceback" logs/app.log -A 5</terminal>
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
structured logging with json
|
|
478
|
+
|
|
479
|
+
use json format for parsing:
|
|
480
|
+
import json
|
|
481
|
+
import logging
|
|
482
|
+
|
|
483
|
+
class JSONFormatter(logging.Formatter):
|
|
484
|
+
def format(self, record):
|
|
485
|
+
log_data = {
|
|
486
|
+
"timestamp": self.formatTime(record),
|
|
487
|
+
"level": record.levelname,
|
|
488
|
+
"logger": record.name,
|
|
489
|
+
"message": record.getMessage(),
|
|
490
|
+
"file": record.pathname,
|
|
491
|
+
"line": record.lineno
|
|
492
|
+
}
|
|
493
|
+
return json.dumps(log_data)
|
|
494
|
+
|
|
495
|
+
query json logs:
|
|
496
|
+
<terminal>grep "process_data" logs/app.log | jq '.level, .message'</terminal>
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
PHASE 6: GIT BISECT FOR REGRESSIONS
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
when to use git bisect
|
|
503
|
+
|
|
504
|
+
use when:
|
|
505
|
+
- bug appeared at some point in history
|
|
506
|
+
- you know a working version exists
|
|
507
|
+
- you cannot identify the problematic commit
|
|
508
|
+
|
|
509
|
+
bisect uses binary search through commits.
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
bisect workflow
|
|
513
|
+
|
|
514
|
+
start bisect:
|
|
515
|
+
<terminal>git bisect start</terminal>
|
|
516
|
+
|
|
517
|
+
mark current as bad:
|
|
518
|
+
<terminal>git bisect bad</terminal>
|
|
519
|
+
|
|
520
|
+
mark known good version:
|
|
521
|
+
<terminal>git bisect good <commit-hash></terminal>
|
|
522
|
+
or
|
|
523
|
+
<terminal>git bisect good v1.2.0</terminal>
|
|
524
|
+
|
|
525
|
+
git will checkout a middle commit.
|
|
526
|
+
test if bug exists:
|
|
527
|
+
[ ] if bug present: git bisect bad
|
|
528
|
+
[ ] if bug absent: git bisect good
|
|
529
|
+
|
|
530
|
+
repeat until git identifies the problematic commit.
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
automated bisect with script
|
|
534
|
+
|
|
535
|
+
create test script:
|
|
536
|
+
#!/bin/bash
|
|
537
|
+
# test_bug.sh
|
|
538
|
+
python -m pytest tests/test_specific.py -q
|
|
539
|
+
exit $? # exit 0 if pass, 1 if fail
|
|
540
|
+
|
|
541
|
+
make executable:
|
|
542
|
+
<terminal>chmod +x test_bug.sh</terminal>
|
|
543
|
+
|
|
544
|
+
run automated bisect:
|
|
545
|
+
<terminal>git bisect run ./test_bug.sh</terminal>
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
bisect cleanup
|
|
549
|
+
|
|
550
|
+
always reset when done:
|
|
551
|
+
<terminal>git bisect reset</terminal>
|
|
552
|
+
|
|
553
|
+
view bisect log:
|
|
554
|
+
<terminal>git bisect log</terminal>
|
|
555
|
+
|
|
556
|
+
replay bisect session:
|
|
557
|
+
<terminal>git bisect replay < logfile</terminal>
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
PHASE 7: MEMORY DEBUGGING
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
detecting memory leaks
|
|
564
|
+
|
|
565
|
+
use tracemalloc:
|
|
566
|
+
import tracemalloc
|
|
567
|
+
|
|
568
|
+
tracemalloc.start()
|
|
569
|
+
|
|
570
|
+
# ... code ...
|
|
571
|
+
|
|
572
|
+
snapshot = tracemalloc.take_snapshot()
|
|
573
|
+
top_stats = snapshot.statistics('lineno')
|
|
574
|
+
|
|
575
|
+
for stat in top_stats[:10]:
|
|
576
|
+
print(stat)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
memory profiling with memory_profiler
|
|
580
|
+
|
|
581
|
+
<terminal>pip install memory_profiler</terminal>
|
|
582
|
+
|
|
583
|
+
decorate function:
|
|
584
|
+
from memory_profiler import profile
|
|
585
|
+
|
|
586
|
+
@profile
|
|
587
|
+
def memory_intensive_function():
|
|
588
|
+
# ...
|
|
589
|
+
pass
|
|
590
|
+
|
|
591
|
+
run profiler:
|
|
592
|
+
<terminal>python -m memory_profiler script.py</terminal>
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
finding reference cycles
|
|
596
|
+
|
|
597
|
+
use gc module:
|
|
598
|
+
import gc
|
|
599
|
+
|
|
600
|
+
# collect garbage
|
|
601
|
+
gc.collect()
|
|
602
|
+
|
|
603
|
+
# get unreachable objects
|
|
604
|
+
unreachable = gc.collect()
|
|
605
|
+
print(f"collected {unreachable} objects")
|
|
606
|
+
|
|
607
|
+
# get all objects
|
|
608
|
+
all_objects = gc.get_objects()
|
|
609
|
+
print(f"total objects: {len(all_objects)}")
|
|
610
|
+
|
|
611
|
+
# find ref cycles
|
|
612
|
+
cycles = gc.collect()
|
|
613
|
+
print(f"found {cycles} cycles")
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
debugging with objgraph
|
|
617
|
+
|
|
618
|
+
<terminal>pip install objgraph</terminal>
|
|
619
|
+
|
|
620
|
+
find growth:
|
|
621
|
+
import objgraph
|
|
622
|
+
|
|
623
|
+
objgraph.show_growth()
|
|
624
|
+
|
|
625
|
+
show references:
|
|
626
|
+
objgraph.show_backrefs(some_object)
|
|
627
|
+
|
|
628
|
+
find common types:
|
|
629
|
+
objgraph.show_most_common_types()
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
PHASE 8: PERFORMANCE DEBUGGING
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
profiling with cProfile
|
|
636
|
+
|
|
637
|
+
<terminal>python -m cProfile -o profile.stats script.py</terminal>
|
|
638
|
+
|
|
639
|
+
view results:
|
|
640
|
+
<terminal>python -c "import pstats; p=pstats.Stats('profile.stats'); p.sort_stats('cumulative'); p.print_stats(20)"</terminal>
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
profiling specific function
|
|
644
|
+
|
|
645
|
+
import cProfile
|
|
646
|
+
import pstats
|
|
647
|
+
|
|
648
|
+
def profile_function(func, *args, **kwargs):
|
|
649
|
+
profiler = cProfile.Profile()
|
|
650
|
+
profiler.enable()
|
|
651
|
+
result = func(*args, **kwargs)
|
|
652
|
+
profiler.disable()
|
|
653
|
+
stats = pstats.Stats(profiler)
|
|
654
|
+
stats.sort_stats('cumulative')
|
|
655
|
+
stats.print_stats(20)
|
|
656
|
+
return result
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
line profiling
|
|
660
|
+
|
|
661
|
+
<terminal>pip install line_profiler</terminal>
|
|
662
|
+
|
|
663
|
+
decorate:
|
|
664
|
+
from line_profiler import LineProfiler
|
|
665
|
+
|
|
666
|
+
@profile
|
|
667
|
+
def my_function():
|
|
668
|
+
# ...
|
|
669
|
+
|
|
670
|
+
profile:
|
|
671
|
+
<terminal>python -m kernprof -l -v script.py</terminal>
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
finding hot spots
|
|
675
|
+
|
|
676
|
+
common performance issues:
|
|
677
|
+
- nested loops with heavy computation
|
|
678
|
+
- repeated database queries in loops
|
|
679
|
+
- excessive string concatenation
|
|
680
|
+
- inefficient data structure choices
|
|
681
|
+
- missing indexes
|
|
682
|
+
|
|
683
|
+
identify by:
|
|
684
|
+
- looking for high cumulative time
|
|
685
|
+
- checking call counts (unusually high?)
|
|
686
|
+
- examining functions called in loops
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
PHASE 9: CONCURRENCY DEBUGGING
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
debugging race conditions
|
|
693
|
+
|
|
694
|
+
signs of race conditions:
|
|
695
|
+
- intermittent failures
|
|
696
|
+
- different results each run
|
|
697
|
+
- crashes only under load
|
|
698
|
+
- works on dev, fails in production
|
|
699
|
+
|
|
700
|
+
debugging techniques:
|
|
701
|
+
- add delays to expose timing issues
|
|
702
|
+
- add logging with timestamps
|
|
703
|
+
- use thread sanitizers
|
|
704
|
+
- increase concurrency to make it more likely
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
using logging for race conditions
|
|
708
|
+
|
|
709
|
+
add thread ID to logs:
|
|
710
|
+
import logging
|
|
711
|
+
import threading
|
|
712
|
+
|
|
713
|
+
formatter = logging.Formatter(
|
|
714
|
+
'%(asctime)s [%(threadName)s] %(message)s'
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
this shows which thread did what.
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
deadlock detection
|
|
721
|
+
|
|
722
|
+
look for:
|
|
723
|
+
- multiple threads acquiring locks in different orders
|
|
724
|
+
- locks held during I/O operations
|
|
725
|
+
- nested lock acquisition
|
|
726
|
+
|
|
727
|
+
prevention:
|
|
728
|
+
- always acquire locks in consistent order
|
|
729
|
+
- use timeout for lock acquisition
|
|
730
|
+
- minimize lock holding time
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
asyncio debugging
|
|
734
|
+
|
|
735
|
+
enable asyncio debug mode:
|
|
736
|
+
import asyncio
|
|
737
|
+
|
|
738
|
+
asyncio.run(main(), debug=True)
|
|
739
|
+
|
|
740
|
+
check for:
|
|
741
|
+
- missing await on coroutines
|
|
742
|
+
- blocking calls in async functions
|
|
743
|
+
- never-awaited coroutines
|
|
744
|
+
|
|
745
|
+
use:
|
|
746
|
+
<terminal>python -X dev script.py</terminal>
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
PHASE 10: COMMON BUG PATTERNS
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
pattern: off-by-one errors
|
|
753
|
+
|
|
754
|
+
symptoms:
|
|
755
|
+
- index out of range
|
|
756
|
+
- missing first/last element
|
|
757
|
+
- fencepost errors
|
|
758
|
+
|
|
759
|
+
examples:
|
|
760
|
+
for i in range(len(items)): # processes all items
|
|
761
|
+
for i in range(len(items) - 1): # misses last item!
|
|
762
|
+
for i in range(1, len(items)): # misses first item!
|
|
763
|
+
|
|
764
|
+
check:
|
|
765
|
+
[ ] loop boundaries
|
|
766
|
+
[ ] slice indices
|
|
767
|
+
[ ] range() arguments
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
pattern: None reference errors
|
|
771
|
+
|
|
772
|
+
symptoms:
|
|
773
|
+
- AttributeError: 'NoneType' object has no attribute
|
|
774
|
+
- TypeError: object of type 'None' has no len()
|
|
775
|
+
|
|
776
|
+
prevention:
|
|
777
|
+
def process(data):
|
|
778
|
+
if data is None:
|
|
779
|
+
return None
|
|
780
|
+
return data.transform()
|
|
781
|
+
|
|
782
|
+
or use exceptions:
|
|
783
|
+
def process(data):
|
|
784
|
+
if data is None:
|
|
785
|
+
raise ValueError("data cannot be None")
|
|
786
|
+
return data.transform()
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
pattern: mutation while iterating
|
|
790
|
+
|
|
791
|
+
symptoms:
|
|
792
|
+
- unexpected behavior in loops
|
|
793
|
+
- skipped or duplicated items
|
|
794
|
+
|
|
795
|
+
wrong:
|
|
796
|
+
for item in items:
|
|
797
|
+
if condition(item):
|
|
798
|
+
items.remove(item)
|
|
799
|
+
|
|
800
|
+
correct:
|
|
801
|
+
items = [item for item in items if not condition(item)]
|
|
802
|
+
|
|
803
|
+
or:
|
|
804
|
+
for item in items[:]: # iterate over copy
|
|
805
|
+
if condition(item):
|
|
806
|
+
items.remove(item)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
pattern: string concatenation in loops
|
|
810
|
+
|
|
811
|
+
symptoms:
|
|
812
|
+
- slow performance
|
|
813
|
+
- high memory usage
|
|
814
|
+
|
|
815
|
+
wrong:
|
|
816
|
+
result = ""
|
|
817
|
+
for item in large_list:
|
|
818
|
+
result += str(item) # creates new string each time
|
|
819
|
+
|
|
820
|
+
correct:
|
|
821
|
+
parts = []
|
|
822
|
+
for item in large_list:
|
|
823
|
+
parts.append(str(item))
|
|
824
|
+
result = "".join(parts)
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
pattern: mutable default arguments
|
|
828
|
+
|
|
829
|
+
symptoms:
|
|
830
|
+
- state persists between calls
|
|
831
|
+
- unexpected data accumulation
|
|
832
|
+
|
|
833
|
+
wrong:
|
|
834
|
+
def append_item(item, items=[]):
|
|
835
|
+
items.append(item)
|
|
836
|
+
return items
|
|
837
|
+
|
|
838
|
+
correct:
|
|
839
|
+
def append_item(item, items=None):
|
|
840
|
+
if items is None:
|
|
841
|
+
items = []
|
|
842
|
+
items.append(item)
|
|
843
|
+
return items
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
pattern: catching too broad exceptions
|
|
847
|
+
|
|
848
|
+
wrong:
|
|
849
|
+
try:
|
|
850
|
+
complex_operation()
|
|
851
|
+
except: # catches everything, including SystemExit
|
|
852
|
+
pass
|
|
853
|
+
|
|
854
|
+
correct:
|
|
855
|
+
try:
|
|
856
|
+
complex_operation()
|
|
857
|
+
except (ValueError, TypeError) as e:
|
|
858
|
+
logger.error("specific error: %s", e)
|
|
859
|
+
raise # or handle appropriately
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
PHASE 11: SYSTEMATIC ELIMINATION
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
divide and conquer
|
|
866
|
+
|
|
867
|
+
half the problem space:
|
|
868
|
+
1. identify midpoint in code flow
|
|
869
|
+
2. add logging/checkpoint
|
|
870
|
+
3. run and observe
|
|
871
|
+
4. is bug before or after checkpoint?
|
|
872
|
+
5. repeat in affected half
|
|
873
|
+
|
|
874
|
+
example:
|
|
875
|
+
def process_workflow(input_data):
|
|
876
|
+
logger.debug("step 1: validate")
|
|
877
|
+
validate(input_data)
|
|
878
|
+
logger.debug("step 2: transform")
|
|
879
|
+
result = transform(input_data)
|
|
880
|
+
logger.debug("step 3: save")
|
|
881
|
+
save(result)
|
|
882
|
+
logger.debug("step 4: notify")
|
|
883
|
+
notify(result)
|
|
884
|
+
|
|
885
|
+
if bug appears between step 2 and 3:
|
|
886
|
+
- focus on transform()
|
|
887
|
+
- add more granular logging inside transform()
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
minimization
|
|
891
|
+
|
|
892
|
+
reduce to minimal reproducible case:
|
|
893
|
+
1. remove unnecessary code
|
|
894
|
+
2. simplify input data
|
|
895
|
+
3. isolate from dependencies
|
|
896
|
+
4. create standalone test case
|
|
897
|
+
|
|
898
|
+
goal: smallest program that still shows the bug.
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
control the variables
|
|
902
|
+
|
|
903
|
+
change one thing at a time:
|
|
904
|
+
[ ] fix one potential issue
|
|
905
|
+
[ ] test
|
|
906
|
+
[ ] if fixed: done
|
|
907
|
+
[ ] if not fixed: revert, try next issue
|
|
908
|
+
|
|
909
|
+
multiple simultaneous changes confuse cause and effect.
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
PHASE 12: DEBUGGING CHECKLIST
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
initial investigation
|
|
916
|
+
|
|
917
|
+
[ ] can you reproduce the bug?
|
|
918
|
+
[ ] what are the exact steps?
|
|
919
|
+
[ ] what is the expected vs actual behavior?
|
|
920
|
+
[ ] when did this start happening?
|
|
921
|
+
[ ] has anything changed recently?
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
information gathering
|
|
925
|
+
|
|
926
|
+
[ ] check error messages and stack traces
|
|
927
|
+
[ ] check logs for related errors
|
|
928
|
+
[ ] check logs for warnings leading up to error
|
|
929
|
+
[ ] check system resources (memory, disk, cpu)
|
|
930
|
+
[ ] check configuration files
|
|
931
|
+
[ ] check environment variables
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
hypothesis formation
|
|
935
|
+
|
|
936
|
+
[ ] what is the most likely cause?
|
|
937
|
+
[ ] what evidence supports this?
|
|
938
|
+
[ ] what evidence contradicts this?
|
|
939
|
+
[ ] what test will confirm or deny?
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
verification
|
|
943
|
+
|
|
944
|
+
[ ] does your hypothesis explain all symptoms?
|
|
945
|
+
[ ] can you prove the hypothesis with a test?
|
|
946
|
+
[ ] does fixing the suspected issue resolve it?
|
|
947
|
+
[ ] does the fix break anything else?
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
documentation
|
|
951
|
+
|
|
952
|
+
[ ] document root cause
|
|
953
|
+
[ ] document how to reproduce
|
|
954
|
+
[ ] document the fix
|
|
955
|
+
[ ] add test to prevent regression
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
PHASE 13: REMOTE/PRODUCTION DEBUGGING
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
safe production debugging
|
|
962
|
+
|
|
963
|
+
rules:
|
|
964
|
+
[1] never add code that might crash
|
|
965
|
+
[2] never enable expensive operations
|
|
966
|
+
[3] never expose sensitive data
|
|
967
|
+
[4] always use logging level changes
|
|
968
|
+
[5] always have rollback plan
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
enable debug logging temporarily
|
|
972
|
+
|
|
973
|
+
change log level:
|
|
974
|
+
import logging
|
|
975
|
+
|
|
976
|
+
logging.getLogger('my.module').setLevel(logging.DEBUG)
|
|
977
|
+
|
|
978
|
+
reload config:
|
|
979
|
+
<terminal>kill -HUP <pid></terminal>
|
|
980
|
+
|
|
981
|
+
remember to revert after debugging.
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
using debug endpoints
|
|
985
|
+
|
|
986
|
+
add guarded debug endpoint:
|
|
987
|
+
@app.route('/debug/info')
|
|
988
|
+
def debug_info():
|
|
989
|
+
if not current_app.debug:
|
|
990
|
+
abort(404)
|
|
991
|
+
return jsonify({
|
|
992
|
+
"version": VERSION,
|
|
993
|
+
"config": {k: v for k, v in config.items()
|
|
994
|
+
if 'secret' not in k.lower()},
|
|
995
|
+
"stats": get_stats()
|
|
996
|
+
})
|
|
997
|
+
|
|
998
|
+
only available in debug mode.
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
core dumps for crashes
|
|
1002
|
+
|
|
1003
|
+
enable core dumps:
|
|
1004
|
+
<terminal>ulimit -c unlimited</terminal>
|
|
1005
|
+
|
|
1006
|
+
analyze with gdb:
|
|
1007
|
+
<terminal>gdb python core</terminal>
|
|
1008
|
+
|
|
1009
|
+
common gdb commands:
|
|
1010
|
+
bt - backtrace
|
|
1011
|
+
f 0 - select frame 0
|
|
1012
|
+
p var - print variable
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
PHASE 14: DEBUGGING RULES (MANDATORY)
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
while this skill is active, these rules are MANDATORY:
|
|
1019
|
+
|
|
1020
|
+
[1] REPRODUCE FIRST before attempting fixes
|
|
1021
|
+
if you cannot reproduce it, you cannot verify the fix
|
|
1022
|
+
gather more information until you can reproduce
|
|
1023
|
+
|
|
1024
|
+
[2] ONE CHANGE AT A TIME
|
|
1025
|
+
never change multiple things simultaneously
|
|
1026
|
+
test each change independently
|
|
1027
|
+
|
|
1028
|
+
[3] UNDERSTAND ROOT CAUSE before fixing
|
|
1029
|
+
a fix that "just works" without understanding
|
|
1030
|
+
will create more problems later
|
|
1031
|
+
|
|
1032
|
+
[4] ADD TEST FOR BUG before fixing
|
|
1033
|
+
this ensures you can verify the fix
|
|
1034
|
+
and prevents regression
|
|
1035
|
+
|
|
1036
|
+
[5] MINIMIZE THE REPRODUCTION CASE
|
|
1037
|
+
smaller test cases are easier to debug
|
|
1038
|
+
and prove you understand the issue
|
|
1039
|
+
|
|
1040
|
+
[6] DOCUMENT YOUR FINDINGS
|
|
1041
|
+
write down what you found
|
|
1042
|
+
future debuggers will thank you
|
|
1043
|
+
|
|
1044
|
+
[7] USE APPROPRIATE TOOLS
|
|
1045
|
+
print debugging for simple cases
|
|
1046
|
+
debugger for complex control flow
|
|
1047
|
+
logging for production issues
|
|
1048
|
+
|
|
1049
|
+
[8] NEVER IGNORE WARNINGS
|
|
1050
|
+
warnings often precede errors
|
|
1051
|
+
fix the warning, not just the error
|
|
1052
|
+
|
|
1053
|
+
[9] CHECK ASSUMPTIONS
|
|
1054
|
+
what you think is true might not be
|
|
1055
|
+
verify with code/tests
|
|
1056
|
+
|
|
1057
|
+
[10] FIX THE ROOT, NOT THE SYMPTOM
|
|
1058
|
+
error handling that swallows exceptions
|
|
1059
|
+
masks the real problem
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
FINAL REMINDERS
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
debugging is learning
|
|
1066
|
+
|
|
1067
|
+
every bug improves your understanding.
|
|
1068
|
+
the system is teaching you something.
|
|
1069
|
+
listen to what it says.
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
slow is smooth, smooth is fast
|
|
1073
|
+
|
|
1074
|
+
systematic debugging beats quick fixes.
|
|
1075
|
+
the time you spend understanding
|
|
1076
|
+
saves time later.
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
the answer is in the code
|
|
1080
|
+
|
|
1081
|
+
not in changing things randomly.
|
|
1082
|
+
not in trying random solutions.
|
|
1083
|
+
read the code, understand the flow.
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
when stuck
|
|
1087
|
+
|
|
1088
|
+
[ ] step away, take a break
|
|
1089
|
+
[ ] explain the problem to someone else
|
|
1090
|
+
[ ] write down what you know
|
|
1091
|
+
[ ] question your assumptions
|
|
1092
|
+
[ ] try a different tool or approach
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
the goal
|
|
1096
|
+
|
|
1097
|
+
not just to fix the bug.
|
|
1098
|
+
to understand why it happened.
|
|
1099
|
+
to prevent similar bugs.
|
|
1100
|
+
to improve the system.
|
|
1101
|
+
|
|
1102
|
+
now go find that bug.
|