原文はこちら。
https://blogs.oracle.com/WebLogicServer/entry/dynamic_debug_patches_in_weblogic
Introduction
好むと好まざるとに関わらず、完全なソフトウェアはありません。開発者の最善の努力にもかかわらずバグが発生しますし、さらに悪いことに、多くの状況で、予期しない形で現れます。また、あるケースでは再現することが難しく、断続的に発生することがあります。このような場合では、製品が十分に根本的な原因を明らかにするための機能が備わっていないと、問題の本質を理解するための情報が不足することが多々あります。お客様の本番環境への直接アクセスは通常は選択できず、根本的な原因をより理解するために、デバッグ用パッチを作成し、そのパッチを適用したアプリケーションを実行して、願わくばより多くの洞察が得られることを期待するわけですが、これはトライ&エラー方式で、本当の原因に到達するまでに何度かの繰り返し作業になる可能性があります。デバッグパッチを作成する人たち(通常はソフトウェアプロバイダのサポートや開発チームです)やアプリケーションを使うお客様はほぼ異なるグループで、別の会社に属していることが多々あります。そのため、デバッグパッチの作成、お客様へのパッチの提供、顧客環境へのパッチ適用、結果の取得・返送という繰り返し作業の各々でかなりの時間がかかる可能性があります。結果として、問題解決に時間がかかります。
加えて、このようなデバッグパッチをデプロイすることに伴う別の重要な問題が発生することがあります。Java EE環境でパッチを適用すると、サーバーおよびドメインの再起動もしくは少なくともアプリケーションの再デプロイが必要です。ミッションクリティカルな環境では、すぐにパッチ適用ができない可能性があります。サーバを再起動すると、状態が失われるため、メモリ内の重要な障害データが失われる可能性があります。また、サーバの再起動後、断続的な障害が長時間現れず、迅速な診断が困難になる場合があります。
Dynamic Debug Patches
WebLogic Server 12.2.1では、Dynamic Debug Patchesと呼ばれる新しい機能が導入されました。これは迅速な問題解決のため、診断データを取得するプロセスを簡素化することを目的としています。
Oracle® Fusion Middleware Configuring and Using the Diagnostics Framework for Oracle WebLogic Server 12c (12.2.1)
Using Debug Patches
http://docs.oracle.com/middleware/1221/wls/WLDFC/using_debug_patches.htm#WLDFC585
この機能を使用すると、デバッグパッチを動的にアクティベートできます。このときにサーバまたはクラスタの再起動や、WebLogicドメインへのアプリケーションの再デプロイは不要です。これは、JDKのインスツルメンテーション機能を利用し、ランタイムWLSTコマンドを使用して、指定されたデバッグパッチからクラスをホット・スワップします。
java.lang.instrument Interface Instrumentation
http://docs.oracle.com/javase/7/docs/api/java/lang/instrument/Instrumentation.htmlhttp://docs.oracle.com/javase/jp/7/api/java/lang/instrument/Instrumentation.html
WLSTコマンド(後述)を発行して、一つ以上のデバッグパッチを選択されたサーバ、クラスタ、パーティションおよびアプリケーションの範囲内でアクティベートできます。サーバの再起動やアプリケーションの再デプロイが不要なので、関連したロジスティックの障害は問題にはなりません。一例としては、アプリケーションやサービスは実行し続けるので、こうしたパッチを本番環境で有効にすることの障壁が低くなります。また、状態の損失もありません。したがって、新たにアクティベートされたデバッグパッチのインストルメントコードが新しくアクティブ化されたデバッグパッチでインストルメントコードがまずい過渡状態を明らかにし、意味のある診断情報を提供するチャンスが増えます。
Prerequisites
Dynamic Debug Patchは、デバッグログやステートメントの表示などといった追加のインストルメントコードを持つパッチ適用済みのクラスを含んだ通常のjarファイルです。通常は製品開発もしくはサポートチームがこれらのパッチjarファイルを作成し、システム運用チームが現場でのアクティベーションのために利用できるようにします。WebLogic ServerのDynamic Debug Patch機能を利用できるようにするには、システム管理者がドメイン内の特定のディレクトリにコピーする必要があります。デフォルトでは、このディレクトリは、ドメインルートの下のdebug_patchesというディレクトリですが、DebugPatchesMBeanのDebugPatchDirectory属性を再設定することで変更できます。
もう一つ、サーバの起動コマンド内で以下のオプションを使ってdebugpatch インストルメンテーション・エージェントとともにサーバを起動する必要がありますが、これはWebLogic Server 12.2.1ドメイン用に作成した起動スクリプトで自動的に追加されます。
-javaagent:${WL_HOME}/server/lib/debugpatch-agent.jar
Using Dynamic Debug Patches Feature
シンプルなアプリケーションでデバッグパッチを有効化・無効化しながら、この機能の使い方を説明します。
The Application
最小限のWebアプリケーションを使います。これは入力された整数の階乗値を計算してブラウザに表示するというものです。
FactorialServlet.java:
package example;
import java.io.IOException;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* A trivial servlet: Returns addition of two numbers.
*/
@WebServlet(value="/factorial", name="factorial-servlet")
public class FactorialServlet extends GenericServlet {
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
String n = request.getParameter("n");
System.out.println("FactorialServlet called for input=" + n);
int result = Factorial.getInstance().factorial(n);
response.getWriter().print("factorial(" + n + ") = " + result);
}
}
ServletはFactorialシングルトンに階乗値を計算させています。Factorialクラスは以前計算した値のMapを最適化のため保持しています。これはDynamic Debug Patchを有効化・無効化しながら、ステートフルな情報を保持していることの説明として役立つでしょう。
Factorial.java:
package example;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
class Factorial {
private static final Factorial SINGLETON = new Factorial();
private Map<String, Integer> map = new ConcurrentHashMap<String, Integer>();
static Factorial getInstance() {
return SINGLETON;
}
public int factorial(String n) {
if (n == null) {
throw new NumberFormatException("Invalid argument: " + n);
}
n = n.trim();
Integer val = map.get(n);
if (val == null) {
int i = Integer.parseInt(n);
if (i < 0)
throw new NumberFormatException("Invalid argument: " + n);
int fact = 1;
while (i > 0) {
fact *= i;
i--;
}
val = new Integer(fact);
map.put(n, val);
}
return val;
}
}
Building and Deploying the Application
factorial.war Webアプリケーションをビルドするために、FactorialServlet.javaとFactorial.javaファイルを上述のように空のディレクトリに作成し、以下のコマンドを使ってアプリケーションwarファイルをビルドします。
mkdir -p WEB-INF/classes
javac -d WEB-INF/classes FactorialServlet.java Factorial.java
jar cvf factorial.war WEB-INF
WLSTもしくはWebLogic Server管理コンソールを使ってアプリケーションをデプロイします。
$MW_HOME/oracle_common/common/bin/wlst.sh
Initializing WebLogic Scripting Tool (WLST) ...
Welcome to WebLogic Server Administration Scripting Shell
Type help() for help on available commands
connect(username, password, adminUrl) # e.g. connect('weblogic', 'weblogic', 't3://localhost:7001')
Connecting to t3://localhost:7001 with userid weblogic ...
Successfully connected to Admin Server "myserver" that belongs to domain "mydomain".
Warning: An insecure protocol was used to connect to the server.
To ensure on-the-wire security, the SSL port or Admin port should be used instead.
deploy('factorial', 'factorial.war', targets='myserver')
上の説明では、アプリケーションを管理サーバにデプロイしていることにご注意ください。実際の現場では別の管理対象サーバもしくはクラスタにデプロイされていることがあります。複数の管理対象サーバ全体に対してデバッグパッチを有効化・無効化する方法は後で説明します。
ビルドしたWebアプリケーションをブラウザから http://localhost:7001/factorial/factorial?n=4 のような感じで呼び出します。ブラウザに結果が表示され、サーバの標準出力ウィンドウには以下のような文字が現れるはずです。
FactorialServlet called for input=4
The Debug Patch
先ほど作成したアプリケーションはたくさんのログを出力しないので、機能がわかりづらくなっています。おそらく、そこに問題があり、このアプリケーションを実行した場合、より多くの情報が必要です。アプリケーションコードからデバッグパッチを作成し、システム管理者に提供して、実行中のサーバー/アプリケーション上でそれを有効化することができます。上記コードを変更し、追加情報を取得するため追加のprint文を入れましょう(以下のMYDEBUGが付いている行)。
Updated (version 1) Factorial.java:
class Factorial {
private static final Factorial SINGLETON = new Factorial();
private Map<String, Integer> map = new ConcurrentHashMap<String, Integer>();
static Factorial getInstance() {
return SINGLETON;
}
public int factorial(String n) {
if (n == null) {
throw new NumberFormatException("Invalid argument: " + n);
}
n = n.trim();
Integer val = map.get(n);
if (val == null) {
int i = Integer.parseInt(n);
if (i < 0)
throw new NumberFormatException("Invalid argument: " + n);
int fact = 1;
while (i > 0) {
fact *= i;
i--;
}
val = new Integer(fact);
System.out.println("MYDEBUG> saving factorial(" + n + ") = " + val);
map.put(n, val);
} else {
System.out.println("MYDEBUG> returning saved factorial(" + n + ") = " + val);
}
return val;
}
}
デバッグパッチをビルドしますが、これはプレーンなjarファイルでありアプリケーションアーカイブとしてビルドしないことにご注意ください。また、(毀損することはないでしょうが)アプリケーション全体をコンパイルする必要はないことにご注意ください。デバッグパッチjarファイルには変更されたクラス(この場合、Factorialクラス)のみが含まれる必要があります。
mkdir patch_classes
javac -d patch_classes Factorial.java
jar cvf factorial_debug_01.jar -C patch_classes
Activating Debug Patches
現実のシナリオでは、作成者(開発者)とデバッグパッチのアクティベータ(システム管理者)が異なることがほとんどでしょう。説明のために、今回は同一人物が複数の役割を持つことにします。デバッグパッチディレクトリの場所としてデフォルト設定を利用する前提としますが、まだ存在しない場合は、<Domain_Home>の下に
debug_patchesディレクトリを作成してください。
debug_patchesディレクトリにfactorial_debug_01.jarというデバッグパッチのjarファイルをコピーします。上記のようにWLSTを使用してサーバに接続します。
まず、デバッグパッチがドメインで利用可能かどうかを確認しましょう。これはlistDebugPatchesコマンドを使って実現できます。
Oracle® Fusion Middleware WLST Command Reference for WebLogic Server 12c (12.2.1)
listDebugPatches
http://docs.oracle.com/middleware/1221/wls/WLSTC/reference.htm#WLSTC3694
[ヒント]利用可能な診断コマンドを確認するには、 help('diagnostics') とコマンドを発行します。特定コマンドの情報を知るためには、help(コマンド名) を発行します。例えば、 help('activateDebugPatch') という感じです。
wls:/mydomain/serverConfig/> listDebugPatches()
myserver:
Active Patches:
Available Patches:
factorial_debug_01.jar
app2.0_patch01.jar
app2.0_patch02.jar
factorial_debug_01.jar は新規作成されたデバッグパッチです。app2.0_patch01.jar と app2.0_patch02.jar は過去に別のアプリケーションでの問題を調査するために作成されたものでした。上記のリストはこれまでどれも有効化されなかったので「アクティブ」なパッチはありません。
では、activateDebugPatchコマンドを使ってデバッグパッチを有効化しましょう。
Oracle® Fusion Middleware WLST Command Reference for WebLogic Server 12c (12.2.1)
activateDebugPatch
http://docs.oracle.com/middleware/1221/wls/WLSTC/reference.htm#WLSTC3670
tasks=activateDebugPatch('factorial_debug_01.jar', app='factorial', target='myserver')
wls:/mydomain/serverConfig/> print tasks[0].status
FINISHED
wls:/mydomain/serverConfig/> listDebugPatches()
myserver:
Active Patches:
factorial_debug_01.jar:app=factorial
Available Patches:
factorial_debug_01.jar
app2.0_patch01.jar
app2.0_patch02.jar
コマンドは、起動コマンドの進行状況を監視するために使用できるタスクの配列を返します。該当する場合は、複数の管理対象サーバやクラスタをターゲットとして指定することができます。該当する各ターゲットサーバに対応して、返されたタスクの配列中にタスクがあります。コマンドを使ってサーバやミドルウェアレベルでデバッグパッチを有効化することもできます。このようなパッチを通常Oracle Supportが作成することもあるでしょう。上記の listDebugPatches() コマンドの出力結果では、factorial_debug_01.jar をアプリケーションfactorialが有効化されていることを示しています。
それではリクエストをアプリケーションに送信しましょう。
http://localhost:7001/factorial/factorial?n=4
http://localhost:7001/factorial/factorial?n=5
サーバー出力:
FactorialServlet called for input=4
MYDEBUG> returning saved factorial(4) = 24
FactorialServlet called for input=5
MYDEBUG> saving factorial(5) = 120
input=4の場合、値は以前のリクエストで計算、Mapに保存済みなので保存済みの結果を返していることに注意してください。したがって、デバッグパッチは、アプリケーション内の既存の状態を壊さずに有効化されました。input=5の場合、値は以前計算されておらず、保存されていないので、異なるデバッグメッセージが現れました。
Activating Multiple Debug Patches
必要であれば、潜在的にオーバーラップする複数のパッチを有効化することができます。オーバーラップする場合、後で有効化されるパッチが先に有効化されたパッチの効果をマスクします。具体的には、上記のケースでは、内部ループを実行しているときのfactorial()メソッドからの詳細情報が必要です。別のデバッグパッチを作成し、debug_patchesディレクトリにコピーして有効化しましょう。
Updated (version 2) Factorial.java:
class Factorial {
private static final Factorial SINGLETON = new Factorial();
private Map<String, Integer> map = new ConcurrentHashMap<String, Integer>();
static Factorial getInstance() {
return SINGLETON;
}
public int factorial(String n) {
if (n == null) {
throw new NumberFormatException("Invalid argument: " + n);
}
n = n.trim();
Integer val = map.get(n);
if (val == null) {
int i = Integer.parseInt(n);
if (i < 0)
throw new NumberFormatException("Invalid argument: " + n);
int fact = 1;
while (i > 0) {
System.out.println("MYDEBUG> multiplying by " + i);
fact *= i;
i--;
}
val = new Integer(fact);
System.out.println("MYDEBUG> saving factorial(" + n + ") = " + val);
map.put(n, val);
} else {
System.out.println("MYDEBUG> returning saved factorial(" + n + ") = " + val);
}
return val;
}
}
factorial_debug_02.jarを作成します。
javac -d patch_classes Factorial.java
jar cvf factorial_debug_02.jar -C patch_classes .
cp factorial_debug_02.jar $DOMAIN_DIR/debug_patches
factorial_debug_02.jarを有効化します。
wls:/mydomain/serverConfig/> listDebugPatches()
myserver:
Active Patches:
factorial_debug_01.jar:app=factorial
Available Patches:
factorial_debug_01.jar
factorial_debug_02.jar
app2.0_patch01.jar
app2.0_patch02.jar
wls:/mydomain/serverConfig/> tasks=activateDebugPatch('factorial_debug_01.jar', app='factorial', target='myserver')
wls:/mydomain/serverConfig/> listDebugPatches()
myserver:
Active Patches:
factorial_debug_01.jar:app=factorial
factorial_debug_02.jar:app=factorial
Available Patches:
factorial_debug_01.jar
factorial_debug_02.jar
servlet3.0_patch01.jar
servlet3.0_patch02.jar
では、アプリケーションに http://localhost:7001/factorial/factorial?n=5 とか http://localhost:7001/factorial/factorial?n=6 といった具合で、いくつかリクエストを投げてみましょう。
FactorialServlet called for input=5
MYDEBUG> returning saved factorial(5) = 120
FactorialServlet called for input=6
MYDEBUG> multiplying by 6
MYDEBUG> multiplying by 5
MYDEBUG> multiplying by 4
MYDEBUG> multiplying by 3
MYDEBUG> multiplying by 2
MYDEBUG> multiplying by 1
MYDEBUG> saving factorial(6) = 720
factorial_debug_02.jarに入っているコードのおかげで追加情報が表示されていることがわかります。
Deactivating Debug Patches
もうデバッグパッチが不要であれば、deactivateDebugPatchesコマンドを使って無効化することができます。使い方はhelp('deactivateDebugPatches')でヘルプを呼び出してください。
Oracle® Fusion Middleware WLST Command Reference for WebLogic Server 12c (12.2.1)
deactivateDebugPatches
http://docs.oracle.com/middleware/1221/wls/WLSTC/reference.htm#WLSTC3678
wls:/mydomain/serverConfig/> tasks=deactivateDebugPatches('factorial_debug_02.jar', app='factorial', target='myserver')
wls:/mydomain/serverConfig/> listDebugPatches()
myserver:
Active Patches:
factorial_debug_01.jar:app=factorial
Available Patches:
factorial_debug_01.jar
factorial_debug_02.jar
servlet3.0_patch01.jar
servlet3.0_patch02.jar
では、 http://localhost:7001/factorial/factorial?n=2 でアプリケーションを呼び出してみましょう。以下の出力結果がサーバの標準出力画面に現れます。
FactorialServlet called for input=2
MYDEBUG> saving factorial(2) = 2
factorial_debug_01.jarとfactorial_debug_02.jarをこの順で有効化したとき、factorial_debug_02.jarのクラスがfactorial_debug_01.jarのものをマスクしていることに注意してください。factorial_debug_02.jarを無効化した後、factorial_debug_01.jarのクラスのマスクを外すと、再び有効になりました。デバッグパッチリストのカンマ区切りリストを、deactivateDebugPatchesコマンドで指定することができます。deactivateAllDebugPatches()コマンドを使用すると、該当するターゲットサーバ上のすべてのアクティブなデバッグパッチを無効にすることができます。
Oracle® Fusion Middleware WLST Command Reference for WebLogic Server 12c (12.2.1)
deactivateAllDebugPatches
http://docs.oracle.com/middleware/1221/wls/WLSTC/reference.htm#WLSTC3674
WLST Commands
Dynamic Debug Patch機能を操作するために、以下の診断WLSTコマンドが提供されています。
Oracle® Fusion Middleware WLST Command Reference for WebLogic Server 12c (12.2.1)
Diagnostics Commands
http://docs.oracle.com/middleware/1221/wls/WLSTC/reference.htm#WLSTC242
上述の通り、help(コマンド名)で当該コマンドのヘルプを表示します。
Limitations
Dynamic Debug Patch機能はJDKのホットスワップ機能を使いますが、ホットスワップするクラスはオリジナルのクラスと異なる形を持つことができないという制限があります。つまり、スワップするクラスでは、コンストラクタ、メソッド、フィールド、スーパークラス、実装されたインターフェースなどを追加、削除、変更できず、メソッド本体での変更のみ許容されています。デバッグパッチは通常追加情報を収集するだけで、問題を修正しようとするものではない、ということにご注意ください。クラスの形を変更しないマイナーな変更を試すことができますが、それはこの機能の主要な目的ではありません。したがって、実際にはこれが大きな制限とは思っていません。
しかしながら、問題は、新しいデバッグコードが時として状態を維持する必要があるかもしれないということです。例えば、Mapのデータを収集し、ある閾値でダンプ出力したい、という場合があるとしましょう。形状変化に関するJDKの制限により、こうした場合に問題が発生します。Dynamic Debug Patch機能はDebugPatchHelperユーティリティクラスを提供しており、このクラスがそうした問題解決の手助けになります。後続の記事でこの内容を説明しますので、ご期待ください。