5
3

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 3 years have passed since last update.

[コピペでOK] GoogleAppsScriptでログを出力する

Last updated at Posted at 2020-05-24

はじめに

GoogleAppsScript(GAS)でconsoleのスプレッドシートシート出力機能を作成しました。GASではLoggerconsoleなどのログ表示機能が備わっておりますが、ここでは使用しません。コピペですぐに使えるものを目指しています。

機能としては下記の通りです。

  • ログレベル
  • 出力先指定

使い方

使い方は下記の通りです。

main.gs
function main() {
    try {
        console.error('log: error.');
        console.warn('log: warn.');
        console.info('log: info.');
        console.debug('log: debug.');
        console.log({a:"aaa",b:1,c:{d:2},d:[3,4,5]});
        throw new Error('log: throw error');
    } catch (e) {
        console.error(e.stack);
    }
}

スプレッドシートにログが下記のように出力されます。

timestamp level path message
2020-05-24 21:59:53:916 ERROR src/main(3) log: error.
2020-05-24 21:59:54:203 WARN src/main(4) log: warn.
2020-05-24 21:59:54:540 INFO src/main(5) log: info.
2020-05-24 21:59:54:938 DEBUG src/main(6) log: debug.
2020-05-24 21:59:55:207 DEBUG src/main(7) {a:"aaa",b:1,c:{d:2},d:[3,4,5]}
2020-05-24 21:59:55:452 ERROR src/main(10) Error: log: throw error at main (src/main:8:15)

ログファイル出力の実装

  1. GoogleDriveで「Google スプレッドシート」を新規作成する。スプレッドシードのURLhttps://docs.google.com/spreadsheets/d/*****/edit?*****の部分がIDになる。
  2. GoogleDriveで「Google Apps Script」を新規作成する。*.gsファイルに以下のJavaScriptコードを実装する。

JavaScriptの実装

log.gs
let log = {}; {
    const config = {
        SSID_LOG: '*****', // ※ログ用のスプレッドシートのIDを指定する
        SSN_LOG: '*****', // ※ログ用のスプレッドシートのシート名を指定する
        IS_LOGFILE: true, // ログフラグ true=ログファイル出力あり/false=なし
        LOG_LEVEL: 3, // ログレベル 0=ERROR/1=WARN/2=INFO/3=DEBUG
    };

    const logLevel = { // ログレベル
        ERROR: 0,
        WARN: 1,
        INFO: 2,
        DEBUG: 3,
    };

    let spreadsheet = SpreadsheetApp.openById(config.SSID_LOG);
    let sheet = spreadsheet.getSheetByName(config.SSN_LOG);

    /**
     * 初期化
     */
    log.init = () => {
        if (config.IS_LOGFILE) {
            overrideConsole();
        }
    }

    /**
     * console関数を上書きする
     */
    let overrideConsole = () => {
        console = {};
        console.error = (...args) => {
            if (logLevel.ERROR <= config.LOG_LEVEL) {
                out('ERROR', args);
            }
        }
        console.warn = (...args) => {
            if (logLevel.WARN <= config.LOG_LEVEL) {
                out('WARN', args);
            }
        }
        console.info = (...args) => {
            if (logLevel.INFO <= config.LOG_LEVEL) {
                out('INFO', args);
            }
        }
        console.debug = (...args) => {
            if (logLevel.DEBUG <= config.LOG_LEVEL) {
                out('DEBUG', args);
            }
        }
        console.log = (...args) => {
            if (logLevel.DEBUG <= config.LOG_LEVEL) {
                out('DEBUG', args);
            }
        }
        console.ws = null;
    }

    /**
     * ログを出力する
     * @param {String} level ログレベル
     * @param {String} msg メッセージ
     */
    let out = (level, msg) => {

        let callerInfo = {};
        let tmpPrepareST = Error.prepareStackTrace;
        Error.prepareStackTrace = (e, stack) => {
            let caller = stack[1];
            return {
                file: caller.getFileName(),
                line: caller.getLineNumber(),    
            }
        };
        Error.captureStackTrace(callerInfo, out);
        let file = callerInfo.stack.file;
        let line = callerInfo.stack.line;
        Error.prepareStackTrace = tmpPrepareST;

        let timestamp = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss:SSS');
        let message = convertMsg(msg);

        sheet.appendRow([timestamp, level, `${file}(${line})`, message]);
    }

    /**
     * ログメッセージを変換する
     * @param {Array} arg メッセージ
     */
    let convertMsg = (arg) => {
        let msg = '';
        for (let i = 0; i < arg.length; i++) {
            msg += ' ' + dumpObject(arg[i]);
        }
        return msg;
    }

    /**
     * オブジェクトを出力する
     * @param {Object} obj 
     */
    let dumpObject = (obj) => {
        let v = '';
        let t = typeof obj;
        switch (t) {
            case 'number':
            case 'boolean':
                v = obj;
                break;
            case 'string':
                v = '"' + obj + '"';
                break;
            case 'object':
                if (isArray(obj)) {
                    v = '[';
                    let count = 0;
                    for (let value of obj) {
                        v += dumpObject(value);
                        count++;
                        if (count < obj.length) {
                            v += ',';
                        }
                    }
                    v += ']';
                } else if (isObject(obj) || isError(obj)) {
                    v = '{';
                    let count = 0;
                    let nameList = Object.getOwnPropertyNames(obj);
                    for (let key of nameList) {
                        let ret = dumpObject(obj[key]);
                        if (ret) {
                            v += key + ':' + ret;
                            count++;
                            if (count < nameList.length) {
                                v += ',';
                            }
                        }
                    }
                    v += '}';
                } else if (isString(obj) || isDate(obj)) {
                    v = '"' + obj.toString() + '"';
                } else if (isNumber(obj) || isBoolean(obj)) {
                    v = obj.valueOf();
                } else if (isFunction(obj)) {
                    v = '<fuction>';
                } else if (isNull(obj)) {
                    v = '<null>';
                } else if (isUndefined(obj)) {
                    v = '<undefined>';
                }
                break;
            case 'function':
                v = '<function>';
                break;
            case 'symbol':
                v = '<symbol>';
                break;
            case 'undefined':
                v = '<undefined>';
                break;
        }
        return v;
    }

    let isArray = (obj) => {
        return Object.prototype.toString.call(obj) === '[object Array]';
    }

    let isBoolean = (obj) => {
        return Object.prototype.toString.call(obj) === '[object Boolean]';
    }

    let isDate = (obj) => {
        return Object.prototype.toString.call(obj) === '[object Date]';
    }

    let isError = (obj) => {
        return Object.prototype.toString.call(obj) === '[object Error]';
    }

    let isNumber = (obj) => {
        return Object.prototype.toString.call(obj) === '[object Number]';
    }

    let isObject = (obj) => {
        return Object.prototype.toString.call(obj) === '[object Object]';
    }

    let isString = (obj) => {
        return Object.prototype.toString.call(obj) === '[object String]';
    }

    let isFunction = (obj) => {
        return Object.prototype.toString.call(obj) === '[object Function]';
    }

    let isNull = (obj) => {
        return Object.prototype.toString.call(obj) === '[object Null]';
    }

    let isUndefined = (obj) => {
        return Object.prototype.toString.call(obj) === '[object Undefined]';
    }
}
log.init();

参考リンク

さいごに

ソースコードをGitHubに公開しています。

以上です。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?