normalian blog

Let's talk about Microsoft Azure, ASP.NET and Java!

AppInsights の Java SDK が出力するログを確認したい場合

Application Insights を利用する場合、設定ファイルが読み込まれたか?何か内部エラーが発生していないか等を詳細に確認したい場合がある。その際に利用できる InternalLogger について紹介する。

有効化する - コンソール出力編

以下のように ApplicationInsights.xml の SDKLogger タグを追加する。

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings" schemaVersion="2014-05-30">
        "中略"
	<SDKLogger>
	</SDKLogger>
	<DisableTelemetry>false</DisableTelemetry>
</ApplicationInsights>

ソースコードを確認すると理解できるが、SDKLogger タグが存在している場合に初期化処理が実行されるようになっている。

デフォルトでは以下のように CONSOLE に最初が"AI"として情報が出力される設定になっている。

情報: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
AI: INFO 30-11-2015 00:08, 19: Registering WebApp with name 'AppInsightWebApp'
AI: INFO 30-11-2015 00:08, 19: Configuration file has been successfully found as resource
AI: WARN 30-11-2015 00:08, 19: 'MaxTelemetryBufferCapacity': null value is replaced with '500'
AI: WARN 30-11-2015 00:08, 19: 'FlushIntervalInSeconds': null value is replaced with '5'
AI: TRACE 30-11-2015 00:08, 19: Using Http Client version 4.3+
AI: TRACE 30-11-2015 00:08, 19: No back-off container defined, using the default 'EXPONENTIAL'
AI: WARN 30-11-2015 00:08, 19: 'Channel.MaxTransmissionStorageCapacityInMB': null value is replaced with '10'
AI: TRACE 30-11-2015 00:08, 19: C:\Users\"username"\AppData\Local\Temp\AISDK\native\1.0.1 folder exists
AI: TRACE 30-11-2015 00:08, 19: Java process name is set to 'javaw'
AI: TRACE 30-11-2015 00:08, 19: Successfully loaded library 'applicationinsights-core-native-win64.dll'
AI: TRACE 30-11-2015 00:08, 19: Registering PC 'JSDK_ProcessMemoryPerformanceCounter'
AI: TRACE 30-11-2015 00:08, 19: Registering PC 'JSDK_ProcessCpuPerformanceCounter'
AI: TRACE 30-11-2015 00:08, 19: Registering PC 'JSDK_WindowsPerformanceCounterAsPC'
AI: INFO 30-11-2015 00:08, 19: Registered WebApp 'AppInsightWebApp' key='AppInsightWebApp'
AI: INFO 30-11-2015 00:08, 19: Successfully registered the filter 'ApplicationInsightsWebFilter'
11 30, 2015 12:08:29 午前 org.apache.coyote.AbstractProtocol start
情報: Starting ProtocolHandler ["http-nio-8080"]
11 30, 2015 12:08:29 午前 org.apache.coyote.AbstractProtocol start
情報: Starting ProtocolHandler ["ajp-nio-8009"]
11 30, 2015 12:08:29 午前 org.apache.catalina.startup.Catalina start
情報: Server startup in 2452 ms
AI: TRACE 30-11-2015 00:08, 32: InProcessTelemetryChannel sending telemetry

有効化する - ファイル出力編

AppInsights Java SDKソースコードを確認すると SDKLoggerXmlElement にて JAXB を利用して拡張機能が実装されていることが分かる。
InternalLogger を確認すると CONSOLE 以外にも FILE が存在するので、さっそく試すと以下のエラーが出力された。

AI: INFO 30-11-2015 00:14, 19: Registering WebApp with name 'AppInsightWebApp'
AI: INFO 30-11-2015 00:14, 19: Configuration file has been successfully found as resource
AI: SDK Internal Logger internal error while initializing 'FILE': 'Unique log file prefix is not defined'.

上記から prefix の文字を含めて設定する必要があるらしいことが分かる。SDKLoggerXmlElement.java を確認すると設定項目があるので、以下を設定する。

	<SDKLogger type="FILE">
		<UniquePrefix>AppInsightWebApp</UniquePrefix>
	</SDKLogger>

無事アプリケーションが起動すると %TEMP%\javasdklogs\AppInsightWebApp-2015-11-29-23-20-05-JavaSDKLog3742616972029564770.jsl としてファイルが作成された。
ソースコードを確認すると File.createTempFile( uniquePrefix + LOG_FILE_PREFIX, LOG_FILE_SUFFIX, baseFolder) としてファイルを作成していることが確認できる。

クラウドサービスで Application Insights の Java SDK を利用する場合の TIPS

本ブログで何度も紹介させて頂いている Application Insights だが、クラウドサービスの利用時に AVAILBABLE MEMORY, PROCESS IO RATE, PROCESSOR TIME が以下の様に出力されない場合がある。
f:id:waritohutsu:20151129143045j:plain

解決方法を先に言ってしまうと、クラウドサービスのスタートアップタスクで Visual C++ Redistributable Packages for Visual Studio 2013 をインストールすればよい。

何でログが出ないの?

クラウドサービスで利用する OS は Windows Server(設定によってバージョンは変わる)なのは周知だと思うが、Java SDK の JniPCConnector.java を確認すればわかるが、JNI を利用してネイティブコードを呼び出している。
さらにソースコード内で ApplicationInsights-Java の C++ ソースを確認すればわかる通り、ネイティブコードを利用してパフォーマンスカウンタに登録していることがわかる。
これらの処理を行う際、Windows Server では Visual C++ Redistributable Packages for Visual Studio 2013 のセットアップが必要になるが、本モジュールが不足しているために AVAILBABLE MEMORY, PROCESS IO RATE, PROCESSOR TIME が出力されないという問題が発生する。

対応方法

Azure Toolkit for Eclipse を利用したプロジェクト構成が前提となるが、以下の手順で問題は解決できる。

  • Visual C++ Redistributable Packages for Visual Studio 2013 から vcredist_x64.exe ファイルを取得
  • "project name"\WorkerRole1\approot\vcredist_x64.exe にモジュールを配置する
  • 以下のバッチファイルを参考に、"project name"\WorkerRole1\approot\startup.cmd に対して vcredist_x64.exe のセットアップ処理を記載する
:: *** This script will run whenever Azure starts this role instance.
:: *** This is where you can describe the deployment logic of your server, JRE and applications 
:: *** or specify advanced custom deployment steps
::     (Note though, that if you're using this in Eclipse, you may find it easier to configure the JDK,
::     the server and the server and the applications using the New Azure Deployment Project wizard 
::     or the Server Configuration property page for a selected role.)

echo Hello World!
vcredist_x64.exe /install /quiet

WildFly Swarm で JAX-RS アプリを動かしてみる #1

今回は Microsoft Azure からちょっと外れ、WildFly Swarm という WildFly プロジェクトの派生プロジェクトをいじってみる。

WildFly Swarm って何?

WildFly Swarm とは Java EE 系の AP サーバ起動が遅かったり、Java EE 系の仕様策定が遅めで各仕様のバージョンアップを個別にしたい等、様々な課題に対しての解決策として提示された機能っぽい*1。Spring Boot の様に実行コンテナごと Fat Jar 化する機能を持っている。

まずは疎通をとってみる

簡単に疎通をとって動かしてみるサンプルとして JAX-RS が簡単そうだったので以下のように動かすとする。以下を参照いただければわかる通り、わずかな plugin 設定と wildfly-swarm-jaxrs への参照設定を追加する程度でアプリケーションの作成が可能になる。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.mydomain</groupId>
	<artifactId>HelloWildFlySwarm</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>HelloWildFlySwarm</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<build>
		<plugins>
			<plugin>
				<groupId>org.wildfly.swarm</groupId>
				<artifactId>wildfly-swarm-plugin</artifactId>
				<configuration>
					<mainClass>com.mydomain.HelloWildFlySwarm.App</mainClass>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>package</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.wildfly.swarm</groupId>
			<artifactId>wildfly-swarm-jaxrs</artifactId>
			<version>1.0.0.Alpha4</version>
		</dependency>
	</dependencies>
</project>

以下を参照いただければ分かるが main 関数内で一つ以上の deploy を実施する必要がある。

package com.mydomain.HelloWildFlySwarm;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.wildfly.swarm.container.Container;
import org.wildfly.swarm.jaxrs.JAXRSArchive;

import com.mydomain.HelloWildFlySwarm.jaxrs.RootApi;

public class App {
	public static void main(String[] args) throws Exception {
		Container container = new Container();
		JAXRSArchive jaxrsDeployment = ShrinkWrap.create(JAXRSArchive.class);
		jaxrsDeployment.addClass(RootApi.class);
		jaxrsDeployment.addAllDependencies();
		container.start().deploy(jaxrsDeployment);
	}
}

また、上記では ShrinkWrap を利用している。

package com.mydomain.HelloWildFlySwarm.jaxrs;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("/")
public class RootApi {

	@GET
	@Produces("text/plain")
	public String get() {
		return "Hello WildFly Swarm";
	}
}

以下の Maven コマンドをたたいて実行すると、AP サーバを立ち上げることなく REST アプリケーションの立ち上げが可能だ。

>mvn clean wildfly-swarm:run

jar ファイルが 30MB 程度になっており、なかなかのサイズになっていた。実行コンテナ、依存 jar も含まれるからまぁそんなもんかというところ。

TIPS

初回動作させる際に Failed to resolve artifact 'org.jboss.msc:jboss-msc:1.2.6.Final' が発生していた。Swarm でなく Maven 側の依存解決の問題となるが、以下を一時的に追加して自身のローカルリポジトリに以下の jar を配置することで回避できる。

		<dependency>
			<groupId>org.jboss.msc</groupId>
			<artifactId>jboss-msc</artifactId>
			<version>1.2.6.Final</version>
		</dependency>
		<dependency>
			<groupId>org.jboss.spec.javax.sql</groupId>
			<artifactId>jboss-javax-sql-api_7.0_spec</artifactId>
			<version>1.1.0.Final</version>
		</dependency>

*1:誰か間違っていたら突っ込みをお願いしたい

Power BI 向けの組織アカウントをお手軽に作ってみる

Stream Analytics の出力先に Power BI が指定可能になってしばらく経った。リアルタイム分析*1を行う場合、Power BI は非常に有力な機能であり、当ブログでも以下のように機能を紹介してきた。

今回は Power BI を利用する際にちょっとハマる出来事について紹介したい。

Stream Analytics の出力先に Power BI を指定すると認証に失敗する?

管理ポータルから Stream Analyics のインスタンスを選択し、「出力の追加」をを指定すると Power BI(プレビュー)が選択できる。
こちらを選択した後、アカウント選択が可能だが、「既存の Microsoft Power BI ユーザ」を選択して xxxx@hotmail.com の様な Microsoft アカウント を入力すると以下のようにエラーになる。
f:id:waritohutsu:20151112132446p:plain

こちらの原因は Power BI 側の承認は組織アカウントのみをサポートしている点だ。組織アカウントと Microsoft アカウントの差異については以下の記事を参考にしてほしい。

お手軽に組織アカウントを作るには?

オンプレミス側に Active Directory サーバを作成し、ディレクトリ同期機能を使って Azure Active Directory にアカウント同期をするといった処理を行えばもちろん組織アカウントの作成は可能だが、そんなにメンドクサイことはしたくない場合も多いだろう(特に検証時)。

その場合、既存の Azure Active Directoryディレクトリに対し、直にユーザを作成する方法があげられる。以下のように、ソースが Microsoft アカウント でなく Microsoft Azure Active Directory ならば Power BI 側でアカウント作成が可能だ。
f:id:waritohutsu:20151112132513p:plain

「ユーザーの追加」ボタンを押下し、以下のように「組織内の新しいユーザー」を作成する。
f:id:waritohutsu:20151112132520p:plain

ウィザードに従ってアカウントの作成後、再度 Stream Analytics の「出力の追加」先で「新規ユーザー」を指定し、作成した組織アカウントでログインを実施する(例:"user name"@"directory name".onmicrosoft.com
)。
設定が完了すると、以下のように「種類」が Power BI の出力先が作成される。
f:id:waritohutsu:20151112132527p:plain

*1:数分程度の誤差は生じる

Application Insigts のリクエストデータを SQL Database に格納する

アプリケーションのログデータを永続化/可視化してくれる Application Insights だが、本ブログでは今まで過去2回にわたり、Web アプリのログに対して Stream Analytics を介し Power BI を利用した可視化を紹介していた。

上記は Java の Web アプリで行っていたが、今回は ASP.NET を利用したログ出力を行い、SQL Database へと Application Insights のログを永続化する方法を紹介する。

ASP.NET で Application Insights を利用する方法

こちらは非常にシンプルだ。以下のサイトを参考に Application Insights の SDK を有効化するだけでアプリケーションの監視が可能になる*1
Application Insights SDK を追加して ASP.NET アプリを監視する

上記のステップを通して Application Insights の SDK を有効化したアプリケーションを、Azure Resource Manager で作成した Windows Server 2012 R2 仮想マシンIIS にデプロイした。

Application Insights のログを SQL Database に出力するまでにステップ

以下の手順が必要になる。連続エクスポートの設定については特に記載は不要だと思うので、他の手順について紹介する。

  • Application Insights の連続エクスポートの有効化
  • ログ格納用に SQL Database のテーブル作成
  • Stream Analytics の設定

今回は サーバによって受信された要求 である Request データを取り扱う。こちらは以下のようなファイル名でデータが格納される。

  • "appinsights name"_"your instrument key"/Requests/{date}/{time}/93ddecba-d09f-4027-aaed-f773de32495e_20151108_085830.blob

Request データのファイルの中身は以下のような形式だ。

{
    "context": {
        "application": {
            "version": "Unknown"
        }, 
        "cloud": {}, 
        "custom": {
            "dimensions": [], 
            "metrics": []
        }, 
        "data": {
            "eventTime": "2015-11-08T08:11:49.5138835Z", 
            "isSynthetic": false, 
            "samplingRate": 100.0
        }, 
        "device": {
            "browser": "Internet Explorer", 
            "browserVersion": "Internet Explorer 11.0", 
            "deviceModel": "Virtual Machine", 
            "deviceName": "Virtual Machine", 
            "id": "(my vm name)", 
            "locale": "en-US", 
            "network": "6", 
            "oemName": "Microsoft Corporation", 
            "os": "Windows", 
            "osVersion": "Windows 10", 
            "roleInstance": "(my vm name)", 
            "screenResolution": {}, 
            "type": "PC"
        }, 
        "location": {
            "city": "Tokyo", 
            "clientip": "119.242.23.0", 
            "continent": "Asia", 
            "country": "Japan", 
            "province": "Tōkyō"
        }, 
        "operation": {
            "id": "3579395009003726603", 
            "name": "GET Account/Register", 
            "parentId": "3579395009003726603"
        }, 
        "serverDevice": {}, 
        "session": {
            "id": "C1A7FDE1-AD1B-4D65-9F05-3DA2421E88FB", 
            "isFirst": false
        }, 
        "user": {
            "accountAcquisitionDate": "2015-11-08T04:20:59Z", 
            "anonAcquisitionDate": "0001-01-01T00:00:00Z", 
            "anonId": "16BADE4E-E5E6-4652-B518-8F012442C25D", 
            "authAcquisitionDate": "0001-01-01T00:00:00Z", 
            "isAuthenticated": false
        }
    }, 
    "internal": {
        "data": {
            "documentVersion": "1.61", 
            "id": "fcac8a5b-7615-4c53-9e51-28ccd6f40eac"
        }
    }, 
    "request": [
        {
            "count": 1, 
            "durationMetric": {
                "count": 1.0, 
                "max": 373751007.0, 
                "min": 373751007.0, 
                "sampledValue": 373751007.0, 
                "stdDev": 0.0, 
                "value": 373751007.0
            }, 
            "id": "3579395009003726603", 
            "name": "GET Account/Register", 
            "responseCode": 200, 
            "success": true, 
            "url": "http://(my vm public ip)/Account/Register", 
            "urlData": {
                "base": "/Account/Register", 
                "hashTag": "", 
                "host": "<my vm public ip>"
            }
        }
    ]
}

シンプルな JSON 形式だが、request 部分は中身が配列になっているので、Stream Analytics でクエリを書く際に注意が必要なポイントになる。

ログ格納用に SQL Database のテーブル作成

自分のサブスクリプションである必要はないが、SQL Database インスタンスを一つ作成(インスタンスサイズは何でも良いが、Standard: S0 以上を推奨する)し、以下のテーブルを作成する。

CREATE TABLE [dbo].[RequestTable](
    [eventTime] [datetime] NOT NULL,
    [isSynthetic] [nvarchar](50) NULL,
    [samplingRate] [real] NULL,

    [browser] [nvarchar](50) NULL,
    [browserVersion] [nvarchar](50) NULL,
    [deviceModel] [nvarchar](50) NULL,
    [deviceName] [nvarchar](50) NULL,
    [deviceId] [nvarchar](50) NULL,
    [locale] [nvarchar](50) NULL,
    [network] [nvarchar](50) NULL,
    [oemName] [nvarchar](50) NULL,
    [os] [nvarchar](50) NULL,
    [osVersion] [nvarchar](50) NULL,

    [roleInstance] [nvarchar](50) NULL,
    [type] [nvarchar](50) NULL,

    [clientIp] [nvarchar](50) NULL,
    [continent] [nvarchar](50) NULL,
    [country] [nvarchar](50) NULL,
    [province] [nvarchar](50) NULL,
    [city] [nvarchar](50) NULL,

    [operationId] [nvarchar](50) NULL,
    [operationName] [nvarchar](50) NULL,
    [parentId] [nvarchar](50) NULL,

    [sessionId] [nvarchar](max) NULL,
    [sessionIsFirst] [nvarchar](50) NULL,

    [requestName] [nvarchar](50) NULL,
    [responseCode] [nvarchar](50) NULL,
    [requestSuccess] [nvarchar](50) NULL,
    [requestUrl] [nvarchar](50) NULL
)

CREATE CLUSTERED INDEX [pvTblIdx] ON [dbo].[RequestTable]
(
    [eventTime] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

全ての JSON データを格納するテーブル構造となっていはいないが、もし必要な場合は自分で修正してほしい。

Stream Analytics の設定

まずは Stream Analytics の input として以下の形式で BLOB を 設定する。

  • 名前:input-blob
  • ソースタイプ:データストリーム
  • ソース:BLOBストレージ
  • パスパターン:(appinsights name)_(your instrument key)/Requests/{date}/{time}
  • 日付の形式:YYYY-MM-DD
  • 時刻の形式:HH

次に、Stream Analytics の output として以下を設定する。サーバ名、データベース名、ユーザ名、パスワード等は適切に設定すること。

  • 名前:output-sqldb
  • シンク:SQL データベース
  • テーブル:RequestTable

上記の設定後、以下のクエリを Stream Analytics に設定する。

    SELECT 
      flat.ArrayValue.name as requestName
      ,flat.ArrayValue.responseCode as responseCode
      ,flat.ArrayValue.success as requestSuccess
      ,flat.ArrayValue.url as requestUrl

      ,A.context.data.eventTime as eventTime
      ,A.context.data.isSynthetic as isSynthetic
      ,A.context.data.samplingRate as samplingRate

      ,A.context.device.browser as browser
      ,A.context.device.browserVersion as browserVersion
      ,A.context.device.deviceModel as deviceModel
      ,A.context.device.deviceName as deviceName
      ,A.context.device.id as deviceId
      ,A.context.device.locale as locale
      ,A.context.device.network as network
      ,A.context.device.oemName as oemName
      ,A.context.device.os as os
      ,A.context.device.osVersion as osVersion
      ,A.context.device.roleInstance as roleInstance
      ,A.context.device.type as type

      ,A.context.location.clientip as clientIp
      ,A.context.location.continent as continent
      ,A.context.location.country as country
      ,A.context.location.province as province
      ,A.context.location.city as city

      ,A.context.operation.id as operationId
      ,A.context.operation.name as operationName
      ,A.context.operation.parentId as parentId

      ,A.context.session.id as sessionId
      ,A.context.session.isFirst as sessionIsFirst
    INTO
      [output-sqldb]
    FROM [input-blob] A
    CROSS APPLY GetElements(A.request) AS flat

上記の完了後、Stream Analytics ジョブを実行すると SQL Database 側にデータが格納されているはずだ。

実行結果の確認

今回は Visual StudioSQL Server オブジェクトエクスプローラから確認する。サーバに接続し、テーブルを右クリックして データ表示 を選択した結果は以下となる。
f:id:waritohutsu:20151108213952p:plain

さらに以下のクエリを発行し、国別のアクセスを確認してみる。

SELECT COUNTRY, COUNT(COUNTRY) as NUM FROM [dbo].[RequestTable] GROUP BY COUNTRY

f:id:waritohutsu:20151108214004p:plain

なぜか United States のリクエストがあり若干衝撃だが、こういったデータの取得も可能になる。

*1:ただし、依存関係の追跡 (および IIS パフォーマンス カウンター)も監視する場合には追加で手順が必要な点も記載がある

Eclipse で Maven Project を使ってる際にはまるかもしれない TIPS

EclipseMaven Project を使っている場合、Eclipse の設定でしばしばはまるので備忘録として書いておく。

Unbound Classpath Variable: 'M2_REPO/... が発生する

Eclipse のクラスパス変数に対して M2_REPO が設定できていないケース。以下のコマンドで対応できる。

>mvn -Declipse.workspace=c:\opt\workspace eclipse:configure-workspace

調べ方によっては eclipse:add-maven-repo を実行する方法が出てくるが、Maven 2.1 辺りからコマンドが変わったそうな。

jsp が javax.servlet.HttpServlet が見つからないと怒る

以下のサイトを参考にビルドパスに Web App Libraries を追加する。
http://stackoverflow.com/questions/4440479/how-can-i-add-jar-files-to-the-web-inf-lib-folder-in-eclipse

WEB-INF/lib に追加したはずの Maven Dependency ライブラリが追加されず、Tomcat 起動時にエラーになる

Eclipse に依存 jar が追加されていない。Eclipse のパッケージエクスプローラ等から [Maven - Update Project...] を実行する。

AppInsights と Power BI を使って Java Web アプリの例外発生をリアルタイム監視する

前回の記事である Application Insights と Power BI を使って Java の Web アプリの PageView を監視する にて、Application Insights の連続エクスポート/Stream Analytics の Power BI 出力を活用して PageView のログが簡単に可視化できることが分かった。
今回は Application Insights に例外ログを出力して Power BI に可視化するまでの手順を紹介する。なお、Application Insights の連続エクスポート、Stream Analytics の設定、Power BI 設定の細かな手順については前回の記事を参照してほしい。

Java アプリケーションの記載

以下の様に入力文字列が空の場合に IllegalArgumentException を出力するコードを作成する。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<script type="text/javascript">
        var appInsights=window.appInsights||function(config){
            function r(config){t[config]=function(){var i=arguments;t.queue.push(function(){t[config].apply(t,i)})}}var t={config:config},u=document,e=window,o="script",s=u.createElement(o),i,f;for(s.src=config.url||"//az416426.vo.msecnd.net/scripts/a/ai.0.js",u.getElementsByTagName(o)[0].parentNode.appendChild(s),t.cookie=u.cookie,t.queue=[],i=["Event","Exception","Metric","PageView","Trace"];i.length;)r("track"+i.pop());return r("setAuthenticatedUserContext"),r("clearAuthenticatedUserContext"),config.disableExceptionTracking||(i="onerror",r("_"+i),f=e[i],e[i]=function(config,r,u,e,o){var s=f&&f(config,r,u,e,o);return s!==!0&&t["_"+i](config,r,u,e,o),s}),t
        }({
            instrumentationKey:"<my instrumentation key>"
        });
        
        window.appInsights=appInsights;
        appInsights.trackPageView();
    </script>
<title>AppInsights-site index page</title>
</head>
<body>
	<h2>ポストしてみる</h2>
	<form method="post" action="<%=request.getContextPath()%>/home">
		文字列: <input name="message" type="text" placeholder="文字列を入力してください" />
		<br /> <input type="submit" value="送信 " />
	</form>
</body>
</html>
package com.mydomain;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.microsoft.applicationinsights.TelemetryClient;

@WebServlet("/home")
public class HomeServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	private static final Logger logger = LogManager.getLogger(HomeServlet.class);

	/**
	 * @see HttpServlet#HttpServlet()
	 */
	public HomeServlet() {
		super();
		// TODO Auto-generated constructor stub
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		TelemetryClient telemetryClient = new TelemetryClient();

		String message = request.getParameter("message");
		if (message == null || message.length() == 0) {
			IllegalArgumentException ex = new IllegalArgumentException("please input message");
			telemetryClient.trackException(ex);
			logger.warn("input warning");
		}
		RequestDispatcher dispatch = request.getRequestDispatcher("/second.jsp");
		dispatch.forward(request, response);
	}
}

非常にシンプルなコードなので、特に解説は不要だと思う。

Stream Analytics を設定する

前回の記事を参考にインプットに BLOB ストレージ、アウトプットに Power BI を指定する。特に Stream Analytics のインプットのパターンは以下となる。

  • プレフィックスのパターン: forpowerbiappinsights_/Exceptions/{date}/{time}
  • 日付形式: YYYY-MM-DD
  • 時刻形式: HH

そんな BLOB の格納される Exception のログは以下になる。

{  
   "basicException":[  
      {  
         "assembly":"Unknown",
         "exceptionType":"java.lang.IllegalArgumentException",
         "outerExceptionType":"java.lang.IllegalArgumentException",
         "outerExceptionThrownAtMethod":"com.mydomain.HomeServlet.doPost",
         "outerExceptionThrownAtAssembly":"",
         "failedUserCodeMethod":"com.mydomain.HomeServlet.doPost",
         "failedUserCodeAssembly":"",
         "exceptionGroup":"java.lang.IllegalArgumentException at com.mydomain.HomeServlet.doPost",
         "id":"Representative",
         "typeName":"java.lang.IllegalArgumentException",
         "handledAt":"UserCode",
         "count":1,
         "method":"com.mydomain.HomeServlet.doPost",
         "problemId":"",
         "outerExceptionMessage":"please input message"
      },
      {  
         "parsedStack":[  
            {  
               "method":"com.mydomain.HomeServlet.doPost",
               "fileName":"HomeServlet.java",
               "level":0,
               "line":78
            },
            {  
               "method":"javax.servlet.http.HttpServlet.service",
               "fileName":"HttpServlet.java",
               "level":1,
               "line":648
            },
            <中略>

上記の "basicException" 内にある "exceptionType" を取得するクエリは以下となる。"parsedStack" 部分をスキップしないと空白の count(*) が出力されるため、HAVING 句で "basicException" の無い項目はスキップしている。

    SELECT
      flat.ArrayValue.exceptionType,
      count(*)
    INTO
      [pbi-exception-output]
    FROM
      [export-input] A
    OUTER APPLY GetElements(A.[basicException]) as flat
    GROUP BY TumblingWindow(minute, 1), flat.ArrayValue.exceptionType
    HAVING LEN(flat.ArrayValue.exceptionType) > 0

Power BI でのリアルタイム表示

Power BI にログインし、Stream Analytics のアウトプットで指定したデータセットが表示されていることを確認する。ポータルから表示方法を設定し、"以下の様に ダッシュボード" にレポートを作成する*1
f:id:waritohutsu:20151012215131p:plain

体感値として、1分弱の時間で Application Insights の連続エクスポートが永続ログを吐き出し、2分~3分で Power BI 側に情報が反映される様だ。

*1:ダッシュボードに保存しないと、リアルタイムでの更新が確認できない