Every developer has a version of this story.
You’re about to open a pull request. The code works. The tests pass. Everything looks good.
Then you do one final scan and find this:
console.log("HERE");
console.log("user:", user);
console.log("final:", result);
And then, before the PR:
Search → Delete. Search → Delete. Search → Delete.
I got tired of it. So I built a tool to automate it.
What is Log Stripper?
Log Stripper is a VS Code extension that removes debug, log, and print statements from 23+ programming languages – with a preview so you always see what will be deleted before anything changes.

🔗 VS Code Marketplace
🔗 GitHub
The Features
🔍 Preview mode – Ctrl+Shift+D
Shows every line that will be removed. You confirm. Only then does anything change.
🎨 Highlight mode – Ctrl+Shift+H
Marks debug lines in red. No file modification. Review first, strip later.
🌍 23+ languages
JS, TS, Python, Java, Go, Rust, C#, Swift, Kotlin, Dart, Ruby, PHP, C, C++, Shell, Lua, Scala, Elixir, Haskell, R, Vue, Svelte, JSX/TSX
🏢 Workspace cleanup
Strip debug statements from an entire project in one command.
Why not just use grep/sed?
You could. But:
- No preview
- No multiline support
- No VS Code integration
- No safety rules
- Works differently on Windows vs Mac vs Linux
Log Stripper handles all of this inside VS Code with a consistent UX.
The Interesting Technical Part: Multiline Removal
Removing a single-line console.log("x") is trivial. The interesting case is:
console.log(
"user:",
JSON.stringify(user, null, 2),
"role:",
user.role
);
To remove this correctly, you need to:
- Find the opening
( - Track paren depth across lines
- Handle parens inside strings (don’t count those)
- Find the exact closing
)and optional; - Remove the entire block without touching surrounding code
Here’s the core ofskipParenBlock:
function skipParenBlock(lines: string[], startLine: number) {
const openIdx = lines[startLine].indexOf("(");
let bal = 1, li = startLine, ci = openIdx + 1;
let inStr: string | null = null;
while (li < lines.length) {
const s = lines[li];
while (ci < s.length) {
const ch = s[ci];
if (inStr) {
if (ch === "\") { ci += 2; continue; }
if (ch === inStr) inStr = null;
} else if (ch === '"' || ch === "'" || ch === "`") {
inStr = ch;
} else if (ch === "(") {
bal++;
} else if (ch === ")") {
bal--;
if (bal === 0) {
ci++;
if (s[ci] === ";") ci++;
return { endLine: li, endCol: ci };
}
}
ci++;
}
li++; ci = 0;
}
}
Architecture Decision
I separated the core logic from the VS Code layer:
src/
├── extension.ts ← VS Code commands, UI, workspace
└── stripper.ts ← Pure logic, zero VS Code deps
Why? Because stripper.ts can be tested without launching a VS Code host:
node --test out-test/test/stripper.test.js
151 tests. Fast. No mocking needed.
Safety Rules
The extension will never remove:
// console.log("commented") ← comment line, always kept
return console.log(x); ← inline code, always kept
const x = doThing() || console.log("fallback"); ← inline, kept
Only whole-statement debug calls that are the entire line (after indentation) are removed.
The Languages Covered
| Language | Examples |
|---|---|
| JS/TS |
console.log, debugger
|
| Python |
print(), logging.debug(), breakpoint()
|
| Java |
System.out.println, logger.debug()
|
| Go |
fmt.Println, log.Fatal
|
| Rust |
println!, dbg!, eprintln!
|
| C# |
Console.WriteLine, Debug.Write
|
| Swift |
print(), NSLog()
|
| Ruby |
puts, binding.pry
|
| PHP |
var_dump(), dd()
|
| Shell |
echo, printf
|
| …13 more | – |
Try It
# Install from Marketplace
code --install-extension saurabhchoudhary.log-stripper
Or search “Log Stripper” in the VS Code extensions panel.
Open a file with debug statements → Ctrl+Shift+D → see the preview → confirm.
-
Toggle Debug Highlights | Highlight/unhighlight debug lines
-
Preview & Strip Current File | Shows preview, then strips on confirm
-
Command Palette | Strip Entire Workspace | Clean whole project
-
Right-click → menu | Strip Current File | Strip without preview
-
Editor toolbar icons (eye / eye-closed / trash)
Built this because I couldn’t find a tool that did all of this properly. Turned out to be one of the most satisfying weekend projects I’ve done.
If you try it, I’d love feedback in the comments or as a GitHub issue. 🙌
By Saurabh Choudhary – Software Engineer
Introduction
Every developer has a version of this story.
You’re about to open a pull request. The code works. The tests pass. You’re ready. Then you do one last scroll through the file and see it:
console.log("HERE");
console.log("user data:", JSON.stringify(userData));
console.log("response", res);
Three debug logs you forgot to remove. You delete them, re-run the linter, and open the PR.
Two weeks later, someone reports that production logs are noisy with debug output. A developer on your team had left a logger.debug() in a hot path. Nobody caught it in review.
This isn’t a rare occurrence. It’s a pattern that plays out in every team, on every codebase, in every programming language.
I’ve experienced it across JavaScript, TypeScript, PHP, Go, Python, Vue.js, NestJS, Laravel, and Angular projects. Different languages, different ecosystems – same repetitive problem.
Eventually I stopped accepting it as “just part of the workflow” and decided to build a proper solution.
That solution is Log Stripper – a VS Code extension that removes debug, log, and print statements from 23+ programming languages with a safety-first, preview-driven approach.
The Problem
The problem has two dimensions that are easy to underestimate.
First: it’s repetitive. Before every commit, before every PR, before every release, developers go through the same manual process of searching for and deleting debug statements. On large codebases with dozens of files touched in a single feature branch, this becomes genuinely time-consuming.
Second: it’s error-prone. Humans miss things. A console.log inside a nested callback, a print() inside a conditional branch, a System.out.println buried in a Java service – these are easy to overlook when scanning files manually. And when they slip through, they end up in production.
There’s also a subtler issue: developers working under time pressure will rush the cleanup. The more pressure, the more likely something gets missed.
This is exactly the kind of task that should be automated. It is deterministic, repetitive, and rule-based. A computer should do it.
Why Existing Workflows Were Frustrating
Before building Log Stripper, I searched for existing solutions.
I found a few extensions that handled console.log removal for JavaScript. Some worked well for that specific case. But they had significant limitations:
Language coverage was narrow. Most extensions focused exclusively on JavaScript. If you wrote Python, Go, Java, or Rust, you were on your own.
No preview mode. Several extensions deleted statements immediately without showing you what would be removed. That’s unsafe. A developer who accidentally removes a legitimate logger call in production code will stop trusting the tool immediately.
No highlight mode. Sometimes I want to see the debug statements in a file without removing anything yet. I want to review them, understand them, decide manually. None of the tools I found offered non-destructive inspection.
No workspace cleanup. File-by-file cleanup doesn’t scale. Before a major release, I want to scan an entire project and clean everything in one pass.
Unreliable multiline handling. A console.log that spans multiple lines is common:
console.log(
"processing user:",
JSON.stringify(user, null, 2)
);
Many tools would only remove the first line and leave the rest, creating syntax errors.
Why I Built Log Stripper
The decision to build rather than adapt came down to one realization: the problem deserved a proper solution, not a workaround.
I wanted an extension that:
- Worked across every major language I use day-to-day
- Showed a preview before making any changes
- Could highlight debug lines without modifying files
- Could clean an entire workspace safely
- Was smart enough to never break code it shouldn’t touch
- Was backed by automated tests
None of those requirements are unreasonable. Together, they’re the minimum bar for a tool I’d actually trust.
The weekend I started building it, I intended it to be a one-day project. It grew into something more substantial as I realized the edge cases, the safety requirements, and the value of doing it properly.
Architecture
The most important architectural decision I made was separating the core logic from the VS Code integration layer.
The repository has two source files:
src/
├── extension.ts ← VS Code commands, UI, workspace operations
└── stripper.ts ← Core strip logic (zero VS Code dependencies)
stripper.ts has no awareness of VS Code. It takes a string of text and a language ID, and returns a new string with debug statements removed plus metadata about what was changed. That’s it.
This separation has two major benefits:
Testability. I can test the core logic without launching a VS Code Extension Development Host. The 151 automated tests run with a plain node --test command. Fast, simple, reliable.
Maintainability. If VS Code changes its API, or if I want to port the logic to a CLI tool or a git pre-commit hook, the core logic doesn’t need to change.
The VS Code layer handles everything user-facing: commands, keybindings, settings, decorations, progress notifications, and workspace file scanning.
Multi-Language Support
Supporting 23 languages sounds ambitious. The implementation is actually straightforward once you have the right architecture.
Each language is a key in the LANGUAGE_PATTERNS object, and its value is an array of regular expressions:
const LANGUAGE_PATTERNS: Record<string, RegExp[]> = {
javascript: [
/^(s*)console.(log|debug|info|warn|error|trace|...)s*(/,
/^(s*)debuggers*;?$/,
],
python: [
/^(s*)prints*(/,
/^(s*)logging.(debug|info|warning|error|critical)s*(/,
/^(s*)breakpoints*(s*)/,
],
// ... 21 more languages
};
Language aliases handle cases where VS Code’s language ID differs from the pattern group:
const LANG_ALIASES: Record<string, string> = {
svelte: "javascript",
"vue-html": "vue",
typescriptreact: "typescript",
};
When the extension receives a file, it looks up the language ID, finds the pattern list, and applies each pattern against every line. If no patterns are found for a language, the file is returned unchanged.
Adding a new language is a matter of adding one entry to LANGUAGE_PATTERNS and a set of test cases to stripper.test.ts. The infrastructure handles the rest.
Preview Mode
The preview mode was the feature I was most deliberate about designing.
When a developer triggers “Preview & Strip” with Ctrl+Shift+D, the extension:
- Runs the stripping logic internally (without modifying the file)
- Collects the list of lines that would be removed
- Shows a modal dialog listing those lines with their line numbers
- Waits for explicit confirmation before making any changes
This turns a potentially destructive operation into a fully transparent, opt-in action. The developer sees exactly what will happen and retains full control.
The modal includes the first 20 matching lines and appends “…and N more” if there are additional matches. This keeps the dialog readable on files with heavy debug coverage.
If the developer cancels, nothing changes. Not a single character in the file is modified.
Highlight Mode
Highlight mode solves a different need: I want to see the debug statements but I’m not ready to remove them yet.
When the developer presses Ctrl+Shift+H, the extension:
- Finds all lines matching debug patterns using
findDebugLineIndices() - Applies a VS Code text decoration that highlights those lines in red
- Adds an inline annotation “← debug” at the end of each line
The file is not modified. This is purely a visual overlay.
Pressing Ctrl+Shift+H again (or clicking the eye-closed icon in the toolbar) removes the highlights.
The highlight controller also handles three lifecycle events automatically:
- When you strip the file, highlights are cleared (no red lines on lines that no longer exist)
- When you edit the file and
refreshHighlightsOnEditis enabled, highlights recompute after a 120ms debounce
– When you switch tabs and clearHighlightsOnTabChange is enabled, highlights are cleared automatically
Workspace Cleanup
The workspace strip command scans every file in the project matching the supported language extensions and strips debug statements from each one.
The implementation uses VS Code’s workspace.findFiles() API with exclusion patterns to skip node_modules, dist, .git, vendor, build, and out by default. All exclusion patterns are configurable in settings.
The operation runs with a progress notification showing the current file being processed and a cancellation option. After completion, a summary shows how many statements were removed across how many files.
Because this modifies files on disk, the documentation explicitly recommends using version control. A git diff after the workspace strip is always informative.
Safety Design
Safety was a first-class concern throughout the design.
The extension enforces several rules to avoid breaking code it shouldn’t touch:
Rule 1: Whole-line only. A debug call is only removed if it is the entire statement on the line (after indentation). This prevents removal of:
return console.log(x); // kept - inline with return
const result = doWork() || console.log("fallback"); // kept - chained
Rule 2: Comments are never touched. Any line where the first non-whitespace characters form a comment prefix (//, #, --, /*, *) is skipped entirely.
Rule 3: Multiline paren balancing. When a debug call spans multiple lines, the extension tracks open and close parentheses (accounting for parens inside strings) to identify the exact end of the statement. The closing ) and optional ; are consumed, and any remaining code on that final line is preserved.
Rule 4: No modification on cancel. Preview mode never touches the file unless the user explicitly confirms. The stripping logic runs on an in-memory copy of the text, and the result is only written back if the user says yes.
Testing Strategy
I wanted the extension to be trustworthy. That required a proper test suite.
The tests live in test/stripper.test.ts and use Node’s built-in test runner – no Jest, no Mocha, no additional dependencies. The test command is:
tsc -p tsconfig.test.json && node --test out-test/test/stripper.test.js
The 151 test cases cover:
- Every language in
LANGUAGE_PATTERNS - Simple single-line removal
- Multiline call removal with paren balancing
- Comment preservation
- Inline code preservation
- Real-world code samples (NestJS controllers, Python classes, Go functions)
- Language aliases (Vue, Svelte, JSX, TSX)
- Unknown languages (should be a no-op)
- The
findDebugLineIndices()function (used by highlight mode)
Each test specifies the input code, the expected output, and the expected removal count. Any test that would cause the extension to modify code it shouldn’t – or fail to modify code it should – is a failing test.
Writing tests before polishing features was the right decision. It caught several edge cases during development and gave me confidence when refactoring the paren-balancing logic.
Publishing to the VS Code Marketplace
The Marketplace publishing process is simpler than I expected.
The key steps:
- Create a publisher account at
marketplace.visualstudio.com - Generate a Personal Access Token from Azure DevOps with Marketplace → Manage scope
- Install
@vscode/vsceglobally:npm install -g @vscode/vsce - Run
vsce packageto create the.vsixfile - Run
vsce publish --pat TOKENor upload the.vsixmanually via the publisher portal
The.vscodeignorefile controls what goes into the package. I excludedsrc/,test/, andnode_modules/– only the compiledout/directory ships. This keeps the extension lightweight.
One thing I’d emphasize: set the repository.url in package.json before publishing. It shows up prominently on the Marketplace listing and signals to users that the extension is open source and maintainable.
Lessons Learned
Separate your core logic from your integration layer. This applies far beyond VS Code extensions. Any time you can isolate the testable business logic from the framework or platform code, do it. It makes everything easier.
Safety features build trust. The preview mode and the “never remove inline code” rule weren’t technically necessary for the extension to work. But they’re what make a developer comfortable trusting the tool with real production code.
Test the edge cases first. Multiline calls, strings containing parens, empty files, unsupported languages – these were the cases most likely to cause silent failures. Writing tests for them before the happy path forced me to build robust logic from the start.
Ship before it’s perfect. The first version didn’t have highlight mode. The workspace strip was added later. Shipping v1 with the core feature – preview and strip – got real feedback faster than spending another two weeks on features users might not need.
Small tools have real value. This isn’t a SaaS platform. It’s not an AI product. It’s a focused tool that does one thing well. Developer tools like this get used quietly but consistently. That’s enough.
Future Improvements
Several improvements are on the roadmap:
Custom pattern rules. Allow developers to define their own patterns to remove. For teams using custom logging frameworks, this would make Log Stripper work with any codebase.
Pre-commit hook integration. A script or CLI wrapper that can run as a git pre-commit hook, catching debug statements before they ever reach a commit.
Team-wide configuration. A .logstripper.json configuration file that defines excluded patterns per project, shareable across a team through version control.
Ignore comments. A // log-stripper-ignore annotation that tells the extension to skip a specific line or block.
More language coverage. Zig, Nim, OCaml, and Erlang are candidates for future additions.
Conclusion
Log Stripper is a small tool. It solves one problem, and it solves it well.
But the process of building it taught me something more valuable than any individual feature: the instinct to look at a repetitive problem and ask “why am I doing this manually?” is one of the most productive instincts an engineer can develop.
We accept small frictions as part of the job. We work around limitations instead of fixing them. We solve the same problem repeatedly instead of automating it once.
Log Stripper exists because I stopped accepting one of those frictions.
If you’re reading this and you have something similar – a small repetitive task, an annoying workflow step, a gap in your tooling – build the solution. The process will teach you more than you expect, and the tool will serve you for years.
Try Log Stripper:
VS Code Marketplace: https://marketplace.visualstudio.com/items?itemName=saurabhchoudhary.log-stripper
GitHub: https://github.com/saurabhzaiswal/Log-Stripper-VS-Code
I’m actively improving Log Stripper, so real-world feedback is incredibly valuable.
If Log Stripper helps keep a few forgotten debug statements out of production, then it has already paid for itself.
