Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,22 @@ const LogParsingSwitches: FC = () => {
<>
<div className="vm-group-logs-configurator-item">
<Switch
label={"Enable markdown parsing"}
label={"Markdown parsing"}
value={markdownParsing}
onChange={handleChangeMarkdownParsing}
/>
<div className="vm-group-logs-configurator-item__info">
Toggle this switch to enable or disable the Markdown formatting for log entries.
Enabling this will parse log texts to Markdown.
Parses log text and renders Markdown formatting.
</div>
</div>
<div className="vm-group-logs-configurator-item">
<Switch
label={"Enable ANSI parsing"}
label={"ANSI parsing"}
value={ansiParsing}
onChange={handleChangeAnsiParsing}
/>
<div className="vm-group-logs-configurator-item__info">
Toggle this switch to enable or disable ANSI escape sequence parsing for log entries.
Enabling this will interpret ANSI codes to render colored log output.
Renders ANSI escape codes as colored text.
</div>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const GroupLogsConfigurators: FC<Props> = ({ logs }) => {
const displayFieldsString = searchParams.get(DISPLAY_FIELDS) || "";
const displayFields = displayFieldsString ? displayFieldsString.split(",") : [LOGS_DISPLAY_FIELDS];

const [disabledHovers, handleSetDisabledHovers] = useLocalStorageBoolean("LOGS_DISABLED_HOVERS");
const [disabledHovers, setDisabledHovers] = useLocalStorageBoolean("LOGS_DISABLED_HOVERS");
const [disabledLevelDetection, setDisabledLevelDetection] = useLocalStorageBoolean("LOGS_DISABLED_LEVEL_DETECTION");

const isGroupChanged = groupBy !== LOGS_GROUP_BY;
const isDisplayFieldsChanged = displayFields.length !== 1 || displayFields[0] !== LOGS_DISPLAY_FIELDS;
Expand Down Expand Up @@ -168,6 +169,17 @@ const GroupLogsConfigurators: FC<Props> = ({ logs }) => {
</span>
</div>

<div className="vm-group-logs-configurator-item">
<Switch
value={!disabledLevelDetection}
onChange={(checked) => setDisabledLevelDetection(!checked)}
label="Detect log level"
/>
<span className="vm-group-logs-configurator-item__info">
Displays a log level label next to each log entry.
</span>
</div>

<LogParsingSwitches/>

<div className="vm-group-logs-configurator-item">
Expand All @@ -177,7 +189,7 @@ const GroupLogsConfigurators: FC<Props> = ({ logs }) => {
label="Single-line message"
/>
<span className="vm-group-logs-configurator-item__info">
Displays message in a single line and truncates it with an ellipsis if it exceeds the available space.
Truncates long messages to a single line with an ellipsis.
</span>
</div>

Expand All @@ -195,11 +207,11 @@ const GroupLogsConfigurators: FC<Props> = ({ logs }) => {
<div className="vm-group-logs-configurator-item">
<Switch
value={disabledHovers}
onChange={handleSetDisabledHovers}
onChange={setDisabledHovers}
label="Disable hover effects"
/>
<span className="vm-group-logs-configurator-item__info">
Disable row highlighting on hover to improve performance with large datasets.
Disables row highlighting on hover to improve performance with large datasets.
</span>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
display: grid;
gap: calc($padding-global * 2);
padding: $padding-global 0;
max-width: 600px;
width: min(90vw, 600px);

&-item {
display: grid;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, memo, useMemo, useCallback, useEffect, useState, ReactNode } from "preact/compat";
import { FC, memo, ReactNode, useCallback, useEffect, useMemo, useState } from "preact/compat";
import { Logs } from "../../../api/types";
import "./style.scss";
import useBoolean from "../../../hooks/useBoolean";
Expand All @@ -19,6 +19,8 @@ import StreamContextButton from "../../../pages/StreamContext/StreamContextButto
import { useAppState } from "../../../state/common/StateContext";
import { formatDateWithNanoseconds } from "../../../utils/time";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
import { getLogLevel } from "../../../utils/logLevel";
import { LOG_LEVEL_COLORS } from "../../../constants/logLevel";

interface Props {
log: Logs;
Expand All @@ -29,7 +31,14 @@ interface Props {
onItemClick?: (log: Logs) => void;
}

const GroupLogsItem: FC<Props> = ({ log, displayFields = [], isContextView, hideGroupButton, className, onItemClick }) => {
const GroupLogsItem: FC<Props> = ({
log,
displayFields = [],
isContextView,
hideGroupButton,
className,
onItemClick
}) => {
const { isDarkTheme } = useAppState();
const { isMobile } = useDeviceDetect();

Expand All @@ -45,6 +54,16 @@ const GroupLogsItem: FC<Props> = ({ log, displayFields = [], isContextView, hide
const { timezone } = useTimeState();

const noWrapLines = searchParams.get(LOGS_URL_PARAMS.NO_WRAP_LINES) === "true";
const [disabledLevelDetection] = useLocalStorageBoolean("LOGS_DISABLED_LEVEL_DETECTION");

const logLevel = useMemo(() => {
if (disabledLevelDetection) return null;
const level = getLogLevel(log);
return {
label: level,
color: LOG_LEVEL_COLORS[level],
};
}, [log, disabledLevelDetection, isDarkTheme]);

const formattedTime = useMemo(() => {
if (!log._time) return "";
Expand Down Expand Up @@ -145,6 +164,19 @@ const GroupLogsItem: FC<Props> = ({ log, displayFields = [], isContextView, hide
>
{formattedTime || "timestamp missing"}
</div>
{logLevel && (
<Tooltip
title="Detected log level. Can be disabled in Group view settings."
placement="top-center"
>
<div
Comment thread
hagen1778 marked this conversation as resolved.
className="vm-group-logs-row__level-badge"
style={{ color: logLevel.color }}
>
{logLevel.label}
</div>
</Tooltip>
)}
<div
className={classNames({
"vm-group-logs-row-content__msg": true,
Expand Down
11 changes: 11 additions & 0 deletions app/vmui/packages/vmui/src/components/Views/GroupView/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,17 @@ $actions-width: calc(($actions-buttons * 30px) + 10px);
background-color: $color-hover-black;
}

&__level-badge {
flex: 0 0 6ch;
width: 6ch;
min-width: 6ch;
text-align: left;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
margin-right: $padding-small
}

&-content {
display: flex;
align-items: flex-start;
Expand Down
44 changes: 44 additions & 0 deletions app/vmui/packages/vmui/src/constants/logLevel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export const LOG_LEVEL_UNKNOWN = "other";

export const LOG_LEVEL_COLORS = {
trace: "#20BFC0",
debug: "#4771E2",
info: "#62A53B",
warn: "#F27800",
error: "#D2323B",
fatal: "#9C50D3",
[LOG_LEVEL_UNKNOWN]: "#A09F9F",
} as const;

export const LOG_LEVEL_FIELDS = [
"level",
Comment thread
hagen1778 marked this conversation as resolved.
"lvl",
"log_level",
"log.level",
"loglevel",

"SeverityText",
"severityText",
"severity_text",
"severity",
Comment thread
arturminchukov marked this conversation as resolved.

"levelname",
"level_name",

"@l",
"@level",

"logLevel",
"logLevelName",

"detected_level",
"status",

"syslog.severity.name",
"log.syslog.severity.name",

"Level",
"severityLevel",
"log_severity",
"severityValue",
] as const;
1 change: 0 additions & 1 deletion app/vmui/packages/vmui/src/constants/palette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,4 @@ export const lightPalette = {
"color-log-hits-bar-3": "#64B5F6",
"color-log-hits-bar-4": "#E57373",
"color-log-hits-bar-5": "#8a62f0",

};
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@
&__content {
font-size: $font-size-small;
padding-top: $padding-medium;
overflow: scroll;

&_table:not(&_hide) {
padding-top: 0;
overflow: scroll;
}

&_mobile {
Expand Down
106 changes: 106 additions & 0 deletions app/vmui/packages/vmui/src/utils/logLevel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { describe, expect, it } from "vitest";
import { LOG_LEVEL_COLORS, LOG_LEVEL_FIELDS, LOG_LEVEL_UNKNOWN } from "../constants/logLevel";
import type { Logs } from "../api/types";
import { getLogLevel, getLogLevelColor } from "./logLevel";

type LogLevel = keyof typeof LOG_LEVEL_COLORS;

const log = (fields: Record<string, unknown>): Logs => fields as Logs;
const logLevels = Object.keys(LOG_LEVEL_COLORS) as LogLevel[];

describe("logLevel utils", () => {
describe("getLogLevel", () => {
it("returns unknown for empty log", () => {
expect(getLogLevel(log({}))).toBe(LOG_LEVEL_UNKNOWN);
});

it("returns unknown for unrecognized field and value", () => {
expect(getLogLevel(log({ foo: "bar" }))).toBe(LOG_LEVEL_UNKNOWN);
});

it("returns unknown for numeric value", () => {
expect(getLogLevel(log({ level: 30 }))).toBe(LOG_LEVEL_UNKNOWN);
});

it("returns unknown for null value", () => {
expect(getLogLevel(log({ level: null }))).toBe(LOG_LEVEL_UNKNOWN);
});

it.each(LOG_LEVEL_FIELDS)("detects level from field \"%s\"", (field) => {
expect(getLogLevel(log({ [field]: "error" }))).toBe("error");
});

it("prefers 'level' over 'severity'", () => {
expect(getLogLevel(log({ level: "debug", severity: "error" }))).toBe("debug");
});

it("prefers 'severity' over 'status'", () => {
expect(getLogLevel(log({ severity: "warn", status: "error" }))).toBe("warn");
});

it("skips unknown value and continues checking next fields", () => {
expect(getLogLevel(log({ level: "custom", severity: "error" }))).toBe("error");
});

it("normalizes aliases", () => {
expect(getLogLevel(log({ level: "verbose" }))).toBe("debug");
expect(getLogLevel(log({ level: "information" }))).toBe("info");
expect(getLogLevel(log({ level: "informational" }))).toBe("info");
expect(getLogLevel(log({ level: "warning" }))).toBe("warn");
expect(getLogLevel(log({ level: "err" }))).toBe("error");
expect(getLogLevel(log({ level: "severe" }))).toBe("error");
expect(getLogLevel(log({ level: "critical" }))).toBe("fatal");
expect(getLogLevel(log({ level: "crit" }))).toBe("fatal");
expect(getLogLevel(log({ level: "alert" }))).toBe("fatal");
expect(getLogLevel(log({ level: "emergency" }))).toBe("fatal");
expect(getLogLevel(log({ level: "emerg" }))).toBe("fatal");
expect(getLogLevel(log({ level: "panic" }))).toBe("fatal");
});

it("handles case-insensitive values", () => {
expect(getLogLevel(log({ level: "INFO" }))).toBe("info");
expect(getLogLevel(log({ level: "Error" }))).toBe("error");
expect(getLogLevel(log({ level: "Warn" }))).toBe("warn");
});

it("trims whitespace from value", () => {
expect(getLogLevel(log({ level: " info " }))).toBe("info");
});
});

describe("getLogLevelColor", () => {
it("returns neutral light color for unknown level", () => {
expect(getLogLevelColor(log({}))).toBe(LOG_LEVEL_COLORS[LOG_LEVEL_UNKNOWN]);
});

it("returns neutral dark color for unknown level", () => {
expect(getLogLevelColor(log({}))).toBe(LOG_LEVEL_COLORS[LOG_LEVEL_UNKNOWN]);
});

it.each(logLevels.filter((level) => level !== LOG_LEVEL_UNKNOWN))(
"returns correct light color for level \"%s\"",
(level) => {
expect(getLogLevelColor(log({ level }))).toBe(LOG_LEVEL_COLORS[level]);
},
);

it.each(logLevels.filter((level) => level !== LOG_LEVEL_UNKNOWN))(
"returns correct dark color for level \"%s\"",
(level) => {
expect(getLogLevelColor(log({ level }))).toBe(LOG_LEVEL_COLORS[level]);
},
);

it("returns normalized level color", () => {
expect(getLogLevelColor(log({ level: "warning" }))).toBe(
LOG_LEVEL_COLORS.warn,
);
expect(getLogLevelColor(log({ level: "critical" }))).toBe(
LOG_LEVEL_COLORS.fatal,
);
expect(getLogLevelColor(log({ level: "information" }))).toBe(
LOG_LEVEL_COLORS.info,
);
});
});
});
Loading
Loading