post Image
Swiftで簡単なロガーを作成する

はじめに

Swift用ロギングライブラリのSwiftyBeaverなどを参考にできるだけシンプルなロガーをSwiftで作成してみます。もちろんOSSのロガーを利用するのも便利で良いですが、自分の思い通りにロガーをつくるのも面白いと思います。

どんなロガーをつくるか

必要な機能

ロガーをつくる上で、あった方が便利な機能を挙げてみます。

  1. 特に何もしなくてもログの実行箇所が出力される
    • クラス
    • メソッド
    • 行数
  2. 実行日時が出力される
  3. ログの重要度でフィルタできるように、ログレベルを出力する
    • verbose
    • debug
    • info
    • warn
    • error
  4. ログレベルがerrorの場合はさらにプログラムを停止させる
  5. デバッグビルド時にのみ出力する(リリースビルド時には出力しない)

利用イメージ

こんな風に書くと、

ログの実行イメージ
Logger.info()
Logger.debug("Viewのサイズ: \(view.frame.size)")
Logger.error("ここが実行されたら何かがおかしい")

こんな風に出力されるものをつくります。

ログの出力イメージ
2018/01/15 23:19:50 [INFO] ViewController.viewDidLoad() #20
2018/01/15 23:20:00 [DEBUG] ViewController.viewDidApper() #39: Viewのサイズ: (375.0, 667.0)
2018/01/15 23:21:10 [ERROR] APIClient.request() #15: ここが実行されたら何かがおかしい

実装

ポイント

「特に何もしなくてもログの実行箇所が出力される」を実現するために、ログの実行メソッドの引数のデフォルト値に、#functionなどを指定し、呼び出し側は何も指定しなくても良いようにしています。

   public static func info(file: String = #file, function: String = #function, line: Int = #line, _ message: String = "") {
        printToConsole(logLevel: .info, file: file, function: function, line: line, message: message)
    }

「ログの重要度でフィルタできるように、ログレベルを出力する」を実現するため、ログレベルをenumで表現しました。ログレベルはログメソッドの引数にしても良かったのですが、メソッド名にしたほうが使いやすいのでメソッド名にしました。

ログの重要度でフィルタできるように、ログレベルを出力する
    public enum LogLevel: String {
        case verbose
        case debug
        case info
        case warn
        case error
    }

    public static func verbose(  ) {  }
    public static func debug(  ) {  }
    public static func info(  ) {  }
    public static func warn(  ) {  }
    public static func error(  ) {  }

「ログレベルがerrorの場合はさらにプログラムを停止させる」を実現するため、ログレベルがerrorの場合は、assertionfailure()を実行し、プログラムを停止させます。

ログレベルがerrorの場合はさらにプログラムを停止させる
    public static func error(file: String = #file, function: String = #function, line: Int = #line, _ message: String = "") {
        printToConsole(logLevel: .error, file: file, function: function, line: line, message: message)
        assertionFailure(message)
    }

「デバッグビルド時にのみ出力する(リリースビルド時には出力しない)」を実現するため、DEBUGのプリプロセッサマクロの判定文をログ出力部分に書いています。

デバッグビルド時にのみ出力する(リリースビルド時には出力しない)
    private static func printToConsole(logLevel: LogLevel, file: String, function: String, line: Int, message: String) {
        #if DEBUG
            print("\(dateString) [\(logLevel.rawValue.uppercased())] \(className(from: file)).\(function) #\(line): \(message)")
        #endif
    }

完成コード

ソースコードは以下の1ファイルのみです。

import Foundation

public struct Logger {
    private static var dateString: String {
        let date = Date()
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
        return formatter.string(from: date)
    }

    public enum LogLevel: String {
        case verbose
        case debug
        case info
        case warn
        case error
    }

    public static func verbose(file: String = #file, function: String = #function, line: Int = #line, _ message: String = "") {
        printToConsole(logLevel: .verbose, file: file, function: function, line: line, message: message)
    }

    public static func debug(file: String = #file, function: String = #function, line: Int = #line, _ message: String = "") {
        printToConsole(logLevel: .debug, file: file, function: function, line: line, message: message)
    }

    public static func info(file: String = #file, function: String = #function, line: Int = #line, _ message: String = "") {
        printToConsole(logLevel: .info, file: file, function: function, line: line, message: message)
    }

    public static func warn(file: String = #file, function: String = #function, line: Int = #line, _ message: String = "") {
        printToConsole(logLevel: .warn, file: file, function: function, line: line, message: message)
    }

    public static func error(file: String = #file, function: String = #function, line: Int = #line, _ message: String = "") {
        printToConsole(logLevel: .error, file: file, function: function, line: line, message: message)
        assertionFailure(message)
    }

    private static func className(from filePath: String) -> String {
        let fileName = filePath.components(separatedBy: "/").last
        return fileName?.components(separatedBy: ".").first ?? ""
    }

    private static func printToConsole(logLevel: LogLevel, file: String, function: String, line: Int, message: String) {
        #if DEBUG
            print("\(dateString) [\(logLevel.rawValue.uppercased())] \(className(from: file)).\(function) #\(line): \(message)")
        #endif
    }
}

まとめ

これだけで簡単にロガーをつくることができました。+αの機能として、ファイルやクラウド上に保存するなど拡張する、もっと見やすくログを色付けしたり、絵文字🍏を出力しても面白いかと思います。もしも、開発アプリにロガーを導入していない場合はデバッグに大いに役立ちますので、検討してみても良いかと思います。

参考記事


『 Swift 』Article List