Updated 2026-10-13: The 2014 implementation wrote directly under the external-storage root, which is outdated. Android 10 (API 29) introduced scoped storage; Android 11 (API 30) ignores
requestLegacyExternalStoragefor apps targeting API 30 or higher. The replacement below uses app-internal storage, requires Android 4.4 (API 19) or later, and needs no storage permission. See Android 11 storage changes.
Log2File records logs explicitly supplied by an Android application to a file. It is useful when:
- A computer cannot be connected for debugging, for example because USB OTG occupies the port.
- Logs are difficult to capture in real time.
- An intermittent bug requires a record of the operations preceding it.
- I’ll leave the other uses to your imagination.
It saves only the content passed to w(); it does not automatically capture system logcat. Use the Logcat tools for system logging.
0. Introduction
This article originally appeared on my CSDN blog before moving here. The original GitHub source URL returned 404 during this update, so the replacement implementation is included below.
1. Log2File Source Code
The original static writer and separate logInit flag could race during concurrent writes and closure. This version gives one object ownership of a logging session and uses the same lock for writing and closing. Initialization, write, and close failures propagate as IOException for the caller to handle.
Files live under context.getFilesDir()/log2file/. This is app-internal storage, needs no storage permission, and is removed when the app is uninstalled. Use temporary read access through FileProvider or a similar sharing mechanism to export a log. See app-specific files.
The implementation uses UTF-8 and Unix epoch milliseconds instead of the deprecated, locale-dependent Date.toLocaleString(). StandardCharsets is available from API 19:
1 | import android.content.Context; |
Use a single Log2File object per file within a process. synchronized does not coordinate different logger objects or different processes. Writing after close() throws an exception; resource ownership must make it clear who closes the object and when.
This example flushes each line, which is suitable for short, low-frequency diagnostic sessions but adds I/O. flush() forwards Java-buffered data to the underlying stream; it does not guarantee recovery after power loss. Long-running or high-frequency capture needs file-size limits, retention rules, and bounded write queues, with batching and rotation as appropriate. Those policies are outside this minimal example. See BufferedWriter.
2. Usage
The constructor replaces the original static init() call. Open a logger in a background task, write the relevant events, and close it when the task finishes:
1 | // Run this on a background worker, not the UI thread. |
try-with-resources calls close() on both normal and exceptional exit. If capture spans several callbacks, let one task or component own the instance and close it only after all writes finish. Do not reopen the file for every log message.
Record only the information required for diagnosis; avoid passwords, tokens, and complete user input. App-internal storage controls file ownership and access, but does not replace redaction or retention limits.
About Me && Blog
Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”
- Blogger Intro: It includes my WeChat and WeChat group links.
- Blog Content Navigation: A guide for my blog content.
- Curated Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome self-recommendations and recommendations (just message me on WeChat).
- Android Performance Optimization Knowledge Planet: The paid community stopped operating in 2026 and no longer accepts new members; this link preserves its history.
One walks faster alone, but a group walks further together.
