Esiste un'alternativa Swift per NSLog (@ "% s", __PRETTY_FUNCTION__)


88

In Objective C puoi registrare il metodo che viene chiamato usando:

NSLog(@"%s", __PRETTY_FUNCTION__)

Di solito viene utilizzato da una macro di registrazione.

Sebbene Swift non supporti le macro (credo), vorrei comunque utilizzare un'istruzione di log generica che includa il nome della funzione che è stata chiamata. È possibile in Swift?

Aggiornamento: ora utilizzo questa funzione globale per la registrazione che può essere trovata qui: https://github.com/evermeer/Stuff#print E che puoi installare utilizzando:

pod 'Stuff/Print'

Ecco il codice:

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

Che puoi usare in questo modo:

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)    

Il che si tradurrà in:

✳️ .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
Io usoNSLog("Running %@ : %@",NSStringFromClass(self.dynamicType),__FUNCTION__)
Magster


1
Penso che il tuo stile di registrazione dovrebbe essere la definizione di "bella funzione". Grazie per la condivisione.
HuaTham

Risposte:


101

Swift ha #file, #function, #line e #column. Dal linguaggio di programmazione Swift :

#file - String - Il nome del file in cui appare.

#line - Int - Il numero di riga su cui appare.

#column - Int: il numero di colonna in cui inizia.

#function - String - Il nome della dichiarazione in cui appare.


11
Be ', certo - quelli sono tutti avanti da C. Ma questo non ha risposto alla domanda su __PRETTY_FUNCTION__, che non è facilmente creata dalle opzioni date. (C'è un __CLASS__? Se è così, sarebbe d'aiuto.)
Olie

10
In Swift 2.2 dovresti usare #function, #file e altri come mostrato qui: stackoverflow.com/a/35991392/1151916
Ramis

70

A partire da Swift 2.2 dovremmo usare:

  • #file (String) Il nome del file in cui appare.
  • #line (Int) Il numero di riga su cui appare.
  • #column (Int) Il numero di colonna in cui inizia.
  • #function (String) Il nome della dichiarazione in cui appare.

Da Swift Programming Language (Swift 3.1) a pagina 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
Questo dovrebbe essere contrassegnato come la risposta attualmente corretta.
Danny Bravo

18

Swift 4
Ecco il mio approccio:

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

Rendila una funzione globale e chiama

pretty_function()

Bonus: vedrai che il thread viene eseguito, [T] per un thread in background e [M] per il thread principale.


È necessario modificare la dichiarazione del file da String a NSString. lastPathComponent non è disponibile su String.
primulaveris

1
Tizio fantastico. Piccolo cambiamento per Swift> 2.1: "println" è stato rinominato "print". print ("file: (file.debugDescription) function: (function) line: (line)")
John Doe

Bello, bene che funzioni. Sarebbe anche bello essere in grado di passare in qualche modo classe / oggetto (un'opzione è usare un argomento auto esplicito). Grazie.
Costa marittima del Tibet

Problemi con il tuo approccio: - Questa funzione non è thread-safe. Se lo chiami da diversi thread contemporaneamente, preparati ad alcune brutte sorprese - Usare le funzioni globali è una cattiva pratica
Karoly Nyisztor

9

A partire da XCode beta 6, puoi usare reflect(self).summaryper ottenere il nome della classe e __FUNCTION__per ottenere il nome della funzione, ma le cose sono un po 'alterate, in questo momento. Si spera che trovino una soluzione migliore. Potrebbe valere la pena usare un #define fino alla fine della beta.

Questo codice:

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

dà risultati come questo:

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

EDIT: Questo è più codice, ma mi ha avvicinato a ciò di cui avevo bisogno, che penso sia quello che volevi.

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

Fornisce un output come questo:

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

8

Preferisco definire una funzione di registro globale:

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

l'output è qualcosa del tipo:

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

In realtà non hai bisogno di una funzione generica qui, perché il objectparametro può essere dichiarato come Anyinvece di T.
werediver

5

Ecco una risposta Swift 2 aggiornata.

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

Esempio di utilizzo:

LogW("Socket connection error: \(error)")

1
Questo è fantastico. Ma poi di nuovo .. LogW non può essere utilizzato esattamente come print () (con parametri separati da virgola) ..
Guntis Treulands

"LogW non può essere utilizzato esattamente come print () (con parametri separati da virgola" Stavo pensando di aggiungere questo supporto ma ho scoperto di non averne bisogno. "LogW (" Errore di connessione socket: (errore) altre informazioni : (otherInfo) ")"
Daniel Ryan,

1
Vero. Bene, ho armeggiato e l'unica altra soluzione che ho trovato è stata - usare extra () per contenere l'istruzione, per renderla il più simile possibile a print (). Hai usato la tua risposta per creare questo github.com/GuntisTreulands/ColorLogger-Swift Comunque, grazie mille! :)
Guntis Treulands

Molto utile! A partire da Swift 2.2,__FUNCTION__ becomes #function, __FILE__ becomes #file, and __LINE__ becomes #line.
Carl Smith

Abbiamo avuto problemi con i nuovi valori. Attenderemo fino a swift 3 fino all'aggiornamento della nostra base di codice.
Daniel Ryan

0

O una leggera modifica della funzione con:

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

}

/ * produrrà una traccia di esecuzione come: AppDelegate: application (_: didFinishLaunchingWithOptions :): 18 Product: init (type: name: year: price :): 34 FirstViewController: viewDidLoad (): 15 AppDelegate: applicationDidBecomeActive: 62 * /


0

Io uso, questo è tutto ciò che è richiesto in un file swift, tutti gli altri file lo prenderanno (come funzione globale). Quando si desidera rilasciare l'applicazione, è sufficiente commentare la riga.

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 non desideri l' intero nome del file, ecco una soluzione rapida per questo.

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

Un altro modo per registrare la chiamata di funzione:

NSLog("\(type(of:self)): %@", #function)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.