◆ デフォルトではログにタイムスタンプは一切なし
◆ ドキュメントにはない timestamp オプションに "true" を指定すれば一部はタイムスタンプが付く
  ◆ forever のシステムログ部分だけで実行するスクリプトの出力にはつかない
◆ 方法1 : 実行するスクリプトの最初で console.log を置き換えてタイムスタンプ付きにする
◆ 方法2 : forever を改造する
  ◆ forever-monitor の logger プラグインでタイムスタンプを追加する Transform を pipe に追加
◆ 方法3 : 出力にタイムスタンプをつけるだけのプロセスを間に挟む
  ◆ forever start ではそのラッパースクリプトを実行する
  ◆ ラッパースクリプトで本来のスクリプトを実行して stdout/stderr を変換
  ◆ タイムスタンプを追加してから process.stdout などに pipe する

タイムスタンプつけれるのは一部だけ

forever ではログにタイムスタンプがついていません
ログという以上 タイムスタンプは必須だと思うんです

というわけで探してみると こんな issue がありました
https://github.com/foreversd/forever/issues/309

対応してるぽいですね

forever set timestamp true

を実行すれば良いみたいです
修正はマージされてるのに Readme のドキュメントには書いてない隠し機能みたいなものです

set コマンドを使うと設定は文字列でセットされます
それに合わせてソースでも 「=== 'true'」 という条件になってるので config.json を直接編集する場合は文字列型で指定しないといけません
https://github.com/foreversd/forever/blob/1.0.0/lib/forever.js#L322

使ってみると

2019-07-25T10:32:14.543Z - error: Forever detected script exited with  code: 1
2019-07-25T10:32:14.551Z - error: Script restart attempt #1

ちゃんとタイムスタンプが入ってます……一部だけ
マージされた修正を見ればわかりますが タイムスタンプを追加してるのは forever.logEvents で console.error していたものだけです
https://github.com/foreversd/forever/pull/380/files

forever のシステムログだけで ユーザが console.log したものは含まれません
別に logger を作らず forever のログ機能だけで完結させたい場合はこれをどうにかしたいものです

console.log を置き換える

一番簡単なのは console.log を置き換えるものです
forever で起動するスクリプトの最初に console.log を置き換えて 渡されたデータの前にタイムスタンプを出力するようにすればいいです

探してみるとこういうパッケージがありました
https://www.npmjs.com/package/log-timestamp

console.log 自体を置き換えてしまうものです
パッケージを入れてもいいですが この程度なら毎回自分で書いても時間はかかりません

const consolelog = console.log
console.log = (...a) => consolelog(new Date(), ...a)

console.log を実行するとこういう表示になります

> console.log(1, "a", {a: 1})
2019-07-25T10:36:31.880Z 1 a { a: 1 }

console はグローバル変数なので require の前に行っておけばライブラリの出力にもタイムスタンプが付きます
console を置き換えず独自の console.log ラッパーを作った場合は自分が出力する部分はいいですが ライブラリが出力する部分は対応できません

forever を拡張する

デーモンじゃなくなる方法

start コマンド実行時のログの保存は startDaemon 関数で monitor プロセス起動時に行われています
https://github.com/foreversd/forever/blob/1.0.0/lib/forever.js#L433

指定したログファイルを open したファイルディスクリプタを子プロセスの stdout と stderr に設定してます

  outFD = fs.openSync(options.logFile, 'a');
errFD = fs.openSync(options.logFile, 'a');
monitorPath = path.resolve(__dirname, '..', 'bin', 'monitor');

monitor = spawn(process.execPath, [monitorPath, script], {
stdio: ['ipc', outFD, errFD],
detached: true
});

単純にここでタイムスタンプを追加する変換を通すようにすればいけそうなのでこう変更しました

const stream = require("stream")
class AddTsTransform extends stream.Transform {
_transform(chunk, enc, cb) {
const transformed = chunk.toString().replace(/\n/g, () => "\n" + new Date().toISOString() + " ")
this.push(transformed)
cb()
}
}

monitorPath = path.resolve(__dirname, '..', 'bin', 'monitor');

monitor = spawn(process.execPath, [monitorPath, script], {
stdio: ['ipc', "pipe", "pipe"],
detached: true
});

monitor.stdout.pipe(new AddTsTransform()).pipe(fs.createWriteStream(options.logFile, {flags: 'a'}))
monitor.stderr.pipe(new AddTsTransform()).pipe(fs.createWriteStream(options.logFile, {flags: 'a'}))

実行すると全部の行にタイムスタンプが追加できました

2019-07-25T11:38:56.161Z /tmp/forev/x.js:1
2019-07-25T11:38:56.164Z setTimeout(() => {console.log("OK");throw new Error()}, 1000*10)
2019-07-25T11:38:56.164Z ^
2019-07-25T11:38:56.164Z
2019-07-25T11:38:56.164Z Error
2019-07-25T11:38:56.164Z at Timeout._onTimeout (/tmp/forev/x.js:1:43)
2019-07-25T11:38:56.164Z at listOnTimeout (internal/timers.js:531:17)
2019-07-25T11:38:56.164Z at processTimers (internal/timers.js:475:7)
2019-07-25T11:38:56.164Z 2019-07-25T11:38:56.166Z - error: Forever detected script exited with code: 1
2019-07-25T11:38:56.167Z 2019-07-25T11:38:56.173Z - error: Script restart attempt #2

timestamp の設定がそのままだったので二重になってる部分がありますが timestamp の設定を外せば消えます

これでうまくいった……と思ってたのですけど よく見ると start コマンドを使ったのに forever コマンドが終わっていません
フォアグラウンドで動いたままです
よく考えてみれば これまでの処理はファイルをオープンして FD を設定するだけだったので forever コマンドのプロセスはすぐに終わりましたが このプロセス上で stream を監視して変換する処理があるといつまで経っても終わりません

デーモンのままにする

monitor プロセスを起動するところで直接設定するのは難しそうです

指定したスクリプトの標準入力がどうやって monitor プロセスの標準出力につながってるのかを調べてみると forever-monitor の logger プラグインで処理がありました
https://github.com/foreversd/forever-monitor/blob/master/lib/forever-monitor/plugins/logger.js#L65

startLog 関数の中がこうなっています

      if (!monitor.silent) {
process.stdout.setMaxListeners(0);
process.stderr.setMaxListeners(0);
monitor.child.stdout.pipe(process.stdout, { end: false });
monitor.child.stderr.pipe(process.stderr, { end: false });
}

if (monitor.stdout) {
monitor.child.stdout.pipe(monitor.stdout, { end: false });
}

if (monitor.stderr) {
monitor.child.stderr.pipe(monitor.stderr, { end: false });
}

child が実行するスクリプトなのでその stdout と stderr を pipe して monitor プロセスの stdout と stderr に流しています
monitor.stdout は -o が指定されていた場合に fs.createWriteStream で作ったストリームが入っています
monitor.stderr は -e 版です
ここのストリームにさっきの変換する処理を入れるとよさそうです

monitor.child.stdout.pipe(new AddTsTransform()).pipe(process.stdout, { end: false });
monitor.child.stderr.pipe(new AddTsTransform()).pipe(process.stderr, { end: false });

こう置き換えます
-o や -e のログにも必要なら下の if 文も同じように pipe を使います

結果こうなりました

out
2019-07-26T14:30:11.729Z err
2019-07-26T14:30:11.730Z out
2019-07-26T14:30:15.732Z err
2019-07-26T14:30:15.732Z out
2019-07-26T14:30:19.735Z

一応うまくは行ってますが 変換の仕組みが改行を置換するものなので 最初にはタイムスタンプがつかないのと 最後の改行で先にタイムスタンプが入ってしまうので 時間があくと正しくないタイムスタンプになってしまいます
先にタイムスタンプが出力されて その後の本文はタイムスタンプが出力されて 2 日後に出力されました とかじゃタイムスタンプの意味ないです

タイムスタンプと本文が同じタイミングで出るように

なので Transform クラスをちょっと修正しました

const stream = require("stream")
class AddTsTransform extends stream.Transform {
constructor() {
super()
this.line_start = true
}

_transform(chunk, enc, cb) {
const str = chunk.toString()
const datestr = new Date().toISOString()

const last_lf = str.endsWith("\n")
let transformed = (last_lf ? str.slice(0, -1) : str).replace(/\n/g, "\n" + datestr + " ")

if (this.line_start) transformed = datestr + " " + transformed
if (last_lf) transformed += "\n"
this.line_start = last_lf

this.push(transformed)
cb()
}
}

これでタイムスタンプと本文が同じタイミングで出力できるようになりました

2019-07-26T14:46:25.871Z out
2019-07-26T14:46:25.873Z err
2019-07-26T14:46:29.873Z out
2019-07-26T14:46:29.873Z err
2019-07-26T14:46:33.877Z out
2019-07-26T14:46:33.877Z err
2019-07-26T14:46:37.880Z out

timestamp も必要

この forever-monitor を使う方法では 実行するスクリプトの出力はタイムスタンプ付きにできます
しかし monitor プロセス自体が出力する部分は対象外です

2019-07-26T16:41:20.468Z out
2019-07-26T16:41:20.470Z err
2019-07-26T16:41:20.470Z /root/nodetest/x.js:4
2019-07-26T16:41:20.470Z throw new Error("KK")
2019-07-26T16:41:20.470Z ^
2019-07-26T16:41:20.470Z
2019-07-26T16:41:20.470Z Error: KK
2019-07-26T16:41:20.470Z at Timeout.setInterval [as _onTimeout] (/root/nodetest/x.js:4:7)
2019-07-26T16:41:20.470Z at ontimeout (timers.js:427:11)
2019-07-26T16:41:20.470Z at tryOnTimeout (timers.js:289:5)
2019-07-26T16:41:20.470Z at listOnTimeout (timers.js:252:5)
2019-07-26T16:41:20.470Z at Timer.processTimers (timers.js:212:10)
error: Forever detected script exited with code: 1
error: Script restart attempt #1

という感じでタイムスタンプが付きません
この部分は timestamp オプションでタイムスタンプをつけれるので合わせて使えば全体にタイムスタンプをつけられます

child_process を使う

forever を改造するのはやっぱり嫌 という場合にはここでやったようなことを実行するスクリプト側で行うという手もあります
forever で実行するのは本来のスクリプトとは別のスクリプトにし そこには下のような処理を書いておきます

const cprocess = require("child_process").spawn("node", ["x.js"])
cprocess.stdout.pipe(new AddTsTransform()).pipe(process.stdout)
cprocess.stderr.pipe(new AddTsTransform()).pipe(process.stderr)

このスクリプトが本来実行したいスクリプトを子プロセスとして実行します

この方法も timestamp オプションを有効にする必要ありです

console を上書きせず forever の書き換えも行わないのでありといえばありですが無駄なプロセスができますし あんまりやりたい方法ではないです