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

1
Eu usoNSLog("Running %@ : %@",NSStringFromClass(self.dynamicType),__FUNCTION__)
Magster


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.


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

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.


É 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

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";
}

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)")

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 * /


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)")
}

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)")
}

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

0

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

NSLog("\(type(of:self)): %@", #function)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.