Todos os produtos
Search
Central de documentação

Simple Log Service:Guia de início rápido do SDK para iOS

Última atualização: Sep 12, 2026

Este guia demonstra como usar rapidamente o Simple Log Service SDK for iOS para coletar dados de log.

Pré-requisitos

Instale o SDK para iOS. Para mais detalhes, consulte Install the iOS SDK.

Início rápido

Inicialize o SDK e chame o método addLog para enviar logs.

Importante
  • O SDK para iOS permite inicializar múltiplas instâncias. Use as instâncias de LogProducerConfig e LogProducerClient em pares.

  • Ao enviar logs para o Simple Log Service, use o par de AccessKey de uma conta Alibaba Cloud ou de um usuário RAM para autenticar as requisições e evitar adulterações. Para evitar riscos de segurança decorrentes da exposição de credenciais, não codifique o par de AccessKey diretamente no aplicativo móvel. Em vez disso, use o service de transferência direta de logs para dispositivos móveis para configure o par de AccessKey. Para mais informações, consulte Build a service to upload logs from mobile devices to Simple Log Service.

@interface ProducerExampleController ()
// We recommend keeping the LogProducerConfig and LogProducerClient instances as global properties.
@property(nonatomic, strong) LogProducerConfig *config;
@property(nonatomic, strong) LogProducerClient *client;
@end

@implementation ProducerExampleController

// The callback is optional. Use it to track the log sending status.
// To configure the AccessKey pair dynamically, set a callback to update it when invoked.
static void _on_log_send_done(const char * config_name, log_producer_result result, size_t log_bytes, size_t compressed_bytes, const char * req_id, const char * message, const unsigned char * raw_buffer, void * userparams) {
    if (result == LOG_PRODUCER_OK) {
        NSString *success = [NSString stringWithFormat:@"send success, config : %s, result : %d, log bytes : %d, compressed bytes : %d, request id : %s", config_name, (result), (int)log_bytes, (int)compressed_bytes, req_id];
        SLSLogV("%@", success);
    } else {
        NSString *fail = [NSString stringWithFormat:@"send fail   , config : %s, result : %d, log bytes : %d, compressed bytes : %d, request id : %s, error message : %s", config_name, (result), (int)log_bytes, (int)compressed_bytes, req_id, message];
        SLSLogV("%@", fail);
    }
}

- (void) initLogProducer {
    // The service endpoint. The value must begin with https:// or http://.
    NSString *endpoint = @"";
    NSString *project = @"";
    NSString *logstore = @"";

    _config = [[LogProducerConfig alloc] initWithEndpoint:endpoint
                                                  project:project
                                                 logstore:logstore
    ];

    // Set a log topic.
    [_config SetTopic:@"example_topic"];
    // Add a global tag, which is attached to all logs.
    [_config AddTag:@"example" value:@"example_tag"];
    // Specifies whether to drop expired logs. 0: Keep the logs and update their timestamps to the current time. 1 (Default): Drop the logs.
    [_config SetDropDelayLog:1];
    // Specifies whether to drop logs that fail authentication. 0 (Default): Keep the logs. 1: Drop the logs.
    [_config SetDropUnauthorizedLog:0];    

    // To monitor the log sending status, pass a callback.
    _client = [[LogProducerClient alloc] initWithLogProducerConfig:_config callback:_on_log_send_done];
}

// Request the AccessKey pair.
- (void) requestAccessKey {
    // We recommend using the direct log transfer service for mobile devices to configure the AccessKey pair.
    // ...

    // After obtaining the AccessKey pair, call this method to update it.
    [self updateAccessKey:accessKeyId accessKeySecret:accessKeySecret securityToken:securityToken];
}

// Update the AccessKey pair.
- (void) updateAccessKey:(NSString *)accessKeyId accessKeySecret:(NSString *)accessKeySecret securityToken:(NSString *)securityToken {
    
    // For an AccessKey pair from Security Token Service (STS) that includes a security token, use this method to update the credentials.
    if (securityToken.length > 0) {
        if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
            [_config ResetSecurityToken:accessKeyId
                        accessKeySecret:accessKeySecret
                          securityToken:securityToken
            ];
        }
    } else {
        // For an AccessKey pair that does not use a security token, use this method.
        if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
            [_config setAccessKeyId: accessKeyId];
            [_config setAccessKeySecret: accessKeySecret];
        }
    }
}
// Send a log.
- (void) addLog {
    Log *log = [Log log];
    // Customize the log fields based on your business requirements.
    [log putContent:@"content_key_1" intValue:123456];
    [log putContent:@"content_key_2" floatValue:23.34f];
    [log putContent:@"content_key_3" value:@"Chinese characters"];
    
    [_client AddLog:log];
}
@end

Uso avançado

Configuração dinâmica de parâmetros

O SDK para iOS permite configure dinamicamente parâmetros como Project, Logstore, endpoint e par de AccessKey. Para obter mais informações sobre como obter um endpoint, consulte Endpoints. Para detalhes sobre a obtenção de um par de AccessKey, consulte AccessKey pair.

  • Configure dinamicamente o endpoint, o Project e o Logstore.

    // You can configure the endpoint, Project, and Logstore parameters independently or together.
    // Update the endpoint.
    [_config setEndpoint:@"your new-endpoint"];
    // Update the Project.
    [_config setProject:@"your new-project"];
    // Update the Logstore.
    [_config setLogstore:@"your new-logstore"];
  • Configure dinamicamente um par de AccessKey.

    Para configure dinamicamente um par de AccessKey, use um callback.

    // If you have initialized the callback when you initialize LogProducerClient, you can ignore the following code.
    static void _on_log_send_done(const char * config_name, log_producer_result result, size_t log_bytes, size_t compressed_bytes, const char * req_id, const char * message, const unsigned char * raw_buffer, void * userparams) {
        if (LOG_PRODUCER_SEND_UNAUTHORIZED == result || LOG_PRODUCER_PARAMETERS_INVALID) {
            [selfClzz requestAccessKey]; // selfClzz is a reference to the current class instance, captured for use in the C-style callback.
        }
    }
    
    // If you want to check the send status of logs, pass a callback as the second parameter.
    _client = [[LogProducerClient alloc] initWithLogProducerConfig:_config callback:_on_log_send_done];
    
    // Request the AccessKey pair.
    - (void) requestAccessKey {
        // We recommend that you first use the service for direct log transfer from mobile devices to configure the AccessKey pair.
        // ...
    
        // After you obtain the AccessKey pair, update the credentials.
        [self updateAccessKey:accessKeyId accessKeySecret:accessKeySecret securityToken:securityToken];
    }
    
    // Update the AccessKey pair.
    - (void) updateAccessKey:(NSString *)accessKeyId accessKeySecret:(NSString *)accessKeySecret securityToken:(NSString *)securityToken {
        
        // If you obtain an AccessKey pair by using STS, the AccessKey pair contains a security token. In this case, you must update the AccessKey pair in the following way.
        if (securityToken.length > 0) {
            if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
                [_config ResetSecurityToken:accessKeyId
                            accessKeySecret:accessKeySecret
                              securityToken:securityToken
                ];
            }
        } else {
            // If you do not obtain an AccessKey pair by using STS, update the AccessKey pair in the following way.
            if (accessKeyId.length > 0 && accessKeySecret.length > 0) {
                [_config setAccessKeyId: accessKeyId];
                [_config setAccessKeySecret: accessKeySecret];
            }
        }
    }
  • Defina dinamicamente a source do log, o tópico do log e a tag.

    Importante

    Não é possível configure a source do log, o tópico do log e as tags para logs individuais. Essas definições são globais e aplicam-se a todos os logs não enviados, incluindo aqueles no buffer de nova tentativa. Essa aplicação global pode causar comportamentos inesperados caso você use esses parâmetros para rastrear categorias específicas de logs. Para rastrear categorias, adicione um campo personalizado a cada log.

    // Set the log topic.
    [_config SetTopic:@"your new-topic"];
    // Set the log source.
    [_config SetSource:@"your new-source"];
    // Set tags. The tags are attached to each log.
    [_config AddTag:@"test" value:@"your new-tag"];

Upload retomável

O SDK para iOS oferece suporte a upload retomável. Quando ativado, esse recurso persiste os logs submetidos pelo método addLog em um arquivo de log binário (binlog) local. O SDK exclui esses dados locais somente após enviar os logs com êxito, garantindo a entrega pelo menos uma vez (at-least-once).

Para ative o upload retomável, adicione o código a seguir durante a inicialização do SDK.

Importante
  • Ao inicializar múltiplas instâncias de LogProducerConfig, forneça um caminho de arquivo exclusivo ao método setPersistentFilePath para cada instância de LogProducerConfig.

  • Se o aplicativo usar múltiplos processos com o upload retomável ativado, inicialize o SDK apenas no processo principal. Se os processos filhos também precisarem coletar dados, garanta que o caminho do arquivo fornecido ao método SetPersistentFilePath seja exclusivo para cada processo. Caso contrário, os dados de log podem ficar desordenados ou ser perdidos.

  • A inicialização repetida do LogProducerConfig a partir de múltiplas threads pode causar problemas.

- (void) initLogProducer {
    // A value of 1 enables resumable upload, and 0 disables it. The default value is 0.
    [_config SetPersistent:1];
    
    // The name of the persistence file. Ensure that the directory for this file exists.
    NSArray  *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *Path = [[paths lastObject] stringByAppendingString:@"/log.dat"];
    [_config SetPersistentFilePath:Path];
    // The number of persistent files to roll over. We recommend a value of 10.
    [_config SetPersistentMaxFileCount:10];
    // The size of each persistent file in bytes, specified as N*1024*1024. We recommend a value for N from 1 to 10.
    [_config SetPersistentMaxFileSize:N*1024*1024];
    // The maximum number of logs to cache locally. Do not exceed 1,048,576. The default is 65,536.
    [_config SetPersistentMaxLogCount:65536];
}

Parâmetros de configuração

A classe LogProducerConfig fornece todos os parâmetros de configuração. A tabela a seguir descreve esses parâmetros.

Parâmetro

Tipo

Descrição

SetTopic

String

Define o valor do campo topic. O valor padrão é uma string vazia.

AddTag

String

Especifica uma tag para um log no formato tag:xxxx. O valor padrão é uma string vazia.

SetSource

String

Define o valor do campo source. O valor padrão é iOS.

SetPacketLogBytes

Int

Define o tamanho máximo de um pacote de logs no buffer. Pacotes que excedem esse limite são enviados imediatamente.

Intervalo de valores: 1 a 5.242.880 bytes. Padrão: 1.048.576 bytes (1 MiB).

SetPacketLogCount

Int

Define o número máximo de logs por pacote. Se esse limite for ultrapassado, o pacote de logs será enviado imediatamente.

Intervalo de valores: 1 a 4.096. Padrão: 1.024.

SetPacketTimeout

Int

Define o tempo máximo que um pacote de logs pode permanecer no buffer. O pacote é enviado assim que esse tempo limite é atingido.

Padrão: 3.000 milissegundos.

SetMaxBufferLimit

Int

Define o tamanho máximo do buffer, em bytes, para uma instância do producer. Chamadas para add_log falham imediatamente se esse limite for excedido.

Padrão: 67.108.864 (64 MiB).

SetPersistent

Int

Ativa ou desativa o recurso de upload retomável.

  • 1: Ativado.

  • 0 (padrão): Desativado.

SetPersistentFilePath

String

Especifica o caminho do arquivo para persistência de dados. O diretório deve existir. Cada instância de LogProducerConfig exige um caminho de arquivo exclusivo.

O valor padrão é uma string vazia.

SetPersistentForceFlush

Int

Define se o flush para o arquivo de persistência deve ser forçado após cada chamada de AddLog.

  • 1: Ativado. Ativar esta opção pode impactar o desempenho. Use com cautela.

  • 0 (padrão): Desativado.

Esta opção é recomendada para cenários de alta confiabilidade.

SetPersistentMaxFileCount

Int

Define o número máximo de arquivos de persistência para rotação. Um valor igual a 0 desativa a rotação e usa um único arquivo. Intervalo recomendado: 1 a 10. Padrão: 0.

SetPersistentMaxFileSize

Int

Especifica o tamanho máximo de cada arquivo de persistência, em bytes. O valor recomendado está entre 1.048.576 bytes (1 MiB) e 10.485.760 bytes (10 MiB).

SetPersistentMaxLogCount

Int

Define a quantidade máxima de logs armazenados localmente para persistência. O valor não deve exceder 1.048.576. Padrão: 65.536.

SetConnectTimeoutSec

Int

Define o tempo limite de conexão de rede, em segundos. O valor padrão é 10.

SetSendTimeoutSec

Int

Define o tempo limite para envio de um pacote de logs, em segundos. O valor padrão é 15.

SetDestroyFlusherWaitSec

Int

Especifica o tempo máximo de espera, em segundos, para destruir a thread de flush. O valor padrão é 1.

SetDestroySenderWaitSec

Int

Define o tempo máximo de espera, em segundos, para destruir o pool de threads de envio. O valor padrão é 1.

SetCompressType

Int

Define o tipo de compressão de dados para uploads.

  • 0: Sem compressão.

  • 1 (padrão): Compressão LZ4.

SetNtpTimeOffset

Int

Define o deslocamento entre a hora do dispositivo e a hora padrão NTP, em segundos. Este valor é calculado como Hora padrão - Hora do dispositivo e serve para corrigir discrepâncias causadas por relógios de cliente não sincronizados. O valor padrão é 0.

SetMaxLogDelayTime

Int

Define a diferença de tempo máxima permitida entre o timestamp de um log e a hora local do dispositivo, em segundos. Se essa diferença for ultrapassada, o log será processado conforme a definição de SetDropDelayLog. O valor padrão é 604.800 (7 24 3600), o que representa 7 dias.

SetDropDelayLog

Int

Define se logs que excedem o limite de SetMaxLogDelayTime devem ser descartados.

  • 0: Não descartar. O timestamp do log é atualizado para a hora atual.

  • 1 (padrão): Descartar o log.

SetDropUnauthorizedLog

Int

Define se logs com falha na autenticação devem ser descartados.

  • 0 (padrão): Não descartar.

  • 1: Descartar o log.

Códigos de erro

Todos os códigos de erro estão definidos em log_producer_result. A tabela abaixo detalha cada código de erro e sua respectiva solução.

Código de erro

Valor

Descrição

Solução

LOG_PRODUCER_OK

0

Sucesso.

N/A.

LOG_PRODUCER_INVALID

1

O SDK foi destruído ou é inválido.

  1. Verifique se o SDK foi inicializado corretamente.

  2. Confira se o método destroy() foi chamado.

LOG_PRODUCER_WRITE_ERROR

2

Falha ao gravar dados. Esse erro pode ocorrer se o tráfego de gravação do Project tiver atingido seu limite.

Aumente o limite de tráfego de gravação para o Project. Para mais informações, consulte Adjust resource quotas.

LOG_PRODUCER_DROP_ERROR

3

O cache de disco ou memória está cheio.

Incremente 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

A cota de tráfego de gravação do Project foi excedida.

Aumente o limite de tráfego de gravação para o Project. Para mais informações, consulte Adjust resource quotas.

LOG_PRODUCER_SEND_UNAUTHORIZED

6

O AccessKey expirou, é inválido ou possui uma política de acesso configurada incorretamente.

Verifique o AccessKey.

O usuário RAM precisa ter permissões para gerencie recursos do Simple Log Service. Para mais informações, consulte Grant permissions to a RAM user.

LOG_PRODUCER_SEND_SERVER_ERROR

7

Ocorreu um erro no lado do servidor.

Entre em contato com o suporte técnico.

LOG_PRODUCER_SEND_DISCARD_ERROR

8

Os dados foram descartados, geralmente devido a uma discrepância de horário entre o dispositivo e o servidor.

O SDK tenta repetir a requisição automaticamente.

LOG_PRODUCER_SEND_TIME_ERROR

9

O relógio do dispositivo não está sincronizado com a hora do servidor.

O SDK corrige esse problema automaticamente.

LOG_PRODUCER_SEND_EXIT_BUFFERED

10

O SDK foi destruído antes que todos os dados em cache pudessem ser enviados.

Para evitar perda de dados, ative o recurso de upload retomável.

LOG_PRODUCER_PARAMETERS_INVALID

11

Parâmetros de inicialização do SDK inválidos.

Verifique parâmetros como AccessKey, Endpoint, Project e Logstore.

LOG_PRODUCER_PERSISTENT_ERROR

99

Falha ao gravar dados em cache no disco.

1. Certifique-se de que o caminho do arquivo de cache esteja correto.

2. Verifique se o arquivo de cache está cheio.

3. Garanta que haja espaço suficiente em disco disponível.

Perguntas frequentes

Por que ocorrem logs duplicados?

O SDK para iOS envia logs de forma assíncrona. Problemas de rede podem causar falhas na transmissão e novas tentativas. O SDK confirma a entrega apenas ao receber um código de status 200. Esse processo pode resultar em logs duplicados. Use consultas SQL para deduplicar os dados durante a análise.

Se você observar uma alta taxa de logs duplicados, verifique possíveis problemas na inicialização do SDK. As causas comuns e suas respectivas soluções incluem:

  • Configuração incorreta do upload retomável

    Para resolver isso, confirme se o caminho do arquivo passado ao método SetPersistentFilePath é globalmente exclusivo.

  • Inicialização repetida do SDK

    • A inicialização repetida do SDK geralmente ocorre devido à falha na aplicação correta do padrão singleton. Inicialize o SDK conforme mostrado no exemplo a seguir.

      // AliyunLogHelper.h
      @interface AliyunLogHelper : NSObject
      + (instancetype)sharedInstance;
      - (void) addLog:(Log *)log;
      @end
      
      // AliyunLogHelper.m
      @interface AliyunLogHelper ()
      @property(nonatomic, strong) LogProducerConfig *config;
      @property(nonatomic, strong) LogProducerClient *client;
      @end
      
      @implementation AliyunLogHelper
      
      + (instancetype)sharedInstance {
          static AliyunLogHelper *sharedInstance = nil;
          static dispatch_once_t onceToken;
          dispatch_once(&onceToken, ^{
              sharedInstance = [[self alloc] init];
          });
          return sharedInstance;
      }
      
      - (instancetype)init {
          self = [super init];
          if (self) {
              [self initLogProducer];
          }
          return self;
      }
      
      - (void) initLogProducer {
          // Replace with your initialization code.
          _config = [[LogProducerConfig alloc] initWithEndpoint:@""
                                                        project:@""
                                                       logstore:@""
                                                    accessKeyID:@""
                                                accessKeySecret:@""
                                                  securityToken:@""
          ];
      
          _client = [[LogProducerClient alloc] initWithLogProducerConfig:_config callback:_on_log_send_done];
      }
      
      - (void) addLog:(Log *)log {
          if (nil == log) {
              return;
          }
      
          [_client AddLog:log];
      }
      
      @end
    • O uso de múltiplos processos também pode causar inicialização repetida. Inicialize o SDK apenas no processo principal. Caso seja necessário inicializar o SDK em diferentes processos, garanta a exclusividade definindo um valor distinto para SetPersistentFilePath em cada processo.

  • Otimização da configuração para ambientes de rede instável

    Se o aplicativo operar em um ambiente de rede instável, otimize a configuração do SDK conforme demonstrado abaixo.

    // Initialize the SDK.
    - (void) initProducer() {
        // Adjust the HTTP connection and send timeouts to help reduce the rate of duplicate logs.
        // You can adjust the timeout values as needed.
        [_config SetConnectTimeoutSec:20];
        [_config SetSendTimeoutSec:20];
      
        // Other initialization parameters.
        // ...
    }

Como lidar com a perda de logs

O envio de logs é um processo assíncrono. Se o aplicativo for encerrado antes da conclusão do envio, pode ocorrer perda de logs. Recomendamos ative o recurso de upload retomável. Para mais informações, consulte Resumable upload.

Como lidar com atrasos no envio de logs

Condições de rede e casos de uso específicos do aplicativo podem atrasar o envio de logs. Se os atrasos ocorrerem apenas em alguns dispositivos, isso geralmente é normal. Caso contrário, solucione o problema usando os códigos de erro na tabela a seguir.

Código de erro

Descrição

LOG_PRODUCER_SEND_NETWORK_ERROR

Verifique se o Endpoint, o Project e o Logstore estão configurados corretamente.

LOG_PRODUCER_SEND_UNAUTHORIZED

Confirme se a chave de acesso é válida, não expirou e se sua política de permissões associada está configurada corretamente.

LOG_PRODUCER_SEND_QUOTA_ERROR

A cota de tráfego de gravação do Project foi excedida. Para mais informações, consulte Adjust resource quotas.

Pré-resolução de DNS e políticas de cache

Sim. O exemplo a seguir mostra como usar o SDK para iOS com o HTTPDNS SDK para implementar pré-resolução de DNS e políticas de cache.

Nota: Se o Endpoint do SLS usar um nome de domínio HTTPS, consulte as Soluções para cenários de HTTPS e SNI.
  1. Implemente uma NSURLProtocol personalizada.

    #import <Foundation/Foundation.h>
    #import "HttpDnsNSURLProtocolImpl.h"
    #import <arpa/inet.h>
    #import <zlib.h>
    #import <objc/runtime.h>
    
    static NSString *const hasBeenInterceptedCustomLabelKey = @"HttpDnsHttpMessagePropertyKey";
    static NSString *const kAnchorAlreadyAdded = @"AnchorAlreadyAdded";
    
    @interface HttpDnsNSURLProtocolImpl () <NSStreamDelegate>
    
    @property (strong, readwrite, nonatomic) NSMutableURLRequest *curRequest;
    @property (strong, readwrite, nonatomic) NSRunLoop *curRunLoop;
    @property (strong, readwrite, nonatomic) NSInputStream *inputStream;
    @property (nonatomic, assign) BOOL responseIsHandle;
    @property (assign, nonatomic) z_stream gzipStream;
    
    @end
    
    @implementation HttpDnsNSURLProtocolImpl
    
    - (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(nullable NSCachedURLResponse *)cachedResponse client:(nullable id <NSURLProtocolClient>)client {
      self = [super initWithRequest:request cachedResponse:cachedResponse client:client];
      if (self) {
        _gzipStream.zalloc = Z_NULL;
        _gzipStream.zfree = Z_NULL;
        if (inflateInit2(&_gzipStream, 16 + MAX_WBITS) != Z_OK) {
          [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"gzip initialize fail" code:-1 userInfo:nil]];
        }
      }
      return self;
    }
    
    /**
     *  Specifies whether to intercept and process the specified request.
     *
     *  @param request The specified request.
     *  @return YES to intercept the request, or NO to pass it through.
     */
    + (BOOL)canInitWithRequest:(NSURLRequest *)request {
      if([[request.URL absoluteString] isEqual:@"about:blank"]) {
        return NO;
      }
    
      // Prevents an infinite loop that can occur if a request is re-intercepted.
      if ([NSURLProtocol propertyForKey:hasBeenInterceptedCustomLabelKey inRequest:request]) {
        return NO;
      }
    
      NSString * url = request.URL.absoluteString;
      NSString * domain = request.URL.host;
    
      // Intercept only HTTPS requests.
      if (![url hasPrefix:@"https"]) {
        return NO;
      }
    
      // You can add more conditions, such as configuring a host array to intercept only the hosts in that array.
    
      // Intercept only requests whose hosts are replaced with IP addresses.
      if (![self isPlainIpAddress:domain]) {
        return NO;
      }
      return YES;
    }
    
    + (BOOL)isPlainIpAddress:(NSString *)hostStr {
      if (!hostStr) {
        return NO;
      }
    
      // Checks whether the address is an IPv4 address.
      const char *utf8 = [hostStr UTF8String];
      int success = 0;
      struct in_addr dst;
      success = inet_pton(AF_INET, utf8, &dst);
      if (success == 1) {
        return YES;
      }
    
      // Checks whether the address is an IPv6 address.
      struct in6_addr dst6;
      success = inet_pton(AF_INET6, utf8, &dst6);
      if (success == 1) {
        return YES;
      }
    
      return NO;
    }
    
    // To redirect the request or add headers, perform the operations in this method.
    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
      return request;
    }
    
    // Starts to load the request.
    - (void)startLoading {
      NSMutableURLRequest *request = [self.request mutableCopy];
      // Marks the request as processed to prevent infinite loops.
      [NSURLProtocol setProperty:@(YES) forKey:hasBeenInterceptedCustomLabelKey inRequest:request];
      self.curRequest = [self createNewRequest:request];
      [self startRequest];
    }
    
    - (NSString *)cookieForURL:(NSURL *)URL {
      NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
      NSMutableArray *cookieList = [NSMutableArray array];
      for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
        if (![self p_checkCookie:cookie URL:URL]) {
          continue;
        }
        [cookieList addObject:cookie];
      }
    
      if (cookieList.count > 0) {
        NSDictionary *cookieDic = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieList];
        if ([cookieDic objectForKey:@"Cookie"]) {
          return cookieDic[@"Cookie"];
        }
      }
      return nil;
    }
    
    - (BOOL)p_checkCookie:(NSHTTPCookie *)cookie URL:(NSURL *)URL {
      if (cookie.domain.length <= 0 || URL.host.length <= 0) {
        return NO;
      }
      if ([URL.host containsString:cookie.domain]) {
        return YES;
      }
      return NO;
    }
    
    - (NSMutableURLRequest *)createNewRequest:(NSURLRequest*)request {
      NSURL* originUrl = request.URL;
      NSString *cookie = [self cookieForURL:originUrl];
    
      NSMutableURLRequest* mutableRequest = [request copy];
      [mutableRequest setValue:cookie forHTTPHeaderField:@"Cookie"];
    
      return [mutableRequest copy];
    }
    
    /**
     * Cancels the request.
     */
    - (void)stopLoading {
      if (_inputStream.streamStatus == NSStreamStatusOpen) {
        [self closeStream:_inputStream];
      }
      [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"stop loading" code:-1 userInfo:nil]];
    }
    
    /**
     * Forwards the request using CFHTTPMessage.
     */
    - (void)startRequest {
      // The header information of the original request.
      NSDictionary *headFields = _curRequest.allHTTPHeaderFields;
      CFStringRef url = (__bridge CFStringRef) [_curRequest.URL absoluteString];
      CFURLRef requestURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL);
    
      // The method of the original request, such as GET or POST.
      CFStringRef requestMethod = (__bridge_retained CFStringRef) _curRequest.HTTPMethod;
    
      // Creates a CFHTTPMessageRef object based on the URL, method, and version of the request.
      CFHTTPMessageRef cfrequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, requestURL, kCFHTTPVersion1_1);
    
      // Adds the data attached to the HTTP POST request.
      CFStringRef requestBody = CFSTR("");
      CFDataRef bodyData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, requestBody, kCFStringEncodingUTF8, 0);
    
      if (_curRequest.HTTPBody) {
        bodyData = (__bridge_retained CFDataRef) _curRequest.HTTPBody;
      }  else if (_curRequest.HTTPBodyStream) {
        NSData *data = [self dataWithInputStream:_curRequest.HTTPBodyStream];
        NSString *strBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"originStrBody: %@", strBody);
    
        CFDataRef body = (__bridge_retained CFDataRef) data;
        CFHTTPMessageSetBody(cfrequest, body);
        CFRelease(body);
      } else {
        CFHTTPMessageSetBody(cfrequest, bodyData);
      }
    
      // Copies the header information of the original request.
      for (NSString* header in headFields) {
        CFStringRef requestHeader = (__bridge CFStringRef) header;
        CFStringRef requestHeaderValue = (__bridge CFStringRef) [headFields valueForKey:header];
        CFHTTPMessageSetHeaderFieldValue(cfrequest, requestHeader, requestHeaderValue);
      }
    
      // Creates an input stream for the CFHTTPMessage object.
      #pragma clang diagnostic push
      #pragma clang diagnostic ignored "-Wdeprecated-declarations"
      CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, cfrequest);
      #pragma clang diagnostic pop
      self.inputStream = (__bridge_transfer NSInputStream *) readStream;
    
      // Sets the Server Name Indication (SNI) host information. This is a key step.
      NSString *host = [_curRequest.allHTTPHeaderFields objectForKey:@"host"];
      if (!host) {
        host = _curRequest.URL.host;
      }
    
      [_inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
      NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys: host, (__bridge id) kCFStreamSSLPeerName, nil];
      [_inputStream setProperty:sslProperties forKey:(__bridge_transfer NSString *) kCFStreamPropertySSLSettings];
      [_inputStream setDelegate:self];
    
      if (!_curRunLoop) {
        // Saves the runloop of the current thread. This is critical for redirected requests.
        self.curRunLoop = [NSRunLoop currentRunLoop];
      }
      // Schedules the stream on the current runloop.
      [_inputStream scheduleInRunLoop:_curRunLoop forMode:NSRunLoopCommonModes];
      [_inputStream open];
    
      CFRelease(cfrequest);
      CFRelease(requestURL);
      cfrequest = NULL;
      CFRelease(bodyData);
      CFRelease(requestBody);
      CFRelease(requestMethod);
    }
    
    - (NSData*)dataWithInputStream:(NSInputStream*)stream {
      NSMutableData *data = [NSMutableData data];
      [stream open];
      NSInteger result;
      uint8_t buffer[1024];
    
      while ((result = [stream read:buffer maxLength:1024]) != 0) {
        if (result > 0) {
          // buffer contains result bytes of data to be handled
          [data appendBytes:buffer length:result];
        } else if (result < 0) {
          // The stream had an error. You can get an NSError object using [iStream streamError]
          data = nil;
          break;
        }
      }
      [stream close];
      return data;
    }
    
    #pragma mark - NSStreamDelegate
    /**
     * Handles stream events.
     */
    - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
      if (eventCode == NSStreamEventHasBytesAvailable) {
        CFReadStreamRef readStream = (__bridge_retained CFReadStreamRef) aStream;
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        CFHTTPMessageRef message = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
        #pragma clang diagnostic pop
        if (CFHTTPMessageIsHeaderComplete(message)) {
          NSInputStream *inputstream = (NSInputStream *) aStream;
          NSNumber *alreadyAdded = objc_getAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded));
          NSDictionary *headDict = (__bridge NSDictionary *) (CFHTTPMessageCopyAllHeaderFields(message));
    
          if (!alreadyAdded || ![alreadyAdded boolValue]) {
            objc_setAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded), [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_COPY);
            // Notifies the client that the response is received. This notification is sent only once.
    
            CFStringRef httpVersion = CFHTTPMessageCopyVersion(message);
            // Obtains the status code from the response header.
            CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(message);
    
            if (!self.responseIsHandle) {
              NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:_curRequest.URL statusCode:statusCode
                                                       HTTPVersion:(__bridge NSString *) httpVersion headerFields:headDict];
              [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
              self.responseIsHandle = YES;
            }
    
            // Verifies the certificate.
            SecTrustRef trust = (__bridge SecTrustRef) [aStream propertyForKey:(__bridge NSString *) kCFStreamPropertySSLPeerTrust];
            SecTrustResultType res = kSecTrustResultInvalid;
            NSMutableArray *policies = [NSMutableArray array];
            NSString *domain = [[_curRequest allHTTPHeaderFields] valueForKey:@"host"];
            if (domain) {
              [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
            } else {
              [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
            }
    
            // Binds the verification policy to the server certificate.
            SecTrustSetPolicies(trust, (__bridge CFArrayRef) policies);
            if (SecTrustEvaluate(trust, &res) != errSecSuccess) {
              [self closeStream:aStream];
              [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"can not evaluate the server trust" code:-1 userInfo:nil]];
              return;
            }
            if (res != kSecTrustResultProceed && res != kSecTrustResultUnspecified) {
              // If the certificate fails verification, close the input stream.
              [self closeStream:aStream];
              [self.client URLProtocol:self didFailWithError:[[NSError alloc] initWithDomain:@"fail to evaluate the server trust" code:-1 userInfo:nil]];
            } else {
              // The certificate is verified.
              if (statusCode >= 300 && statusCode < 400) {
                // Handles the redirection error code.
                [self closeStream:aStream];
                [self handleRedirect:message];
              } else {
                NSError *error = nil;
                NSData *data = [self readDataFromInputStream:inputstream headerDict:headDict stream:aStream error:&error];
                if (error) {
                  [self.client URLProtocol:self didFailWithError:error];
                } else {
                  [self.client URLProtocol:self didLoadData:data];
                }
              }
            }
          } else {
            NSError *error = nil;
            NSData *data = [self readDataFromInputStream:inputstream headerDict:headDict stream:aStream error:&error];
            if (error) {
              [self.client URLProtocol:self didFailWithError:error];
            } else {
              [self.client URLProtocol:self didLoadData:data];
            }
          }
          CFRelease((CFReadStreamRef)inputstream);
          CFRelease(message);
        }
      } else if (eventCode == NSStreamEventErrorOccurred) {
        [self closeStream:aStream];
        inflateEnd(&_gzipStream);
        // Notifies the client that an error occurred.
        [self.client URLProtocol:self didFailWithError:
             [[NSError alloc] initWithDomain:@"NSStreamEventErrorOccurred" code:-1 userInfo:nil]];
      } else if (eventCode == NSStreamEventEndEncountered) {
        CFReadStreamRef readStream = (__bridge_retained CFReadStreamRef) aStream;
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        CFHTTPMessageRef message = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
        #pragma clang diagnostic pop
        if (CFHTTPMessageIsHeaderComplete(message)) {
          NSNumber *alreadyAdded = objc_getAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded));
          NSDictionary *headDict = (__bridge NSDictionary *) (CFHTTPMessageCopyAllHeaderFields(message));
    
          if (!alreadyAdded || ![alreadyAdded boolValue]) {
            objc_setAssociatedObject(aStream, (__bridge const void *)(kAnchorAlreadyAdded), [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_COPY);
            // Notifies the client that the response is received. This notification is sent only once.
    
            if (!self.responseIsHandle) {
              CFStringRef httpVersion = CFHTTPMessageCopyVersion(message);
              // Obtains the status code from the response header.
              CFIndex statusCode = CFHTTPMessageGetResponseStatusCode(message);
              NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:_curRequest.URL statusCode:statusCode
                                                       HTTPVersion:(__bridge NSString *) httpVersion headerFields:headDict];
              [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
              self.responseIsHandle = YES;
            }
          }
        }
    
        [self closeStream:_inputStream];
        inflateEnd(&_gzipStream);
        [self.client URLProtocolDidFinishLoading:self];
      }
    }
    
    - (NSData *)readDataFromInputStream:(NSInputStream *)inputStream headerDict:(NSDictionary *)headDict stream:(NSStream *)aStream error:(NSError **)error {
      // In case the response header is incomplete.
      UInt8 buffer[16 * 1024];
    
      NSInteger length = [inputStream read:buffer maxLength:sizeof(buffer)];
      if (length < 0) {
        *error = [[NSError alloc] initWithDomain:@"inputstream length is invalid"
                      code:-2
                      userInfo:nil];
        [aStream removeFromRunLoop:_curRunLoop forMode:NSRunLoopCommonModes];
        [aStream setDelegate:nil];
        [aStream close];
        return nil;
      }
    
      NSData *data = [[NSData alloc] initWithBytes:buffer length:length];
      if (headDict[@"Content-Encoding"] && [headDict[@"Content-Encoding"] containsString:@"gzip"]) {
        data = [self gzipUncompress:data];
        if (!data) {
          *error = [[NSError alloc] initWithDomain:@"can't read any data"
                          code:-3
                          userInfo:nil];
          return nil;
        }
      }
    
      return data;
    }
    
    - (void)closeStream:(NSStream*)stream {
      [stream removeFromRunLoop:_curRunLoop forMode:NSRunLoopCommonModes];
      [stream setDelegate:nil];
      [stream close];
    }
    
    - (void)handleRedirect:(CFHTTPMessageRef)messageRef {
      // Response header.
      CFDictionaryRef headerFieldsRef = CFHTTPMessageCopyAllHeaderFields(messageRef);
      NSDictionary *headDict = (__bridge_transfer NSDictionary *)headerFieldsRef;
      [self redirect:headDict];
    }
    
    - (void)redirect:(NSDictionary *)headDict {
      // If cookies are required for redirection, handle them.
      NSString *location = headDict[@"Location"];
      if (!location)
        location = headDict[@"location"];
      NSURL *url = [[NSURL alloc] initWithString:location];
      _curRequest.URL = url;
      if ([[_curRequest.HTTPMethod lowercaseString] isEqualToString:@"post"]) {
        // Per RFCs, a redirected POST request should be converted to a GET request.
        _curRequest.HTTPMethod = @"GET";
        _curRequest.HTTPBody = nil;
      }
      [self startRequest];
    }
    
    - (NSData *)gzipUncompress:(NSData *)gzippedData {
      if ([gzippedData length] == 0) {
        return gzippedData;
      }
    
      unsigned full_length = (unsigned) [gzippedData length];
      unsigned half_length = (unsigned) [gzippedData length] / 2;
    
      NSMutableData *decompressed = [NSMutableData dataWithLength:full_length + half_length];
      BOOL done = NO;
      int status;
      _gzipStream.next_in = (Bytef *)[gzippedData bytes];
      _gzipStream.avail_in = (uInt)[gzippedData length];
      _gzipStream.total_out = 0;
    
      while (_gzipStream.avail_in != 0 && !done) {
        if (_gzipStream.total_out >= [decompressed length]) {
          [decompressed increaseLengthBy:half_length];
        }
    
        _gzipStream.next_out = (Bytef *)[decompressed mutableBytes] + _gzipStream.total_out;
        _gzipStream.avail_out = (uInt)([decompressed length] - _gzipStream.total_out);
    
        status = inflate(&_gzipStream, Z_SYNC_FLUSH);
    
        if (status == Z_STREAM_END) {
          done = YES;
        } else if (status == Z_BUF_ERROR) {
          // If Z_BUF_ERROR is caused by an insufficient output buffer, the input buffer is not fully processed and the output buffer is full. In this case, the loop must continue to expand the buffer.
          // The opposite condition indicates that the error is not caused by an insufficient output buffer. In this case, the loop must be terminated, which indicates an error.
          if (_gzipStream.avail_in == 0 || _gzipStream.avail_out != 0) {
            return nil;
          }
        } else if (status != Z_OK) {
                return nil;
            }
        }
    
        [decompressed setLength:_gzipStream.total_out];
        return [NSData dataWithData:decompressed];
    }
    
    @end
    
  2. Implemente um BeforeSend personalizado.

    - (void)setupHttpDNS:(NSString *)accountId {
      // Configure HTTPDNS.
      HttpDnsService *httpdns = [[HttpDnsService alloc] initWithAccountID:accountId];
      [httpdns setHTTPSRequestEnabled:YES];
      [httpdns setPersistentCacheIPEnabled:YES];
      [httpdns setReuseExpiredIPEnabled:YES];
      [httpdns setIPv6Enabled:YES];
    
      NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    
      NSMutableArray *protocolsArray = [NSMutableArray arrayWithArray:configuration.protocolClasses];
      // Set the custom NSURLProtocol defined in the preceding step.
      [protocolsArray insertObject:[HttpDnsNSURLProtocolImpl class] atIndex:0];
      [configuration setProtocolClasses:protocolsArray];
      NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:nil];
    
      [SLSURLSession setURLSession:session];
      [SLSURLSession setBeforeSend:^NSMutableURLRequest * _Nonnull(NSMutableURLRequest * _Nonnull request) {
        NSURL *url = request.URL;
        // You can add a condition here to apply this logic only to specific URLs.
        
        HttpdnsResult *result = [httpdns resolveHostSync:url.host byIpType:HttpdnsQueryIPTypeAuto];
        if (!result) {
          return request;
        }
    
        NSString *ipAddress = nil;
        if (result.hasIpv4Address) {
          ipAddress = result.firstIpv4Address;
        } else if(result.hasIpv6Address) {
          ipAddress = result.firstIpv6Address;
        } else {
          return request;
        }
    
        NSString *requestUrl = url.absoluteString;
        requestUrl = [requestUrl stringByReplacingOccurrencesOfString: url.host withString:ipAddress];
    
        [request setURL:[NSURL URLWithString:requestUrl]];
        [request setValue:url.host forHTTPHeaderField:@"host"];
    
        return request;
      }];
    }
  3. Conclua a inicialização do SLS SDK.

    @implementation AliyunSLS
    
    - (void)initSLS {
      // Set custom HTTPDNS for the SLS SDK.
      // Note:
      // This setting applies to all SLS SDK instances.
      [self setupHttpDNS:accountId];
      [self initProducer];
    }
    
    - (void)initProducer {
        // Initialize LogProducerConfig and LogProducerClient as usual.
        // ...
    }
    
    @end