allow changing the log level while the server is running

This commit is contained in:
Robin Appelman 2021-02-03 21:09:30 +01:00
Родитель de18fd6d0e
Коммит 31db8adf3f
10 изменённых файлов: 176 добавлений и 59 удалений

80
Cargo.lock сгенерированный
Просмотреть файл

@ -635,19 +635,6 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "event-listener"
version = "2.5.1"
@ -679,6 +666,22 @@ dependencies = [
"instant",
]
[[package]]
name = "flexi_logger"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab94b6ac8eb69f1496a6993f26f785b5fd6d99b7416023eb2a6175c0b242b1"
dependencies = [
"atty",
"chrono",
"glob",
"lazy_static",
"log",
"regex",
"thiserror",
"yansi",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -893,6 +896,12 @@ version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "gloo-timers"
version = "0.2.1"
@ -1033,15 +1042,6 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "hyper"
version = "0.13.9"
@ -1513,6 +1513,7 @@ dependencies = [
"ctrlc",
"dashmap",
"dotenv",
"flexi_logger",
"futures",
"http-auth-basic",
"log",
@ -1521,7 +1522,6 @@ dependencies = [
"parse-display",
"percent-encoding",
"php-literal-parser",
"pretty_env_logger",
"rand 0.7.3",
"redis",
"reqwest",
@ -1821,16 +1821,6 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [
"env_logger",
"log",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -2654,15 +2644,6 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "termion"
version = "1.5.5"
@ -3305,15 +3286,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@ -3345,6 +3317,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "yansi"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
[[package]]
name = "zeroize"
version = "1.1.1"

Просмотреть файл

@ -12,7 +12,6 @@ thiserror = "1.0.22"
warp = "0.2.5"
tokio = { version = "0.2", features = ["macros", "rt-threaded"] }
futures = "0.3"
pretty_env_logger = "0.4.0"
log = "0.4.11"
sqlx = { version = "0.4.1", features = ["runtime-tokio-rustls", "any", "macros", "mysql", "sqlite", "postgres"] }
dotenv = "0.15.0"
@ -28,6 +27,7 @@ percent-encoding = "2.1.0"
ctrlc = { version = "3.1.7", features = ["termination"] }
rand = "0.7.3"
ahash = "0.7.0"
flexi_logger = { version = "0.17", features = ["colors"] }
[dev-dependencies]
mini-redis = "0.2.0"

Просмотреть файл

@ -144,6 +144,29 @@ the push server is listening.
The app will automatically run some tests to verify that the push server is configured correctly.
### Logging
By default, the push server only logs warnings, you can temporarily change the log level with an occ command
```bash
occ notify_push:log <level>
```
Where level is `error`, `warn`, `info`, debug` or `trace`, or restore the log level to the previous value using
```bash
occ notify_push:log --restore
```
Alternatively you can set the log level of the push server in the `LOG` environment variable.
### Metrics
The push server can expose some basic metrics about the number of connected clients and the traffic flowing trough the server
by setting the `METRICS_PORT` environment variable.
Once set the metrics are available in a prometheus compatible format at `/metrics` on the configured port.
## Developing
As developer of a Nextcloud app or client you can use the `notify_push` app to receive real time notifications from the

Просмотреть файл

@ -23,5 +23,6 @@
<commands>
<command>OCA\NotifyPush\Command\Setup</command>
<command>OCA\NotifyPush\Command\SelfTest</command>
<command>OCA\NotifyPush\Command\Log</command>
</commands>
</info>

68
lib/Command/Log.php Normal file
Просмотреть файл

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\NotifyPush\Command;
use OC\Core\Command\Base;
use OCA\NotifyPush\Queue\IQueue;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class Log extends Base {
private $queue;
public function __construct(
IQueue $queue,
) {
parent::__construct();
$this->queue = $queue;
}
protected function configure() {
$this
->setName('notify_push:log')
->setDescription('Temporarily set the log level of the push server')
->addOption("restore", "r", InputOption::VALUE_NONE, "restore the log level to the previous value")
->addArgument("level", InputArgument::OPTIONAL, "the new log level to set");
parent::configure();
}
protected function execute(InputInterface $input, OutputInterface $output) {
$level = $input->getArgument("level");
if ($input->getOption("restore")) {
$output->writeln("restoring log level");
$this->queue->push("notify_config", "log_restore");
} elseif ($level) {
// by default dont touch the log level of the libraries
if (!strpos($level, "=") and $level !== "trace") {
$level = "notify_push=$level";
}
$this->queue->push("notify_config", ["log_spec" => $level]);
}
return 0;
}
}

Просмотреть файл

@ -97,7 +97,13 @@ pub async fn handle_user_socket(mut ws: WebSocket, app: Arc<App>, forwarded_for:
let _msg = match result {
Ok(msg) => msg,
Err(e) => {
log::warn!("websocket error: {}", e);
let formatted = e.to_string();
// hack while warp only has opaque error types
if formatted
!= "WebSocket protocol error: Connection reset without closing handshake"
{
log::warn!("websocket error: {}", e);
}
break;
}
};

Просмотреть файл

@ -41,6 +41,13 @@ pub struct PreAuth {
pub token: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Config {
LogSpec(String),
LogRestore,
}
#[derive(Debug, Deserialize)]
pub struct Custom {
pub user: UserId,
@ -65,6 +72,8 @@ pub enum Event {
PreAuth(PreAuth),
#[display("custom notification {0.message} for user {0.user}")]
Custom(Custom),
#[display("config update")]
Config(Config),
}
#[derive(Debug, Error)]
@ -104,6 +113,9 @@ impl TryFrom<Msg> for Event {
"notify_custom" => Ok(Event::Custom(serde_json::from_slice(
msg.get_payload_bytes(),
)?)),
"notify_config" => Ok(Event::Config(serde_json::from_slice(
msg.get_payload_bytes(),
)?)),
_ => Err(MessageDecodeError::UnsupportedEventType),
}
}
@ -126,6 +138,7 @@ pub async fn subscribe(
"notify_notification",
"notify_pre_auth",
"notify_custom",
"notify_config",
];
for channel in channels.iter() {
pubsub

Просмотреть файл

@ -9,6 +9,7 @@ pub use crate::user::UserId;
use ahash::RandomState;
use color_eyre::{eyre::WrapErr, Result};
use dashmap::DashMap;
use flexi_logger::LoggerHandle;
use futures::StreamExt;
use smallvec::alloc::sync::Arc;
use sqlx::AnyPool;
@ -16,6 +17,7 @@ use std::convert::Infallible;
use std::net::{IpAddr, SocketAddr};
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Instant;
use tokio::sync::Mutex;
use warp::filters::addr::remote;
use warp::Filter;
use warp_real_ip::get_forwarded_for;
@ -36,10 +38,11 @@ pub struct App {
pre_auth: DashMap<String, (Instant, UserId), RandomState>,
test_cookie: AtomicU32,
redis_url: String,
log_handle: Mutex<LoggerHandle>,
}
impl App {
pub async fn new(config: Config) -> Result<Self> {
pub async fn new(config: Config, log_handle: LoggerHandle) -> Result<Self> {
let connections = ActiveConnections::default();
let nc_client = nc::Client::new(&config.nextcloud_url)?;
let test_cookie = AtomicU32::new(0);
@ -57,10 +60,15 @@ impl App {
pre_auth,
storage_mapping,
redis_url,
log_handle: Mutex::new(log_handle),
})
}
pub async fn with_connection(connection: AnyPool, config: Config) -> Result<Self> {
pub async fn with_connection(
connection: AnyPool,
config: Config,
log_handle: LoggerHandle,
) -> Result<Self> {
let connections = ActiveConnections::default();
let nc_client = nc::Client::new(&config.nextcloud_url)?;
let test_cookie = AtomicU32::new(0);
@ -78,6 +86,7 @@ impl App {
pre_auth,
storage_mapping,
redis_url,
log_handle: Mutex::new(log_handle),
})
}
@ -139,6 +148,14 @@ impl App {
.send_to_user(&user, MessageType::Custom(message))
.await;
}
Event::Config(event::Config::LogSpec(spec)) => {
self.log_handle.lock().await.parse_and_push_temp_spec(&spec);
log::info!("Set log level to {}", spec);
}
Event::Config(event::Config::LogRestore) => {
self.log_handle.lock().await.pop_temp_spec();
log::info!("Restored log level");
}
}
}
}

Просмотреть файл

@ -1,4 +1,5 @@
use color_eyre::{eyre::WrapErr, Result};
use flexi_logger::{colored_detailed_format, LogTarget, Logger};
use notify_push::config::Config;
use notify_push::message::DEBOUNCE_ENABLE;
use notify_push::metrics::serve_metrics;
@ -10,7 +11,12 @@ use tokio::time::Duration;
#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;
pretty_env_logger::init();
let level = dotenv::var("LOG").ok();
let level = level.as_ref().map(String::as_str).unwrap_or("warn");
let log_handle = Logger::with_str(level)
.log_target(LogTarget::StdOut)
.format(colored_detailed_format)
.start()?;
let _ = dotenv::dotenv();
ctrlc::set_handler(move || {
@ -41,7 +47,7 @@ async fn main() -> Result<()> {
log::trace!("Running with config: {:?} on port {}", config, port);
let app = Arc::new(App::new(config).await?);
let app = Arc::new(App::new(config, log_handle).await?);
app.self_test().await?;
tokio::task::spawn(serve(app.clone(), port));

Просмотреть файл

@ -1,10 +1,12 @@
use dashmap::DashMap;
use flexi_logger::{Logger, LoggerHandle};
use futures::future::select;
use futures::{pin_mut, FutureExt};
use futures::{SinkExt, StreamExt};
use http_auth_basic::Credentials;
use notify_push::config::Config;
use notify_push::{listen, serve, App};
use once_cell::sync::Lazy;
use redis::AsyncCommands;
use smallvec::alloc::sync::Arc;
use sqlx::AnyPool;
@ -44,9 +46,10 @@ struct Services {
db: AnyPool,
}
static LOG_HANDLE: Lazy<LoggerHandle> = Lazy::new(|| Logger::with_str("").start().unwrap());
impl Services {
pub async fn new() -> Self {
let _ = pretty_env_logger::try_init();
let redis_tcp = listen_available_port()
.await
.expect("Can't find open port for redis");
@ -145,7 +148,9 @@ impl Services {
async fn app(&self) -> App {
let config = self.config();
App::with_connection(self.db.clone(), config).await.unwrap()
App::with_connection(self.db.clone(), config, LOG_HANDLE.clone())
.await
.unwrap()
}
async fn spawn_server(&self) -> ServerHandle {