本文介绍如何快速使用日志服务Android SDK采集日志。
前提条件
已安装Android SDK。具体操作,请参见 安装Android SDK 。
快速使用
您可以按照以下方式初始化SDK,并通过调用
addLog
方法上报日志。
-
SDK支持初始化多个实例,
LogProducerConfig
实例与LogProducerClient
实例需成对使用。 -
上报日志到日志服务时需使用阿里云账号或RAM用户的AccessKey,用于鉴权及防篡改。为避免将AccessKey保存在移动端应用中,造成安全风险,推荐您使用移动端日志直传服务配置AccessKey。具体操作,请参见 采集-搭建移动端日志直传服务 。
// 建议您全局保存LogProducerConfig实例和LogProducerClient实例。
private LogProducerConfig config = null;
private LogProducerClient client = null;
* 初始化SDK
private void initProducer() {
try {
// 日志服务的服务接入点。此处必须是以https://或http://开头。
final String endpoint = "your endpoint";
final String project = "your project";
final String logstore = "your logstore";
config = new LogProducerConfig(context, endpoint, project, logstore);
// 设置日志主题。
config.setTopic("example_topic");
// 设置tag信息,此tag信息将被附加在每条日志上。
config.addTag("example", "example_tag");
// 是否丢弃过期日志。0表示不丢弃,把日志时间修改为当前时间; 1表示丢弃。默认值为1。
config.setDropDelayLog(0);
// 是否丢弃鉴权失败的日志。0表示不丢弃,1表示丢弃。默认值为0。
config.setDropUnauthorizedLog(0);
// LogProducerCallback为可选配置, 如果您不需要关注日志的发送成功或失败状态, 可以不注册callback。
// 如果需要动态化配置AccessKey,建议设置LogProducerCallback,并在onCall方法被调用时更新AccessKey。
final LogProducerCallback callback = new LogProducerCallback() {
@Override
public void onCall(int resultCode, String reqId, String errorMessage, int logBytes, int compressedBytes) {
// resultCode: 状态码, 更多信息,请参见错误码。
// reqId: 请求ID,已废弃。
// errorMessage: 失败信息。
// logBytes: 日志原始字节数。
// compressedBytes: 日志压缩字节数。
final LogProducerResult result = LogProducerResult.fromInt(resultCode);
if (LogProducerResult.LOG_PRODUCER_SEND_UNAUTHORIZED == result || LogProducerResult.LOG_PRODUCER_PARAMETERS_INVALID == result) {
// 更新AccessKey或者SDK的初始化参数。
// 需要关注日志的发送成功或失败状态时, 第二个参数需要传入一个callback。
client = new LogProducerClient(config, callback);
} catch (LogProducerException e) {
e.printStackTrace();
// 请求AccessKey信息。
private void requestAccessKey() {
// 推荐您先使用移动端日志直传服务配置AccessKey信息。
// ...
// 获取AccessKey信息后,完成更新。
updateAccessKey(accessKeyId, accessKeySecret, securityToken);
// 更新AccessKey信息。
private void updateAccessKey(String accessKeyId, String accessKeySecret, String securityToken) {
// 通过STS服务获取的AccessKey包含securityToken,需要使用以下方式更新。
if (null != securityToken && !"".equals(securityToken)) {
config.resetSecurityToken(accessKeyId, accessKeySecret, securityToken);
} else {
// 不是通过STS服务获取的AccessKey,使用以下方式更新。
config.setAccessKeyId(accessKeyId);
config.setAccessKeySecret(accessKeySecret);
* 上报日志
public void addLog() {
Log log = new Log();
// 您可以根据实际业务需要调整需上报的字段。
log.putContent("content_key_1", 123456);
log.putContent("content_key_2", 23.34f);
log.putContent("content_key_3", "中文️");
log.putContent(null, "null");
log.putContent("null", (String) null);
client.addLog(log);
}
高级用法
动态配置参数
Android SDK支持动态化设置Endpoint、ProjectName、Logstore、AccessKey等参数。
-
动态化配置Endpoint、ProjectName、Logstore。
// 支持独立配置或一起配置Endpoint、ProjectName、Logstore等参数。 // 更新Endpoint。 config.setEndpoint("your new-endpoint"); // 更新Project名称。 config.setProject("your new-project"); // 更新Logstore名称。 config.setLogstore("your new-logstore");
-
动态化配置AccessKey。
动态化配置AccessKey时,一般建议与LogProducerCallback结合使用。
// 如果您在初始化LogProducerClient时已经完成了LogProducerCallback的初始化,以下代码可忽略。 final LogProducerCallback callback = new LogProducerCallback() { @Override public void onCall(int resultCode, String reqId, String errorMessage, int logBytes, int compressedBytes) { // resultCode: 状态码。更多信息,请参见错误码。 // reqId: 请求ID, 已废弃。 // errorMessage: 失败信息。 // logBytes: 日志原始字节数。 // compressedBytes: 日志压缩字节数。 final LogProducerResult result = LogProducerResult.fromInt(resultCode); if (LogProducerResult.LOG_PRODUCER_SEND_UNAUTHORIZED == result || LogProducerResult.LOG_PRODUCER_PARAMETERS_INVALID == result) { // 需要更新AccessKey或者SDK的初始化参数。 requestAccessKey(); // 需要关注日志的发送成功或失败状态时, 第二个参数需要传入一个callback。 client = new LogProducerClient(config, callback); // 请求AccessKey信息。 private void requestAccessKey() { // 推荐您先使用移动端日志直传服务配置AccessKey信息。 // ... // 获取AccessKey信息后,完成更新。 updateAccessKey(accessKeyId, accessKeySecret, securityToken); // 更新AccessKey信息。 private void updateAccessKey(String accessKeyId, String accessKeySecret, String securityToken) { // 通过STS服务获取的AccessKey包含securityToken,需要使用以下方式更新。 if (null != securityToken && !"".equals(securityToken)) { config.resetSecurityToken(accessKeyId, accessKeySecret, securityToken); } else { // 不是通过STS服务获取的AccessKey,使用以下方式更新。 config.setAccessKeyId(accessKeyId); config.setAccessKeySecret(accessKeySecret); }
-
动态化配置source、topic、tag。
重要source、topic、tag无法针对某类日志进行设置。一旦设置后,所有未成功发送到日志服务的日志,都可能会更新。如果您需要通过source、topic、tag来跟踪具体类别的日志,可能会导致与您的业务预期不相符合。建议您在生成Log时新增字段来标识对应的类别信息。
// 设置日志来源。 config.setSource("your new-source"); // 设置日志主题。 config.setTopic("your new-topic"); // 设置tag信息,此tag信息将被附加在每条日志上。 config.addTag("test", "your new-tag");
断点续传
Android SDK支持断点续传。开启断点续传后,每次通过
addLog
方法写入成功的日志都会先保存在本地binlog文件中,确认日志发送成功后才会删除本地数据,确保日志上传实现At Least Once。
您可以在SDK初始化时加入以下代码,实现断点续传。
-
初始化多个LogProducerConfig实例时,
LogProducerConfig
类的setPersistentFilePath
方法需要传入不同的值。 -
如果您的App存在多进程且开启了断点续传功能,您应只在主进程初始化SDK。如果子进程也有采集数据的需求,您需要确保
setPersistentFilePath
方法传入的文件路径的唯一性,否则可能会导致日志数据错乱、丢失等问题。 -
使用时应注意多线程导致的LogProducerConfig重复初始化问题。
private void initProducer() {
// 1表示开启断点续传功能,0表示关闭。默认值为0。
config.setPersistent(1);
// 持久化的文件名,需要保证文件所在的文件夹已创建。
final String persistentFilePath = getFilesDir() + File.separator + "log_data";
config.setPersistentFilePath(persistentFilePath);
// 持久化文件滚动个数,建议设置为10。
config.setPersistentMaxFileCount(10);
// 每个持久化文件的大小,单位为Byte,格式为N*1024*1024。建议N的取值范围为1~10。
config.setPersistentMaxFileSize(N*1024*1024);
// 本地最多缓存的日志数量,不建议超过1048576,默认为65536。
config.setPersistentMaxLogCount(65536);
}
混淆规则
-keep class com.aliyun.sls.android.producer.* { *; }
-keep interface com.aliyun.sls.android.producer.* { *; }
Android权限
<uses-permission android:name="android.permission.INTERNET" />
配置参数说明
所有的配置参数由LogProducerConfig类提供,详细说明如下表所示。
参数 |
数据类型 |
说明 |
setTopic |
String |
设置topic字段的值。默认值为空符串。 |
addTag |
String |
设置tag,格式为
|
setSource |
String |
设置source字段的值。默认值为Android。 |
setPacketLogBytes |
Int |
每个缓存的日志包大小上限。超过上限后,日志会被立即发送。 取值范围为1~5242880,默认值为1024 * 1024,单位为字节。 |
setPacketLogCount |
Int |
每个缓存的日志包中包含日志数量的最大值。超过上限后日志会被立即发送。 取值范围为1~4096,默认值为1024。 |
setPacketTimeout |
Int |
被缓存日志的发送超时时间,如果缓存超时,日志会被立即发送。 默认值为3000,单位为毫秒。 |
setMaxBufferLimit |
Int |
单个Producer Client实例可以使用的内存上限,超出缓存时add_log接口会立即返回失败。 默认值为64 * 1024 * 1024。 |
setSendThreadCount |
Int |
发送线程数。开启断点续传后,该值强制为1。 |
setPersistent |
Int |
是否开启断点续传功能。
|
setPersistentFilePath |
String |
持久化的文件名,需保证文件所在的文件夹已创建。配置多个LogProducerConfig实例时,需确保唯一性。 默认值为空。 |
setPersistentForceFlush |
Int |
是否开启每次AddLog强制刷新功能。
在高可靠性场景时建议开启, |
setPersistentMaxFileCount |
Int |
持久化文件滚动个数,建议设置成10。默认值为0。 |
setPersistentMaxFileSize |
Int |
每个持久化文件的大小,单位为Byte,格式为N*1024*1024。建议N的取值范围为1~10。 |
setPersistentMaxLogCount |
Int |
本地最多缓存的日志数量,不建议超过1048576,默认为65536。 |
setConnectTimeoutSec |
Int |
网络连接超时时间。默认值为10,单位为秒。 |
setSendTimeoutSec |
Int |
日志发送超时时间。默认值为15,单位为秒。 |
setDestroyFlusherWaitSec |
Int |
flusher线程销毁最大等待时间。默认值为1,单位为秒。 |
setDestroySenderWaitSec |
Int |
sender线程池销毁最大等待时间。默认值为1,单位为秒。 |
setCompressType |
Int |
数据上传时的压缩类型。
|
setNtpTimeOffset |
Int |
设备时间与标准时间的差值,值为标准时间-设备时间。一般这种差值是由于用户客户端设备时间不同步场景。默认值为0,单位秒。 |
setMaxLogDelayTime |
Int |
日志时间与本机时间的差值。超过该差值后,SDK会根据setDropDelayLog选项进行处理。单位秒,默认值为7243600,即7天。 |
setDropDelayLog |
Int |
是否丢弃超过setMaxLogDelayTime的过期日志。
|
setDropUnauthorizedLog |
Int |
是否丢弃鉴权失败的日志。
|
setCallbackFromSenderThread |
Boolean |
是否从sender线程回调callback。
|
错误码
全部的错误码定义在枚举类LogProducerResult,详细说明如下表所示。
错误码 |
数值 |
说明 |
解决方案 |
LOG_PRODUCER_OK |
0 |
成功。 |
不涉及。 |
LOG_PRODUCER_INVALID |
1 |
SDK已销毁或无效。 |
|
LOG_PRODUCER_WRITE_ERROR |
2 |
数据写入错误,可能原因是Project写入流量已达上限。 |
调整Project写入流量上限。具体操作,请参见 调整资源配额 。 |
LOG_PRODUCER_DROP_ERROR |
3 |
磁盘或内存缓存已满,日志无法写入。 |
调整maxBufferLimit、persistentMaxLogCount、persistentMaxFileSize参数值后重试。 |
LOG_PRODUCER_SEND_NETWORK_ERROR |
4 |
网络错误。 |
检查Endpoint、Project、Logstore的配置情况。 |
LOG_PRODUCER_SEND_QUOTA_ERROR |
5 |
Project写入流量已达上限。 |
调整Project写入流量上限。具体操作,请参见 调整资源配额 。 |
LOG_PRODUCER_SEND_UNAUTHORIZED |
6 |
AccessKey过期、无效或AccessKey权限策略配置不正确。 |
检查AccessKey。 RAM用户需具备操作日志服务资源的权限。具体操作,请参见 为RAM用户授权 。 |
LOG_PRODUCER_SEND_SERVER_ERROR |
7 |
服务错误。 |
提请 工单 联系技术支持。 |
LOG_PRODUCER_SEND_DISCARD_ERROR |
8 |
数据被丢弃,一般是设备时间与服务器时间不同步导致。 |
SDK会自动重新发送。 |
LOG_PRODUCER_SEND_TIME_ERROR |
9 |
与服务器时间不同步。 |
SDK会自动修复该问题。 |
LOG_PRODUCER_SEND_EXIT_BUFFERED |
10 |
SDK销毁时缓存数据还没有上报。 |
建议开启断点续传功能可避免数据丢失。 |
LOG_PRODUCER_PARAMETERS_INVALID |
11 |
SDK初始化参数错误。 |
检查AccessKey、Endpoint、Project、Logstore等参数配置。 |
LOG_PRODUCER_PERSISTENT_ERROR |
99 |
缓存数据写入磁盘失败。 |
1、检查缓存文件路径配置不正确。 2、检查缓存文件是否写满 3、检查系统磁盘空间是否充足。 |
常见问题
为什么会存在重复日志?
Android SDK发送日志的过程是异步的,受网络状态影响,日志可能会发送失败并重新发送。由于SDK只在接口返回200状态码时才认为发送成功,因此日志会存在一定的重复率。建议您通过SQL查询分析语句对日志进行去重。
如果日志重复率较高,您需要先排查下SDK初始化是否存在问题。主要原因和解决方案如下:
-
断点续传配置错误
针对断点续传配置错误,您需要确认
setPersistentFilePath
方法传入的文件路径是否全局唯一。 -
SDK重复初始化
-
造成SDK重复初始化的常见原因是单个实例写法错误,或者没有使用单例模式封装SDK的初始化,建议您参考以下方式完成SDK初始化。
public class AliyunLogHelper { private LogProducerConfig config; private LogProducerClient client; private static class Holder { private static final AliyunLogHelper INSTANCE = new AliyunLogHelper(); public static AliyunLogHelper getInstance() { return Holder.INSTANCE; private AliyunLogHelper() { initProducer(); private void initProducer() { // 以下代码替换为您的初始化代码。 try { config = new LogProducerConfig(); client = new LogProducerClient(config); } catch (LogProducerException e) { e.printStackTrace(); public void addLog(Log log) { if (null == client) { return; client.addLog(log); }
-
另外一个造成SDK重复初始化的原因是多进程。建议您只在主进程初始化SDK,或者在不同进程中初始化SDK时,设置
setPersistentFilePath
为不同的值,确保唯一性。
-
-
弱网环境配置优化
如果您的应用是在弱网环境下使用,建议您参考如下示例优化SDK配置参数。
// 初始化SDK。 private void initProducer() { // 调整HTTP链接和发送超时时间,有利于减少日志重复率。 // 您可以根据实际情况调整具体的超时时间。 config.setConnectTimeoutSec(20); config.setSendTimeoutSec(20); // 其他初始化参数。 // ... }
日志缺失,如何处理?
日志上报的过程是异步的,如果在上报日志前App被关闭,则日志有可能无法被上报,造成日志丢失。建议您开启断点续传功能,具体操作,请参见 断点续传 。
日志上报延时,如何处理?
SDK发送日志的过程是异步的,受网络环境以及应用使用场景的影响,日志可能不会立即上报。如果只有个别设备出现日志上报延时,这种情况是正常的,否则请您根据如下错误码进行排查。
错误码 |
说明 |
LOG_PRODUCER_SEND_NETWORK_ERROR |
检查Endpoint、Project、Logstore的配置是否正确。 |
LOG_PRODUCER_SEND_UNAUTHORIZED |
检查AccessKey是否过期、有效或AccessKey权限策略配置是否正确。 |
LOG_PRODUCER_SEND_QUOTA_ERROR |
调整Project写入流量已达上限。具体操作,请参见 调整资源配额 。 |