5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FortranAdvent Calendar 2021

Day 9

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

Last updated at Posted at 2021-12-08

概要

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
5
1
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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?