Javaでファイルにログ出力

#はじめに
定期的に発生するJavaネタ。ある情報をログ出力することになったが、データベースに出力するか、ファイルに出力するか検討した結果、ファイルにログを出力することにしたので事前に検討した時のメモ。前提はLinux。

#ファイル出力のメリット
観点は全くバラバラだが、思いつくままに列挙

- データベースに比べて格納領域のオーバヘッドが少ないため容量を食わない 
- ログが大量になる場合に、データの分割を検討する必要があるが、データベースの場合はテーブルを作成する処理等の実装が面倒だが、ファイルであれば新たにファイルを作成するだけなのでプログラムが容易
- ログを参照する場合、データベースであればログインしてSQLを叩かないと見えないが、ファイルの場合はmore/lessで簡単に参照できる。
- データベースの場合、データベースのバックアップへの影響(処理時間等）を追加で検討する必要があるが、ファイルの場合はシステム全体のバックアップのみ考慮すればよい。

#ファイル出力のデメリット
- 排他処理を適切に行わないと、ファイルが壊れたりする。
- 検索を行う場合、インデックスがないため性能を出すための工夫が必要
- 検索を行う場合、SQLが使えないため、自分で検索ロジックを作りこむ必要がある


#ソース
ということでファイルに時間、テキストからなる1行ログを出力するプログラムを作ってみた。ファイルは月別に生成するようにした。
排他制御として、synchronizedメソッドを使う方法が簡単そうなので、この方法にした。
もちろん、このメソッド以外でログファイルの操作をしないことは大前提だ。

```LogWriter.java
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Calendar;
import java.io.*;
public class LogWriterNew{

    public static SimpleDateFormat sdf = new SimpleDateFormat("YYYY/MM/dd HH:mm:ss");

    public static synchronized void writeLog(String text){
        Calendar calendar = Calendar.getInstance();

        String OUTPUT_DIR= "out";

        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH) + 1;
        Date date = calendar.getTime();

        String yearStr = String.format("%04d", year);
        String monthStr = String.format("%02d", month);

        //　ログ出力
        String file_name = OUTPUT_DIR + File.separator + yearStr + "_" + monthStr + ".log";
        File file = new File(file_name);
        FileWriter fw = null;
        String line = sdf.format(date) + "," +  text;
        try{
            fw = new FileWriter(file, true);
            fw.write(line + "\n");
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            if(fw != null) {
                try {
                    fw.close();
                }catch(Exception e2) {
                    e2.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] argv){

        for(int i=0; i<1000;i++){
            System.out.println(i);
            writeLog(i + "aaa");
        }
    }
}
```

#出力
こんな感じ。

```
2020/05/23 08:25:02,0aaa
2020/05/23 08:25:02,1aaa
2020/05/23 08:25:02,2aaa
2020/05/23 08:25:02,3aaa
2020/05/23 08:25:02,4aaa
2020/05/23 08:25:02,5aaa
2020/05/23 08:25:02,6aaa
2020/05/23 08:25:02,7aaa
2020/05/23 08:25:02,8aaa
2020/05/23 08:25:02,9aaa
2020/05/23 08:25:02,10aaa
・・・・
```

#処理時間(参考)
小職のPCでは、```850件/秒``` のスループットが得られた。

#追記(2020/5/24)
FileWriterでは文字コード指定できないため、OutputStreamWriterを使って文字コード指定できるようにしてみた。


```java
 // ログ出力メソッド
    public static synchronized boolean writeLog(String fileName, String date, String[] items){

        for(int i=0; i<items.length; i++){
            items[i] = escape(items[i]);
        }
        String line = date + "," + String.join(",", items);

        File file = new File(fileName);
        PrintWriter pw   = null;
        try {
            pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), "Windows-31j")));
            pw.println(line);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (pw != null) {
                try {
                    pw.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                    return false;
                }
            }
        }

```

#おわりに
次回は、このsyncronizedで本当に排他制御がされるのか検証する。