mirror of
https://github.com/tstack/lnav
synced 2024-11-17 15:29:40 +00:00
292 lines
12 KiB
ReStructuredText
292 lines
12 KiB
ReStructuredText
.. _usage:
|
||
|
||
Usage
|
||
=====
|
||
|
||
This chapter contains an overview of how to use **lnav**.
|
||
|
||
|
||
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 :kbd:`:`. To analyze data in a
|
||
log file, you can enter the :ref:`SQL prompt<sql-ext>` by pressing :kbd:`;`.
|
||
|
||
.. tip::
|
||
|
||
Check the bottom right corner of the screen for tips on hotkeys that might
|
||
be useful in the current context.
|
||
|
||
.. figure:: hotkey-tips.png
|
||
:align: center
|
||
|
||
When **lnav** is first open, it suggests using :kbd:`e` and
|
||
:kbd:`Shift` + :kbd:`e` to jump to error messages.
|
||
|
||
|
||
Viewing Files
|
||
-------------
|
||
|
||
The files to view in **lnav** can be given on the command-line or passed to the
|
||
:ref:`:open<open>` command. A
|
||
`glob pattern <https://en.wikipedia.org/wiki/Glob_(programming)>`_ can be given
|
||
to watch for files with a common name. If the path is a directory, all of the
|
||
files in the directory will be opened and the directory will be monitored for
|
||
files to be added or removed from the view. If the path is an archive or
|
||
compressed file (and lnav was built with libarchive), the archive will be
|
||
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 :kbd:`t`.
|
||
|
||
|
||
Archive Support
|
||
^^^^^^^^^^^^^^^
|
||
|
||
.. f0:archive
|
||
|
||
If **lnav** is compiled with `libarchive <https://www.libarchive.org>`_,
|
||
any files to be opened will be examined to see if they are a supported archive
|
||
type. If so, the contents of the archive will be extracted to the
|
||
:code:`$TMPDIR/lnav-user-${UID}-work/archives/` directory. Once extracted, the
|
||
files within will be loaded into lnav. To speed up opening large amounts of
|
||
files, any file that meets the following conditions will be automatically
|
||
hidden and not indexed:
|
||
|
||
* Binary files
|
||
* Plain text files that are larger than 128KB
|
||
* Duplicate log files
|
||
|
||
The unpacked files will be left in the temporary directory after exiting
|
||
**lnav** so that opening the same archive again will be faster. Unpacked
|
||
archives that have not been accessed in the past two days will be automatically
|
||
deleted the next time **lnav** is started.
|
||
|
||
|
||
.. _remote:
|
||
|
||
Remote Files
|
||
^^^^^^^^^^^^
|
||
|
||
Files on remote machines can be viewed and tailed if you have access to the
|
||
machines via SSH. First, make sure you can SSH into the remote machine without
|
||
any interaction by: 1) accepting the host key as known and 2) copying your
|
||
identity's public key to the :file:`.ssh/authorized_keys` file on the remote
|
||
machine. Once the setup is complete, you can open a file on a remote host
|
||
using the same syntax as :command:`scp(1)` where the username and host are
|
||
given, followed by a colon, and then the path to the files, like so::
|
||
|
||
[user@]host:/path/to/logs
|
||
|
||
For example, to open :file:`/var/log/syslog.log` on "host1.example.com" as the
|
||
user "dean", you would write:
|
||
|
||
.. prompt:: bash
|
||
|
||
lnav dean@host1.example.com:/var/log/syslog.log
|
||
|
||
Remote files can also be opened using the :ref:`:open<open>` command. Opening
|
||
a remote file in the TUI has the advantage that the file path can be
|
||
:kbd:`TAB`-completed and a preview is shown of the first few lines of the
|
||
file.
|
||
|
||
.. note::
|
||
|
||
If lnav is installed from the `snap <https://snapcraft.io/lnav>`_, you will
|
||
need to connect it to the
|
||
`ssh-keys plug <https://snapcraft.io/docs/ssh-keys-interface>`_ using the
|
||
following command:
|
||
|
||
.. prompt:: bash
|
||
|
||
sudo snap connect lnav:ssh-keys
|
||
|
||
.. note::
|
||
|
||
Remote file access is implemented by transferring an
|
||
`αcτµαlly pδrταblε εxεcµταblε <https://justine.lol/ape.html>`_ to the
|
||
destination and invoking it. An APE binary can run on most any x86_64
|
||
machine and OS (i.e. MacOS, Linux, FreeBSD, Windows). The binary is
|
||
baked into the lnav executable itself, so there is no extra setup that
|
||
needs to be done on the remote machine.
|
||
|
||
The binary file is named ``tailer.bin.XXXXXX`` where *XXXXXX* is 6 random digits.
|
||
The file is, under normal circumstancies, deleted immediately.
|
||
|
||
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 :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
|
||
---------
|
||
|
||
To reduce the amount of noise in a log file, **lnav** can hide log messages
|
||
that match certain criteria. The following sub-sections explain ways to go
|
||
about that.
|
||
|
||
|
||
Regular Expression Match
|
||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
If there are log messages that you are not interested in, you can do a
|
||
"filter out" to hide messages that match a pattern. A filter can be created
|
||
using the interactive editor, the :ref:`:filter-out<filter_out>` command, or
|
||
by doing an :code:`INSERT` into the
|
||
:ref:`lnav_view_filters<table_lnav_view_filters>` table.
|
||
|
||
If there are log messages that you are only interested in, you can do a
|
||
"filter in" to only show messages that match a pattern. The filter can be
|
||
created using the interactive editor, the :ref:`:filter-in<filter_in>` command,
|
||
or by doing an :code:`INSERT` into the
|
||
:ref:`lnav_view_filters<table_lnav_view_filters>` table.
|
||
|
||
|
||
SQLite Expression
|
||
^^^^^^^^^^^^^^^^^
|
||
|
||
Complex filtering can be done by passing a SQLite expression to the
|
||
:ref:`:filter-expr<filter_expr>` command. The expression will be executed for
|
||
every log message and if it returns true, the line will be shown in the log
|
||
view.
|
||
|
||
|
||
Time
|
||
^^^^
|
||
|
||
To limit log messages to a given time frame, the
|
||
:ref:`:hide-lines-before<hide_lines_before>` and
|
||
:ref:`:hide-lines-after<hide_lines_after>` commands can be used to specify
|
||
the beginning and end of the time frame.
|
||
|
||
|
||
Log level
|
||
^^^^^^^^^
|
||
|
||
To hide messages below a certain log level, you can use the
|
||
:ref:`:set-min-log-level<set_min_log_level>` command.
|
||
|
||
.. _search_tables:
|
||
|
||
Search Tables
|
||
-------------
|
||
|
||
Search tables allow you to access arbitrary data in log messages through
|
||
SQLite virtual tables. If there is some data in a log message that you can
|
||
match with a regular expression, you can create a search-table that matches
|
||
that data and any capture groups will be plumbed through as columns in the
|
||
search table.
|
||
|
||
Creating a search table can be done interactively using the
|
||
:ref:`:create-search-table<create_search_table>` command or by adding it to
|
||
a :ref:`log format definition<log_formats>`. The main difference between
|
||
the two is that tables defined as part of a format will only search messages
|
||
from log files with that format and the tables will include log message
|
||
columns defined in that format. Whereas a table created with the command
|
||
will search messages from all different formats and no format-specific
|
||
columns will be included in the table.
|
||
|
||
.. _taking_notes:
|
||
|
||
Taking Notes
|
||
------------
|
||
|
||
A few of the columns in the log tables can be updated on a row-by-row basis to
|
||
allow you to take notes. The majority of the columns in a log table are
|
||
read-only since they are backed by the log files themselves. However, the
|
||
following columns can be changed by an :code:`UPDATE` statement:
|
||
|
||
* **log_part** - The "partition" the log message belongs to. This column can
|
||
also be changed by the :ref:`:partition-name<partition_name>` command.
|
||
* **log_mark** - Indicates whether the line has been bookmarked.
|
||
* **log_comment** - A free-form text field for storing commentary. This
|
||
column can also be changed by the :ref:`:comment<comment>` command.
|
||
* **log_tags** - A JSON list of tags associated with the log message. This
|
||
column can also be changed by the :ref:`:tag<tag>` command.
|
||
|
||
While these columns can be updated by through other means, using the SQL
|
||
interface allows you to make changes automatically and en masse. For example,
|
||
to bookmark all lines that have the text "something interesting" in the log
|
||
message body, you can execute:
|
||
|
||
.. code-block:: custsqlite
|
||
|
||
;UPDATE all_logs SET log_mark = 1 WHERE log_body LIKE '%something interesting%'
|
||
|
||
As a more advanced example of the power afforded by SQL and **lnav**'s virtual
|
||
tables, we will tag log messages where the IP address bound by dhclient has
|
||
changed. For example, if dhclient reports "bound to 10.0.0.1" initially and
|
||
then reports "bound to 10.0.0.2", we want to tag only the messages where the
|
||
IP address was different from the previous message. While this can be done
|
||
with a single SQL statement [#]_, we will break things down into a few steps for
|
||
this example. First, we will use the :ref:`:create-search-table<create_search_table>`
|
||
command to match the dhclient message and extract the IP address:
|
||
|
||
.. code-block:: lnav
|
||
|
||
:create-search-table dhclient_ip bound to (?<ip>[^ ]+)
|
||
|
||
The above command will create a new table named :code:`dhclient_ip` with the
|
||
standard log columns and an :code:`ip` column that contains the IP address.
|
||
Next, we will create a view over the :code:`dhclient_ip` table that returns
|
||
the log message line number, the IP address from the current row and the IP
|
||
address from the previous row:
|
||
|
||
.. code-block:: custsqlite
|
||
|
||
;CREATE VIEW IF NOT EXISTS dhclient_ip_changes AS SELECT log_line, ip, lag(ip) OVER (ORDER BY log_line) AS prev_ip FROM dhclient_ip
|
||
|
||
Finally, the following :code:`UPDATE` statement will concatenate the tag
|
||
"#ipchanged" onto the :code:`log_tags` column for any rows in the view where
|
||
the current IP is different from the previous IP:
|
||
|
||
.. code-block:: custsqlite
|
||
|
||
;UPDATE syslog_log SET log_tags = json_concat(log_tags, '#ipchanged') WHERE log_line IN (SELECT log_line FROM dhclient_ip_changes WHERE ip != prev_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
|
||
: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.
|
||
|
||
Sharing Sessions With Others
|
||
----------------------------
|
||
|
||
After setting up filters, bookmarks, and making notes, you might want to share
|
||
your work with others. If they have access to the same log files, you can
|
||
use the :ref:`:export-session-to<export_session_to>` command to write an
|
||
executable **lnav** script that will recreate the current session state. The
|
||
script contains various SQL statements and **lnav** commands that capture the
|
||
current state. So, you should feel free to modify the script or use it as a
|
||
reference to learn about more advanced uses of lnav.
|
||
|
||
The script will capture the file paths that were explicitly specified and
|
||
not the files that were actually opened. For example, if you specified
|
||
"/var/log" on the command line, the script will include
|
||
:code:`:open /var/log/*` and not an individual open for each file in that
|
||
directory.
|
||
|
||
Also, in order to support archives of log files, lnav will try to find the
|
||
directory where the archive was unpacked and use that as the base for the
|
||
:code:`:open` command. Currently, this is done by searching for the top
|
||
"README" file in the directory hierarchy containing the files [1]_. The
|
||
consumer of the session script can then set the :code:`LOG_DIR_0` (or 1, 2,
|
||
...) environment variable to change where the log files will be loaded from.
|
||
|
||
.. [1] It is assumed a log archive would have a descriptive README file.
|
||
Other heuristics may be added in the future.
|