
XCGlogger é o módulo de log de depuração original para uso em projetos SWIFT.
O SWIFT não inclui um pré-processador C, para que os desenvolvedores não possam usar as macros de log #define que eles usariam no Objective-C. Isso significa que nossa maneira tradicional de gerar bons logs de depuração não funciona mais. Recorrer a chamadas print simples significa que você perde muitas informações úteis ou exige que você digite muito mais código.
O XCGlogger permite registrar detalhes no console (e opcionalmente um arquivo ou outros destinos personalizados), assim como você teria com NSLog() ou print() , mas com informações adicionais, como data, nome da função, nome do arquivo e número da linha.
Vá disso:
Simple message
Para isso:
2014-06-09 06:44:43.600 [Debug] [AppDelegate.swift:40] application(_:didFinishLaunchingWithOptions:): Simple message
Executar:
git submodule add https://github.com/DaveWoodCom/XCGLogger.git
na sua pasta repositório.
Adicione a seguinte linha ao seu Cartfile .
github "DaveWoodCom/XCGLogger" ~> 7.1.5
Em seguida, execute carthage update --no-use-binaries ou apenas carthage update . Para obter detalhes da instalação e uso do Cartago, visite sua página de projeto.
Os desenvolvedores em execução 5.0 e acima no SWIFT precisarão adicionar $(SRCROOT)/Carthage/Build/iOS/ObjcExceptionBridging.framework para seus arquivos de entrada na fase de criação de estruturas de cópia Cartage.
Adicione algo semelhante às seguintes linhas ao seu Podfile . Pode ser necessário ajustar com base na sua plataforma, versão/ramificação etc.
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '12.0'
use_frameworks!
pod 'XCGLogger', '~> 7.1.5'
A especificação do POD XCGLogger por conta própria incluirá a estrutura principal. Estamos começando a adicionar subespecs para permitir que você inclua componentes opcionais também:
pod 'XCGLogger/UserInfoHelpers', '~> 7.1.5' : inclua algum código experimental para ajudar a lidar com o uso de dicionários do userInfo para marcar mensagens de log.
Em seguida, execute pod install . Para obter detalhes da instalação e uso de Cocoapods, visite seu site oficial.
NOTA: Antes de Cocoapods 1.4.0, não foi possível usar várias vagens com uma mistura de versões SWIFT. Pode ser necessário garantir que cada pod esteja configurado para a versão SWIFT correta (verifique os alvos no projeto POD do seu espaço de trabalho). Se você ajustar manualmente a versão SWIFT para um projeto, ele redefinirá na próxima vez que você executar pod install . Você pode adicionar um gancho post_install no seu poderoso para automatizar a definição das versões SWIFT corretas. Isso é amplamente não testado, e não tenho certeza se é uma boa solução, mas parece funcionar:
post_install do |installer|
installer.pods_project.targets.each do |target|
if ['SomeTarget-iOS', 'SomeTarget-watchOS'].include? "#{target}"
print "Setting #{target}'s SWIFT_VERSION to 4.2n"
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '4.2'
end
else
print "Setting #{target}'s SWIFT_VERSION to Undefined (Xcode will automatically resolve)n"
target.build_configurations.each do |config|
config.build_settings.delete('SWIFT_VERSION')
end
end
end
print "Setting the default SWIFT_VERSION to 3.2n"
installer.pods_project.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '3.2'
end
end
Você pode ajustar isso para atender às suas necessidades, é claro.
Adicione a seguinte entrada às dependências do seu pacote:
.Package(url: "https://github.com/DaveWoodCom/XCGLogger.git", majorVersion: 7)
Usar:
Este método de início rápido pretende apenas colocá -lo em funcionamento com o logger. No entanto, você deve usar o uso avançado abaixo para tirar o máximo proveito desta biblioteca.
Adicione o projeto XCGlogger como um subprojeto ao seu projeto e adicione a biblioteca apropriada como uma dependência do (s) seu (s) alvo (s). Na guia General do seu destino, adicione XCGLogger.framework e ObjcExceptionBridging.framework à seção Embedded Binaries .
Então, em cada arquivo de origem:
import XCGLoggerNo seu AppDelegate (ou outro arquivo global), declare uma constante global na instância padrão do XCGlogger.
let log = XCGLogger . defaultNo
application ( _ application : UIApplication , didFinishLaunchingWithOptions launchOptions : [ UIApplicationLaunchOptionsKey : Any ] ? = nil ) // iOS, tvOSou
applicationDidFinishLaunching ( _ notification : Notification ) // macOSfunção, configure as opções necessárias:
log . setup ( level : . debug , showThreadName : true , showLevel : true , showFileNames : true , showLineNumbers : true , writeToFile : " path/to/file " , fileLevel : . debug ) O valor do writeToFile: pode ser uma String ou URL . Se o arquivo já existir, ele será liberado antes de usá -lo. Omita o parâmetro ou defina -o como nil para fazer login apenas no console. Opcionalmente, você pode definir um nível de log diferente para a saída de arquivo usando o parâmetro fileLevel: Defina -o para nil ou omitir para usar o mesmo nível de log do console.
Então, sempre que você quiser registrar algo, use um dos métodos de conveniência:
log . verbose ( " A verbose message, usually useful when working on a specific problem " )
log . debug ( " A debug message " )
log . info ( " An info message, probably useful to power users looking in console.app " )
log . notice ( " A notice message " )
log . warning ( " A warning message, may indicate a possible error " )
log . error ( " An error occurred, but it's recoverable, just info about what happened " )
log . severe ( " A severe error occurred, we are likely about to crash now " )
log . alert ( " An alert error occurred, a log destination could be made to email someone " )
log . emergency ( " An emergency error occurred, a log destination could be made to text someone " ) Os diferentes métodos definem o nível de log da mensagem. O XCGlogger apenas imprimirá mensagens com um nível de log que é maior ou igual à sua configuração atual de nível de log. Portanto, um madeireiro com um nível de .error produzirá apenas mensagens de log com um nível de .error , .severe , .alert ou .emergency .
O XCGlogger pretende ser simples de usar e colocá -lo em funcionamento rapidamente com apenas 2 linhas de código acima. Mas permite um controle e flexibilidade muito maiores.
Um madeireiro pode ser configurado para fornecer mensagens de log a uma variedade de destinos. Usando a configuração básica acima, o Logger produzirá mensagens de log para o console de depuração Xcode padrão e, opcionalmente, um arquivo se um caminho for fornecido. É bem provável que você queira enviar logs para lugares mais interessantes, como o Apple System Console, um banco de dados, servidor de terceiros ou outro aplicativo como o NSLogger. Isso é realizado adicionando o destino ao logger.
Aqui está um exemplo de configuração do madeireiro para saída para o log do sistema Apple e também um arquivo.
// Create a logger object with no destinations
let log = XCGLogger ( identifier : " advancedLogger " , includeDefaultDestinations : false )
// Create a destination for the system console log (via NSLog)
let systemDestination = AppleSystemLogDestination ( identifier : " advancedLogger.systemDestination " )
// Optionally set some configuration options
systemDestination . outputLevel = . debug
systemDestination . showLogIdentifier = false
systemDestination . showFunctionName = true
systemDestination . showThreadName = true
systemDestination . showLevel = true
systemDestination . showFileName = true
systemDestination . showLineNumber = true
systemDestination . showDate = true
// Add the destination to the logger
log . add ( destination : systemDestination )
// Create a file log destination
let fileDestination = FileDestination ( writeToFile : " /path/to/file " , identifier : " advancedLogger.fileDestination " )
// Optionally set some configuration options
fileDestination . outputLevel = . debug
fileDestination . showLogIdentifier = false
fileDestination . showFunctionName = true
fileDestination . showThreadName = true
fileDestination . showLevel = true
fileDestination . showFileName = true
fileDestination . showLineNumber = true
fileDestination . showDate = true
// Process this destination in the background
fileDestination . logQueue = XCGLogger . logQueue
// Add the destination to the logger
log . add ( destination : fileDestination )
// Add basic app info, version info etc, to the start of the logs
log . logAppDetails ( )Você pode configurar cada destino de log com diferentes opções, dependendo de suas necessidades.
Outro padrão de uso comum é ter vários madeireiros, talvez um para problemas de interface do usuário, um para redes e outro para problemas de dados.
Cada destino de log pode ter seu próprio nível de log. Como conveniência, você pode definir o nível de log no próprio objeto de log e ele passará esse nível para cada destino. Em seguida, defina os destinos que precisam ser diferentes.
NOTA : Um objeto de destino só pode ser adicionado a um objeto de logger, adicioná -lo a um segundo o removerá do primeiro.
Como alternativa, você pode usar um fechamento para inicializar sua variável global, para que toda a inicialização seja feita em um só lugar
let log : XCGLogger = {
let log = XCGLogger ( identifier : " advancedLogger " , includeDefaultDestinations : false )
// Customize as needed
return log
} ( ) Nota : isso cria o objeto de log preguiçosamente, o que significa que não é criado até que seja realmente necessário. Isso atrasa a saída inicial dos detalhes da informação do aplicativo. Por esse motivo, recomendo forçar o objeto de log a ser criado no lançamento do aplicativo adicionando a linha let _ = log na parte superior do seu método didFinishLaunching , se você ainda não registrar algo no lançamento do aplicativo.
Você pode registrar strings:
log . debug ( " Hi there! " )Ou praticamente tudo o que você quiser:
log . debug ( true )
log . debug ( CGPoint ( x : 1.1 , y : 2.2 ) )
log . debug ( MyEnum . Option )
log . debug ( ( 4 , 2 ) )
log . debug ( [ " Device " : " iPhone " , " Version " : 7 ] ) Novo no XCGlogger 4, agora você pode criar filtros para se inscrever no seu logger (ou a destinos específicos). Crie e configure seus filtros (exemplos abaixo) e adicione -os aos objetos Logger ou Destination, definindo a propriedade Opcional filters em uma matriz que contém os filtros. Os filtros são aplicados na ordem em que existem na matriz. Durante o processamento, cada filtro é perguntado se a mensagem de log deve ser excluída do log. Se algum filtro excluir a mensagem de log, ele será excluído. Os filtros não têm como reverter a exclusão de outro filtro.
Se a propriedade filters de um destino estiver nil , a propriedade filters do log será usada. Para ter um destino, registrar tudo, enquanto tive todos os outros destinos filtrando algo, adicione os filtros ao objeto de log e defina a propriedade filters de um destino para uma matriz vazia [] .
NOTA : Ao contrário dos destinos, você pode adicionar o mesmo objeto de filtro a vários loggers e/ou vários destinos.
Para excluir todas as mensagens de log de um arquivo específico, crie um filtro de exclusão como assim:
log . filters = [ FileNameFilter ( excludeFrom : [ " AppDelegate.swift " ] , excludePathWhenMatching : true ) ] excludeFrom: pega uma Array<String> ou Set<String> para que você possa especificar vários arquivos ao mesmo tempo.
excludePathWhenMatching: os padrões para true para que você possa omiti -lo, a menos que deseje corresponder ao Path's também.
Para incluir mensagens de log apenas para um conjunto específico para arquivos, crie o filtro usando o includeFrom: Initializer. Também é possível apenas alternar a propriedade inverse para girar o filtro de exclusão para um filtro de inclusão.
Para filtrar as mensagens de log por tag, é claro que você deve definir uma tag nas mensagens de log. Cada mensagem de log agora pode ter dados adicionais definidos pelo usuário anexados a eles, a serem usados por filtros (e/ou formatados etc.). Isso é tratado com um userInfo: Dictionary<String, Any> objeto. A chave do dicionário deve ser uma string nomes para evitar colisões com adições futuras. As chaves oficiais começarão com com.cerebralgardens.xcglogger . A chave de tag pode ser acessada por XCGLogger.Constants.userInfoKeyTags . Você definitivamente não quer digitar isso, então sinta -se à vontade para criar um atalho global: let tags = XCGLogger.Constants.userInfoKeyTags . Agora você pode marcar facilmente seus logs:
let sensitiveTag = " Sensitive "
log . debug ( " A tagged log message " , userInfo : [ tags : sensitiveTag ] ) O valor para tags pode ser uma Array<String> , Set<String> ou apenas uma String , dependendo das suas necessidades. Todos eles funcionarão da mesma maneira quando filtrados.
Dependendo do seu fluxo de trabalho e uso, você provavelmente criará métodos mais rápidos para configurar o dicionário userInfo . Veja abaixo para outros atalhos possíveis.
Agora que você tem seus logs marcados, você pode filtrar facilmente:
log . filters = [ TagFilter ( excludeFrom : [ sensitiveTag ] ) ] Assim como o FileNameFilter , você pode usar includeFrom: ou alternar inverse para incluir apenas mensagens de log que possuem as tags especificadas.
A filtragem pelo desenvolvedor é exatamente como filtragem por tag, apenas usando a tecla userInfo de XCGLogger.Constants.userInfoKeyDevs . De fato, ambos os filtros são subclasses da classe UserInfoFilter que você pode usar para criar filtros adicionais. Consulte estendendo o XCGLogger abaixo.
Em grandes projetos com vários desenvolvedores, você provavelmente deseja começar a marcar mensagens de log e indicar o desenvolvedor que adicionou a mensagem.
Embora extremamente flexível, o dicionário userInfo pode ser um pouco pesado para usar. Existem alguns métodos possíveis que você pode usar para simplesmente coisas. Ainda estou testando isso sozinho, para que eles ainda não sejam oficialmente parte da biblioteca (eu adoraria feedback ou outras sugestões).
Criei algum código experimental para ajudar a criar os dicionários do usuário. (Inclua o subspec opcional UserInfoHelpers se estiver usando cocoapods). Verifique o aplicativo de demonstração do iOS para vê -lo em uso.
Existem duas estruturas que estão em conformidade com o protocolo UserInfoTaggingProtocol . Tag e Dev .
Você pode criar uma extensão em cada um deles que se adapte ao seu projeto. Por exemplo:
extension Tag {
static let sensitive = Tag ( " sensitive " )
static let ui = Tag ( " ui " )
static let data = Tag ( " data " )
}
extension Dev {
static let dave = Dev ( " dave " )
static let sabby = Dev ( " sabby " )
} Junto com esses tipos, há um operador sobrecarregado | Isso pode ser usado para mesclá -los em um dicionário compatível com o UserInfo: Parâmetro das chamadas de registro.
Então você pode registrar mensagens como esta:
log . debug ( " A tagged log message " , userInfo : Dev . dave | Tag . sensitive ) Existem alguns problemas atuais que vejo com esses UserInfoHelpers , e é por isso que o tornei opcional/experimental por enquanto. Eu adoraria ouvir comentários/sugestões para melhorias.
| mescla dicionários, desde que não haja Set . Se um dos dicionários contiver um Set , ele usará um deles, sem mesclá -los. Preferir o lado esquerdo se os dois lados tiverem um conjunto para a mesma chave.userInfo: Parâmetro precisa de um dicionário, você não pode passar em um único dev ou objeto de tag. Você precisa usar pelo menos dois com o | Operador para converter automaticamente em um dicionário compatível. Se você deseja apenas uma tag, por exemplo, deve acessar o parâmetro .dictionary manualmente: userInfo: Tag("Blah").dictionary . Todos os métodos de log operam nos fechamentos. Usando o mesmo açúcar sintático da função assert() de Swift, essa abordagem garante que não desperdiçamos recursos construindo mensagens de log que não serão produzidas de qualquer maneira, enquanto, ao mesmo tempo, preservam um site de chamada limpa.
Por exemplo, a seguinte declaração de log não desperdiçará recursos se o nível de log de depuração for suprimido:
log . debug ( " The description of ( thisObject ) is really expensive to create " ) Da mesma forma, digamos que você precise iterar através de um loop para fazer algum cálculo antes de registrar o resultado. No Objective-C, você pode colocar esse bloco de código entre #if #endif e impedir que o código seja executado. Mas em Swift, anteriormente você ainda precisaria processar esse loop, desperdiçando recursos. Com XCGLogger é tão simples quanto:
log . debug {
var total = 0.0
for receipt in receipts {
total += receipt . total
}
return " Total of all receipts: ( total ) "
} Nos casos em que você deseja executar seletivamente o código sem gerar uma linha de log, retornar nil ou usar um dos métodos: verboseExec , debugExec , infoExec , warningExec , errorExec e severeExec .
Você pode criar seu próprio objeto DateFormatter e atribuí -lo ao logger.
let dateFormatter = DateFormatter ( )
dateFormatter . dateFormat = " MM/dd/yyyy hh:mma "
dateFormatter . locale = Locale . current
log . dateFormatter = dateFormatterO XCGlogger suporta adicionar códigos de formatação às suas mensagens de log para ativar a cor em vários lugares. A opção original era usar o plug-in xCodecolors. No entanto, o Xcode (na versão 8) não suporta mais oficialmente os plug-ins. Você ainda pode visualizar seus logs em cores, mas não no Xcode no momento. Você pode usar o suporte de cor ANSI para adicionar cores aos seus objetos de arquivamento e visualizar seus logs por meio de uma janela de terminal. Isso oferece algumas opções extras, como adicionar ousado, itálico ou (por favor, não) piscando!
Uma vez ativado, cada nível de log pode ter sua própria cor. Essas cores podem ser personalizadas conforme desejado. Se estiver usando vários loggers, você pode definir alternativamente cada logger para sua própria cor.
Um exemplo de configuração do formatador ANSI:
if let fileDestination : FileDestination = log . destination ( withIdentifier : XCGLogger . Constants . fileDestinationIdentifier ) as? FileDestination {
let ansiColorLogFormatter : ANSIColorLogFormatter = ANSIColorLogFormatter ( )
ansiColorLogFormatter . colorize ( level : . verbose , with : . colorIndex ( number : 244 ) , options : [ . faint ] )
ansiColorLogFormatter . colorize ( level : . debug , with : . black )
ansiColorLogFormatter . colorize ( level : . info , with : . blue , options : [ . underline ] )
ansiColorLogFormatter . colorize ( level : . notice , with : . green , options : [ . italic ] )
ansiColorLogFormatter . colorize ( level : . warning , with : . red , options : [ . faint ] )
ansiColorLogFormatter . colorize ( level : . error , with : . red , options : [ . bold ] )
ansiColorLogFormatter . colorize ( level : . severe , with : . white , on : . red )
ansiColorLogFormatter . colorize ( level : . alert , with : . white , on : . red , options : [ . bold ] )
ansiColorLogFormatter . colorize ( level : . emergency , with : . white , on : . red , options : [ . bold , . blink ] )
fileDestination . formatters = [ ansiColorLogFormatter ]
} Como nos filtros, você pode usar os mesmos objetos formatados para vários madeireiros e/ou vários destinos. Se a propriedade formatters de um destino for nil , a propriedade formatters do Logger será usada.
Consulte a extensão do XCGLogger abaixo para obter informações sobre como criar seus próprios formatados personalizados.
Ao usar sinalizadores SWIFT Build, diferentes níveis de log podem ser usados na depuração versus estadiamento/produção. Vá para as configurações de construção -> Swift Compiler -sinalizadores personalizados -> Outros sinalizadores Swift e adicione -DDEBUG à entrada de depuração.
#if DEBUG
log . setup ( level : . debug , showThreadName : true , showLevel : true , showFileNames : true , showLineNumbers : true )
#else
log . setup ( level : . severe , showThreadName : true , showLevel : true , showFileNames : true , showLineNumbers : true )
#endif Você pode definir qualquer número de opções de maneira semelhante. Consulte o aplicativo iOSDEMO atualizado para obter um exemplo de uso de destinos de log diferentes com base nas opções, pesquise USE_NSLOG .
Por padrão, os destinos de log fornecidos processarão os logs no thread que eles são chamados. Isso é para garantir que a mensagem de log seja exibida imediatamente ao depurar um aplicativo. Você pode adicionar um ponto de interrupção imediatamente após uma chamada de log e ver os resultados quando o ponto de interrupção chegar.
No entanto, se você não estiver deputando ativamente o aplicativo, o processamento dos logs no thread atual pode introduzir um acerto de desempenho. Agora você pode especificar um processo de destino seus logs em uma fila de despacho de sua escolha (ou até mesmo usar um padrão fornecido).
fileDestination . logQueue = XCGLogger . logQueueou até
fileDestination . logQueue = DispatchQueue . global ( qos : . background )Isso funciona extremamente bem quando combinado com o método de configurações alternativas acima.
#if DEBUG
log . setup ( level : . debug , showThreadName : true , showLevel : true , showFileNames : true , showLineNumbers : true )
#else
log . setup ( level : . severe , showThreadName : true , showLevel : true , showFileNames : true , showLineNumbers : true )
if let consoleLog = log . logDestination ( XCGLogger . Constants . baseConsoleDestinationIdentifier ) as? ConsoleDestination {
consoleLog . logQueue = XCGLogger . logQueue
}
#endifAo usar a configuração avançada do Logger (consulte o uso avançado acima), agora você pode especificar que o logger anexa a um arquivo de log existente, em vez de substituí -lo automaticamente.
Adicione o parâmetro Opcional shouldAppend: ao inicializar o objeto FileDestination . Você também pode adicionar o appendMarker: Parâmetro para adicionar um marcador ao arquivo de log indicando onde uma nova instância do seu aplicativo começou a anexar. Por padrão, adicionaremos -- ** ** ** -- se o parâmetro for omitido. Defina -o como nil para pular anexando o marcador.
let fileDestination = FileDestination(writeToFile: "/path/to/file", identifier: "advancedLogger.fileDestination", shouldAppend: true, appendMarker: "-- Relauched App --")
Ao fazer login em um arquivo, você tem a opção de girar automaticamente o arquivo de log para um destino arquivado e fazer com que o madeireiro crie automaticamente um novo arquivo de log no lugar do antigo.
Crie um destino usando a classe AutoRotatingFileDestination e defina as seguintes propriedades:
targetMaxFileSize : Gire automático quando o arquivo é maior que este
targetMaxTimeInterval : girar automaticamente após isso muitos segundos
targetMaxLogFiles : Número de arquivos de log arquivados para manter, os mais antigos são excluídos automaticamente
Essas são todas as diretrizes para o madeireiro, não limites rígidos.
Você pode criar destinos de log alternativos (além dos incorporados). O seu destino de log personalizado deve implementar o protocolo DestinationProtocol . Instancie seu objeto, configure -o e adicione -o ao objeto XCGLogger com add(destination:) . Existem duas classes de destino básico ( BaseDestination e BaseQueuedDestination ) de que você pode herdar para lidar com a maior parte do processo para você, exigindo que você implemente apenas um método adicional em sua classe personalizada. Dê uma olhada na ConsoleDestination e FileDestination para exemplos.
Você também pode criar filtros ou formatados personalizados. Dê uma olhada nas versões fornecidas como ponto de partida. Observe que filtros e formatados têm a capacidade de alterar as mensagens de log à medida que são processadas. Isso significa que você pode criar um filtro que retire as senhas, destaca palavras específicas, criptografa mensagens, etc.
O XCGlogger é o melhor madeireiro disponível para Swift por causa das contribuições da comunidade como você. Existem muitas maneiras de ajudar a continuar a torná -lo ótimo.
NOTA : Ao enviar uma solicitação de tração, use muitas pequenas confirmações versículos um enorme compromisso. É muito mais fácil se fundir quando há várias solicitações de tração que precisam ser combinadas para uma nova versão.
Se você achar útil esta biblioteca, definitivamente encontrará essa outra ferramenta útil:
Watchdog: https://watchdogforxcode.com/
Além disso, confira alguns dos meus outros projetos:
O log de alterações está agora em seu próprio arquivo: Changelog.md