Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record logs into step artifacts #1339

Merged
merged 46 commits into from
Dec 17, 2024

Conversation

satansdeer
Copy link
Contributor

@satansdeer satansdeer commented Dec 6, 2024

Recording step execution logs into artifacts.

During step, task and workflow_run execution we add log entries into the log field of the context.

Once step, task or workflow_run state is updated we record the entries into an artifact.

If there already is a log artifact for the given entity - it will be updated instead of creating a new one.

Assumptions

  • we should show all the "info", "warning", "error", "critical", "exception" logs for each step
  • we can record logs to artifacts on step, task and workflow_run update, for now no workflow_run_block
  • context gets wiped for each task
  • logs are stored inside the {entity_type}_update method

Log Artifacts API Access

I've introduced a new endpoint to access artifacts for different entities:

GET /{entity_type}/{entity_id}/artifacts/

Storing Log Artifact Files

skyvern/artifacts/logs
├── step
│   └── stp_336689492028900908
│       ├── 2024-12-13T07:27:20.956676_skyvern_log_raw.json
│       └── 2024-12-13T07:27:20.963041_skyvern_log.log
├── task
│   └── tsk_336815971888446190
│       ├── 2024-12-13T15:39:11.626435_skyvern_log_raw.json
│       └── 2024-12-13T15:39:11.637557_skyvern_log.log
└── workflow_run
    └── wr_336802094723172252
        ├── 2024-12-13T14:44:08.085465_skyvern_log_raw.json
        └── 2024-12-13T14:44:08.099234_skyvern_log.log

Testing plan

  • Run successful task
    • Logs should be recorded into step artifacts for each step
    • Logs should be recorded into task artifact
  • Run failing task
    • Logs should be recorded into step artifact
    • Logs should be recorded into task artifact
  • Run workflow
    • Logs should be recorded into workflow_run artifacts

Screenshots

Successful run:
Screenshot 2024-12-06 at 11 32 50
Failing run:
Screenshot 2024-12-06 at 11 56 07

Updates

Added text log formatter:
Screenshot 2024-12-09 at 12 46 57


Important

This PR adds functionality to record step execution logs as SkyvernLog artifacts in JSON format, with updates to logging, context, and artifact management.

  • Behavior:
    • Logs from step execution are recorded in the log field of SkyvernContext and stored as SkyvernLog artifacts.
    • Logs are stored in JSON format using SkyvernLogEncoder.
  • Artifact Management:
    • Added SkyvernLog to ArtifactType in models.py and types.ts.
    • Updated FILE_EXTENSION_MAP in storage/base.py to include SkyvernLog with json extension.
  • Logging:
    • Added skyvern_logs_processor in forge_log.py to append logs to SkyvernContext.
    • Integrated skyvern_logs_processor into setup_logger().
  • Frontend:
    • Updated StepArtifacts.tsx to display SkyvernLog artifacts in the UI.
  • Misc:
    • Created SkyvernLogEncoder in skyvern_log_encoder.py for custom JSON encoding of logs.

This description was created by Ellipsis for b2cb9c4. It will automatically update as commits are pushed.

@satansdeer satansdeer changed the title Maksimi/record logs Record logs into step artifacts Dec 6, 2024
Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me! Reviewed everything up to b2cb9c4 in 2 minutes and 53 seconds

More details
  • Looked at 196 lines of code in 8 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 drafted comments based on config settings.
1. skyvern/forge/agent.py:1778
  • Draft comment:
    The try-except block for recording the Skyvern log is redundant here as it is already handled in the update_step method. Consider removing this block to avoid code duplication.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable:
    The comment claims redundancy, but the diff does not show any evidence that the update_step method handles the same logging functionality. Without strong evidence of redundancy, the comment should be removed. The comment does not provide actionable or clear evidence of the issue.
    I might be missing the full implementation details of the update_step method, which could potentially handle the logging. However, the diff does not provide this information, so I should not assume it.
    The diff should provide strong evidence of redundancy for the comment to be valid. Without this evidence, the comment should be removed.
    Remove the comment as it lacks strong evidence of redundancy in the try-except block for logging.

Workflow ID: wflow_tO2RAXSK8tQ1bpap


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

@suchintan
Copy link
Contributor

I don't think JSON logs are useful to the reader. Can we generate 2 artifacts instead?

  1. JSON Logs
  2. Log lines similar in a readable fashion (event | param1=param1 param2=param2 ...)

log = skyvern_context.current().log
current_step_log = [entry for entry in log if entry.get("step_id", "") == step.step_id]
log_json = json.dumps(current_step_log, cls=SkyvernLogEncoder, indent=2)
await app.ARTIFACT_MANAGER.create_artifact(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we can also capture task and workflow level logs? Even if we don't render them in the UI today

We historically associate logs like that w/ the first step, although @wintonzheng is currently overhauling it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/Skyvern-AI/skyvern/pull/1345/files

here's the example of supporting artifact upload for observer thought.

going forward we can add support for task level and workflow run level artifacts


try:
log = skyvern_context.current().log
current_step_log = [entry for entry in log if entry.get("step_id", "") == step.step_id]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this PR is a good start.

There are a couple of problems i'm seeing right now (not because of the code here) that we need keep iterating:

  • sometimes we don't have step_id in a log. example. TODO: We need to add step_id to all the logs
  • for each task in a workflow run, we don't set the task_id in the SkyvernContext before the task starts. this means many logs won't have task_id - if we want to store logs according to the "task_id" field in the json. TODO: set task_id in SkyvernContext before a task starts and reset it after a task ends (completed, failed, terminated, canceled)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: We need to add step_id to all the logs

@wintonzheng are we certain that each log must have a step_id?

From what I see in the logs when I ran tasks there are several logs in the beginning that don't belong to any step:

2024-12-09T11:09:09.220558Z [info     ] Created new task               organization_id=o_333794107196816612 proxy_location=RESIDENTIAL task_id=tsk_335262355392558810 url=https://www.nytimes.com/books/best-sellers
2024-12-09T11:09:09.220672Z [info     ] Executing task using background task executor task_id=tsk_335262355392558810
2024-12-09T11:09:09.266772Z [info     ] Creating browser state for task task_id=tsk_335262355392558810
2024-12-09T11:09:12.092991Z [info     ] browser console log is saved   log_path=./log/2024-12-09/95733df0-f2d8-428d-89ce-f71221f0cbe1.log
2024-12-09T11:09:12.326437Z [info     ] Trying to navigate to https://www.nytimes.com/books/best-sellers and waiting for 5 seconds. retry_time=0 url=https://www.nytimes.com/books/best-sellers
2024-12-09T11:09:14.148604Z [info     ] Page loading time              loading_time=1.8220469951629639 url=https://www.nytimes.com/books/best-sellers
2024-12-09T11:09:19.148456Z [info     ] Successfully went to https://www.nytimes.com/books/best-sellers retry_time=0 url=https://www.nytimes.com/books/best-sellers
2024-12-09T11:09:19.162015Z [info     ] Starting agent step            step_id=stp_335262355392558812 step_order=0 step_retry=0 task_id=tsk_335262355392558810

Here first 7 logs would be without a step because the first step is not created yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wintonzheng do I undestand correctly that we want to create task and step ids in advance?

This way the logs that are currently not associated with a step or task because step/task not created yet would be associated with the first task and first step?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wintonzheng are we certain that each log must have a step_id?

no, step_id needs to be manually defined in LOG.info("xxx", step_id="stp_xxxx") nowadays. those logs you shared here don't belong to any step so it's okay many logs don't have step_id.

@wintonzheng do I undestand correctly that we want to create task and step ids in advance? This way the logs that are currently not associated with a step or task because step/task not created yet would be associated with the first task and first step?

yep, exactly what i mean. we need to backfill step_id and task_id in some logs.

@satansdeer
Copy link
Contributor Author

@suchintan here are logs in text format, I'm now generating both text and json artifacts
Screenshot 2024-12-09 at 12 46 57

I'm only showing the text logs in the UI for now.

@suchintan
Copy link
Contributor

@suchintan here are logs in text format, I'm now generating both text and json artifacts Screenshot 2024-12-09 at 12 46 57

I'm only showing the text logs in the UI for now.

This is perfect

@@ -10,6 +10,8 @@
ArtifactType.SCREENSHOT_LLM: "png",
ArtifactType.SCREENSHOT_ACTION: "png",
ArtifactType.SCREENSHOT_FINAL: "png",
ArtifactType.SKYVERN_LOG: "log",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Step log..

Task log..

Workflow log...

Copy link
Contributor

@wintonzheng wintonzheng Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think adding STEP_LOG, TASK_LOG, WORKFLOW_LOG helps.

It's better to use step_id, task_id and workflow_run_id columns to filter artifacts. Also, step log and task log are not mutually exclusive. TaskLogs should be the super set of StepLogs. WorkflowRunLogs should be the super set of TaskLogs. We don't have to create multiple sets of log artifacts that have duplicated data.

Copy link
Contributor

@suchintan suchintan Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I agree with this -- why not have multiple artifact types that may or may not include duplicate data? It makes the implementation + writing of data + reading of data dead simple.

Why is it better to use step_id / task_id / workflow_run_id columns to filter artifacts w/ the same name + type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly in this case we'll store workflow and task level logs in the step artifact folders

Current implementation of bulid uri always assumes step:

def build_uri(self, artifact_id: str, step: Step, artifact_type: ArtifactType) -> str:
        file_ext = FILE_EXTENTSION_MAP[artifact_type]
        return f"file://{self.artifact_path}/{step.task_id}/{step.order:02d}_{step.retry_index}_{step.step_id}/{datetime.utcnow().isoformat()}_{artifact_id}_{artifact_type}.{file_ext}"

We could define a build uri method for logs that would take entity type and id

So that the uri would look like this:

return f"file://{self.artifact_path}/{entity_type}_{entity_id}/{datetime.utcnow().isoformat()}_{artifact_type}.{file_ext}"

This way we would be able to fetch logs on the frontend using a similar query as for the other artifacts

We would be able to use artifact type for all the levels

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@suchintan no concern of data duplication. you're wright, having more artifact_types makes the implementation easier.

@@ -65,11 +63,11 @@ function ScrollableActionList({
onClick={() => onActiveIndexChange(i)}
onMouseEnter={() => {
queryClient.prefetchQuery({
queryKey: ["task", taskId, "steps", action.stepId, "artifacts"],
queryKey: ["step", action.stepId, "artifacts"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@msalihaltun can you review?

LOG = structlog.get_logger()

def primary_key_from_log_entity_type(log_entity_type: LogEntityType) -> str:
if log_entity_type == LogEntityType.STEP:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great

skyvern/exceptions.py Outdated Show resolved Hide resolved
skyvern/forge/agent.py Outdated Show resolved Hide resolved
Copy link
Contributor

@wintonzheng wintonzheng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a couple of small comments. Code looks great.

from skyvern.forge.sdk.db.id import generate_artifact_id
from skyvern.forge.sdk.models import Step
from skyvern.forge.sdk.schemas.observers import ObserverCruise, ObserverThought

LOG = structlog.get_logger(__name__)

PRIMARY_KEY = Literal[
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LawyZheng we're introducing new primary keys for artifacts

Now i think we need to ensure those aio tasks for new primary keys (workflow_run_id, step_id) will be awaited at cleanup time.

@wintonzheng
Copy link
Contributor

wintonzheng commented Dec 17, 2024

typing fix here: 63829fa

feel free to pick that commit and we can merge this PR

@wintonzheng wintonzheng merged commit b8e2527 into Skyvern-AI:main Dec 17, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants