【问题标题】:Android send log lines as an email periodicallyAndroid 定期将日志行作为电子邮件发送
【发布时间】:2020-02-14 19:29:52
【问题描述】:

我想每 10 分钟将日志行发送到电子邮件。

为此,我使用了一个计时器,并在计时器内部通过电子邮件发送日志。

但是我在 2 封电子邮件之间丢失了一些日志行。

例如,根据我的算法,我的第一封电子邮件不包含任何正常的行。

我的第二封电子邮件包含 15.37 到 15.38 秒之间的日志行。

我的第三封电子邮件包含 15.44 到 15.48 时间间隔之间的日志。

我的第四封电子邮件包含 15.55 到 15.58 时间间隔之间的日志。

如您所见,我丢失了一些日志,但找不到避免这种情况的方法。

以下是我的服务类中的代码:

@Override
public void onCreate() {
    super.onCreate();

    mTimer = new Timer();

    mTimer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {

            sendLogFile();

        }
    }, 0, 1000 * 60 * 10 );

}

在 sendSupport 方法内部,第二个参数作为日志行的内容发送,其中 logs 是静态字符串变量。

private void sendLogFile() {

    mInteractor.sendSupport("LOG FILE", "MSG"+logs, "SUBJECT"+ System.currentTimeMillis(), "",

            result -> {

                Timber.log(Log.DEBUG, "sendSupport Thread.currentThread().getName() " + Thread.currentThread().getName());

                if (result.isSuccess) {

                    Timber.d("is sent");

                    writeLogFile();

                } else {

                    Timber.d("is NOT sent");

                }
            }

    );

}



private void writeLogFile()
{

    try {

        StringBuilder logBuilder = new StringBuilder();

        process = Runtime.getRuntime().exec( "logcat -d");

        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(process.getInputStream()));

        String line;

        while ((line = bufferedReader.readLine()) != null) {
            logBuilder.append(line + "\n");
        }

        logs =  logBuilder.toString();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

因此,我不知道如何才能在电子邮件中定期获取所有日志。

谢谢。

【问题讨论】:

  • 仍在等待想法..
  • 你想在这 10 分钟之间发送日志还是整个日志?
  • 我会尝试将日志保存到数据库表中,当计时器触发时,我会尝试从表中读取日志并使用异步任务发送电子邮件。
  • 如何旋转日志文件,为日志创建 2 个文件 - 10 分钟后写入一个文件 切换,开始写入另一个 - 发送并清除第一个文件。我的猜测是,由于执行 Internet 操作需要很长时间,您会丢失数据,如果您将其切换到内部操作并放弃您的连接依赖,它将有所帮助(它仍然不是 100% 丢失证明)
  • 嗨@Hilal,您有机会使用 FileTree 查看建议的解决方案吗?如果您有任何问题,请告诉我。

标签: android logging


【解决方案1】:

虽然@Knossos 的回答指出了一些日志丢失的原因,但它并没有建议如何使用这些知识可靠地从用户的手机获取日志,当您无权访问他们的设备时运行一些adb 命令。

这是我建议做的事情:

  1. 由于您已经使用Timber,只需为其添加更多功能即可。即,添加一个额外的LoggingTree,它将日志保存到一个文件中,而不是仅仅将它们发布到 Logcat。您可以让多个 Timber Tree 同时工作,因此如果需要,您可以同时拥有 Logcat 和 File 日志。
  2. 使用相同的计时器在需要的时间和地点发送电子邮件。但是,不要使用对logcat -d 的访问,只需使用 Timber 已经写入日志的文件。不要忘记在发送电子邮件之前刷新流。为了不一次又一次地发送相同的日志,请配置FileTree,使其每次发送前一个文件时都会创建一个新文件(例如,通过方法调用设置一个新文件名)。
  3. 利润:)

要登录两个不同的系统(树),您只需在 Timber 中再添加一个:

Timber.plant(new Timber.DebugTree());
Timber.plant(new FileLoggingTree());

下面是FileLoggingTree(source) 的示例:

public class FileLoggingTree extends Timber.DebugTree {
    private static Logger mLogger = LoggerFactory.getLogger(FileLoggingTree.class);
    private static final String LOG_PREFIX = "my-log";

    public FileLoggingTree(Context context) {
        final String logDirectory = context.getFilesDir() + "/logs";
        configureLogger(logDirectory);
    }

    private void configureLogger(String logDirectory) {
        // reset the default context (which may already have been initialized)
        // since we want to reconfigure it
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        loggerContext.reset();

        RollingFileAppender<ILoggingEvent> rollingFileAppender = new RollingFileAppender<>();
        rollingFileAppender.setContext(loggerContext);
        rollingFileAppender.setAppend(true);
        rollingFileAppender.setFile(logDirectory + "/" + LOG_PREFIX + "-latest.html");

        SizeAndTimeBasedFNATP<ILoggingEvent> fileNamingPolicy = new SizeAndTimeBasedFNATP<>();
        fileNamingPolicy.setContext(loggerContext);
        fileNamingPolicy.setMaxFileSize("1MB");

        TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<>();
        rollingPolicy.setContext(loggerContext);
        rollingPolicy.setFileNamePattern(logDirectory + "/" + LOG_PREFIX + ".%d{yyyy-MM-dd}.%i.html");
        rollingPolicy.setMaxHistory(5);
        rollingPolicy.setTimeBasedFileNamingAndTriggeringPolicy(fileNamingPolicy);
        rollingPolicy.setParent(rollingFileAppender);  // parent and context required!
        rollingPolicy.start();

        HTMLLayout htmlLayout = new HTMLLayout();
        htmlLayout.setContext(loggerContext);
        htmlLayout.setPattern("%d{HH:mm:ss.SSS}%level%thread%msg");
        htmlLayout.start();

        LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<>();
        encoder.setContext(loggerContext);
        encoder.setLayout(htmlLayout);
        encoder.start();

        // Alternative text encoder - very clean pattern, takes up less space
//        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
//        encoder.setContext(loggerContext);
//        encoder.setCharset(Charset.forName("UTF-8"));
//        encoder.setPattern("%date %level [%thread] %msg%n");
//        encoder.start();

        rollingFileAppender.setRollingPolicy(rollingPolicy);
        rollingFileAppender.setEncoder(encoder);
        rollingFileAppender.start();

        // add the newly created appenders to the root logger;
        // qualify Logger to disambiguate from org.slf4j.Logger
        ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
        root.setLevel(Level.DEBUG);
        root.addAppender(rollingFileAppender);

        // print any status messages (warnings, etc) encountered in logback config
        StatusPrinter.print(loggerContext);
    }

    @Override
    protected void log(int priority, String tag, String message, Throwable t) {
        if (priority == Log.VERBOSE) {
            return;
        }

        String logMessage = tag + ": " + message;
        switch (priority) {
            case Log.DEBUG:
                mLogger.debug(logMessage);
                break;
            case Log.INFO:
                mLogger.info(logMessage);
                break;
            case Log.WARN:
                mLogger.warn(logMessage);
                break;
            case Log.ERROR:
                mLogger.error(logMessage);
                break;
        }
    }
}

【讨论】:

    【解决方案2】:

    问题在于logcat -d 只为您提供流中最新的 X 字节数据。不能保证在 10 分钟的间隔内获得所有内容。

    在最好的情况下,你会得到你想要的。在最坏的情况下,您会丢失日志数据或日志部分重叠(您也会从之前的转储中获得一些数据)。

    你可以在这里看到这个:adb logcat -d | dd

    ...
    03-27 11:36:27.474   791 22420 E ResolverController: No valid NAT64 prefix (147, <unspecified>/0)
    03-27 11:36:27.612  3466  3521 I PlayCommon: [657] alsu.c(187): Successfully uploaded logs.
    453+111 records in
    499+1 records out
    255863 bytes (256 kB, 250 KiB) copied, 0,136016 s, 1,9 MB/s
    

    如您所见,它显然是通过 logcat -d 拉取的 256 kB 块。

    从好的方面来说,你可以改变它!如果您查看adb logcat --help,您可以看到选项。

    例如,如果您使用adb logcat -d -t '100000' | dd(过去 100000 秒内的所有日志。我现在有以下内容:

    ...
    03-27 11:45:41.687   791  1106 I netd    : bandwidthSetGlobalAlert(2097152) <0.90ms>
    03-27 11:45:42.098 21897 23376 V FA      : Inactivity, disconnecting from the service
    2237+1558 records in
    2879+1 records out
    1474408 bytes (1,5 MB, 1,4 MiB) copied, 1,20785 s, 1,2 MB/s
    

    1.5 MB 的日志。您应该能够使用此获取所有日志。

    您记录每个 logcat 拉取的时间戳,然后每次使用它来确定自上次拉取以来的秒数。

    希望对你有帮助!

    【讨论】:

      【解决方案3】:

      您不能在没有用户交互的情况下从设备发送电子邮件,或者您自己实现允许在没有用户交互的情况下执行此操作的电子邮件功能。

      大多数应用都有一些 api 端点来发送日志。

      【讨论】:

      • 我使用的是安卓盒子,我可以发送它。你是说,这就是我丢失日志的原因吗?
      猜你喜欢
      • 1970-01-01
      • 2016-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-20
      • 1970-01-01
      • 2016-11-23
      • 1970-01-01
      相关资源
      最近更新 更多