CloudWatchのWebアプリケーションログ(その1 )


このポストでは、LambdaSharpアプリAPIをCloudWatch Logs REST APIを使用してログに記録する方法を説明しています.
可観測性は開発者にとって重要なビルディングブロックである.したがって、それはLambdSharp開発者の経験の不可欠な部分です.参考のために、これはどのようにAmazon API Gatewayアプリが作成され、自動的にLambdashharpでCloudWatchのログリングのために配線されています.
Module: Sample.BlazorWebAssembly
Items:
  - App: MyBlazorApp
はい、それは本当にです!何も追加は必要ありませんが、Blazor WebAssemblyの多くがあります.しかし、非LambsSharp開発者は、彼らのアプリが彼らの好ましいフレームワークを使用するために、同じ能力を達成したいかもしれません.それがこのポストの目的です.どのフレームワークを使用してフロントエンドアプリケーションのCloudWatchのログ機能を構築する方法を示します.
additional capabilities
概要
この実装ではラムダ関数は使用しません.代わりに、APIゲートウェイREST APIを テンプレートを使用してCloudWatchログAPIと直接統合することでCloudWatchにログを有効にします.この設計は、最小限のコードが含まれる、ラムダコールドスタートの遅延、およびラムダ呼び出しコストなしであることを意味します.
実装はYAML記法を使用しているCloudFormationリソースに関して記述されます、しかし、同じ結果は代わりにAWSコンソールを使用することによって達成されることができます.
Apache Velocity
APIのログ記録
まず、新しいAPIゲートウェイリソースを作成する必要があります.APIをアンカーするトップレベル.appリソースを定義します.LambdSharpでは、この最上位のリソース名はCloudFormationパラメーターを使用して設定可能です.
RestApi:
  Type: AWS::ApiGateway::RestApi
  Properties:
    Name: !Sub "${AWS::StackName} App API"

RestApiAppResource:
  Type: AWS::ApiGateway::Resource
  Properties:
    RestApiId: !Ref RestApi
    ParentId: !GetAtt RestApi.RootResourceId
    PathPart: .app

ロググループ
CloudWatchロググループは、アプリケーション間で区別するのは簡単にするために、各アプリケーションのために明示的に作成する必要があります.加えて、ログ保持ポリシーはロググループがそれのために無期限に請求されるのを避けるために使用する記憶の量を制限するように設定されるべきです.
LogGroup:
  Type: AWS::Logs::LogGroup
  Properties:
    RetentionInDays: 90

ゲートウェイ
APIゲートウェイはロググループでログストリームを作成し、それらに書き込む許可を必要とします.これは、以下のIAMロール定義によって達成されます.
RestApiRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: 2012-10-17
      Statement:
        - Sid: ApiGatewayPrincipal
          Effect: Allow
          Principal:
            Service: apigateway.amazonaws.com
          Action: sts:AssumeRole
    Policies:
      - PolicyName: ApiLogsPolicy
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Sid: LogGroupPermission
              Effect: Allow
              Action:
                - logs:CreateLogStream
                - logs:PutLogEvents
              Resource:
                - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:${LogGroup}"
                - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:${LogGroup}:log-stream:*"

API検証
APIゲートウェイREST APIのきちんとした機能は、JSONスキーマモデルに対する要求を検証できることです.この機能は、受信ペイロードが有効でないときにAPIの不要な呼び出しを防止します.妥当性検査は、各APIゲートウェイメソッドを次のバリデータ宣言に関連付けて有効になります.
RestApiValidator:
  Type: AWS::ApiGateway::RequestValidator
  Properties:
    RestApiId: !Ref RestApi
    ValidateRequestBody: true
    ValidateRequestParameters: true

REST API
この次のセクションは少し重いです.APIゲートウェイリソース、メソッド、および統合がどのように構築されるか.
まず、APIメソッドに関連するlogsリソースを作成します.
RestApiAppLogsResource:
  Type: AWS::ApiGateway::Resource
  Properties:
    RestApiId: !Ref RestApi
    ParentId: !Ref RestApiAppResource
    PathPart: "logs"
次に、CORSリクエストを処理するためのオプションメソッドを作成する必要があります.この実装はAllow-Origin: '*'を使用します.これは実際のホストスキームとアプリケーションが提供される名前に置き換えられなければなりません.Lambdasharpは設定可能にするためにCloudFormationパラメータを使用しますが、これらは簡潔に省略されました.
RestApiAppLogsResourceOPTIONS:
  Type: AWS::ApiGateway::Method
  Properties:
    AuthorizationType: NONE
    RestApiId: !Ref RestApi
    ResourceId: !Ref RestApiAppLogsResource
    HttpMethod: OPTIONS
    Integration:
      IntegrationResponses:
        - StatusCode: 204
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
            method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST,PUT'"
            method.response.header.Access-Control-Allow-Origin: "'*'"
            method.response.header.Access-Control-Max-Age: "'600'"
          ResponseTemplates:
            application/json: ''
      PassthroughBehavior: WHEN_NO_MATCH
      RequestTemplates:
        application/json: '{"statusCode": 200}'
      Type: MOCK
    MethodResponses:
      - StatusCode: 204
        ResponseModels:
          application/json: 'Empty'
        ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: false
            method.response.header.Access-Control-Allow-Methods: false
            method.response.header.Access-Control-Allow-Origin: false
            method.response.header.Access-Control-Max-Age: false
オプションのリクエストを使用してブラウザが承認されると、POSTを使用して新しいログストリームを作成するための1つ、およびPUTを使用してログストリームに書き込むための別の2つの追加のエンドポイントを提供する必要があります.また、各エンドポイントのJSONスキーマモデルを定義して、実行前に要求を検証します.
アプリケーションは、新しいログストリームを作成する責任があることに注意してください.シングルページアプリ(SPA)の場合は、新しいログストリームは、アプリケーションの負荷たびに作成する必要があります.また、これはLambdasharpで構築されたBlazor WBabassemblyアプリの動作です.

を作成します.アプリケーションログ
Postメソッドは、関連するロググループに新しいログストリームを作成します.応答処理のいくつかは、呼び出し元アプリケーションにエラーがどのように返されるかに関するものです.強調はあまりにも多くの内部の詳細を明らかにせずに有用なフィードバックを提供する上です.
オプションのメソッドと同様に、この構成はAllow-Origin: '*'を使用します.これは実際のホストスキームとアプリケーションが提供される名前に置き換えられます.Lambdasharpは設定可能にするためにCloudFormationパラメータを使用しますが、これらは簡潔に省略されました.
RestApiAppLogsResourcePOST:
  Type: AWS::ApiGateway::Method
  Properties:
    OperationName: CreateLogStream
    ApiKeyRequired: true
    RestApiId: !Ref RestApi
    ResourceId: !Ref RestApiAppLogsResource
    AuthorizationType: NONE
    HttpMethod: POST
    RequestModels:
      application/json: !Ref RestApiAppLogsResourcePOSTRequestModel
    RequestValidatorId: !Ref RestApiValidator
    Integration:
      Type: AWS
      IntegrationHttpMethod: POST
      Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:logs:action/CreateLogStream"
      Credentials: !GetAtt RestApiRole.Arn
      PassthroughBehavior: WHEN_NO_TEMPLATES
      RequestParameters:
        integration.request.header.Content-Type: "'application/x-amz-json-1.1'"
        integration.request.header.X-Amz-Target: "'Logs_20140328.CreateLogStream'"
      RequestTemplates:
        application/json: !Sub |-
          #set($body = $input.path('$'))
          {
            "logGroupName": "${LogGroup}",
            "logStreamName": "$body.logStreamName"
          }
      IntegrationResponses:
        - SelectionPattern: "200"
          StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: "'*'"
          ResponseTemplates:
            application/x-amz-json-1.1: |-
              { }

        - SelectionPattern: "400"
          StatusCode: 400
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: "'*'"
          ResponseTemplates:
            application/x-amz-json-1.1: |-
              #set($body = $input.path('$'))
              {
              #if($body.message.isEmpty())
                "error": "Unknown error"
              #else
                "error": "$util.escapeJavaScript($body.message).replaceAll("\\'","'")"
              #end
              }

        - StatusCode: 500
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: "'*'"
          ResponseTemplates:
            application/x-amz-json-1.1: |-
              {
                "error": "Unexpected response from service."
              }

    MethodResponses:
      - StatusCode: 200
        ResponseModels:
          application/json: Empty
        ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: false

      - StatusCode: 400
        ResponseModels:
          application/json: Empty
        ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: false

      - StatusCode: 500
        ResponseModels:
          application/json: Empty
        ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: false

RestApiAppLogsResourcePOSTRequestModel:
  Type: AWS::ApiGateway::Model
  Properties:
    Description: CreateLogStream
    ContentType: application/json
    RestApiId: !Ref RestApi
    Schema:
      $schema: http://json-schema.org/draft-04/schema#
      type: object
      properties:
        logStreamName:
          type: string
      required:
        - logStreamName

ログを追加します.アプリケーションログ
POSTメソッドと同様に、PUTメソッドは、受信要求を検証し、エラーが発生したときにどのような内部詳細が公開されるかを制限します.
オプションのメソッドと同様に、この構成はAllow-Origin: '*'を使用します.これは実際のホストスキームとアプリケーションが提供される名前に置き換えられます.Lambdasharpは設定可能にするためにCloudFormationパラメータを使用しますが、これらは簡潔に省略されました.
RestApiAppLogsResourcePUT:
  Type: AWS::ApiGateway::Method
  Properties:
    OperationName: PutLogEvents
    ApiKeyRequired: true
    RestApiId: !Ref RestApi
    ResourceId: !Ref RestApiAppLogsResource
    AuthorizationType: NONE
    HttpMethod: PUT
    RequestModels:
      application/json: !Ref RestApiAppLogsResourcePUTRequestModel
    RequestValidatorId: !Ref RestApiValidator
    Integration:
      Type: AWS
      IntegrationHttpMethod: POST
      Uri: !Sub "arn:${AWS::Partition}:apigateway:${AWS::Region}:logs:action/PutLogEvents"
      Credentials: !GetAtt  RestApiRole.Arn
      PassthroughBehavior: WHEN_NO_TEMPLATES
      RequestParameters:
        integration.request.header.Content-Type: "'application/x-amz-json-1.1'"
        integration.request.header.X-Amz-Target: "'Logs_20140328.PutLogEvents'"
        integration.request.header.X-Amzn-Logs-Format: "'json/emf'"
      RequestTemplates:
        application/json: !Sub |-
          #set($body = $input.path('$'))
          {
            "logEvents": [
          #foreach($logEvent in $body.logEvents)
                {
                  "message": "$util.escapeJavaScript($logEvent.message).replaceAll("\\'","'")",
                  "timestamp": $logEvent.timestamp
                }#if($foreach.hasNext),#end
          #end
            ],
            "logGroupName": "${LogGroup}",
            "logStreamName": "$body.logStreamName",
            "sequenceToken": #if($body.sequenceToken.isEmpty()) null#else "$body.sequenceToken"#end
          }
      IntegrationResponses:
        - SelectionPattern: "200"
          StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: "'*'"
          ResponseTemplates:
            application/x-amz-json-1.1: |-
              {
                "nextSequenceToken": "$input.path('$.nextSequenceToken')"
              }

        - SelectionPattern: "400"
          StatusCode: 400
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: "'*'"
          ResponseTemplates:
            application/x-amz-json-1.1: |-
              #set($body = $input.path('$'))
              #if($body.expectedSequenceToken.isEmpty())
              {
              #if($body.message.isEmpty())
                "error": "Unknown error"
              #else
                "error": "$util.escapeJavaScript($body.message).replaceAll("\\'","'")"
              #end
              }
              #else
              {
              #if($body.message.isEmpty())
                "error": "unknown error",
              #else
                "error": "$util.escapeJavaScript($body.message).replaceAll("\\'","'")",
              #end
                "nextSequenceToken": "$body.expectedSequenceToken"
              }
              #end

        - StatusCode: 500
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: "'*'"
          ResponseTemplates:
            application/x-amz-json-1.1: |-
              {
                "error": "Unexpected response from service."
              }

    MethodResponses:
      - StatusCode: 200
        ResponseModels:
          application/json: Empty
        ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: false

      - StatusCode: 400
        ResponseModels:
          application/json: Empty
        ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: false

      - StatusCode: 500
        ResponseModels:
          application/json: Empty
        ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: false

RestApiAppLogsResourcePUTRequestModel:
  Type: AWS::ApiGateway::Model
  Properties:
    Description: PutLogEvents
    ContentType: application/json
    RestApiId: !Ref RestApi
    Schema:
      $schema: http://json-schema.org/draft-04/schema#
      type: object
      properties:
        logEvents:
          type: array
          items:
            - type: object
              properties:
                message:
                  type: string
                timestamp:
                  type: integer
              required:
                - message
                - timestamp
        logStreamName:
          type: string
        sequenceToken:
          type:
            - string
            - "null"
      required:
        - logEvents
        - logStreamName

APIと使用計画
次のリソースは、APIキーと使用計画を宣言します.APIキーは、デフォルトでCloudFormation Stack GuidのBase 64値に設定されます.フロントエンドアプリケーションがログ記録REST APIを使用するためにそれにアクセスする必要があるので、明示的にAPIキーを設定することをお勧めします.APIキーはさらにアプリケーションの内部値と組み合わせることによって難読化することができます.LambdSharpでは、APIキーは、CloudFormation Stack Guidとコンパイル済みの組み合わせを組み合わせて生成されます.NETコアアセンブリ識別子guid.
RestApiKey:
  Type: AWS::ApiGateway::ApiKey
  Properties:
    Description: !Sub "${AWS::StackName} App API Key"
    Enabled: true
    StageKeys:
      - RestApiId: !Ref RestApi
        StageName: !Ref RestApiStage
    Value:
      Fn::Base64: !Select [ 2, !Split [ "/", !Ref AWS::StackId ]]

RestApiUsagePlan:
  Type: AWS::ApiGateway::UsagePlan
  Properties:
    ApiStages:
      - ApiId: !Ref RestApi
        Stage: !Ref RestApiStage
    Description: !Sub "${AWS::StackName} App API Usage Plan"
    Throttle:
      BurstLimit: 200
      RateLimit: 100

RestApiUsagePlanKey:
  Type: AWS::ApiGateway::UsagePlanKey
  Properties:
    KeyId: !Ref RestApiKey
    KeyType: API_KEY
    UsagePlanId: !Ref RestApiUsagePlan

API展開
最後に、展開リソースによって使用されるLATESTというステージを定義します.CloudFormationは一度だけ配備を実行することに注意してください.その後のCloudFormationスタック更新は、残りのAPIが変更されたときに手動で配置する必要があります.Lambdasharpは、設定変更を常に自動的に適用できるように を使用します.
RestApiStage:
  Type: AWS::ApiGateway::Stage
  Properties:
    DeploymentId: !Ref RestApiDeployment
    Description: App API LATEST Stage
    RestApiId: !Ref RestApi
    StageName: LATEST

RestApiDeployment:
  Type: AWS::ApiGateway::Deployment
  Properties:
    Description: !Sub "${AWS::StackName} App API"
    RestApiId: !Ref RestApi
  DependsOn:
    - RestApiAppLogsResource
    - RestApiAppLogsResourcePOST
    - RestApiAppLogsResourcePOSTRequestModel
    - RestApiAppLogsResourcePUT
    - RestApiAppLogsResourcePUTRequestModel
Finalizer
結論-継続する.
このポストでは、フロントエンドのアプリを直接CloudWatchにログオンするために必要なリソースを作成しました.次のポストでは、このREST API経由でログを記録するプロトコルをカバーします.
ハッピーハッキング!