Skip to content

Logs

Logs shows the application's Python logs live, newest first. See the Logs guide.

import dachshund
from dachshund.widgets import Logs

with dachshund.row():
    dachshund.add(Logs("api_errors", logger="myapp.api", level="ERROR"))

dachshund.widgets.logs.logs.Logs dataclass

Logs(name, *, source='', title=None, actions=list(), action_args=dict(), persist=False, columns=(lambda: ['time', 'level', 'logger', 'message'])(), key=None, page_size=50, loader=None, search=True, sortable=list(), logger='', level='INFO', keep=500)

Bases: Table

The application's Python logs, newest first, live.

Adding the widget attaches a handler to its logger, removed when the widget leaves the dashboard or the dashboard stops. Lines are events of its source, and the newest keep stay in the event store, across restarts.

Example

with dachshund.row():
    dachshund.add(Logs("logs"))
    dachshund.add(Logs("api_errors", logger="myapp.api", level="ERROR"))

Options

  • logger (str) - The logger captured, with its children. Defaults to "", every logger.
  • level (str) - The lowest level captured, like "WARNING". Defaults to "INFO".
  • keep (int) - Lines kept. Defaults to 500.
  • page_size (int) - Lines per page. Defaults to 50.
  • search (bool) - Shows a search box. Defaults to True.

logger class-attribute instance-attribute

logger = ''

level class-attribute instance-attribute

level = 'INFO'

keep class-attribute instance-attribute

keep = 500

columns class-attribute instance-attribute

columns = field(default_factory=lambda: ['time', 'level', 'logger', 'message'])

page_size class-attribute instance-attribute

page_size = 50

sortable class-attribute instance-attribute

sortable = field(default_factory=list)

handler class-attribute instance-attribute

handler = field(default=None, init=False, repr=False, compare=False)

type class-attribute

type = 'logs'

script class-attribute

script = Path(__file__).parent / 'logs.js'

style class-attribute

style = Path(__file__).parent / 'logs.css'

levelno property

levelno

raw_events

raw_events()

Its newest keep lines.

Source code in dachshund/widgets/logs/logs.py
119
120
121
def raw_events(self) -> int | None:
    """Its newest `keep` lines."""
    return self.keep

bind

bind(dashboard)

Attach this widget's handler to its logger.

Source code in dachshund/widgets/logs/logs.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def bind(self, dashboard: "Dashboard") -> None:
    """Attach this widget's handler to its logger."""
    if self.handler is not None:
        return

    captured = logging.getLogger(self.logger or None)
    self.handler = LogsHandler(self, dashboard)
    captured.addHandler(self.handler)

    if captured.getEffectiveLevel() > self.levelno:
        logger.warning(
            "%r shows %s and up, but its logger %r only passes %s and up. "
            "Lower the logger's level to see those lines.",
            self.name,
            self.level,
            captured.name,
            logging.getLevelName(captured.getEffectiveLevel()),
        )

unbind

unbind()

Detach this widget's handler from its logger.

Source code in dachshund/widgets/logs/logs.py
146
147
148
149
150
151
152
def unbind(self) -> None:
    """Detach this widget's handler from its logger."""
    if self.handler is None:
        return

    logging.getLogger(self.logger or None).removeHandler(self.handler)
    self.handler = None

render

render(events)

Every kept line, newest first.

Source code in dachshund/widgets/logs/logs.py
154
155
156
157
158
159
def render(self, events: Sequence[Event]) -> dict[str, Any]:
    """Every kept line, newest first."""
    payload = super().render(events)
    payload["rows"].reverse()

    return payload

page async

page(events, query, context, host_loop=None)

A page of lines, with the levels offered and the ones shown.

levels are the toggles drawn, the levels this widget captures. shown are the ones picked, empty meaning all of them.

Source code in dachshund/widgets/logs/logs.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
async def page(
    self,
    events: Sequence[Event],
    query: TableQuery,
    context: Context,
    host_loop: asyncio.AbstractEventLoop | None = None,
) -> dict[str, Any]:
    """A page of lines, with the levels offered and the ones shown.

    `levels` are the toggles drawn, the levels this widget captures.
    `shown` are the ones picked, empty meaning all of them.
    """
    payload = await super().page(events, query, context, host_loop)
    payload["levels"] = [level for level in SHOWN if level >= self.levelno]
    payload["shown"] = sorted(query.levels)

    return payload

query

query(rows, query)

rows at one of query's levels, all of them when it names none, matching its search.

Source code in dachshund/widgets/logs/logs.py
179
180
181
182
183
184
185
186
def query(
    self, rows: list[dict[str, Any]], query: TableQuery
) -> list[dict[str, Any]]:
    """`rows` at one of `query`'s levels, all of them when it names none, matching its search."""
    if query.levels:
        rows = [row for row in rows if row["fields"].get("levelno") in query.levels]

    return super().query(rows, query)