logtap 0.3.0__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- logtap-0.4.1/.github/workflows/codeql.yml +52 -0
- {logtap-0.3.0 → logtap-0.4.1}/.github/workflows/tests.yml +11 -0
- logtap-0.4.1/PKG-INFO +304 -0
- logtap-0.4.1/README.md +261 -0
- logtap-0.4.1/SECURITY.md +165 -0
- logtap-0.4.1/docs/PRODUCTION_LEARNINGS.md +229 -0
- logtap-0.4.1/docs/plans/2026-02-02-gpu-cloud-pivot-design.md +334 -0
- {logtap-0.3.0 → logtap-0.4.1}/index.html +99 -48
- {logtap-0.3.0 → logtap-0.4.1}/pyproject.toml +1 -1
- logtap-0.4.1/scripts/validate_prod.py +324 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/__init__.py +1 -1
- logtap-0.4.1/src/logtap/api/app.py +111 -0
- logtap-0.4.1/src/logtap/api/routes/health.py +41 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/api/routes/logs.py +26 -31
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/api/routes/parsed.py +8 -7
- logtap-0.4.1/src/logtap/api/routes/runs.py +330 -0
- logtap-0.4.1/src/logtap/cli/commands/collect.py +107 -0
- logtap-0.4.1/src/logtap/cli/commands/doctor.py +127 -0
- logtap-0.4.1/src/logtap/cli/commands/ingest.py +123 -0
- logtap-0.4.1/src/logtap/cli/commands/runs.py +116 -0
- logtap-0.4.1/src/logtap/cli/commands/tail.py +318 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/cli/main.py +12 -5
- logtap-0.4.1/src/logtap/core/runs.py +433 -0
- logtap-0.4.1/src/logtap/core/validation.py +184 -0
- logtap-0.4.1/src/logtap/models/responses.py +118 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/chaos/test_security.py +6 -19
- logtap-0.4.1/tests/unit/test_path_security.py +318 -0
- logtap-0.4.1/tests/unit/test_runs.py +175 -0
- {logtap-0.3.0 → logtap-0.4.1}/uv.lock +1 -1
- logtap-0.3.0/PKG-INFO +0 -319
- logtap-0.3.0/README.md +0 -276
- logtap-0.3.0/SECURITY.md +0 -20
- logtap-0.3.0/src/logtap/api/app.py +0 -45
- logtap-0.3.0/src/logtap/api/routes/health.py +0 -19
- logtap-0.3.0/src/logtap/cli/commands/tail.py +0 -121
- logtap-0.3.0/src/logtap/core/validation.py +0 -52
- logtap-0.3.0/src/logtap/models/responses.py +0 -65
- {logtap-0.3.0 → logtap-0.4.1}/.dockerignore +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/.env.example +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/.github/FUNDING.yml +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/.github/workflows/publish.yml +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/.gitignore +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/.pre-commit-config.yaml +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/.python-version +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/CODE_OF_CONDUCT.md +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/CONTRIBUTING.md +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/Dockerfile +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/LICENSE +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/docker-compose.yml +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/docs/CHAOS_TEST_REPORT.md +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/__main__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/api/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/api/dependencies.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/api/routes/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/api/routes/files.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/cli/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/cli/commands/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/cli/commands/files.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/cli/commands/query.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/cli/commands/serve.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/parsers/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/parsers/apache.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/parsers/auto.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/parsers/base.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/parsers/json_parser.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/parsers/nginx.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/parsers/syslog.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/reader.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/core/search.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/models/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/src/logtap/models/config.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/chaos/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/chaos/test_parsers.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/chaos/test_robustness.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/conftest.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/fixtures/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/fixtures/log/.gitkeep +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/integration/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/integration/test_api.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/syslog +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp05x_q6nb +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp065rykpi +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp0bm3cs8k +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp0fivu3up +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp0ijz83f3 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp0rney_h_ +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp14lnvavq +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp1sps52p0 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp23kjlxys +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp2dqr9age +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp34evzgj3 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp36pg8nhr +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp3qua_9f7 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp409e3kxw +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp40wj2d8h +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp4av7aq1x +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp4f2yhnse +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp5_fts0ah +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp5cz1fdvm +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp5fy_6kqm +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp5jkv8ly_ +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp5pyk6rzf +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp5pzzgnl_ +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp5w4es6gq +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp69zb3sz9 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp6ap9i9r0 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp6dhhwml0 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp6syz7tnt +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp784bp8l5 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp8hg6l0m8 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp90rfsnyx +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp916cjvmi +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp91sd1e55 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp94c0aoeb +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp98m7nh88 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp9c6k8nk6 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp9nwk10y6 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp9vph0i97 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp_81gfiki +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp_ct_338e +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp_f0hoyd4 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmp_y251lk8 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpaacnlvjn +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpawrsvp35 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpb1ntqz4a +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpb7djh3dt +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpbsuxncdv +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpc6mlcsdl +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpc8qb9l4k +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpcc8h0x4q +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpcdvslz5p +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpcq19058a +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpcw2qflak +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpe3pta4d3 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpe8493tiy +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpe946rhvt +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpef1f9h9m +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpeg2f7oov +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpepqoaana +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpf9uyvwel +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpfab61tmu +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpfu4d3omv +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpg93pkzoc +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmph17de7no +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmph59moiyt +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmphn0i0ngy +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpi25qtdum +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpiso7tb71 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpju_l_8ur +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpkewrczka +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpkuob2ku5 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpl1xj0zyo +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpl9rq9k_j +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpm2bmkbvd +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpm_xd1lzm +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpmc_npe2u +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpmq1l8ses +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpmvz0bev1 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpmywg8jr4 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpn2ep2xoe +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpn_3mnuzy +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpnmn9ob75 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpocsk9b52 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpod_7ghhq +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpofk_ue9w +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpp3t0hk_v +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmppas_s166 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmppn4p6_2h +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmppv7dcstw +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmppwn77fw3 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpq6ru59zb +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpq_k47l0a +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpqevemzw1 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpqzd9zjft +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpr2bv_nlo +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpr4txnw42 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmprknp7bj3 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmprl6k40a_ +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmprl8i4tj4 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmproqzibwd +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmprs1hp8wh +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpstuzwlgi +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpt6c4i65h +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmptd48yi53 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmptdqndikp +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpu101z7gi +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpu2te57pf +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpu5atvpbl +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpu7ty_3jg +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpuk4xm9wu +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpupxqxpc_ +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpv318rejg +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpvcrh7utz +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpvsayrh3y +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpw_4wsps5 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpwbcafh_z +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpwfacpdft +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpwg97gouq +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpxwt4w91l +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpy21c1m2c +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpy4qf8eco +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpy_qj2ih8 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpya6d_rq0 +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpyf_tdrun +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpyiar72k_ +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpykep4ebm +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpyqgzw6nh +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpz4t5g3ee +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpz65snq8z +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpzaxdh_3c +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpzeplzl1z +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/log/tmpzkitocmv +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/unit/__init__.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/unit/test_parsers.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/unit/test_reader.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/unit/test_search.py +0 -0
- {logtap-0.3.0 → logtap-0.4.1}/tests/unit/test_validation.py +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: "CodeQL"
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main" ]
|
|
6
|
+
paths-ignore:
|
|
7
|
+
- "*.md"
|
|
8
|
+
- "docs/**"
|
|
9
|
+
- ".github/FUNDING.yml"
|
|
10
|
+
pull_request:
|
|
11
|
+
branches: [ "main" ]
|
|
12
|
+
paths-ignore:
|
|
13
|
+
- "*.md"
|
|
14
|
+
- "docs/**"
|
|
15
|
+
schedule:
|
|
16
|
+
- cron: "0 6 * * 1" # Mondays 06:00 UTC
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: codeql-${{ github.ref }}
|
|
20
|
+
cancel-in-progress: true
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
analyze:
|
|
24
|
+
name: Analyze (Python)
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
timeout-minutes: 60
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
actions: read
|
|
30
|
+
contents: read
|
|
31
|
+
security-events: write
|
|
32
|
+
|
|
33
|
+
strategy:
|
|
34
|
+
fail-fast: false
|
|
35
|
+
matrix:
|
|
36
|
+
language: [ "python" ]
|
|
37
|
+
|
|
38
|
+
steps:
|
|
39
|
+
- name: Checkout repository
|
|
40
|
+
uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- name: Initialize CodeQL
|
|
43
|
+
uses: github/codeql-action/init@v3
|
|
44
|
+
with:
|
|
45
|
+
languages: ${{ matrix.language }}
|
|
46
|
+
queries: security-extended
|
|
47
|
+
|
|
48
|
+
- name: Autobuild
|
|
49
|
+
uses: github/codeql-action/autobuild@v3
|
|
50
|
+
|
|
51
|
+
- name: Perform CodeQL Analysis
|
|
52
|
+
uses: github/codeql-action/analyze@v3
|
|
@@ -4,9 +4,20 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches:
|
|
6
6
|
- main
|
|
7
|
+
paths-ignore:
|
|
8
|
+
- "*.md"
|
|
9
|
+
- "docs/**"
|
|
10
|
+
- ".github/FUNDING.yml"
|
|
7
11
|
pull_request:
|
|
8
12
|
branches:
|
|
9
13
|
- main
|
|
14
|
+
paths-ignore:
|
|
15
|
+
- "*.md"
|
|
16
|
+
- "docs/**"
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: tests-${{ github.ref }}
|
|
20
|
+
cancel-in-progress: true
|
|
10
21
|
|
|
11
22
|
permissions:
|
|
12
23
|
contents: read
|
logtap-0.4.1/PKG-INFO
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: logtap
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: A CLI-first log access tool for Unix systems. Remote log file access without SSH.
|
|
5
|
+
Project-URL: Homepage, https://github.com/cainky/logtap
|
|
6
|
+
Project-URL: Repository, https://github.com/cainky/logtap
|
|
7
|
+
Author-email: cainky <kylecain.me@gmail.com>
|
|
8
|
+
License: GPL-3.0-or-later
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: cli,devops,logs,monitoring,sysadmin
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: System :: Logging
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: aiofiles>=23.2.1
|
|
26
|
+
Requires-Dist: fastapi>=0.109.0
|
|
27
|
+
Requires-Dist: google-re2>=1.1
|
|
28
|
+
Requires-Dist: httpx>=0.26.0
|
|
29
|
+
Requires-Dist: pydantic-settings>=2.1.0
|
|
30
|
+
Requires-Dist: pydantic>=2.5.0
|
|
31
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
32
|
+
Requires-Dist: rich>=13.7.0
|
|
33
|
+
Requires-Dist: typer>=0.9.0
|
|
34
|
+
Requires-Dist: uvicorn[standard]>=0.27.0
|
|
35
|
+
Requires-Dist: websockets>=12.0
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pre-commit>=4.5.1; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# logtap
|
|
45
|
+
|
|
46
|
+
[](https://badge.fury.io/py/logtap)
|
|
47
|
+
[](https://github.com/cainky/logtap/actions/workflows/tests.yml)
|
|
48
|
+
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
49
|
+
[](https://www.python.org/downloads/)
|
|
50
|
+
|
|
51
|
+
**`tail -f` for GPU clouds. Survives disconnects, aggregates multi-node.**
|
|
52
|
+
|
|
53
|
+
> Stop losing your training logs when SSH drops. Watch from anywhere, reconnect seamlessly.
|
|
54
|
+
|
|
55
|
+
## The Problem
|
|
56
|
+
|
|
57
|
+
You're training a model on RunPod, Vast.ai, or Lambda. You SSH in, start training, and:
|
|
58
|
+
|
|
59
|
+
- Your terminal disconnects after an hour
|
|
60
|
+
- You lose visibility into what's happening
|
|
61
|
+
- You resort to tmux hacks just to keep logs alive
|
|
62
|
+
- Multi-node training means logs scattered across machines
|
|
63
|
+
|
|
64
|
+
## The Solution
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# On your GPU instance
|
|
68
|
+
pip install logtap
|
|
69
|
+
logtap collect &
|
|
70
|
+
python train.py 2>&1 | logtap ingest run1
|
|
71
|
+
|
|
72
|
+
# From your laptop (or phone)
|
|
73
|
+
logtap tail run1 --follow
|
|
74
|
+
|
|
75
|
+
# Connection drops... reconnects automatically
|
|
76
|
+
# "reconnected (missed 0 lines)"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quickstart: RunPod / Vast.ai
|
|
80
|
+
|
|
81
|
+
On the GPU instance:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install logtap
|
|
85
|
+
export LOGTAP_API_KEY=secret
|
|
86
|
+
logtap collect --port 8000
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Start training and stream logs:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python train.py 2>&1 | logtap ingest run1 --tag node=$(hostname)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
From your laptop:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
export LOGTAP_SERVER=http://<gpu-ip>:8000
|
|
99
|
+
export LOGTAP_API_KEY=secret
|
|
100
|
+
logtap tail run1 --follow
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Disconnect, close your terminal, or switch networks.
|
|
104
|
+
Re-run `logtap tail` anytime to resume where you left off.
|
|
105
|
+
|
|
106
|
+
Works the same on RunPod, Vast.ai, Lambda, and any ephemeral GPU cloud.
|
|
107
|
+
|
|
108
|
+
## Features
|
|
109
|
+
|
|
110
|
+
- **Survives Disconnects** - Resume from where you left off with cursor-based streaming
|
|
111
|
+
- **Pipe-Friendly** - Works with any training script via stdin
|
|
112
|
+
- **Multi-Node Ready** - Tag runs with `node=gpu1` and filter/aggregate
|
|
113
|
+
- **Zero Infra** - No database, no complex setup, just pip install
|
|
114
|
+
- **Lightweight** - <50MB memory, append-only file storage
|
|
115
|
+
|
|
116
|
+
## Why not tmux / mosh?
|
|
117
|
+
|
|
118
|
+
tmux and mosh help keep SSH sessions alive.
|
|
119
|
+
logtap solves a different problem.
|
|
120
|
+
|
|
121
|
+
- SSH can still drop (web terminals, proxies, idle timeouts)
|
|
122
|
+
- tmux doesn't aggregate logs across machines
|
|
123
|
+
- tmux can't be viewed from another device without SSH
|
|
124
|
+
- tmux sessions die when ephemeral instances stop
|
|
125
|
+
|
|
126
|
+
logtap streams logs over HTTP:
|
|
127
|
+
- survives disconnects
|
|
128
|
+
- resumes without gaps
|
|
129
|
+
- aggregates multi-node training via tags
|
|
130
|
+
- works from anywhere (no SSH required)
|
|
131
|
+
|
|
132
|
+
You can still use tmux. You just don't have to rely on it.
|
|
133
|
+
|
|
134
|
+
## Quick Start
|
|
135
|
+
|
|
136
|
+
### 1. Install
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pip install logtap
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 2. Start Collector (on GPU instance)
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
logtap collect --api-key secret
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 3. Pipe Your Training Logs
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
python train.py 2>&1 | logtap ingest run1 --api-key secret
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 4. Tail From Anywhere
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
export LOGTAP_SERVER=http://your-gpu-ip:8000
|
|
158
|
+
export LOGTAP_API_KEY=secret
|
|
159
|
+
|
|
160
|
+
logtap tail run1 --follow
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## CLI Commands
|
|
164
|
+
|
|
165
|
+
| Command | Description |
|
|
166
|
+
|---------|-------------|
|
|
167
|
+
| `logtap collect` | Start collector server (accepts ingested runs) |
|
|
168
|
+
| `logtap ingest <run>` | Pipe stdin to collector |
|
|
169
|
+
| `logtap tail <run>` | Tail a run with `--follow` for streaming |
|
|
170
|
+
| `logtap runs` | List active runs |
|
|
171
|
+
| `logtap doctor` | Check server connectivity and diagnose issues |
|
|
172
|
+
|
|
173
|
+
### Ingest Options
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Auto-generate run name
|
|
177
|
+
python train.py | logtap ingest
|
|
178
|
+
|
|
179
|
+
# Add tags for multi-node
|
|
180
|
+
python train.py | logtap ingest run1 --tag node=gpu1 --tag rank=0
|
|
181
|
+
|
|
182
|
+
# Quiet mode (no status messages)
|
|
183
|
+
python train.py | logtap ingest run1 --quiet
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Tail Options
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# Follow mode (like tail -f)
|
|
190
|
+
logtap tail run1 --follow
|
|
191
|
+
|
|
192
|
+
# Resume from specific cursor (survives disconnects!)
|
|
193
|
+
logtap tail run1 --follow --since 5000
|
|
194
|
+
|
|
195
|
+
# Filter by tag
|
|
196
|
+
logtap tail run1 --tag node=gpu1
|
|
197
|
+
|
|
198
|
+
# Output formats
|
|
199
|
+
logtap tail run1 --output jsonl | jq '.line'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Collector Options
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
logtap collect \
|
|
206
|
+
--port 8000 \
|
|
207
|
+
--api-key secret \
|
|
208
|
+
--data-dir ~/.logtap/runs \
|
|
209
|
+
--max-disk-mb 5000 \
|
|
210
|
+
--retention-hours 72
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Multi-Node Training
|
|
214
|
+
|
|
215
|
+
Tag each node and aggregate:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Node 1
|
|
219
|
+
python train.py | logtap ingest run1 --tag node=gpu1
|
|
220
|
+
|
|
221
|
+
# Node 2
|
|
222
|
+
python train.py | logtap ingest run1 --tag node=gpu2
|
|
223
|
+
|
|
224
|
+
# Watch all nodes
|
|
225
|
+
logtap tail run1 --follow
|
|
226
|
+
|
|
227
|
+
# Watch specific node
|
|
228
|
+
logtap tail run1 --follow --tag node=gpu1
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Environment Variables
|
|
232
|
+
|
|
233
|
+
| Variable | Default | Description |
|
|
234
|
+
|----------|---------|-------------|
|
|
235
|
+
| `LOGTAP_SERVER` | `http://localhost:8000` | Collector URL |
|
|
236
|
+
| `LOGTAP_API_KEY` | - | API key for auth |
|
|
237
|
+
|
|
238
|
+
Set these to avoid typing `--server` and `--api-key` every time.
|
|
239
|
+
|
|
240
|
+
## How It Works
|
|
241
|
+
|
|
242
|
+
1. **Collector** writes logs to append-only files with cursor tracking
|
|
243
|
+
2. **Ingest** streams stdin over HTTP chunked POST
|
|
244
|
+
3. **Tail** uses SSE (Server-Sent Events) with resume support
|
|
245
|
+
4. **Reconnect** passes `?since=<cursor>` to continue without gaps
|
|
246
|
+
|
|
247
|
+
No database. No message queue. Just files and HTTP.
|
|
248
|
+
|
|
249
|
+
## API Endpoints
|
|
250
|
+
|
|
251
|
+
For scripting or custom integrations:
|
|
252
|
+
|
|
253
|
+
| Endpoint | Description |
|
|
254
|
+
|----------|-------------|
|
|
255
|
+
| `POST /runs/{id}/ingest` | Stream lines (chunked POST) |
|
|
256
|
+
| `GET /runs/{id}/stream` | SSE stream with `?since=&follow=` |
|
|
257
|
+
| `GET /runs/{id}/query` | Query with `?from=&to=&search=` |
|
|
258
|
+
| `GET /runs` | List runs |
|
|
259
|
+
| `GET /health` | Health check with capabilities |
|
|
260
|
+
|
|
261
|
+
## Legacy: Static File Mode
|
|
262
|
+
|
|
263
|
+
logtap also works as a simple remote log viewer (the original use case):
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# On server with log files
|
|
267
|
+
logtap serve --log-dir /var/log
|
|
268
|
+
|
|
269
|
+
# From client
|
|
270
|
+
logtap tail syslog --server http://myserver:8000 --follow
|
|
271
|
+
logtap query auth.log --regex "Failed password"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Security
|
|
275
|
+
|
|
276
|
+
- **API Key Auth** - Optional but recommended for production
|
|
277
|
+
- **Path Traversal Protection** - Comprehensive defense with symlink-safe containment checks (see [SECURITY.md](SECURITY.md))
|
|
278
|
+
- **ReDoS Protection** - Uses google-re2 for guaranteed linear-time regex matching
|
|
279
|
+
- **Read-Only by Default** - Collector only writes to its data directory
|
|
280
|
+
- **Input Validation** - Rejects control characters, NUL bytes, and malicious path patterns
|
|
281
|
+
|
|
282
|
+
## Development
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
git clone https://github.com/cainky/logtap.git
|
|
286
|
+
cd logtap
|
|
287
|
+
|
|
288
|
+
# Install with uv
|
|
289
|
+
uv sync --extra dev
|
|
290
|
+
|
|
291
|
+
# Run tests
|
|
292
|
+
uv run pytest
|
|
293
|
+
|
|
294
|
+
# Run collector in dev mode
|
|
295
|
+
uv run logtap collect --reload
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
GPL v3 - see [LICENSE](LICENSE)
|
|
301
|
+
|
|
302
|
+
## Author
|
|
303
|
+
|
|
304
|
+
Kyle Cain - [@cainky](https://github.com/cainky)
|
logtap-0.4.1/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# logtap
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/logtap)
|
|
4
|
+
[](https://github.com/cainky/logtap/actions/workflows/tests.yml)
|
|
5
|
+
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
|
|
8
|
+
**`tail -f` for GPU clouds. Survives disconnects, aggregates multi-node.**
|
|
9
|
+
|
|
10
|
+
> Stop losing your training logs when SSH drops. Watch from anywhere, reconnect seamlessly.
|
|
11
|
+
|
|
12
|
+
## The Problem
|
|
13
|
+
|
|
14
|
+
You're training a model on RunPod, Vast.ai, or Lambda. You SSH in, start training, and:
|
|
15
|
+
|
|
16
|
+
- Your terminal disconnects after an hour
|
|
17
|
+
- You lose visibility into what's happening
|
|
18
|
+
- You resort to tmux hacks just to keep logs alive
|
|
19
|
+
- Multi-node training means logs scattered across machines
|
|
20
|
+
|
|
21
|
+
## The Solution
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# On your GPU instance
|
|
25
|
+
pip install logtap
|
|
26
|
+
logtap collect &
|
|
27
|
+
python train.py 2>&1 | logtap ingest run1
|
|
28
|
+
|
|
29
|
+
# From your laptop (or phone)
|
|
30
|
+
logtap tail run1 --follow
|
|
31
|
+
|
|
32
|
+
# Connection drops... reconnects automatically
|
|
33
|
+
# "reconnected (missed 0 lines)"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quickstart: RunPod / Vast.ai
|
|
37
|
+
|
|
38
|
+
On the GPU instance:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install logtap
|
|
42
|
+
export LOGTAP_API_KEY=secret
|
|
43
|
+
logtap collect --port 8000
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Start training and stream logs:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
python train.py 2>&1 | logtap ingest run1 --tag node=$(hostname)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
From your laptop:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
export LOGTAP_SERVER=http://<gpu-ip>:8000
|
|
56
|
+
export LOGTAP_API_KEY=secret
|
|
57
|
+
logtap tail run1 --follow
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Disconnect, close your terminal, or switch networks.
|
|
61
|
+
Re-run `logtap tail` anytime to resume where you left off.
|
|
62
|
+
|
|
63
|
+
Works the same on RunPod, Vast.ai, Lambda, and any ephemeral GPU cloud.
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
- **Survives Disconnects** - Resume from where you left off with cursor-based streaming
|
|
68
|
+
- **Pipe-Friendly** - Works with any training script via stdin
|
|
69
|
+
- **Multi-Node Ready** - Tag runs with `node=gpu1` and filter/aggregate
|
|
70
|
+
- **Zero Infra** - No database, no complex setup, just pip install
|
|
71
|
+
- **Lightweight** - <50MB memory, append-only file storage
|
|
72
|
+
|
|
73
|
+
## Why not tmux / mosh?
|
|
74
|
+
|
|
75
|
+
tmux and mosh help keep SSH sessions alive.
|
|
76
|
+
logtap solves a different problem.
|
|
77
|
+
|
|
78
|
+
- SSH can still drop (web terminals, proxies, idle timeouts)
|
|
79
|
+
- tmux doesn't aggregate logs across machines
|
|
80
|
+
- tmux can't be viewed from another device without SSH
|
|
81
|
+
- tmux sessions die when ephemeral instances stop
|
|
82
|
+
|
|
83
|
+
logtap streams logs over HTTP:
|
|
84
|
+
- survives disconnects
|
|
85
|
+
- resumes without gaps
|
|
86
|
+
- aggregates multi-node training via tags
|
|
87
|
+
- works from anywhere (no SSH required)
|
|
88
|
+
|
|
89
|
+
You can still use tmux. You just don't have to rely on it.
|
|
90
|
+
|
|
91
|
+
## Quick Start
|
|
92
|
+
|
|
93
|
+
### 1. Install
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install logtap
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Start Collector (on GPU instance)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
logtap collect --api-key secret
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. Pipe Your Training Logs
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
python train.py 2>&1 | logtap ingest run1 --api-key secret
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 4. Tail From Anywhere
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
export LOGTAP_SERVER=http://your-gpu-ip:8000
|
|
115
|
+
export LOGTAP_API_KEY=secret
|
|
116
|
+
|
|
117
|
+
logtap tail run1 --follow
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## CLI Commands
|
|
121
|
+
|
|
122
|
+
| Command | Description |
|
|
123
|
+
|---------|-------------|
|
|
124
|
+
| `logtap collect` | Start collector server (accepts ingested runs) |
|
|
125
|
+
| `logtap ingest <run>` | Pipe stdin to collector |
|
|
126
|
+
| `logtap tail <run>` | Tail a run with `--follow` for streaming |
|
|
127
|
+
| `logtap runs` | List active runs |
|
|
128
|
+
| `logtap doctor` | Check server connectivity and diagnose issues |
|
|
129
|
+
|
|
130
|
+
### Ingest Options
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Auto-generate run name
|
|
134
|
+
python train.py | logtap ingest
|
|
135
|
+
|
|
136
|
+
# Add tags for multi-node
|
|
137
|
+
python train.py | logtap ingest run1 --tag node=gpu1 --tag rank=0
|
|
138
|
+
|
|
139
|
+
# Quiet mode (no status messages)
|
|
140
|
+
python train.py | logtap ingest run1 --quiet
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Tail Options
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Follow mode (like tail -f)
|
|
147
|
+
logtap tail run1 --follow
|
|
148
|
+
|
|
149
|
+
# Resume from specific cursor (survives disconnects!)
|
|
150
|
+
logtap tail run1 --follow --since 5000
|
|
151
|
+
|
|
152
|
+
# Filter by tag
|
|
153
|
+
logtap tail run1 --tag node=gpu1
|
|
154
|
+
|
|
155
|
+
# Output formats
|
|
156
|
+
logtap tail run1 --output jsonl | jq '.line'
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Collector Options
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
logtap collect \
|
|
163
|
+
--port 8000 \
|
|
164
|
+
--api-key secret \
|
|
165
|
+
--data-dir ~/.logtap/runs \
|
|
166
|
+
--max-disk-mb 5000 \
|
|
167
|
+
--retention-hours 72
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Multi-Node Training
|
|
171
|
+
|
|
172
|
+
Tag each node and aggregate:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Node 1
|
|
176
|
+
python train.py | logtap ingest run1 --tag node=gpu1
|
|
177
|
+
|
|
178
|
+
# Node 2
|
|
179
|
+
python train.py | logtap ingest run1 --tag node=gpu2
|
|
180
|
+
|
|
181
|
+
# Watch all nodes
|
|
182
|
+
logtap tail run1 --follow
|
|
183
|
+
|
|
184
|
+
# Watch specific node
|
|
185
|
+
logtap tail run1 --follow --tag node=gpu1
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Environment Variables
|
|
189
|
+
|
|
190
|
+
| Variable | Default | Description |
|
|
191
|
+
|----------|---------|-------------|
|
|
192
|
+
| `LOGTAP_SERVER` | `http://localhost:8000` | Collector URL |
|
|
193
|
+
| `LOGTAP_API_KEY` | - | API key for auth |
|
|
194
|
+
|
|
195
|
+
Set these to avoid typing `--server` and `--api-key` every time.
|
|
196
|
+
|
|
197
|
+
## How It Works
|
|
198
|
+
|
|
199
|
+
1. **Collector** writes logs to append-only files with cursor tracking
|
|
200
|
+
2. **Ingest** streams stdin over HTTP chunked POST
|
|
201
|
+
3. **Tail** uses SSE (Server-Sent Events) with resume support
|
|
202
|
+
4. **Reconnect** passes `?since=<cursor>` to continue without gaps
|
|
203
|
+
|
|
204
|
+
No database. No message queue. Just files and HTTP.
|
|
205
|
+
|
|
206
|
+
## API Endpoints
|
|
207
|
+
|
|
208
|
+
For scripting or custom integrations:
|
|
209
|
+
|
|
210
|
+
| Endpoint | Description |
|
|
211
|
+
|----------|-------------|
|
|
212
|
+
| `POST /runs/{id}/ingest` | Stream lines (chunked POST) |
|
|
213
|
+
| `GET /runs/{id}/stream` | SSE stream with `?since=&follow=` |
|
|
214
|
+
| `GET /runs/{id}/query` | Query with `?from=&to=&search=` |
|
|
215
|
+
| `GET /runs` | List runs |
|
|
216
|
+
| `GET /health` | Health check with capabilities |
|
|
217
|
+
|
|
218
|
+
## Legacy: Static File Mode
|
|
219
|
+
|
|
220
|
+
logtap also works as a simple remote log viewer (the original use case):
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# On server with log files
|
|
224
|
+
logtap serve --log-dir /var/log
|
|
225
|
+
|
|
226
|
+
# From client
|
|
227
|
+
logtap tail syslog --server http://myserver:8000 --follow
|
|
228
|
+
logtap query auth.log --regex "Failed password"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Security
|
|
232
|
+
|
|
233
|
+
- **API Key Auth** - Optional but recommended for production
|
|
234
|
+
- **Path Traversal Protection** - Comprehensive defense with symlink-safe containment checks (see [SECURITY.md](SECURITY.md))
|
|
235
|
+
- **ReDoS Protection** - Uses google-re2 for guaranteed linear-time regex matching
|
|
236
|
+
- **Read-Only by Default** - Collector only writes to its data directory
|
|
237
|
+
- **Input Validation** - Rejects control characters, NUL bytes, and malicious path patterns
|
|
238
|
+
|
|
239
|
+
## Development
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
git clone https://github.com/cainky/logtap.git
|
|
243
|
+
cd logtap
|
|
244
|
+
|
|
245
|
+
# Install with uv
|
|
246
|
+
uv sync --extra dev
|
|
247
|
+
|
|
248
|
+
# Run tests
|
|
249
|
+
uv run pytest
|
|
250
|
+
|
|
251
|
+
# Run collector in dev mode
|
|
252
|
+
uv run logtap collect --reload
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
GPL v3 - see [LICENSE](LICENSE)
|
|
258
|
+
|
|
259
|
+
## Author
|
|
260
|
+
|
|
261
|
+
Kyle Cain - [@cainky](https://github.com/cainky)
|