feat: implementation for domo_node
This commit is contained in:
parent
405cf8b368
commit
3365ca09a5
8 changed files with 445 additions and 72 deletions
160
domo_node/Cargo.lock
generated
160
domo_node/Cargo.lock
generated
|
@ -80,6 +80,8 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"domo_proto",
|
||||
"fern",
|
||||
"futures",
|
||||
"hex",
|
||||
"humantime",
|
||||
"log",
|
||||
"serde",
|
||||
|
@ -92,6 +94,7 @@ name = "domo_proto"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -109,6 +112,106 @@ dependencies = [
|
|||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.0"
|
||||
|
@ -127,6 +230,12 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
|
@ -239,6 +348,18 @@ version = "0.2.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.67"
|
||||
|
@ -257,6 +378,36 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
|
@ -316,6 +467,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.1"
|
||||
|
|
|
@ -8,10 +8,12 @@ edition = "2021"
|
|||
[dependencies]
|
||||
fern = "0.6.2"
|
||||
log = "0.4.20"
|
||||
serde = "1.0.188"
|
||||
serde = { version = "1.0.188", features = ["serde_derive"] }
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
toml = "0.8.2"
|
||||
humantime = "2.1.0"
|
||||
futures = "0.3.28"
|
||||
hex = "0.4.3"
|
||||
|
||||
[dependencies.domo_proto]
|
||||
path = "../domo_proto"
|
||||
|
|
2
domo_node/config.toml
Normal file
2
domo_node/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[node.type.relay]
|
||||
master_address = "0.0.0.0:2000"
|
48
domo_node/src/config/log.rs
Normal file
48
domo_node/src/config/log.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use log::LevelFilter;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum LogLevel {
|
||||
#[serde(rename = "trace")]
|
||||
Trace,
|
||||
#[serde(rename = "debug")]
|
||||
Debug,
|
||||
#[serde(rename = "Info")]
|
||||
Info,
|
||||
#[serde(rename = "warn")]
|
||||
Warn,
|
||||
#[serde(rename = "error")]
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Into<LevelFilter> for LogLevel {
|
||||
fn into(self) -> LevelFilter {
|
||||
match self {
|
||||
LogLevel::Trace => LevelFilter::Trace,
|
||||
LogLevel::Debug => LevelFilter::Debug,
|
||||
LogLevel::Info => LevelFilter::Info,
|
||||
LogLevel::Warn => LevelFilter::Warn,
|
||||
LogLevel::Error => LevelFilter::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct LogConfig {
|
||||
pub stdout_level: LogLevel,
|
||||
pub file_level: LogLevel,
|
||||
}
|
||||
|
||||
impl Default for LogConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// Linter says it's not fine but it is.
|
||||
#[cfg(not(debug_assertions))]
|
||||
stdout_level: LogLevel::Info,
|
||||
#[cfg(debug_assertions)]
|
||||
stdout_level: LogLevel::Trace,
|
||||
|
||||
file_level: LogLevel::Trace,
|
||||
}
|
||||
}
|
||||
}
|
30
domo_node/src/config/mod.rs
Normal file
30
domo_node/src/config/mod.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use log::LogConfig;
|
||||
use node::NodeConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, io};
|
||||
use ::log::trace;
|
||||
|
||||
pub mod log;
|
||||
pub mod node;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub node: NodeConfig,
|
||||
pub log: LogConfig,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_path<S: Into<String>>(path: S) -> io::Result<Self> {
|
||||
let path = path.into().clone();
|
||||
trace!("importing config from {}...", path);
|
||||
let s = fs::read_to_string(path.clone())?;
|
||||
let c: Config = toml::from_str(s.as_str()).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("{} has invalid data", path),
|
||||
)
|
||||
})?;
|
||||
Ok(c)
|
||||
}
|
||||
}
|
39
domo_node/src/config/node.rs
Normal file
39
domo_node/src/config/node.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum NodeType {
|
||||
#[serde(rename = "master")]
|
||||
Master,
|
||||
#[serde(rename = "relay")]
|
||||
Relay {
|
||||
master_address: String
|
||||
},
|
||||
#[serde(rename = "subnet")]
|
||||
Subnet {
|
||||
master_address: String
|
||||
},
|
||||
}
|
||||
|
||||
impl Display for NodeType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
NodeType::Master => write!(f, "master"),
|
||||
NodeType::Relay { .. } => write!(f, "relay"),
|
||||
NodeType::Subnet { .. } => write!(f, "subnet"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NodeType {
|
||||
fn default() -> Self {
|
||||
Self::Master
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct NodeConfig {
|
||||
#[serde(rename = "type")]
|
||||
pub node_type: NodeType,
|
||||
pub device_id: Option<String>
|
||||
}
|
77
domo_node/src/connection.rs
Normal file
77
domo_node/src/connection.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use tokio::net::{TcpSocket, TcpStream};
|
||||
use std::io;
|
||||
use domo_proto::packet::Packet;
|
||||
use std::net::SocketAddr;
|
||||
use log::trace;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use domo_proto::packet::identifier::Identifier;
|
||||
use domo_proto::packet::packet_data::PacketData;
|
||||
|
||||
pub struct Client {
|
||||
socket_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(socket_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
socket_addr
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn client_connection(self) -> io::Result<ClientConnection> {
|
||||
ClientConnection::new(self).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientConnection {
|
||||
stream: TcpStream,
|
||||
}
|
||||
|
||||
impl ClientConnection {
|
||||
pub async fn new(c: Client) -> io::Result<Self> {
|
||||
let sock = TcpSocket::new_v4()?;
|
||||
let stream = sock.connect(c.socket_addr).await?;
|
||||
Ok(Self {
|
||||
stream
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send(&mut self, packet: Packet) -> io::Result<Packet> {
|
||||
self.stream.write(packet.build_full_packet().as_slice()).await?;
|
||||
from_tcp_stream(&mut self.stream).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn from_tcp_stream(stream: &mut TcpStream) -> io::Result<Packet> {
|
||||
// Implement the logic to read data from the TcpStream asynchronously and create a Packet instance
|
||||
|
||||
match stream.read_u8().await? {
|
||||
0x01 => {
|
||||
// Example: Read packet data and create a Packet::V1 instance
|
||||
let src = Identifier::from(stream.read_u32().await?); // Assuming you have a similar function for Identifier
|
||||
let dest = Identifier::from(stream.read_u32().await?); // Assuming you have a similar function for Identifier
|
||||
let packet_id = Identifier::from(stream.read_u32().await?); // Assuming you have a similar function for Identifier
|
||||
let reply_to = Identifier::from(stream.read_u32().await?); // Assuming you have a similar function for Identifier
|
||||
let command = stream.read_u8().await?; // Read u8 directly
|
||||
let data_length = stream.read_u16().await?; // Read u16 directly
|
||||
|
||||
trace!("received => src: {src}, dest: {dest}, packet_id: {packet_id}, reply_to: {reply_to}, command: {command}, data_length: {data_length}");
|
||||
|
||||
let mut data = vec![0u8; data_length as usize];
|
||||
stream.read_exact(&mut data).await?;
|
||||
|
||||
// Create a Packet::V1 instance
|
||||
let packet = Packet::V1 {
|
||||
src,
|
||||
dest,
|
||||
packet_id,
|
||||
reply_to,
|
||||
command,
|
||||
data: PacketData::new(data), // Assuming PacketData has a constructor
|
||||
};
|
||||
|
||||
Ok(packet)
|
||||
}
|
||||
_ => Err(io::Error::new(io::ErrorKind::InvalidData, "received invalid data."))
|
||||
}
|
||||
}
|
|
@ -1,94 +1,109 @@
|
|||
use std::io;
|
||||
use std::time::Duration;
|
||||
use log::{error, info, LevelFilter, trace};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpSocket;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::time::sleep;
|
||||
use crate::config::node::NodeType;
|
||||
use log::{debug, info, trace};
|
||||
use std::{env, io};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use connection::Client;
|
||||
use domo_proto::commands::node_management::NodeManagement;
|
||||
use domo_proto::packet::ToPacket;
|
||||
use domo_proto::packet::identifier::Identifier;
|
||||
use domo_proto::packet::Packet;
|
||||
use domo_proto::packet::packet_data::PacketData;
|
||||
use std::cell::RefCell;
|
||||
|
||||
async fn app(mut recv: Receiver<Packet>) -> io::Result<()> {
|
||||
trace!("Entered runtime!");
|
||||
pub mod config;
|
||||
pub mod connection;
|
||||
|
||||
let sock = TcpSocket::new_v4()?;
|
||||
let mut stream = sock.connect("127.0.0.1:2000".parse().unwrap()).await?;
|
||||
|
||||
while let Some(p) = recv.recv().await {
|
||||
let bytes = p.build_full_packet();
|
||||
match stream.write(bytes.as_slice()).await {
|
||||
Ok(s) => trace!("Packet [id: {}] was sent. [sent bytes: {}/{}]", p.packet_id(), s, bytes.len()),
|
||||
Err(e) => error!("Packet [id: {}] was not sent properly: {}", p.packet_id(), e)
|
||||
}
|
||||
}
|
||||
|
||||
stream.shutdown().await?;
|
||||
Ok(())
|
||||
thread_local! {
|
||||
static CONFIG: RefCell<config::Config> = RefCell::new(config::Config::from_path(
|
||||
env::var("DOMO_CONFIG_PATH")
|
||||
.unwrap_or("config.toml".into())
|
||||
).unwrap_or({ info!("Using default config..."); config::Config::default() }))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
setup_logging()
|
||||
.expect("could not setup logging");
|
||||
trace!("Building runtime...");
|
||||
setup_logging().expect("could not setup logging");
|
||||
|
||||
let (send, recv) = tokio::sync::mpsc::channel::<Packet>(128);
|
||||
debug!("Building tokio runtime...");
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
runtime.spawn(async move {
|
||||
sleep(Duration::new(1, 0)).await;
|
||||
let p =Packet::V1 {
|
||||
src: Identifier::from(0x00000000),
|
||||
dest: Identifier::from(0x00000000),
|
||||
packet_id: Identifier::from(0x00000000),
|
||||
reply_to: Identifier::from(0x00000000),
|
||||
command: 0x00,
|
||||
data: PacketData::new(
|
||||
vec![0x1,0x11,0xDD]
|
||||
)
|
||||
};
|
||||
info!("{:X?}", p.build_full_packet());
|
||||
send.send(p).await
|
||||
});
|
||||
debug!("Built tokio runtime with all features...");
|
||||
|
||||
runtime.block_on(async {
|
||||
info!("Starting app...");
|
||||
match app(recv).await {
|
||||
Ok(()) => info!("App exited."),
|
||||
Err(e) => error!("App exited with error: {}", e)
|
||||
info!(
|
||||
"Staring as {} node!",
|
||||
CONFIG.with_borrow(|c| c.node.node_type.clone())
|
||||
);
|
||||
|
||||
match CONFIG.with_borrow(|c| c.node.node_type.clone()) {
|
||||
NodeType::Master => runtime.block_on(async {}),
|
||||
NodeType::Relay { master_address } => {
|
||||
let client = Client::new(master_address.parse().unwrap());
|
||||
runtime.block_on(async {
|
||||
let mut conn = client.client_connection().await.unwrap();
|
||||
let device_id = match CONFIG.with_borrow(|c| c.node.device_id.clone()) {
|
||||
Some(s) => {
|
||||
let device_id = Identifier::from(domo_proto::prelude::as_u32_be(hex::decode(s)
|
||||
.expect("Could not decode hex device_id").as_slice()));
|
||||
let p = conn.send(NodeManagement::RegisterNode {
|
||||
device_id
|
||||
}.to_v1_packet(
|
||||
Identifier::from(0),
|
||||
Identifier::from(0),
|
||||
Identifier::random(),
|
||||
Identifier::from(0)
|
||||
)).await.unwrap();
|
||||
if p.command() == 0x0E {
|
||||
panic!("device id is taken")
|
||||
}
|
||||
trace!("server assigned us: {}", device_id);
|
||||
device_id
|
||||
},
|
||||
None => {
|
||||
trace!("requesting device_id...");
|
||||
let p = conn.send(NodeManagement::RegisterNode {
|
||||
device_id: Identifier::from(0)
|
||||
}.to_v1_packet(
|
||||
Identifier::from(0),
|
||||
Identifier::from(0),
|
||||
Identifier::random(),
|
||||
Identifier::from(0)
|
||||
)).await;
|
||||
Identifier::from(p.unwrap().data().get_data())
|
||||
}
|
||||
};
|
||||
CONFIG.with_borrow_mut(move |c| c.node.device_id = Some(hex::encode(device_id.bytes)));
|
||||
})
|
||||
}
|
||||
});
|
||||
NodeType::Subnet { master_address } => {
|
||||
runtime.block_on(async {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_logging() -> Result<(), fern::InitError> {
|
||||
let stdout = fern::Dispatch::new()
|
||||
.level(LevelFilter::Debug)
|
||||
.chain(io::stdout());
|
||||
|
||||
let file = fern::Dispatch::new()
|
||||
.level(LevelFilter::Trace)
|
||||
.chain(fern::log_file("node.log")?);
|
||||
|
||||
fern::Dispatch::new()
|
||||
.format(|out, message, record| {
|
||||
match record.metadata().target() {
|
||||
_ => out.finish(format_args!(
|
||||
"[{} - {}] <{}> {}: {}",
|
||||
humantime::format_rfc3339(std::time::SystemTime::now()),
|
||||
record.level(),
|
||||
record.target(),
|
||||
record.file().unwrap_or("??"),
|
||||
message
|
||||
))
|
||||
}
|
||||
.format(|out, message, record| match record.metadata().target() {
|
||||
_ => out.finish(format_args!(
|
||||
"[{} - {}] <{}> {}: {}",
|
||||
humantime::format_rfc3339(std::time::SystemTime::now()),
|
||||
record.level(),
|
||||
record.target(),
|
||||
record.file().unwrap_or("??"),
|
||||
message
|
||||
)),
|
||||
})
|
||||
.chain({
|
||||
fern::Dispatch::new()
|
||||
.level(CONFIG.with_borrow(|c| c.log.stdout_level.clone()).into())
|
||||
.chain(io::stdout())
|
||||
})
|
||||
.chain({
|
||||
fern::Dispatch::new()
|
||||
.level(CONFIG.with_borrow(|c| c.log.stdout_level.clone()).into())
|
||||
.chain(fern::log_file("./node.log")?)
|
||||
})
|
||||
.chain(stdout)
|
||||
.chain(file)
|
||||
.apply()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue