将崩溃日志存到本地

首先看下工具类:

package com.hugedata.share_demo;

/**
 * Created by DELL on 2014/9/26.
 */
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

/**
 * UncaughtException处理类,当程序发生Uncaught异常的时候,由该类来接管程序,并记录发送错误报告.
 *
 * @author way
 *
 */
public class CrashHandler implements UncaughtExceptionHandler {
    private static final String TAG = "CrashHandler";
    private Thread.UncaughtExceptionHandler mDefaultHandler;// 系统默认的UncaughtException处理类
    private static CrashHandler INSTANCE = new CrashHandler();// CrashHandler实例
    private Context mContext;// 程序的Context对象
    private Map<String, String> info = new HashMap<String, String>();// 用来存储设备信息和异常信息
    private SimpleDateFormat format = new SimpleDateFormat(
            "yyyy-MM-dd-HH-mm-ss");// 用于格式化日期,作为日志文件名的一部分

    /** 保证只有一个CrashHandler实例 */
    private CrashHandler() {

    }

    /** 获取CrashHandler实例 ,单例模式 */
    public static CrashHandler getInstance() {
        return INSTANCE;
    }

    /**
     * 初始化
     *
     * @param context
     */
    public void init(Context context) {
        mContext = context;
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 获取系统默认的UncaughtException处理器
        Thread.setDefaultUncaughtExceptionHandler(this);// 设置该CrashHandler为程序的默认处理器
    }

    /**
     * 当UncaughtException发生时会转入该重写的方法来处理
     */
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            // 如果自定义的没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(3000);// 如果处理了,让程序继续运行3秒再退出,保证文件保存并上传到服务器
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 退出程序
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        }
    }

    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param ex
     *            异常信息
     * @return true 如果处理了该异常信息;否则返回false.
     */
    public boolean handleException(Throwable ex) {
        if (ex == null)
            return false;
        new Thread() {
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出", 0).show();
                Looper.loop();
            }
        }.start();
        // 收集设备参数信息
        collectDeviceInfo(mContext);
        // 保存日志文件
        saveCrashInfo2File(ex);
        return true;
    }

    /**
     * 收集设备参数信息
     *
     * @param context
     */
    public void collectDeviceInfo(Context context) {
        try {
            PackageManager pm = context.getPackageManager();// 获得包管理器
            PackageInfo pi = pm.getPackageInfo(context.getPackageName(),
                    PackageManager.GET_ACTIVITIES);// 得到该应用的信息,即主Activity
            if (pi != null) {
                String versionName = pi.versionName == null ? "null"
                        : pi.versionName;
                String versionCode = pi.versionCode + "";
                info.put("versionName", versionName);
                info.put("versionCode", versionCode);
            }
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }

        Field[] fields = Build.class.getDeclaredFields();// 反射机制
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                info.put(field.getName(), field.get("").toString());
                Log.d(TAG, field.getName() + ":" + field.get(""));
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private String saveCrashInfo2File(Throwable ex) {
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : info.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\r\n");
        }
        Writer writer = new StringWriter();
        PrintWriter pw = new PrintWriter(writer);
        ex.printStackTrace(pw);
        Throwable cause = ex.getCause();
        // 循环着把所有的异常信息写入writer中
        while (cause != null) {
            cause.printStackTrace(pw);
            cause = cause.getCause();
        }
        pw.close();// 记得关闭
        String result = writer.toString();
        sb.append(result);
        // 保存文件
        long timetamp = System.currentTimeMillis();
        String time = format.format(new Date());
        String fileName = "crash-" + time + "-" + timetamp + ".log";
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            try {
                File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +                           File.separator + "crash");
                Log.i("CrashHandler", dir.toString());
                if (!dir.exists())
                    dir.mkdir();
                FileOutputStream fos = new FileOutputStream(new File(dir,
                        fileName));
                fos.write(sb.toString().getBytes());
                fos.close();
                return fileName;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

重写Application类,初始化全局Context,和上面的异常处理类:

package com.hugedata.share_demo;

import android.app.Application;

/**
 * Created by DELL on 2014/9/9.
 */
public class MyAppilcation extends Application {
    private static MyAppilcation mContext;
    @Override
    public void onCreate () {
        super.onCreate ();
        mContext = this;
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(this);
    }
    public static MyAppilcation getContext(){
        return mContext;
    }
}

在manifest里面将Application参数定义好:

 <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:name=".MyAppilcation"
        android:theme="@style/AppTheme" />

以上,当程序崩溃时,日志将会保存在本地sdcard/crash目录下。
<think>好的,用户问的是“Kafka多久存一次本地文件”,我需要先理解这个问题。Kafka存储文件主要是指将数据写入磁盘,对吧?那用户可能想知道Kafka的数据持久化机制是怎样的,多久会保存一次数据到本地磁盘。 首先,得回顾一下Kafka的存储机制。Kafka的消息是存储在Topic的分区中的,每个分区对应一个日志文件。生产者发送的消息会被追加到日志末尾,然后消费者按顺序读取。那这些日志什么时候被写入磁盘呢?是不是每次写入都立即刷盘,还是有其他策略? 接下来,应该考虑Kafka的持久化策略。Kafka主要通过两个参数控制数据写入磁盘的频率:`log.flush.interval.messages`和`log.flush.interval.ms`。默认情况下,Kafka依赖操作系统的页缓存,并不主动刷盘,而是由操作系统决定何时将数据从页缓存写入磁盘。这种情况下,数据可能不会立即持久化,但性能更好。 如果用户希望更频繁地刷盘,可以调整这两个参数。例如,设置`log.flush.interval.messages=1`会让每个消息都立即刷盘,但这会影响吞吐量。而`log.flush.interval.ms`设置的是时间间隔,比如设置为1000,就是每1秒刷一次盘。不过,默认情况下这些参数可能没有被显式设置,所以Kafka不会主动频繁刷盘,而是依赖OS的后台刷新,通常默认是5秒一次。 另外,生产者的配置也会影响数据持久化的时机。比如,生产者可以设置`acks`参数为all,确保数据被所有副本确认,同时结合`min.insync.replicas`来提高持久性。还有`linger.ms`控制生产者发送消息的等待时间,如果设为0,就是立即发送,而设置更大的值则会等待更多消息批量发送,可能影响刷盘频率。 用户可能关心的是数据的可靠性和性能之间的权衡。如果刷盘太频繁,会影响吞吐量;如果不频繁,可能会有数据丢失的风险。比如,在默认配置下,如果Broker崩溃,最近5秒的数据可能还没刷盘,导致丢失。所以需要根据业务需求来调整参数。 总结一下,Kafka的本地文件存储频率主要受两个因素影响:Broker的刷盘配置和生产者的发送策略。默认情况下依赖操作系统,大约5秒一次,但可以通过参数调整。需要根据业务对数据可靠性和性能的需求来选择合适的配置。</think>### Kafka数据存储到本地文件的频率分析 Kafka的数据存储机制与操作系统的文件系统交互密切相关,其持久化行为由**Broker配置**和**生产者行为**共同决定。以下是关键因素和配置的逐步说明: --- #### 1. **Broker刷盘(Flush)机制** Kafka Broker在接收消息后,会先将数据写入**操作系统的页缓存(Page Cache)**,而不是直接写入物理磁盘。何时将页缓存数据真正写入磁盘(即“刷盘”)由以下参数控制: - **`log.flush.interval.messages`** 指定累计接收多少条消息后触发一次刷盘。 **默认值**:`9223372036854775807`(即几乎不主动刷盘)。 - **`log.flush.interval.ms`** 指定间隔多少毫秒触发一次刷盘。 **默认值**:`null`(即不依赖时间触发刷盘)。 - **操作系统默认行为** 如果未显式配置上述参数,刷盘由操作系统决定,通常通过`fsync()`操作完成。 **Linux默认策略**:后台线程每**5秒**执行一次刷盘。 --- #### 2. **生产者(Producer)的持久化控制** 生产者可以通过配置控制数据何时被Broker视为“已提交”: - **`acks`配置** - `acks=0`:不等待Broker确认(可能丢失数据)。 - `acks=1`:等待Leader副本写入页缓存(默认)。 - `acks=all`:等待所有ISR副本写入页缓存。 - **注意**:即使设置为`acks=all`,仍需结合刷盘配置确保数据持久化。 - **`linger.ms`** 生产者等待多少毫秒聚合消息后批量发送。 **影响**:间接影响Broker的写入频率,但不直接控制刷盘。 --- #### 3. **关键结论:数据持久化的实际频率** | 场景 | 刷盘频率 | 可靠性 | 性能 | |------|----------|--------|------| | **默认配置** | 依赖操作系统(约5秒) | 可能丢失最近5秒数据 | 高吞吐量 | | **强制刷盘** | 通过`log.flush.interval.ms=1000`设置每秒刷盘 | 高可靠性 | 吞吐量下降 | --- #### 4. **配置建议** - **高可靠性场景** 显式配置Broker参数: ```properties log.flush.interval.messages=1 # 每条消息刷盘 log.flush.interval.ms=1000 # 每秒刷盘 ``` 同时设置生产者: ```properties acks=all min.insync.replicas=2 # 至少2个副本确认 ``` - **高吞吐量场景** 保持默认配置,依赖操作系统刷盘。 --- #### 5. **验证方法** - **监控日志分段(Log Segment)** Kafka数据按分段(Segment)存储,新分段创建时会触发刷盘。 检查日志目录: ```bash ls -l /tmp/kafka-logs/<topic>-<partition>/ ``` - **使用工具测试** 通过`kafka-producer-perf-test`压测并观察IO波动。 --- ### 总结 Kafka默认**每5秒**由操作系统刷盘一次,但可通过Broker参数调整频率。实际应用中需权衡**可靠性**与**性能**,选择适合业务的配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值