製品におけるログ・トレースは、ITエンジニア(設計者、開発者)にとって生命線です。
社内のテストのときも有効ですし、社外で不良が出たときも迅速に対処するためにとても必要なことです。
ログ・トレースをどうやって出力すべきかを説明します。C++で書いていますが、他言語でもほぼ同様と思います。
ログ・トレース
プロジェクトや人によってログと言ったりトレースと言ったりしますが、文字列をどこかに出力することは製品を強くするために、とても重要です。
何をどうやって出力するかは、ITエンジニア(設計者、開発者、プログラマ)にとっての腕の見せ所です。ログ・トレース出力をちゃんとできない人はプロではありません。システマティックにログ・トレースを出力する方法をぜひ身に着けてください。
この記事ではログ・トレースの出力方法をC++を例に説明します。
プロジェクトの規模によって(任された仕事の大きさによって)、使い分けてください。
- 関数内に直接書く
- 関数化する
- メンバー関数にする
- クラスにする
- クラスを進化させる
関数内に直接書く
ログ・トレースの最初の発想です。
テストで原因不明の不良があったとします。いつも発生するわけではなく、たまに発生します。不良発生時に何が起きているかを確かめるため、何らかの情報を出力しておこう、というのがログ・出力の動機のひとつです。
まずは関数内に直接べたべたと書いてみましょう。以下では標準出力に出しています。こういうデバグのやり方をprintfデバグと言います。もしかしたら死語かもしれませんが、C言語で標準出力に出すのがprintfだからです。
void test1(void){ // 何かやっている std::cout << "Log trace test" << std::endl; // 何かやっている return; }
関数化する
ログ・トレースの出力は、syslogに出したり、ファイルに出力したりといろいろです。
また、出力内容全体に入れたい文字列などが出てきたりするので、関数化するのは自然です。
以下のような関数を作っておいて、好きなときに呼び出すことにします。
void trace_func(std::string& msg){ std::cout << msg << std::endl; return; }
メンバー関数にする
クラスの中のメンバー関数としてログ・トレース出力用関数を持っておくのはよい発想です。
private: void myTrace(std::string& msg){ std::cout << msg << std::endl; return; }
クラスにする
ログ・トレースについて、レベル分けをしたいとか、ときどきによってメッセージ内容を変えたいという欲求がそのうち出てきます。
上記のようにクラスの中に関数を作る感じにするとクラスを作るごとにメンバ関数を用意しなければならず面倒です。トレースを出力する専用のクラスを作成します。
こういうのはセンスというか好みですが、私はいつもIndicatorというクラスを作って、error(), warn(), info(), debug()という関数を作っています。
※私は関数名でログレベルを表すのが好きですが、引数でやりたい人もいるでしょうし、クラス名でError、Warnと分けたい人もいると思います。お好みでどうぞ。
class Indicator { public: void error(std::string& msg){ std::string m = "ERROR " + msg; myTrace(m); return; } void warn(std::string& msg){ std::string m = "WARNING " + msg; myTrace(m); return; } void info(std::string& msg){ std::string m = "INFO " + msg; myTrace(m); return; } void debug(std::string& msg){ std::string m = "DEBUG " + msg; myTrace(m); return; } private: void myTrace(std::string& msg){ std::cout << msg << std::endl; return; } };
こうやっておくと、後で修正するのが楽になります。全体のトレースで追加したい情報があるならmyTrace()に追加すればよいし、デバグ時にだけ出したいトレースはdebug()に追加すればよいです。
あるいはエラー時だけsyslog出力したい、他はファイルのみという欲求にも簡単に応えられます。
クラスを進化させる
トレース設計というのはクラス設計の練習問題のような感じですので、用途や好みに応じてよいと思うクラスを作るのが、熟練のエンジニアです。
一例として、上記のクラスを進化させてみます。ご自身でよくトレースがどうあるべきかを考えるのは楽しいですよ。
まず、モジュール名を出すのは必須と思います。
private: void myTrace(std::string& msg){ std::cout << "MYMODULE " << msg << std::endl; return; }
トレースIDみたいなもの、このトレースはここを通ったときに出力される、というのがあると便利ですので全インタフェースにIDを追加します。
class Indicator { public: void error(int id, std::string& msg){ std::string m = "ERROR " + msg; myTrace(id, m); return; } void warn(int id, std::string& msg){ std::string m = "WARNING " + msg; myTrace(id, m); return; } void info(int id, std::string& msg){ std::string m = "INFO " + msg; myTrace(id, m); return; } void debug(int id, std::string& msg){ std::string m = "DEBUG " + msg; myTrace(id, m); return; } private: void myTrace(int id, std::string& msg){ std::cout << "MYMODULE " << id << " " << msg << std::endl; return; } };
トレースID自体の定義はconstかenum classで持っておきましょう。決して即値を書かないように。
以下はTraceIDというenum classを作った例です。Indicatorの関数のidの型も変更しておくと、引数にTraceIDを強制するのでよい設計と言えます。
enum class TraceID { SOMEID = 1, SOME_FUNC_1_start, SOME_FUNC_1_finish,//以下、どんどん増やす }; class Indicator { public: void error(TraceID id, std::string& msg){ std::string m = "ERROR " + msg; myTrace(id, m); return; } void warn(TraceID id, std::string& msg){ std::string m = "WARNING " + msg; myTrace(id, m); return; } void info(TraceID id, std::string& msg){ std::string m = "INFO " + msg; myTrace(id, m); return; } void debug(TraceID id, std::string& msg){ std::string m = "DEBUG " + msg; myTrace(id, m); return; } private: void myTrace(TraceID id, std::string& msg){ std::cout << "MYMODULE " << (int)id << " " << msg << std::endl; return; } };
どこまで抽象化するかはいつも悩みます。
モジュール名をコンストラクタに入れて各機能に継承するようにすると全モジュールで使えます。もし、全体設計をする立場にいるのでしたらログ・トレースの専門モジュールを作るのもありですね。
まとめ
ITエンジニアが知っておくべきログ・トレースの出力方法を説明しました。
ログ・トレース出力するクラスを作ってシステマティックに出力してください。きっと幸せになれると思います。
ログ・トレースを上手に出力できるというのは、間違いなく熟練である証です。転職のときなどにアピールするのもありです。
コメント