package jp.co.y_net.amm.common;

import org.slf4j.LoggerFactory;

/**
 * 標準のロギングクラス
 * 
 * エラーレベルは logback.xml の LEVEL_APP にて定義される。
 * 　・通常運用中は ERROR レベル
 * 　・調査中は INFO レベル
 * 　とする。
 * 
 * ■ 各開発者による利用方法
 *  debug, info, error のいずれかのメソッドを使用する。詳しくは、各メソッドのコメントを参照のこと。
 *  
 * ■ アプリケーションアーキテクチャからの利用
 *  アプリケーションのエラーが発生したときは、WicketApplicationクラスから、RequestCycleListenerを経由して呼び出される。
 *  
 * ■ 利用のメリット
 * 　・サーバーの catalina.out, catalina.err にアプリケーション固有のエラーが出力されなくなり、
 * 　　/var/log/app/tomcat/～/logs/AppLog に出力され、世代管理される。
 * 　・ログのレベルを指定できるため、開発時に記述したデバッグメッセージをわざわざ消さなくても良くなる。
 * 　・logbackを使用しているので、運用中にログ出力レベルの変更が可能。
 *  
 * K.inaba 2014/02/01
 */
public class AppLogger {
    private static boolean isLocal;
    private static org.slf4j.Logger loggerSlf = LoggerFactory.getLogger(AppLogger.class.getName());
    
    static {
        /*
         * 開発者担当者のOSのユーザ名を列挙する。
         * ここに記載がある場合に限り、ローカル開発環境とみなして、標準出力（Eclipseのコンソールなど）へ
         * 出力を行う。
         */
        String debugKeys = ResourceReader.getStringQuick("debug.users");
        String[] arrayDebugKey;
        if(debugKeys == null) {
            arrayDebugKey = new String[]{};
        } else {
            arrayDebugKey = debugKeys.split(",");
        }
        for(String key: arrayDebugKey) {
            if (key.trim().equals(System.getenv("USERNAME"))) {
                isLocal = true;
                break;
            }
        }
    }
    

    public static void main(String[] args) throws InterruptedException {
        sample();
    }
    /** 利用方法のサンプル */
    private static void sample() {
        String str = "sundata";
        AppLogger.debug("変数 str の値 = " + str);
        
        /*
         * Infoレベルは運用中に必要となるかもしれないレベルの出力を担う。
         * 通常は表示されない。(開発者のコンソールには表示される。)
         * 運用中に詳細なログが必要となった時に、「LEVEL_APP」property の設定を INFO にすること。
         */
        AppLogger.info("Infoレベルの出力。");
        AppLogger.info(new Exception("例外クラスにも対応"));
        
        /*
         * Errorレベルは必ずログに出力される。
         * Try ～ Catch で活用する。
         */
        AppLogger.error("Errorレベルの出力。");
        AppLogger.error(new Exception("例外クラスにも対応"));
        try {
            int[] i = new int[]{1, 2, 3};
            if (i[3] == 4) {
                return;
            }
        } catch (Exception e) {
            AppLogger.error("配列の操作に失敗", e);
        }
    }
    public static boolean isDebug() {
        return isLocal;
    }
    /**
     * 開発時のみ出力したいログを出力する。
     * → ローカル環境の標準出力（Eclipseのコンソールなど）にのみ出力される。
     * 
     * @param message 出力する文字列
     */
    public static void debug(String message) {
        if (isLocal) System.out.println("[DEBUG] " + toPlace(getFromStack()) + "\n" + toString(message));
    }
    /**
     * 何らかのエラーが発生した時に、調査の参考となるログを出力する。
     * → logback.xml LEVEL_APP が INFO であれば、/var/log/app/tomcat/～/logs/AppLog に出力される。
     * → ローカル環境の標準出力（Eclipseのコンソールなど）にも出力される。
     *
     * 【ログの出力先】
     *   Eclipse ： プロジェクト直下 sdc/logs/AppLog.yyyy-mm-dd
     *   Linux：%Tomcatインスタンス%/logs/AppLog.yyyy-mm-dd
     * 
     * @param message 出力する文字列
     */
    public static void info(String message) {
        if (isLocal) System.out.println("[INFO ] " + toPlace(getFromStack()) + "\n" + toString(message));
        loggerSlf.info(toPlace(getFromStack()) + "\n" + toString(message));
    }
    /**
     * @see AppLogger#info(String)
     * @param exception 出力する例外クラス
     */
    public static void info(Throwable exception) {
        if (isLocal) System.out.println("[INFO ] " + toPlace(getFromStack()) + "\n" + toString(exception));
        loggerSlf.info(toPlace(getFromStack()) + "\n" + toString(exception));
    }
    /**
     * @see AppLogger#info(String)
     * @param message 出力する文字列
     * @param exception 出力する例外クラス
     */
    public static void info(String message, Throwable exception) {
        if (isLocal) System.out.println("[INFO ] " + toPlace(getFromStack()) + "\n" + toString(message));
        if (isLocal) System.out.println("[INFO ] " + toPlace(getFromStack()) + "\n" + toString(exception));
        loggerSlf.info(toPlace(getFromStack()) + "\n" + toString(message));
        loggerSlf.info(toPlace(getFromStack()) + "\n" + toString(exception));
    }
    /**
     * Try ～ Catch などで使用する。System.err.println の代用。
     * @param message 出力する文字列
     */
    public static void error(String message) {
        if (isLocal) System.out.println("[ERROR] " + toPlace(getFromStack()) + "\n" + toString(message));
        loggerSlf.error(toPlace(getFromStack()) + "\n" + toString(message));
    }
    /**
     * Try ～ Catch などで使用する。e.printStackTrace();の代用
     * @param exception 出力する例外クラス
     */
    public static void error(Throwable exception) {
        if (isLocal) System.out.println("[ERROR] " + toPlace(getFromStack()) + "\n" + toString(exception));
        loggerSlf.error(toPlace(getFromStack()) + "\n" + toString(exception));
    }
    /**
     * @param message 
     * @param exception 
     */
    public static void error(String message, Throwable exception) {
        if (isLocal) System.out.println("[ERROR] " + toPlace(getFromStack()) + "\n" + toString(message));
        if (isLocal) System.out.println("[ERROR] " + toPlace(getFromStack()) + "\n" + toString(exception));
        loggerSlf.error(toPlace(getFromStack()) + "\n" + toString(message));
        loggerSlf.error(toPlace(getFromStack()) + "\n" + toString(exception));
    }
    
    
    private static StackTraceElement getFromStack() {
        return new Throwable().getStackTrace()[2]; // 必ず呼び出し元から2階層目でこのメソッドを呼ぶこと
    }
    private static String toPlace(StackTraceElement stack) {
        return stack.getClassName() + "#" + stack.getMethodName() + "("
                + stack.getFileName() + ":" + stack.getLineNumber() + ")";
    }
    private static String toString(Object message) {
        String log;
        if (message instanceof Throwable) {
            log = "    " + readStackTrace(new StringBuilder(), (Throwable) message, 1, null).toString();
        } else if (message == null) {
            log = "    " + null;
        } else {
            log = "    " + message.toString();
        }
        return log;
    }
    /**
     * Exceptionの解析ツール
     * @param sb
     * @param throwable
     * @param filter フィルター (スタックトレースの内、指定された文字列以外は無視する。全て有効とする場合はnullを指定。)
     */
    private static StringBuilder readStackTrace(StringBuilder sb, Throwable throwable, int token, String filter) {
        if(sb == null) sb = new StringBuilder();
        final String LINE_SEP = System.getProperty("line.separator");
        try {
            String spacer = "";
            for (int i = 0; i < token; i++) {
                spacer += " ";
            }
            sb.append(spacer + throwable.getClass().getName() + ":" + throwable.getMessage() + LINE_SEP);
            StackTraceElement[] se = throwable.getStackTrace();
            for (int i = 0; i < se.length; i++) {
                String trace = se[i].toString();
                if(filter == null
                        || (filter != null && trace.startsWith(filter))) {

                        sb.append(spacer + "    " + trace + LINE_SEP);
                }
            }
            if (throwable.getCause() != null) {
                /* 再帰呼び出し */
                readStackTrace(sb, throwable.getCause(), token + 1, filter);
            }
        } catch (Exception e) {
            /* Exception解析でのエラーは無視することで、無限ループを回避 */
            e.printStackTrace();
        }
        return sb;
    }
}