mirror of
https://github.com/moghtech/komodo.git
synced 2026-03-11 17:44:19 -05:00
improve insturmentation / serror
This commit is contained in:
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user