improve insturmentation / serror

This commit is contained in:
mbecker20
2024-04-12 03:07:09 -07:00
parent 7bbd22b4b2
commit 46ee857c22
10 changed files with 239 additions and 104 deletions

8
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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();

View File

@@ -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<User>,
Json(request): Json<ExecuteRequest>,
) -> AppResult<(TypedHeader<ContentType>, 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<String> {
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
}

View File

@@ -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();

View File

@@ -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<User>,
Json(request): Json<WriteRequest>,
) -> AppResult<(TypedHeader<ContentType>, 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<String> {
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
}

View File

@@ -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<crate::api::PeripheryRequest>,
) -> AppResult<(TypedHeader<ContentType>, 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<String> {
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
}

View File

@@ -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 {

View File

@@ -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<HTMLInputElement>();
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 (
<Tabs defaultValue="stdout">
<Section
@@ -48,60 +62,121 @@ const DeploymentLogsInner = ({ id }: { id: string }) => {
icon={<TerminalSquare className="w-4 h-4" />}
actions={
<div className="flex gap-2">
<TabsList className="w-fit place-self-end">
<div className="relative">
<Input
ref={searchRef}
placeholder="Search Logs"
onBlur={updateSearch}
onKeyDown={(e) => {
if (e.key === "Enter") updateSearch();
}}
/>
<Button
variant="ghost"
size="icon"
onClick={clearSearch}
className="absolute right-0 top-1/2 -translate-y-1/2"
>
<X className="w-4 h-4" />
</Button>
</div>
<Button variant="outline" size="icon" onClick={updateSearch}>
<Search className="w-4 h-4" />
</Button>
<TabsList>
<TabsTrigger value="stdout" onClick={to_bottom("stdout")}>
stdout
</TabsTrigger>
<TabsTrigger value="stderr" onClick={to_bottom("stderr")}>
stderr
{logs?.stderr && (
{stderr && (
<AlertOctagon className="w-4 h-4 ml-2 stroke-red-500" />
)}
</TabsTrigger>
</TabsList>
<Button variant="secondary" onClick={() => refetch()}>
<Button variant="secondary" size="icon" onClick={() => refetch()}>
<RefreshCw className="w-4 h-4" />
</Button>
<TailLengthSelector selected={tail} onSelect={set} />
<TailLengthSelector
selected={tail}
onSelect={set}
disabled={search.length > 0}
/>
</div>
}
>
{["stdout", "stderr"].map((t) => {
const log = logs?.[t as keyof typeof logs] as string | undefined;
return (
<TabsContent key={t} className="h-full relative" value={t}>
<div className="h-[70vh] overflow-y-auto">
<pre
id={t}
dangerouslySetInnerHTML={{
__html: log ? logToHtml(log) : `no ${t} logs`,
}}
className="-scroll-mt-24"
/>
</div>
<Button
className="absolute bottom-4 right-4"
onClick={to_bottom(t)}
>
<ChevronDown className="h-4 w-4" />
</Button>
</TabsContent>
);
})}
{Log}
</Section>
</Tabs>
);
};
const NoSearchLogs = (id: string, tail: string) => {
const { data: log, refetch } = useRead(
"GetLog",
{ deployment: id, tail: Number(tail) },
{ refetchInterval: 30000 }
);
return {
Log: <Log log={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: <Log log={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 (
<TabsContent key={stream} className="h-full relative" value={stream}>
<div className="h-[70vh] overflow-y-auto">
<pre
id={stream}
dangerouslySetInnerHTML={{
__html: _log ? logToHtml(_log) : `no ${stream} logs`,
}}
className="-scroll-mt-24"
/>
</div>
<Button
className="absolute bottom-4 right-4"
onClick={to_bottom(stream)}
>
<ChevronDown className="h-4 w-4" />
</Button>
</TabsContent>
);
})}
</>
);
};
const TailLengthSelector = ({
selected,
onSelect,
disabled,
}: {
selected: string;
onSelect: (value: string) => void;
disabled?: boolean;
}) => (
<Select value={selected} onValueChange={onSelect}>
<SelectTrigger>
<Select value={selected} onValueChange={onSelect} disabled={disabled}>
<SelectTrigger className="w-[120px]">
<SelectValue />
</SelectTrigger>
<SelectContent>

View File

@@ -77,7 +77,7 @@ export const Login = () => {
</div>
<div className="flex justify-center items-center container mt-32">
<Card className="w-full max-w-[500px] place-self-center">
<CardHeader className="flex justify-between">
<CardHeader className="flex-row justify-between">
<div>
<CardTitle className="text-xl">Monitor</CardTitle>
<CardDescription>Log In</CardDescription>