Existe-t-il une alternative Swift pour NSLog (@ «% s», __PRETTY_FUNCTION__)

87

Dans Objective C, vous pouvez enregistrer la méthode appelée en utilisant:

NSLog(@"%s", __PRETTY_FUNCTION__)

Cela est généralement utilisé à partir d'une macro de journalisation.

Bien que Swift ne prenne pas en charge les macros (je pense), j'aimerais toujours utiliser une instruction de journal générique qui inclut le nom de la fonction qui a été appelée. Est-ce possible dans Swift?

Mise à jour: J'utilise maintenant cette fonction globale pour la journalisation qui peut être trouvée ici: https://github.com/evermeer/Stuff#print Et que vous pouvez installer en utilisant:

pod 'Stuff/Print'

Voici le code:

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 vous pouvez utiliser comme ceci:

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)    

Ce qui se traduira par:

✳️ .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
la source
1
J'utiliseNSLog("Running %@ : %@",NSStringFromClass(self.dynamicType),__FUNCTION__)
Magster
J'utilise github.com/goktugyil/QorumLogs
Thellimist
1
Je pense que votre style de journalisation devrait être la définition de «jolie fonction». Merci d'avoir partagé.
HuaTham

Réponses:

101

Swift a #file, #function, #line et #column. À partir du langage de programmation Swift :

#file - Chaîne - Le nom du fichier dans lequel il apparaît.

#line - Int - Le numéro de ligne sur lequel il apparaît.

#column - Int - Le numéro de colonne dans lequel il commence.

#function - String - Le nom de la déclaration dans laquelle elle apparaît.

Kreiri
la source
11
Bien sûr - ceux-ci proviennent tous de C. Mais cela n'a pas répondu à la question sur __PRETTY_FUNCTION__, qui n'est pas facilement créée à partir des options données. (Y a-t-il un __CLASS__? Si oui, cela aiderait.)
Olie
10
Dans Swift 2.2, utilisez #function, #file et autres comme indiqué ici: stackoverflow.com/a/35991392/1151916
Ramis
70

À partir de Swift 2.2, nous devrions utiliser:

  • #file (String) Le nom du fichier dans lequel il apparaît.
  • #line (Int) Le numéro de ligne sur lequel il apparaît.
  • #column (Int) Le numéro de la colonne dans laquelle il commence.
  • #function (String) Le nom de la déclaration dans laquelle elle apparaît.

À partir du langage de programmation Swift (Swift 3.1) à la page 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
la source
1
Cela devrait être marqué comme la réponse actuellement correcte.
Danny Bravo
18

Swift 4
Voici mon approche:

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

Faites-en une fonction globale et appelez simplement

pretty_function()

Bonus: Vous verrez que le thread est exécuté sur, [T] pour un thread d'arrière-plan et [M] pour le thread principal.

pegpeg
la source
Besoin de changer la déclaration du fichier de String en NSString. lastPathComponent n'est pas disponible sur String.
primulaveris
1
Génial frérot. Petit changement pour Swift> 2.1: "println" a été renommé en "print". print ("file: (file.debugDescription) function: (function) line: (line)")
John Doe
Cool, bien que ça marche. Ce serait également génial de pouvoir y passer une classe / un objet (une option consiste à utiliser un auto-argument explicite). Merci.
Côte de la mer du Tibet
Problèmes avec votre approche: - Cette fonction n'est pas thread-safe. Si vous l'appelez à partir de différents threads à la fois, préparez-vous à de mauvaises surprises - L'utilisation de fonctions globales est une mauvaise pratique
Karoly Nyisztor
9

Depuis XCode beta 6, vous pouvez utiliser reflect(self).summarypour obtenir le nom de la classe et__FUNCTION__ pour obtenir le nom de la fonction, mais les choses sont un peu mutilées, pour le moment. J'espère qu'ils trouveront une meilleure solution. Cela peut valoir la peine d'utiliser une #define jusqu'à ce que nous soyons hors de la version bêta.

Ce code:

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

donne des résultats comme celui-ci:

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

EDIT: C'est plus de code, mais m'a rapproché de ce dont j'avais besoin, ce que je pense que c'est ce que vous vouliez.

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

Il donne une sortie comme celle-ci:

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

Je préfère définir une fonction de journalisation 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")
}

la sortie est quelque chose comme:

****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
la source
Vous n'avez pas réellement besoin d'une fonction générique ici, car le objectparamètre peut être déclaré comme Anyau lieu de T.
werediver
5

Voici une réponse Swift 2 mise à jour.

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

Exemple d'utilisation:

LogW("Socket connection error: \(error)")
Daniel Ryan
la source
1
C'est superbe. Mais là encore ... LogW ne peut pas être utilisé exactement de la même manière que print () (avec des paramètres séparés par des virgules) ..
Guntis Treulands
"LogW ne peut pas être utilisé exactement de la même manière que print () (avec des paramètres séparés par des virgules" Je pensais ajouter ce support mais j'ai trouvé que je n'en avais pas besoin. "LogW (" Erreur de connexion de socket: (erreur) autres informations : (otherInfo) ")"
Daniel Ryan
1
Vrai. Eh bien, j'ai bricolé et la seule autre solution que j'ai trouvée était - d'utiliser extra () pour contenir l'instruction, pour la rendre aussi similaire que possible à print (). Utilisé votre réponse pour créer celui-ci github.com/GuntisTreulands/ColorLogger-Swift Quoi qu'il en soit, merci beaucoup! :)
Guntis Treulands
Très utile! À partir de Swift 2.2,__FUNCTION__ becomes #function, __FILE__ becomes #file, and __LINE__ becomes #line.
Carl Smith
Nous avons eu du mal avec les nouvelles valeurs. Nous attendrons jusqu'à Swift 3 pour mettre à jour notre base de code.
Daniel Ryan
0

Ou légère modification de fonction avec:

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

}

/ * produira une trace d'exécution comme: AppDelegate: application (_: didFinishLaunchingWithOptions :): 18 Product: init (type: name: year: price :): 34 FirstViewController: viewDidLoad (): 15 AppDelegate: applicationDidBecomeActive: 62 * /

user3620768
la source
0

J'utilise, c'est tout ce qui est nécessaire dans un fichier swift, tous les autres fichiers le récupéreront (en tant que fonction globale). Lorsque vous souhaitez libérer l'application, commentez simplement la ligne.

import UIKit

func logFunctionName(file:NSString = __FILE__, fnc:String = __FUNCTION__){  
    println("\(file.lastPathComponent):\(fnc)")
}
iCyberPaul
la source
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)")
}
AleyRobotique
la source
0

Swift 3.x +

Si vous ne voulez pas le nom complet du fichier, voici une solution rapide pour cela.

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
la source
0

Une autre façon de consigner l'appel de fonction:

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