[docs] add ARCHITECTURE.md and various other things

pull/857/head
Timothy Stack 3 years ago
parent fd40b55e0a
commit f5e88b7158

@ -0,0 +1,10 @@
name: Check Markdown links
on: push
jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@v1

@ -0,0 +1,80 @@
# Architecture
This document covers the internal architecture of the Logfile Navigator (lnav),
a terminal-based tool for viewing and analyzing log files.
## Goals
The following goals drive the design and implementation of lnav:
- Don't make the user do something that can be done automatically.
Example: Automatically detect log formats for files instead of making them
specify the format for each file.
- Be performant on low-spec hardware.
Example: Prefer single-threaded optimizations over trying to parallelize
- Operations should be "live" and not block the user from continuing to work.
Example: Searches are run in the background.
- Provide context-sensitive help.
Example: When the cursor is over a SQL keyword/function, the help text for
that is shown above.
- Show a preview of operations so the user knows what is going to happen.
Example: When entering a `:filter-out` command, the matched parts of the
lines are highlighted in red.
## Overview
The whole of lnav consists of a
[log file parser](https://lnav.readthedocs.io/en/latest/formats.html),
[text UI](https://lnav.readthedocs.io/en/latest/ui.html),
[integrations with SQLite](https://lnav.readthedocs.io/en/latest/sqlext.html),
[command-line interface](https://lnav.readthedocs.io/en/latest/cli.html),
and
[commands for operating on logs](https://lnav.readthedocs.io/en/latest/commands.html).
Since the majority of lnav's operations center around logs, the core
data-structure is the combined log message index. The message index
is populated when new messages are read from log files. The text UI
displays a subset of messages from the index. The SQLite virtual-tables
allow for programmatic access to the messages and lnav's internal state.
[![lnav architecture](docs/lnav-architecture.png)](https://whimsical.com/lnav-architecture-UM594Qo4G3nt2XWaSZA1mh)
## File Monitoring
Each file being monitored by lnav has an associated [`logfile`](src/logfile.hh)
object, be they plaintext files or files with a recognized format. These
objects are periodically polled by the main event loop to check if the file
was deleted, truncated, or new lines added. While reading new lines, if no
log format has matched yet, each line will be passed through the log format
regular expressions to try and find a match. Each line that is read is added
to an index
### Why is `mmap()` not used?
Note that file contents are consumed using `pread(2)`/`read(2)` and not
`mmap(2)` since `mmap(2)` does not react well to files changing out from
underneath it. For example, a truncated file would likely result in a
`SIGBUS`.
## Log Messages
As files are being indexed, if a matching format is found, the file is
"promoted" from a plaintext file to a log file. When the file is promoted,
it is added to the [logfile_sub_source](src/logfile_sub_source.hh), which
collates all log messages together into a single index.
## User Interface
[![lnav TUI](docs/lnav-tui.png)](https://whimsical.com/lnav-tui-MQjXc7Vx23BxQTHrnuNp5F)
The lnav text-user-interface is built on top of the basic drawing functionality
of [ncurses](https://invisible-island.net/ncurses/announce.html). However,

@ -32,6 +32,7 @@ lnav v0.9.1:
"lnav_views" SQLite table.
* Themes can now include definitions for text highlights under:
/ui/theme-defs/<theme_name>/highlights
* Added a "grayscale" theme that isn't so colorful.
Interface Changes:
* When copying log lines, the file name and time offset will be included

@ -1,4 +1,4 @@
![Build](https://github.com/tstack/lnav/workflows/ci-build/badge.svg)
[![Build](https://github.com/tstack/lnav/workflows/ci-build/badge.svg)](https://github.com/tstack/lnav/actions?query=workflow%3Aci-build)
[![Coverage Status](https://coveralls.io/repos/github/tstack/lnav/badge.svg?branch=master)](https://coveralls.io/github/tstack/lnav?branch=master)
[![lnav](https://snapcraft.io//lnav/badge.svg)](https://snapcraft.io/lnav)
@ -15,6 +15,7 @@ no setup.
- [Main Site](https://lnav.org)
- [**Documentation**](https://lnav.readthedocs.io) on Read the Docs
- [Architecture](ARCHITECTURE.md)
## Contributing

@ -1,6 +1,6 @@
# aminclude_static.am generated automatically by Autoconf
# from AX_AM_MACROS_STATIC on Tue Feb 2 19:06:51 PST 2021
# from AX_AM_MACROS_STATIC on Tue Feb 9 14:05:27 PST 2021
# Code coverage

@ -0,0 +1,4 @@
# Docs
This directory contains the ReST documentation that is published to
[lnav.readthedocs.io](https://lnav.readthedocs.io)

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

@ -0,0 +1,26 @@
__all__ = ['LnavCommandLexer']
import re
from pygments.token import Whitespace, Text, Keyword, Literal
from pygments.lexers._mapping import LEXERS
from pygments.lexers.python import RegexLexer
class LnavCommandLexer(RegexLexer):
name = 'lnav'
flags = re.IGNORECASE
tokens = {
'root': [
(r'\s+', Whitespace),
(r':[\w\-]+', Keyword),
(r'\<[\w\-]+\>', Literal.String.Doc),
(r'.', Text),
]
}
def setup(app):
LEXERS['LnavCommandLexer'] = (
'_ext.lnavlexer', 'lnav', ('lnav',), ('*.lnav',), ('text/lnav',))
app.add_lexer('lnav', LnavCommandLexer)

@ -20,3 +20,37 @@ th p {
table.query-results p {
font-size: 0.9em !important;
}
DL DT:target, :target > H2, :target > H3, span:target + H2, span:target + H3 {
border: 0 !important;
border-bottom: 1px solid #d3d381 !important;
background: #ffc !important;
/* padding: 1em !important;*/
}
DL DT {
border: 0 !important;
background: inherit !important;
border-bottom: 1px solid #c3c0ee !important;
display: block !important;
}
kbd {
padding: 0.1em 0.6em;
border: 1px solid #ccc;
font-size: 11px;
font-family: Arial, Helvetica, sans-serif;
background-color: #f7f7f7;
color: #333;
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset;
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset;
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 0 0 2px #ffffff inset;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
display: inline-block;
margin: 0 0.1em 0.1em 0.1em;
text-shadow: 0 1px 0 #fff;
line-height: 1.4;
white-space: nowrap;
}

@ -6,33 +6,92 @@ Command Line Interface
The following options can be used when starting **lnav**. There are not
many flags because the majority of the functionality is accessed using
the :code:`-c` option to execute :ref:`commands<commands>` or
the :option:`-c` option to execute :ref:`commands<commands>` or
:ref:`SQL queries<sql-ext>`.
-h Print these command-line options and exit.
-H Start lnav and switch to the help view.
-C Check the given files against the configuration, report any errors, and
exit. This option can be helpful for validating that a log format is
well-formed.
-c cmd Execute the given lnav command, SQL query, or lnav script. The
Options
-------
.. option:: -h
Print these command-line options and exit.
.. option:: -H
Start lnav and switch to the help view.
.. option:: -C
Check the given files against the configuration, report any errors, and
exit. This option can be helpful for validating that a log format is
well-formed.
.. option:: -c <command>
Execute the given lnav command, SQL query, or lnav script. The
argument must be prefixed with the character used to enter the prompt
to distinguish between the different types (i.e. ':', ';', '|').
This option can be given multiple times.
-f path Execute the given command file. This option can be given multiple times.
-I path Add a configuration directory.
-i Install the format files in the :file:`.lnav/formats/` directory.
Individual files will be installed in the :file:`installed`
directory and git repositories will be cloned with a directory
name based on their repository URI.
-u Update formats installed from git repositories.
-d path Write debug messages to the given file.
-n Run without the curses UI (headless mode).
-r Recursively load files from the given base directories.
-t Prepend timestamps to the lines of data being read in on the standard
input.
-w path Write the contents of the standard input to this file.
-V Print the version of lnav.
-q Do not print the log messages after executing all of the commands.
.. option:: -f <path>
Execute the given command file. This option can be given multiple times.
.. option:: -I <path>
Add a configuration directory.
.. option:: -i
Install the format files in the :file:`.lnav/formats/` directory.
Individual files will be installed in the :file:`installed`
directory and git repositories will be cloned with a directory
name based on their repository URI.
.. option:: -u
Update formats installed from git repositories.
.. option:: -d <path>
Write debug messages to the given file.
.. option:: -n
Run without the curses UI (headless mode).
.. option:: -r
Recursively load files from the given base directories.
.. option:: -t
Prepend timestamps to the lines of data being read in on the standard input.
.. option:: -w <path>
Write the contents of the standard input to this file.
.. option:: -V
Print the version of lnav.
.. option:: -q
Do not print the log messages after executing all of the commands.
Environment Variables
---------------------
.. envvar:: XDG_CONFIG_HOME
If this variable is set,
.. envvar:: HOME
Examples
--------

@ -1,5 +1,7 @@
.. include:: kbd.rst
.. role:: lnavcmd(code)
:language: lnav
:class: highlight
.. _commands:
@ -7,13 +9,14 @@ Commands
========
Commands provide access to some of the more advanced features in **lnav**, like
filtering and "search tables". You can activate the command prompt by
pressing the |ks| : |ke| key. At the prompt, you can start typing in the
desired command and/or double-tap |ks| TAB |ke| to activate auto-completion
and show the available commands. To guide you in the usage of the commands,
a help window will appear above the command prompt with an explanation of the
command and its parameters (if it has any). For example, the screenshot below
shows the help for the :code:`:open` command:
:ref:`filtering<filtering>` and
:ref:`"search tables"<search_tables>`. You can activate the command
prompt by pressing the :kbd:`:` key. At the prompt, you can start typing
in the desired command and/or double-tap :kbd:`TAB` to activate
auto-completion and show the available commands. To guide you in the usage of
the commands, a help window will appear above the command prompt with an
explanation of the command and its parameters (if it has any). For example,
the screenshot below shows the help for the :code:`:open` command:
.. figure:: open-help.png
:align: center
@ -22,7 +25,7 @@ shows the help for the :code:`:open` command:
In addition to online help, many commands provide a preview of the effects that
the command will have. This preview will activate shortly after you have
finished typing, but before you have pressed |ks| Enter |ke| to execute the
finished typing, but before you have pressed :kbd:`Enter` to execute the
command. For example, the :code:`:open` command will show a preview of the
first few lines of the file given as its argument:
@ -31,7 +34,7 @@ first few lines of the file given as its argument:
Screenshot of the preview shown for the :code:`:open` command.
The :code:`:filter-out` command is another instance where the preview behavior
The :lnavcmd:`:filter-out pattern` command is another instance where the preview behavior
can help you craft the correct command-line. This command takes a PCRE regular
expression that specifies the log messages that should be filtered out of the
view. The preview for this command will highlight the portion of the log
@ -58,7 +61,7 @@ an error message in the status bar, like so:
Note that almost all commands support TAB-completion for their arguments.
So, if you are in doubt as to what to type for an argument, you can double-
tap the |ks| TAB |ke| key to get suggestions. For example, the
tap the :kbd:`TAB` key to get suggestions. For example, the
TAB-completion for the :code:`filter-in` command will suggest words that are
currently displayed in the view.

@ -18,6 +18,7 @@ import sys, os
# documentation root, use os.path.abspath to make it absolute, like shown here.
this_dir = os.path.abspath('.')
src_dir = os.path.join(this_dir, "..", "..", "src")
sys.path.insert(0, this_dir)
sys.path.insert(0, src_dir)
import format2csv
@ -216,21 +217,6 @@ class CustSqliteLexer(RegexLexer):
lexers['custsqlite'] = CustSqliteLexer(startinline=True)
class LnavCommandLexer(RegexLexer):
name = 'lnav'
flags = re.IGNORECASE
tokens = {
'root': [
(r'\s+', Whitespace),
(r':[\w\-]+', Keyword),
(r'\<[\w\-]+\>', Literal.String.Doc),
(r'.', Text),
]
}
lexers['lnav'] = LnavCommandLexer()
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@ -243,7 +229,9 @@ extensions = [
"sphinx_rtd_theme",
'sphinx-jsonschema',
'sphinx-prompt',
'_ext.lnavlexer',
]
import sphinx_rtd_theme
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@ -259,7 +247,7 @@ master_doc = 'index'
# General information about the project.
project = u'lnav'
copyright = u'2020, Tim Stack'
copyright = u'2021, Tim Stack'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

@ -14,7 +14,7 @@ The configuration for **lnav** is stored in the following JSON files in
Removing the :file:`.sample` extension and editing the file will allow you to
do basic customizations.
* :file:`configs/installed/*.json` -- Contains configuration files installed
using the :code:`-i` flag (e.g. :code:`$ lnav -i /path/to/config.json`).
using the :option:`-i` flag (e.g. :code:`$ lnav -i /path/to/config.json`).
* :file:`configs/*/*.json` -- Other directories that contain :file:`*.json`
files will be loaded on startup. This structure is convenient for installing
**lnav** configurations, like from a git repository.

@ -1,6 +1,4 @@
.. include:: kbd.rst
.. _Cookbook:
Cookbook
@ -11,6 +9,16 @@ These recipes can be used as a starting point for your own needs after some
adaptation.
Log Formats
-----------
TBD
Defining a New Format
^^^^^^^^^^^^^^^^^^^^^
TBD
Log Analysis
------------
@ -19,11 +27,26 @@ following examples should give you some ideas to start leveraging this
functionality. One thing to keep in mind is that if a query gets to be too
large or multiple statements need to be executed, you can create a
:code:`.lnav` script that contains the statements and execute it using the
|ks| | |ke| command prompt.
:kbd:`\|` command prompt.
Count client IPs in web access logs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* To count the number of times a client IP shows up in the loaded web access
logs:
To count the occurrences of an IP in web access logs and order the results
from highest to lowest:
.. code-block:: custsqlite
;SELECT c_ip, count(*) as hits FROM access_log GROUP BY c_ip ORDER BY hits DESC
Show only lines where a numeric field is in a range
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :ref:`:filter-expr<filter_expr>` command can be used to filter web access
logs to only show lines where the number of bytes transferred to the client is
between 10,000 and 40,000 bytes like so:
.. code-block:: custsqlite
:filter-expr :sc_bytes BETWEEN 10000 AND 40000

@ -0,0 +1,2 @@
[restructuredtext parser]
syntax_highlight = short

@ -0,0 +1,57 @@
.. _faq:
Frequently Asked Questions
==========================
Q: How can I copy & paste without decorations?
----------------------------------------------
:Answer: There are a few ways to do this:
* Use the :ref:`bookmark<hotkeys_bookmarks>` hotkeys to mark lines and then
press :kbd:`c` to copy to the local system keyboard.
* Press :kbd:`CTRL` + :kbd:`l` to temporarily switch to "lo-fi"
mode where the contents of the current view are printed to the terminal.
This option is useful when you are logged into a remote host.
Q: How can I force a format for a file?
---------------------------------------
:Answer: The log format for a file is automatically detected and cannot be
forced.
:Solution: Add some of the log file lines to the :ref:`sample<format_sample>`
array and then startup lnav to get a detailed explanation of where the format
patterns are not matching the sample lines.
:Details: The first lines of the file are matched against the
:ref:`regular expressions defined in the format definitions<format_regex>`.
The order of the formats is automatically determined so that more specific
formats are tried before more generic ones. Therefore, if the expected
format is not being chosen for a file, then it means the regular expressions
defined by that format are not matching the first few lines of the file.
See :ref:`format_order` for more information.
Q: Why isn't my log file highlighted correctly?
-----------------------------------------------
TBD
Q: Why isn't a file being displayed?
------------------------------------
:Answer: Plaintext files are displayed separately from log files in the TEXT
view.
:Solution: Press the :kbd:`t` key to switch to the text view. Or, open the
files configuration panel by pressing :kbd:`TAB` to cycle through the
panels, and then press :kbd:`/` to search for the file you're interested in.
If the file is a log, a new :ref:`log format<log_formats>` will need to be
created or an existing one modified.
:Details: If a file being monitored by lnav does not match a known log file
format, it is treated as plaintext and will be displayed in the TEXT view.

@ -62,6 +62,8 @@ fields:
can be used to limit which files a format is applied to in case there is
a potential for conflicts.
.. _format_regex:
:regex: This object contains sub-objects that describe the message formats
to match in a plain log file. Log files that contain JSON messages should
not specify this field.
@ -218,6 +220,8 @@ fields:
SELECT message FROM http_status_codes
WHERE status = :sc_status) || ') '
.. _format_sample:
:sample: A list of objects that contain sample log messages. All formats
must include at least one sample and it must be matched by one of the
included regexes. Each object must contain the following field:
@ -377,6 +381,7 @@ Executing the format file should then install it automatically:
$ ./myformat.json
info: installed: /home/example/.lnav/formats/installed/myformat_log.json
.. _format_order:
Format Order When Scanning a File
---------------------------------

@ -1,6 +1,4 @@
.. include:: kbd.rst
.. _hotkeys:
Hotkey Reference
@ -21,86 +19,86 @@ Spatial Navigation
-
-
- Command
* - |ks| Space |ke|
- |ks| PgDn |ke|
* - :kbd:`Space`
- :kbd:`PgDn`
-
- Down a page
* - |ks| b |ke|
- |ks| Backspace |ke|
- |ks| PgUp |ke|
* - :kbd:`b`
- :kbd:`Backspace`
- :kbd:`PgUp`
- Up a page
* - |ks| j |ke|
- |ks| Return |ke|
- |ks| ↓ |ke|
* - :kbd:`j`
- :kbd:`Return`
- :kbd:`↓`
- Down a line
* - |ks| k |ke|
- |ks| ↑ |ke|
* - :kbd:`k`
- :kbd:`↑`
-
- Up a line
* - |ks| h |ke|
- |ks| ← |ke|
* - :kbd:`h`
- :kbd:`←`
-
- Left half a page. In the log view, pressing left while at the start of
the message text will reveal the source file name for each line.
Pressing again will reveal the full path.
* - |ks| Shift |ke| + |ks| h |ke|
- |ks| Shift |ke| + |ks| ← |ke|
* - :kbd:`Shift` + :kbd:`h`
- :kbd:`Shift` + :kbd:`←`
-
- Left ten columns
* - |ks| l |ke|
- |ks| → |ke|
* - :kbd:`l`
- :kbd:`→`
-
- Right half a page
* - |ks| Shift |ke| + |ks| l |ke|
- |ks| Shift |ke| + |ks| → |ke|
* - :kbd:`Shift` + :kbd:`l`
- :kbd:`Shift` + :kbd:`→`
-
- Right ten columns
* - |ks| Home |ke|
- |ks| g |ke|
* - :kbd:`Home`
- :kbd:`g`
-
- Top of the view
* - |ks| End |ke|
- |ks| G |ke|
* - :kbd:`End`
- :kbd:`G`
-
- Bottom of the view
* - |ks| e |ke|
- |ks| Shift |ke| + |ks| e |ke|
* - :kbd:`e`
- :kbd:`Shift` + :kbd:`e`
-
- Next/previous error
* - |ks| w |ke|
- |ks| Shift |ke| + |ks| w |ke|
* - :kbd:`w`
- :kbd:`Shift` + :kbd:`w`
-
- Next/previous warning
* - |ks| n |ke|
- |ks| Shift |ke| + |ks| n |ke|
* - :kbd:`n`
- :kbd:`Shift` + :kbd:`n`
-
- Next/previous search hit
* - |ks| > |ke|
- |ks| < |ke|
* - :kbd:`>`
- :kbd:`<`
-
- Next/previous search hit (horizontal)
* - |ks| f |ke|
- |ks| Shift |ke| + |ks| f |ke|
* - :kbd:`f`
- :kbd:`Shift` + :kbd:`f`
-
- Next/previous file
* - |ks| u |ke|
- |ks| Shift |ke| + |ks| u |ke|
* - :kbd:`u`
- :kbd:`Shift` + :kbd:`u`
-
- Next/previous bookmark
* - |ks| o |ke|
- |ks| Shift |ke| + |ks| o |ke|
* - :kbd:`o`
- :kbd:`Shift` + :kbd:`o`
-
- Forward/backward through log messages with a matching "opid" field
* - |ks| y |ke|
- |ks| Shift |ke| + |ks| y |ke|
* - :kbd:`y`
- :kbd:`Shift` + :kbd:`y`
-
- Next/prevous SQL result
* - |ks| s |ke|
- |ks| Shift |ke| + |ks| s |ke|
- Next/previous SQL result
* - :kbd:`s`
- :kbd:`Shift` + :kbd:`s`
-
- Next/prevous slow down in the log message rate
* - |ks| { |ke|
- |ks| } |ke|
- Next/previous slow down in the log message rate
* - :kbd:`{`
- :kbd:`}`
-
- Previous/next location in history
@ -114,22 +112,25 @@ Chronological Navigation
* - Keypress
-
- Command
* - |ks| d |ke|
- |ks| Shift |ke| + |ks| d |ke|
* - :kbd:`d`
- :kbd:`Shift` + :kbd:`d`
- Forward/backward 24 hours
* - |ks| 1 |ke| - |ks| 6 |ke|
- |ks| Shift |ke| + |ks| 1 |ke| - |ks| 6 |ke|
* - :kbd:`1` - :kbd:`6`
- :kbd:`Shift` + :kbd:`1` - :kbd:`6`
- Next/previous n'th ten minute of the hour
* - |ks| 7 |ke|
- |ks| 8 |ke|
* - :kbd:`7`
- :kbd:`8`
- Previous/next minute
* - |ks| 0 |ke|
- |ks| Shift |ke| + |ks| 0 |ke|
* - :kbd:`0`
- :kbd:`Shift` + :kbd:`0`
- Next/previous day
* - |ks| r |ke|
- |ks| Shift |ke| + |ks| r |ke|
* - :kbd:`r`
- :kbd:`Shift` + :kbd:`r`
- Forward/backward by the relative time that was last used with the goto command.
.. _hotkeys_bookmarks:
Bookmarks
---------
@ -139,17 +140,17 @@ Bookmarks
* - Keypress
- Command
* - |ks| m |ke|
* - :kbd:`m`
- Mark/unmark the top line
* - |ks| Shift |ke| + |ks| m |ke|
* - :kbd:`Shift` + :kbd:`m`
- Mark/unmark the range of lines from the last marked to the top
* - |ks| Shift |ke| + |ks| j |ke|
* - :kbd:`Shift` + :kbd:`j`
- Mark/unmark the next line after the previously marked
* - |ks| Shift |ke| + |ks| k |ke|
* - :kbd:`Shift` + :kbd:`k`
- Mark/unmark the previous line
* - |ks| c |ke|
* - :kbd:`c`
- Copy marked lines to the clipboard
* - |ks| Shift |ke| + |ks| c |ke|
* - :kbd:`Shift` + :kbd:`c`
- Clear marked lines
Display
@ -161,51 +162,51 @@ Display
* - Keypress
- Command
* - |ks| ? |ke|
* - :kbd:`?`
- View/leave builtin help
* - |ks| q |ke|
* - :kbd:`q`
- Return to the previous view/quit
* - |ks| Shift |ke| + |ks| q |ke|
* - :kbd:`Shift` + :kbd:`q`
- Return to the previous view/quit while matching the top times of the two views
* - |ks| a |ke|
* - :kbd:`a`
- Restore the view that was previously popped with 'q/Q'
* - |ks| Shift |ke| + |ks| a |ke|
* - :kbd:`Shift` + :kbd:`a`
- Restore the view that was previously popped with 'q/Q' and match the top times of the views
* - |ks| Shift |ke| + |ks| p |ke|
* - :kbd:`Shift` + :kbd:`p`
- Switch to/from the pretty-printed view of the displayed log or text files
* - |ks| Shift |ke| + |ks| t |ke|
* - :kbd:`Shift` + :kbd:`t`
- Display elapsed time between lines
* - |ks| t |ke|
* - :kbd:`t`
- Switch to/from the text file view
* - |ks| i |ke|
* - :kbd:`i`
- Switch to/from the histogram view
* - |ks| Shift |ke| + |ks| i |ke|
* - :kbd:`Shift` + :kbd:`i`
- Switch to/from the histogram view
* - |ks| v |ke|
* - :kbd:`v`
- Switch to/from the SQL result view
* - |ks| Shift |ke| + |ks| v |ke|
* - :kbd:`Shift` + :kbd:`v`
- Switch to/from the SQL result view and move to the corresponding in the
log_line column
* - |ks| p |ke|
* - :kbd:`p`
- Toggle the display of the log parser results
* - |ks| Tab |ke|
* - :kbd:`Tab`
- In the log/text views, focus on the configuration panel for editing
filters and examining the list of loaded files. In the SQL result view,
cycle through columns to display as bar graphs
* - |ks| Ctrl |ke| + |ks| l |ke|
* - :kbd:`Ctrl` + :kbd:`l`
- Switch to lo-fi mode. The displayed log lines will be dumped to the
terminal without any decorations so they can be copied easily.
* - |ks| Ctrl |ke| + |ks| w |ke|
* - :kbd:`Ctrl` + :kbd:`w`
- Toggle word-wrap.
* - |ks| Ctrl |ke| + |ks| p |ke|
* - :kbd:`Ctrl` + :kbd:`p`
- Show/hide the data preview panel that may be opened when entering
commands or SQL queries.
* - |ks| Ctrl |ke| + |ks| f |ke|
* - :kbd:`Ctrl` + :kbd:`f`
- Toggle the enabled/disabled state of all filters in the current view.
* - |ks| x |ke|
* - :kbd:`x`
- Toggle the hiding of log message fields. The hidden fields will be
replaced with three bullets and highlighted in yellow.
* - |ks| = |ke|
* - :kbd:`=`
- Pause/unpause loading of new file data.
Session
@ -217,11 +218,11 @@ Session
* - Keypress
- Command
* - |ks| Ctrl |ke| + |ks| R |ke|
* - :kbd:`Ctrl` + :kbd:`R`
- Reset current session.
Query
-----
Query Prompts
-------------
.. list-table::
:header-rows: 1
@ -229,15 +230,15 @@ Query
* - Keypress
- Command
* - |ks| / |ke|
* - :kbd:`/`
- Search for lines matching a regular expression
* - |ks| ; |ke|
- Execute an SQL query
* - |ks| : |ke|
* - :kbd:`;`
- Open the :ref:`sql-ext` to execute SQL statements/queries
* - :kbd:`:`
- Execute an internal command, see :ref:`commands` for more information
* - |ks| \| |ke|
- Execute an lnav script located in a format directory.
* - |ks| Ctrl |ke| + |ks| ] |ke|
* - :kbd:`\|`
- Execute an lnav script located in a format directory
* - :kbd:`Ctrl` + :kbd:`]`
- Abort the prompt
Customizing

@ -18,16 +18,17 @@ Contents:
usage
cookbook
howitworks
config
cli
ui
hotkeys
config
formats
sessions
commands
sqlext
sqltab
data
faq
Indices and tables

@ -1,6 +1,4 @@
.. include:: kbd.rst
Introduction
============
@ -27,13 +25,14 @@ When compiling from source, the following dependencies are required:
* `Bzip2 <http://www.bzip.org>`_
* `Readline <http://www.gnu.org/s/readline>`_
* `libcurl <https://curl.haxx.se>`_
* `libarchive <https://libarchive.org>`_
Installation
------------
Check the `downloads page <http://lnav.org/downloads>`_ to see if there are
packages for your operating system. Compiling from source is just a matter of
doing:
packages for your operating system. To compile from source, use the following
commands:
.. prompt:: bash
@ -70,7 +69,7 @@ Setup
After starting **lnav**, you might want to set the
:ref:`configuration options<Configuration>` mentioned below. Configuration in
**lnav** is done using the :code:`:config` command. To change a configuration
option, start by pressing |ks|:|ke| to enter the command prompt. Then,
option, start by pressing :kbd:`:` to enter the command prompt. Then,
type "config" followed by the option name and value.
.. note::
@ -113,6 +112,7 @@ command can be used to the change the theme:
The builtin themes are:
`default <https://github.com/tstack/lnav/blob/master/src/themes/default-theme.json>`_,
`eldar <https://github.com/tstack/lnav/blob/master/src/themes/eldar.json>`_,
`grayscale <https://github.com/tstack/lnav/blob/master/src/themes/grayscale.json>`_,
`monocai <https://github.com/tstack/lnav/blob/master/src/themes/monocai.json>`_,
`night-owl <https://github.com/tstack/lnav/blob/master/src/themes/night-owl.json>`_,
`solarized-dark <https://github.com/tstack/lnav/blob/master/src/themes/solarized-dark.json>`_,

@ -1,32 +0,0 @@
.. |ks| raw:: html
<kbd>
.. |ke| raw:: html
</kbd>
.. raw:: html
<style>
kbd {
padding: 0.1em 0.6em;
border: 1px solid #ccc;
font-size: 11px;
font-family: Arial,Helvetica,sans-serif;
background-color: #f7f7f7;
color: #333;
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset;
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset;
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2),0 0 0 2px #ffffff inset;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
display: inline-block;
margin: 0 0.1em 0.1em 0.1em;
text-shadow: 0 1px 0 #fff;
line-height: 1.4;
white-space: nowrap;
}
</style>

@ -1,6 +1,4 @@
.. include:: kbd.rst
.. _sql-ext:
SQLite Interface
@ -27,9 +25,9 @@ These columns would be available for its row in the :code:`access_log` table:
results, but they can still be accessed when explicitly used. The hidden
columns are: :code:`log_path`, :code:`log_text`, and :code:`log_body`.
You can activate the SQL prompt by pressing the |ks| ; |ke| key. At the
You can activate the SQL prompt by pressing the :kbd:`;` key. At the
prompt, you can start typing in the desired SQL statement and/or double-tap
|ks| TAB |ke| to activate auto-completion. A help window will appear above
:kbd:`TAB` to activate auto-completion. A help window will appear above
the prompt to guide you in the usage of SQL keywords and functions.
.. figure:: sql-help.png
@ -49,12 +47,12 @@ and maximum number of bytes returned by the server, grouped by IP address:
;SELECT c_ip, avg(sc_bytes), max(sc_bytes) FROM access_log GROUP BY c_ip
After pressing |ks| Enter |ke|, SQLite will execute the query using **lnav**'s
After pressing :kbd:`Enter`, SQLite will execute the query using **lnav**'s
virtual table implementation to extract the data directly from the log files.
Once the query has finished, the main window will switch to the DB view to
show the results. Press |ks| q |ke| to return to the log view and press |ks|
v |ke| to return to the log view. If the SQL results contain a
:code:`log_line` column, you can press to |ks| Shift |ke| + |ks| V |ke| to
show the results. Press :kbd:`q` to return to the log view and press :kbd:`v`
to return to the log view. If the SQL results contain a
:code:`log_line` column, you can press to :kbd:`Shift` + :kbd:`V` to
switch between the DB view and the log
.. figure:: query-results.png
@ -66,9 +64,9 @@ The DB view has the following display features:
* Column headers stick to the top of the view when scrolling.
* A stacked bar chart of the numeric column values is displayed underneath the
rows. Pressing |ks| TAB |ke| will cycle through displaying no columns, each
rows. Pressing :kbd:`TAB` will cycle through displaying no columns, each
individual column, or all columns.
* JSON columns in the top row can be pretty-printed by pressing |ks| p |ke|.
* JSON columns in the top row can be pretty-printed by pressing :kbd:`p`.
The display will show the value and JSON-Pointer path that can be passed to
the `jget`_ function.
@ -144,7 +142,7 @@ the current IP is different from the previous IP:
Since the above can be a lot to type out interactively, you can put these
commands into a :ref:`script<scripts>` and execute that script with the
|ks| \| |ke| hotkey.
:kbd:`\|` hotkey.
.. [#] The expression :code:`regexp_match('bound to ([^ ]+)', log_body) as ip`
can be used to extract the IP address from the log message body.

@ -1,4 +1,3 @@
.. include:: kbd.rst
.. _usage:
@ -13,8 +12,8 @@ Basic Controls
Like most file viewers, scrolling through files can be done with the usual
:ref:`hotkeys<hotkeys>`. For non-trivial operations, you can enter the
:ref:`command<commands>` prompt by pressing |ks| : |ke|. To analyze data in a
log file, you can enter the :ref:`SQL prompt<sql-ext>` by pressing |ks| ; |ke|.
:ref:`command<commands>` prompt by pressing :kbd:`:`. To analyze data in a
log file, you can enter the :ref:`SQL prompt<sql-ext>` by pressing :kbd:`;`.
.. tip::
@ -24,8 +23,8 @@ log file, you can enter the :ref:`SQL prompt<sql-ext>` by pressing |ks| ; |ke|.
.. figure:: hotkey-tips.png
:align: center
When **lnav** is first open, it suggests using |ks| e |ke| and
|ks| Shift |ke| + |ks| e |ke| to jump to error messages.
When **lnav** is first open, it suggests using :kbd:`e` and
:kbd:`Shift` + :kbd:`e` to jump to error messages.
Viewing Files
@ -42,7 +41,7 @@ extracted to a temporary location and the files within will be loaded. The
files that are found will be scanned to identify their file format. Files
that match a log format will be collated by time and displayed in the LOG
view. Plain text files can be viewed in the TEXT view, which can be accessed
by pressing |ks| t |ke|.
by pressing :kbd:`t`.
Archive Support
@ -71,13 +70,15 @@ Searching
Any log messages that are loaded into **lnav** are indexed by time and log
level (e.g. error, warning) to make searching quick and easy with
:ref:`hotkeys<hotkeys>`. For example, pressing |ks| e |ke| will jump to the
next error in the file and pressing |ks| Shift |ke| + |ks| e |ke| will jump to
the previous error. Plain text searches can be done by pressing |ks| / |ke|
:ref:`hotkeys<hotkeys>`. For example, pressing :kbd:`e` will jump to the
next error in the file and pressing :kbd:`Shift` + :kbd:`e` will jump to
the previous error. Plain text searches can be done by pressing :kbd:`/`
to enter the search prompt. A regular expression can be entered into the
prompt to start a search through the current view.
.. _filtering:
Filtering
---------
@ -126,6 +127,8 @@ Log level
To hide messages below a certain log level, you can use the
:ref:`:set-min-log-level<set_min_log_level>`.
.. _search_tables:
Search Tables
-------------

@ -0,0 +1,4 @@
# Release
This directory contains the [Makefile](Makefile) and scripts used to build the
binaries for a release.

@ -171,6 +171,7 @@ set(CONFIG_FILES
keymaps/uk-keymap.json
keymaps/us-keymap.json
themes/default-theme.json
themes/grayscale.json
themes/eldar.json
themes/monocai.json
themes/night-owl.json

@ -21,80 +21,28 @@ RE2C_V = $(RE2C_V_@AM_V@)
RE2C_V_ = $(RE2C_V_@AM_DEFAULT_V@)
RE2C_V_0 = @echo " RE2C " $@;
FORMAT_FILES = \
$(srcdir)/formats/access_log.json \
$(srcdir)/formats/alb_log.json \
$(srcdir)/formats/autodeploy_log.json \
$(srcdir)/formats/block_log.json \
$(srcdir)/formats/candlepin_log.json \
$(srcdir)/formats/choose_repo_log.json \
$(srcdir)/formats/cups_log.json \
$(srcdir)/formats/dpkg_log.json \
$(srcdir)/formats/elb_log.json \
$(srcdir)/formats/engine_log.json \
$(srcdir)/formats/error_log.json \
$(srcdir)/formats/fsck_hfs_log.json \
$(srcdir)/formats/glog_log.json \
$(srcdir)/formats/haproxy_log.json \
$(srcdir)/formats/java_log.json \
$(srcdir)/formats/journald_json_log.json \
$(srcdir)/formats/katello_log.json \
$(srcdir)/formats/openam_log.json \
$(srcdir)/formats/openamdb_log.json \
$(srcdir)/formats/openstack_log.json \
$(srcdir)/formats/page_log.json \
$(srcdir)/formats/papertrail_log.json \
$(srcdir)/formats/snaplogic_log.json \
$(srcdir)/formats/sssd_log.json \
$(srcdir)/formats/strace_log.json \
$(srcdir)/formats/sudo_log.json \
$(srcdir)/formats/syslog_log.json \
$(srcdir)/formats/s3_log.json \
$(srcdir)/formats/tcf_log.json \
$(srcdir)/formats/tcsh_history.json \
$(srcdir)/formats/uwsgi_log.json \
$(srcdir)/formats/vdsm_log.json \
$(srcdir)/formats/vmk_log.json \
$(srcdir)/formats/vmw_log.json \
$(srcdir)/formats/xmlrpc_log.json \
$()
include formats/formats.am
default-formats.h default-formats.cc: bin2c$(BUILD_EXEEXT) $(FORMAT_FILES)
$(BIN2C_V)./bin2c$(BUILD_EXEEXT) -n lnav_format_json default-formats $(FORMAT_FILES)
include keymaps/keymaps.am
include themes/themes.am
CONFIG_FILES = \
$(srcdir)/root-config.json \
$(srcdir)/keymaps/de-keymap.json \
$(srcdir)/keymaps/default-keymap.json \
$(srcdir)/keymaps/fr-keymap.json \
$(srcdir)/keymaps/uk-keymap.json \
$(srcdir)/keymaps/us-keymap.json \
$(srcdir)/themes/default-theme.json \
$(srcdir)/themes/eldar.json \
$(srcdir)/themes/monocai.json \
$(srcdir)/themes/night-owl.json \
$(srcdir)/themes/solarized-dark.json \
$(srcdir)/themes/solarized-light.json \
$(KEYMAP_FILES) \
$(THEME_FILES) \
$()
default-config.h default-config.cc: bin2c$(BUILD_EXEEXT) $(CONFIG_FILES)
$(BIN2C_V)./bin2c$(BUILD_EXEEXT) -n lnav_config_json default-config $(CONFIG_FILES)
BUILTIN_LNAVSCRIPTS = \
$(srcdir)/scripts/dhclient-summary.lnav \
$(srcdir)/scripts/lnav-pop-view.lnav \
$(srcdir)/scripts/partition-by-boot.lnav \
$(srcdir)/scripts/rename-stdin.lnav \
$(srcdir)/scripts/search-for.lnav \
$()
include scripts/scripts.am
builtin-scripts.h builtin-scripts.cc: bin2c$(BUILD_EXEEXT) $(BUILTIN_LNAVSCRIPTS)
$(BIN2C_V)./bin2c$(BUILD_EXEEXT) -n lnav_scripts builtin-scripts $(BUILTIN_LNAVSCRIPTS)
BUILTIN_SHSCRIPTS = \
$(srcdir)/scripts/dump-pid.sh \
$()
builtin-sh-scripts.h builtin-sh-scripts.cc: bin2c$(BUILD_EXEEXT) $(BUILTIN_SHSCRIPTS)
$(BIN2C_V)./bin2c$(BUILD_EXEEXT) -n lnav_sh_scripts builtin-sh-scripts $(BUILTIN_SHSCRIPTS)

@ -99,15 +99,21 @@ bool is_archive(const fs::path& filename)
archive_read_support_filter_all(arc);
archive_read_support_format_all(arc);
archive_read_support_format_raw(arc);
auto r = archive_read_open_filename(arc, filename.c_str(), 16384);
log_debug("read open %s", filename.c_str());
auto r = archive_read_open_filename(arc, filename.c_str(), 128 * 1024);
if (r == ARCHIVE_OK) {
struct archive_entry *entry;
auto format_name = archive_format_name(arc);
log_debug("read next header %s %s", format_name, filename.c_str());
if (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
log_debug("read next done %s", filename.c_str());
static const auto RAW_FORMAT_NAME = string_fragment("raw");
static const auto GZ_FILTER_NAME = string_fragment("gzip");
auto format_name = archive_format_name(arc);
format_name = archive_format_name(arc);
if (RAW_FORMAT_NAME == format_name) {
auto filter_count = archive_filter_count(arc);

@ -34,7 +34,7 @@
#include "date_time_scanner.hh"
#include "ptimec.hh"
size_t date_time_scanner::ftime(char *dst, size_t len, const exttm &tm)
size_t date_time_scanner::ftime(char *dst, size_t len, const exttm &tm) const
{
off_t off = 0;

@ -120,7 +120,7 @@ struct date_time_scanner {
struct timeval &tv_out,
bool convert_local = true);
size_t ftime(char *dst, size_t len, const struct exttm &tm);;
size_t ftime(char *dst, size_t len, const struct exttm &tm) const;
bool convert_to_timeval(const char *time_src,
ssize_t time_len,

@ -34,10 +34,14 @@
#include "intern_string.hh"
using file_off_t = int64_t;
using file_size_t = uint64_t;
using file_ssize_t = int64_t;
class file_range {
public:
off_t fr_offset{0};
ssize_t fr_size{0};
file_off_t fr_offset{0};
file_ssize_t fr_size{0};
void clear() {
this->fr_offset = 0;

@ -159,9 +159,9 @@ void bottom_status_source::update_hits(textview_curses *tc)
this->update_marks(tc);
}
void bottom_status_source::update_loading(off_t off, size_t total)
void bottom_status_source::update_loading(file_off_t off, file_size_t total)
{
status_field &sf = this->bss_fields[BSF_LOADING];
auto &sf = this->bss_fields[BSF_LOADING];
require(off >= 0);

@ -103,7 +103,7 @@ public:
void update_hits(textview_curses *tc);
void update_loading(off_t off, size_t total);
void update_loading(file_off_t off, file_size_t total);
private:
status_field bss_prompt{1024, view_colors::VCR_STATUS};

@ -283,9 +283,6 @@ void files_sub_source::text_attrs_for_line(textview_curses &tc, int line,
(int) filename_width + 3 + 4,
(int) filename_width + 3 + 10,
};
value_out.emplace_back(lr, &view_curses::VC_FOREGROUND, COLOR_WHITE);
lr.lr_start = lr.lr_end;
lr.lr_end += 2;
value_out.emplace_back(lr, &view_curses::VC_STYLE, A_BOLD);
}

@ -42,7 +42,7 @@ public:
};
void logline_restart(const logfile &lf, size_t rollback_size) {
void logline_restart(const logfile &lf, file_size_t rollback_size) {
for (auto &filter : this->lfo_filter_stack) {
filter->revert_to_last(this->lfo_filter_state, rollback_size);
}

@ -305,7 +305,11 @@ void filter_sub_source::text_value_for_line(textview_curses &tc, int line,
value_out.append(" IN ");
break;
case text_filter::EXCLUDE:
value_out.append("OUT ");
if (tf->get_lang() == filter_lang_t::REGEX) {
value_out.append("OUT ");
} else {
value_out.append(" ");
}
break;
default:
ensure(0);

@ -1,4 +1,5 @@
# Formats
This directory contains the built-in log file format definitions. These files
are converted to C by `bin2c` and compiled into the executable.
are converted to C by `bin2c` and compiled into the executable. New formats
need to be added to the [formats.am](formats.am) file.

@ -0,0 +1,38 @@
FORMAT_FILES = \
$(srcdir)/%reldir%/access_log.json \
$(srcdir)/%reldir%/alb_log.json \
$(srcdir)/%reldir%/autodeploy_log.json \
$(srcdir)/%reldir%/block_log.json \
$(srcdir)/%reldir%/candlepin_log.json \
$(srcdir)/%reldir%/choose_repo_log.json \
$(srcdir)/%reldir%/cups_log.json \
$(srcdir)/%reldir%/dpkg_log.json \
$(srcdir)/%reldir%/elb_log.json \
$(srcdir)/%reldir%/engine_log.json \
$(srcdir)/%reldir%/error_log.json \
$(srcdir)/%reldir%/fsck_hfs_log.json \
$(srcdir)/%reldir%/glog_log.json \
$(srcdir)/%reldir%/haproxy_log.json \
$(srcdir)/%reldir%/java_log.json \
$(srcdir)/%reldir%/journald_json_log.json \
$(srcdir)/%reldir%/katello_log.json \
$(srcdir)/%reldir%/openam_log.json \
$(srcdir)/%reldir%/openamdb_log.json \
$(srcdir)/%reldir%/openstack_log.json \
$(srcdir)/%reldir%/page_log.json \
$(srcdir)/%reldir%/papertrail_log.json \
$(srcdir)/%reldir%/snaplogic_log.json \
$(srcdir)/%reldir%/sssd_log.json \
$(srcdir)/%reldir%/strace_log.json \
$(srcdir)/%reldir%/sudo_log.json \
$(srcdir)/%reldir%/syslog_log.json \
$(srcdir)/%reldir%/s3_log.json \
$(srcdir)/%reldir%/tcf_log.json \
$(srcdir)/%reldir%/tcsh_history.json \
$(srcdir)/%reldir%/uwsgi_log.json \
$(srcdir)/%reldir%/vdsm_log.json \
$(srcdir)/%reldir%/vmk_log.json \
$(srcdir)/%reldir%/vmw_log.json \
$(srcdir)/%reldir%/xmlrpc_log.json \
$()

@ -569,7 +569,7 @@ void format_help_text_for_rst(const help_text &ht,
}
if (param_count > 0) {
fprintf(rst_file, " **Parameters:**\n\n");
fprintf(rst_file, " **Parameters**\n");
for (auto &param: ht.ht_parameters) {
if (param.ht_summary && param.ht_summary[0]) {
fprintf(rst_file, " * **%s%s** --- %s\n",
@ -584,7 +584,7 @@ void format_help_text_for_rst(const help_text &ht,
prefix = ";";
}
if (!ht.ht_example.empty()) {
fprintf(rst_file, " **Examples:**\n\n");
fprintf(rst_file, " **Examples**\n");
for (auto &example: ht.ht_example) {
fprintf(rst_file, " %s:\n\n", example.he_description);
fprintf(rst_file, " .. code-block:: %s\n\n",
@ -616,7 +616,7 @@ void format_help_text_for_rst(const help_text &ht,
}
stable_sort(related_refs.begin(), related_refs.end());
fmt::print(rst_file, " **See Also:**\n\n {}\n",
fmt::print(rst_file, " **See Also**\n {}\n",
fmt::join(related_refs, ", "));
}

@ -44,7 +44,6 @@ highlighter::highlighter(const highlighter &other)
this->h_text_format = other.h_text_format;
this->h_format_name = other.h_format_name;
this->h_nestable = other.h_nestable;
this->h_semantic = other.h_semantic;
}
highlighter &highlighter::operator=(const highlighter &other)
@ -70,7 +69,6 @@ highlighter &highlighter::operator=(const highlighter &other)
this->h_attrs = other.h_attrs;
this->h_text_format = other.h_text_format;
this->h_nestable = other.h_nestable;
this->h_semantic = other.h_semantic;
return *this;
}
@ -138,25 +136,6 @@ void highlighter::annotate(attr_line_t &al, int start) const
if (this->h_attrs != -1) {
attrs = this->h_attrs;
}
if (this->h_semantic) {
bool found_color = false;
if (str[lr.lr_start] == '#' &&
(lr.length() == 4 || lr.length() == 7)) {
auto fg_res = rgb_color::from_str(
string_fragment(str.data(), lr.lr_start, lr.lr_end));
if (fg_res.isOk()) {
sa.emplace_back(lr,
&view_curses::VC_FOREGROUND,
vc.match_color(fg_res.unwrap()));
found_color = true;
}
}
if (!found_color) {
attrs |= vc.attrs_for_ident(&str[lr.lr_start],
lr.length());
}
}
if (!this->h_fg.empty()) {
sa.emplace_back(lr,
&view_curses::VC_FOREGROUND,
@ -172,7 +151,9 @@ void highlighter::annotate(attr_line_t &al, int start) const
&view_curses::VC_ROLE,
this->h_role);
}
sa.emplace_back(lr, &view_curses::VC_STYLE, attrs);
if (attrs) {
sa.emplace_back(lr, &view_curses::VC_STYLE, attrs);
}
off = matches[1];
}

@ -93,7 +93,7 @@ struct highlighter {
return *this;
};
highlighter &with_color(const rgb_color &fg, const rgb_color &bg) {
highlighter &with_color(const styling::color_unit &fg, const styling::color_unit &bg) {
this->h_fg = fg;
this->h_bg = bg;
@ -105,11 +105,6 @@ struct highlighter {
return *this;
}
highlighter &with_semantic(bool val) {
this->h_semantic = val;
return *this;
}
int get_attrs() const
{
ensure(this->h_attrs != -1);
@ -121,15 +116,14 @@ struct highlighter {
std::string h_pattern;
view_colors::role_t h_role{view_colors::VCR_NONE};
rgb_color h_fg;
rgb_color h_bg;
styling::color_unit h_fg{styling::color_unit::make_empty()};
styling::color_unit h_bg{styling::color_unit::make_empty()};
pcre *h_code;
pcre_extra *h_code_extra;
int h_attrs{-1};
text_format_t h_text_format{text_format_t::TF_UNKNOWN};
intern_string_t h_format_name;
bool h_nestable{true};
bool h_semantic{false};
};
#endif

File diff suppressed because it is too large Load Diff

@ -405,11 +405,6 @@
"type": "object",
"$$target": "#/definitions/style",
"properties": {
"semantic": {
"title": "/semantic",
"description": "Pick a color based on the text being highlighted",
"type": "boolean"
},
"color": {
"title": "/color",
"description": "The foreground color value for this style. The value can be the name of an xterm color, the hexadecimal value, or a theme variable reference.",

File diff suppressed because it is too large Load Diff

@ -1,4 +1,5 @@
# Keymaps
This directory contains the built-in keymap definitions. The files are
turned into C using `bin2c` and compiled into the executable.
turned into C using `bin2c` and compiled into the executable. New keymaps
need to be added to the [keymaps.am](keymaps.am) file.

@ -0,0 +1,8 @@
KEYMAP_FILES = \
$(srcdir)/%reldir%/de-keymap.json \
$(srcdir)/%reldir%/default-keymap.json \
$(srcdir)/%reldir%/fr-keymap.json \
$(srcdir)/%reldir%/uk-keymap.json \
$(srcdir)/%reldir%/us-keymap.json \
$()

@ -319,7 +319,7 @@ line_buffer::~line_buffer()
void line_buffer::set_fd(auto_fd &fd)
{
off_t newoff = 0;
file_off_t newoff = 0;
if (this->lb_gz_file) {
this->lb_gz_file.close();
@ -411,14 +411,14 @@ void line_buffer::resize_buffer(size_t new_max)
}
}
void line_buffer::ensure_available(off_t start, ssize_t max_length)
void line_buffer::ensure_available(file_off_t start, ssize_t max_length)
{
ssize_t prefill, available;
require(max_length <= MAX_LINE_BUFFER_SIZE);
if (this->lb_file_size != -1) {
if (start + (off_t)max_length > this->lb_file_size) {
if (start + (file_off_t)max_length > this->lb_file_size) {
max_length = (this->lb_file_size - start);
}
}
@ -428,7 +428,7 @@ void line_buffer::ensure_available(off_t start, ssize_t max_length)
* after.
*/
if (start < this->lb_file_offset ||
start > (off_t)(this->lb_file_offset + this->lb_buffer_size)) {
start > (file_off_t)(this->lb_file_offset + this->lb_buffer_size)) {
/*
* The request is outside the cached range, need to reload the
* whole thing.
@ -444,7 +444,7 @@ void line_buffer::ensure_available(off_t start, ssize_t max_length)
*/
this->lb_file_offset = this->lb_file_size -
std::min(this->lb_file_size,
this->lb_buffer_max);
(file_ssize_t) this->lb_buffer_max);
}
else {
this->lb_file_offset = start;
@ -483,7 +483,7 @@ void line_buffer::ensure_available(off_t start, ssize_t max_length)
}
}
bool line_buffer::fill_range(off_t start, ssize_t max_length)
bool line_buffer::fill_range(file_off_t start, ssize_t max_length)
{
bool retval = false;
@ -530,7 +530,7 @@ bool line_buffer::fill_range(off_t start, ssize_t max_length)
lock_hack::guard guard;
char scratch[32 * 1024];
BZFILE * bz_file;
off_t seek_to;
file_off_t seek_to;
int bzfd;
/*
@ -602,7 +602,7 @@ bool line_buffer::fill_range(off_t start, ssize_t max_length)
if (!this->lb_seekable) {
this->lb_file_size = this->lb_file_offset + this->lb_buffer_size;
}
if (start < (off_t) this->lb_file_size) {
if (start < (file_off_t) this->lb_file_size) {
retval = true;
}
@ -763,7 +763,7 @@ Result<shared_buffer_ref, std::string> line_buffer::read_range(const file_range
{
shared_buffer_ref retval;
char *line_start;
ssize_t avail;
file_ssize_t avail;
if (this->lb_last_line_offset != -1 &&
fr.fr_offset > this->lb_last_line_offset) {

@ -178,7 +178,7 @@ public:
/**
* @return The size of the file or the amount of data pulled from a pipe.
*/
ssize_t get_file_size() const { return this->lb_file_size; };
file_ssize_t get_file_size() const { return this->lb_file_size; };
bool is_pipe() const {
return !this->lb_seekable;
@ -192,7 +192,7 @@ public:
return this->lb_gz_file || this->lb_bz_file;
};
off_t get_read_offset(off_t off) const
file_off_t get_read_offset(file_off_t off) const
{
if (this->is_compressed()) {
return this->lb_compressed_offset;
@ -202,7 +202,7 @@ public:
}
};
bool is_data_available(off_t off, off_t stat_size) const {
bool is_data_available(file_off_t off, file_off_t stat_size) const {
if (this->is_compressed()) {
return (this->lb_file_size == -1 || off < this->lb_file_size);
}
@ -253,10 +253,10 @@ private:
* @param off The file offset to check for in the buffer.
* @return True if the given offset is cached in the buffer.
*/
bool in_range(off_t off) const
bool in_range(file_off_t off) const
{
return this->lb_file_offset <= off &&
off < (int)(this->lb_file_offset + this->lb_buffer_size);
off < (this->lb_file_offset + this->lb_buffer_size);
};
void resize_buffer(size_t new_max);
@ -273,7 +273,7 @@ private:
* @param start The file offset of the start of the line.
* @param max_length The amount of data to be cached in the buffer.
*/
void ensure_available(off_t start, ssize_t max_length);
void ensure_available(file_off_t start, ssize_t max_length);
/**
* Fill the buffer with the given range of data from the file.
@ -283,7 +283,7 @@ private:
* @param max_length The maximum amount of data to read from the file.
* @return True if any data was read from the file.
*/
bool fill_range(off_t start, ssize_t max_length);
bool fill_range(file_off_t start, ssize_t max_length);
/**
* After a successful fill, the cached data can be retrieved with this
@ -295,9 +295,9 @@ private:
* @return A pointer to the start of the cached data in the internal
* buffer.
*/
char *get_range(off_t start, ssize_t &avail_out) const
char *get_range(file_off_t start, file_ssize_t &avail_out) const
{
off_t buffer_offset = start - this->lb_file_offset;
auto buffer_offset = start - this->lb_file_offset;
char *retval;
require(buffer_offset >= 0);
@ -314,16 +314,16 @@ private:
auto_fd lb_fd; /*< The file to read data from. */
gz_indexed lb_gz_file; /*< File reader for gzipped files. */
bool lb_bz_file; /*< Flag set for bzip2 compressed files. */
off_t lb_compressed_offset; /*< The offset into the compressed file. */
file_off_t lb_compressed_offset; /*< The offset into the compressed file. */
auto_mem<char> lb_buffer; /*< The internal buffer where data is cached */
ssize_t lb_file_size; /*<
file_ssize_t lb_file_size; /*<
* The size of the file. When lb_fd refers to
* a pipe, this is set to the amount of data
* read from the pipe when EOF is reached.
*/
off_t lb_file_offset; /*<
file_off_t lb_file_offset; /*<
* Data cached in the buffer comes from this
* offset in the file.
*/
@ -332,6 +332,6 @@ private:
ssize_t lb_buffer_max; /*< The amount of allocated memory for the
* buffer. */
bool lb_seekable; /*< Flag set for seekable file descriptors. */
off_t lb_last_line_offset; /*< */
file_off_t lb_last_line_offset; /*< */
};
#endif

@ -388,7 +388,9 @@ public:
};
void logfile_indexing(const shared_ptr<logfile>& lf, off_t off, size_t total)
void logfile_indexing(const shared_ptr<logfile>& lf,
file_off_t off,
file_size_t total)
{
static sig_atomic_t index_counter = 0;

@ -459,10 +459,6 @@ static struct json_path_container global_var_handlers = {
static struct json_path_container style_config_handlers =
json_path_container{
yajlpp::property_handler("semantic")
.with_description(
"Pick a color based on the text being highlighted")
.FOR_FIELD(style_config, sc_semantic),
yajlpp::property_handler("color")
.with_synopsis("#hex|color_name")
.with_description(

@ -1926,11 +1926,12 @@ void external_log_format::build(std::vector<std::string> &errors) {
external_log_format::highlighter_def &hd = hd_pair.second;
const std::string &pattern = hd.hd_pattern;
const char *errptr;
rgb_color fg, bg;
auto fg = styling::color_unit::make_empty();
auto bg = styling::color_unit::make_empty();
int eoff, attrs = 0;
if (!hd.hd_color.empty()) {
fg = rgb_color::from_str(hd.hd_color)
fg = styling::color_unit::from_str(hd.hd_color)
.unwrapOrElse([&](const auto& msg) {
errors.push_back("error:"
+ this->elf_name.to_string()
@ -1938,12 +1939,12 @@ void external_log_format::build(std::vector<std::string> &errors) {
+ hd_pair.first.to_string()
+ "/color:"
+ msg);
return rgb_color{};
return styling::color_unit::make_empty();
});
}
if (!hd.hd_background_color.empty()) {
bg = rgb_color::from_str(hd.hd_background_color)
bg = styling::color_unit::from_str(hd.hd_background_color)
.unwrapOrElse([&](const auto& msg) {
errors.push_back("error:"
+ this->elf_name.to_string()
@ -1951,7 +1952,7 @@ void external_log_format::build(std::vector<std::string> &errors) {
+ hd_pair.first.to_string()
+ "/color:"
+ msg);
return rgb_color{};
return styling::color_unit::make_empty();
});
}

@ -62,7 +62,7 @@ public:
* @param millis The millisecond timestamp for the line.
* @param l The logging level.
*/
logline(off_t off,
logline(file_off_t off,
time_t t,
uint16_t millis,
log_level_t l,
@ -80,7 +80,7 @@ public:
memset(this->ll_schema, 0, sizeof(this->ll_schema));
};
logline(off_t off,
logline(file_off_t off,
const struct timeval &tv,
log_level_t l,
uint8_t mod = 0,
@ -97,7 +97,7 @@ public:
};
/** @return The offset of the line in the file. */
off_t get_offset() const { return this->ll_offset; };
file_off_t get_offset() const { return this->ll_offset; };
uint16_t get_sub_offset() const { return this->ll_sub_offset; };
@ -283,8 +283,8 @@ public:
(this->ll_millis <= (rhs.tv_usec / 1000))));
};
private:
off_t ll_offset;
time_t ll_time;
file_off_t ll_offset;
time_t ll_time;
unsigned int ll_millis : 10;
unsigned int ll_opid : 6;
unsigned int ll_sub_offset : 15;

@ -506,7 +506,7 @@ static struct json_path_container value_def_handlers = {
yajlpp::property_handler("foreign-key")
.with_synopsis("<bool>")
.with_description("Indicates whether or not this field should be treated as a foreign key for row in another table")
.FOR_FIELD(external_log_format::value_def, vd_foreign_key),
.for_field(&external_log_format::value_def::vd_foreign_key),
yajlpp::property_handler("hidden")
.with_synopsis("<bool>")

@ -123,6 +123,9 @@ bool logfile::exists() const
}
if (::stat(this->lf_filename.c_str(), &st) == -1) {
log_error("%s: stat failed -- %s",
this->lf_filename.c_str(),
strerror(errno));
return false;
}
@ -318,7 +321,7 @@ logfile::rebuild_result_t logfile::rebuild_index()
// line buffer's notion of the file size since it may be compressed.
bool has_format = this->lf_format.get() != nullptr;
struct rusage begin_rusage;
off_t off;
file_off_t off;
size_t begin_size = this->lf_index.size();
bool record_rusage = this->lf_index.size() == 1;
off_t begin_index_size = this->lf_index_size;
@ -350,7 +353,7 @@ logfile::rebuild_result_t logfile::rebuild_index()
if (!this->lf_index.empty()) {
auto last_line = this->lf_index.end();
--last_line;
off_t check_line_off = last_line->get_offset();
auto check_line_off = last_line->get_offset();
auto last_length = ssize_t(this->line_length(last_line, false));
auto read_result = this->lf_line_buffer.read_range({
@ -384,6 +387,9 @@ logfile::rebuild_result_t logfile::rebuild_index()
auto load_result = this->lf_line_buffer.load_next_line(prev_range);
if (load_result.isErr()) {
log_error("%s: load next line failure -- %s",
this->lf_filename.c_str(),
load_result.unwrapErr().c_str());
this->close();
return RR_INVALID;
}
@ -418,6 +424,9 @@ logfile::rebuild_result_t logfile::rebuild_index()
auto read_result = this->lf_line_buffer.read_range(li.li_file_range);
if (read_result.isErr()) {
log_error("%s:read failure -- %s",
this->lf_filename.c_str(),
read_result.unwrapErr().c_str());
this->close();
return RR_INVALID;
}

@ -68,8 +68,8 @@ public:
* @param total The total size of the file.
*/
virtual void logfile_indexing(const std::shared_ptr<logfile>& lf,
off_t off,
size_t total) = 0;
file_off_t off,
file_size_t total) = 0;
};
struct logfile_activity {
@ -154,7 +154,7 @@ public:
return this->lf_valid_filename;
};
off_t get_index_size() const {
file_off_t get_index_size() const {
return this->lf_index_size;
}
@ -302,7 +302,7 @@ public:
file_range get_file_range(const_iterator ll, bool include_continues = true) {
return {ll->get_offset(),
(ssize_t) this->line_length(ll, include_continues)};
(file_ssize_t) this->line_length(ll, include_continues)};
}
void read_full_message(const_iterator ll, shared_buffer_ref &msg_out, int max_lines=50);
@ -392,7 +392,7 @@ protected:
std::shared_ptr<log_format> lf_format;
std::vector<logline> lf_index;
time_t lf_index_time{0};
off_t lf_index_size{0};
file_off_t lf_index_size{0};
bool lf_sort_needed{false};
line_buffer lf_line_buffer;
int lf_time_offset_line{0};
@ -406,14 +406,14 @@ protected:
text_format_t lf_text_format{text_format_t::TF_UNKNOWN};
uint32_t lf_out_of_time_order_count{0};
nonstd::optional<std::pair<off_t, size_t>> lf_next_line_cache;
nonstd::optional<std::pair<file_off_t, size_t>> lf_next_line_cache;
};
class logline_observer {
public:
virtual ~logline_observer() = default;
virtual void logline_restart(const logfile &lf, size_t rollback_size) = 0;
virtual void logline_restart(const logfile &lf, file_size_t rollback_size) = 0;
virtual void logline_new_lines(
const logfile &lf,

@ -395,17 +395,15 @@ void logfile_sub_source::text_attrs_for_line(textview_curses &lv,
continue;
}
auto start = this->lss_token_value.c_str();
int id_attrs = vc.attrs_for_ident(&start[line_value.lv_origin.lr_start],
line_value.lv_origin.sublen(this->lss_token_value));
line_range ident_range = line_value.lv_origin;
if (this->lss_token_flags & RF_FULL) {
ident_range = line_value.origin_in_full_msg(
this->lss_token_value.c_str(), this->lss_token_value.length());
}
value_out.emplace_back(ident_range, &view_curses::VC_STYLE, id_attrs);
value_out.emplace_back(ident_range,
&view_curses::VC_ROLE,
view_colors::VCR_IDENTIFIER);
}
if (this->lss_token_shift_size) {

@ -404,9 +404,9 @@ void readline_command_highlighter(attr_line_t &al, int x)
attr_t color_hint_attrs = vc.attrs_for_role(view_colors::VCR_COLOR_HINT);
int pnum = PAIR_NUMBER(color_hint_attrs);
rgb_color::from_str(hash_color).then([&](const auto& rgb_fg) {
styling::color_unit::from_str(hash_color).then([&](const auto& rgb_fg) {
pnum -= 1;
vc.ensure_color_pair(pnum, rgb_fg, rgb_color{});
vc.ensure_color_pair(pnum, rgb_fg, styling::color_unit::make_empty());
al.get_attrs().emplace_back(
line_range{cap->c_begin, cap->c_begin + 1},

@ -257,7 +257,7 @@ void add_filter_expr_possibilities(readline_curses *rlc, int context, const std:
lf->read_full_message(ll, sbr);
format->annotate(cl, sbr, sa, values);
for (auto& lv : values) {
if (!lv.lv_meta.lvm_identifier) {
if (!lv.lv_meta.lvm_struct_name.empty()) {
continue;
}

@ -1,4 +1,5 @@
# Scripts
This directory contains the built-in lnav scripts. The files are
turned into C using `bin2c` and compiled into the executable.
turned into C using `bin2c` and compiled into the executable. New scripts
need to be added to the [scripts.am](scripts.am) file.

@ -0,0 +1,12 @@
BUILTIN_LNAVSCRIPTS = \
$(srcdir)/scripts/dhclient-summary.lnav \
$(srcdir)/scripts/lnav-pop-view.lnav \
$(srcdir)/scripts/partition-by-boot.lnav \
$(srcdir)/scripts/rename-stdin.lnav \
$(srcdir)/scripts/search-for.lnav \
$()
BUILTIN_SHSCRIPTS = \
$(srcdir)/scripts/dump-pid.sh \
$()

@ -211,3 +211,18 @@ short term_color_palette::match_color(const lab_color &to_match)
return lowest_id;
}
namespace styling {
Result<color_unit, std::string> color_unit::from_str(const string_fragment &sf)
{
if (sf == "semantic()") {
return Ok(color_unit{ semantic{} });
}
auto retval = TRY(rgb_color::from_str(sf));
return Ok(color_unit{ retval });
}
}

@ -32,21 +32,27 @@
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "log_level.hh"
#include "base/result.h"
#include "base/intern_string.hh"
#include "mapbox/variant.hpp"
struct rgb_color {
static Result<rgb_color, std::string> from_str(const string_fragment &sf);
explicit rgb_color(short r = -1, short g = -1, short b = -1)
: rc_r(r), rc_g(g), rc_b(b) {
: rc_r(r), rc_g(g), rc_b(b)
{
}
bool empty() const {
return this->rc_r == -1 && this->rc_g == -1 && this->rc_b == -1;
bool empty() const
{
return this->rc_r == -1 &&
this->rc_g == -1 &&
this->rc_b == -1;
}
bool operator==(const rgb_color &rhs) const;
@ -115,8 +121,36 @@ struct term_color_palette {
std::vector<term_color> tc_palette;
};
namespace styling {
struct semantic {};
class color_unit {
public:
static Result<color_unit, std::string> from_str(const string_fragment& sf);
static color_unit make_empty() {
return { rgb_color{} };
}
bool empty() const {
return this->cu_value.match(
[](semantic) { return false; },
[](const rgb_color& rc) { return rc.empty(); }
);
}
using variants_t = mapbox::util::variant<semantic, rgb_color>;
variants_t cu_value;
private:
color_unit(variants_t value) : cu_value(std::move(value)) {}
};
}
struct style_config {
bool sc_semantic{false};
std::string sc_color;
std::string sc_background_color;
bool sc_underline{false};

@ -45,7 +45,7 @@ static pcre *xpcre_compile(const char *pattern, int options = 0)
options,
&errptr,
&eoff,
NULL)) == NULL) {
nullptr)) == nullptr) {
fprintf(stderr, "internal error: failed to compile -- %s\n", pattern);
fprintf(stderr, "internal error: %s\n", errptr);
@ -395,10 +395,10 @@ void setup_highlights(highlight_map_t &hm)
"\\d+"))
.with_role(view_colors::VCR_FILE);
hm[{highlight_source_t::INTERNAL, "1.stringd"}] = highlighter(xpcre_compile(
"\"(?:\\\\.|[^\"])*\""))
R"("(?:\\.|[^"])*")"))
.with_role(view_colors::VCR_STRING);
hm[{highlight_source_t::INTERNAL, "1.strings"}] = highlighter(xpcre_compile(
"(?<![A-WY-Za-qstv-z])\'(?:\\\\.|[^'])*\'"))
R"((?<![A-WY-Za-qstv-z])'(?:\\.|[^'])*')"))
.with_role(view_colors::VCR_STRING);
hm[{highlight_source_t::INTERNAL, "1.stringb"}] = highlighter(xpcre_compile(
"`(?:\\\\.|[^`])*`"))
@ -413,7 +413,7 @@ void setup_highlights(highlight_map_t &hm)
"^\\@@ .*"))
.with_role(view_colors::VCR_DIFF_SECTION);
hm[{highlight_source_t::INTERNAL, "0.comment"}] = highlighter(xpcre_compile(
"(?<=[\\s;])//.*|/\\*.*\\*/|\\(\\*.*\\*\\)|^#.*|\\s+#.*|dnl.*"))
R"((?<=[\s;])//.*|/\*.*\*/|\(\*.*\*\)|^#.*|\s+#.*|dnl.*)"))
.with_role(view_colors::VCR_COMMENT);
hm[{highlight_source_t::INTERNAL, "javadoc"}] = highlighter(xpcre_compile(
"@(?:author|deprecated|exception|file|param|return|see|since|throws|todo|version)"))

@ -137,6 +137,12 @@ void textview_curses::reload_config(error_reporter &reporter)
iter = this->tc_highlights.erase(iter);
}
std::map<std::string, std::string> vars;
auto curr_theme_iter = lnav_config.lc_ui_theme_defs.find(lnav_config.lc_ui_theme);
if (curr_theme_iter != lnav_config.lc_ui_theme_defs.end()) {
vars = curr_theme_iter->second.lt_vars;
}
for (const auto& theme_name : {DEFAULT_THEME_NAME, lnav_config.lc_ui_theme}) {
auto theme_iter = lnav_config.lc_ui_theme_defs.find(theme_name);
@ -171,20 +177,20 @@ void textview_curses::reload_config(error_reporter &reporter)
fg1 = sc.sc_color;
bg1 = sc.sc_background_color;
shlex(fg1).eval(fg_color, theme_iter->second.lt_vars);
shlex(bg1).eval(bg_color, theme_iter->second.lt_vars);
shlex(fg1).eval(fg_color, vars);
shlex(bg1).eval(bg_color, vars);
auto fg = rgb_color::from_str(fg_color)
auto fg = styling::color_unit::from_str(fg_color)
.unwrapOrElse([&](const auto& msg) {
reporter(&sc.sc_color, errmsg);
invalid = true;
return rgb_color{};
return styling::color_unit::make_empty();
});
auto bg = rgb_color::from_str(bg_color)
auto bg = styling::color_unit::from_str(bg_color)
.unwrapOrElse([&](const auto& msg) {
reporter(&sc.sc_background_color, errmsg);
invalid = true;
return rgb_color{};
return styling::color_unit::make_empty();
});
if (invalid) {
continue;
@ -200,8 +206,7 @@ void textview_curses::reload_config(error_reporter &reporter)
highlighter(code)
.with_pattern(hl_pair.second.hc_regex)
.with_attrs(attrs != 0 ? attrs : -1)
.with_color(fg, bg)
.with_semantic(sc.sc_semantic);
.with_color(fg, bg);
}
}
}

@ -1,4 +1,5 @@
# Themes
This directory contains the built-in theme definitions. The files are
turned into C using `bin2c` and compiled into the executable.
turned into C using `bin2c` and compiled into the executable. New themes
need to be added to the [themes.am](themes.am) file.

@ -3,13 +3,17 @@
"ui": {
"theme-defs": {
"default": {
"vars": {
"semantic_highlight_color": "semantic()"
},
"styles": {
"text": {
"color": "Silver",
"background-color": "Black"
},
"identifier": {
"background-color": ""
"background-color": "",
"color": "semantic()"
},
"alt-text": {
"color": "Silver",
@ -158,21 +162,21 @@
},
"highlights": {
"colors": {
"pattern": "(?:#[a-fA-F0-9]{6}|#[a-fA-F0-9]{3})",
"pattern": "(?:#[a-fA-F0-9]{6}|#[a-fA-F0-9]{3}\\b)",
"style": {
"semantic": true
"color": "${semantic_highlight_color}"
}
},
"ipv4": {
"pattern": "\\b(?<!\\d\\.)\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b(?!\\.\\d)",
"style": {
"semantic": true
"color": "${semantic_highlight_color}"
}
},
"xml": {
"pattern": "</?([^ >=]+)[^>]*>",
"style": {
"semantic": true
"color": "${semantic_highlight_color}"
}
}
}

@ -11,11 +11,13 @@
"blue": "#729fcf",
"cyan": "#34e2e2",
"green": "#8ae234",
"white": "#ffffff"
"white": "#ffffff",
"semantic_highlight_color": "semantic()"
},
"styles": {
"identifier": {
"background-color": ""
"background-color": "",
"color": "semantic()"
},
"text": {
"color": "$white",

@ -0,0 +1,141 @@
{
"$schema": "https://lnav.org/schemas/config-v1.schema.json",
"ui": {
"theme-defs": {
"grayscale": {
"vars": {
"black": "#2d2a2e",
"red": "#f92772",
"green": "#a7e22e",
"yellow": "#fe9720",
"blue": "#5394ec",
"magenta": "#ae81ff",
"cyan": "#66d9ee",
"white": "#f6f6f6",
"plaintext": "#ccc"
},
"styles": {
"identifier": {
"background-color": "",
"color": "",
"bold": true
},
"text": {
"color": "",
"background-color": ""
},
"alt-text": {
"color": "",
"background-color": "",
"bold": true
},
"ok": {
"color": "$green",
"bold": true
},
"error": {
"color": "$red",
"bold": true
},
"warning": {
"color": "$yellow",
"bold": true
},
"hidden": {
"color": "$yellow",
"bold": true
},
"adjusted-time": {
"color": "$magenta"
},
"skewed-time": {
"color": "$yellow"
},
"offset-time": {
"color": "$cyan"
},
"invalid-msg": {
"color": "$yellow"
},
"focused": {
"color": "$black",
"background-color": "$plaintext"
},
"disabled-focused": {
"color": "$plaintext",
"background-color": "#333"
},
"popup": {
"color": "$plaintext",
"background-color": "#626262"
},
"scrollbar": {
"color": "$black",
"background-color": "#888"
}
},
"status-styles": {
"disabled-title": {
"color": "#5394ec",
"background-color": "#353535",
"bold": true
},
"title": {
"color": "#f6f6f6",
"background-color": "#8a8a8a",
"bold": true
},
"subtitle": {
"color": "#e4e4e4",
"background-color": "#626262",
"bold": true
},
"title-hotkey": {
"color": "$black",
"background-color": "#5394ec",
"underline": true
},
"hotkey": {
"color": "#fff",
"background-color": "#353535",
"underline": true
},
"text": {
"color": "#f6f6f6",
"background-color": "#353535"
},
"warn": {
"color": "$yellow",
"background-color": "#353535"
},
"alert": {
"color": "$red",
"background-color": "#353535"
},
"active": {
"color": "$green",
"background-color": "#353535"
},
"inactive": {
"color": "#555",
"background-color": "#2f2f2f"
}
},
"log-level-styles": {
"warning": {
"color": "$yellow"
},
"error": {
"color": "$red"
},
"critical": {
"color": "$red"
},
"fatal": {
"color": "$red"
}
}
}
}
}
}

@ -11,11 +11,13 @@
"blue": "#5394ec",
"magenta": "#ae81ff",
"cyan": "#66d9ee",
"white": "#808080"
"white": "#808080",
"semantic_highlight_color": "semantic()"
},
"styles": {
"identifier": {
"background-color": "$black"
"background-color": "$black",
"color": "semantic()"
},
"text": {
"color": "#f6f6f6",

@ -11,11 +11,13 @@
"blue": "#5394ec",
"magenta": "#ff70ff",
"cyan": "#33cccc",
"white": "#d6deeb"
"white": "#d6deeb",
"semantic_highlight_color": "semantic()"
},
"styles": {
"identifier": {
"background-color": "#011627"
"background-color": "#011627",
"color": "semantic()"
},
"text": {
"color": "#d6deeb",

@ -20,11 +20,13 @@
"violet": "#6c71c4",
"blue": "#268bd2",
"cyan": "#2aa198",
"green": "#859900"
"green": "#859900",
"semantic_highlight_color": "semantic()"
},
"styles": {
"identifier": {
"background-color": "$base03"
"background-color": "$base03",
"color": "semantic()"
},
"text": {
"color": "$base0",

@ -20,11 +20,13 @@
"violet": "#6c71c4",
"blue": "#268bd2",
"cyan": "#2aa198",
"green": "#859900"
"green": "#859900",
"semantic_highlight_color": "semantic()"
},
"styles": {
"identifier": {
"background-color": "$base3"
"background-color": "$base3",
"color": "semantic()"
},
"text": {
"color": "$base00",

@ -0,0 +1,10 @@
THEME_FILES = \
$(srcdir)/%reldir%/default-theme.json \
$(srcdir)/%reldir%/eldar.json \
$(srcdir)/%reldir%/grayscale.json \
$(srcdir)/%reldir%/monocai.json \
$(srcdir)/%reldir%/night-owl.json \
$(srcdir)/%reldir%/solarized-dark.json \
$(srcdir)/%reldir%/solarized-light.json \
$()

@ -81,10 +81,9 @@ void top_status_source::update_time()
void top_status_source::update_filename(listview_curses *lc)
{
status_field & sf_partition = this->tss_fields[TSF_PARTITION_NAME];
status_field & sf_format = this->tss_fields[TSF_FORMAT];
status_field & sf_filename = this->tss_fields[TSF_FILENAME];
struct line_range lr(0);
auto &sf_partition = this->tss_fields[TSF_PARTITION_NAME];
auto &sf_format = this->tss_fields[TSF_FORMAT];
auto &sf_filename = this->tss_fields[TSF_FILENAME];
if (lc->get_inner_height() > 0) {
string_attrs_t::const_iterator line_attr;

@ -260,7 +260,13 @@ void view_curses::mvwattrline(WINDOW *window,
if (!has_fg) {
memset(fg_color, -1, line_width_chars * sizeof(short));
}
fill(&fg_color[attr_range.lr_start], &fg_color[attr_range.lr_end], (short) iter->sa_value.sav_int);
short attr_fg = iter->sa_value.sav_int;
if (attr_fg == view_colors::MATCH_COLOR_SEMANTIC) {
attr_fg = vc.color_for_ident(
&line[iter->sa_range.lr_start],
iter->sa_range.length());
}
fill(&fg_color[attr_range.lr_start], &fg_color[attr_range.lr_end], attr_fg);
has_fg = true;
continue;
}
@ -269,7 +275,13 @@ void view_curses::mvwattrline(WINDOW *window,
if (!has_bg) {
memset(bg_color, -1, line_width_chars * sizeof(short));
}
fill(bg_color + attr_range.lr_start, bg_color + attr_range.lr_end, (short) iter->sa_value.sav_int);
short attr_bg = iter->sa_value.sav_int;
if (attr_bg == view_colors::MATCH_COLOR_SEMANTIC) {
attr_bg = vc.color_for_ident(
&line[iter->sa_range.lr_start],
iter->sa_range.length());
}
fill(bg_color + attr_range.lr_start, bg_color + attr_range.lr_end, attr_bg);
has_bg = true;
continue;
}
@ -307,6 +319,24 @@ void view_curses::mvwattrline(WINDOW *window,
int ch_width = min(awidth, (line_width_chars - attr_range.lr_start));
cchar_t row_ch[ch_width + 1];
if (attrs & (A_LEFT|A_RIGHT)) {
short pair_fg, pair_bg;
pair_content(color_pair, &pair_fg, &pair_bg);
if (attrs & A_LEFT) {
pair_fg = vc.color_for_ident(
&line[iter->sa_range.lr_start],
iter->sa_range.length());
}
if (attrs & A_RIGHT) {
pair_bg = vc.color_for_ident(
&line[iter->sa_range.lr_start],
iter->sa_range.length());
}
color_pair = vc.ensure_color_pair(pair_fg, pair_bg);
attrs &= ~(A_LEFT|A_RIGHT);
}
mvwin_wchnstr(window, y, x_pos, row_ch, ch_width);
for (int lpc = 0; lpc < ch_width; lpc++) {
bool clear_rev = false;
@ -535,7 +565,16 @@ inline attr_t attr_for_colors(int &pair_base, short fg, short bg)
init_pair(pair, fg, bg);
}
return COLOR_PAIR(pair);
auto retval = COLOR_PAIR(pair);
if (fg == view_colors::MATCH_COLOR_SEMANTIC) {
retval |= A_LEFT;
}
if (bg == view_colors::MATCH_COLOR_SEMANTIC) {
retval |= A_RIGHT;
}
return retval;
}
pair<attr_t, attr_t> view_colors::to_attrs(
@ -556,13 +595,13 @@ pair<attr_t, attr_t> view_colors::to_attrs(
shlex(fg1).eval(fg_color, lt.lt_vars);
shlex(bg1).eval(bg_color, lt.lt_vars);
auto fg = rgb_color::from_str(fg_color).unwrapOrElse([&](const auto& msg) {
auto fg = styling::color_unit::from_str(fg_color).unwrapOrElse([&](const auto& msg) {
reporter(&sc.sc_color, msg);
return rgb_color{};
return styling::color_unit::make_empty();
});
auto bg = rgb_color::from_str(bg_color).unwrapOrElse([&](const auto& msg) {
auto bg = styling::color_unit::from_str(bg_color).unwrapOrElse([&](const auto& msg) {
reporter(&sc.sc_background_color, msg);
return rgb_color{};
return styling::color_unit::make_empty();
});
attr_t retval1 = attr_for_colors(pair_base,
@ -590,7 +629,7 @@ void view_colors::init_roles(const lnav_theme &lt,
string err;
if (COLORS == 256) {
const style_config &ident_sc = lt.lt_style_identifier;
const auto &ident_sc = lt.lt_style_identifier;
int ident_bg = (lnav_config.lc_ui_default_colors ? -1 : COLOR_BLACK);
if (!ident_sc.sc_background_color.empty()) {
@ -673,6 +712,8 @@ void view_colors::init_roles(const lnav_theme &lt,
this->vc_role_colors[VCR_TEXT].second |= A_DIM;
}
this->vc_role_colors[VCR_SEARCH] = make_pair(A_REVERSE, A_REVERSE);
this->vc_role_colors[VCR_IDENTIFIER] = this->to_attrs(
color_pair_base, lt, lt.lt_style_identifier, lt.lt_style_text, reporter);
this->vc_role_colors[VCR_OK] = this->to_attrs(color_pair_base,
lt, lt.lt_style_ok,
lt.lt_style_text,
@ -853,7 +894,7 @@ void view_colors::init_roles(const lnav_theme &lt,
this->vc_role_colors[VCR_HIGH_THRESHOLD] = this->to_attrs(
color_pair_base, lt, lt.lt_style_high_threshold, lt.lt_style_text, reporter);
for (log_level_t level = static_cast<log_level_t>(LEVEL_UNKNOWN + 1);
for (auto level = static_cast<log_level_t>(LEVEL_UNKNOWN + 1);
level < LEVEL__MAX;
level = static_cast<log_level_t>(level + 1)) {
auto level_iter = lt.lt_level_styles.find(level);
@ -875,8 +916,8 @@ void view_colors::init_roles(const lnav_theme &lt,
int view_colors::ensure_color_pair(int &pair_base, short fg, short bg)
{
require(fg >= -1);
require(bg >= -1);
require(fg >= -100);
require(bg >= -100);
auto index_pair = make_pair(fg, bg);
auto existing = this->vc_dyn_pairs.find(index_pair);
@ -902,38 +943,78 @@ int view_colors::ensure_color_pair(int &pair_base, short fg, short bg)
return retval;
}
int view_colors::ensure_color_pair(int &pair_base, const rgb_color &rgb_fg, const rgb_color &rgb_bg)
int view_colors::ensure_color_pair(int &pair_base,
const styling::color_unit &rgb_fg,
const styling::color_unit &rgb_bg)
{
int fg = this->match_color(rgb_fg);
int bg = this->match_color(rgb_bg);
auto fg = this->match_color(rgb_fg);
auto bg = this->match_color(rgb_bg);
return this->ensure_color_pair(pair_base, fg, bg);
}
int view_colors::match_color(const rgb_color &color)
short view_colors::match_color(const styling::color_unit &color) const
{
if (color.empty()) {
return -1;
}
return color.cu_value.match(
[](styling::semantic) {
return MATCH_COLOR_SEMANTIC;
},
[](const rgb_color& color) {
if (color.empty()) {
return MATCH_COLOR_DEFAULT;
}
return vc_active_palette->match_color(lab_color(color));
return vc_active_palette->match_color(lab_color(color));
}
);
}
attr_t view_colors::attrs_for_ident(const char *str, size_t len) const
int view_colors::color_for_ident(const char *str, size_t len) const
{
unsigned long index = crc32(1, (const Bytef*)str, len);
attr_t retval;
int retval;
if (COLORS >= 256) {
if (str[0] == '#' && (len == 4 || len == 7)) {
auto fg_res = styling::color_unit::from_str(string_fragment(str, 0, len));
if (fg_res.isOk()) {
return this->match_color(fg_res.unwrap());
}
}
unsigned long offset = index % HI_COLOR_COUNT;
retval = COLOR_PAIR(VC_ANSI_END + offset);
auto cpair = COLOR_PAIR(VC_ANSI_END + offset);
short fg, bg;
int pnum = PAIR_NUMBER(retval);
auto pnum = PAIR_NUMBER(cpair);
pair_content(pnum, &fg, &bg);
retval = fg;
}
else {
retval = A_BOLD;
retval = -1;
}
return retval;
}
attr_t view_colors::attrs_for_ident(const char *str, size_t len)
{
auto retval = this->attrs_for_role(VCR_IDENTIFIER);
if (retval & (A_LEFT|A_RIGHT)) {
auto color_pair = PAIR_NUMBER(retval);
short pair_fg, pair_bg;
pair_content(color_pair, &pair_fg, &pair_bg);
if (retval & A_LEFT) {
pair_fg = this->color_for_ident(str, len);
}
if (retval & A_RIGHT) {
pair_bg = this->color_for_ident(str, len);
}
color_pair = this->ensure_color_pair(pair_fg, pair_bg);
retval &= ~(A_COLOR|A_LEFT|A_RIGHT);
retval |= COLOR_PAIR(color_pair);
}
return retval;

@ -189,6 +189,7 @@ public:
VCR_NONE = -1,
VCR_TEXT, /*< Raw text. */
VCR_IDENTIFIER,
VCR_SEARCH, /*< A search hit. */
VCR_OK,
VCR_ERROR, /*< An error message. */
@ -283,13 +284,15 @@ public:
return this->vc_role_reverse_colors[role];
};
attr_t attrs_for_ident(const char *str, size_t len) const;
int color_for_ident(const char *str, size_t len) const;
attr_t attrs_for_ident(intern_string_t str) const {
attr_t attrs_for_ident(const char *str, size_t len);
attr_t attrs_for_ident(intern_string_t str) {
return this->attrs_for_ident(str.get(), str.size());
}
attr_t attrs_for_ident(const std::string &str) const {
attr_t attrs_for_ident(const std::string &str) {
return this->attrs_for_ident(str.c_str(), str.length());
};
@ -299,13 +302,19 @@ public:
return this->ensure_color_pair(this->vc_color_pair_end, fg, bg);
}
int ensure_color_pair(int &pair_base, const rgb_color &fg, const rgb_color &bg);
int ensure_color_pair(int &pair_base,
const styling::color_unit &fg,
const styling::color_unit &bg);
int ensure_color_pair(const rgb_color &fg, const rgb_color &bg) {
int ensure_color_pair(const styling::color_unit &fg,
const styling::color_unit &bg) {
return this->ensure_color_pair(this->vc_color_pair_end, fg, bg);
}
int match_color(const rgb_color &color);
static constexpr short MATCH_COLOR_DEFAULT = -1;
static constexpr short MATCH_COLOR_SEMANTIC = -10;
short match_color(const styling::color_unit &color) const;
static inline int ansi_color_pair_index(int fg, int bg)
{

@ -631,7 +631,7 @@ struct json_path_handler : public json_path_handler_base {
yajlpp_generator gen(handle);
relative_time rt;
rt.from_timeval({ field.count() });
rt.from_timeval({ field.count(), 0 });
return gen(rt.to_string());
};
this->jph_field_getter = [args...](void *root, nonstd::optional<std::string> name) {

@ -62,6 +62,9 @@ add_executable(test_top_status test_top_status.cc)
target_link_libraries(test_top_status diag PkgConfig::libpcre)
add_test(NAME test_top_status COMMAND test_top_status)
add_executable(drive_view_colors drive_view_colors.cc)
target_link_libraries(drive_view_colors diag PkgConfig::ncursesw)
add_executable(drive_vt52_curses drive_vt52_curses.cc)
target_link_libraries(drive_vt52_curses diag PkgConfig::ncursesw)

@ -39,11 +39,10 @@ class test_colors : public view_curses {
public:
test_colors()
: tc_window(NULL) {
: tc_window(nullptr) {
}
void do_update(void) {
void do_update() override {
view_colors &vc = view_colors::singleton();
int lpc;
@ -54,21 +53,16 @@ public:
line_range lr;
snprintf(label, sizeof(label), "This is line: %d", lpc);
attrs = view_colors::singleton().attrs_for_ident(label);
attrs = vc.attrs_for_ident(label);
al = label;
al.get_attrs().push_back(string_attr(
al.get_attrs().emplace_back(
line_range(0, -1),
&view_curses::VC_STYLE,
attrs
));
);
lr.lr_start = 0;
lr.lr_end = 40;
this->mvwattrline(this->tc_window,
lpc,
0,
al,
lr,
view_colors::VCR_TEXT);
test_colors::mvwattrline(this->tc_window, lpc, 0, al, lr);
}
attr_line_t al;
@ -76,14 +70,9 @@ public:
al = "before <123> after";
al.with_attr({line_range{8, 11}, &VC_STYLE,
(int64_t) vc.ansi_color_pair(COLOR_CYAN, COLOR_BLACK)});
(int64_t) view_colors::ansi_color_pair(COLOR_CYAN, COLOR_BLACK)});
al.with_attr({line_range{8, 11}, &VC_STYLE, A_REVERSE});
this->mvwattrline(this->tc_window,
lpc,
0,
al,
lr,
view_colors::VCR_TEXT);
test_colors::mvwattrline(this->tc_window, lpc, 0, al, lr);
};
WINDOW *tc_window;
@ -107,7 +96,7 @@ int main(int argc, char *argv[])
}
}
view_colors::singleton().init();
view_colors::init();
curs_set(0);
tc.tc_window = win;
tc.do_update();

@ -9,40 +9,38 @@ S -1 ┋
A └ normal, normal, normal
CSI Erase all
S 1 ┋This is line: 0 ┋
A └ bold │
A ········································└ carriage-return
A ···············└ carriage-return
S 2 ┋This is line: 1 ┋
A ········································└ carriage-return
A ···············└ carriage-return
S 3 ┋This is line: 2 ┋
A ········································└ carriage-return
A ···············└ carriage-return
S 4 ┋This is line: 3 ┋
A ········································└ carriage-return
A ···············└ carriage-return
S 5 ┋This is line: 4 ┋
A ········································└ carriage-return
A ···············└ carriage-return
S 6 ┋This is line: 5 ┋
A ········································└ carriage-return
A ···············└ carriage-return
S 7 ┋This is line: 6 ┋
A ········································└ carriage-return
A ···············└ carriage-return
S 8 ┋This is line: 7 ┋
A ········································└ carriage-return
A ···············└ carriage-return
S 9 ┋This is line: 8 ┋
A ········································└ carriage-return
A ···············└ carriage-return
S 10 ┋This is line: 9 ┋
A ········································└ carriage-return
A ···············└ carriage-return
S 11 ┋This is line: 10 ┋
A ········································└ carriage-return
A ················└ carriage-return
S 12 ┋This is line: 11 ┋
A ········································└ carriage-return
A ················└ carriage-return
S 13 ┋This is line: 12 ┋
A ········································└ carriage-return
A ················└ carriage-return
S 14 ┋This is line: 13 ┋
A ········································└ carriage-return
A ················└ carriage-return
S 15 ┋This is line: 14 ┋
A ········································└ carriage-return
A ················└ carriage-return
S 16 ┋This is line: 15 ┋
A ········································└ carriage-return
A ················└ carriage-return
S 17 ┋before <123> after ┋
A └ normal│ │
A ········└ fg(#008080), inverse
A ···········└ normal
S 17 ┋ ┋

@ -88,12 +88,11 @@ A ····································
A ·······································································└ normal, fg(#000000), bg(#c0c0c0)
S 19 ┋ ┋
S 20 ┋→ ` logfile_xml_msg.0 0.0 B — x┋
A ··├ fg(#008000), bg(#c0c0c0) │ │ │
A └┛ alt │ │ │ │
A ···└ fg(#000000), bg(#c0c0c0) │ │ │
A ························└ fg(#c0c0c0), bg(#c0c0c0) │
A ······························└ fg(#000000), bg(#c0c0c0), bold │
A ································└ normal, fg(#000000), bg(#c0c0c0) │
A ··├ fg(#008000), bg(#c0c0c0) │ │
A └┛ alt │ │ │
A ···└ fg(#000000), bg(#c0c0c0) │ │
A ························└ bold│ │
A ······························└ normal, fg(#000000), bg(#c0c0c0) │
A ················································································└ normal
A └┛ alt
A ················································································└ normal
@ -118,14 +117,13 @@ A ·············└ bold
A ··················└ normal, fg(#000000), bg(#c0c0c0)
S 20 ┋ 64.0 B 2020-12-10 06:56:41.061 — 2020-12-10 06:56:41. ┋
A ···························└ backspace │
A ··························└ fg(#c0c0c0), bg(#c0c0c0) │
A ······························└ fg(#000000), bg(#c0c0c0), bold │
A ································└ normal, fg(#000000), bg(#c0c0c0) │
A ··························└ bold │
A ······························└ normal, fg(#000000), bg(#c0c0c0) │
A ···············································································└ carriage-return
S 22 ┋ ┋
A └ normal, normal
S 20 ┋ 628 ┋
A ·························└ fg(#c0c0c0), bg(#c0c0c0)
A ·························└ fg(#000000), bg(#c0c0c0), bold
A ····························└ carriage-return
S 22 ┋ ┋
A └ normal, normal
@ -135,94 +133,66 @@ A ·······························└ fg(#c0c
S 1 ┋ xml_msg_log ┋
A ······································································└ carriage-return
S 2 ┋x x ┋
A ├ normal, bold
A └┛ alt│
A ·└ normal
A ······└ carriage-return
S 3 ┋x </head> ┋
A ├ bold ││
A └┛ alt ││
A ·└ normal││
A ·····└ bold
A ·········└ normal
A └┛ alt │
A ·└ normal │
A ··········└ carriage-return
S 4 ┋x <reply id="2"> ┋
A ├ bold ││ ││ ││
A └┛ alt ││ ││ ││
A ·└ normal││ ││ ││
A ····└ bold│ ││ ││
A ·········└ normal│
A └┛ alt │ ││ ││
A ·└ normal │ ││ ││
A ··········└ fg(#008080)
A ············└ normal
A ·············└ fg(#008000), bold
A ················└ normal
A ·················└ carriage-return
S 5 ┋x <status> ┋
A ├ bold│ ││
A └┛ alt│ ││
A ·└ normal ││
A ······└ bold││
A ············└ normal
A └┛ alt │
A ·└ normal │
A ·············└ carriage-return
S 6 ┋x <result>OK</result> ┋
A ├ bold ││ │ │ ││
A └┛ alt ││ │ │ ││
A ·└ normal │ │ ││
A ·······└ bold, normal ││
A ········└ bold│ │ ││
A ··············└ normal ││
A ···················└ bold││
A ·························└ normal
A ·└ normal │
A └┛ alt │ │
A ·└ normal │
A ·······└ normal │
A ··························└ carriage-return
S 7 ┋x </status> ┋
A ├ bold │ ││
A └┛ alt │ ││
A ·└ normal ││
A ·······└ bold││
A ·············└ normal
A └┛ alt │
A ·└ normal │
A ··············└ carriage-return
S 8 ┋x <name> ┋
A ├ bold│ ││
A └┛ alt│ ││
A ·└ normal ││
A ······└ bold
A ··········└ normal
A └┛ alt │
A ·└ normal │
A ···········└ carriage-return
S 9 ┋x x ┋
A ├ bold ││
A ·└ normal
A └┛ alt ││
A ·└ normal
A ·······└ bold, normal
A ·······└ normal
A ········└ carriage-return
S 10 ┋x </name> ┋
A ├ bold │ ││
A └┛ alt │ ││
A ·└ normal ││
A ·······└ bold
A ···········└ normal
A └┛ alt │
A ·└ normal │
A ············└ carriage-return
S 11 ┋x </reply> x┋
A ├ bold │ ││
A └┛ alt │ ││
A ·└ normal │ ││
A ·····└ bold ││
A ··········└ normal ││
A └┛ alt ││
A ·└ normal ││
A ···············································································└ fg(#000000), bg(#c0c0c0)
A ················································································└ normal
A └┛ alt
A ················································································└ normal
S 12 ┋x <technical-track> x┋
A ├ fg(#000000), bg(#c0c0c0), normal, bold ││
A └┛ alt │ ││
A ·└ normal │ ││
A ····└ bold │ ││
A ···················└ normal ││
A ├ fg(#000000), bg(#c0c0c0) ││
A └┛ alt ││
A ·└ normal ││
A ···············································································└ fg(#000000), bg(#c0c0c0)
A ················································································└ normal
A └┛ alt
A ················································································└ normal
S 13 ┋x x x┋
A ├ fg(#000000), bg(#c0c0c0), normal, bold ││
A ├ fg(#000000), bg(#c0c0c0) ││
A └┛ alt ││
A ·└ normal ││
A ···············································································└ fg(#000000), bg(#c0c0c0)
@ -230,21 +200,17 @@ A ····································
A └┛ alt
A ················································································└ normal
S 14 ┋x </technical-track> x┋
A ├ fg(#000000), bg(#c0c0c0), normal, bold ││
A └┛ alt │ ││
A ·└ normal │ ││
A ·····└ bold │ ││
A ····················└ normal ││
A ├ fg(#000000), bg(#c0c0c0) ││
A └┛ alt ││
A ·└ normal ││
A ···············································································└ fg(#000000), bg(#c0c0c0)
A ················································································└ normal
A └┛ alt
A ················································································└ normal
S 15 ┋x</a-reply> x┋
A ├ fg(#000000), bg(#c0c0c0), normal, bold ││
A └┛ alt │ ││
A ·└ normal │ ││
A ···└ bold │ ││
A ··········└ normal ││
A ├ fg(#000000), bg(#c0c0c0) ││
A └┛ alt ││
A ·└ normal ││
A ···············································································└ fg(#000000), bg(#c0c0c0)
A ················································································└ normal
A └┛ alt
@ -283,12 +249,9 @@ A ····································
A ·······································································└ fg(#008080), bg(#000080)
A ········································································└ fg(#c0c0c0), bg(#000080), bold
S 2 ┋ [2020-12-10 06:56:41,092] DEBUG [connect.client:69] Full request text: ┋
A ·└ normal │ │
A ··································└ bold │
A ·└ normal │
A ················································└ normal
S 3 ┋ <?xml version='1.0' encoding='iso-8859-2'?> x┋
A ··└ bold ││ ││ ││ │ ││
A ······└ normal││ ││ ││ │ ││
A ·······└ fg(#008080)││ ││ │ ││
A ··············└ normal ││ │ ││
A ···············└ fg(#008000), bold │ ││
@ -303,16 +266,12 @@ A
A ················································································└ normal
S 4 ┋ <a-request> x┋
A ·└ fg(#000000), bg(#c0c0c0), normal ││
A ··└ bold │ ││
A ···········└ normal ││
A ···············································································└ fg(#000000), bg(#c0c0c0)
A ················································································└ normal
A └┛ alt
A ················································································└ normal
S 5 ┋ <head> x┋
A ···└ fg(#000000), bg(#c0c0c0), normal ││
A ····└ bold ││
A ········└ normal ││
A ···············································································└ fg(#000000), bg(#c0c0c0)
A ················································································└ normal
A └┛ alt
@ -325,16 +284,13 @@ A
A ················································································└ normal
S 7 ┋ </head> x┋
A ···└ fg(#000000), bg(#c0c0c0), normal ││
A ·····└ bold ││
A ·········└ normal ││
A ···············································································└ fg(#000000), bg(#c0c0c0)
A ················································································└ normal
A └┛ alt
A ················································································└ normal
S 8 ┋ <sourc x┋
A ···└ fg(#000000), bg(#c0c0c0), normal ││
A ····└ bold ││
A ···············································································└ normal, fg(#000000), bg(#c0c0c0)
A ···············································································└ fg(#000000), bg(#c0c0c0)
A ················································································└ normal
A └┛ alt
A ················································································└ normal
@ -346,58 +302,41 @@ A
A ················································································└ normal
S 10 ┋ </sourc x┋
A ···└ fg(#000000), bg(#c0c0c0), normal ││
A ·····└ bold ││
A ···············································································└ normal, fg(#000000), bg(#c0c0c0)
A ···············································································└ fg(#000000), bg(#c0c0c0)
A ················································································└ normal
A └┛ alt
A ················································································└ normal
S 11 ┋ request id="1"> ┋
A ····└ fg(#000000), bg(#c0c0c0), normal, bold
A ···········└ normal
A ····└ fg(#000000), bg(#c0c0c0), normal
A ············└ fg(#008080)
A ··············└ normal
A ···············└ fg(#008000), bold
A ··················└ normal
S 12 ┋ <name> ┋
A ······└ bold
A ··········└ normal
S 13 ┋ x ┋
S 14 ┋ </name> ┋
A ·······└ bold
A ···········└ normal
S 15 ┋ </request> x┋
A ·····└ bold │ ││
A ············└ normal ││
A └┛ alt
A ················································································└ normal
S 16 ┋x</a-request> x┋
A ├ bold │ ││
A └┛ alt │ ││
A ·└ normal │ ││
A ···└ bold │ ││
A ············└ normal ││
A └┛ alt ││
A ·└ normal ││
A └┛ alt
A ················································································└ normal
S 17 ┋x x┋
A ├ bold ││
A └┛ alt ││
A ·└ normal ││
A └┛ alt
A ················································································└ normal
S 18 ┋x[2020-12-10 06:56:41,099] DEBUG [m:85] Full reply text: x┋
A ├ bold ││ ││
A └┛ alt ││ ││
A ·└ normal ││ ││
A ··································└ bold ││
A └┛ alt │ ││
A ·└ normal │ ││
A ···································└ normal ││
A └┛ alt
A ················································································└ normal
S 19 ┋x<?xml version='1.0' encoding='iso-8859-2'?> x┋
A ├ bold││ ││ ││ ││ │ ││
A └┛ alt││ ││ ││ ││ │ ││
A └┛ alt │ ││ ││ ││ │ ││
A ·└ normal ││ ││ ││ │ ││
A ··└ bold ││ ││ ││ │ ││
A ······└ normal││ ││ ││ │ ││
A ·······└ fg(#008080)││ ││ │ ││
A ··············└ normal ││ │ ││
A ···············└ fg(#008000), bold │ ││
@ -409,19 +348,13 @@ A ····································
A └┛ alt
A ················································································└ normal
S 20 ┋x<a-reply> x┋
A ├ bold │ ││
A └┛ alt │ ││
A ·└ normal│ ││
A ··└ bold │ ││
A ·········└ normal ││
A └┛ alt ││
A ·└ normal ││
A └┛ alt
A ················································································└ normal
S 21 ┋x <head> x┋
A ├ bold │ ││
A └┛ alt │ ││
A └┛ alt ││
A ·└ normal ││
A ····└ bold ││
A ········└ normal ││
A └┛ alt
A ················································································└ normal
S 22 ┋ Files :: Text Filters :: Press TAB to edit ┋
@ -477,42 +410,36 @@ A ····································
A ············································└ carriage-return
S 6 ┋ t timestamp = 2020-12-10 06:56:41,092 ┋
A └┛ alt │ │ │
A ···└ bold │ │ │
A ············└ normal │
A ···············└ bold │
A ···············································································└ carriage-return
S 7 ┋ t level = DEBUG ┋
A └ normal│ │ │
A └┛ alt │ │ │
A ···└ bold │ │
A ········└ normal │
A ···············└ bold │
A ···············································································└ carriage-return
S 8 ┋ t module = connect.client ┋
A └ normal │ │ │
A └┛ alt │ │ │
A ···└ bold│ │ │
A ·········└ normal │
A ···············└ bold │
A ···············································································└ carriage-return
S 9 ┋ t line = 69 ┋
A └ normal │ │ │
A └┛ alt │ │ │
A ···└ bold │ │ │
A ·············└ normal │
A ···············└ bold │
A ···············································································└ carriage-return
S 10 ┋ t body = Full request text: ┋
A └ normal │ │
A └┛ alt│ │ │
A ···└ bold │ │
A ·······└ normal│ │
A ···············└ bold │
A ···············································································└ carriage-return
S 11 ┋ t msg_data = <?xml version='1.0' encoding='iso-8859-2'?> <a-request> <head> ┋
A └ normal │ │ │
A └┛ alt │ │ │
A ···└ bold │ │ │
A ···········└ normal │
A ···············└ bold │
A ···············································································└ carriage-return
@ -541,8 +468,6 @@ A ····································
S 17 ┋ No discovered message fields ┋
A └ normal
S 18 ┋ <?xml version='1.0' encoding='iso-8859-2'?> ┋
A ··└ bold ││ ││ ││ │
A ······└ normal││ ││ ││ │
A ·······└ fg(#008080)││ ││ │
A ··············└ normal ││ │
A ···············└ fg(#008000), bold │
@ -552,11 +477,7 @@ A ·····························└ normal
A ······························└ fg(#008000), bold
A ··········································└ normal
S 19 ┋ a-request> ┋
A ··└ bold │
A ···········└ normal
S 20 ┋ <head> ┋
A ····└ bold
A ········└ normal
S 21 ┋ x ┋
A ·········└ carriage-return
S 24 ┋ ┋

Loading…
Cancel
Save