From 46ee857c229d2e86cc22b582591c0e38a3c44ad3 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Fri, 12 Apr 2024 03:07:09 -0700 Subject: [PATCH] improve insturmentation / serror --- Cargo.lock | 8 +- Cargo.toml | 4 +- bin/core/src/api/auth.rs | 15 +- bin/core/src/api/execute/mod.rs | 50 +++--- bin/core/src/api/read/mod.rs | 17 +- bin/core/src/api/write/mod.rs | 50 ++++-- bin/periphery/src/handler.rs | 47 ++++-- client/core/ts/src/types.ts | 5 +- .../components/resources/deployment/logs.tsx | 145 +++++++++++++----- frontend/src/pages/login.tsx | 2 +- 10 files changed, 239 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 204fcda75..e6b93baa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2896,9 +2896,9 @@ dependencies = [ [[package]] name = "resolver_api" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9245971c55ef05018019a1da642fce51439df2b4336369144a7bb866e91f79cf" +checksum = "43981e6bc9a85f1072ffd38bcaca5823e3cd7f24bb675fa5e79736a318f1f998" dependencies = [ "anyhow", "async-trait", @@ -3251,9 +3251,9 @@ dependencies = [ [[package]] name = "serror" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d3df9a2b74d806ecbe70d5156f436edca357e458b8d970ce88cab324a599190" +checksum = "ac699ec20dda9d3630e499777c7822d9811beafbedf3b6305586b7338eb5a50e" dependencies = [ "anyhow", "axum 0.7.4", diff --git a/Cargo.toml b/Cargo.toml index 0e978d4da..89260ccf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ logger = { path = "lib/logger" } # MOGH run_command = { version = "0.0.6", features = ["async_tokio"] } -serror = { version = "0.1.9", features = ["axum"] } +serror = { version = "0.1.10", features = ["axum"] } slack = { version = "0.1.0", package = "slack_client_rs" } derive_default_builder = "0.1.8" derive_empty_traits = "0.1.0" @@ -27,7 +27,7 @@ async_timing_util = "0.1.14" partial_derive2 = "0.2.2" derive_variants = "0.1.3" mongo_indexed = "0.2.2" -resolver_api = "0.1.8" +resolver_api = "0.1.9" parse_csl = "0.1.0" mungos = "0.5.4" svi = "0.1.4" diff --git a/bin/core/src/api/auth.rs b/bin/core/src/api/auth.rs index 197c3cc25..0ca4b79ea 100644 --- a/bin/core/src/api/auth.rs +++ b/bin/core/src/api/auth.rs @@ -1,5 +1,6 @@ use std::{sync::OnceLock, time::Instant}; +use anyhow::anyhow; use async_trait::async_trait; use axum::{http::HeaderMap, routing::post, Json, Router}; use axum_extra::{headers::ContentType, TypedHeader}; @@ -58,11 +59,15 @@ async fn handler( let timer = Instant::now(); let req_id = Uuid::new_v4(); debug!("/auth request {req_id} | METHOD: {}", request.req_type()); - let res = State.resolve_request(request, headers).await; - if let Err(resolver_api::Error::Serialization(e)) = &res { - debug!("/auth request {req_id} | serialization error: {e:?}"); - } - if let Err(resolver_api::Error::Inner(e)) = &res { + let res = State.resolve_request(request, headers).await.map_err( + |e| match e { + resolver_api::Error::Serialization(e) => { + anyhow!("{e:?}").context("response serialization error") + } + resolver_api::Error::Inner(e) => e, + }, + ); + if let Err(e) = &res { debug!("/auth request {req_id} | error: {e:#}"); } let elapsed = timer.elapsed(); diff --git a/bin/core/src/api/execute/mod.rs b/bin/core/src/api/execute/mod.rs index 464371ca1..8fd20fc07 100644 --- a/bin/core/src/api/execute/mod.rs +++ b/bin/core/src/api/execute/mod.rs @@ -1,6 +1,6 @@ use std::time::Instant; -use anyhow::Context; +use anyhow::{anyhow, Context}; use axum::{middleware, routing::post, Extension, Json, Router}; use axum_extra::{headers::ContentType, TypedHeader}; use monitor_client::{api::execute::*, entities::user::User}; @@ -54,38 +54,52 @@ pub fn router() -> Router { .layer(middleware::from_fn(auth_request)) } -#[instrument(name = "ExecuteHandler", skip(user))] async fn handler( Extension(user): Extension, Json(request): Json, ) -> AppResult<(TypedHeader, String)> { - let timer = Instant::now(); let req_id = Uuid::new_v4(); - info!( - "/execute request {req_id} | user: {} ({})", - user.username, user.id - ); - let res = tokio::spawn(async move { - State.resolve_request(request, user).await - }) - .await - .context("failure in spawned execute task"); + + let res = tokio::spawn(task(req_id, request, user)) + .await + .context("failure in spawned execute task"); if let Err(e) = &res { warn!("/execute request {req_id} spawn error: {e:#}",); } - let res = res?; + AppResult::Ok((TypedHeader(ContentType::json()), res??)) +} - if let Err(resolver_api::Error::Serialization(e)) = &res { - warn!("/execute request {req_id} serialization error: {e:?}"); - } - if let Err(resolver_api::Error::Inner(e)) = &res { +#[instrument(name = "ExecuteTask")] +async fn task( + req_id: Uuid, + request: ExecuteRequest, + user: User, +) -> anyhow::Result { + info!( + "/execute request {req_id} | user: {} ({})", + user.username, user.id + ); + let timer = Instant::now(); + + let res = + State + .resolve_request(request, user) + .await + .map_err(|e| match e { + resolver_api::Error::Serialization(e) => { + anyhow!("{e:?}").context("response serialization error") + } + resolver_api::Error::Inner(e) => e, + }); + + if let Err(e) = &res { warn!("/execute request {req_id} error: {e:#}"); } let elapsed = timer.elapsed(); info!("/execute request {req_id} | resolve time: {elapsed:?}"); - AppResult::Ok((TypedHeader(ContentType::json()), res?)) + res } diff --git a/bin/core/src/api/read/mod.rs b/bin/core/src/api/read/mod.rs index 6180c4e8a..45c736dfe 100644 --- a/bin/core/src/api/read/mod.rs +++ b/bin/core/src/api/read/mod.rs @@ -1,5 +1,6 @@ use std::time::Instant; +use anyhow::anyhow; use async_trait::async_trait; use axum::{middleware, routing::post, Extension, Json, Router}; use axum_extra::{headers::ContentType, TypedHeader}; @@ -150,11 +151,17 @@ async fn handler( "/read request {req_id} | user: {} ({})", user.username, user.id ); - let res = State.resolve_request(request, user).await; - if let Err(resolver_api::Error::Serialization(e)) = &res { - warn!("/read request {req_id} serialization error: {e:?}"); - } - if let Err(resolver_api::Error::Inner(e)) = &res { + let res = + State + .resolve_request(request, user) + .await + .map_err(|e| match e { + resolver_api::Error::Serialization(e) => { + anyhow!("{e:?}").context("response serialization error") + } + resolver_api::Error::Inner(e) => e, + }); + if let Err(e) = &res { warn!("/read request {req_id} error: {e:#}"); } let elapsed = timer.elapsed(); diff --git a/bin/core/src/api/write/mod.rs b/bin/core/src/api/write/mod.rs index 734cbe8a3..d60b8b01d 100644 --- a/bin/core/src/api/write/mod.rs +++ b/bin/core/src/api/write/mod.rs @@ -1,6 +1,6 @@ use std::time::Instant; -use anyhow::Context; +use anyhow::{anyhow, Context}; use axum::{middleware, routing::post, Extension, Json, Router}; use axum_extra::{headers::ContentType, TypedHeader}; use monitor_client::{api::write::*, entities::user::User}; @@ -115,33 +115,49 @@ async fn handler( Extension(user): Extension, Json(request): Json, ) -> AppResult<(TypedHeader, String)> { - let timer = Instant::now(); let req_id = Uuid::new_v4(); - info!( - "/write request {req_id} | user: {} ({})", - user.username, user.id - ); - let res = tokio::spawn(async move { - State.resolve_request(request, user).await - }) - .await - .context("failure in spawned task"); + + let res = tokio::spawn(task(req_id, request, user)) + .await + .context("failure in spawned task"); if let Err(e) = &res { warn!("/write request {req_id} spawn error: {e:#}"); } - let res = res?; + AppResult::Ok((TypedHeader(ContentType::json()), res??)) +} - if let Err(resolver_api::Error::Serialization(e)) = &res { - warn!("/write request {req_id} serialization error: {e:?}"); - } - if let Err(resolver_api::Error::Inner(e)) = &res { +#[instrument(name = "WriteTask")] +async fn task( + req_id: Uuid, + request: WriteRequest, + user: User, +) -> anyhow::Result { + info!( + "/write request {req_id} | user: {} ({})", + user.username, user.id + ); + + let timer = Instant::now(); + + let res = + State + .resolve_request(request, user) + .await + .map_err(|e| match e { + resolver_api::Error::Serialization(e) => { + anyhow!("{e:?}").context("response serialization error") + } + resolver_api::Error::Inner(e) => e, + }); + + if let Err(e) = &res { warn!("/write request {req_id} error: {e:#}"); } let elapsed = timer.elapsed(); info!("/write request {req_id} | resolve time: {elapsed:?}"); - AppResult::Ok((TypedHeader(ContentType::json()), res?)) + res } diff --git a/bin/periphery/src/handler.rs b/bin/periphery/src/handler.rs index 42379fd5b..5ff53ed84 100644 --- a/bin/periphery/src/handler.rs +++ b/bin/periphery/src/handler.rs @@ -1,5 +1,6 @@ use std::time::Instant; +use anyhow::{anyhow, Context}; use axum::Json; use axum_extra::{headers::ContentType, TypedHeader}; use resolver_api::Resolver; @@ -8,33 +9,47 @@ use uuid::Uuid; use crate::State; -#[instrument(name = "PeripheryHandler")] pub async fn handler( Json(request): Json, ) -> AppResult<(TypedHeader, String)> { - let timer = Instant::now(); let req_id = Uuid::new_v4(); - info!("request {req_id} | {request:?}"); - let res = - tokio::spawn( - async move { State.resolve_request(request, ()).await }, - ) - .await; - let elapsed = timer.elapsed(); - info!("request {req_id} | resolve time: {elapsed:?}"); + let res = tokio::spawn(task(req_id, request)) + .await + .context("task handler spawn error"); if let Err(e) = &res { warn!("request {req_id} spawn error: {e:#}"); } - let res = res?; - if let Err(resolver_api::Error::Serialization(e)) = &res { - warn!("request {req_id} serialization error: {e:?}"); - } - if let Err(resolver_api::Error::Inner(e)) = &res { + AppResult::Ok((TypedHeader(ContentType::json()), res??)) +} + +#[instrument(name = "PeripheryTask")] +async fn task( + req_id: Uuid, + request: crate::api::PeripheryRequest, +) -> anyhow::Result { + info!("request {req_id} | {request:?}"); + let timer = Instant::now(); + + let res = + State + .resolve_request(request, ()) + .await + .map_err(|e| match e { + resolver_api::Error::Serialization(e) => { + anyhow!("{e:?}").context("response serialization error") + } + resolver_api::Error::Inner(e) => e, + }); + + if let Err(e) = &res { warn!("request {req_id} error: {e:#}"); } - AppResult::Ok((TypedHeader(ContentType::json()), res?)) + let elapsed = timer.elapsed(); + info!("request {req_id} | resolve time: {elapsed:?}"); + + res } diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 4bbdc7551..1ba0f6fbe 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -1027,6 +1027,9 @@ export type AlertData = server_name: string; from: DockerContainerState; to: DockerContainerState; +}} + | { type: "AwsBuilderTerminationFailed", data: { + instance_id: string; }} | { type: "None", data: { }}; @@ -1166,7 +1169,7 @@ export interface GetLog { export interface SearchLog { /** Id or name */ deployment: string; - search: string; + terms: string[]; } export interface GetDeployedVersion { diff --git a/frontend/src/components/resources/deployment/logs.tsx b/frontend/src/components/resources/deployment/logs.tsx index 12a02f0c3..d55eec9ce 100644 --- a/frontend/src/components/resources/deployment/logs.tsx +++ b/frontend/src/components/resources/deployment/logs.tsx @@ -1,4 +1,4 @@ - import { Section } from "@components/layouts"; +import { Section } from "@components/layouts"; import { useRead } from "@lib/hooks"; import { Types } from "@monitor/client"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@ui/tabs"; @@ -8,8 +8,10 @@ import { AlertOctagon, RefreshCw, ChevronDown, + Search, + X, } from "lucide-react"; -import { useState } from "react"; +import { createRef, useState } from "react"; import { useDeployment } from "."; import { Select, @@ -21,6 +23,7 @@ import { } from "@ui/select"; import sanitizeHtml from "sanitize-html"; import Convert from "ansi-to-html"; +import { Input } from "@ui/input"; export const DeploymentLogs = ({ id }: { id: string }) => { const state = useDeployment(id)?.info.state; @@ -36,11 +39,22 @@ export const DeploymentLogs = ({ id }: { id: string }) => { const DeploymentLogsInner = ({ id }: { id: string }) => { const [tail, set] = useState("100"); - const { data: logs, refetch } = useRead( - "GetLog", - { deployment: id, tail: Number(tail) }, - { refetchInterval: 30000 } - ); + const searchRef = createRef(); + const [search, setSearch] = useState(""); + + const updateSearch = () => + searchRef.current && setSearch(searchRef.current.value); + const clearSearch = () => { + if (searchRef.current) { + searchRef.current.value = ""; + } + setSearch(""); + }; + + const { Log, refetch, stderr } = search + ? SearchLogs(id, search) + : NoSearchLogs(id, tail); + return (
{ icon={} actions={
- +
+ { + if (e.key === "Enter") updateSearch(); + }} + /> + +
+ + stdout stderr - {logs?.stderr && ( + {stderr && ( )} - - + 0} + />
} > - {["stdout", "stderr"].map((t) => { - const log = logs?.[t as keyof typeof logs] as string | undefined; - return ( - -
-
-              
- -
- ); - })} + {Log}
); }; +const NoSearchLogs = (id: string, tail: string) => { + const { data: log, refetch } = useRead( + "GetLog", + { deployment: id, tail: Number(tail) }, + { refetchInterval: 30000 } + ); + return { + Log: , + refetch, + stderr: !!log?.stderr, + }; +}; + +const SearchLogs = (id: string, search: string) => { + const { data: log, refetch } = useRead( + "SearchLog", + { deployment: id, terms: search.split(" ").filter((term) => term) }, + { refetchInterval: 30000 } + ); + return { + Log: , + refetch, + stderr: !!log?.stderr, + }; +}; + +const Log = ({ log }: { log: Types.Log | undefined }) => { + return ( + <> + {["stdout", "stderr"].map((stream) => { + const _log = log?.[stream as keyof typeof log] as string | undefined; + return ( + +
+
+            
+ +
+ ); + })} + + ); +}; + const TailLengthSelector = ({ selected, onSelect, + disabled, }: { selected: string; onSelect: (value: string) => void; + disabled?: boolean; }) => ( - + diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index e3fd743d5..b1e04ce98 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -77,7 +77,7 @@ export const Login = () => {
- +
Monitor Log In