mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat(LogsViewer): add service name resolution and improve value suggestions handling
feat(LogQueryToFilter): normalize severity values and enhance filter application logic chore(config): update OpenTelemetry exporter endpoint and headers
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
parseLogQuery,
|
||||
} from "./LogQueryParser";
|
||||
import Search from "../BaseDatabase/Search";
|
||||
import NotEqual from "../BaseDatabase/NotEqual";
|
||||
import GreaterThan from "../BaseDatabase/GreaterThan";
|
||||
import GreaterThanOrEqual from "../BaseDatabase/GreaterThanOrEqual";
|
||||
import LessThan from "../BaseDatabase/LessThan";
|
||||
@@ -33,12 +34,35 @@ const TOP_LEVEL_FIELDS: Set<string> = new Set([
|
||||
"body",
|
||||
]);
|
||||
|
||||
// Severity values stored in the database use title case (e.g. "Error", "Debug").
|
||||
// Normalise user input so that "error" matches "Error", etc.
|
||||
const SEVERITY_CANONICAL: Record<string, string> = {
|
||||
fatal: "Fatal",
|
||||
error: "Error",
|
||||
warning: "Warning",
|
||||
warn: "Warning",
|
||||
information: "Information",
|
||||
info: "Information",
|
||||
debug: "Debug",
|
||||
trace: "Trace",
|
||||
unspecified: "Unspecified",
|
||||
};
|
||||
|
||||
function normalizeSeverityValue(value: string): string {
|
||||
return SEVERITY_CANONICAL[value.toLowerCase()] || value;
|
||||
}
|
||||
|
||||
function applyFieldFilter(filter: LogFilter, token: ParsedToken): void {
|
||||
if (!token.field) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value: string = token.value;
|
||||
let value: string = token.value;
|
||||
|
||||
// Normalise severity values to match database casing
|
||||
if (token.field === "severityText") {
|
||||
value = normalizeSeverityValue(value);
|
||||
}
|
||||
|
||||
if (TOP_LEVEL_FIELDS.has(token.field)) {
|
||||
applyTopLevelFilter(filter, token.field, value, token.operator);
|
||||
@@ -70,8 +94,10 @@ function applyTopLevelFilter(
|
||||
case FilterOperator.LessThanOrEqual:
|
||||
filter[field] = new LessThanOrEqual(parseNumericOrString(value));
|
||||
break;
|
||||
case FilterOperator.Equals:
|
||||
case FilterOperator.NotEquals:
|
||||
filter[field] = new NotEqual(value);
|
||||
break;
|
||||
case FilterOperator.Equals:
|
||||
default:
|
||||
filter[field] = value;
|
||||
break;
|
||||
@@ -82,13 +108,41 @@ function applyAttributeFilter(
|
||||
filter: LogFilter,
|
||||
field: string,
|
||||
value: string,
|
||||
_operator: FilterOperator,
|
||||
operator: FilterOperator,
|
||||
): void {
|
||||
if (!filter.attributes) {
|
||||
filter.attributes = {};
|
||||
}
|
||||
|
||||
filter.attributes[field] = value;
|
||||
switch (operator) {
|
||||
case FilterOperator.Contains:
|
||||
case FilterOperator.Wildcard:
|
||||
filter.attributes[field] = new Search(value.replace(/\*/g, ""));
|
||||
break;
|
||||
case FilterOperator.NotEquals:
|
||||
filter.attributes[field] = new NotEqual(value);
|
||||
break;
|
||||
case FilterOperator.GreaterThan:
|
||||
filter.attributes[field] = new GreaterThan(parseNumericOrString(value));
|
||||
break;
|
||||
case FilterOperator.GreaterThanOrEqual:
|
||||
filter.attributes[field] = new GreaterThanOrEqual(
|
||||
parseNumericOrString(value),
|
||||
);
|
||||
break;
|
||||
case FilterOperator.LessThan:
|
||||
filter.attributes[field] = new LessThan(parseNumericOrString(value));
|
||||
break;
|
||||
case FilterOperator.LessThanOrEqual:
|
||||
filter.attributes[field] = new LessThanOrEqual(
|
||||
parseNumericOrString(value),
|
||||
);
|
||||
break;
|
||||
case FilterOperator.Equals:
|
||||
default:
|
||||
filter.attributes[field] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function applyFreeTextFilter(filter: LogFilter, token: ParsedToken): void {
|
||||
|
||||
@@ -110,6 +110,22 @@ const getSeverityWeight: (severity: string | undefined) => number = (
|
||||
return severityWeight[normalized] || 0;
|
||||
};
|
||||
|
||||
// Resolve a human-readable service name to its UUID using the serviceMap.
|
||||
function resolveServiceNameToId(
|
||||
name: string,
|
||||
serviceMap: Dictionary<Service>,
|
||||
): string | undefined {
|
||||
const lowerName: string = name.toLowerCase();
|
||||
|
||||
for (const [id, service] of Object.entries(serviceMap)) {
|
||||
if (service?.name && service.name.toLowerCase() === lowerName) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const LogsViewer: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
@@ -356,6 +372,23 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
||||
const queryFilter: Record<string, unknown> = queryStringToFilter(
|
||||
searchQuery,
|
||||
) as Record<string, unknown>;
|
||||
|
||||
// Resolve human-readable service name to UUID if needed
|
||||
if (
|
||||
queryFilter["serviceId"] &&
|
||||
typeof queryFilter["serviceId"] === "string"
|
||||
) {
|
||||
const serviceName: string = queryFilter["serviceId"] as string;
|
||||
const resolvedId: string | undefined = resolveServiceNameToId(
|
||||
serviceName,
|
||||
serviceMap,
|
||||
);
|
||||
|
||||
if (resolvedId) {
|
||||
queryFilter["serviceId"] = resolvedId;
|
||||
}
|
||||
}
|
||||
|
||||
const mergedFilter: Query<Log> = {
|
||||
...filterData,
|
||||
...queryFilter,
|
||||
@@ -456,6 +489,54 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
||||
const showSidebar: boolean =
|
||||
props.showFacetSidebar !== false && Boolean(props.facetData);
|
||||
|
||||
// Replace serviceId UUIDs with human-readable names in value suggestions
|
||||
const resolvedValueSuggestions: Record<string, Array<string>> | undefined =
|
||||
useMemo(() => {
|
||||
if (!props.valueSuggestions) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const suggestions: Record<string, Array<string>> = {
|
||||
...props.valueSuggestions,
|
||||
};
|
||||
|
||||
if (suggestions["serviceId"] && Object.keys(serviceMap).length > 0) {
|
||||
suggestions["serviceId"] = suggestions["serviceId"].map(
|
||||
(id: string) => {
|
||||
const service: Service | undefined = serviceMap[id];
|
||||
return service?.name || id;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}, [props.valueSuggestions, serviceMap]);
|
||||
|
||||
// Wrap onFieldValueSelect to resolve service names back to UUIDs
|
||||
const handleFieldValueSelectWithServiceResolve:
|
||||
| ((fieldKey: string, value: string) => void)
|
||||
| undefined = useMemo(() => {
|
||||
if (!props.onFieldValueSelect) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (fieldKey: string, value: string): void => {
|
||||
if (fieldKey === "service") {
|
||||
const resolvedId: string | undefined = resolveServiceNameToId(
|
||||
value,
|
||||
serviceMap,
|
||||
);
|
||||
|
||||
if (resolvedId) {
|
||||
props.onFieldValueSelect!(fieldKey, resolvedId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
props.onFieldValueSelect!(fieldKey, value);
|
||||
};
|
||||
}, [props.onFieldValueSelect, serviceMap]);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{props.showFilters && (
|
||||
@@ -465,8 +546,8 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
||||
searchQuery={searchQuery}
|
||||
onSearchQueryChange={setSearchQuery}
|
||||
onSearchSubmit={handleSearchSubmit}
|
||||
valueSuggestions={props.valueSuggestions}
|
||||
onFieldValueSelect={props.onFieldValueSelect}
|
||||
valueSuggestions={resolvedValueSuggestions}
|
||||
onFieldValueSelect={handleFieldValueSelectWithServiceResolve}
|
||||
toolbar={<LogsViewerToolbar {...toolbarProps} />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user