Skip to content

Commit 5e0f970

Browse files
committed
feat: Add a "tutor do" command to transform tracking logs
While this command can be run as a management command on the server, it needs to run as a job for Vector to pick up the logging statements and insert them into the xAPI table. Also fixes some bugs with Vector reading xAPI logging statements.
1 parent 1eaa9a5 commit 5e0f970

File tree

7 files changed

+58
-7
lines changed

7 files changed

+58
-7
lines changed

tutoraspects/commands_v0.py

+25-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from tutor import config as tutor_config
44

55

6-
@click.command(help="Create an Open edX user and interactively set their password")
6+
@click.command(help="Run dbt with the provided command and options.")
77
@click.option(
88
"-c",
99
"--command",
@@ -36,7 +36,7 @@ def dbt(context, command) -> None:
3636
runner.run_job("aspects", command)
3737

3838

39-
@click.command()
39+
@click.command(help="Load generated fake xAPI test data to ClickHouse.")
4040
@click.option("-n", "--num_batches", default=100)
4141
@click.option("-s", "--batch_size", default=100)
4242
@click.pass_obj
@@ -55,7 +55,10 @@ def load_xapi_test_data(context, num_batches, batch_size) -> None:
5555
runner.run_job("aspects", command)
5656

5757

58-
@click.command(context_settings={"ignore_unknown_options": True})
58+
@click.command(
59+
help="Run Alembic migrations with the given options.",
60+
context_settings={"ignore_unknown_options": True},
61+
)
5962
@click.option(
6063
"-c",
6164
"--command",
@@ -104,9 +107,28 @@ def dump_courses_to_clickhouse(context, options) -> None:
104107
runner.run_job("cms", command)
105108

106109

110+
# pylint: disable=line-too-long
111+
# Ex: tutor local do transform-tracking-logs --options "--source_provider MINIO --source_config '{\"key\": \"openedx\", \"secret\": \"h3SIhXAqDDxJAP6TcXklNxro\", \"container\": \"openedx\", \"prefix\": \"/tracking_logs\", \"host\": \"files.local.overhang.io\", \"secure\": false}' --destination_provider LRS --transformer_type xapi"
112+
@click.command(help="Uses event-routing-backends to replay tracking logs.")
113+
@click.option("--options", default="")
114+
@click.pass_obj
115+
def transform_tracking_logs(context, options) -> None:
116+
"""
117+
Job that proxies the dump_courses_to_clickhouse commands.
118+
"""
119+
config = tutor_config.load(context.root)
120+
runner = context.job_runner(config)
121+
122+
command = f"""
123+
./manage.py lms transform_tracking_logs {options}
124+
"""
125+
runner.run_job("lms", command)
126+
127+
107128
COMMANDS = (
108129
load_xapi_test_data,
109130
dbt,
110131
alembic,
111132
dump_courses_to_clickhouse,
133+
transform_tracking_logs,
112134
)

tutoraspects/commands_v1.py

+13
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,22 @@ def dump_courses_to_clickhouse(options) -> list[tuple[str, str]]:
101101
return [("cms", f"./manage.py cms dump_courses_to_clickhouse {options}")]
102102

103103

104+
# pylint: disable=line-too-long
105+
# Ex: tutor local do transform-tracking-logs --options "--source_provider LOCAL --source_config '{\"key\": \"/openedx/data/\", \"prefix\": \"tracking.log\", \"container\": \"logs\"}' --destination_provider LRS --transformer_type xapi"
106+
# Ex: tutor local do transform-tracking-logs --options "--source_provider MINIO --source_config '{\"key\": \"openedx\", \"secret\": \"h3SIhXAqDDxJAP6TcXklNxro\", \"container\": \"openedx\", \"prefix\": \"/tracking_logs\", \"host\": \"files.local.overhang.io\", \"secure\": false}' --destination_provider LRS --transformer_type xapi"
107+
@click.command(context_settings={"ignore_unknown_options": True})
108+
@click.option("--options", default="", type=click.UNPROCESSED)
109+
def transform_tracking_logs(options) -> list[tuple[str, str]]:
110+
"""
111+
Job that proxies the dump_courses_to_clickhouse commands.
112+
"""
113+
return [("lms", f"./manage.py lms transform_tracking_logs {options}")]
114+
115+
104116
COMMANDS = (
105117
load_xapi_test_data,
106118
dbt,
107119
alembic,
108120
dump_courses_to_clickhouse,
121+
transform_tracking_logs,
109122
)

tutoraspects/patches/local-docker-compose-jobs-services

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ aspects-job:
77
volumes:
88
- ../../env/plugins/aspects/apps/aspects:/app/aspects
99
- ../../env/plugins/aspects/apps/aspects/scripts/:/app/aspects/scripts:ro
10-
depends_on:
11-
- superset {% if RUN_CLICKHOUSE%}
10+
{% if RUN_SUPERSET or RUN_CLICKHOUSE or RUN_RALPH %}depends_on:{% if RUN_SUPERSET %}
11+
- superset{% endif %}{% if RUN_CLICKHOUSE%}
1212
- clickhouse{% endif %}{% if RUN_RALPH %}
1313
- ralph{% endif %}
14+
{% endif %}
1415
clickhouse-job:
1516
image: {{DOCKER_IMAGE_CLICKHOUSE}}
1617
{% if RUN_CLICKHOUSE%}depends_on:

tutoraspects/templates/aspects/apps/vector/file.toml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[sources.tracking_log_file]
66
type = "file"
77
include = ["/var/log/openedx/tracking.log"]
8+
89
[transforms.openedx_containers]
910
type = "filter"
1011
# no-op filter: created for future-proof compatibility

tutoraspects/templates/aspects/apps/vector/k8s.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ extra_namespace_label_selector = "kubernetes.io/metadata.name={{ K8S_NAMESPACE }
99
[transforms.openedx_containers]
1010
type = "filter"
1111
inputs = ["kubernetes_logs"]
12-
condition = '.kubernetes.pod_namespace == "{{ K8S_NAMESPACE }}" && includes(["lms", "cms"], .kubernetes.container_name)'
12+
condition = '.kubernetes.pod_namespace == "{{ K8S_NAMESPACE }}" && includes(["lms", "cms", "lms-job", "cms-job"], .kubernetes.container_name)'
1313

1414
{% include "aspects/apps/vector/partials/common-post.toml" %}

tutoraspects/templates/aspects/apps/vector/local.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
# Capture logs from all docker containers
55
[sources.docker_logs]
66
type = "docker_logs"
7+
78
[transforms.openedx_containers]
89
type = "filter"
910
inputs = ["docker_logs"]
10-
condition = 'includes(["lms", "cms", "lms-worker"], .label."com.docker.compose.service")'
11+
condition = 'includes(["lms", "cms", "lms-job", "cms-job"], .label."com.docker.compose.service")'
1112

1213
{% include "aspects/apps/vector/partials/common-post.toml" %}

tutoraspects/templates/aspects/apps/vector/partials/common-post.toml

+13
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ event_id = parsed_json.id
7272
drop_on_error = true
7373
drop_on_abort = true
7474

75+
[transforms.xapi_debug]
76+
type = "remap"
77+
inputs = ["xapi"]
78+
# Time formats: https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html#specifiers
79+
source = '''
80+
.message = parse_json!(.event_str)
81+
'''
7582

7683
### Sinks
7784

@@ -82,6 +89,12 @@ inputs = ["tracking_debug"]
8289
encoding.codec = "json"
8390
encoding.only_fields = ["time", "message.context.course_id", "message.context.user_id", "message.name"]
8491

92+
[sinks.out_xapi]
93+
type = "console"
94+
inputs = ["xapi_debug"]
95+
encoding.codec = "json"
96+
encoding.only_fields = ["event_id", "emission_time", "message.verb.id"]
97+
8598
# # Send logs to clickhouse
8699
[sinks.clickhouse]
87100
type = "clickhouse"

0 commit comments

Comments
 (0)