LoginSignup
40
28

More than 5 years have passed since last update.

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

Posted at

はじめに

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

まとめ

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

参考記事

40
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
28