Azure関数カスタムハンドラ


導入


最近アジュールannounced general availability of custom handlers for Azure functions . カスタムハンドラーは、関数ホストからHTTPリクエストを受け取る軽量Webサーバーです.任意のプログラミング言語またはランタイムは、この要求を受信するWebサーバーを設定することができますカスタムハンドラとして使用することができます.この投稿では、goのカスタムハンドラを考慮し、特にこのカスタムハンドラからログオンします.

カスタムハンドラからのログの制限


カスタムハンドラを使用するにはいくつかの制限があります.ログは、公式の制限として記載されていませんが、一度はすぐにこの事実を発見する生産の実装のためのカスタムハンドラを探索を開始します.現在のソリューションでは、すべてのログを、関数のホストへの応答で返される文字列配列に格納できます.これらのログをあなたのログ先に挿入されます.
これはなぜ制限ですか?懸念される2つの主要なものがあります.
  • あなたは、各ログのメッセージフィールドを制御することができます.他のフィールドに影響を与えたり、カスタムフィールドを含めることはできません.これには重大度フィールドが含まれています.
  • すべてのログは、同じタイムスタンプに近いでしょう.これは、配列内のホストに返され、バッチ先にログ先に挿入されるためです.私が命令することができる限り、命令は保存されます、しかし、私はそれを当てにしません.
  • どうやってログ?


    Azureは将来のカスタムハンドラのログ機能を拡張しますが、それまでのオプションは何ですか?
    stdOUTへのログはまだ簡単です.fmt.Println(...) , しかし、それをすることはログがあなたがそうしなければならない何らかの文脈を失うでしょう.どのようなコンテキストですか?例えば、ログを作成した関数の呼び出しIDと名前.
    ログメッセージが既知の形式に従っている場合は、ログ配列のソリューションを使用してメッセージフィールドを検索してログを検索できます.あなたのログがJSON構造化されているならば、あなたは構文解析を扱うためにKusto質問言語で機能を使用することができます、そして、これは私が下でテキストで示すものです.

    カスタムJSONログの使用例


    このサンプルの完全なコードはhttps://github.com/mattias-fjellstrom/go-custom-handler-logging . このサンプルではenableForwardingHttpRequest to true , そして、これは関数ホストがHTTPリクエストを私のハンドラに転送せずに転送することを意味します.
    マイエントリーポイントmain.go このように見える
    package main
    
    import (
      "log"
      "net/http"
      "os"
    
      "github.com/mattias-fjellstrom/go-custom-handler/src/handlers"
    )
    
    func main() {
      port, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
      if !exists {
        port = "8080"
      }
    
      mux := http.NewServeMux()
      mux.HandleFunc("/api/greeting", handlers.GreetingHandler)
    
      log.Fatal(http.ListenAndServe(":"+port, mux)
    }
    
    と私のカスタムロガーを初期化するハンドラ関数
    package handlers
    
    import (
      "net/http"
      "time"
      "encoding/json"
    
      "github.com/mattias-fjellstrom/go-custom-handler-logging/src/logging"
    )
    
    // GreetingHandler handles GET requests to /api/greeting
    func GreetingHandler(w http.ResponseWriter, r *http.Request) {
      logger := logging.NewLogger()
    
      logger.Info("Log before a five seconds sleep")
      time.Sleep(5 * time.Second)
      logger.Info("Log after a five seconds sleep")
      logger.Warning("This is a warning log")
      logger.Error("This is an error log")
    
      response := "Hello!"
      w.Header().Set("Content-Type", "application/json")
      w.WriteHeader(http.StatusOK)
      json.NewEncoder(w).Encode(response)
    }
    
    最後に私のカスタムロガーは
    package logging
    
    import (
      "time"
    
      "github.com/sirupsen/logrus"
    )
    
    func NewLogger() logrus.FieldLogger {
      logger := logrus.New()
      logger.SetLevel(logrus.InfoLevel)
      jsonFormatter := logrus.JSONFormatter{
        TimestampFormat: time.RFC3339Nano,
        FieldMap: logrus.FieldMap{
          logrus.FieldKeyTime: "timestamp",
          logrus.FieldKeyMsg: "message",
          logrus.FieldKeyLevel: "level",     
        },
      }
      logger.SetFormatter(&jsonFormatter)
      return logger
    }
    
    Azure関数の優れたVSコード拡張を使用してこの関数を展開します.展開されると、数回呼び出し、アプリケーションの洞察リソースに頭を向け、次のクエリを実行します
    traces 
    | where message startswith "{"
    | extend d=parse_json(message)
    | project d.timestamp, d.level, d.message
    
    The where message startswith "{" 文はJSONとしてログオンされるメッセージを含む簡単な方法です.ホストがいくつかのJSONメッセージをログに記録するので、それは理想的ではありません.The extend d=parse_json(message) 文読み込みmessage JSONとしてのフィールドproject JSON LOGオブジェクトからフィールドを指定します.サンプル応答は以下のイメージに示されます

    私たちは、上記の制限の両方に解決策を持っているという結果から明らかです.私たちは、必要なフィールドを作り、ログ間の相対的な時間を保存することができます.ログ形式を拡張して、必要なコンテキストを取得できます.