1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2023 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Functions for specialised logging with HTML output.
10 from typing import Any, Optional, cast
11 from contextvars import ContextVar
14 import sqlalchemy as sa
15 from sqlalchemy.ext.asyncio import AsyncConnection
18 from pygments import highlight
19 from pygments.lexers import PythonLexer, PostgresLexer
20 from pygments.formatters import HtmlFormatter
22 except ModuleNotFoundError:
23 CODE_HIGHLIGHT = False
27 """ Interface for logging function.
29 The base implementation does nothing. Overwrite the functions
30 in derived classes which implement logging functionality.
32 def get_buffer(self) -> str:
35 def function(self, func: str, **kwargs: Any) -> None:
36 """ Start a new debug chapter for the given function and its parameters.
40 def section(self, heading: str) -> None:
41 """ Start a new section with the given title.
45 def comment(self, text: str) -> None:
46 """ Add a simple comment to the debug output.
50 def var_dump(self, heading: str, var: Any) -> None:
51 """ Print the content of the variable to the debug output prefixed by
56 def sql(self, conn: AsyncConnection, statement: 'sa.Executable') -> None:
57 """ Print the SQL for the given statement.
61 class HTMLLogger(BaseLogger):
62 """ Logger that formats messages in HTML.
65 self.buffer = io.StringIO()
68 def get_buffer(self) -> str:
69 return HTML_HEADER + self.buffer.getvalue() + HTML_FOOTER
71 def function(self, func: str, **kwargs: Any) -> None:
72 """ Start a new debug chapter for the given function and its parameters.
74 self._write(f"<h1>Debug output for {func}()</h1>\n<p>Parameters:<dl>")
75 for name, value in kwargs.items():
76 self._write(f'<dt>{name}</dt><dd>{self._python_var(value)}</dd>')
77 self._write('</dl></p>')
80 def section(self, heading: str) -> None:
81 """ Start a new section with the given title.
83 self._write(f"<h2>{heading}</h2>")
85 def comment(self, text: str) -> None:
86 """ Add a simple comment to the debug output.
88 self._write(f"<p>{text}</p>")
90 def var_dump(self, heading: str, var: Any) -> None:
91 """ Print the content of the variable to the debug output prefixed by
94 self._write(f'<h5>{heading}</h5>{self._python_var(var)}')
97 def sql(self, conn: AsyncConnection, statement: 'sa.Executable') -> None:
98 """ Dump the SQL statement to debug output.
100 sqlstr = str(cast('sa.ClauseElement', statement)
101 .compile(conn.sync_engine, compile_kwargs={"literal_binds": True}))
103 sqlstr = highlight(sqlstr, PostgresLexer(),
104 HtmlFormatter(nowrap=True, lineseparator='<br>'))
105 self._write(f'<div class="highlight"><code class="lang-sql">{sqlstr}</code></div>')
107 self._write(f'<code class="lang-sql">{sqlstr}</code>')
110 def _python_var(self, var: Any) -> str:
112 fmt = highlight(repr(var), PythonLexer(), HtmlFormatter(nowrap=True))
113 return f'<div class="highlight"><code class="lang-python">{fmt}</code></div>'
115 return f'<code class="lang-python">{str(var)}</code>'
118 def _write(self, text: str) -> None:
119 """ Add the raw text to the debug output.
121 self.buffer.write(text)
124 logger: ContextVar[BaseLogger] = ContextVar('logger', default=BaseLogger())
127 def set_log_output(fmt: str) -> None:
128 """ Enable collecting debug information.
131 logger.set(HTMLLogger())
133 logger.set(TextLogger())
135 logger.set(BaseLogger())
138 def log() -> BaseLogger:
139 """ Return the logger for the current context.
144 def get_and_disable() -> str:
145 """ Return the current content of the debug buffer and disable logging.
147 buf = logger.get().get_buffer()
148 logger.set(BaseLogger())
152 HTML_HEADER: str = """<!DOCTYPE html>
155 <title>Nominatim - Debug</title>
158 (HtmlFormatter(nobackground=True).get_style_defs('.highlight') if CODE_HIGHLIGHT else '') +\
160 h2 { font-size: x-large }
164 font-family: monospace
173 dt::after { content: ": "; }
186 border: solid lightgrey 0.1pt;
188 background-color: #f7f7f7
193 border: solid lightgrey 0.1pt
200 HTML_FOOTER: str = "</body></html>"