Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What is going on with this article?
@yun_bow

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

はじめに

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に公開しています。
- ソースファイルはこちら

以上です。

2
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
yun_bow
サービス志向エンジニアです。プログラミングを使ったモノづくりが好きです。AWS、Python、GO言語を勉強中。 こちらで投稿した記事は、所属会社の公式見解を示すものではないです。
pa-rk
Webアプリ、スマホアプリの開発を手掛ける技術者集団です。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
2
Help us understand the problem. What is going on with this article?