アクセスのノードJS上のウィンストン、モーガン、およびストレージのログを検索



動機
Promyzeで、我々のPAAプロバイダーCleverCloudは、多くの大きな特徴を提供して、ウェブアプリを走らせるアドオンを提供します.しかし、現在アクセスログを管理するための制限があり、ELKスタックのような外部システムにそれらをルーティングします.CleverCloudは、RelticSearchにサーバーログを排出することができますが、解析オプションはありません、そして、我々のログは一つのストリングオブジェクトと考えられます.
我々は、アクセスログを当社のAPIの監視を設定し、我々のトラフィックの統計情報を設定できるように興味を持っている.
このポストでは、ノードのために要求LoggerミドルウェアMorganを使用するつもりです.JSはExpressとの組み合わせで使用するには、アクセスログを作成するには、エラスティックサーチデータベースに送信し、キバナとレポートを可視化します.(良いニュース:CleverCloudで、我々は数秒で弾力検索とKibanaを展開することができます!)
私たちは、この図式が12 factors appの第11の原則に反しているのを知っています、しかし、これは我々によると、公平なトレードオフです.
このポストのすべてのコードは、このポストの終わりに利用できます.

セットアップ急行とモーガン
ここでは、ノードの基本的なセットアップです.このチュートリアルに使用されるJSアプリ.Express、Morgan、およびLogger Framework Winstonをインストールします.
まず最初にモジュールを入力しましょう.
npm install --save express winston morgan 
そして、これはどのように標準的な出力にロガーを書くと、すべての着信要求をログにモーガンを使用して、明示的なサーバーを起動することができます.
const express = require('express');
const http = require('http');
const morgan = require('morgan');
const winston = require('winston');

const logger = winston.createLogger({
    transports: [
        new winston.transports.Console({
            level: 'info',
            json: true
        })
    ]
});

function startServer() {
    const app = express();
    app.use(morgan('combined'));

    app.get('/', (req, res) => {
        logger.info("Hi there !");
        res.status(200).json({});
    });

    const server = http.createServer(app);

    server.listen(3001, () => {
        logger.info("Server listens on 3001");
    });
}

startServer();
.
また、モルガン(' Combined ')形式は標準のApache結合ログ出力ですが、以下の手順で変更します.
すべてが準備ができていることを確認するために、あなたのサーバを走らせ、ping http://localhost:3001を実行すると、端末のログを見ることができます.
> node index.js

{"level":"info","message":"Server listens on 3001"}
::1 - - [08/Oct/2021:10:03:26 +0000] "GET / HTTP/1.1" 304 - "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
モーガンが入って来るクエリのため、最後の行を生成することに注意してください.

モーガンとカスタムロガーを作成する
モルガンでは、我々のクエリをログにカスタム形式を作成することができます.我々のログをエラスティックサーチデータベースに保存するため、JSON形式でアクセスログを作成します.
モーガンのドキュメントは、どのプロパティを取得するかについての詳細を提供します.この例では、文字列化され、アクセスログの基本プロパティを含むJSONオブジェクトを計算します.
const morganJSONFormat = () => JSON.stringify({
        method: ':method',
        url: ':url',
        http_version: ':http-version',
        remote_addr: ':remote-addr',
        response_time: ':response-time',
        status: ':status',
        content_length: ':res[content-length]',
        timestamp: ':date[iso]',
        user_agent: ':user-agent',
    });
app.use(morgan(morganJSONFormat()));
サーバを起動してpingを実行すると、次のログを端末で取得します.
{"method":"GET","url":"/","http_version":"1.1","remote_addr":"::1","response_time":"6.822","status":"304","content_length":"-","timestamp":"2021-10-08T10:04:55.197Z","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"}
しかし、あなたが見ることができるように、我々はまだ我々のウィンストンロガーを使用しません.これは標準出力にのみ書き込まれます.モーガンを設定する必要があります.
app.use(morgan(morganJSONFormat(), {
    'stream': {
        write: (message) => {
            const data = JSON.parse(message);
            return logger.info("accesslog", data);
        }
    }
}));
適切なアクセスログを持っています.
{"content_length":"-","http_version":"1.1","level":"info","message":"accesslog","method":"GET","remote_addr":"::1","response_time":"3.736","status":"304","timestamp":"2021-10-08T10:22:56.767Z","url":"/","user_agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"}

Tip - Cone - 1 :ユーザエージェントの解析
既にユーザーエージェントで動作している場合は、おそらくどのように理解していない知っている.次の例を考えます.
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36". 
私の言いたいことは何ですか?
幸いにも、NPMパッケージua-parser-jsは救出に来ます!このライブラリのおかげで、私たちの複雑なユーザーエージェントを簡単に理解できるプロパティを持つオブジェクトに翻訳することができます.
npm install --save ua-parser-js
このバージョンでは、ブラウザ名とバージョンをキャッチする4つのプロパティを追加するには、OSの名前、およびバージョンに加えて:
//At the top of the file
const userAgentParser = require('ua-parser-js'); 
//...
app.use(morgan(morganJSONFormat(), {
    'stream': {
        write: (message) => {
            const data = JSON.parse(message);
            parseUserAgent(data); //Enrich data
            return logger.info("accesslog", data);
        }
    }
}));

function parseUserAgent(data) {
    if (data.user_agent) {
        const ua = userAgentParser(data.user_agent);
        if (ua.browser) {
            data.user_agent_browser_name = ua.browser.name;
            data.user_agent_browser_version = ua.browser.major || ua.browser.version;
        }
        if (ua.os) {
            data.user_agent_os_name = ua.os.name;
            data.user_agent_os_version = ua.os.version;
        }
    }
}

ヒント- 1 - 2 : URLを消毒する
APIをどのように設計したかによって、URLに識別子を使用することができます.この文脈では、次のような呼び出しを受ける可能性があります.
/api/product/10
/api/product/101
/api/product/1001
しかし、監視目的のために単一の1/API/product/idの下にこれらのルートをすべて集めたいです.
ここでは、URLを消毒する方法の簡単な例を示します.
app.use(morgan(morganJSONFormat(), {
    'stream': {
        write: (message) => {
            const data = JSON.parse(message);
            parseUserAgent(data);
            sanitizeUrl(data);
            return logger.info("accesslog", data);
        }
    }
}));

function sanitizeUrl(data) {
    if (!data.url) {
        return;
    }
    const regex = /\/[0-9]+/g; //Adapt to your context
    const urlWithoutParameter = data.url.replace(regex, '/:id');
    data.url_sanitized = urlWithoutParameter;
}

Tip - no - 3 - 3 :プロキシを実行する際にリモートIPを取得する
あなたのAPIがNGinxまたはどんなプロキシの後ろにも呼ばれるならば、あなたが元のソースIPを得ないので、あなたはRemoteRadioAddr属性で問題に遭遇するかもしれません、しかし、プロキシのもの.あなたのプロキシの構成がそれを許すならば、あなたはちょうどこの情報にアクセスするためにリクエストヘッダーX-Forwarded-Forを使うことができます.このようにしてコードを更新しました.
const morganJSONFormat = () => JSON.stringify({
        method: ':method',
        url: ':url',
        http_version: ':http-version',
        remote_addr: ':remote-addr',
        remote_addr_forwarded: ':req[x-forwarded-for]', //Get a specific header
        response_time: ':response-time',
        status: ':status',
        content_length: ':res[content-length]',
        timestamp: ':date[iso]',
        user_agent: ':user-agent',
    });

ログを送信する
このステップについては、別のNPMモジュールが救助に来る.このモジュールは、弾性検索用のWinstonトランスポートを提供します.我々は、我々のロガーのための新しいトランスポーターを含むように我々のコードを更新する必要があります:
const winstonElasticsearch = require('winston-elasticsearch');
//... 
const esTransportOpts = {
    level: 'info',
    indexPrefix: 'logging-api',
    indexSuffixPattern: 'YYYY-MM-DD',
    clientOpts : {
        node: process.env.ES_ADDON_URI,
        maxRetries: 5,
        requestTimeout: 10000,
        sniffOnStart: false,
        auth: {
            username: process.env.ES_ADDON_USER,
            password: process.env.ES_ADDON_PASSWORD
        }
    },
    source: process.env.LOG_SOURCE || 'api'
};
const esTransport = new winstonElasticsearch.ElasticsearchTransport(esTransportOpts);

const logger = winston.createLogger({
    transports: [
        new winston.transports.Console({
            level: 'info',
            json: true
        }),
        esTransport //Add es transport
    ]
});
clientoptsは設定に依存します.認証がなければ、必要はないかもしれません.
winston-elasticsearch
結果
それだ!
Kibanaでインデックスを設定した後、ログを視覚化できます.

あなたが見てみたいなら、コードの最終版はで公的に利用できます!