LogFileSink.java
// SPDX-License-Identifier: Apache-2.0
package com.shengzhiai.yugu.stcompat.internal;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/** Appends SDK log lines to a file (LogCat util and EngineSetting.setEnableSaveLogCatToFile). */
public final class LogFileSink implements YLog.Sink {
/** Files are rotated (truncated) beyond this size. */
static final long MAX_BYTES = 2L * 1024 * 1024;
private static final String[] LEVELS = {"OFF", "E", "W", "I", "D"};
private final File file;
private OutputStream out;
public LogFileSink(File file) throws IOException {
this.file = file;
File dir = file.getAbsoluteFile().getParentFile();
if (dir != null && !dir.isDirectory() && !dir.mkdirs() && !dir.isDirectory()) {
throw new IOException("cannot create " + dir);
}
this.out = new FileOutputStream(file, file.length() < MAX_BYTES);
}
public File file() {
return file;
}
/**
* Keeps a sink that already writes to {@code file} while the file is below {@link #MAX_BYTES},
* otherwise installs a new one (which starts the file over beyond that size).
*/
public static synchronized boolean ensure(File file) {
YLog.Sink s = YLog.getSink();
if (s instanceof LogFileSink) {
LogFileSink current = (LogFileSink) s;
if (current.file.getAbsoluteFile().equals(file.getAbsoluteFile()) && current.isOpen()
&& file.length() < MAX_BYTES) {
return true;
}
}
return install(file);
}
private synchronized boolean isOpen() {
return out != null;
}
/** Installs a sink writing to {@code file}; returns false when the file cannot be opened. */
public static synchronized boolean install(File file) {
uninstall();
try {
YLog.setSink(new LogFileSink(file));
return true;
} catch (IOException e) {
YLog.w("cannot open log file " + file + ": " + e.getMessage());
return false;
}
}
public static synchronized void uninstall() {
YLog.Sink s = YLog.getSink();
YLog.setSink(null);
if (s instanceof LogFileSink) {
((LogFileSink) s).close();
}
}
@Override
public synchronized void log(int level, String tag, String message, Throwable error) {
if (out == null) {
return;
}
StringBuilder sb = new StringBuilder();
sb.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US).format(new Date()))
.append(' ').append(level >= 0 && level < LEVELS.length ? LEVELS[level] : "?")
.append('/').append(tag).append(": ").append(message).append('\n');
if (error != null) {
StringWriter sw = new StringWriter();
error.printStackTrace(new PrintWriter(sw));
sb.append(sw).append('\n');
}
try {
out.write(sb.toString().getBytes(Codec.UTF_8));
out.flush();
} catch (IOException e) {
close();
}
}
public synchronized void close() {
if (out != null) {
try {
out.close();
} catch (IOException ignored) {
// closing a log file
}
out = null;
}
}
}