Skip to content

[BUG] event_to_span が dict を受け取ると既定値だけの span を黙って返す #92

Description

@m96-chan

Bug Description

event_to_span() はイベントを属性アクセスでしか読みませんdict を渡すと getattr が既定値を返すため、エラーにならず全項目が既定値の span が黙って作られます。

同じ dictpyetwkit.export の関数に渡すと正しく処理されます。export.py には _event_to_dict() があり、オブジェクトと dict の両方を受けるためです。エクスポータ同士で挙動が食い違っています。

from types import SimpleNamespace
from pyetwkit.exporters import event_to_span
from pyetwkit.export import to_json

ev = {
    "provider_name": "Microsoft-Windows-Kernel-Process",
    "event_id": 1,
    "process_id": 4104,
    "properties": {"ImageName": "cmd.exe"},
}

event_to_span(ev)
#   name  = 'unknown.0'
#   attrs = etw.provider='unknown', etw.event_id=0, process.pid=0

event_to_span(SimpleNamespace(**ev, thread_id=0, timestamp=1788613764.0))
#   name  = 'Microsoft-Windows-Kernel-Process.1'
#   attrs = etw.provider='Microsoft-Windows-Kernel-Process', etw.event_id=1, process.pid=4104

to_json([ev])   # 同じ dict を正しく処理する

実際の EtwEvent を使う限り問題ありません。困るのは、記録した dict を読み込んで送る場合や、テストで dict を使う場合です。

SpanMapper.get_span_name()extract_attributes()event_to_log() も同じ getattr のみの実装です。

Steps to Reproduce

  1. provider_name / event_id などを持つ dict を用意する
  2. event_to_span(その dict) を呼ぶ
  3. 返ってきた span の nameattributes を見る

Expected Behavior

dict でも属性を持つオブジェクトでも同じ span になること。export.py_event_to_dict() と同じ扱いをするのが自然です。

dict を受け付けない設計にするなら、黙って既定値を返すのではなく TypeError を投げるべきです。現在の「静かに間違った結果を返す」だけは避けたいという趣旨の Issue です。

Actual Behavior

dict を渡すと unknown.0 という名前の、PID も provider も空の span が生成されます。例外も警告も出ません。

Environment

  • OS: Windows 11 (10.0.26200)
  • Python version: 3.12.10
  • PyETWkit version: 3.1.0 (main, b696112)
  • Rust toolchain: 1.98.1

Error Log / Stack Trace

(エラーは出ません。それが問題です)

dict  -> name='unknown.0'
        attrs= [('etw.provider', {'stringValue': 'unknown'}), ('etw.event_id', {'intValue': 0}), ('process.pid', {'intValue': 0})]
object-> name='Microsoft-Windows-Kernel-Process.1'
        attrs= [('etw.provider', {'stringValue': 'Microsoft-Windows-Kernel-Process'}), ('etw.event_id', {'intValue': 1}), ('process.pid', {'intValue': 4104})]

Minimal Reproduction Code

上記「Bug Description」のコードがそのまま再現コードです。

Related ETW Provider (if applicable)

  • Provider name: 該当なし(エクスポータ側の問題)
  • Event ID: 該当なし

Test Case Proposal (TDD)

from types import SimpleNamespace

import pytest

from pyetwkit.exporters import event_to_log, event_to_span


EVENT_FIELDS = {
    "provider_name": "Microsoft-Windows-Kernel-Process",
    "event_id": 1,
    "process_id": 4104,
    "thread_id": 512,
    "timestamp": 1788613764.0,
    "properties": {"ImageName": "cmd.exe"},
}


def test_event_to_span_reads_a_dict_the_same_as_an_object() -> None:
    """dict とオブジェクトで同じ span になること。現状は失敗する。"""
    from_dict = event_to_span(dict(EVENT_FIELDS))
    from_object = event_to_span(SimpleNamespace(**EVENT_FIELDS))

    assert from_dict["name"] == from_object["name"]

    def attrs(span):
        return {a["key"]: a["value"] for a in span["attributes"]}

    assert attrs(from_dict)["etw.provider"] == attrs(from_object)["etw.provider"]
    assert attrs(from_dict)["etw.event_id"] == attrs(from_object)["etw.event_id"]
    assert attrs(from_dict)["process.pid"] == attrs(from_object)["process.pid"]


def test_event_to_log_reads_a_dict_too() -> None:
    from_dict = event_to_log(dict(EVENT_FIELDS))
    from_object = event_to_log(SimpleNamespace(**EVENT_FIELDS))

    assert from_dict["timeUnixNano"] == from_object["timeUnixNano"]


def test_span_mapper_matches_a_dict() -> None:
    from pyetwkit.exporters import SpanMapper

    mapper = SpanMapper()
    mapper.add_rule(
        provider="Microsoft-Windows-Kernel-Process",
        event_id=1,
        span_name="process.start",
        attributes=["ImageName"],
    )

    assert mapper.get_span_name(dict(EVENT_FIELDS)) == "process.start"
    assert mapper.extract_attributes(dict(EVENT_FIELDS)) == {"ImageName": "cmd.exe"}


def test_something_that_is_not_an_event_is_rejected() -> None:
    """既定値の span を黙って返すのではなく、はっきり失敗すること。"""
    with pytest.raises(TypeError):
        event_to_span("not an event")

Additional Context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions