Use o Simple Log Service (SLS) Android SDK para coletar e enviar logs do seu aplicativo Android.
Pré-requisitos
Início rápido
Inicialize o SDK e chame o método addLog para enviar logs.
O SDK suporta várias instâncias. Cada instância de
LogProducerConfigdeve ter uma instância correspondente deLogProducerClient.O envio de logs para o SLS exige o AccessKey de uma conta Alibaba Cloud ou de um usuário do Resource Access Management (RAM) para autenticação e proteção contra adulteração. Para evitar riscos de segurança ao armazenar um AccessKey no aplicativo móvel, recomendamos usar o serviço de upload direto de logs móveis para configurar o AccessKey. Para obter mais informações, consulte Collect logs - Build a mobile log direct upload service.
// We recommend that you save the LogProducerConfig and LogProducerClient instances globally.
private LogProducerConfig config = null;
private LogProducerClient client = null;
/**
* Initialize the SDK.
*/
private void initProducer() {
try {
// The service endpoint of SLS. The endpoint must start with https:// or http://.
final String endpoint = "your endpoint";
final String project = "your project";
final String logstore = "your logstore";
config = new LogProducerConfig(context, endpoint, project, logstore);
// Set the log topic.
config.setTopic("example_topic");
// Set the tag information. This tag is added to each log.
config.addTag("example", "example_tag");
// Specifies whether to discard expired logs. 0: Do not discard the logs. The log time is changed to the current time. 1: Discard the logs. Default value: 1.
config.setDropDelayLog(0);
// Specifies whether to discard logs that fail authentication. 0: Do not discard the logs. 1: Discard the logs. Default value: 0.
config.setDropUnauthorizedLog(0);
// LogProducerCallback is an optional configuration. If you do not need to follow the success or failure status of log sending, you do not need to register a callback.
// To dynamically configure the AccessKey, set LogProducerCallback and update the AccessKey when the onCall method is invoked.
final LogProducerCallback callback = new LogProducerCallback() {
@Override
public void onCall(int resultCode, String reqId, String errorMessage, int logBytes, int compressedBytes) {
// resultCode: the status code. For more information, see Error codes.
// reqId: the request ID. This parameter is deprecated.
// errorMessage: the failure information.
// logBytes: the original log size in bytes.
// compressedBytes: the compressed log size in bytes.
final LogProducerResult result = LogProducerResult.fromInt(resultCode);
if (LogProducerResult.LOG_PRODUCER_SEND_UNAUTHORIZED == result || LogProducerResult.LOG_PRODUCER_PARAMETERS_INVALID == result) {
// Update the AccessKey or the initialization parameters of the SDK.
}
}
};
// To follow the success or failure status of log sending, you must pass a callback as the second parameter.
client = new LogProducerClient(config, callback);
} catch (LogProducerException e) {
e.printStackTrace();
}
}
// Request the AccessKey information.
private void requestAccessKey() {
// We recommend that you use the mobile log direct upload service to configure the AccessKey information.
// ...
// After you obtain the AccessKey information, update it.
updateAccessKey(accessKeyId, accessKeySecret, securityToken);
}
// Update the AccessKey information.
private void updateAccessKey(String accessKeyId, String accessKeySecret, String securityToken) {
// The AccessKey obtained from Security Token Service (STS) contains a securityToken. You must update the AccessKey in the following way.
if (null != securityToken && !"".equals(securityToken)) {
config.resetSecurityToken(accessKeyId, accessKeySecret, securityToken);
} else {
// If the AccessKey is not obtained from STS, update the AccessKey in the following way.
config.setAccessKeyId(accessKeyId);
config.setAccessKeySecret(accessKeySecret);
}
}
/**
* Report logs.
*/
public void addLog() {
Log log = new Log();
// Adjust the fields to be reported as needed.
log.putContent("content_key_1", 123456);
log.putContent("content_key_2", 23.34f);
log.putContent("content_key_3", "Chinese️");
log.putContent(null, "null");
log.putContent("null", (String) null);
client.addLog(log);
}
Recursos avançados
Configurar parâmetros dinamicamente
O SDK do Android permite configurar dinamicamente parâmetros como Endpoint, ProjectName, Logstore e AccessKey.
-
Configure dinamicamente o Endpoint, ProjectName e logstore.
// Configure parameters such as Endpoint, ProjectName, and logstore together or separately. // Update the endpoint. config.setEndpoint("your new-endpoint"); // Update the project name. config.setProject("your new-project"); // Update the logstore name. config.setLogstore("your new-logstore"); -
Configure o AccessKey dinamicamente.
Ao configurar o AccessKey dinamicamente, recomendamos usá-lo com o LogProducerCallback.
// If you have initialized LogProducerCallback when you initialized LogProducerClient, ignore the following code. final LogProducerCallback callback = new LogProducerCallback() { @Override public void onCall(int resultCode, String reqId, String errorMessage, int logBytes, int compressedBytes) { // resultCode: the status code. For more information, see Error codes. // reqId: the request ID. This parameter is deprecated. // errorMessage: the failure information. // logBytes: the original log size in bytes. // compressedBytes: the compressed log size in bytes. final LogProducerResult result = LogProducerResult.fromInt(resultCode); if (LogProducerResult.LOG_PRODUCER_SEND_UNAUTHORIZED == result || LogProducerResult.LOG_PRODUCER_PARAMETERS_INVALID == result) { // You need to update the AccessKey or the initialization parameters of the SDK. requestAccessKey(); } } }; // To follow the success or failure status of log sending, you must pass a callback as the second parameter. client = new LogProducerClient(config, callback); // Request the AccessKey information. private void requestAccessKey() { // We recommend that you use the mobile log direct upload service to configure the AccessKey information. // ... // After you obtain the AccessKey information, update it. updateAccessKey(accessKeyId, accessKeySecret, securityToken); } // Update the AccessKey information. private void updateAccessKey(String accessKeyId, String accessKeySecret, String securityToken) { // The AccessKey obtained from STS contains a securityToken. You must update the AccessKey in the following way. if (null != securityToken && !"".equals(securityToken)) { config.resetSecurityToken(accessKeyId, accessKeySecret, securityToken); } else { // If the AccessKey is not obtained from STS, update the AccessKey in the following way. config.setAccessKeyId(accessKeyId); config.setAccessKeySecret(accessKeySecret); } } -
Defina dinamicamente source, topic e tag.
ImportanteNão é possível definir source, topic ou tag para um tipo específico de log. Esses parâmetros se aplicam a todos os logs pendentes de envio para o SLS. Se você usar source, topic e tag para rastrear tipos específicos de log, os resultados podem ser imprecisos. Recomendamos adicionar campos durante a geração do log para identificar o tipo.
// Set the log source. config.setSource("your new-source"); // Set the log topic. config.setTopic("your new-topic"); // Set the tag information. This tag is added to each log. config.addTag("test", "your new-tag");
Upload retomável
O SDK do Android oferece suporte a upload retomável. Ao ativar esse recurso, os logs adicionados pelo método addLog são salvos em um arquivo binlog local. Os dados locais são excluídos apenas após o envio bem-sucedido dos logs, garantindo a entrega pelo menos uma vez.
Adicione o código a seguir durante a inicialização do SDK para ativar o upload retomável.
Ao inicializar várias instâncias de LogProducerConfig, o método
setPersistentFilePathde cada instância deLogProducerConfigdeve receber um valor exclusivo.Se o aplicativo usar vários processos com o upload retomável ativado, inicialize o SDK apenas no processo principal. Se processos secundários também precisarem coletar dados, garanta que o caminho de arquivo especificado em
setPersistentFilePathseja único para cada processo. Caso contrário, pode haver perda ou corrupção de dados de log.Evite reinicializar o LogProducerConfig em várias threads ao usar o SDK.
private void initProducer() {
// 1: enables the resumable upload feature. 0: disables the feature. Default value: 0.
config.setPersistent(1);
// The name of the persistence file. Make sure that the folder where the file is located has been created.
final String persistentFilePath = getFilesDir() + File.separator + "log_data";
config.setPersistentFilePath(persistentFilePath);
// The number of scrolling persistence files. We recommend that you set this parameter to 10.
config.setPersistentMaxFileCount(10);
// The size of each persistence file. Unit: bytes. The format is N*1024*1024. We recommend that you set N to a value from 1 to 10.
config.setPersistentMaxFileSize(N*1024*1024);
// The maximum number of logs that can be cached locally. We recommend that you do not set this parameter to a value greater than 1,048,576. Default value: 65,536.
config.setPersistentMaxLogCount(65536);
}
Regras de obfuscação
-keep class com.aliyun.sls.android.producer.** { *; }
-keep interface com.aliyun.sls.android.producer.** { *; }
Permissões do Android
<uses-permission android:name="android.permission.INTERNET" />
Parâmetros de configuração
A classe LogProducerConfig fornece todos os parâmetros de configuração.
|
Parâmetro |
Tipo de dados |
Descrição |
|
setTopic |
String |
Define o valor do campo topic. O valor padrão é uma string vazia. |
|
addTag |
String |
Define uma tag. O formato é |
|
setSource |
String |
Define o valor do campo source. O valor padrão é Android. |
|
setPacketLogBytes |
Int |
Tamanho máximo de cada pacote de logs em cache. Os logs são enviados imediatamente quando esse limite é excedido. Valores válidos: 1 a 5.242.880. Valor padrão: 1024 × 1024. Unidade: bytes. |
|
setPacketLogCount |
Int |
Número máximo de logs em cada pacote de logs em cache. Os logs são enviados imediatamente quando esse limite é excedido. Valores válidos: 1 a 4.096. Valor padrão: 1.024. |
|
setPacketTimeout |
Int |
Tempo limite para logs em cache. Os logs são enviados imediatamente quando esse tempo expira. Valor padrão: 3.000. Unidade: milissegundos. |
|
setMaxBufferLimit |
Int |
Memória máxima que uma única instância do Producer Client pode usar. Quando esse limite é excedido, a interface add_log retorna falha imediatamente. Valor padrão: 64 × 1024 × 1024. |
|
setSendThreadCount |
Int |
Número de threads de envio. Se o upload retomável estiver ativado, esse parâmetro será definido forçadamente como 1. |
|
setPersistent |
Int |
Indica se o recurso de upload retomável deve ser ativado.
|
|
setPersistentFilePath |
String |
Nome do arquivo de persistência. Certifique-se de que a pasta pai exista. Ao configurar várias instâncias de LogProducerConfig, cada nome deve ser exclusivo. O valor padrão é vazio. |
|
setPersistentForceFlush |
Int |
Determina se os logs devem ser gravados em disco sempre que AddLog for chamado.
Ative este recurso em cenários de alta confiabilidade. |
|
setPersistentMaxFileCount |
Int |
Número de arquivos de persistência rotativos. Recomenda-se definir este parâmetro como 10. Valor padrão: 0. |
|
setPersistentMaxFileSize |
Int |
Tamanho de cada arquivo de persistência. Unidade: bytes. O formato é N × 1024 × 1024. Recomenda-se definir N com um valor entre 1 e 10. |
|
setPersistentMaxLogCount |
Int |
Número máximo de logs que podem ser armazenados em cache localmente. Recomenda-se não definir este parâmetro com valor superior a 1.048.576. Valor padrão: 65.536. |
|
setConnectTimeoutSec |
Int |
Tempo limite de conexão de rede. Valor padrão: 10. Unidade: segundos. |
|
setSendTimeoutSec |
Int |
Tempo limite para envio de logs. Valor padrão: 15. Unidade: segundos. |
|
setDestroyFlusherWaitSec |
Int |
Tempo máximo de espera para destruir a thread flusher. Valor padrão: 1. Unidade: segundos. |
|
setDestroySenderWaitSec |
Int |
Tempo máximo de espera para destruir o pool de threads de envio. Valor padrão: 1. Unidade: segundos. |
|
setCompressType |
Int |
Tipo de compressão para upload de dados.
|
|
setNtpTimeOffset |
Int |
Diferença entre a hora do dispositivo e a hora padrão. O valor é |
|
setMaxLogDelayTime |
Int |
Diferença entre a hora do log e a hora local. Se a diferença exceder este valor, o SDK tratará o log conforme a configuração setDropDelayLog. Unidade: segundos. Valor padrão: 7.243.600 (7 dias). |
|
setDropDelayLog |
Int |
Indica se logs expirados que excedem setMaxLogDelayTime devem ser descartados.
|
|
setDropUnauthorizedLog |
Int |
Indica se logs que falham na autenticação devem ser descartados.
|
|
setCallbackFromSenderThread |
Boolean |
Define se o callback deve ser executado a partir da thread de envio.
|
Códigos de erro
Todos os códigos de erro estão definidos na enumeração LogProducerResult.
|
Código de erro |
Valor |
Descrição |
Solução |
|
LOG_PRODUCER_OK |
0 |
Sucesso. |
Não aplicável. |
|
LOG_PRODUCER_INVALID |
1 |
O SDK foi destruído ou é inválido. |
|
|
LOG_PRODUCER_WRITE_ERROR |
2 |
Ocorreu um erro de gravação de dados. O tráfego de gravação do projeto pode ter atingido o limite superior. |
Ajuste o limite superior do tráfego de gravação do projeto. Para obter mais informações, consulte Adjust resource quotas. |
|
LOG_PRODUCER_DROP_ERROR |
3 |
O cache de disco ou memória está cheio e não é possível gravar logs. |
Ajuste os valores dos parâmetros maxBufferLimit, persistentMaxLogCount e persistentMaxFileSize e tente novamente. |
|
LOG_PRODUCER_SEND_NETWORK_ERROR |
4 |
Ocorreu um erro de rede. |
Verifique as configurações de Endpoint, Project e logstore. |
|
LOG_PRODUCER_SEND_QUOTA_ERROR |
5 |
O tráfego de gravação do projeto atingiu o limite superior. |
Ajuste o limite superior do tráfego de gravação do projeto. Para obter mais informações, consulte Adjust resource quotas. |
|
LOG_PRODUCER_SEND_UNAUTHORIZED |
6 |
O AccessKey expirou, é inválido ou a política de acesso do AccessKey está configurada incorretamente. |
Verifique o AccessKey. O usuário RAM deve ter permissões para operar recursos do SLS. Para obter mais informações, consulte Grant permissions to a RAM user. |
|
LOG_PRODUCER_SEND_SERVER_ERROR |
7 |
Ocorreu um erro de serviço. |
Envie um ticket para entrar em contato com o suporte técnico. |
|
LOG_PRODUCER_SEND_DISCARD_ERROR |
8 |
Dados foram descartados. Geralmente isso ocorre porque a hora do dispositivo não está sincronizada com a hora do servidor. |
O SDK reenvia os dados automaticamente. |
|
LOG_PRODUCER_SEND_TIME_ERROR |
9 |
A hora não está sincronizada com a hora do servidor. |
O SDK corrige esse problema automaticamente. |
|
LOG_PRODUCER_SEND_EXIT_BUFFERED |
10 |
No momento da destruição do SDK, ainda havia dados em cache não reportados. |
Recomendamos ativar o recurso de upload retomável para evitar perda de dados. |
|
LOG_PRODUCER_PARAMETERS_INVALID |
11 |
Os parâmetros de inicialização do SDK são inválidos. |
Verifique as configurações de parâmetros como AccessKey, Endpoint, Project e logstore. |
|
LOG_PRODUCER_PERSISTENT_ERROR |
99 |
Falha ao gravar dados em cache no disco. |
1. Verifique se o caminho do arquivo de cache está configurado corretamente. 2. Verifique se o arquivo de cache está cheio. 3. Verifique se há espaço suficiente em disco no sistema. |
Perguntas frequentes
Por que existem logs duplicados?
O SDK do Android envia logs de forma assíncrona. Devido às condições de rede, um log pode falhar no envio e ser reenviado. O SDK considera uma operação de envio bem-sucedida apenas quando recebe um código de status 200, portanto alguns logs podem ser duplicados. Recomendamos usar consultas SQL e instruções de análise para deduplicar logs.
Se a taxa de duplicação de logs for alta, verifique se há problemas na inicialização do SDK. Causas comuns e soluções:
-
Configuração incorreta de upload retomável
Se o upload retomável estiver configurado incorretamente, confirme se o caminho de arquivo passado ao método
setPersistentFilePathé globalmente exclusivo. -
Inicialização repetida do SDK
-
Uma causa comum é a implementação incorreta do padrão singleton para inicialização do SDK. Recomendamos a seguinte abordagem.
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() { // Replace the following code with your initialization code. 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); } } Outra causa é o uso de vários processos. Inicialize o SDK apenas no processo principal. Se você inicializar o SDK em processos diferentes, defina um valor exclusivo para
setPersistentFilePathem cada processo.
-
-
Otimização de configuração para ambientes de rede instável
Se o aplicativo for executado em um ambiente de rede instável, otimize os parâmetros de configuração do SDK conforme mostrado no exemplo a seguir.
// Initialize the SDK. private void initProducer() { // Adjust the HTTP connection and sending timeout periods to reduce the log duplication rate. // Adjust the specific timeout periods as needed. config.setConnectTimeoutSec(20); config.setSendTimeoutSec(20); // Other initialization parameters. // ... }
O que fazer se houver logs ausentes?
O envio de logs é assíncrono. Se o aplicativo for fechado antes que os logs sejam enviados, logs não enviados podem ser perdidos. Recomendamos ativar o recurso de upload retomável. Para obter mais informações, consulte Resumable upload.
O que fazer se houver atraso no reporte de logs?
O SDK envia logs de forma assíncrona. Os logs podem não ser enviados imediatamente devido a condições de rede ou cenários específicos do aplicativo. Atrasos em um pequeno número de dispositivos são normais. Se os atrasos forem generalizados, verifique os seguintes códigos de erro.
|
Código de erro |
Descrição |
|
LOG_PRODUCER_SEND_NETWORK_ERROR |
Verifique se as configurações de Endpoint, Project e logstore estão corretas. |
|
LOG_PRODUCER_SEND_UNAUTHORIZED |
Verifique se o AccessKey expirou ou é inválido, ou se a política de acesso do AccessKey está configurada corretamente. |
|
LOG_PRODUCER_SEND_QUOTA_ERROR |
O tráfego de gravação do projeto atingiu o limite superior. Para obter mais informações, consulte Adjust resource quotas. |
O SDK do Android suporta pré-resolução de DNS e políticas de cache?
Use o SDK do Android, o SDK HTTPDNS e o OkHttp para implementar pré-resolução de DNS e políticas de cache.
-
Implemente uma interface DNS personalizada.
/** * Implement a custom DNS parsing service based on the OkHttp Dns interface for your reference. */ public class OkHttpDns implements Dns { private Context mContext; public OkHttpDns(Context context) { mContext = context; } @Override public List<InetAddress> lookup(String host) throws UnknownHostException { // !!Note!! // Replace accountId with the value that you obtained from the HTTPDNS service. For more information, see the HTTPDNS documentation. HTTPDNSResult httpdnsResult = HttpDns.getService(mContext, accountId) .getHttpDnsResultForHostSync(host, RequestIpType.both); List<InetAddress> inetAddresses = new ArrayList<>(); InetAddress address; try { if (httpdnsResult.getIps() != null) { // Process IPv4 addresses. for (String ipv4 : httpdnsResult.getIps()) { address = InetAddress.getByName(ipv4); inetAddresses.add(address); } } if (httpdnsResult.getIpv6s() != null) { // Process IPv6 addresses. for (String ipv6 : httpdnsResult.getIpv6s()) { address = InetAddress.getByName(ipv6); inetAddresses.add(address); } } } catch (UnknownHostException e) { } if (!inetAddresses.isEmpty()) { return inetAddresses; } return okhttp3.Dns.SYSTEM.lookup(host); } } -
Implemente uma NetworkInterface personalizada.
/** * Implement the NetworkInterface of the SLS SDK based on OkHttp for your reference. */ private static class OkHttpNetworkInterface implements LogProducerHttpTool.NetworkInterface { private OkHttpClient client; public OkHttpNetworkInterface(Context context) { client = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .dns(new OkHttpDns(context)) .build(); } @Override public NetworkResponse send(String urlString, String method, String[] header, byte[] body) { // We recommend that you first call the original implementation of the SDK and then report logs using OkHttp when log reporting fails. LogHttpResponse logHttpResponse = LogProducerHttpTool.android_http_post(urlString, method, header, body); if (logHttpResponse.getStatusCode() != -1) { NetworkResponse response = new NetworkResponse(); response.statusCode = logHttpResponse.getStatusCode(); response.errorMessage = logHttpResponse.getErrorMessage(); return response; } // Report logs using OkHttp. Request.Builder builder = new Request.Builder(); builder.url(urlString); builder.post(RequestBody.create(body)); for (int i = 0; i < header.length; i += 2) { builder.addHeader(header[i], header[i + 1]); } // Regardless of whether the request is successful, you must return a NetworkResponse. Otherwise, the SDK may not run as expected. try (okhttp3.Response response = client.newCall(builder.build()).execute()) { NetworkResponse httpResponse = new NetworkResponse(); httpResponse.statusCode = response.code(); httpResponse.headers = response.headers().toMultimap(); httpResponse.errorMessage = response.message(); return httpResponse; } catch (IOException e) { NetworkResponse httpResponse = new NetworkResponse(); httpResponse.statusCode = -1; httpResponse.headers = null; httpResponse.errorMessage = e.getLocalizedMessage(); return httpResponse; } } } -
Inicialize o SDK do SLS.
public class AliyunSLS { private void initSLS(Context context) { // Set a custom NetworkInterface for the SLS SDK. // !!!Note!!! // This setting takes effect for all SLS SDK instances. LogProducerHttpTool.setNetworkInterface(new OkHttpNetworkInterface(context)); // Initialize the SLS SDK. initProducer(); } private void initProducer() { // Implement the initialization of LogProducerConfig and LogProducerClient here. // ... } }