はじめに
バックエンドの開発・運用業務の中で必ずおこなうといっても過言ではないログの分析。そこで利用するLinuxコマンドについて経験のない初心者向けに基礎的な内容の説明する。現在筆者は、主にAPIやバッチの処理が正しくおこなわれているかを確認する目的でログを分析することがある。
なぜログの分析をする必要があるのか
問題の原因を特定する
プロダクト上で起こってしまったバグやエラーの原因を探り特定し、スピーディーな修正に繋げるためである。複雑にいろんなロジックが絡んでしまっている処理ほど実装を一から追っても気づきにくい。初めからログを追ってどの処理でエラーを吐いたのかを見つけた方が効率も良い。知見の浅い方でも実装を確認することでそのプロダクトに対する理解も深めることができる。
パフォーマンスの最適化をはかる
アプリケーションの動作やリソース使用状況を詳細に把握することができる。例えば、リクエストの処理時間が長いエンドポイントや、負荷のかかっているデータベースクエリなどを特定し、これらのボトルネックを解消することで全体的なパフォーマンスを向上させることができる。
セキュリティインシデントを未然に防ぐ
ログインIDなどに紐づいた情報から、ログイン試行の多さや不審なIPアドレスからのアクセスなどの異常を監視し、早期にセキュリティインシデントを検知することができる。
ユーザーの行動を分析する
利用パターンや傾向、特定の機能やコンテンツの人気度を把握することができる。これにより、ニーズや行動に基づいた改善策や新機能の開発に繋げやすくなり、UXの向上をはかることもできる。
ログ調査方法
- サーバーにsshで入る
- grepやtailで特定のキーワードなどで必要なログを探す
※ 既にホスティングをしている環境での手順です
利用するLinuxコマンド
※ ログ出力のためのビジネスロジックの実装方法は流石に解説しません。
※ Webアプリ開発で当たり前に利用するGitコマンドやcdコマンドの説明は省略します。
(1) ssh
サーバにSSH接続をする。その際には基本的に以下の項目が必要になるため、前もって準備をしておく。
- サーバーのIPアドレスorホスト名
- どっちかあれば大丈夫だが、Hosts設定を適切におこなっていない場合ホスト名を認識しないことがあるため気をつける(IPアドレスを使用すると確実)
- ユーザー名
- サーバーにログインするために必要。root権限には気をつける。
- 認証情報
- ユーザのパスワードとSSHの秘密鍵が必要になる。秘密鍵の権限を適切に設定しないと情報漏洩に繋がるため気をつける。
- ポート番号
- 通常は22ですが、変更している場合もあるためご注意を。
以下のようなコマンドを叩く。権限周りで弾かれる場合はsudoを先頭につけると解消することも(扱いには配慮が必要)
ssh ユーザー名@サーバーIPアドレス(or ホスト名)
例
$ ssh yamato@192.168.1.100
送信後にパスワード入力が求められるため、従って入力して送信し、問題なければそのままログインできる。
(2) grep, tail
grepコマンドやtailコマンドを叩いて実施にログを分析する。
2つの違いを軽く記す。
grep
ファイルから特定のキーワード、文字列などが含まれる行を検索する。
例) logfile.txtファイルの中から"error"というキーワードの出力を検索したい時
$ grep "error" logfile.txt
別の階層にある場合でも現在の位置からのパスを入力すれば検索できる。
例) app/log/logfile.txtに検索をしたい時
$ grep "error" app/log/logfile.txt
複数条件を絞って検索をかけることもできる。
その際は、| (ポール?っていうのかな?)で区切ってさらにgrepを重ねていく。
例) "error"と"errorCount=1"をどちらも含む行を検索したい時
$ grep "error" app/log/logfile.txt | grep "errorCount=1"
さらにオプションを利用することでかなり細かく検索できるため、調べてやってみると良い。
「欲しいログにどのような文字列が含まれているか分からない」と思った場合は、実際にプロダクトに実装されているビジネスロジックを確認することで把握することができる。
以下にサンプルコードを記す。
package com.example.loggingdemo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class SampleService {
private static final Logger logger = LoggerFactory.getLogger(SampleService.class);
public String processBusinessLogic() {
try {
logger.info("計算の処理を開始しました");
// 例: 計算のロジック
int result = performCalculation(10, 2);
logger.info("計算が成功しました: {}", result);
return "処理が成功しました";
} catch (Exception e) {
logger.error("エラーが発生しました: {}", e.getMessage(), e);
return "エラーが発生しました";
}
}
private int performCalculation(int a, int b) {
logger.debug("performCalculationメソッド: 入力値 a={}, b={}", a, b);
return a / b;
}
}
これは計算処理とその結果をログに出力するロジックである。(言語はJava)
Java標準のライブラリを使用してログ出力の実装をしている。
例えば、tryの中の処理が進み、loggerの記述に"計算が成功しました"とあるが、成功処理のログを追いたい場合はこの文字でgrepをおこなうと良いだろう。
grepしてヒットしなかった場合は、エラー処理が走っているor他の実装で影響が出ている可能性がある。
また、最適化のためにログを圧縮ファイルで保存している場合がある。
その際にgrepコマンドを使用しても文字化けしたものが出力される恐れがあるため、"zgrep"コマンドをその際は使用すると良い(ファイル表示の際はzcat)
$ zgrep "error" logfile.txt
tail
ファイルの末尾から指定された行数を表示する。
例) logfile.txtファイルの末尾から10行を表示する
$ tail -n 10 logfile.txt
過去ログだけでなくリアルタイムでログの分析をおこなうこともできる。
例) リアルタイムにログを追う
$ tail -f logfile.txt
grepコマンドも組み合わせることでより細かくリアルタイムに分析することも可能である。
例) "error"というキーワードのログを追う
$ tail -f logfile.txt | grep "error"
気をつけること
紹介したコマンドはとても便利だが、実際に作業をおこなう際には「サーバーに負荷をかけすぎないこと」を意識するべきである。特にgrepコマンドはとても便利で、1ファイルのみならず階層下におけるファイル全てを対象に検索をかけることもできる。
慣れないうちはそのような曖昧な検索をおこないがちだが、サーバーのメモリを逼迫してしまいパフォーマンスを低下させる原因にもなりかねない。
エンジニアであれば内部実装にも目を配り、できる限り細かい範囲でgrepできると省エネでよし。
最後に
ログ調査の業務を始めてまだ1週間しか経過していない駆け出しなので、今後の学びをシリーズ化して発信する予定を立てた。