diff --git a/pyproject.toml b/pyproject.toml index d4db2c890..e6750897d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,6 @@ dependencies = [ "boltons>=24.1.0", "construct >=2.10,<3.0", "more-itertools >=10.3.0,<11.0.0", - "msgspec >=0.11,<0.19", "platformdirs >=2.6,<5.0", "pydantic >=2.0,<3.0", "tabulate >=0.9", diff --git a/src/gallia/cli/hr.py b/src/gallia/cli/hr.py index ee0d03a3d..63961d59b 100644 --- a/src/gallia/cli/hr.py +++ b/src/gallia/cli/hr.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 import argparse +import json import os import signal import sys @@ -10,8 +11,6 @@ from pathlib import Path from typing import cast -import msgspec - from gallia import exitcodes from gallia.log import ColorMode, PenlogPriority, PenlogReader, resolve_color_mode @@ -89,7 +88,7 @@ def _main() -> int: def main() -> None: try: sys.exit(_main()) - except (msgspec.DecodeError, msgspec.ValidationError) as e: + except json.JSONDecodeError as e: print(f"invalid file format: {e}", file=sys.stderr) sys.exit(exitcodes.DATAERR) # BrokenPipeError appears when stuff is piped to | head. diff --git a/src/gallia/command/base.py b/src/gallia/command/base.py index 3bb0fbdd1..8731fbe66 100644 --- a/src/gallia/command/base.py +++ b/src/gallia/command/base.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 import asyncio +import dataclasses import json import os import os.path @@ -19,7 +20,6 @@ from tempfile import gettempdir from typing import Any, Protocol, Self, cast -import msgspec from pydantic import ConfigDict, field_serializer, model_validator from gallia import exitcodes @@ -49,7 +49,8 @@ class HookVariant(Enum): POST = "post" -class RunMeta(msgspec.Struct): +@dataclasses.dataclass +class RunMeta: command: str start_time: str end_time: str @@ -57,7 +58,7 @@ class RunMeta(msgspec.Struct): config: MutableMapping[str, Any] def json(self) -> str: - return msgspec.json.encode(self).decode() + return json.dumps(dataclasses.asdict(self)) logger = get_logger(__name__) diff --git a/src/gallia/log.py b/src/gallia/log.py index bbf1986fc..fdcbdf96f 100644 --- a/src/gallia/log.py +++ b/src/gallia/log.py @@ -5,9 +5,11 @@ from __future__ import annotations import atexit +import dataclasses import datetime import gzip import io +import json import logging import mmap import os @@ -18,7 +20,6 @@ import time import traceback from collections.abc import Iterator -from dataclasses import dataclass from enum import Enum, IntEnum, unique from logging.handlers import QueueHandler, QueueListener from pathlib import Path @@ -26,7 +27,6 @@ from types import TracebackType from typing import TYPE_CHECKING, Any, BinaryIO, Self, TextIO, TypeAlias, cast -import msgspec import zstandard if TYPE_CHECKING: @@ -327,12 +327,14 @@ def add_zst_log_handler( return zstd_handler -class _PenlogRecordV2(msgspec.Struct, omit_defaults=True, tag=2, tag_field="version"): +@dataclasses.dataclass +class _PenlogRecordV2: module: str host: str data: str datetime: str priority: int + version: int tags: list[str] | None = None line: str | None = None stacktrace: str | None = None @@ -417,7 +419,7 @@ def _format_record( # noqa: PLR0913 return msg -@dataclass +@dataclasses.dataclass class PenlogRecord: module: str host: str @@ -459,20 +461,26 @@ def parse_json(cls, data: bytes) -> Self: if data.startswith(b"<"): data = data[data.index(b">") + 1 :] - record = msgspec.json.decode(data, type=_PenlogRecordV2) + record = json.loads(data.decode()) + if (v := record["version"]) != 2: + raise json.JSONDecodeError(f"invalid log record version {v}", data.decode(), 0) return cls( - module=record.module, - host=record.host, - data=record.data, - datetime=datetime.datetime.fromisoformat(record.datetime), - priority=PenlogPriority(record.priority), - tags=record.tags, - line=record.line, - stacktrace=record.stacktrace, - _python_level_no=record._python_level_no, - _python_level_name=record._python_level_name, - _python_func_name=record._python_func_name, + module=record["module"], + host=record["host"], + data=record["data"], + datetime=datetime.datetime.fromisoformat(record["datetime"]), + priority=PenlogPriority(record["priority"]), + tags=record["tags"] if "tags" in record else None, + line=record["line"] if "line" in record else None, + stacktrace=record["stacktrace"] if "stacktrace" in record else None, + _python_level_no=record["_python_level_no"] if "_python_level_no" in record else None, + _python_level_name=record["_python_level_name"] + if "_python_level_name" in record + else None, + _python_func_name=record["_python_func_name"] + if "_python_func_name" in record + else None, ) def to_log_record(self) -> logging.LogRecord: @@ -692,9 +700,9 @@ def format(self, record: logging.LogRecord) -> str: _python_level_no=record.levelno, _python_level_name=record.levelname, _python_func_name=record.funcName, + version=2, ) - - return msgspec.json.encode(penlog_record).decode() + return json.dumps(dataclasses.asdict(penlog_record)) class _ConsoleFormatter(logging.Formatter): diff --git a/uv.lock b/uv.lock index 374cd0d36..76e52f258 100644 --- a/uv.lock +++ b/uv.lock @@ -278,7 +278,6 @@ dependencies = [ { name = "boltons" }, { name = "construct" }, { name = "more-itertools" }, - { name = "msgspec" }, { name = "platformdirs" }, { name = "pydantic" }, { name = "tabulate" }, @@ -312,7 +311,6 @@ requires-dist = [ { name = "boltons", specifier = ">=24.1.0" }, { name = "construct", specifier = ">=2.10,<3.0" }, { name = "more-itertools", specifier = ">=10.3.0,<11.0.0" }, - { name = "msgspec", specifier = ">=0.11,<0.19" }, { name = "platformdirs", specifier = ">=2.6,<5.0" }, { name = "pydantic", specifier = ">=2.0,<3.0" }, { name = "tabulate", specifier = ">=0.9" }, @@ -471,28 +469,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", size = 60952 }, ] -[[package]] -name = "msgspec" -version = "0.18.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/fb/42b1865063fddb14dbcbb6e74e0a366ecf1ba371c4948664dde0b0e10f95/msgspec-0.18.6.tar.gz", hash = "sha256:a59fc3b4fcdb972d09138cb516dbde600c99d07c38fd9372a6ef500d2d031b4e", size = 216757 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/20/278def3822dec807be1e2a734ba9547500ff06667be9dda00ab5d277d605/msgspec-0.18.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e77e56ffe2701e83a96e35770c6adb655ffc074d530018d1b584a8e635b4f36f", size = 200058 }, - { url = "https://files.pythonhosted.org/packages/25/8c/75bfafb040934dd3eb46234a2bd4d8fcc7b646f77440866f954b60e0886b/msgspec-0.18.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5351afb216b743df4b6b147691523697ff3a2fc5f3d54f771e91219f5c23aaa", size = 189108 }, - { url = "https://files.pythonhosted.org/packages/0d/e6/5dd960a7678cbaf90dc910611a0e700775ee341876f029c3c987122afe84/msgspec-0.18.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3232fabacef86fe8323cecbe99abbc5c02f7698e3f5f2e248e3480b66a3596b", size = 208138 }, - { url = "https://files.pythonhosted.org/packages/6a/73/1b2f991dc26899d2f999c938cbc82c858b3cb7e3ccaad317b32760dbe1da/msgspec-0.18.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b524df6ea9998bbc99ea6ee4d0276a101bcc1aa8d14887bb823914d9f60d07", size = 209538 }, - { url = "https://files.pythonhosted.org/packages/29/d4/2fb2d40b3bde566fd14bf02bf503eea20a912a02cdf7ff100629906c9094/msgspec-0.18.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37f67c1d81272131895bb20d388dd8d341390acd0e192a55ab02d4d6468b434c", size = 213571 }, - { url = "https://files.pythonhosted.org/packages/59/5a/c2aeeefd78946713047637f0c422c0b8b31182eb9bbed0068e906cc8aca0/msgspec-0.18.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0feb7a03d971c1c0353de1a8fe30bb6579c2dc5ccf29b5f7c7ab01172010492", size = 215785 }, - { url = "https://files.pythonhosted.org/packages/51/c6/0a8ae23c91ba1e6d58ddb089bba4ce8dad5815411b4a2bb40a5f15d2ab73/msgspec-0.18.6-cp311-cp311-win_amd64.whl", hash = "sha256:41cf758d3f40428c235c0f27bc6f322d43063bc32da7b9643e3f805c21ed57b4", size = 185877 }, - { url = "https://files.pythonhosted.org/packages/1d/b5/c8fbf1db814eb29eda402952374b594b2559419ba7ec6d0997a9e5687530/msgspec-0.18.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d86f5071fe33e19500920333c11e2267a31942d18fed4d9de5bc2fbab267d28c", size = 202109 }, - { url = "https://files.pythonhosted.org/packages/d7/9a/235d2dbab078a0b8e6f338205dc59be0b027ce000554ee6a9c41b19339e5/msgspec-0.18.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce13981bfa06f5eb126a3a5a38b1976bddb49a36e4f46d8e6edecf33ccf11df1", size = 190281 }, - { url = "https://files.pythonhosted.org/packages/0e/f2/f864ed36a8a62c26b57c3e08d212bd8f3d12a3ca3ef64600be5452aa3c82/msgspec-0.18.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97dec6932ad5e3ee1e3c14718638ba333befc45e0661caa57033cd4cc489466", size = 210305 }, - { url = "https://files.pythonhosted.org/packages/73/16/dfef780ced7d690dd5497846ed242ef3e27e319d59d1ddaae816a4f2c15e/msgspec-0.18.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad237100393f637b297926cae1868b0d500f764ccd2f0623a380e2bcfb2809ca", size = 212510 }, - { url = "https://files.pythonhosted.org/packages/c1/90/f5b3a788c4b3d92190e3345d1afa3dd107d5f16b8194e1f61b72582ee9bd/msgspec-0.18.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db1d8626748fa5d29bbd15da58b2d73af25b10aa98abf85aab8028119188ed57", size = 214844 }, - { url = "https://files.pythonhosted.org/packages/ce/0b/d4cc1b09f8dfcc6cc4cc9739c13a86e093fe70257b941ea9feb15df22996/msgspec-0.18.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d70cb3d00d9f4de14d0b31d38dfe60c88ae16f3182988246a9861259c6722af6", size = 217113 }, - { url = "https://files.pythonhosted.org/packages/3f/76/30d8f152299f65c85c46a2cbeaf95ad1d18516b5ce730acdaef696d4cfe6/msgspec-0.18.6-cp312-cp312-win_amd64.whl", hash = "sha256:1003c20bfe9c6114cc16ea5db9c5466e49fae3d7f5e2e59cb70693190ad34da0", size = 187184 }, -] - [[package]] name = "mypy" version = "1.13.0"