Existe uma alternativa Swift para NSLog (@ “% s”, __PRETTY_FUNCTION__)

87

Em Objective C, você pode registrar o método que está sendo chamado usando:

NSLog(@"%s", __PRETTY_FUNCTION__)

Normalmente, isso é usado a partir de uma macro de registro.

Embora o Swift não suporte macro (eu acho), ainda gostaria de usar uma instrução de log genérica que inclua o nome da função que foi chamada. Isso é possível em Swift?

Atualização: agora uso esta função global para registro que pode ser encontrada aqui: https://github.com/evermeer/Stuff#print E que você pode instalar usando:

pod 'Stuff/Print'

Aqui está o código:

public class Stuff {

    public enum logLevel: Int {
        case info = 1
        case debug = 2
        case warn = 3
        case error = 4
        case fatal = 5
        case none = 6

        public func description() -> String {
            switch self {
            case .info:
                return "❓"
            case .debug:
                return "✳️"
            case .warn:
                return "⚠️"
            case .error:
                return "🚫"
            case .fatal:
                return "🆘"
            case .none:
                return ""
            }
        }
    }

    public static var minimumLogLevel: logLevel = .info

    public static func print<T>(_ object: T, _ level: logLevel = .debug, filename: String = #file, line: Int = #line, funcname: String = #function) {
        if level.rawValue >= Stuff.minimumLogLevel.rawValue {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "MM/dd/yyyy HH:mm:ss:SSS"
            let process = ProcessInfo.processInfo
            let threadId = "?"
            let file = URL(string: filename)?.lastPathComponent ?? ""
            Swift.print("\n\(level.description()) .\(level) ⏱ \(dateFormatter.string(from: Foundation.Date())) 📱 \(process.processName) [\(process.processIdentifier):\(threadId)] 📂 \(file)(\(line)) ⚙️ \(funcname) ➡️\r\t\(object)")
        }
    }
}

Que você pode usar assim:

Stuff.print("Just as the standard print but now with detailed information")
Stuff.print("Now it's a warning", .warn)
Stuff.print("Or even an error", .error)

Stuff.minimumLogLevel = .error
Stuff.print("Now you won't see normal log output")
Stuff.print("Only errors are shown", .error)

Stuff.minimumLogLevel = .none
Stuff.print("Or if it's disabled you won't see any log", .error)    

O que resultará em:

✳️ .debug ⏱ 02/13/2017 09:52:51:852 📱 xctest [18960:?] 📂 PrintStuffTests.swift(15) ⚙️ testExample() ➡️
    Just as the standard print but now with detailed information

⚠️ .warn ⏱ 02/13/2017 09:52:51:855 📱 xctest [18960:?] 📂 PrintStuffTests.swift(16) ⚙️ testExample() ➡️
    Now it's a warning

🚫 .error ⏱ 02/13/2017 09:52:51:855 📱 xctest [18960:?] 📂 PrintStuffTests.swift(17) ⚙️ testExample() ➡️
    Or even an error

🚫 .error ⏱ 02/13/2017 09:52:51:855 📱 xctest [18960:?] 📂 PrintStuffTests.swift(21) ⚙️ testExample() ➡️
    Only errors are shown
Edwin Vermeer
fonte
1
Eu usoNSLog("Running %@ : %@",NSStringFromClass(self.dynamicType),__FUNCTION__)
Magster
Eu uso github.com/goktugyil/QorumLogs
Thellimist
1
Eu acho que seu estilo de registro deve ser a definição de "função bonita". Obrigado por compartilhar.
HuaTham

Respostas:

101

Swift tem #file, #function, #line e #column. Da linguagem de programação Swift :

#file - String - O nome do arquivo em que aparece.

#line - Int - O número da linha em que aparece.

#column - Int - O número da coluna em que começa.

#function - String - O nome da declaração em que aparece.

Kreiri
fonte
11
Bem, com certeza - todos encaminhados de C. Mas isso não respondeu à pergunta sobre __PRETTY_FUNCTION__, que não é facilmente criada a partir das opções fornecidas. (Existe um __CLASS__? Em caso afirmativo, isso ajudaria.)
Olie
10
Em Swift 2.2 deve usar #function, #file e outros como mostrado aqui: stackoverflow.com/a/35991392/1151916
Ramis
70

A partir do Swift 2.2, devemos usar:

  • #file (String) O nome do arquivo no qual aparece.
  • #line (Int) O número da linha em que aparece.
  • #column (Int) O número da coluna na qual começa.
  • #function (String) O nome da declaração em que aparece.

Da linguagem de programação Swift (Swift 3.1) na página 894.

func specialLiterals() {
    print("#file literal from file: \(#file)")
    print("#function literal from function: \(#function)")
    print("#line: \(#line) -> #column: \(#column)")
}
// Output:
// #file literal from file: My.playground
// #function literal from function: specialLiterals()
// #line: 10 -> #column: 42
Ramis
fonte
1
Isso deve ser marcado como a resposta correta no momento.
Danny Bravo
18

Swift 4
Esta é minha abordagem:

func pretty_function(_ file: String = #file, function: String = #function, line: Int = #line) {

    let fileString: NSString = NSString(string: file)

    if Thread.isMainThread {
        print("file:\(fileString.lastPathComponent) function:\(function) line:\(line) [M]")
    } else {
        print("file:\(fileString.lastPathComponent) function:\(function) line:\(line) [T]")
    }
}

Faça disso uma função global e apenas chame

pretty_function()

Bônus: você verá que o thread é executado em, [T] para um thread de fundo e [M] para o thread principal.

pegpeg
fonte
É necessário alterar a declaração do arquivo de String para NSString. lastPathComponent não está disponível em String.
primulaveris
1
Cara incrível. Pequena alteração para Swift> 2.1: "println" foi renomeado para "imprimir". print ("file: (file.debugDescription) function: (function) line: (line)")
John Doe
Legal, bom que funcione. Também seria ótimo poder passar classe / objeto para ele de alguma forma (uma opção é usar um argumento próprio explícito). Obrigado.
Costa do Mar do Tibete
Problemas com sua abordagem: - Esta função não é segura para threads. Se você chamá-lo de tópicos diferentes ao mesmo tempo, esteja preparado para algumas surpresas ruins - Usar funções globais é uma prática ruim
Karoly Nyisztor
9

A partir do XCode beta 6, você pode usar reflect(self).summarypara obter o nome da classe e __FUNCTION__o nome da função, mas as coisas estão um pouco confusas agora. Esperançosamente, eles encontrarão uma solução melhor. Pode valer a pena usar um #define até que estejamos fora do beta.

Este código:

NSLog("[%@ %@]", reflect(self).summary, __FUNCTION__)

dá resultados como este:

2014-08-24 08:46:26.606 SwiftLessons[427:16981938] [C12SwiftLessons24HelloWorldViewController (has 2 children) goodbyeActiongoodbyeAction]

EDIT: Este é mais código, mas me aproximou do que eu precisava, o que acho que é o que você queria.

func intFromString(str: String) -> Int
{
    var result = 0;
    for chr in str.unicodeScalars
    {
        if (chr.isDigit())
        {
            let value = chr - "0";
            result *= 10;
            result += value;
        }
        else
        {
            break;
        }
    }

    return result;
}


@IBAction func flowAction(AnyObject)
{
    let cname = _stdlib_getTypeName(self)
    var parse = cname.substringFromIndex(1)                                 // strip off the "C"
    var count = self.intFromString(parse)
    var countStr = String(format: "%d", count)                              // get the number at the beginning
    parse = parse.substringFromIndex(countStr.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
    let appName = parse.substringToIndex(count)                             // pull the app name

    parse = parse.substringFromIndex(count);                                // now get the class name
    count = self.intFromString(parse)
    countStr = String(format: "%d", count)
    parse = parse.substringFromIndex(countStr.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
    let className = parse.substringToIndex(count)
    NSLog("app: %@ class: %@ func: %@", appName, className, __FUNCTION__)
}

Ele fornece uma saída como esta:

2014-08-24 09:52:12.159 SwiftLessons[1397:17145716] app: SwiftLessons class: ViewController func: flowAction
Olie
fonte
8

Eu prefiro definir uma função de log global:

[Swift 3.1]

func ZYLog(_ object: Any?, filename: String = #file, line: Int = #line, funcname: String = #function) {
    #if DEBUG
    print("****\(Date()) \(filename)(\(line)) \(funcname):\r\(object ?? "nil")\n")
    #endif
}

[Swift 3.0]

func ZYLog<T>(_ object: T?, filename: String = #file, line: Int = #line, funcname: String = #function) {
    #if DEBUG
    print("****\(Date()) \(filename)(\(line)) \(funcname):\r\(object)\n")
    #endif
}

[Swift 2.0]

func ZYLog<T>(object: T, filename: String = __FILE__, line: Int = __LINE__, funcname: String = __FUNCTION__) {
    println("****\(filename.lastPathComponent)(\(line)) \(funcname):\r\(object)\n")
}

a saída é algo como:

****ZYHttpSessionManager.swift(78) POST(_:parameters:success:failure:):
[POST] user/login, {
    "auth_key" = xxx;
    "auth_type" = 0;
    pwd = xxx;
    user = "xxx";
}

****PointViewController.swift(162) loadData():
review/list [limit: 30, skip: 0]

****ZYHttpSessionManager.swift(66) GET(_:parameters:success:failure:):
[GET] review/list, {
    "auth_key" = xxx;
    uuid = "xxx";
}
ZYiOS
fonte
Na verdade, você não precisa de uma função genérica aqui, porque o objectparâmetro pode ser declarado como em Anyvez de T.
werediver
5

Aqui está uma resposta atualizada do Swift 2.

func LogW(msg:String, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__){
    print("[WARNING]\(makeTag(function, file: file, line: line)) : \(msg)")
}

private func makeTag(function: String, file: String, line: Int) -> String{
    let url = NSURL(fileURLWithPath: file)
    let className:String! = url.lastPathComponent == nil ? file: url.lastPathComponent!
    return "\(className) \(function)[\(line)]"
}

Exemplo de uso:

LogW("Socket connection error: \(error)")
Daniel Ryan
fonte
1
Isso é excelente. Mas, novamente .. LogW não pode ser usado exatamente da mesma forma que print () (com parâmetros, separados por vírgula) ..
Guntis Treulands
"LogW não pode ser usado exatamente da mesma forma que print () (com parâmetros separados por vírgula" Eu estava pensando em adicionar este suporte, mas descobri que não precisava dele. "LogW (" Erro de conexão de soquete: (erro) outras informações : (otherInfo) ")"
Daniel Ryan
1
Verdade. Bem, eu tentei e apenas outra solução que encontrei foi - usar extra () para manter a instrução, para torná-la o mais semelhante possível a print (). Usei sua resposta para criar este github.com/GuntisTreulands/ColorLogger-Swift De qualquer forma, muito obrigado! :)
Guntis Treulands
Muito útil! A partir do Swift 2.2,__FUNCTION__ becomes #function, __FILE__ becomes #file, and __LINE__ becomes #line.
Carl Smith
Tivemos problemas com os novos valores. Vamos esperar até o swift 3 para atualizar nossa base de código.
Daniel Ryan
0

Ou ligeira modificação da função com:

func logFunctionName(file:String = __FILE__, fnc:String = __FUNCTION__, line:(Int)=__LINE__) {
    var className = file.lastPathComponent.componentsSeparatedByString(".")
    println("\(className[0]):\(fnc):\(line)")

}

/ * produzirá um rastreamento de execução como: AppDelegate: application (_: didFinishLaunchingWithOptions :): 18 Produto: init (tipo: nome: ano: preço :): 34 FirstViewController: viewDidLoad (): 15 AppDelegate: applicationDidBecomeActive: 62 * /

user3620768
fonte
0

Eu uso, isso é tudo o que é necessário em um arquivo swift, todos os outros arquivos irão pegá-lo (como uma função global). Quando quiser liberar o aplicativo basta comentar a linha.

import UIKit

func logFunctionName(file:NSString = __FILE__, fnc:String = __FUNCTION__){  
    println("\(file.lastPathComponent):\(fnc)")
}
iCyberPaul
fonte
0

Swift 3.0

public func LogFunction<T>(object: T, filename: String = #file, line: Int = #line, funcname: String = #function) {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "MM/dd/yyyy HH:mm:ss:SSS"
    let process = ProcessInfo.processInfo()
    let threadId = "?"
    print("\(dateFormatter.string(from:Date())) \(process.processName) [\(process.processIdentifier):\(threadId)] \(filename)(\(line)) \(funcname)::: \(object)")
}
AleyRobotics
fonte
0

Swift 3.x +

Se você não quiser o nome do arquivo inteiro , aqui está uma solução rápida para isso.

func trace(fileName:String = #file, lineNumber:Int = #line, functionName:String = #function) -> Void {
    print("filename: \(fileName.components(separatedBy: "/").last!) function: \(functionName) line: #\(lineNumber)")
}

filename: ViewController.swift function: viewDidLoad() line: #42
Hemang
fonte
0

Outra maneira de registrar uma chamada de função:

NSLog("\(type(of:self)): %@", #function)
Ako
fonte