Trace に出力した内容を Application Insights に保存する

はじめに

セルフホストしてる WCF サービスで、例外が発生したら Application Insights に記録したい。ただ、WCF 用のパッケージはラボの段階で、しかもリポジトリアーカイブされている。開発されていない。

github.com

今から TelemetryClient を使って Application Insights に送信するコードを書いて回るほどの日程は無い。というか死にゆく WCF のためにそこまでリソースを使いたくない。

そういえばトレースを出力するコードは、いくらか仕込んでいたな。トレースを Application Insights に送信するパッケージはちゃんとある。

www.nuget.org

こいつを使えば良さそうだ。

サービスコントラクトを定義

using System.ServiceModel;

namespace WcfAppInsights.Shared
{
    [ServiceContract]
    public interface IEchoService
    {
        [OperationContract]
        string Echo(string message);
    }
}

WCF サービスを実装

NuGet で Microsoft.ApplicationInsights.TraceListener をインストールし、サービスクラスを実装する。引数で渡された文字列をエラーとしてトレースに出力するだけ。Application Insights にちゃんと送信されるよう、即フラッシュしておく。

using System;
using System.Diagnostics;
using System.ServiceModel;
using WcfAppInsights.Shared;

namespace WcfAppInsights.Server
{
    class Program
    {
        static void Main(string[] args)
        {
            var host = new ServiceHost(typeof(EchoService));
            host.AddServiceEndpoint(
                typeof(IEchoService),
                new NetTcpBinding(),
                "net.tcp://localhost:8081/EchoService");
            host.Open();
            Console.WriteLine("Enter で終了");
            Console.ReadLine();
            host.Close();
        }
    }

    public class EchoService : IEchoService
    {
        public string Echo(string message)
        {
            Trace.TraceError(message);
            Trace.Flush();

            return message;
        }
    }
}

App.config を記述

トレースを Application Insights に送信するために、Microsoft.ApplicationInsights.TraceListener.ApplicationInsightsTraceListener を使うように構成する。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <trace>
      <listeners>
        <add name="myAppInsightsListener" type="Microsoft.ApplicationInsights.TraceListener.ApplicationInsightsTraceListener, Microsoft.ApplicationInsights.TraceListener" />
      </listeners>
    </trace>
  </system.diagnostics>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  </startup>
</configuration>

ApplicationInsights.config を追加

ApplicationInsightsTraceListener というか TelemetryClient は、コードで InstrumentationKey を指定しなかったら、ApplicationInsights.config に記述してあるものを使うようだ。

<?xml version="1.0" encoding="utf-8"?>
<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings">
  <InstrumentationKey>your-instrumentation-key</InstrumentationKey>
</ApplicationInsights>

ビルド時にちゃんと出力先にコピーされるようにしておく。うっかり忘れて気づくまで時間かかった。

WCF クライアントを実装

using System;
using System.ServiceModel;
using WcfAppInsights.Shared;

namespace WcfAppInsights.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            var channel = ChannelFactory<IEchoService>.CreateChannel(
                new NetTcpBinding(),
                new EndpointAddress("net.tcp://localhost:8081/EchoService"));

            var reply = channel.Echo("Hello Application Insights");

            Console.WriteLine(reply);

            Console.WriteLine("Enter で終了");
            Console.ReadLine();
            ((IClientChannel)channel).Close();
        }
    }
}

実行結果

WCF サーバーと WCF クライアントを実行して、少し待つと、トレースに出力したメッセージが Application Insights に送信されていることを確認できた。

f:id:griefworker:20210510151249p:plain

おわりに

Application Insights の使い方を詳しく解説してくれる書籍が欲しい。技術同人誌でもいい。

Xenoblade Definitive Edition

Rebuild.fm で評判を聞いて、いつかプレイしたいと思っていた『Xenoblade』が、Definitive Edition として Nintendo Switch で発売されていたので購入した。ゼノシリーズはゼノギアスゼノサーガをプレイしたことあるけど、どれも途中までしかやっていないので、クリアした最初のゼノシリーズになる。世界観的には、ゼノサーガよりはゼノギアスに近いかも。

アーツにスキルツリー、ジェムクラフトとシステムてんこ盛りで、慣れるまでだいぶ時間かかった。詰め込みすぎでは?と思わないでもない。ゼルダはあれだけ自由度あったけど、まだシンプルだったな。先にプレイしたのもあり、どうしてもゼルダ基準になってしまう。

物語は機神兵への復讐から始まり、巨神界と機神界の対決、最後には神との対決と、進むにつれ世界中を巻き込んでスケールアップしていく。本質的には、神というか、たった1人の人物に振り回された話だったな。メタな視点のシーンがあったので、上位世界とかあるのかもしれない。続編で触れられるだろうか。

そんなメインストーリーを進めつつも、プレイの大半は街やフィールドで片っ端からクエストの依頼を受けてクリアしていた。クエストの中には報酬で経験値が入るものがあり、物語終盤までは、しらみつぶしにクエストこなしていけば、自ずとレベル上がっていった。意識的にレベル上げしたのは最終章ぐらいだ。ゼルダほどどこでも行けるわけではないけど、ストーリー放置して、寄り道したり、今まで訪れた場所に再び行ける自由度はあった。

登場人物はシュルクやフィオルン、ラインら若者だけでなく、ダンバンでさえもまだ青く感じてしまうのは、自分の趣味嗜好から離れている故か、はたまた年か。皆真っ直ぐで眩しい。個人的には、フィオルンよりメリア派。マルチエンディングだったら、メリアの評価をひたすら上げただろうな。ただ、シュルクやフィオルン、ダンバンの攻撃性能が高くて、ラスボスまでほとんど力押しで倒せてしまったので、この3人は外せないか。

最初はシステムの複雑さに面食らって、自分に合わないと思ったものだけど、クリアした今では、名作の評判も納得。ゼノブレイド2やってもいいかなと思えた。

WCF の BasicHttpBinding を使ったセルフホストで HTTPS を構成してみた

はじめに

WCFHTTPS を構成できるか試すことになった。 しかも IIS を使わず、セルフホストで。

サービスコントラクトの定義

using System.ServiceModel;

namespace WcfHttpsSample.Shared
{
    [ServiceContract]
    public interface ISampleService
    {
        [OperationContract]
        string Echo(string message);
    }
}

WCF サービスの実装

using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using WcfHttpsSample.Shared;

namespace WcfHttpsSample.Server
{
    class Program
    {
        static void Main(string[] args)
        {
            var baseAddress = new Uri($"https://{Dns.GetHostEntry("").HostName}:8080");
            var host = new ServiceHost(
                typeof(SampleService),
                baseAddress);

            // https で待ち受けられるようにする
            var binding = new BasicHttpBinding();
            binding.Security.Mode = BasicHttpSecurityMode.Transport;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

            host.AddServiceEndpoint(
                typeof(ISampleService),
                binding,
                "Sample");

            host.Open();

            Console.WriteLine("Enter で終了");
            Console.ReadLine();

            host.Close();
        }
    }

    public class SampleService : ISampleService
    {
        public string Echo(string message) => message;
    }
}

WCF クライアントの実装

using System;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using WcfHttpsSample.Shared;

namespace WcfHttpsSample.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            // 自己署名証明書を使って試す場合は証明書の検証をしない
            ServicePointManager.ServerCertificateValidationCallback =
                new RemoteCertificateValidationCallback((a, b, c, d) => true);

            var endpointAddress = new EndpointAddress(
                $"https://{Dns.GetHostEntry("").HostName}:8080/Sample");

            // https で通信できるようにする
            var binding = new BasicHttpBinding();
            binding.Security.Mode = BasicHttpSecurityMode.Transport;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

            var channel = ChannelFactory<ISampleService>.CreateChannel(
                binding,
                endpointAddress);

            var reply = channel.Echo("Hello World!");
            Console.WriteLine(reply);

            ((IChannel)channel).Close();
            Console.WriteLine("Enter で終了");
            Console.ReadLine();
        }
    }
}

SSL 証明書をインポート

HTTP.sys から見られるよう、ローカルマシンの個人にインポートする。 ローカルマシンにインストールしておかないと、SSL 証明書をポート番号にバインドできない。 そのせいで数時間ハマった。

SSL 証明書をポート番号にバインド

このままだと、クライアントからアクセスした際に下記のような例外が発生する。

System.ServiceModel.CommunicationException: 'https://ホスト名:8080/Sample に対する HTTP 要求の発行中にエラーが発生しました。 この原因としては、HTTPS ケースの HTTP.SYS でサーバー証明書が正しく構成されていないこと、 またはクライアントとサーバーの間でセキュリティ バインドが整合していないことが考えられます。

下記のコマンドを実行して、SSL 証明書をポート番号にバインドする必要がある。

netsh http add sslcert ipport=0.0.0.0:8080 certhash=<証明書のサムプリント> appid={<適当な Guid>}

appid は適当な GUID で良いみたい。

実行結果

f:id:griefworker:20210423103301p:plain

おわりに

早く gRPC に移行したい。

Node のバージョン管理を nvs に移行した

Node のバージョン管理には nvm を使っていたけど、クロスプラットフォームnvs の存在を知り乗り換えた。

github.com

Windows では msi をダウンロードしてインストールするだけ。

Windows では PowerShell Core を使っているので、<ユーザー>\Documents\PowerShell\Microsoft.PowerShell_profile.ps1nvs のバージョン自動切り替え機能を有効にする設定を記述しておく。

nvs auto on

あとは、プロジェクトのディレクトリ内に .node-version を配置しておけば、シェルで移動したとき自動で Node のバージョンを切り替えてくれる。便利。

React の SPA を Azure App Service でホストする

はじめに

React で実装した Single Page Application(SPA)を Azure App Service でホストしたい。Azure Static Web Apps ではなく、Web Apps。というのも、大人の事情で、GitHub や Azure DevOps が使えず、ローカル Git からデプロイするしかないので。

Kudu のカスタムデプロイスクリプトを書いて、なんとか実現できた。

express で React アプリをサーブするために server.js を作成

不本意だけど、express をプロジェクトに追加し、エントリポイントとして server.js を用意する。

const express = require('express');
const path = require('path');
const app = express();
const port = process.env.PORT || 3000;

app.use(express.static(path.join(__dirname, 'build')));

app.get('/*', function (req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(port);

.deployment を Kudu から入手

Kudu から入手できるやつそのまま。

[config]
command = deploy.cmd

deploy.cmd を Kudu から入手して改変

Kudu から入手できるファイルに React アプリのビルドを追加した。 「Build React SPA」の箇所がそう。

@if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off

:: ----------------------
:: KUDU Deployment Script
:: Version: 1.0.17
:: ----------------------

:: Prerequisites
:: -------------

:: Verify node.js installed
where node 2>nul >nul
IF %ERRORLEVEL% NEQ 0 (
  echo Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment.
  goto error
)

:: Setup
:: -----

setlocal enabledelayedexpansion

SET ARTIFACTS=%~dp0%..\artifacts

IF NOT DEFINED DEPLOYMENT_SOURCE (
  SET DEPLOYMENT_SOURCE=%~dp0%.
)

IF NOT DEFINED DEPLOYMENT_TARGET (
  SET DEPLOYMENT_TARGET=%ARTIFACTS%\wwwroot
)

IF NOT DEFINED NEXT_MANIFEST_PATH (
  SET NEXT_MANIFEST_PATH=%ARTIFACTS%\manifest

  IF NOT DEFINED PREVIOUS_MANIFEST_PATH (
    SET PREVIOUS_MANIFEST_PATH=%ARTIFACTS%\manifest
  )
)

IF NOT DEFINED KUDU_SYNC_CMD (
  :: Install kudu sync
  echo Installing Kudu Sync
  call npm install kudusync -g --silent
  IF !ERRORLEVEL! NEQ 0 goto error

  :: Locally just running "kuduSync" would also work
  SET KUDU_SYNC_CMD=%appdata%\npm\kuduSync.cmd
)
goto Deployment

:: Utility Functions
:: -----------------

:SelectNodeVersion

IF DEFINED KUDU_SELECT_NODE_VERSION_CMD (
  :: The following are done only on Windows Azure Websites environment
  call %KUDU_SELECT_NODE_VERSION_CMD% "%DEPLOYMENT_SOURCE%" "%DEPLOYMENT_TARGET%" "%DEPLOYMENT_TEMP%"
  IF !ERRORLEVEL! NEQ 0 goto error

  IF EXIST "%DEPLOYMENT_TEMP%\__nodeVersion.tmp" (
    SET /p NODE_EXE=<"%DEPLOYMENT_TEMP%\__nodeVersion.tmp"
    IF !ERRORLEVEL! NEQ 0 goto error
  )
  
  IF EXIST "%DEPLOYMENT_TEMP%\__npmVersion.tmp" (
    SET /p NPM_JS_PATH=<"%DEPLOYMENT_TEMP%\__npmVersion.tmp"
    IF !ERRORLEVEL! NEQ 0 goto error
  )

  IF NOT DEFINED NODE_EXE (
    SET NODE_EXE=node
  )

  SET NPM_CMD="!NODE_EXE!" "!NPM_JS_PATH!"
) ELSE (
  SET NPM_CMD=npm
  SET NODE_EXE=node
)

goto :EOF

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Deployment
:: ----------

:Deployment
echo Handling node.js deployment.

:: 1. KuduSync
IF /I "%IN_PLACE_DEPLOYMENT%" NEQ "1" (
  call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_SOURCE%" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd"
  IF !ERRORLEVEL! NEQ 0 goto error
)

:: 2. Select node version
call :SelectNodeVersion

:: 3. Install npm packages
IF EXIST "%DEPLOYMENT_TARGET%\package.json" (
  pushd "%DEPLOYMENT_TARGET%"
  call :ExecuteCmd !NPM_CMD! install --production
  IF !ERRORLEVEL! NEQ 0 goto error

  :: Build React SPA
  call :ExecuteCmd !NPM_CMD! run build
  IF !ERRORLEVEL! NEQ 0 goto error

  popd
)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
goto end

:: Execute command routine that will echo out when error
:ExecuteCmd
setlocal
set _CMD_=%*
call %_CMD_%
if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_%
exit /b %ERRORLEVEL%

:error
endlocal
echo An error has occurred during web site deployment.
call :exitSetErrorLevel
call :exitFromFunction 2>nul

:exitSetErrorLevel
exit /b 1

:exitFromFunction
()

:end
endlocal
echo Finished successfully.

デプロイ

git push してからビルドが終わるまでの間は、アプリに繋がらない。 ステージング用のデプロイスロットを用意し、ステージング環境にデプロイした後、本番環境とスワップする必要がある。

git push staging main:master

おわりに

Azure Static Web Apps が使える場合は使いましょう。

パン シャルマン

揚げパンが評判と聞いて、早良区田隈にある『パン シャルマン』に行ってみた。気合入れて開店後ほぼすぐに入店したら、パンはまだ出揃っておらず、少し待つことに。揚げパンは並んでいたけど、ミルクスティックはまだだった。聞いたらすぐ作ってもらえたので、待ったといっても5分くらい。待っている間に他のパンを選んでいれば問題ない。

f:id:griefworker:20210405231855j:plain

お目当てその1の揚げパンは、きな粉たっぷりでこぼさないように注意が必要。自分はこぼした。揚げパンを食べるのなんて、中学校の給食以来かも。パンだけどなんかジューシー。きな粉もいいけどグラニュー糖かけたバージョンも売ってくれないかなぁ。

f:id:griefworker:20210405231904j:plain

子どもがチョイスしたシナモンロールは、ナッツが香ばしくてグッド。温めなくても十分すぎるくらい美味だった。

f:id:griefworker:20210405231913j:plain

お目当てその2のミルクスティックは、濃厚練乳クリームたっぷりで、これは間違いない。出来上がるの待ったかいがあった。パンは今回買った中で一番のハード系。まさにスティックだな。

f:id:griefworker:20210405231923j:plain

チョコチップメロンパンは、表面のクッキー生地がサクサクで、妻に好評価だった。

f:id:griefworker:20210405231931j:plain

子ども用にはトトロのパン。中はチョコクリームだった。関係ないけど、トトロのパンがあるから、カオナシのパンもあるかもね、といったら子どもが怖がって買いに行きたがらなくなったのは困った。いやまぁ自業自得なのだけど。

f:id:griefworker:20210405231939j:plain

アンパンマンのパンも、中はチョコクリームだった。アンパンマンじゃなくて、チョコパンマンだな。中身を聞かずに買ったのは失敗だったな。チョコ好きの子供も、さすがに食べきれなかった。せめてアンパンマンとトトロのどちらかが、カスタードクリームだったらよかったのに。

f:id:griefworker:20210405231947j:plain

今度は、パンが出揃ってる時間帯に買いに行って、違うパンを買ってみたい。ホワイトチョコとピスタチオのパンも目当てだったけど、まだ出てなかったし。リベンジしたい。普段使いに良さそうで、こういうパン屋が家の近くに欲しいと、心底思う。

関連ランキング:パン | 賀茂駅

ストロベリーフェチ

福岡の情報番組で紹介されていたのを見た子供が食べたがったので、キャナルシティに行く用事のついでに立ち寄った。

f:id:griefworker:20210412192604j:plain

イチゴだけでなくブドウも食べたいと欲張ったので、イチゴ&ブドウを購入。表面の飴は薄くパリパリ、イチゴは予想以上にジューシーで驚いた。さすが、フェチと言うだけある。ブドウは美味しいんだけど、中の種が余計だったな。種なしの品種を使って欲しいところ。イチゴオンリーの方がオススメ。

f:id:griefworker:20210412192613j:plain

関連ランキング:スイーツ(その他) | 祇園駅中洲川端駅渡辺通駅