mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
improve service logs with json_fmt
This commit is contained in:
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7)",
|
||||
"query": "INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,10 +11,11 @@
|
||||
"Timestamp",
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Int8"
|
||||
"Int8",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5c54f145e94dac117de02a94adf207684c52d8571b3507f4877c2cc151ff18b9"
|
||||
"hash": "33c1793e55b1127d88d2509aadd0eb04e042463200f237b4c2cb176612fa16fe"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE log_file ADD COLUMN IF NOT EXISTS json_fmt boolean DEFAULT false;
|
||||
@@ -42,6 +42,7 @@ use windmill_common::{
|
||||
jobs::QueuedJob,
|
||||
oauth2::REQUIRE_PREEXISTING_USER_FOR_OAUTH,
|
||||
server::load_server_config,
|
||||
tracing_init::JSON_FMT,
|
||||
users::truncate_token,
|
||||
utils::{now_from_db, rd_string, report_critical_error, Mode},
|
||||
worker::{
|
||||
@@ -500,8 +501,8 @@ async fn send_log_file_to_object_store(
|
||||
|
||||
let (ok_lines, err_lines) = read_log_counters(ts_str);
|
||||
|
||||
if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7)",
|
||||
hostname, mode.to_string(), worker_group.clone(), ts, highest_file, ok_lines as i64, err_lines as i64)
|
||||
if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8)",
|
||||
hostname, mode.to_string(), worker_group.clone(), ts, highest_file, ok_lines as i64, err_lines as i64, *JSON_FMT)
|
||||
.execute(db)
|
||||
.await {
|
||||
tracing::error!("Error inserting log file: {:?}", e);
|
||||
|
||||
@@ -8642,11 +8642,14 @@ paths:
|
||||
type: integer
|
||||
err_lines:
|
||||
type: integer
|
||||
json_fmt:
|
||||
type: boolean
|
||||
required:
|
||||
- hostname
|
||||
- mode
|
||||
- log_ts
|
||||
- file_path
|
||||
- json_fmt
|
||||
|
||||
/service_logs/get_log_file/{path}:
|
||||
get:
|
||||
|
||||
@@ -43,6 +43,7 @@ pub struct LogFile {
|
||||
pub file_path: String,
|
||||
pub ok_lines: Option<i64>,
|
||||
pub err_lines: Option<i64>,
|
||||
pub json_fmt: bool,
|
||||
}
|
||||
async fn list_files(
|
||||
ApiAuthed { email, .. }: ApiAuthed,
|
||||
@@ -62,6 +63,7 @@ async fn list_files(
|
||||
"file_path",
|
||||
"ok_lines",
|
||||
"err_lines",
|
||||
"json_fmt",
|
||||
])
|
||||
.order_by("log_ts", true)
|
||||
.offset(offset)
|
||||
|
||||
@@ -26,15 +26,16 @@ fn compact_layer<S>() -> Layer<S, format::DefaultFields, format::Format<format::
|
||||
tracing_subscriber::fmt::layer().compact()
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref JSON_FMT: bool = std::env::var("JSON_FMT").map(|x| x == "true").unwrap_or(false);
|
||||
}
|
||||
|
||||
pub const LOGS_SERVICE: &str = "logs/services/";
|
||||
|
||||
pub const TMP_WINDMILL_LOGS_SERVICE: &str = concatcp!("/tmp/windmill/", LOGS_SERVICE);
|
||||
|
||||
pub fn initialize_tracing(hostname: &str) -> WorkerGuard {
|
||||
let style = std::env::var("RUST_LOG_STYLE").unwrap_or_else(|_| "auto".into());
|
||||
let json_fmt = std::env::var("JSON_FMT")
|
||||
.map(|x| x == "true")
|
||||
.unwrap_or(false);
|
||||
|
||||
if std::env::var("RUST_LOG").is_ok_and(|x| x == "debug" || x == "info") {
|
||||
std::env::set_var(
|
||||
@@ -71,13 +72,14 @@ pub fn initialize_tracing(hostname: &str) -> WorkerGuard {
|
||||
ts_base.with(layer)
|
||||
};
|
||||
|
||||
match json_fmt {
|
||||
match *JSON_FMT {
|
||||
true => ts_base
|
||||
.with(
|
||||
json_layer()
|
||||
.with_writer(stdout_and_log_file_writer)
|
||||
.flatten_event(true),
|
||||
)
|
||||
.with(CountingLayer::new())
|
||||
.init(),
|
||||
false => ts_base
|
||||
.with(
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { truncateRev } from '$lib/utils'
|
||||
|
||||
let minTs: undefined | string = undefined
|
||||
let maxTs: undefined | string = undefined
|
||||
@@ -29,6 +30,7 @@
|
||||
file_path: string
|
||||
ok_lines: number
|
||||
err_lines: number
|
||||
json_fmt: boolean
|
||||
}
|
||||
|
||||
type ByHostname = Record<string, LogFile[]>
|
||||
@@ -98,7 +100,8 @@
|
||||
ts: ts,
|
||||
file_path: log.file_path,
|
||||
ok_lines: log.ok_lines ?? 1,
|
||||
err_lines: log.err_lines ?? 0
|
||||
err_lines: log.err_lines ?? 0,
|
||||
json_fmt: log.json_fmt
|
||||
})
|
||||
if (
|
||||
log.ok_lines != undefined &&
|
||||
@@ -213,6 +216,50 @@
|
||||
onDestroy(() => {
|
||||
timeout && clearTimeout(timeout)
|
||||
})
|
||||
|
||||
function processLogWithJsonFmt(log: string | undefined, jsonFmt: boolean): string {
|
||||
if (!log) {
|
||||
return ''
|
||||
}
|
||||
if (!jsonFmt) {
|
||||
return log
|
||||
}
|
||||
try {
|
||||
let res = ''
|
||||
log.split('\n').forEach((line) => {
|
||||
if (line.startsWith('{') && line.endsWith('}')) {
|
||||
let obj = JSON.parse(line)
|
||||
if (typeof obj == 'object') {
|
||||
let nl = ''
|
||||
if (obj['timestamp']) {
|
||||
nl += obj['timestamp'] + ' '
|
||||
}
|
||||
if (obj['level']) {
|
||||
nl += obj['level'] + ' '
|
||||
}
|
||||
if (obj['message']) {
|
||||
nl += obj['message'] + ' '
|
||||
}
|
||||
delete obj['timestamp']
|
||||
delete obj['level']
|
||||
delete obj['message']
|
||||
Object.keys(obj).forEach((key) => {
|
||||
nl +=
|
||||
key +
|
||||
'=' +
|
||||
(typeof obj[key] == 'object' ? JSON.stringify(obj[key]) : obj[key]) +
|
||||
' '
|
||||
})
|
||||
res += nl + '\n'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return res
|
||||
} catch (e) {
|
||||
return log
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full h-[70vh]" on:scroll|preventDefault>
|
||||
@@ -346,7 +393,11 @@
|
||||
scrollToBottom()
|
||||
}}
|
||||
>
|
||||
<div class="text-sm pt-2 pl-0.5" style="width: 90px;">{hn}</div>
|
||||
<div
|
||||
class="text-sm pt-2 pl-0.5 whitespace-nowrap"
|
||||
title={hn}
|
||||
style="width: 90px;">{truncateRev(hn, 8)}</div
|
||||
>
|
||||
<div class="relative grow h-8 mr-2">
|
||||
{#each files as file}
|
||||
{@const okHeight = 100.0 * ((file.ok_lines * 1.0) / (max_lines ?? 1))}
|
||||
@@ -421,7 +472,10 @@
|
||||
noMaxH
|
||||
isLoading={false}
|
||||
tag={undefined}
|
||||
content={logsContent[file.file_path].content}
|
||||
content={processLogWithJsonFmt(
|
||||
logsContent[file.file_path].content,
|
||||
file.json_fmt
|
||||
)}
|
||||
/></div
|
||||
>
|
||||
{:else}
|
||||
|
||||
Reference in New Issue
Block a user