Logging & Debugging
Logging & Debugging
Every Android developer spends a significant portion of their time reading log output and stepping through code in the debugger. Mastering these two tools is not optional — they are your primary instruments for understanding what your app is doing at runtime, diagnosing crashes, and verifying correctness. This lesson covers Logcat in depth and walks through the debugger workflow inside Android Studio.
Understanding Logcat
Logcat is the Android system log. Every component of the OS, every library, and every app on the device writes to this single shared stream. Android Studio's Logcat tool window shows you that stream filtered by process, device, and log level in real time.
The entry point for your own log messages is the android.util.Log class. It exposes one static method per log level:
The Five Log Levels
Android defines five log levels, ordered from lowest to highest severity:
- VERBOSE (
Log.v) — the most granular output; usually disabled in release builds. Use for extremely detailed trace information that you only want temporarily. - DEBUG (
Log.d) — development-time messages: method entry/exit, variable values, flow checkpoints. The workhorse of day-to-day debugging. - INFO (
Log.i) — informational messages that confirm expected milestones: "user logged in", "cache warmed", "config loaded". - WARN (
Log.w) — something unexpected happened but the app can continue: a deprecated API is in use, a retried operation succeeded on the second attempt. - ERROR (
Log.e) — a significant failure has occurred. Always pass the fullThrowableas the third argument so the stack trace is preserved in the log.
Log.e and Log.w is the exception itself, not its message. Writing Log.e(TAG, e.getMessage()) loses the stack trace entirely — you see only the message string. Always write Log.e(TAG, "description", e) to get the full chain of causes printed beneath your message.
Defining a Good TAG
The tag is what you filter on in Logcat. Two conventions exist side by side:
private static final String TAG = "NetworkRepository";— a plain string you type once per class. Simple and predictable.private static final String TAG = NetworkRepository.class.getSimpleName();— automatically matches the class name; safe to rename with a refactor tool.
Either is fine. The important rule is: one TAG per class, defined as a constant, never constructed inline. Avoid Log.d("my-feature-debug-thing", ...) scattered through multiple files — it is impossible to filter consistently.
Using the Logcat Tool Window
Open Logcat via View → Tool Windows → Logcat (or the Logcat tab at the bottom of Android Studio). Key controls:
- Device / Process selector — filter to your emulator or connected device, then to your app's process (shown as
com.example.myapp). - Log level dropdown — selecting DEBUG shows DEBUG, INFO, WARN, and ERROR; selecting ERROR shows only ERROR. Lower levels are always inclusive of higher ones.
- Search / filter bar — type a tag, a package name, or any text. Use the dropdown arrow to switch between "Show only selected application" and a custom filter expression like
tag:NetworkRepository level:error. - Clear logcat button — clears the scrollback buffer. Useful before reproducing a specific bug so irrelevant old lines do not distract you.
Stripping Verbose and Debug Logs from Release Builds
Log calls are cheap but not free, and the strings you pass to them may contain sensitive data (user IDs, tokens, internal paths). There are two standard approaches to suppressing them in production:
Approach 1 — BuildConfig.DEBUG guard:
The BuildConfig.DEBUG constant is true for debug builds and false for release builds. The JIT eliminates the dead branch entirely in a release APK, so there is zero runtime cost.
Approach 2 — ProGuard/R8 rule: Add this to your proguard-rules.pro to strip all verbose and debug calls at build time:
adb logcat), and some devices write them to world-readable storage. Treat every log line as potentially visible.
Debugging with the Android Studio Debugger
Logcat shows you what happened. The debugger lets you freeze time and inspect the full state of your program. To attach the debugger, launch the app with the bug icon (Debug 'app') instead of the run icon. Android Studio compiles a debuggable APK, installs it, and connects the JDWP debugger.
Setting a breakpoint: Click in the gutter to the left of a line number. A red circle appears. When execution reaches that line, the app pauses and Android Studio surfaces the Debug tool window.
Key debugger controls:
- Step Over (F8) — execute the current line and move to the next one in the same method. Does not enter called methods.
- Step Into (F7) — enter the method being called on the current line. Essential for tracing through your own code.
- Step Out (Shift+F8) — finish the current method and return to its caller.
- Resume Program (F9) — continue execution until the next breakpoint or the app finishes.
- Evaluate Expression (Alt+F8) — type any Java expression and evaluate it in the current stack frame. You can call methods, read fields, and even mutate variables.
The Variables pane shows every local variable and field in scope. Expand objects to see nested fields. The Watches pane lets you pin specific expressions so they are always visible across multiple breakpoint hits.
Conditional Breakpoints
If a loop runs 1,000 times and crashes only on iteration 753, a plain breakpoint is useless — you would have to press F9 thousands of times. Right-click the breakpoint circle and select Edit breakpoint. Enter a condition like:
The debugger now pauses only when that expression evaluates to true. This is one of the most powerful features of the IDE debugger and is essential for diagnosing intermittent failures.
Attaching the Debugger to a Running Process
If your app is already running and you want to debug without restarting it, use Run → Attach Debugger to Android Process. Select your process from the list. This is invaluable for debugging initialization-order bugs where relaunching changes the timing.
Practical Workflow
A productive debug session typically looks like this:
- Reproduce the bug and read the Logcat output. Look for the first
Eline or any unexpectedWlines near the time of failure. - If it is a crash, find the exception class and the first line of your code in the stack trace — that is your entry point.
- Set a breakpoint on that line and rerun in debug mode.
- When the breakpoint hits, use the Variables pane to inspect state. Evaluate expressions to test hypotheses without changing the code.
- Step through the suspect code path until you identify the wrong value or unexpected branch.
- Fix, re-run, confirm the
Log.doutput now shows the expected flow, and remove any temporary debug log lines before committing.
Log.i and Log.e calls in your code permanently. They are invaluable for diagnosing issues reported by users or found on CI — situations where you cannot attach a debugger. Think of them as permanent diagnostic instruments, not temporary scaffolding.
Summary
The android.util.Log class is your lightweight diagnostic tool — five levels from VERBOSE to ERROR, each paired with a TAG that makes filtering in Logcat instant. Guard DEBUG-level messages with BuildConfig.DEBUG to keep release builds clean and secure. For deeper inspection, the Android Studio debugger lets you pause execution, inspect every variable, evaluate arbitrary expressions, and apply conditional breakpoints to isolate intermittent failures. Together, Logcat and the debugger eliminate the guesswork from Android development.