mirror of
https://github.com/tstack/lnav.git
synced 2024-10-04 00:31:31 +03:00
[crashd] support uploading crash logs
This commit is contained in:
parent
fc79d703b1
commit
d4cdbc1f49
19
crashd/Dockerfile
Normal file
19
crashd/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG PYTHON_VERSION=3.12.3
|
||||
|
||||
FROM python:${PYTHON_VERSION}-slim
|
||||
|
||||
LABEL fly_launch_runtime="flask"
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcc build-essential
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0", "--port=8080"]
|
1
crashd/Procfile
Normal file
1
crashd/Procfile
Normal file
@ -0,0 +1 @@
|
||||
web: gunicorn app:app
|
148
crashd/app.py
Normal file
148
crashd/app.py
Normal file
@ -0,0 +1,148 @@
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
from flask import Flask, request, send_file, after_this_request
|
||||
import os
|
||||
import glob
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import spookyhash
|
||||
|
||||
app = Flask(__name__)
|
||||
app.wsgi_app = ProxyFix(
|
||||
app.wsgi_app, x_for=2, x_proto=2, x_host=2, x_prefix=2
|
||||
)
|
||||
|
||||
ROOT_PAGE = """
|
||||
<html>
|
||||
<head>
|
||||
<title>lnav crash upload site</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
You can help improve <b>lnav</b> by uploading your crash logs by running:
|
||||
<pre>
|
||||
lnav -m crash upload
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
# Function to check free space on filesystem
|
||||
def check_free_space():
|
||||
statvfs = os.statvfs('/logs')
|
||||
# Calculate free space in MB
|
||||
free_space_mb = (statvfs.f_bsize * statvfs.f_bavail) / (1024 * 1024)
|
||||
return free_space_mb
|
||||
|
||||
|
||||
@app.route('/crash', methods=['POST'])
|
||||
def crash_handler():
|
||||
# Check free space on filesystem
|
||||
free_space_mb = check_free_space()
|
||||
if free_space_mb < 100:
|
||||
return 'Insufficient free space on the filesystem!\n', 500
|
||||
|
||||
# Retrieve the secret value from the environment variable
|
||||
lnav_secret_env = os.environ.get('LNAV_SECRET')
|
||||
|
||||
# Check if header 'lnav-secret' exists and has the correct value
|
||||
if request.headers.get('lnav-secret') == lnav_secret_env:
|
||||
# Check if content length is provided
|
||||
if 'Content-Length' not in request.headers:
|
||||
return 'Content length header is missing!', 400
|
||||
|
||||
# Get the content length
|
||||
content_length = int(request.headers['Content-Length'])
|
||||
|
||||
# Check if content length is zero
|
||||
if content_length == 0:
|
||||
return 'Empty request body!', 400
|
||||
|
||||
# Check if content length exceeds 10MB
|
||||
if content_length > 10 * 1024 * 1024: # 10MB limit
|
||||
return 'Content size exceeds the limit of 10MB!', 413
|
||||
|
||||
# Get the content from the request body
|
||||
content = request.data
|
||||
|
||||
nonce = request.headers.get('X-lnav-nonce', '')
|
||||
crash_hash = request.headers.get('X-lnav-hash', '')
|
||||
if not crash_hash.startswith("0000"):
|
||||
return "Invalid proof of work hash", 401
|
||||
|
||||
sh = spookyhash.Hash128()
|
||||
sh.update(nonce.encode('utf-8'))
|
||||
sh.update(content)
|
||||
verify_hash = sh.hexdigest()
|
||||
if verify_hash != crash_hash:
|
||||
return "Proof of work hash does not match", 401
|
||||
|
||||
# Generate a unique ID
|
||||
unique_id = str(uuid.uuid4())
|
||||
|
||||
# Get the current time
|
||||
current_time = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
||||
|
||||
# Construct the file name with current time and unique ID
|
||||
file_name = f'crash_log_{current_time}_{unique_id}.txt'
|
||||
full_path = os.path.join("/logs", file_name)
|
||||
|
||||
# Save the content to the file in the current directory
|
||||
with open(full_path, 'wb') as f:
|
||||
f.write(content)
|
||||
|
||||
return 'Data saved successfully!', 200
|
||||
else:
|
||||
return 'Unauthorized access!', 401
|
||||
|
||||
|
||||
@app.route('/download_crashes', methods=['GET'])
|
||||
def download_crashes():
|
||||
# Retrieve the secret value for downloading from the environment variable
|
||||
lnav_download_secret_env = os.environ.get('LNAV_DOWNLOAD_SECRET')
|
||||
|
||||
# Check if header 'lnav-secret' exists and has the correct value for downloading
|
||||
if request.headers.get('lnav-secret') == lnav_download_secret_env:
|
||||
# Get all the files in the current directory
|
||||
crash_files = glob.glob("/logs/crash_log_*")
|
||||
|
||||
# Generate a unique ID for the zip file
|
||||
zip_id = str(uuid.uuid4())
|
||||
|
||||
# Construct the zip file name
|
||||
zip_file_name = f'crash_archive_{zip_id}.zip'
|
||||
|
||||
# Create a new zip file
|
||||
with zipfile.ZipFile(zip_file_name, 'w') as zipf:
|
||||
# Add each crash file to the zip file
|
||||
for crash_file in crash_files:
|
||||
zipf.write(crash_file)
|
||||
|
||||
# Delete the crash files
|
||||
for crash_file in crash_files:
|
||||
os.remove(crash_file)
|
||||
|
||||
@after_this_request
|
||||
def remove_zip(response):
|
||||
try:
|
||||
os.remove(zip_file_name)
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error removing zip file: {e}")
|
||||
return response
|
||||
|
||||
# Send the zip file as a response
|
||||
return send_file(zip_file_name, as_attachment=True, download_name=zip_file_name)
|
||||
|
||||
else:
|
||||
return 'Unauthorized access!\n', 401
|
||||
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def root_page():
|
||||
return ROOT_PAGE, 200
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run the Flask app
|
||||
app.run()
|
26
crashd/fly.toml
Normal file
26
crashd/fly.toml
Normal file
@ -0,0 +1,26 @@
|
||||
# fly.toml app configuration file generated for crashd on 2024-05-24T15:13:43-07:00
|
||||
#
|
||||
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
|
||||
#
|
||||
|
||||
app = 'crashd'
|
||||
primary_region = 'sea'
|
||||
|
||||
[build]
|
||||
|
||||
[mounts]
|
||||
source = "crash_logs"
|
||||
destination = "/logs"
|
||||
|
||||
[http_service]
|
||||
internal_port = 8080
|
||||
force_https = true
|
||||
auto_stop_machines = true
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
processes = ['app']
|
||||
|
||||
[[vm]]
|
||||
memory = '1gb'
|
||||
cpu_kind = 'shared'
|
||||
cpus = 1
|
10
crashd/requirements.txt
Normal file
10
crashd/requirements.txt
Normal file
@ -0,0 +1,10 @@
|
||||
blinker==1.8.2
|
||||
click==8.1.7
|
||||
Flask==3.0.3
|
||||
gunicorn==22.0.0
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.4
|
||||
MarkupSafe==2.1.5
|
||||
packaging==24.0
|
||||
spookyhash==2.1.0
|
||||
Werkzeug==3.0.3
|
@ -423,6 +423,7 @@ add_library(
|
||||
collation-functions.cc
|
||||
column_namer.cc
|
||||
command_executor.cc
|
||||
crashd.client.cc
|
||||
curl_looper.cc
|
||||
db_sub_source.cc
|
||||
dump_internals.cc
|
||||
@ -543,6 +544,7 @@ add_library(
|
||||
byte_array.hh
|
||||
command_executor.hh
|
||||
column_namer.hh
|
||||
crashd.client.hh
|
||||
curl_looper.hh
|
||||
doc_status_source.hh
|
||||
dump_internals.hh
|
||||
|
@ -227,6 +227,7 @@ noinst_HEADERS = \
|
||||
byte_array.hh \
|
||||
column_namer.hh \
|
||||
command_executor.hh \
|
||||
crashd.client.hh \
|
||||
curl_looper.hh \
|
||||
data_scanner.hh \
|
||||
data_scanner_re.re \
|
||||
@ -426,6 +427,7 @@ libdiag_a_SOURCES = \
|
||||
collation-functions.cc \
|
||||
column_namer.cc \
|
||||
command_executor.cc \
|
||||
crashd.client.cc \
|
||||
curl_looper.cc \
|
||||
db_sub_source.cc \
|
||||
document.sections.cc \
|
||||
|
@ -85,25 +85,14 @@
|
||||
# error "SysV or X/Open-compatible Curses header file required"
|
||||
#endif
|
||||
|
||||
#include "ansi_scrubber.hh"
|
||||
#include "auto_mem.hh"
|
||||
#include "enum_util.hh"
|
||||
#include "lnav_log.hh"
|
||||
#include "opt_util.hh"
|
||||
|
||||
static const size_t BUFFER_SIZE = 256 * 1024;
|
||||
static const size_t MAX_LOG_LINE_SIZE = 2 * 1024;
|
||||
|
||||
static const char* CRASH_MSG
|
||||
= "\n"
|
||||
"\n"
|
||||
"==== GURU MEDITATION ====\n"
|
||||
"Unfortunately, lnav has crashed, sorry for the inconvenience.\n"
|
||||
"\n"
|
||||
"You can help improve lnav by sending the following file "
|
||||
"to " PACKAGE_BUGREPORT
|
||||
" :\n"
|
||||
" %s\n"
|
||||
"=========================\n";
|
||||
static constexpr size_t BUFFER_SIZE = 256 * 1024;
|
||||
static constexpr size_t MAX_LOG_LINE_SIZE = 2 * 1024;
|
||||
|
||||
std::optional<FILE*> lnav_log_file;
|
||||
lnav_log_level_t lnav_log_level = lnav_log_level_t::DEBUG;
|
||||
@ -546,7 +535,29 @@ sigabrt(int sig, siginfo_t* info, void* ctx)
|
||||
tcsetattr(STDOUT_FILENO, TCSAFLUSH, termios);
|
||||
dup2(STDOUT_FILENO, STDERR_FILENO);
|
||||
};
|
||||
fprintf(stderr, CRASH_MSG, crash_path);
|
||||
fmt::print(R"(
|
||||
|
||||
{red_start}==== GURU MEDITATION ===={norm}
|
||||
|
||||
Unfortunately, lnav has crashed, sorry for the inconvenience.
|
||||
|
||||
You can help improve lnav by executing the following command
|
||||
to upload the crash logs to https://crash.lnav.org:
|
||||
|
||||
{green_start}${norm} {bold_start}lnav -m crash upload{norm}
|
||||
|
||||
Or, you can send the following file to {PACKAGE_BUGREPORT}:
|
||||
|
||||
{crash_path}
|
||||
|
||||
{red_start}========================={norm}
|
||||
)",
|
||||
fmt::arg("red_start", ANSI_COLOR(1)),
|
||||
fmt::arg("green_start", ANSI_COLOR(2)),
|
||||
fmt::arg("bold_start", ANSI_BOLD_START),
|
||||
fmt::arg("norm", ANSI_NORM),
|
||||
fmt::arg("PACKAGE_BUGREPORT", PACKAGE_BUGREPORT),
|
||||
fmt::arg("crash_path", crash_path));
|
||||
|
||||
#ifndef ATTACH_ON_SIGNAL
|
||||
if (isatty(STDIN_FILENO)) {
|
||||
|
132
src/crashd.client.cc
Normal file
132
src/crashd.client.cc
Normal file
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) 2024, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "crashd.client.hh"
|
||||
|
||||
#include "base/fs_util.hh"
|
||||
#include "curl_looper.hh"
|
||||
#include "hasher.hh"
|
||||
|
||||
namespace lnav::crashd::client {
|
||||
|
||||
static constexpr auto USER_AGENT = "lnav/" PACKAGE_VERSION;
|
||||
static constexpr auto SECRET = "2F40374C-25CE-4472-883F-CBBA4660A586";
|
||||
|
||||
static int
|
||||
progress_tramp(
|
||||
void* clientp, double dltotal, double dlnow, double ultotal, double ulnow)
|
||||
{
|
||||
progress_callback& cb = *static_cast<progress_callback*>(clientp);
|
||||
|
||||
switch (cb(dltotal, dlnow, ultotal, ulnow)) {
|
||||
case progress_result_t::ok:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Result<void, lnav::console::user_message>
|
||||
upload(const std::filesystem::path& log_path, progress_callback cb)
|
||||
{
|
||||
const auto read_res = lnav::filesystem::read_file(log_path);
|
||||
if (read_res.isErr()) {
|
||||
return Err(lnav::console::user_message::error(
|
||||
attr_line_t("unable to read crash log: ")
|
||||
.append(lnav::roles::file(log_path)))
|
||||
.with_reason(read_res.unwrapErr()));
|
||||
}
|
||||
|
||||
const auto log_content = read_res.unwrap();
|
||||
std::string hash_str;
|
||||
auto nonce = 0;
|
||||
|
||||
{
|
||||
while (true) {
|
||||
auto ha = hasher();
|
||||
|
||||
ha.update(fmt::to_string(nonce));
|
||||
ha.update(log_content);
|
||||
|
||||
hash_str = ha.to_string();
|
||||
if (startswith(hash_str, "0000")) {
|
||||
break;
|
||||
}
|
||||
nonce += 1;
|
||||
}
|
||||
}
|
||||
|
||||
curl_request cr("https://crash.lnav.org/crash");
|
||||
|
||||
curl_easy_setopt(cr, CURLOPT_VERBOSE, 0);
|
||||
curl_easy_setopt(cr, CURLOPT_URL, cr.get_name().c_str());
|
||||
curl_easy_setopt(cr, CURLOPT_POST, 1);
|
||||
curl_easy_setopt(cr, CURLOPT_POSTFIELDS, log_content.c_str());
|
||||
curl_easy_setopt(cr, CURLOPT_POSTFIELDSIZE, log_content.size());
|
||||
curl_easy_setopt(cr, CURLOPT_USERAGENT, USER_AGENT);
|
||||
curl_easy_setopt(cr, CURLOPT_XFERINFODATA, &cb);
|
||||
curl_easy_setopt(cr, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(cr, CURLOPT_XFERINFOFUNCTION, progress_tramp);
|
||||
|
||||
const auto secret_header
|
||||
= fmt::format(FMT_STRING("lnav-secret: {}"), SECRET);
|
||||
const auto nonce_header
|
||||
= fmt::format(FMT_STRING("X-lnav-nonce: {}"), nonce);
|
||||
const auto hash_header
|
||||
= fmt::format(FMT_STRING("X-lnav-hash: {}"), hash_str);
|
||||
|
||||
auto_mem<curl_slist> list(curl_slist_free_all);
|
||||
|
||||
list = curl_slist_append(list, "Content-Type: text/plain");
|
||||
list = curl_slist_append(list, secret_header.c_str());
|
||||
list = curl_slist_append(list, nonce_header.c_str());
|
||||
list = curl_slist_append(list, hash_header.c_str());
|
||||
|
||||
curl_easy_setopt(cr, CURLOPT_HTTPHEADER, list.in());
|
||||
|
||||
const auto perform_res = cr.perform();
|
||||
if (perform_res.isErr()) {
|
||||
return Err(
|
||||
lnav::console::user_message::error("unable to upload crash log")
|
||||
.with_reason(curl_easy_strerror(perform_res.unwrapErr())));
|
||||
}
|
||||
|
||||
const auto response = perform_res.unwrap();
|
||||
if (cr.get_response_code() != 200) {
|
||||
return Err(lnav::console::user_message::error(
|
||||
attr_line_t("server rejected crash log: ")
|
||||
.append(lnav::roles::file(log_path)))
|
||||
.with_reason(response));
|
||||
}
|
||||
log_info("crashd response: %s", response.c_str());
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
} // namespace lnav::crashd::client
|
54
src/crashd.client.hh
Normal file
54
src/crashd.client.hh
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2024, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef lnav_crashd_client_hh
|
||||
#define lnav_crashd_client_hh
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
|
||||
#include "base/lnav.console.hh"
|
||||
#include "base/result.h"
|
||||
|
||||
namespace lnav::crashd::client {
|
||||
|
||||
enum class progress_result_t {
|
||||
ok,
|
||||
abort,
|
||||
};
|
||||
|
||||
using progress_callback = std::function<progress_result_t(
|
||||
double dltotal, double dlnow, double ultotal, double ulnow)>;
|
||||
|
||||
Result<void, lnav::console::user_message> upload(
|
||||
const std::filesystem::path& log_path, progress_callback callback);
|
||||
|
||||
} // namespace lnav::crashd::client
|
||||
|
||||
#endif
|
@ -174,6 +174,27 @@ curl_request::string_cb(void* data, size_t size, size_t nmemb, void* userp)
|
||||
return realsize;
|
||||
}
|
||||
|
||||
curl_request::
|
||||
curl_request(std::string name)
|
||||
: cr_name(std::move(name)), cr_handle(curl_easy_cleanup)
|
||||
{
|
||||
this->cr_handle.reset(curl_easy_init());
|
||||
curl_easy_setopt(this->cr_handle, CURLOPT_NOSIGNAL, 1);
|
||||
curl_easy_setopt(
|
||||
this->cr_handle, CURLOPT_ERRORBUFFER, this->cr_error_buffer);
|
||||
curl_easy_setopt(this->cr_handle, CURLOPT_DEBUGFUNCTION, debug_cb);
|
||||
curl_easy_setopt(this->cr_handle, CURLOPT_DEBUGDATA, this);
|
||||
curl_easy_setopt(this->cr_handle, CURLOPT_VERBOSE, 1);
|
||||
if (getenv("SSH_AUTH_SOCK") != nullptr) {
|
||||
curl_easy_setopt(this->cr_handle,
|
||||
CURLOPT_SSH_AUTH_TYPES,
|
||||
# ifdef CURLSSH_AUTH_AGENT
|
||||
CURLSSH_AUTH_AGENT |
|
||||
# endif
|
||||
CURLSSH_AUTH_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
long
|
||||
curl_request::complete(CURLcode result)
|
||||
{
|
||||
@ -191,6 +212,22 @@ curl_request::complete(CURLcode result)
|
||||
return -1;
|
||||
}
|
||||
|
||||
Result<std::string, CURLcode>
|
||||
curl_request::perform() const
|
||||
{
|
||||
std::string response;
|
||||
|
||||
curl_easy_setopt(this->get_handle(), CURLOPT_WRITEFUNCTION, string_cb);
|
||||
curl_easy_setopt(this->get_handle(), CURLOPT_WRITEDATA, &response);
|
||||
|
||||
auto rc = curl_easy_perform(this->get_handle());
|
||||
if (rc == CURLE_OK) {
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
return Err(rc);
|
||||
}
|
||||
|
||||
curl_looper::
|
||||
curl_looper()
|
||||
: cl_curl_multi(curl_multi_cleanup)
|
||||
|
@ -74,25 +74,7 @@ public:
|
||||
|
||||
class curl_request {
|
||||
public:
|
||||
curl_request(std::string name)
|
||||
: cr_name(std::move(name)), cr_handle(curl_easy_cleanup)
|
||||
{
|
||||
this->cr_handle.reset(curl_easy_init());
|
||||
curl_easy_setopt(this->cr_handle, CURLOPT_NOSIGNAL, 1);
|
||||
curl_easy_setopt(
|
||||
this->cr_handle, CURLOPT_ERRORBUFFER, this->cr_error_buffer);
|
||||
curl_easy_setopt(this->cr_handle, CURLOPT_DEBUGFUNCTION, debug_cb);
|
||||
curl_easy_setopt(this->cr_handle, CURLOPT_DEBUGDATA, this);
|
||||
curl_easy_setopt(this->cr_handle, CURLOPT_VERBOSE, 1);
|
||||
if (getenv("SSH_AUTH_SOCK") != nullptr) {
|
||||
curl_easy_setopt(this->cr_handle,
|
||||
CURLOPT_SSH_AUTH_TYPES,
|
||||
# ifdef CURLSSH_AUTH_AGENT
|
||||
CURLSSH_AUTH_AGENT |
|
||||
# endif
|
||||
CURLSSH_AUTH_PASSWORD);
|
||||
}
|
||||
}
|
||||
explicit curl_request(std::string name);
|
||||
|
||||
curl_request(const curl_request&) = delete;
|
||||
curl_request(curl_request&&) = delete;
|
||||
@ -114,20 +96,7 @@ public:
|
||||
|
||||
virtual long complete(CURLcode result);
|
||||
|
||||
Result<std::string, CURLcode> perform()
|
||||
{
|
||||
std::string response;
|
||||
|
||||
curl_easy_setopt(this->get_handle(), CURLOPT_WRITEFUNCTION, string_cb);
|
||||
curl_easy_setopt(this->get_handle(), CURLOPT_WRITEDATA, &response);
|
||||
|
||||
auto rc = curl_easy_perform(this->get_handle());
|
||||
if (rc == CURLE_OK) {
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
return Err(rc);
|
||||
}
|
||||
Result<std::string, CURLcode> perform() const;
|
||||
|
||||
long get_response_code() const
|
||||
{
|
||||
|
@ -29,6 +29,8 @@
|
||||
|
||||
#include "lnav.management_cli.hh"
|
||||
|
||||
#include <glob.h>
|
||||
|
||||
#include "base/fs_util.hh"
|
||||
#include "base/humanize.hh"
|
||||
#include "base/humanize.time.hh"
|
||||
@ -36,6 +38,7 @@
|
||||
#include "base/paths.hh"
|
||||
#include "base/result.h"
|
||||
#include "base/string_util.hh"
|
||||
#include "crashd.client.hh"
|
||||
#include "file_options.hh"
|
||||
#include "fmt/chrono.h"
|
||||
#include "fmt/format.h"
|
||||
@ -1109,11 +1112,100 @@ struct subcmd_regex101_t {
|
||||
}
|
||||
};
|
||||
|
||||
struct subcmd_crash_t {
|
||||
using action_t = std::function<perform_result_t(const subcmd_crash_t&)>;
|
||||
|
||||
CLI::App* sc_app{nullptr};
|
||||
action_t sc_action;
|
||||
|
||||
subcmd_crash_t& set_action(action_t act)
|
||||
{
|
||||
if (!this->sc_action) {
|
||||
this->sc_action = std::move(act);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
static perform_result_t default_action(const subcmd_crash_t& sc)
|
||||
{
|
||||
auto um = console::user_message::error(
|
||||
"expecting an operation related to crash logs");
|
||||
um.with_help(
|
||||
sc.sc_app->get_subcommands({})
|
||||
| lnav::itertools::fold(
|
||||
subcmd_reducer, attr_line_t{"the available operations are:"}));
|
||||
|
||||
return {um};
|
||||
}
|
||||
|
||||
static perform_result_t upload_action(const subcmd_crash_t&)
|
||||
{
|
||||
static constexpr char SPINNER_CHARS[] = "-\\|/";
|
||||
constexpr size_t SPINNER_SIZE = sizeof(SPINNER_CHARS) - 1;
|
||||
|
||||
static_root_mem<glob_t, globfree> gl;
|
||||
const auto path = lnav::paths::dotlnav() / "crash" / "crash-*.log";
|
||||
perform_result_t retval;
|
||||
|
||||
auto glob_rc = glob(path.c_str(), 0, nullptr, gl.inout());
|
||||
if (glob_rc == GLOB_NOMATCH) {
|
||||
auto um = console::user_message::info("no crash logs to upload");
|
||||
return {um};
|
||||
}
|
||||
if (glob_rc != 0) {
|
||||
auto um = console::user_message::error("unable to find crash logs");
|
||||
return {um};
|
||||
}
|
||||
|
||||
for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
|
||||
auto crash_file = std::filesystem::path(gl->gl_pathv[lpc]);
|
||||
int spinner_index = 0;
|
||||
|
||||
log_info("uploading crash log: %s", crash_file.c_str());
|
||||
printf("~");
|
||||
fflush(stdout);
|
||||
auto upload_res = crashd::client::upload(
|
||||
crash_file,
|
||||
[&spinner_index](double dltotal,
|
||||
double dlnow,
|
||||
double ultotal,
|
||||
double ulnow) {
|
||||
printf("\b%c", SPINNER_CHARS[spinner_index % SPINNER_SIZE]);
|
||||
spinner_index += 1;
|
||||
fflush(stdout);
|
||||
return crashd::client::progress_result_t::ok;
|
||||
});
|
||||
if (spinner_index > 0) {
|
||||
printf("\b");
|
||||
}
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
if (upload_res.isErr()) {
|
||||
retval.push_back(upload_res.unwrapErr());
|
||||
} else {
|
||||
std::error_code ec;
|
||||
|
||||
std::filesystem::remove(crash_file, ec);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
auto um = console::user_message::ok(
|
||||
attr_line_t("uploaded ")
|
||||
.append(lnav::roles::number(fmt::to_string(gl->gl_pathc)))
|
||||
.append(" crash logs, thank you!"));
|
||||
retval.push_back(um);
|
||||
|
||||
return retval;
|
||||
}
|
||||
};
|
||||
|
||||
using operations_v = mapbox::util::variant<no_subcmd_t,
|
||||
subcmd_config_t,
|
||||
subcmd_format_t,
|
||||
subcmd_piper_t,
|
||||
subcmd_regex101_t>;
|
||||
subcmd_regex101_t,
|
||||
subcmd_crash_t>;
|
||||
|
||||
class operations {
|
||||
public:
|
||||
@ -1135,6 +1227,7 @@ describe_cli(CLI::App& app, int argc, char* argv[])
|
||||
subcmd_format_t format_args;
|
||||
subcmd_piper_t piper_args;
|
||||
subcmd_regex101_t regex101_args;
|
||||
subcmd_crash_t crash_args;
|
||||
|
||||
{
|
||||
auto* subcmd_config
|
||||
@ -1336,6 +1429,22 @@ describe_cli(CLI::App& app, int argc, char* argv[])
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto* subcmd_crash
|
||||
= app.add_subcommand("crash", "manage crash logs")->callback([&]() {
|
||||
crash_args.set_action(subcmd_crash_t::default_action);
|
||||
retval->o_ops = crash_args;
|
||||
});
|
||||
crash_args.sc_app = subcmd_crash;
|
||||
|
||||
{
|
||||
subcmd_crash->add_subcommand("upload", "upload crash logs")
|
||||
->callback([&]() {
|
||||
crash_args.set_action(subcmd_crash_t::upload_action);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.parse(argc, argv);
|
||||
|
||||
return retval;
|
||||
@ -1358,7 +1467,8 @@ perform(std::shared_ptr<operations> opts)
|
||||
[](const subcmd_config_t& sc) { return sc.sc_action(sc); },
|
||||
[](const subcmd_format_t& sf) { return sf.sf_action(sf); },
|
||||
[](const subcmd_piper_t& sp) { return sp.sp_action(sp); },
|
||||
[](const subcmd_regex101_t& sr) { return sr.sr_action(sr); });
|
||||
[](const subcmd_regex101_t& sr) { return sr.sr_action(sr); },
|
||||
[](const subcmd_crash_t& sc) { return sc.sc_action(sc); });
|
||||
}
|
||||
|
||||
} // namespace management
|
||||
|
@ -7042,6 +7042,18 @@ static std::unordered_map<char const*, std::vector<char const*>> aliases = {
|
||||
{"write-table-to", {"write-cols-to"}},
|
||||
};
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
com_crash(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
{
|
||||
if (args.empty()) {
|
||||
} else if (!ec.ec_dry_run) {
|
||||
int* nums = nullptr;
|
||||
|
||||
return ec.make_error(FMT_STRING("oops... {}"), nums[0]);
|
||||
}
|
||||
return Ok(std::string());
|
||||
}
|
||||
|
||||
void
|
||||
init_lnav_commands(readline_context::command_map_t& cmd_map)
|
||||
{
|
||||
@ -7064,10 +7076,12 @@ init_lnav_commands(readline_context::command_map_t& cmd_map)
|
||||
}
|
||||
if (getenv("lnav_test") != nullptr) {
|
||||
static readline_context::command_t shexec(com_shexec),
|
||||
poll_now(com_poll_now), test_comment(com_test_comment);
|
||||
poll_now(com_poll_now), test_comment(com_test_comment),
|
||||
crasher(com_crash);
|
||||
|
||||
cmd_map["shexec"] = &shexec;
|
||||
cmd_map["poll-now"] = &poll_now;
|
||||
cmd_map["test-comment"] = &test_comment;
|
||||
cmd_map["crash"] = &crasher;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user