WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

JavaScript の console.log()

by WebSurfer 2024年1月31日 20:15

JavaScript の console.log() で開発者ツールの Console に JavaScript オブジェクトや html 要素 (DOM) を出力すると、Console を開いた時点での内容 (下の (1) の時点での内容ではなくて) が表示されるという話を書きます。

開発者ツールの Console 出力

Qiita の質問「javascriptとブラウザのコンソールについて」で調べたことですが、忘れそうなので自分のブログに備忘録として残しておこうと思った次第です。

上の画像の JavaScript のコードの (1) を見てください。obj = { prop: 123 } という JavaScript オブジェクトを作成してから即 console.log(obj) とし、その後で prop の値を 456 に書き換えています。しかしながら、コンソールを開いて見ると prop が書き換えられた後の 456 という結果が表示されています。

これは、MDN のドキュメント console: log() static method の「Logging objects」のセクションに書いてあるように、"Information about an object is lazily retrieved. This means that the log message shows the content of an object at the time when it's first viewed, not when it was logged." ということによります。

コード上で console.log() とした時点での JavaScript オブジェクトのログを取りたい場合は、MDN のドキュメントに書いてあるように "A common way is to JSON.stringify() and then JSON.parse() it" とするのが良さそうです。それが上の画像の JavaScript のコード (2) です。

Console を開いた時点での内容が表示されるというのは、JavaScript オブジェクトだけでなく、 html 要素 (DOM) を JavaScript で書き換えても同じことが起こります。

上の画像の (4) を見てください。JavaScript で html の p 要素を生成して id と textContent を設定してから即 console.log(p) し、その後で id と textContent を書き換えています。しかし、Console には書き換えた後の結果が表示されています。

コード上で console.log() とした時点での p 要素のログを取りたい場合は、p.outerHTML を出力するのが良さそうです。それが上の画像の (5) です。

JavaScript はブラウザ依存ですが、Windows 10 PC の Edge 121.0.2277.83, Chrome 121.0.6167.140, Firefox 122.0, Opera 106.0.4998.66 で試して同じ結果となることは確認しました。

Tags: , ,

JavaScript

サーバー側で応答コンテンツの取得 (CORE)

by WebSurfer 2022年7月7日 16:30

ASP.NET Core Web アプリの応答ボディの文字列を、サーバー側でのログなどの目的で、取得する方法を書きます。先の記事「サーバー側で応答コンテンツの取得」の ASP.NET Core 版です。

応答ボディの文字列

.NET Framework 版 ASP.NET で利用した HttpModule、HttpResponse.Filter プロパティ、HttpResponse.OutputStream プロパティは ASP.NET Core ではサポートされていません。

代わりにカスタム MiddlewareHttpResponse.Body プロパティを使って同様なことを行います。

ただし、HttpResponse.Body プロパティで取得できる Stream は CanRead, CanSeek が false になっており読むことができないという問題があります。

その解決に、先の記事「HttpRequest.Body から読み取る方法 (CORE)」で書いた EnableBuffering メソッドが使えるかと思ったのですが、応答側 HttpResponse ではサポートされていませんでした。

やむを得ず、Middleware で next.Invoke の前に応答ボディ用の Stream を MemoryStream に差し替えて、next.Invoke の後で MemoryStream に書き込まれた応答ボディを取得するという手段を取りました。

そのサンプルコードは以下の通りです。デバッグ実行すると上の画像ように「出力」ウィンドウに応答ボディの文字列が取得できます。

namespace MvcCore6App3.Middleware
{
    public class ResponseContentLogMiddleware
    {
        private readonly RequestDelegate _next;

        public ResponseContentLogMiddleware(RequestDelegate next)
        {
            this._next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            // この例では Home/Privacy のみログを取るという前提
            if (context.Request.Path.ToString().Contains("/Home/Privacy"))
            {
                // 応答ボディの Stream を取得して保持
                Stream responseStream = context.Response.Body;

                // 応答ボディの文字列を取得する
                string bodyContent = "";

                try
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        // 応答ストリームを MemoryStream に差し替えて、それに
                        // 応答ボディを取得。_next.Invoke の前でないとダメ
                        context.Response.Body = memoryStream;

                        await _next.Invoke(context);

                        // この時点ではすでに MemoryStream には応答ボディは
                        // 書き込まれている

                        if (memoryStream.Length > 0L &&
                            memoryStream.CanRead &&
                            memoryStream.CanSeek)
                        {
                            memoryStream.Position = 0L;
                            var reader = new StreamReader(memoryStream);
                            bodyContent = await reader.ReadToEndAsync();

                            // 確認用(デバッグ実行で Visual Studio の「出力」
                            // ウィンドウに表示される)
                            System.Diagnostics.Debug.Write(bodyContent);

                            memoryStream.Position = 0L;

                            // MemoryStream に取得した応答ボディのバイト列を
                            // responseStream にコピー
                            await memoryStream.CopyToAsync(responseStream);
                        }
                    }
                }
                finally
                {
                    // 上のコードで MemoryStream に差し替えた応答ボディの
                    // Stream を元に戻す
                    context.Response.Body = responseStream;
                }
            }
            else
            {
                // 何もしない場合でも以下のコードは必須。これが無いとミドル
                // ウェアのチェーンの途中で止まってしまう
                await _next.Invoke(context);
            }
        }
    }
}

上の Middleware が動くようにするには Program.cs (.NET 5.0 以前では Startup.cs) での設定が必要です。以下のコードで「追記」とコメントした一行を追加します。

// ・・・前略・・・

app.UseAuthentication();
app.UseAuthorization();

// 追記
app.UseMiddleware<ResponseContentLogMiddleware>();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();

app.Run();

HttpResponse.Body プロパティで取得できる Stream が読めないのは、HttpRequest.Body と同様に、"lightweight and performant as possible" ということを狙ってのことではないかと思います。

なので、上記のようなことをすると性能上の劣化が生じるかもしれません。どうしてもログが必要と言うような場合にとどめておいた方が良さそうな気がします。

Tags: , , ,

CORE

サーバー側で応答コンテンツの取得

by WebSurfer 2018年7月14日 13:26

ASP.NET Web アプリの応答コンテンツの文字列を、サーバー側でのログ取得などの目的で取得する方法を書きます。(あまり需要はないかもしれませんが)

参考にしたのは stackoverflow のスレッド Logging raw HTTP request/response in ASP.NET MVC & IIS7 です。その記事を見れば情報としては十分かもしれませんが、リンク切れになると困るし、記事にはない HTTP モジュールを使っての設定方法も追加して書いておきます。

基本的には、応答コンテンツは応答ストリームに含まれますのでそれを何らかの手段で取得するということになります。

stackoverflow の記事では、応答ストリームをラッピングするフィルターを作成し、フィルターの中に応答ストリームとは別に MemoryStream を用意し、ASP.NET が応答ストリームに書き込む際 MemoryStream にも同じ内容を書き込むようにし(要するに MemoryStream にコピーを作成し)、MemoryStream から応答コンテンツを取得するという方法が紹介されています。

下のコード例を見てください。その中の OutputFilterStream クラスがラッピングフィルターです。stackoverflow の記事のコードをそのままコピーしたものです。

HTTP モジュール(下のコードでは ResponseContentLogHttpModule クラス)を用い、BeginRequest イベントのタイミングでラッピングフィルターを HttpResponse.Filter プロパティに設定します。(注:EndRequest のタイミングではダメです。ストリームにはその前に書き込まれるので)

HttpResponse.Filter プロパティの NSDN ライブラリの説明には "伝送する前に HTTP エンティティ本体を変更するために使用される、ラッピングフィルターオブジェクトを取得または設定します" とありますが、OutputFilterStream はコンテンツの変更は一切せずそのまま応答として返し、MemoryStream にコピーを取得することのみ行います。

BeginRequest のタイミングで設定したラッピングフィルターオブジェクトを EndRequest のタイミングで取得し MemoryStream にコピーされた応答コンテンツを取得します。

BeginRequest のタイミングから EndRequest のタイミングまでラッピングフィルターオブジェクトを保持するには HttpContext.Items プロパティを利用します。下のコード例を見てください。

クラスのフィールド等で保持するのは NG という報告がありましたので注意してください。HttpContext.Items を使えば、少なくとも HttpContext が存在する限りラッピングフィルターオブジェクトは保持されるが(GC の対象にはならないが)、クラスのフィールド変数ではその限りではないということのようです。

以下に HTTP モジュールとラッピングフィルターのコード例をアップしておきます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;

// HTTP モジュール
public class ResponseContentLogHttpModule : IHttpModule
{
  public ResponseContentLogHttpModule()
  {
  }

  public String ModuleName
  {
    get { return "ResponseContentLogHttpModule"; }
  }

  public void Init(HttpApplication application)
  {
    application.BeginRequest += this.BeginRequest;
    application.EndRequest += this.EndRequest;
  }

  private void BeginRequest(Object source, EventArgs e)
  {
    HttpApplication application = (HttpApplication)source;
    HttpContext context = application.Context;
    string filePath = context.Request.FilePath;
    string fileExtension =
        VirtualPathUtility.GetExtension(filePath);

    // とりあえず .aspx のみ対象(.js, .css 等は対象外)
    if (fileExtension.Equals(".aspx"))
    {
      HttpResponse response = context.Response;
      var filter = new OutputFilterStream(response.Filter);
      response.Filter = filter;

      // OutputFilterStream オブジェクトを EndRequest で
      // 利用できるよう HttoContext.Items に参照を保持。
      // (注:クラスのフィールド等で保持するのは NG)
      context.Items["FilterStream"] = filter;
    }
  }

  private void EndRequest(Object source, EventArgs e)
  {
    HttpApplication application = (HttpApplication)source;
    HttpContext context = application.Context;
    string filePath = context.Request.FilePath;
    string fileExtension =
        VirtualPathUtility.GetExtension(filePath);

    // とりあえず .aspx のみ対象(.js, .css 等は対象外)
    if (fileExtension.Equals(".aspx"))
    {
      // BegineRequest で HttpContext.Items に保持した
      // OutputFilterStream オブジェクトへの参照を取得
      var filter =
        (OutputFilterStream)context.Items["FilterStream"];

      // 応答コンテンツの文字列を取得
      string responseContent = filter.ReadStream();
    }
  }

  // IHttpModule に定義されているので空でも以下が必要
  public void Dispose() { }
}

// ラッピングフィルタークラス
// stackoverflow の記事のコードをそのままコピー
public class OutputFilterStream : Stream
{
  private readonly Stream InnerStream;
  private readonly MemoryStream CopyStream;

  public OutputFilterStream(Stream inner)
  {
    this.InnerStream = inner;
    this.CopyStream = new MemoryStream();
  }

  public string ReadStream()
  {
    lock (this.InnerStream)
    {
      if (this.CopyStream.Length <= 0L ||
          !this.CopyStream.CanRead ||
          !this.CopyStream.CanSeek)
      {
        return String.Empty;
      }

      long pos = this.CopyStream.Position;
      this.CopyStream.Position = 0L;
      try
      {
        return new StreamReader(this.CopyStream).ReadToEnd();
      }
      finally
      {
        try
        {
          this.CopyStream.Position = pos;
        }
        catch { }
      }
    }
  }

  public override bool CanRead
  {
    get { return this.InnerStream.CanRead; }
  }

  public override bool CanSeek
  {
    get { return this.InnerStream.CanSeek; }
  }

  public override bool CanWrite
  {
    get { return this.InnerStream.CanWrite; }
  }

  public override void Flush()
  {
    this.InnerStream.Flush();
  }

  public override long Length
  {
    get { return this.InnerStream.Length; }
  }

  public override long Position
  {
    get { return this.InnerStream.Position; }
    set
    {
      this.CopyStream.Position =
      this.InnerStream.Position = value;
    }
  }

  public override int Read(byte[] buffer,
                          int offset, int count)
  {
    return this.InnerStream.Read(buffer, offset, count);
  }

  public override long Seek(long offset, SeekOrigin origin)
  {
    this.CopyStream.Seek(offset, origin);
    return this.InnerStream.Seek(offset, origin);
  }

  public override void SetLength(long value)
  {
    this.CopyStream.SetLength(value);
    this.InnerStream.SetLength(value);
  }

  public override void Write(byte[] buffer,
                             int offset, int count)
  {
    this.CopyStream.Write(buffer, offset, count);
    this.InnerStream.Write(buffer, offset, count);
  }
}

上の HTTP モジュールが動く ようにするには web.config での設定が必要ですので注意してください。以下に設定例を書いておきます。

<system.webServer>
  <modules>
    <add name="ResponseContentLogHttpModule" 
         type="ResponseContentLogHttpModule"/>
  </modules>
</system.webServer>

Tags: , ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar