github-actions-logger 0.1.0__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.
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: github-actions-logger
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Cyb3r-Jak3
|
|
6
|
+
Author-email: Cyb3r-Jak3 <git@cyberjake.xyz>
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Natural Language :: English
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
17
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# github-actions-logger
|
|
22
|
+
|
|
23
|
+
A `logging.Logger` subclass that emits [GitHub Actions workflow commands](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands) when running in CI, and behaves like a normal logger everywhere else.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install github-actions-logger
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or with uv:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv add github-actions-logger
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requires Python 3.12 or newer.
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from github_actions_logger import Logger
|
|
43
|
+
|
|
44
|
+
log = Logger("my-app")
|
|
45
|
+
|
|
46
|
+
log.debug("Cache key: %s", key)
|
|
47
|
+
log.info("Deployed")
|
|
48
|
+
log.warning("Deprecated option used")
|
|
49
|
+
log.error("Build failed")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
In GitHub Actions (detected via `GITHUB_ACTIONS=true`) these print:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
::debug::Cache key: abc123
|
|
56
|
+
::notice::Deployed
|
|
57
|
+
::warning::Deprecated option used
|
|
58
|
+
::error::Build failed
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Anywhere else, the calls go through standard `logging`, so handlers, levels and formatters work as usual.
|
|
62
|
+
|
|
63
|
+
### Level mapping
|
|
64
|
+
|
|
65
|
+
| Method | Workflow command |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `debug` | `::debug::` |
|
|
68
|
+
| `info` | `::notice::` |
|
|
69
|
+
| `warning` | `::warning::` |
|
|
70
|
+
| `error`, `critical`, `exception` | `::error::` |
|
|
71
|
+
|
|
72
|
+
`exception()` (or any call with `exc_info`) appends the traceback to the message.
|
|
73
|
+
|
|
74
|
+
### Annotations
|
|
75
|
+
|
|
76
|
+
`notice`, `warning` and `error` messages can be attached to a file and line by passing the standard `extra` argument:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
log.warning(
|
|
80
|
+
"Unused import",
|
|
81
|
+
extra={"title": "Lint", "file": "src/app.py", "line": 12, "end_line": 12, "col": 1, "end_column": 20},
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
::warning title=Lint,file=src/app.py,line=12,endLine=12,col=1,endColumn=20::Unused import
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| `extra` key | Workflow property |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `title` | `title` |
|
|
92
|
+
| `file` | `file` |
|
|
93
|
+
| `line` | `line` |
|
|
94
|
+
| `end_line` | `endLine` |
|
|
95
|
+
| `col` | `col` |
|
|
96
|
+
| `end_column` | `endColumn` |
|
|
97
|
+
|
|
98
|
+
Missing or `None` keys and unrelated keys are ignored, and `debug` ignores them entirely since `::debug::` takes no properties. Outside CI, `extra` becomes attributes on the `LogRecord`, so a formatter can use it (for example `%(file)s:%(line)s`).
|
|
99
|
+
|
|
100
|
+
### Grouping log lines
|
|
101
|
+
|
|
102
|
+
`group()` returns a context manager that wraps output in a collapsible [log group](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#grouping-log-lines):
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
with log.group("Install dependencies"):
|
|
106
|
+
log.info("Resolving...")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
::group::Install dependencies
|
|
111
|
+
::notice::Resolving...
|
|
112
|
+
::endgroup::
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`::endgroup::` is emitted even if the block raises. Outside CI, `group()` does nothing.
|
|
116
|
+
|
|
117
|
+
### Overriding detection
|
|
118
|
+
|
|
119
|
+
CI detection happens once, when the logger is created. To force it on or off, pass `in_ci`:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
Logger("my-app", in_ci=True) # always emit workflow commands
|
|
123
|
+
Logger("my-app", in_ci=False) # always use standard logging
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Notes
|
|
127
|
+
|
|
128
|
+
- In CI, messages are written straight to stdout and bypass the logger's level and handlers. GitHub only displays `::debug::` lines when step debug logging is enabled (`ACTIONS_STEP_DEBUG`).
|
|
129
|
+
- Create the logger with `Logger(...)` directly. `logging.setLoggerClass(Logger)` plus `logging.getLogger(name)` works, but only passes `name`, so `in_ci` can't be set that way.
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv run pytest
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Runs the tests with a coverage report.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# github-actions-logger
|
|
2
|
+
|
|
3
|
+
A `logging.Logger` subclass that emits [GitHub Actions workflow commands](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands) when running in CI, and behaves like a normal logger everywhere else.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install github-actions-logger
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with uv:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv add github-actions-logger
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Python 3.12 or newer.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from github_actions_logger import Logger
|
|
23
|
+
|
|
24
|
+
log = Logger("my-app")
|
|
25
|
+
|
|
26
|
+
log.debug("Cache key: %s", key)
|
|
27
|
+
log.info("Deployed")
|
|
28
|
+
log.warning("Deprecated option used")
|
|
29
|
+
log.error("Build failed")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
In GitHub Actions (detected via `GITHUB_ACTIONS=true`) these print:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
::debug::Cache key: abc123
|
|
36
|
+
::notice::Deployed
|
|
37
|
+
::warning::Deprecated option used
|
|
38
|
+
::error::Build failed
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Anywhere else, the calls go through standard `logging`, so handlers, levels and formatters work as usual.
|
|
42
|
+
|
|
43
|
+
### Level mapping
|
|
44
|
+
|
|
45
|
+
| Method | Workflow command |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `debug` | `::debug::` |
|
|
48
|
+
| `info` | `::notice::` |
|
|
49
|
+
| `warning` | `::warning::` |
|
|
50
|
+
| `error`, `critical`, `exception` | `::error::` |
|
|
51
|
+
|
|
52
|
+
`exception()` (or any call with `exc_info`) appends the traceback to the message.
|
|
53
|
+
|
|
54
|
+
### Annotations
|
|
55
|
+
|
|
56
|
+
`notice`, `warning` and `error` messages can be attached to a file and line by passing the standard `extra` argument:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
log.warning(
|
|
60
|
+
"Unused import",
|
|
61
|
+
extra={"title": "Lint", "file": "src/app.py", "line": 12, "end_line": 12, "col": 1, "end_column": 20},
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
::warning title=Lint,file=src/app.py,line=12,endLine=12,col=1,endColumn=20::Unused import
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| `extra` key | Workflow property |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `title` | `title` |
|
|
72
|
+
| `file` | `file` |
|
|
73
|
+
| `line` | `line` |
|
|
74
|
+
| `end_line` | `endLine` |
|
|
75
|
+
| `col` | `col` |
|
|
76
|
+
| `end_column` | `endColumn` |
|
|
77
|
+
|
|
78
|
+
Missing or `None` keys and unrelated keys are ignored, and `debug` ignores them entirely since `::debug::` takes no properties. Outside CI, `extra` becomes attributes on the `LogRecord`, so a formatter can use it (for example `%(file)s:%(line)s`).
|
|
79
|
+
|
|
80
|
+
### Grouping log lines
|
|
81
|
+
|
|
82
|
+
`group()` returns a context manager that wraps output in a collapsible [log group](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#grouping-log-lines):
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
with log.group("Install dependencies"):
|
|
86
|
+
log.info("Resolving...")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
::group::Install dependencies
|
|
91
|
+
::notice::Resolving...
|
|
92
|
+
::endgroup::
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`::endgroup::` is emitted even if the block raises. Outside CI, `group()` does nothing.
|
|
96
|
+
|
|
97
|
+
### Overriding detection
|
|
98
|
+
|
|
99
|
+
CI detection happens once, when the logger is created. To force it on or off, pass `in_ci`:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
Logger("my-app", in_ci=True) # always emit workflow commands
|
|
103
|
+
Logger("my-app", in_ci=False) # always use standard logging
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Notes
|
|
107
|
+
|
|
108
|
+
- In CI, messages are written straight to stdout and bypass the logger's level and handlers. GitHub only displays `::debug::` lines when step debug logging is enabled (`ACTIONS_STEP_DEBUG`).
|
|
109
|
+
- Create the logger with `Logger(...)` directly. `logging.setLoggerClass(Logger)` plus `logging.getLogger(name)` works, but only passes `name`, so `in_ci` can't be set that way.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv run pytest
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Runs the tests with a coverage report.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "github-actions-logger"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = []
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Development Status :: 4 - Beta",
|
|
10
|
+
"Intended Audience :: Developers",
|
|
11
|
+
"Natural Language :: English",
|
|
12
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Programming Language :: Python :: 3.14",
|
|
17
|
+
"Programming Language :: Python :: 3.15",
|
|
18
|
+
"Programming Language :: Python :: Implementation :: PyPy",
|
|
19
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[[project.authors]]
|
|
23
|
+
name = "Cyb3r-Jak3"
|
|
24
|
+
email = "git@cyberjake.xyz"
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["uv_build>=0.13.1,<0.14.0"]
|
|
28
|
+
build-backend = "uv_build"
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=9",
|
|
33
|
+
"pytest-cov>=7",
|
|
34
|
+
"ruff>=0.17.1",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
pythonpath = ["src"]
|
|
39
|
+
testpaths = ["tests"]
|
|
40
|
+
addopts = "--cov=github_actions_logger --cov-report=term-missing"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "github-actions-logger"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Cyb3r-Jak3", email = "git@cyberjake.xyz" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = []
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Natural Language :: English",
|
|
15
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Programming Language :: Python :: 3.14",
|
|
20
|
+
"Programming Language :: Python :: 3.15",
|
|
21
|
+
"Programming Language :: Python :: Implementation :: PyPy",
|
|
22
|
+
"Programming Language :: Python :: Implementation :: CPython"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["uv_build>=0.13.1,<0.14.0"]
|
|
27
|
+
build-backend = "uv_build"
|
|
28
|
+
|
|
29
|
+
[dependency-groups]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest>=9",
|
|
32
|
+
"pytest-cov>=7",
|
|
33
|
+
"ruff>=0.17.1",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
pythonpath = ["src"]
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
addopts = "--cov=github_actions_logger --cov-report=term-missing"
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import traceback
|
|
5
|
+
from typing import Self
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_github_actions() -> bool:
|
|
9
|
+
"""Return True when running inside a GitHub Actions workflow."""
|
|
10
|
+
return os.environ.get("GITHUB_ACTIONS") == "true"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _escape_data(message: str) -> str:
|
|
14
|
+
"""Escape a message as required by workflow commands."""
|
|
15
|
+
return message.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _escape_property(value: object) -> str:
|
|
19
|
+
"""Escape an annotation property value as required by workflow commands."""
|
|
20
|
+
return _escape_data(str(value)).replace(":", "%3A").replace(",", "%2C")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# `extra` key -> workflow command property name
|
|
24
|
+
_ANNOTATION_PROPERTIES = {
|
|
25
|
+
"title": "title",
|
|
26
|
+
"file": "file",
|
|
27
|
+
"line": "line",
|
|
28
|
+
"end_line": "endLine",
|
|
29
|
+
"col": "col",
|
|
30
|
+
"end_column": "endColumn",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _exc_text(exc_info: object) -> str:
|
|
35
|
+
"""Format an `exc_info` argument the way `logging` accepts it."""
|
|
36
|
+
if exc_info is True:
|
|
37
|
+
exc_info = sys.exc_info()
|
|
38
|
+
elif isinstance(exc_info, BaseException):
|
|
39
|
+
exc_info = (type(exc_info), exc_info, exc_info.__traceback__)
|
|
40
|
+
if not isinstance(exc_info, tuple) or exc_info[0] is None:
|
|
41
|
+
return ""
|
|
42
|
+
return "".join(traceback.format_exception(*exc_info)).rstrip("\n")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Group:
|
|
46
|
+
"""Context manager that wraps output in a collapsible log group.
|
|
47
|
+
|
|
48
|
+
Emits `::group::{title}` on entry and `::endgroup::` on exit. Does nothing
|
|
49
|
+
when not running in CI.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, title: str, in_ci: bool) -> None:
|
|
53
|
+
self.title = title
|
|
54
|
+
self._in_ci = in_ci
|
|
55
|
+
|
|
56
|
+
def __enter__(self) -> Self:
|
|
57
|
+
if self._in_ci:
|
|
58
|
+
sys.stdout.write(f"::group::{_escape_data(self.title)}\n")
|
|
59
|
+
sys.stdout.flush()
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def __exit__(self, *exc_info: object) -> None:
|
|
63
|
+
if self._in_ci:
|
|
64
|
+
sys.stdout.write("::endgroup::\n")
|
|
65
|
+
sys.stdout.flush()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Logger(logging.Logger):
|
|
69
|
+
"""Logger that emits GitHub Actions workflow commands when running in CI.
|
|
70
|
+
|
|
71
|
+
`debug` -> `::debug::`, `info` -> `::notice::`, `warning` -> `::warning::`,
|
|
72
|
+
`error`/`critical`/`exception` -> `::error::`. Outside CI, standard logging is used.
|
|
73
|
+
|
|
74
|
+
Annotation properties can be passed via `extra` (ignored for `debug`):
|
|
75
|
+
`title`, `file`, `line`, `end_line`, `col` and `end_column`.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, name: str, in_ci: bool | None = None) -> None:
|
|
79
|
+
super().__init__(name)
|
|
80
|
+
self._in_ci = is_github_actions() if in_ci is None else in_ci
|
|
81
|
+
|
|
82
|
+
def _command(self, command: str, msg: object, args: tuple, kwargs: dict) -> None:
|
|
83
|
+
message = str(msg) % args if args else str(msg)
|
|
84
|
+
if kwargs.get("exc_info"):
|
|
85
|
+
text = _exc_text(kwargs["exc_info"])
|
|
86
|
+
if text:
|
|
87
|
+
message = f"{message}\n{text}"
|
|
88
|
+
properties = ""
|
|
89
|
+
extra = kwargs.get("extra")
|
|
90
|
+
if command != "debug" and isinstance(extra, dict):
|
|
91
|
+
parts = [
|
|
92
|
+
f"{name}={_escape_property(extra[key])}"
|
|
93
|
+
for key, name in _ANNOTATION_PROPERTIES.items()
|
|
94
|
+
if extra.get(key) is not None
|
|
95
|
+
]
|
|
96
|
+
if parts:
|
|
97
|
+
properties = " " + ",".join(parts)
|
|
98
|
+
sys.stdout.write(f"::{command}{properties}::{_escape_data(message)}\n")
|
|
99
|
+
sys.stdout.flush()
|
|
100
|
+
|
|
101
|
+
def group(self, title: str) -> Group:
|
|
102
|
+
"""Return a context manager that wraps output in a collapsible log group."""
|
|
103
|
+
return Group(title, self._in_ci)
|
|
104
|
+
|
|
105
|
+
def debug(self, msg: object, *args: object, **kwargs: object) -> None:
|
|
106
|
+
if self._in_ci:
|
|
107
|
+
self._command("debug", msg, args, kwargs)
|
|
108
|
+
else:
|
|
109
|
+
super().debug(msg, *args, **kwargs) # type: ignore[arg-type]
|
|
110
|
+
|
|
111
|
+
def info(self, msg: object, *args: object, **kwargs: object) -> None:
|
|
112
|
+
if self._in_ci:
|
|
113
|
+
self._command("notice", msg, args, kwargs)
|
|
114
|
+
else:
|
|
115
|
+
super().info(msg, *args, **kwargs) # type: ignore[arg-type]
|
|
116
|
+
|
|
117
|
+
def warning(self, msg: object, *args: object, **kwargs: object) -> None:
|
|
118
|
+
if self._in_ci:
|
|
119
|
+
self._command("warning", msg, args, kwargs)
|
|
120
|
+
else:
|
|
121
|
+
super().warning(msg, *args, **kwargs) # type: ignore[arg-type]
|
|
122
|
+
|
|
123
|
+
def error(self, msg: object, *args: object, **kwargs: object) -> None:
|
|
124
|
+
if self._in_ci:
|
|
125
|
+
self._command("error", msg, args, kwargs)
|
|
126
|
+
else:
|
|
127
|
+
super().error(msg, *args, **kwargs) # type: ignore[arg-type]
|
|
128
|
+
|
|
129
|
+
def critical(self, msg: object, *args: object, **kwargs: object) -> None:
|
|
130
|
+
if self._in_ci:
|
|
131
|
+
self._command("error", msg, args, kwargs)
|
|
132
|
+
else:
|
|
133
|
+
super().critical(msg, *args, **kwargs) # type: ignore[arg-type]
|