Repository: PrefectHQ/prefect

Issue:
Filtering with OrionClient.read_logs does not work
### First check

- [X] I added a descriptive title to this issue.
- [X] I used the GitHub search to find a similar issue and didn't find it.
- [X] I searched the Prefect documentation for this issue.
- [X] I checked that this issue is related to Prefect and not one of its dependencies.

### Bug summary

When passing a LogFilter object to the OrionClient.read_logs function, it seems not to be taken into account. 

### Reproduction

1. Install prefect
2. Run at least 2 flows, so you can check if the id filter works. 
3. Run the following script: 

```python
import asyncio
from uuid import UUID
from prefect.orion.schemas.filters import LogFilter, LogFilterFlowRunId, LogFilterLevel
from prefect.client import OrionClient

ORION_URL = "https://orion.capture-vintecc.com/api"
FLOW_RUN_ID = UUID("2f9ca187-b057-4e2a-8f71-5c5121e8d9a8")

async def get_logs(id):
    async with OrionClient(ORION_URL) as client:
        filter = LogFilter(flow_run_id=LogFilterFlowRunId(any_=[id]), level=LogFilterLevel(ge_=200))
        logs = await client.read_logs(log_filter=filter, limit=100, offset=0)
        for log in logs:
            if log.flow_run_id != id:
                print("Wrong flow run id found.")
                break


if __name__ ==  '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(get_logs(FLOW_RUN_ID))
```


### Error

OUTPUT: Wrong flow run id found.

### Versions

```
Version:             2.4.0
API version:         0.8.0
Python version:      3.8.13
Git commit:          513639e8
Built:               Tue, Sep 13, 2022 2:15 PM
OS/Arch:             win32/AMD64
Profile:             default
Server type:         hosted
```

### Additional context

_No response_


Discussion:
Thanks for the well written issue! This is confusing since the UI uses this route to retrieve logs. Perhaps the Python client just isn't passing the filter through?

Resolving patch:
diff --git a/src/prefect/client/orion.py b/src/prefect/client/orion.py
--- a/src/prefect/client/orion.py
+++ b/src/prefect/client/orion.py
@@ -1670,7 +1670,7 @@ async def read_logs(
         Read flow and task run logs.
         """
         body = {
-            "filter": log_filter.dict(json_compatible=True) if log_filter else None,
+            "logs": log_filter.dict(json_compatible=True) if log_filter else None,
             "limit": limit,
             "offset": offset,
         }


Test patch:
diff --git a/tests/client/test_orion_client.py b/tests/client/test_orion_client.py
--- a/tests/client/test_orion_client.py
+++ b/tests/client/test_orion_client.py
@@ -1,8 +1,9 @@
+import datetime
 import random
 import threading
-from datetime import timedelta
+from datetime import datetime, timedelta, timezone
 from unittest.mock import MagicMock
-from uuid import UUID
+from uuid import UUID, uuid4
 
 import anyio
 import httpx
@@ -18,7 +19,9 @@
 from prefect.orion import schemas
 from prefect.orion.api.server import ORION_API_VERSION, create_app
 from prefect.orion.orchestration.rules import OrchestrationResult
+from prefect.orion.schemas.actions import LogCreate
 from prefect.orion.schemas.data import DataDocument
+from prefect.orion.schemas.filters import LogFilter, LogFilterFlowRunId
 from prefect.orion.schemas.schedules import IntervalSchedule
 from prefect.orion.schemas.states import Pending, Running, Scheduled, StateType
 from prefect.settings import (
@@ -899,6 +902,30 @@ def bar(orion_client):
     assert run.state.message == "Test!"
 
 
+async def test_read_filtered_logs(session, orion_client, deployment):
+
+    flow_runs = [uuid4() for i in range(5)]
+    logs = [
+        LogCreate(
+            name="prefect.flow_runs",
+            level=20,
+            message=f"Log from flow_run {id}.",
+            timestamp=datetime.now(tz=timezone.utc),
+            flow_run_id=id,
+        )
+        for id in flow_runs
+    ]
+
+    await orion_client.create_logs(logs)
+
+    logs = await orion_client.read_logs(
+        log_filter=LogFilter(flow_run_id=LogFilterFlowRunId(any_=flow_runs[:3]))
+    )
+    for log in logs:
+        assert log.flow_run_id in flow_runs[:3]
+        assert log.flow_run_id not in flow_runs[3:]
+
+
 class TestResolveDataDoc:
     async def test_does_not_allow_other_types(self, orion_client):
         with pytest.raises(TypeError, match="invalid type str"):