search
LoginSignup
1

More than 1 year has passed since last update.

posted at

updated at

Fortranの標準ライブラリstdlibの紹介(ロガー)

概要

Fortran-langコミュニティが主導して開発しているFortraの標準ライブラリstdlibについて紹介します.紹介する内容は,stdlibの現行バージョン0.1.0に基づいています.

本記事では,stdlibに実装されているロガーを紹介します.

  • stdlib_logger

stdlib_logger

stdlib_loggerモジュールでは,ロギングに用いる派生型logger_typeを定義しています.派生型logger_typeを用いることで,エラー/警告の報告,デバッグ情報の報告などを統一的に実現できます.

stdlib_loggerモジュールの中では,派生型logger_typeの型束縛手続きや,ログレベルを表す定数などが定義されています.ロガーを利用する度にlogger_type型の変数を作るのは煩わしいという意見に応えて,モジュール変数global_loggerが利用できるようになっています.

global_loggerは,global_loggerに追加された装置に情報を出力します.何も装置が追加されていない場合は,標準出力output_unitに情報を出力します.この挙動が少し判りにくいので,後ほど実例で説明します.

使い方

全ての型束縛手続を網羅的に解説するのは骨が折れるので,使い方の中で解説をしていきます.

メッセージ出力

エラー・警告などの情報を報告するには,log_debug等の型束縛手続きを用います.

  • log_debug
    debugログを画面に出力する.接頭辞"DEBUG: "を付けて,"タイムスタンプ: DEBUG: メッセージ"の形式で出力する.
  • log_information
    informationログを画面に出力する.接頭辞"INFO: "を付けて,"タイムスタンプ: INFO: メッセージ"の形式で出力する.
  • log_warning
    warningログを画面に出力する.接頭辞"WARN: "を付けて"タイムスタンプ: WARN: メッセージ"の形式で出力する.
  • log_error
    errorログを画面に出力する.接頭辞"ERROR: "を付けて"タイムスタンプ: ERROR: メッセージ"の形式で出力する.
  • log_message
    メッセージを画面に出力する.接頭辞を付けずに"タイムスタンプ: メッセージ"の形式で出力する.

装置を追加していないので,標準出力に情報が出力されます.

program main
    use :: stdlib_logger
    implicit none

    call global_logger%log_debug(message="log message debug")
    ! 標準設定では出力されない.

    call global_logger%log_information(message="log message information")
    ! 2021-12-04 10:48:22.886: INFO: log message information

    call global_logger%log_warning(message="log message warning")
    ! 2021-12-04 10:48:22.887: WARN: log message warning

    call global_logger%log_error(message="log message error")
    ! 2021-12-04 10:48:22.888: ERROR: log message error

    call global_logger%log_message(message="log message")
    ! 2021-12-04 10:48:22.888: log message
end program main

メッセージ出力には,手続名やモジュール名を出力する引数が存在します.
message以外に,モジュール名,手続名を指定する引数moduleprocedureも存在しています.引数を指定すると,"タイムスタンプ: モジュール名 % 手続名: INFO: メッセージ"の形式で出力されます.

! ロガーを呼び出しているモジュール名と手続名の出力.
call global_logger%log_information("log message information", &
                                    module="ex_stdlib_system", &
                                    procedure="ex_stdlib_logger")
! 2021-12-04 10:48:22.888: ex_stdlib_system % ex_stdlib_logger: INFO: log message information

ログ出力の設定

logger_typeには,ログ出力を設定する型束縛手続きconfigureが用意されています.

出力するメッセージのレベル設定

引数levelを指定することで,出力されるメッセージのレベルを設定できます.levelは整数で指定しますが,stdlib_loggerにはレベルを指定するための定数が定義されています.

数値はあくまで参考情報で覚えておく必要はなく,定数で指定するようにしてください.

  • debug_level = 10
  • information_level = 20
  • warning_level = 30
  • error_level = 40
  • io_error_level = 40
  • text_error_level = 50

標準ではlevel = information_levelです.下の例では,標準では出力されないdebugログを出力するように設定を変更しています.設定変更前のdebugログ"log message debug 1"は出力されず,設定を変更した後のdebugログ"log message debug 2"は画面に出力されます.

call global_logger%log_debug(message="log message debug 1")

! debugログを出力するよう設定
call global_logger%configure(level=debug_level)

call global_logger%log_debug(message="log message debug 2")
! 2021-12-04 11:26:53.059: DEBUG: log message debug 2

メッセージの出力幅および改行時のインデントの設定

引数max_widthに数値を与えることで,出力されるメッセージ幅の上限を整数値で設定できます.4より大きいと出力幅として設定され,それより小さいと上限なしと設定されます.これはあくまで上限であって,上限を超えないように適切にスペースで改行されます.
メッセージ幅の上限を解除するには,0を指定します.

改行時にメッセージが字下げされます.この字下げの制御には,indentオプションを利用します.論理値を引数として取り,.true.で改行されたメッセージを字下げし,.false.で字下げを抑制します.

! debugログを出力するよう設定
call global_logger%configure(level=debug_level)

! メッセージ幅の上限を40文字に設定
call global_logger%configure(max_width=40)

call global_logger%log_debug(message="log message debug", &
                             module="ex_stdlib_system", &
                             procedure="ex_stdlib_logger")
! 2021-12-04 11:53:24.523:
!     ex_stdlib_system %
!     ex_stdlib_logger: DEBUG: log
!     message debug
! 1234567890123456789012345678901234567890

! メッセージが改行されたときの字下げを無効
call global_logger%configure(indent=.false.)

call global_logger%log_debug(message="log message debug", &
                             module="ex_stdlib_system", &
                             procedure="ex_stdlib_logger")
! 2021-12-04 11:53:24.523:
! ex_stdlib_system % ex_stdlib_logger:
! DEBUG: log message debug
! 1234567890123456789012345678901234567890

! メッセージ幅の上限をなしに設定
call global_logger%configure(max_width=0)
call global_logger%log_debug(message="log message debug", &
                             module="ex_stdlib_system", &
                             procedure="ex_stdlib_logger")
! 2021-12-04 11:44:48.302: ex_stdlib_system % ex_stdlib_logger: DEBUG: log message debug

タイムスタンプの出力の設定

ログメッセージにタイムスタンプを付けるか否かの設定を,time_stamp引数で行います.論理値を引数として取り,.true.でタイムスタンプを出力し,.false.でタイムスタンプの出力を抑制します.

! debugログを出力するよう設定
call global_logger%configure(level=debug_level)

! タイムスタンプを出力しない
call global_logger%configure(time_stamp=.false.)

call global_logger%log_debug(message="long long long long log message debug", &
                             module="ex_stdlib_system", &
                             procedure="ex_stdlib_logger")
! ex_stdlib_system % ex_stdlib_logger: DEBUG: long long long long log message debug

メッセージ間の改行

ログメッセージを出力する際に,改行を入れるか否かを制御します.引数add_blank_line.true.を指定すると,メッセージ出力に改行を出力します.

! ログメッセージを出力する前に改行を出力する
call global_logger%configure(add_blank_line=.true.)

! debugログを出力+メッセージが改行されたときの字下げを無効+メッセージ幅を30文字に設定
call global_logger%configure(level=debug_level, indent=.false., max_width=30)

call global_logger%log_debug(message="long long long long log message debug", &
                             module="ex_stdlib_system", &
                             procedure="ex_stdlib_logger")
call global_logger%log_debug(message="next log message debug", &
                             module="ex_stdlib_system", &
                             procedure="ex_stdlib_logger")
!
! ex_stdlib_system %
! ex_stdlib_logger: DEBUG: long
! long long long log message
! debug
!
! ex_stdlib_system %
! ex_stdlib_logger: DEBUG: next
! log message debug

ログメッセージを出力する装置の追加と削除

ここまで,標準出力にログメッセージを出力する例を見てきました.logger_type型変数に出力先の装置を追加することで,標準出力output_unit以外にも出力できます.

出力先を追加する方法は2通りあります.

  • add_log_unit
    出力する装置番号を追加する.標準出力やエラー出力,既に開いてあるファイルの装置番号を追加する用途で用いられる.
  • add_log_file
    ファイルを開いた上で,その装置番号をログ出力先装置として追加する.open文でよく用いられる引数が利用可能.
引数名 用途
unit integer, intent(out), optional 追加されたファイルの装置番号.
action character(*), intent(in), optional ファイルに対する処理を指定する.書き込み"write"か追記"readwrite"かを指定し,標準は"write"
position character(*), intent(in), optional 開いたファイル内の位置を指定する.ファイル内の位置を変更しない場合はasis,ファイルの先頭に移動する場合はrewind,ファイル終端に移動する場合はappendを指定する.標準は"rewind"
status character(*), intent(in), optional ファイルが存在しているかを指定する.既に存在している場合は"old",存在しておらず新しく作成する場合は"new",存在しているファイルを新しく置き換える場合は"replace",ファイルの存在が事前に判らないは"unknown"を指定する.標準は"replace"
stat integer, intent(out), optional ファイルオープンの結果を返す.定数successと一致しない場合はファイルオープンに失敗している.

装置の追加には一つ落とし穴があります.logger_typeは,装置を追加していなければ標準出力に出力します.何も装置を追加していない状態から一つ装置を追加すると,標準出力には出力されなくなります.ファイルに出力すると同時に標準出力にも出力するには,明示的にoutput_unitを追加する必要があります.

ロガーに関連付けられている装置の数を出力するには,log_units_assigned手続を用います.ロガーから装置を削除するには,remove_log_unitを用います.

使用例が少し長くなるので,一つのプログラムを途中で区切りながら説明を加えていきます.

program main
    use, intrinsic :: iso_fortran_env
    use :: stdlib_logger
    use :: stdlib_strings
    implicit none

    integer(int32) :: log_file_unit, stat

    ! debugログを出力するように設定
    call global_logger%configure(level=debug_level)

既定のロガー(global_logger)にファイルを追加し,ログメッセージをファイル"log_message.txt"に出力します."log_message.txt"の装置番号がlog_file_unitに格納されます.
この手続実行以降,ログメッセージは画面には出力されなくなります.

ロガーへのファイル追加に成功していれば,その装置番号を文字列に変換して出力するようにしています.数字と文字の変換には,stdlib_stringsで定義されているto_stringを用いました.

    call global_logger%add_log_file("log_message.txt", unit=log_file_unit, stat=stat)

    if (stat == success) then
        call global_logger%log_information("log file unit ="//to_string(log_file_unit))
    end if

このメッセージは画面に出力されず,"log_message.txt"に書き込まれます.

log_message.txt
2021-12-04 13:22:36.558: INFO: log file unit =-10

いくつかログメッセージを出力してみます.

    call global_logger%log_debug("log message debug")
    call global_logger%log_information("log message information")
    call global_logger%log_warning("log message warning")
    call global_logger%log_error("log message error")

これらのメッセージも画面に出力されず,"log_message.txt"に書き込まれます.

log_message.txt
2021-12-04 13:22:36.558: INFO: log file unit =-10
2021-12-04 13:22:36.558: DEBUG: log message debug
2021-12-04 13:22:36.558: INFO: log message information
2021-12-04 13:22:36.558: WARN: log message warning
2021-12-04 13:22:36.558: ERROR: log message error

ロガーの出力装置に標準出力を追加し,ログファイルと画面の双方にメッセージが出力されるようにします.

    ! ロガーの出力装置に,標準出力を追加.
    call global_logger%add_log_unit(unit=output_unit)

    ! ログメッセージの出力.ファイルと画面の両方に同じメッセージが出力される.
    call global_logger%log_debug("2nd log message debug")
    call global_logger%log_information("2nd log message information")
    call global_logger%log_warning("2nd log message warning")
    call global_logger%log_error("2nd log message error")
    ! 2021-12-04 13:22:36.558: DEBUG: 2nd log message debug
    ! 2021-12-04 13:22:36.558: INFO: 2nd log message information
    ! 2021-12-04 13:22:36.558: WARN: 2nd log message warning
    ! 2021-12-04 13:22:36.558: ERROR: 2nd log message error

"log_message.txt"の内容も更新されます.

log_message.txt
2021-12-04 13:22:36.558: INFO: log file unit =-10
2021-12-04 13:22:36.558: DEBUG: log message debug
2021-12-04 13:22:36.558: INFO: log message information
2021-12-04 13:22:36.558: WARN: log message warning
2021-12-04 13:22:36.558: ERROR: log message error
2021-12-04 13:22:36.558: DEBUG: 2nd log message debug
2021-12-04 13:22:36.558: INFO: 2nd log message information
2021-12-04 13:22:36.558: WARN: 2nd log message warning
2021-12-04 13:22:36.558: ERROR: 2nd log message error

ロガーに登録されている装置の数を出力し,その後ロガーから"log_message.txt"を削除してみましょう.

    ! ロガーに登録されている装置数を出力.
    call global_logger%log_debug("number of log units = "//to_string(global_logger%log_units_assigned()))
    ! 2021-12-04 13:22:36.558: DEBUG: number of log units = 2

    ! ロガーからログファイル"log_message.txt"の装置番号を削除.
    call global_logger%remove_log_unit(unit=log_file_unit, close_unit=.true.)

    ! ログメッセージの出力.これは画面にのみ出力される.
    call global_logger%log_information("3rd log message information")
    ! 2021-12-04 13:22:36.559: INFO: 3rd log message information

"log_message.txt"には,装置数までは出力されていますが,"3rd log message information"は出力されていません.

log_message.txt
2021-12-04 13:22:36.558: INFO: log file unit =-10
2021-12-04 13:22:36.558: DEBUG: log message debug
2021-12-04 13:22:36.558: INFO: log message information
2021-12-04 13:22:36.558: WARN: log message warning
2021-12-04 13:22:36.558: ERROR: log message error
2021-12-04 13:22:36.558: DEBUG: 2nd log message debug
2021-12-04 13:22:36.558: INFO: 2nd log message information
2021-12-04 13:22:36.558: WARN: 2nd log message warning
2021-12-04 13:22:36.558: ERROR: 2nd log message error
2021-12-04 13:22:36.558: DEBUG: number of log units = 2

既存のログファイルに追記できることを確認するために,先ほど削除したログファイルを再び開き,メッセージを追記します.

add_log_fileに引数を指定して,"log_message.txt"を開いた後,メッセージを出力してファイルを閉じます.このとき,上で標準出力output_unitを追加しているので,add_log_fileの後であっても標準出力にも出力されます.

    ! ロガーにファイルを追加.既存のログファイルに追記するように設定.
    call global_logger%add_log_file("log_message.txt", unit=log_file_unit, &
                                    action="write", position="append", status="old")

    ! ログメッセージの出力.このメッセージは画面に出力され,ログファイル"log_message.txt"に追記される.
    call global_logger%log_information("4th log message information")
    ! 2021-12-04 13:22:36.561: INFO: 4th log message information

    ! ロガーからログファイル"log_message.txt"の装置番号を削除.
    call global_logger%remove_log_unit(unit=log_file_unit, close_unit=.true.)
end program main

最終的な出力内容は,下記のようになりました.

2021-12-04 13:22:36.558: DEBUG: 2nd log message debug
2021-12-04 13:22:36.558: INFO: 2nd log message information
2021-12-04 13:22:36.558: WARN: 2nd log message warning
2021-12-04 13:22:36.558: ERROR: 2nd log message error
2021-12-04 13:22:36.558: DEBUG: number of log units = 2
2021-12-04 13:22:36.559: INFO: 3rd log message information
2021-12-04 13:22:36.561: INFO: 4th log message information
log_message.txt
2021-12-04 13:22:36.558: INFO: log file unit =-10
2021-12-04 13:22:36.558: DEBUG: log message debug
2021-12-04 13:22:36.558: INFO: log message information
2021-12-04 13:22:36.558: WARN: log message warning
2021-12-04 13:22:36.558: ERROR: log message error
2021-12-04 13:22:36.558: DEBUG: 2nd log message debug
2021-12-04 13:22:36.558: INFO: 2nd log message information
2021-12-04 13:22:36.558: WARN: 2nd log message warning
2021-12-04 13:22:36.558: ERROR: 2nd log message error
2021-12-04 13:22:36.558: DEBUG: number of log units = 2
2021-12-04 13:22:36.561: INFO: 4th log message information

ロガーの設定の取得

ロガーの現在の設定を取得するには,型束縛手続きconfigurationを用います.configurationでは,configureで設定できる項目と,開いている装置番号一覧を取得できます.

引数名 用途
add_blank_line logical,intent(out),optional ログメッセージを出力する際に,改行を入れる設定になっていれば.true.,そうでなければ.false.が返る.
indent logical,intent(out),optional 出力するメッセージが改行された際に,字下げをする設定になっていれば.true.,そうでなければ.false.が返る.
level integer,intent(out),optional 出力するメッセージのレベルが返る.
max_width integer,intent(out),optional メッセージの出力幅が返る.
time_stamp logical,intent(out),optional メッセージにタイプスタンプを付けて出力する設定になっていれば.true.,そうでなければ.false.が返る.
log_units integer,dimension(:),allocatable,intent(out),optional ロガーに関連付けられた装置の番号が返る.
integer(int32), allocatable :: log_units(:)
integer(int32) :: log_level

! ロガーに関連付けられた装置番号,ログレベルを取得
call global_logger%configuration(level=log_level, log_units=log_units)

if (size(log_units) == 0) call global_logger%add_log_unit(output_unit)
if (log_level >= information_level) call global_logger%configure(level=debug_level)

その他のログ出力手続

logger_typeには,一般的な用途のログ出力以外に,特定の用途に使うログ出力手続が二つ用意されています.

一つは,IOに関係するエラーログを出力するlog_io_errorで,もう一つはテキスト内の誤り位置を指定するlog_text_errorです.

log_io_error

log_io_errorでは,接頭辞"I/O ERROR: "を付けてメッセージを出力します.また,open文の動作結果状態iostatやエラーメッセージiomsgを引数で受け付けます.

下の例では,log_io_errorの使い方を確認するために,存在しないファイルを開いています.

program main
    use, intrinsic :: iso_fortran_env
    use :: stdlib_logger
    implicit none

    integer(int32) :: iostat, io_unit
    character(128) :: iomsg

    ! 存在しないファイルを開く.
    open (newunit=io_unit, file="test.txt", status="old", iostat=iostat, iomsg=iomsg)

    ! ioエラーをログメッセージとして画面に出力.
    call global_logger%log_io_error("file "//"test.txt"//" open failure", iostat=iostat, iomsg=iomsg)
    ! 2021-12-04 13:22:36.561: I/O ERROR: file test.txt open failure
    ! With iostat = 2
    ! With iomsg = "Cannot open file 'test.txt': No such file or directory"
end program main

log_text_error

log_text_errorは,下記のような書式でテキスト内の誤り位置を指定する手続です.

test.txt:0:7

text massage
      ^
Error: spelling mistake

テキスト内の誤りを指定するために,他の手続とは異なった引数を取ります.

引数名 用途
line character(*),intent(in) エラーを含んでいる文字列.
column integer,intent(in) エラーが生じている箇所の先頭列番号(1開始).
summary character(*),intent(in) エラーの要約.
filename character(*),intent(in),optional lineが見つかったファイルの名前.
line_number integer,intent(in),optional ファイルfilename内でlineが存在する行番号.
caret character,intent(in),optional エラーが生じている箇所を明示する記号.標準は"^"
stat integer,intent(out),optional log_text_errorの実行結果を返す.定数successと一致しない場合は失敗しており,停止コードが返される.

テキスト内のエラーを自動で検出するわけではないので,比較的手間はかかりますが,非常に判りやすくエラーを指摘できます.

call global_logger%log_text_error(line="text massage", &
                                  column=7, &
                                  summary="spelling mistake", &
                                  filename="test.txt", &
                                  line_number=0, &
                                  caret="^")
! 2021-12-04 14:21:57.161
! test.txt:0:7
!
! text massage
!       ^
! Error: spelling mistake

複数のロガーの利用

ここまでは全て既定のモジュール変数であるglobal_loggerを使ってきました.stdlib_loggerで定義されているロガーは派生型logger_typeなので,logger_type型変数として新たにロガーを設ける事ができます.

global_logger以外のロガーを設けることで,ロギングに柔軟性を持たせることができます.例えば,画面にはInformationあるいはWarningレベル以上を出力して,ログファイルにはDebugレベルで出力するといった使い分けができます.

! 新しいロガーを宣言.
type(logger_type) :: logger

! global_loggerの出力レベルをwarning以上に変更.
call global_logger%configure(level=warning_level)
call logger%configure(level=debug_level)

call logger%log_debug("user logger")

! global_logger%log_informationは出力されない.
call global_logger%log_information("global logger")
call logger%log_information("user logger")

call global_logger%log_warning("global logger")
call logger%log_warning("user logger")

call global_logger%log_error("global logger")
call logger%log_error("user logger")

! 2021-12-04 15:40:38.737: DEBUG: user logger
! 2021-12-04 15:40:38.737: INFO: user logger
! 2021-12-04 15:40:38.738: WARN: global logger
! 2021-12-04 15:40:38.738: WARN: user logger
! 2021-12-04 15:40:38.739: ERROR: global logger
! 2021-12-04 15:40:38.739: ERROR: user logger

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
What you can do with signing up
1