Czy istnieje alternatywa Swift dla NSLog (@ „% s”, __PRETTY_FUNCTION__)

88

W Objective C możesz zarejestrować wywoływaną metodę za pomocą:

NSLog(@"%s", __PRETTY_FUNCTION__)

Zwykle jest to używane z makra rejestrującego.

Chociaż Swift nie obsługuje makr (wydaje mi się), nadal chciałbym użyć ogólnej instrukcji dziennika, która zawiera nazwę funkcji, która została wywołana. Czy to możliwe w Swift?

Aktualizacja: Teraz używam tej globalnej funkcji do logowania, którą można znaleźć tutaj: https://github.com/evermeer/Stuff#print I którą możesz zainstalować za pomocą:

pod 'Stuff/Print'

Oto kod:

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

Którego możesz użyć w ten sposób:

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)    

Co spowoduje:

✳️ .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
źródło
1
UżywamNSLog("Running %@ : %@",NSStringFromClass(self.dynamicType),__FUNCTION__)
Magster
Używam github.com/goktugyil/QorumLogs
Thellimist
1
Myślę, że twój styl logowania powinien być definicją „ładnej funkcji”. Dzięki za udostępnienie.
HuaTham

Odpowiedzi:

101

Swift ma #file, #function, #line i #column. Z języka szybkiego programowania :

#file - String - nazwa pliku, w którym się pojawia.

#line - Int - numer wiersza, w którym się pojawia.

#column - Int - numer kolumny, w której się zaczyna.

#function - String - nazwa deklaracji, w której występuje.

Kreiri
źródło
11
Cóż - te wszystkie przekazane przez C. Ale to nie dało odpowiedzi na pytanie __PRETTY_FUNCTION__, co nie jest łatwe do stworzenia z podanych opcji. (Czy istnieje __CLASS__? Jeśli tak, to by pomogło.)
Olie
10
W Swift 2.2 należy używać #function, #file i innych, jak pokazano tutaj: stackoverflow.com/a/35991392/1151916
Ramis
70

Począwszy od Swift 2.2 powinniśmy użyć:

  • #file (String) Nazwa pliku, w którym się pojawia.
  • #line (Int) Numer wiersza, w którym się pojawia.
  • #column (Int) Numer kolumny, w której się zaczyna.
  • #function (String) Nazwa deklaracji, w której występuje.

Z języka programowania Swift (Swift 3.1) na stronie 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
źródło
1
Powinno to być oznaczone jako aktualnie poprawna odpowiedź.
Danny Bravo
18

Swift 4
Oto moje podejście:

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

Ustaw tę funkcję jako globalną i po prostu wywołaj

pretty_function()

Bonus: Zobaczysz, że wątek jest wykonywany, [T] dla wątku w tle i [M] dla wątku głównego.

pegpeg
źródło
Trzeba zmienić deklarację pliku z String na NSString. lastPathComponent nie jest dostępny w String.
primulaveris
1
Niesamowity koleś. Drobna zmiana dla Swift> 2.1: nazwa „println” została zmieniona na „print”. print ("file: (file.debugDescription) function: (function) line: (line)")
John Doe
Fajnie, dobrze, że to działa. Byłoby również wspaniale móc w jakiś sposób przekazać do niego klasę / obiekt (jedną z opcji jest użycie jawnego argumentu własnego). Dzięki.
Sea Coast of Tibet
Problemy z Twoim podejściem: - Ta funkcja nie jest bezpieczna dla wątków. Jeśli zadzwonisz z różnych wątków naraz, przygotuj się na złe niespodzianki - Korzystanie z funkcji globalnych to zła praktyka
Karoly Nyisztor
9

Począwszy od XCode beta 6, możesz użyć reflect(self).summarydo uzyskania nazwy klasy i __FUNCTION__nazwy funkcji, ale w tej chwili sytuacja jest nieco zniekształcona. Miejmy nadzieję, że znajdą lepsze rozwiązanie. Warto użyć #define, dopóki nie wyjdziemy z wersji beta.

Ten kod:

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

daje takie wyniki:

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

EDYCJA: To jest więcej kodu, ale zbliżyło mnie do tego, czego potrzebowałem, a myślę, że właśnie tego chciałeś.

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

Daje takie wyjście:

2014-08-24 09:52:12.159 SwiftLessons[1397:17145716] app: SwiftLessons class: ViewController func: flowAction
Olie
źródło
8

Wolę zdefiniować funkcję dziennika globalnego:

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

wynik jest podobny do:

****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
źródło
W rzeczywistości nie potrzebujesz tutaj funkcji ogólnej, ponieważ objectparametr można zadeklarować jako Anyzamiast T.
werediver
5

Oto zaktualizowana odpowiedź 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)]"
}

Przykład użycia:

LogW("Socket connection error: \(error)")
Daniel Ryan
źródło
1
To jest wspaniałe. Ale z drugiej strony ... LogW nie może być używane dokładnie tak samo jak print () (z parametrami oddzielonymi przecinkami) ..
Guntis Treulands
"LogW nie może być używane dokładnie tak samo jak print () (z parametrami oddzielonymi przecinkami" Myślałem o dodaniu tej obsługi, ale okazało się, że jej nie potrzebuję. "LogW (" Błąd połączenia z gniazdem: (błąd) inne informacje : (otherInfo) ")"
Daniel Ryan
1
Prawdziwe. Cóż, majstrowałem wokół i jedynym rozwiązaniem, które znalazłem, było użycie extra () do przechowywania instrukcji, aby było jak najbardziej podobne do print (). Użyłem Twojej odpowiedzi, aby utworzyć ten jeden github.com/GuntisTreulands/ColorLogger-Swift Tak czy inaczej, wielkie dzięki! :)
Guntis Treulands
Bardzo przydatne! Od wersji Swift 2.2,__FUNCTION__ becomes #function, __FILE__ becomes #file, and __LINE__ becomes #line.
Carl Smith
Mieliśmy problem z nowymi wartościami. Poczekamy do Swift 3, aż zaktualizujemy naszą bazę kodu.
Daniel Ryan
0

Lub niewielka modyfikacja funkcji za pomocą:

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

}

/ * wygeneruje ślad wykonania, taki jak: AppDelegate: application (_: didFinishLaunchingWithOptions :): 18 Product: init (typ: nazwa: rok: cena :): 34 FirstViewController: viewDidLoad (): 15 AppDelegate: applicationDidBecomeActive: 62 * /

user3620768
źródło
0

Używam, to wszystko, co jest wymagane w pliku swift, wszystkie inne pliki będą go odbierać (jako funkcja globalna). Jeśli chcesz zwolnić aplikację, po prostu zakomentuj linię.

import UIKit

func logFunctionName(file:NSString = __FILE__, fnc:String = __FUNCTION__){  
    println("\(file.lastPathComponent):\(fnc)")
}
iCyberPaul
źródło
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
źródło
0

Swift 3.x +

Jeśli nie chcesz pełnej nazwy pliku, oto szybka poprawka.

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
źródło
0

Inny sposób rejestrowania wywołań funkcji:

NSLog("\(type(of:self)): %@", #function)
Ako
źródło