¿Existe una alternativa Swift para NSLog (@ "% s", __PRETTY_FUNCTION__)

88

En Objective C puede registrar el método que se está llamando usando:

NSLog(@"%s", __PRETTY_FUNCTION__)

Por lo general, esto se usa desde una macro de registro.

Aunque Swift no es compatible con macros (creo), todavía me gustaría usar una declaración de registro genérica que incluya el nombre de la función que se llamó. ¿Es eso posible en Swift?

Actualización: ahora uso esta función global para el registro que se puede encontrar aquí: https://github.com/evermeer/Stuff#print Y que puede instalar usando:

pod 'Stuff/Print'

Aquí está el 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 puedes usar así:

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)    

Lo que resultará en:

✳️ .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
fuente
1
Yo usoNSLog("Running %@ : %@",NSStringFromClass(self.dynamicType),__FUNCTION__)
Magster
1
Creo que su estilo de registro debería ser la definición de "función bonita". Gracias por compartir.
HuaTham

Respuestas:

101

Swift tiene #file, #function, #line y #column. Desde Swift Programming Language :

#file - Cadena: el nombre del archivo en el que aparece.

#line - Int: el número de línea en el que aparece.

#column - Int: el número de columna en el que comienza.

#function - Cadena: el nombre de la declaración en la que aparece.

Kreiri
fuente
11
Bueno, claro, todos los de C. Pero eso no respondió a la pregunta sobre __PRETTY_FUNCTION__, que no se crea fácilmente a partir de las opciones dadas. (¿Hay un __CLASS__? Si es así, eso ayudaría.)
Olie
10
En Swift 2.2 debería usar #function, #file y otros como se muestra aquí: stackoverflow.com/a/35991392/1151916
Ramis
70

A partir de Swift 2.2 deberíamos usar:

  • #file (String) El nombre del archivo en el que aparece.
  • #line (Int) El número de línea en el que aparece.
  • #column (Int) El número de columna en el que comienza.
  • #function (String) El nombre de la declaración en la que aparece.

Desde El lenguaje de programación Swift (Swift 3.1) en la 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
fuente
1
Esto debe marcarse como la respuesta correcta actualmente.
Danny Bravo
18

Swift 4
Este es mi enfoque:

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

Haga de esto una función global y simplemente llame

pretty_function()

Bono: Verá que el hilo se ejecuta en, [T] para un hilo de fondo y [M] para el hilo principal.

pegpeg
fuente
Necesita cambiar la declaración del archivo de String a NSString. lastPathComponent no está disponible en String.
primulaveris
1
Tipo impresionante. Diminuto cambio para Swift> 2.1: "println" ha sido renombrado a "print". print ("file: (file.debugDescription) function: (function) line: (line)")
John Doe
Genial, bueno que funcione. También sería genial poder pasarle clase / objeto de alguna manera (una opción es usar un argumento propio explícito). Gracias.
Sea Coast of Tibet
Problemas con su enfoque: - Esta función no es segura para subprocesos. Si lo llama desde diferentes hilos a la vez, prepárese para algunas sorpresas
desagradables
9

A partir de XCode beta 6, puede usar reflect(self).summarypara obtener el nombre de la clase y el nombre __FUNCTION__de la función, pero las cosas están un poco destrozadas, en este momento. Con suerte, encontrarán una mejor solución. Podría valer la pena usar un #define hasta que salgamos de la versión beta.

Este código:

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

da resultados como este:

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

EDITAR: Esto es más código, pero me acercó más a lo que necesitaba, que creo que es lo que querías.

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

Da una salida como esta:

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

Prefiero definir una función de registro 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")
}

la salida es 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
fuente
En realidad, no necesita una función genérica aquí, porque el objectparámetro se puede declarar como en Anylugar de T.
werediver
5

Aquí hay una respuesta actualizada de 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)]"
}

Ejemplo de uso:

LogW("Socket connection error: \(error)")
Daniel Ryan
fuente
1
Esto es magnífico. Pero, de nuevo ... LogW no se puede usar exactamente igual que print () (con parámetros, separados por comas) ..
Guntis Treulands
"LogW no se puede usar exactamente igual que print () (con parámetros, separados por comas" Estaba pensando en agregar este soporte pero descubrí que no lo necesitaba ". LogW (" Error de conexión de socket: (error) otra información : (otherInfo) ")"
Daniel Ryan
1
Cierto. Bueno, modifiqué y la única solución que encontré fue: usar extra () para mantener la declaración, para que sea lo más similar posible a print (). Usé tu respuesta para crear este github.com/GuntisTreulands/ColorLogger-Swift De todos modos, ¡muchas gracias! :)
Guntis Treulands
¡Muy útil! A partir de Swift 2.2,__FUNCTION__ becomes #function, __FILE__ becomes #file, and __LINE__ becomes #line.
Carl Smith
Tuvimos problemas con los nuevos valores. Esperaremos hasta swift 3 hasta actualizar nuestro código base.
Daniel Ryan
0

O una ligera modificación de la función con:

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

}

/ * producirá un seguimiento de ejecución como: AppDelegate: application (_: didFinishLaunchingWithOptions :): 18 Producto: init (tipo: nombre: año: precio :): 34 FirstViewController: viewDidLoad (): 15 AppDelegate: applicationDidBecomeActive: 62 * /

user3620768
fuente
0

Yo uso, esto es todo lo que se requiere en un archivo rápido, todos los demás archivos lo recogerán (como una función global). Cuando desee lanzar la aplicación, simplemente comente la línea.

import UIKit

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

Swift 3.x +

Si no desea el nombre completo del archivo, aquí tiene una solución rápida.

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
fuente
0

Otra forma de registrar la llamada a la función:

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