Elastic CloudにTraffic filtersがキタ

概要

www.elastic.co

今まで、Elastic Cloudは接続元のIP縛りできないんですか、と何回かそういうお声を聞いたことがあります。

ついに実現されたそうです。

おれ専用Elasticsearch Serviceができるようになります。

Tips!

Traffic Filterのルールについては、Deploymentごとに作るのではなく、Accountの下で作るようです。

ルールの作成

Accountの下にあるTraffic filtersを選択し、右の方にある「Create filters」を押下します。

f:id:tsgkdt:20200717214922p:plain

Ruleを記載してCreateをしましょう。この辺りはAWSでセキュリティグループ、AzureでVNETのネットワーク設定をしたことがあるひとは、すぐ設定できるかと思います。

f:id:tsgkdt:20200717215350p:plain

Add to deploymentsにチェックを入れておくことで、規定のルールとすることができるようですね。うっかり作って、どこからでも繋がる状態になってインシデントや!となるのを避けられそうです。

Deploymentに対しての適用

ルールを適用したいDeploymentを選択し、SecurityメニューからApply filterを選択しましょう。

f:id:tsgkdt:20200717215749p:plain

プルダウンから設定したいfilterを選択肢、Applyボタンを押下します。

f:id:tsgkdt:20200717220031p:plain

適用したルールを削除する場合は、×ボタンをクリックします。

f:id:tsgkdt:20200717220203p:plain

以上で、特定のIPアドレスからのみ接続を許可することができました。

終わりに

これまで社内システムのルールの都合で、接続元、IP制限ができないからElastic Cloudが使えない!という人にとって、とても良い機能追加なのではないでしょうか。

では良いElasticライフを。(最近あんまり触れてないけど、久しぶりに試してみたくなる機能追加でした)

退職しました

はじめに

男もすなる退職エントリーといふものを、女もしてみむとてするなり。 と紀貫之が言ったかどうかは知りませんが、令和2年3月19日付で新卒以来18年勤めた会社を退職します。

記念パピコ世代としては、記念にこれを残しておくものです。

なぜこの期に及んで転職なのか

いま、ちょうど40歳です。 何ごともなく60、70まで生きるだろうと根拠もなく思っていたところ、およそ半年前ぐらいに同期入社の方が急逝されました。
40過ぎの男が急逝する。これが自身のライフプランを見直す大きなきっかけになりました。

安定した職場、安定した同僚、安定した仕事、安全(セーフティ)という名の愉悦はあるけれども、 それを何年も続けた結果、50前後になったときに、世の中の他の人と戦っていけるのかどうか、 また、そのときも楽しみをもって仕事をやれているだろうか、そこに対する疑問が出てきました。

今の会社に専属というか隷属することでしか生き永らえない、生殺与奪の権を会社が持つのは困ったことです。

オプーナを買う権利をやろう」という話が一昔前にありましたが、コールオプションを持つことはリスクヘッジになります。

この場合で言えば、いつでも会社を乗り換えられるように自分をしておくこと、いざとなれば会社に見切りをつけられるという選択肢を持つこと、それが良いと考えました。

定年がどんどん延長され、年金支給開始年齢もあがっていくことが予想される中、まだまだあと30年ぐらいは働かんとあかんわけです。

とするならば、今からでも遅くない、むしろここから第2章、半荘でいえば南場、起承転結でいえばまだまだ承ぐらい、心機一転ねじを締めなおしてみようと。

史記の李斯列伝には、ネズミのくだりがあり、結局人はその居るところ、環境がすべてだ、というような部分がありました。 確かに 環境は大事です。また、筋トレも同じ種目ばかりやっていると刺激が入らなくなってくると聞きました。
より刺激的な環境を求めて、周りを変えるのではなく自ら変化することを選択しました。

フツーのおじさん転職活動はつらいのか

ぬるま湯にどっぷり浸かり、イイ感じに出来上がっている私に対して、転職の際の面接官の目は厳しかったです。

内国法人であれば、年齢というのはやはり重要な数字であるようで、入社後のキャリアパスが用意できそうにない、という理由で断られたところもありました。

あるいは、管理職経験のない人は・・・という理由で断られたところもあります。
本当か嘘かは分かりません。

それでも、捨てる神あれば拾う神あり、というところで、とんとん拍子で面接が進み、ありがたく次の職場を決めることができました。
まじめに王道を進んでみようかと思います。

次は何するのか

いわゆるSIerみたいなアプリケーション、システム開発のテクノロジー系の部門に配属になるようです。
職場は案件ごとによって違うらしいので、東京近郊のどこからしいです。

おわりに

会社支給のPCを今日で返却してしまうので、Elastic Stackの勉強はしばらくお休みになります。 4月1日から新しい会社のはずですが、コロナのおかげでオリエンテーションも中途社員向けのトレーニングも全部オンラインでされるそうです。大変ですね。

Elastic Stack 7.6.0リリース記念 MetricbeatのAzureモジュールを紹介したい

概要

先日リリースされました Metricbeat 7.6.0では、いくつかの機能が追加されています。

www.elastic.co

  • Billing and usage for AWS
  • Monitoring Google Cloud Platform
  • Azure Storage monitoring

AWSでの課金情報、GCPでのモニタリング、Azureでのストレージモニタリングとありますが、今回はAzureのストレージ部分について非常に気になったので、これを紹介したいです。

正常に稼働しているか、という観点ではSLAの観点で気になりますし、ストレージの使用量は課金に直結しますから、モニタリングしておく必要があろうかと思います。

そこで、今回は以下のような背景の方には、特に刺さる内容になるのではと思い、紹介を致します。

  • Azure cliPowershellで自作することは避け、慣れたツールで見たい
  • Azure Monitorでも良いんだけれども、Elastic Stackに集約する方がメリットがあると感じる人向け

Azure Moduleについて

Azure Moduleについてのドキュメントはこちらになります。 現時点では、Azure Moduleそのものがbeta版となっていることに留意してください。

azure module | Metricbeat Reference [7.6] | Elastic

事前準備

Azureへの接続時にService Principalを使いますので、これを作成しておく必要があります。

Service Principalってなんじゃらホイ、という方は以下の公式ドキュメントを片手に作業を進めると良いです。

ポータルで Azure AD アプリとサービス プリンシパルを作成する - Microsoft identity platform | Microsoft Docs

作成したService Principalには、ロールを割り当てておきましょう。

Users will have to make sure the roles assigned to the application contain at least reading permissions to the monitor data, more on the roles here

可能であればMetricbeat用のService Principalを作成し、権限を必要最低限のものに設定しておくことが望ましいです。 しかし、簡易に試したいだけ、という人は・・・「共同作成者」みたいな強いロールを割り当ててしまうと動作確認は可能です。

Azure Moduleの有効化

デフォルトでは、Azureモジュールは有効となっていないので、Azureモジュールを有効化します。

metricbeat modules enable azure

MetricbeatのAzureモジュールの設定

Azureモジュールの設定ファイルは、{metricbeat}/module.d/azure.yml になります。 先の有効化のコマンドで、azure.yml.disabledがazure.ymlになっただけです。

事前準備で作成したService Principalの情報を埋めていきましょう。

- module: azure
  metricsets:
  - storage
  enabled: true
  period: 300s
  client_id: '*********************'   # Service Principalからひっぱってくる情報
  client_secret: '*********'  # Service Principalからひっぱってくる情報
  tenant_id: '**************'  # テナントID
  subscription_id: '*******************' # サブスクリプションID
  refresh_list_interval: 600s

ひな形にある設定ファイルですと、resoucesのセクションがありますが、取得する条件を絞り込むときの要素で必須ではないため、今回は外しておきました。

リソースIDやグループで絞ると、取得対象が絞られるのと、またBlobやFile、Queueなどの種類も絞ることができます。

Metricbeatの実行

metricbeat -e

f:id:tsgkdt:20200215220729p:plain

azure storage metricsetがベータ版ですよ、というWARNINGが出力されます。 しかし、それは100も承知なので無視して次に進みましょう。

Kibanaで用意されているダッシュボード

metricbeat setup

このコマンドで設定されるダッシュボードには、Azure用のダッシュボードがいくつか含まれています。

f:id:tsgkdt:20200215221936p:plain

今回は、ストレージの情報を見たいので、[Metricbeat Azure] Storage Overviewを選択することにしましょう。 このダッシュボードからコンテナ、キュー、ファイルなど他のダッシュボードにも遷移できますので、まずはOverviewを見るのが良いかと思います。

[Metricbeat Azure] Storage Overview

f:id:tsgkdt:20200216221258p:plain

ここでは各ストレージのメトリクスが表示されています。 ストレージアカウントごとのUsage(使用量)を始めとして、レイテンシやIngress/Egressなどが表示されています。

画面の左には、サブスクリプション、リソースグループ、ストレージアカウントのフィルター条件を設定できるパーツがあります。

注意点1

あまりに長い期間を検索条件として指定しますと、以下のようなエラーが出ます。

f:id:tsgkdt:20200216220338p:plain

The request for this panel failed Trying to create too many buckets. Must be less than or equal to: [10000] but was [10001]. This limit can be set by changing the [search.max_buckets] cluster level setting.

max_bucketsを大きくすることは、他への影響も考える必要が出てきそうです。

ですので、このダッシュボードでは、直近どうなのか、というところで確認するのが良く、長いタイムスパンで見る場合には、別途ダッシュボードを作って対応するのがよさそうです。

終わりに

Elastic APMやLog UIなどと併用すると、システムに何かあったときの調査が1つのツールで完結するために、よりやりやすくなるかと思います。

Azureの中の製品群で、上記のことはもちろん出来る内容ですが、既にElastic製品を使っていてさらに活用していきたい、という人には役立つモジュールかと思います。

AWSのbeatモジュールも開発が活発ですが、Azureも負けずに頑張って欲しいところです。

それでは、ごきげんよう

yyyy/MM/dd HH:mm:ssな文字列をISO8601の形式にして格納したい

概要

日付、Datetimeを示すフォーマットは多々ありますが、yyyy-MM-ddが標準なのか、yyyy/MM/ddが標準なのか、どっちだったっけ?ということはよくありますね。

今回は、Elasticseardchのフィールドの定義でdate型のフィールドなのだけども、格納時にはISO8601の形式にしておきたいが、Inputする文字列はそうでない(yyyy/MM/dd HH:mm:ssなときは)どうしようか、という話です。

確認環境

  • Elasticsearch 7.5.0

Input対象の文字列をそのまま入れるなら

2019/12/12 09:00:00 といった文字列を想定します。
これをdate型のフィールドに入れたければ、mappingの定義においてformatを指定しておくと良いです。

{
  "forum1212" : {
    "mappings" : {
      "properties" : {
         "testdate" : {
            "type" : "date",
            "format" : "yyyy/MM/dd HH:mm:ss"
          }
      }
   }
}

こうすることによって、文字列 -> 日付型 で格納がされますね。

ISO8601形式に変換する方法1

上のようなフォーマットを指定したものではなく、普通のdate(つまりISO8601形式)のものが良い場合は、どうしたらよいでしょうか。

フォーマットとしては、下のhogeフィールドのような定義のところに 2019/12/12 00:00:00といった文字列から生成されるdateを入れたいです。

{
  "forum1212" : {
    "mappings" : {
      "properties" : {
         "testdate" : {
            "type" : "date",
            "format" : "yyyy/MM/dd HH:mm:ss"
          },
         "hoge": {
           "type": "date"
         }
      }
   }
}

Ingest Pipeline

もっとも簡単な方法はIngest Pipelineでdate processorを使うことでしょう。

Date Processor | Elasticsearch Reference [master] | Elastic

dateプロセッサのformatsのところに文字列にあうフォーマット形式を書けば良いです。

PUT _ingest/pipeline/test
{
  "description": "test",
  "processors": [
    {
        "date" : {
          "field" : "testdate",
          "target_field" : "hoge",
          "formats" : ["yyyy/MM/dd HH:mm:ss"],
          "timezone" : "UTC"
        }
    }
  ]
}

Simulate

どのようになるかは、simulateをしてみましょう。

POST _ingest/pipeline/_simulate
{
  "pipeline": {
    "processors": [
      {
        "date" : {
          "field" : "testdate",
          "target_field" : "hoge",
          "formats" : ["yyyy/MM/dd HH:mm:ss"],
          "timezone" : "UTC"
        }
      }
    ]
  },
  "docs": [
    {
      "_index": "aaa",
      "_id": "id1",
      "_source": {
        "testdate": "2019/12/12 00:00:00"
      }
    }
  ]
}

そうしますと、結果はこのようになります。

{
  "docs" : [
    {
      "doc" : {
        "_index" : "aaa",
        "_type" : "_doc",
        "_id" : "id1",
        "_source" : {
          "testdate" : "2019/12/12 00:00:00",
          "hoge" : "2019-12-12T00:00:00.000Z"
        },
        "_ingest" : {
          "timestamp" : "2019-12-12T13:13:11.994933Z"
        }
      }
    }
  ]
}

hogeフィールドが、ISO8601形式になっていることが確認できましたね。

ISO8601形式に変換する方法2

dateプロセッサのアプローチが簡単かと思いますが、日付の加減算があるとか何らかの理由でスクリプトでアプローチする場合もあろうかと思います。

そんなときは、こうすれば良いでしょう。

PUT _ingest/pipeline/test
{
  "description": "test",
  "processors": [
    {
      "script": {
        "lang": "painless",
        "source": """
          String testdate = ctx['testdate'];
          DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
          LocalDateTime date = LocalDateTime.parse(testdate, dtf);
          ctx.hoge = date.format(DateTimeFormatter.ISO_DATE_TIME);
          
        """
      }
    }
  ]
}

ここでのポイントは2つあります。

  • DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss") で文字列をパースするフォーマッターを作るところ
  • LocalDateTimeを使うこと

ZonedDateTimeを使うとトラップにはまる

今回の文字列は yyyy/MM/dd HH:mm:ssという形式なので、タイムゾーンを表すものがありません。

そのため、ZonedDateTimeをうっかりつかってしまうとフォーマットの形式はあっているように見えても、ずっとparseでエラーになります。

タイムゾーンがないので、LocalDateTimeを使ってください。ここがポイントです。

        "caused_by" : {
          "type" : "date_time_parse_exception",
          "reason" : "Text '2019/12/12 09:00:00' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2019-12-12T09:00 of type java.time.format.Parsed",
          "caused_by" : {
            "type" : "date_time_exception",
            "reason" : "Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2019-12-12T09:00 of type java.time.format.Parsed",
            "caused_by" : {
              "type" : "date_time_exception",
              "reason" : "Unable to obtain ZoneId from TemporalAccessor: {},ISO resolved to 2019-12-12T09:00 of type java.time.format.Parsed"
            }
          }
        }

Simulate

LocalDateTimeを指定していることを再度確認して、Simulateを実行してみましょう。

POST _ingest/pipeline/_simulate
{
  "pipeline": {
    "processors": [
      {
       "script": {
        "source": """
          String testdate = ctx['testdate'];
          DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
          LocalDateTime date = LocalDateTime.parse(testdate, dtf);
          ctx.hoge = date.format(DateTimeFormatter.ISO_DATE_TIME);
        """
        }
      }
    ]
  },
  "docs": [
    {
      "_index": "aaa",
      "_id": "id1",
      "_source": {
        "testdate": "2019/12/12 00:00:00"
      }
    }
  ]
}

そうしますと、結果はこのように確認できます。

{
  "docs" : [
    {
      "doc" : {
        "_index" : "aaa",
        "_type" : "_doc",
        "_id" : "id1",
        "_source" : {
          "testdate" : "2019/12/12 00:00:00",
          "hoge" : "2019-12-12T00:00:00"
        },
        "_ingest" : {
          "timestamp" : "2019-12-12T13:26:01.755883Z"
        }
      }
    }
  ]
}

良い感じにyyyy-MM-ddの形式に変わりましたね。 もともとタイムゾーン指定がないので、UTCの日付である、という前提ですが必要があればscriptの中で9時間引くとか、足すとかあっても良いかと思います。

まとめ

dateプロセッサを使うのが楽、scriptを使うアプローチはLocalDateTimeなのか、ZonedDateTimeなのか、自分がどっちを使う(使える)のかをよく考えるのが大事。

では、Advent Calendar2019 n日目の記事をこれで終わります。(嘘)

ごきげんよう

Elasticsearchパフォーマンスの5つの基本

概要

フランスで行われたElasticのMeetupの動画がYoutubeにあがっていました。

www.youtube.com

公式だと、こちらのページで紹介されています。

www.elastic.co

フランスで行われているとはいえ、発表は英語でされています。
しかも、Youtubeなので自動字幕に翻訳機能をかませれば、日本語字幕を表示させて何となく話していることが分かるようにもなります。

せっかく視聴したので、メモをとったログをここに置いておくことにします。

OS編

ストレージ

パフォーマンスに関するところで基本のキ。

  • ディスクのパフォーマンスのベースラインを取得しておく(スループット、レイテンシ)
  • ベースラインを他のノードと比較してみることで、問題が分かることがある
  • ネットワークのファイルシステムは使わないで。(ストレージの仮想化も含みます)

ディスクパフォーマンスチューニングについて。

  • できることなら、SSDを使おう
  • HDDの方なら、index.merge.scheduler.max_thread.countを1に。SSDならデフォルト値でおk
  • RAIDを組むのであれば、RAID 0 (ストライピング)で最大パフォーマンスが出る

メモリ

  • ファイルシステムのキャッシュはElasticsearchにとって重要である。(可能であれば、メモリの半分以上をFSキャッシュにあてるのを推奨)
  • スワッピングが起きるのを計測する
  • OSのメモリだけでなく、JVMもモニタリングする必要がある

CPU

  • CPUも他のメトリクスと比較する必要があるため計測する必要がある。

    • GC
    • Thread pools
    • Index throttle time
    • ES performance metrics
  • CGroup throttlingのモニタリングも忘れないでください

JVM

Heapは大きすぎても、小さすぎてもダメ!

小さすぎると・・・

  • OutOfMemoryがおきる可能性がある
  • 短い間隔でメモリに負荷がかかり、スパイクがおきる
  • 頻繁な小さなGCによる停止(Elasticsearch自体のスループットの低下)
  • 使用できるIndexバッファやキャッシュが減る
  • Aggregationのパフォーマンスリミットに良くない影響が起きるかもしれない

大きすぎると・・・

  • GCの間隔がのびる(そのため1回あたりのGCによる停止が増える)

Cluster State

以下を確認してみよう。

GET /_cluster/state

アプリケーション

Elasticsearch自体のパフォーマンスについて。

Kibanaのモニタリングの画面で、Elasticsearchのクラスタ状況が確認できる。

  • クエリに対するキャッシュがあれば検索のレイテンシは小さい(が、ここではどんなクエリがキャッシュされるのか、されないのかについては触れない)
  • 負荷が高まって受け付けられない状態になると、リクエストはReject(拒否)される
  • リクエストを処理するのに使うバッファも、HeapSizeから取得するので、Heapメモリ大事

Logging

  • ログにはさまざまな情報が含まれている。
  • パフォーマンスを分析するには、時系列のグラフを確認するのが良い。(変化を確認することができるから)
  • OSの層からアプリケーションの層まで、いろんな観点で見られるようなモニタリングのツールがあると便利(Elastic APM

最後に

Finally, We're hiring...

Elastic APM .NETAgentで独自のSpanを仕込んでみる

概要

ASP.NETCoreのWebアプリケーションに、.NETAgentを導入した環境においてTransactionとSpanは標準で以下のようになっています。

  • Transaction

    • 外部からのHTTPリクエスト受信からレスポンス返却
  • Span

    • EntityFrameworkCoreによるSQLの実行
    • 外部へのHttpRequest送受信

しかし、実際のアプリケーションにおいては外部のリソースのHTTPによる呼び出しや、SQLの実行以外にも複雑なビジネスロジックがあったりなど、特定のメソッドの処理時間も取りたい!と思うときがありますね。

そんなときにも対応できるよう、Spanについては独自に定義することができます。
今回はそれをやってみましょう。

結論の図

論より証拠、やってみた結果の画面を貼ってみます。

f:id:tsgkdt:20191008214050p:plain

最初のHTTPGetのところと、一番下にある外部へのGetリクエスト送信は標準で出力される内容です。
注目いただきたいところは、その間に複数の処理が入っているところです。これが独自に定義したSpanで出力した内容の部分です。
メソッドのコールの様子、処理時間が可視化されている様子が分かります。

実践

用意するもの

  • VisualStudioで作成できるWebAPIのプロジェクト(ValuesControllerがあるやつです)
  • AspectInjector (APMの処理を書くために便宜的に使用)

参照するドキュメント

こちらにSpanの書き方が書いてあったのでこれを参考にします。

www.elastic.co

Span定義部分

Spanの定義を、各クラス、各メソッドに全部埋めていくのはちょっと手間がかかりそうでしたので、AspectInjectorを使って、アノテーションを付与することでメソッドに対してAPMの処理をかけられるようにしました。

そのアノテーション用のクラスの中で、Spanの定義を書きます。

using AspectInjector.Broker;
using Elastic.Apm.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;

namespace WebApplication3.Filter
{
    [Aspect(Scope.Global)]
    [Injection(typeof(TraceAspectAttribute))]
    public sealed class TraceAspectAttribute : Attribute
    {
        [Advice(Kind.Around, Targets = Target.Method)]
        public object TraceDuration([Argument(Source.Type)] Type type,
                                    [Argument(Source.Name)] string name,
                                    [Argument(Source.Target)] Func<object[], object> target,
                                    [Argument(Source.Metadata)] MethodBase methodBase,
                                    [Argument(Source.Arguments)] object[] arguments)
        {
            //現在のトランザクションの取得
            ITransaction transaction = Elastic.Apm.Agent.Tracer.CurrentTransaction;

            //トランザクション用のラベルの設定
            var label = transaction.Labels;
            if (!label.ContainsKey("target")) transaction.Labels.Add("target", "ElasticTest");

            //Spanの定義 名前をクラスのFullNameとメソッド名とし、処理のタイプをExecにする
            var result = transaction.CaptureSpan($"{type.FullName}-{name}",
                ApiConstants.ActionExec, (s) =>
            {
                //トランザクションではなく、Spanにもラベルを定義できる
                s.Labels.Add("MethodName", name);
                //メソッドの引数と中身をラベルに渡してみる
                methodBase.GetParameters().ToList().ForEach(x =>
                {
                    s.Labels.Add(x.Name, arguments[x.Position].ToString());
                });
                //実際のメソッドの呼び出し
                return target(arguments);

            });
            return result;
        }
    }
}

[Advice(Kind.Around, Targets = Target.Method)]

この部分、Kind.Aroundとすることでメソッドの開始と終了の周り(Around)に処理を書くことにします。

実際の本体の処理の呼び出し部分は、target(arguments);の部分です。他の部分はAPMに関する処理です。

コントローラ部分

VisualStudioが最初に作ってくれるコントローラの部分を流用して処理を追加しました。 本当は、DIしてインスタンスを持ってくるべきところですがそこはご容赦を。

        // GET api/values/5
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
        {
            var function = new ValueFunction();
            var waitFunction = new WaitFunction();
            var httpRequestFunction = new HttpRequestFunction();

            var result = function.FunctionFirst(id).ToString();
            waitFunction.Wait(id);
            httpRequestFunction.Request();

            return result;
        }

Span処理をかけたいロジック部分

何の重要な処理もなクラスとメソッドですが、クラスに先ほど作成したTraceAspectを指定しておきます。 こうすることで、FunctionFirst, FunctionSecond, FunctionThirdといった各種メソッドがSpanとして処理されるようになります。

FunctionFirst -> FunctionSecond -> FunctionThirdとそれぞれ内部で呼び出すような順序としました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApplication3.Filter;

namespace WebApplication3.Functions
{
    [TraceAspect] //AspectInjectorの対象にする
    public class ValueFunction
    {
        public int FunctionFirst(int val)
        {
            return FunctionSecond(val, DateTime.Now);
        }

        public int FunctionSecond(int val, DateTime date)
        {
            try
            {
                return FunctionThird(val, 2, "偶数のときはエラーにしたい");
            } catch 
            {
                return 0;
            }
        }

        public int FunctionThird(int val, int powNumber, string message = "")
        {
            if (val > 0 && val % 2 == 0)
            {
                throw new ElasticApmTestException($"与えられたメッセセージは「{message}」です");
            }
            return (int)Math.Pow(val, 2);
        }
    }

    public class ElasticApmTestException : Exception
    {
        public ElasticApmTestException(string message) : base(message)
        {
        }
    }
}

画面の見え方

トランザクションのラベル

Transaction画面でMetadataのリンクを押すか、トランザクションのところをクリックすることでDetail画面が表示されます。
そこで、指定したラベルが設定されていることが確認できます。

f:id:tsgkdt:20191008215319p:plain

Spanのラベル

出力した引数と、引数の中身がラベルとして表示されています。
もちろんパスワード文字列などを表示するのは良くないですが、特定の処理が遅いときなどの場合、どんな引数でそれが実行されたのか知りたい、と思うことはありそうです。
そんな用途に使えそうです。

f:id:tsgkdt:20191008215745p:plain

Exceptionの処理

先に示したValueFunctionでは、0でない偶数を渡すと例外が発生する実装が含まれていました。
でも、処理としてはcatchで握りつぶされ、0が返る実装となっています。

そんなときに、Elastic APMでは画面上どう見えるかというと、こうなります。 catchで握りつぶしているのに!

f:id:tsgkdt:20191008220804p:plain

トランザクション部分にある赤字で1の部分がリンクになっていて、そこからエラー確認画面に遷移できます。

f:id:tsgkdt:20191008221001p:plain

発生した例外メッセージが表示されていて、この部分をクリックするとさらに詳細画面に遷移します。

f:id:tsgkdt:20191008221239p:plain

ここではスタックトレースが確認できるので、どのソースでエラーが起きていたかを確認することができますね。

Exceptionの処理おまけ

catchしない例外がスローされたら、どうなるんでしょうか。

        public int FunctionSecond(int val, DateTime date)
        {
            //try
            //{
              return FunctionThird(val, 2, "偶数のときはエラーにしたい try-catchもしない");
            //} catch 
            //{
            //    return 0;
            //}
        }

このようにしてみて、同じようにエラー画面を見てみます。

f:id:tsgkdt:20191008221907p:plain

おや、エラーが1件かと思いきや4件になってますね。 同じように4の部分をクリックして、エラー画面に遷移します。

f:id:tsgkdt:20191008222033p:plain

たしかに4つ同じエラーで出ています。

それぞれの中身を見ると、StackTraceの中身が違うことからコントローラ、FunctionFirst, FunctionSecond, FunctionThirdでそれぞれ例外が渡っていくたびに出力されているように思えます。

ActionFilterで例外を処理したらどうなるんかな?という疑問が出てきましたが、いまこれを書いているスターバックスが22時30分で閉店ガラガラとなるので、今日はここまで。

ごきげんよう!!!!

Elastic APM .NETAgentで独自のSpanを仕込んでみる

概要

ASP.NETCoreのWebアプリケーションに、.NETAgentを導入した環境においてTransactionとSpanは標準で以下のようになっています。

  • Transaction

  • Span

    • EntityFrameworkCoreによるSQLの実行
    • 外部へのHttpRequest送受信