概要
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
以外に,モジュール名,手続名を指定する引数module
,procedure
も存在しています.引数を指定すると,"タイムスタンプ: モジュール名 % 手続名: 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"
に書き込まれます.
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"
に書き込まれます.
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"
の内容も更新されます.
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"
は出力されていません.
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
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