Skip to content

Commit

Permalink
Adapt File Tracer
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianSchepersAA committed May 22, 2024
1 parent 36c63b4 commit 79cf85b
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 75 deletions.
19 changes: 17 additions & 2 deletions src/intelligence_layer/core/tracer/composite_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
from typing import Generic, Optional, Sequence, TypeVar

from intelligence_layer.core.tracer.tracer import (
Context,
ExportedSpan,
PydanticSerializable,
Span,
TaskSpan,
Tracer,
utc_now,
Context
)

TracerVar = TypeVar("TracerVar", bound=Tracer)
Expand Down Expand Up @@ -73,7 +73,10 @@ class CompositeSpan(Generic[SpanVar], CompositeTracer[SpanVar], Span):
Args:
tracers: spans that will be forwarded all subsequent log and span calls.
"""
def __init__(self, tracers: Sequence[SpanVar],context: Optional[Context] = None) -> None:

def __init__(
self, tracers: Sequence[SpanVar], context: Optional[Context] = None
) -> None:
CompositeTracer.__init__(self, tracers)
Span.__init__(self, context=context)

Expand All @@ -92,6 +95,18 @@ def end(self, timestamp: Optional[datetime] = None) -> None:
for tracer in self.tracers:
tracer.end(timestamp)

@property
def status_code(self):
status_codes = {tracer.status_code for tracer in self.tracers}
if len(status_codes) > 1:
raise ValueError("Inconsistent status of traces in composite tracer. Status of all traces should be the same but they are different.")
return status_code[0]

@status_code.setter
def status_code(self, value):
for tracer in self.tracers:
tracer.status_code = value


class CompositeTaskSpan(CompositeSpan[TaskSpan], TaskSpan):
"""A :class:`TaskSpan` that allows for recording to multiple TaskSpans simultaneously.
Expand Down
23 changes: 4 additions & 19 deletions src/intelligence_layer/core/tracer/file_tracer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime
from json import loads
from pathlib import Path
from typing import Optional, Sequence
from typing import Optional

from pydantic import BaseModel

Expand All @@ -11,22 +11,8 @@
PersistentTaskSpan,
PersistentTracer,
)
from intelligence_layer.core.tracer.tracer import LogLine, PydanticSerializable
from intelligence_layer.core.tracer.tracer import (
Context,
Event,
ExportedSpan,
ExportedSpanList,
LogEntry,
PydanticSerializable,
Span,
SpanAttributes,
TaskSpan,
TaskSpanAttributes,
Tracer,
_render_log_value,
utc_now,
)
from intelligence_layer.core.tracer.tracer import Context, LogLine, PydanticSerializable


class FileTracer(PersistentTracer):
"""A `Tracer` that logs to a file.
Expand Down Expand Up @@ -91,16 +77,15 @@ def trace(self, trace_id: Optional[str] = None) -> InMemoryTracer:
return self._parse_log(filtered_traces)



class FileSpan(PersistentSpan, FileTracer):
"""A `Span` created by `FileTracer.span`."""

def __init__(self, log_file_path: Path, context: Optional[Context] = None) -> None:
PersistentSpan.__init__(self, context=context)
FileTracer.__init__(self, log_file_path=log_file_path)



class FileTaskSpan(PersistentTaskSpan, FileSpan):
"""A `TaskSpan` created by `FileTracer.task_span`."""

pass
19 changes: 14 additions & 5 deletions src/intelligence_layer/core/tracer/in_memory_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import requests
import rich
from pydantic import BaseModel, Field, SerializeAsAny
from pydantic import SerializeAsAny
from requests import HTTPError
from rich.tree import Tree

Expand Down Expand Up @@ -171,6 +171,8 @@ def _span_attributes(self) -> SpanAttributes:
return SpanAttributes()

def export_for_viewing(self) -> Sequence[ExportedSpan]:
if not self._closed:
raise RuntimeError("Span is not closed. A Span must be closed befor it is exported for viewing.")
logs: list[LogEntry] = []
exported_spans: list[ExportedSpan] = []
for entry in self.entries:
Expand Down Expand Up @@ -246,9 +248,11 @@ def start_task(self, log_line: LogLine) -> None:
name=start_task.name,
input=start_task.input,
start_timestamp=start_task.start,
context=Context(
context=Context(
trace_id=start_task.trace_id, span_id=str(start_task.parent)
) if start_task.trace_id != str(start_task.uuid) else None
)
if start_task.trace_id != str(start_task.uuid)
else None,
)
# if root, also change the trace id
if converted_span.context.trace_id == converted_span.context.span_id:
Expand All @@ -262,6 +266,7 @@ def end_task(self, log_line: LogLine) -> None:
end_task = EndTask.model_validate(log_line.entry)
task_span = self.tasks[end_task.uuid]
task_span.record_output(end_task.output)
task_span.status_code = end_task.status_code
task_span.end(end_task.end)

def start_span(self, log_line: LogLine) -> None:
Expand All @@ -271,22 +276,26 @@ def start_span(self, log_line: LogLine) -> None:
start_timestamp=start_span.start,
context=Context(
trace_id=start_span.trace_id, span_id=str(start_span.parent)
) if start_span.trace_id != str(start_span.uuid) else None
)
if start_span.trace_id != str(start_span.uuid)
else None,
)
# if root, also change the trace id
if converted_span.context.trace_id == converted_span.context.span_id:
converted_span.context.trace_id = str(start_span.uuid)
converted_span.context.span_id = str(start_span.uuid)

self.tracers.get(start_span.parent, self.root).entries.append(converted_span)
self.tracers[start_span.uuid] = converted_span
self.spans[start_span.uuid] = converted_span

def end_span(self, log_line: LogLine) -> None:
end_span = EndSpan.model_validate(log_line.entry)
span = self.spans[end_span.uuid]
span.status_code = end_span.status_code
span.end(end_span.end)


def plain_entry(self, log_line: LogLine) -> None:
plain_entry = PlainEntry.model_validate(log_line.entry)
entry = LogEntry(
Expand Down
38 changes: 18 additions & 20 deletions src/intelligence_layer/core/tracer/persistent_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from intelligence_layer.core.tracer.tracer import (
EndSpan,
EndTask,
ExportedSpan,
LogLine,
PlainEntry,
PydanticSerializable,
Expand All @@ -20,26 +21,11 @@
utc_now,
)

from intelligence_layer.core.tracer.tracer import (
Context,
Event,
ExportedSpan,
ExportedSpanList,
LogEntry,
PydanticSerializable,
Span,
SpanAttributes,
TaskSpan,
TaskSpanAttributes,
Tracer,
_render_log_value,
utc_now,
)

class PersistentTracer(Tracer, ABC):
def __init__(self) -> None:
self.current_id = uuid4()

@abstractmethod
def _log_entry(self, id: str, entry: BaseModel) -> None:
pass
Expand Down Expand Up @@ -77,7 +63,9 @@ def _log_task(
task_span.context.trace_id,
StartTask(
uuid=task_span.context.span_id,
parent=self.context.span_id if self.context else task_span.context.trace_id,
parent=self.context.span_id
if self.context
else task_span.context.trace_id,
name=task_name,
start=timestamp or utc_now(),
input=input,
Expand All @@ -89,7 +77,9 @@ def _log_task(
task_span.context.trace_id,
StartTask(
uuid=task_span.context.span_id,
parent=self.context.span_id if self.context else task_span.context.trace_id,
parent=self.context.span_id
if self.context
else task_span.context.trace_id,
name=task_name,
start=timestamp or utc_now(),
input=error.description,
Expand Down Expand Up @@ -151,7 +141,10 @@ def log(
def end(self, timestamp: Optional[datetime] = None) -> None:
if not self.end_timestamp:
self.end_timestamp = timestamp or utc_now()
self._log_entry(self.context.trace_id, EndSpan(uuid=self.context.span_id, end=self.end_timestamp))
self._log_entry(
self.context.trace_id,
EndSpan(uuid=self.context.span_id, end=self.end_timestamp, status_code=self.status_code),
)


class PersistentTaskSpan(TaskSpan, PersistentSpan, ABC):
Expand All @@ -165,7 +158,12 @@ def end(self, timestamp: Optional[datetime] = None) -> None:
self.end_timestamp = timestamp or utc_now()
self._log_entry(
self.context.trace_id,
EndTask(uuid=self.context.span_id, end=self.end_timestamp, output=self.output),
EndTask(
uuid=self.context.span_id,
end=self.end_timestamp,
output=self.output,
status_code=self.status_code
),
)


Expand Down
6 changes: 4 additions & 2 deletions src/intelligence_layer/core/tracer/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class Span(Tracer, AbstractContextManager["Span"]):
"""

def __init__(self, context: Optional[Context] = None):
#super().__init__()
# super().__init__()
span_id = str(uuid4())
if context is None:
trace_id = span_id
Expand All @@ -216,7 +216,7 @@ def __init__(self, context: Optional[Context] = None):

def __enter__(self) -> Self:
if self._closed:
raise ValueError("Spans cannot be opened once they have been close.")
raise ValueError("Spans cannot be opened once they have been closed.")
return self

@abstractmethod
Expand Down Expand Up @@ -444,6 +444,7 @@ class EndTask(BaseModel):
uuid: UUID
end: datetime
output: SerializeAsAny[PydanticSerializable]
status_code: SpanStatus = SpanStatus.OK


class StartSpan(BaseModel):
Expand Down Expand Up @@ -475,6 +476,7 @@ class EndSpan(BaseModel):

uuid: UUID
end: datetime
status_code: SpanStatus = SpanStatus.OK


class PlainEntry(BaseModel):
Expand Down
11 changes: 11 additions & 0 deletions tests/core/tracer/fixtures/old_file_trace_format.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"StartTask","entry":{"uuid":"41528209-1b78-4785-a00d-7f65af1bb09c","parent":"75e79a11-1a26-4731-8b49-ef8634c352ed","name":"TestTask","start":"2024-05-22T09:43:37.428758Z","input":"input","trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a"}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"StartSpan","entry":{"uuid":"ad1ed79b-6ad6-4ea5-8ee8-26be4055e228","parent":"41528209-1b78-4785-a00d-7f65af1bb09c","name":"span","start":"2024-05-22T09:43:37.429448Z","trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a"}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"PlainEntry","entry":{"message":"message","value":"a value","timestamp":"2024-05-22T09:43:37.429503Z","parent":"ad1ed79b-6ad6-4ea5-8ee8-26be4055e228","trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a"}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"StartTask","entry":{"uuid":"e8cca541-57a8-440a-b848-7c3b33a97f52","parent":"ad1ed79b-6ad6-4ea5-8ee8-26be4055e228","name":"TestSubTask","start":"2024-05-22T09:43:37.429561Z","input":null,"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a"}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"PlainEntry","entry":{"message":"subtask","value":"value","timestamp":"2024-05-22T09:43:37.429605Z","parent":"e8cca541-57a8-440a-b848-7c3b33a97f52","trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a"}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"EndTask","entry":{"uuid":"e8cca541-57a8-440a-b848-7c3b33a97f52","end":"2024-05-22T09:43:37.429647Z","output":null}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"EndSpan","entry":{"uuid":"ad1ed79b-6ad6-4ea5-8ee8-26be4055e228","end":"2024-05-22T09:43:37.429687Z"}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"StartTask","entry":{"uuid":"8840185c-2019-4105-9178-1b0e20ab6388","parent":"41528209-1b78-4785-a00d-7f65af1bb09c","name":"TestSubTask","start":"2024-05-22T09:43:37.429728Z","input":null,"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a"}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"PlainEntry","entry":{"message":"subtask","value":"value","timestamp":"2024-05-22T09:43:37.429768Z","parent":"8840185c-2019-4105-9178-1b0e20ab6388","trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a"}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"EndTask","entry":{"uuid":"8840185c-2019-4105-9178-1b0e20ab6388","end":"2024-05-22T09:43:37.429806Z","output":null}}
{"trace_id":"00b96d92-d1d1-454a-b8e9-f67e8541759a","entry_type":"EndTask","entry":{"uuid":"41528209-1b78-4785-a00d-7f65af1bb09c","end":"2024-05-22T09:43:37.429842Z","output":"output"}}
25 changes: 20 additions & 5 deletions tests/core/tracer/test_composite_tracer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from intelligence_layer.core import (
CompositeTracer,
InMemoryTracer,
Task,
)
import pytest
from intelligence_layer.core import CompositeTracer, InMemoryTracer, Task


def test_composite_tracer(test_task: Task[str, str]) -> None:
Expand All @@ -17,3 +14,21 @@ def test_composite_tracer(test_task: Task[str, str]) -> None:
assert trace_1.status == trace_2.status
assert trace_1.context.trace_id != trace_2.context.trace_id
assert trace_1.context.span_id != trace_2.context.span_id

def test_composite_tracer_raises_for_inconsistent_span_status(test_task: Task[str, str]) -> None:
tracer_1 = InMemoryTracer()
tracer_2 = InMemoryTracer()

composite_tracer = CompositeTracer([tracer_1, tracer_2])

with composite_tracer.span("test_name") as composite_span:
spans = composite_span.tracers
single_span = spans[0]
try:
with single_span:
raise Exception
except Exception:
pass

with pytest.raises(ValueError):
composite_span.status_code
27 changes: 16 additions & 11 deletions tests/core/tracer/test_file_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pytest import fixture

from intelligence_layer.core import CompositeTracer, FileTracer, InMemoryTracer, Task
from intelligence_layer.core.tracer.in_memory_tracer import InMemoryTaskSpan
from intelligence_layer.core.tracer.persistent_tracer import TracerLogEntryFailed


Expand All @@ -13,17 +14,6 @@ def file_tracer(tmp_path: Path) -> FileTracer:
return FileTracer(tmp_path / "log.log")


def test_file_tracer(file_tracer: FileTracer, test_task: Task[str, str]) -> None:
input = "input"
expected = InMemoryTracer()

test_task.run(input, file_tracer)

log_tree = file_tracer.trace()
trace_1 = log_tree.export_for_viewing()



def test_file_tracer_retrieves_correct_trace(
file_tracer: FileTracer, test_task: Task[str, str]
) -> None:
Expand Down Expand Up @@ -59,3 +49,18 @@ def test_file_tracer_raises_non_log_entry_failed_exceptions(
file_tracer.task_span(
task_name="mock_task_name", input="42", timestamp=None, trace_id="21"
)


def test_file_tracer_is_backwards_compatible() -> None:
current_file_location = Path(__file__)
file_tracer = FileTracer(
current_file_location.parent / "fixtures/old_file_trace_format.jsonl"
)
tracer = file_tracer.trace()

assert len(tracer.entries) == 1
task_span = tracer.entries[0]
assert isinstance(task_span, InMemoryTaskSpan)
assert task_span.input == "input"
assert task_span.start_timestamp and task_span.end_timestamp
assert task_span.start_timestamp < task_span.end_timestamp
1 change: 1 addition & 0 deletions tests/core/tracer/test_in_memory_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def test_task_span_records_error_value() -> None:
def test_task_automatically_logs_input_and_output(
test_task: Task[str, str],
) -> None:
input = "input"
tracer = InMemoryTracer()
output = test_task.run(input=input, tracer=tracer)

Expand Down
Loading

0 comments on commit 79cf85b

Please sign in to comment.