The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/logs/components/logParser.ts

171 lines
5.6 KiB

import { partition } from 'lodash';
import { DataFrame, Field, FieldWithIndex, LinkModel, LogRowModel, ScopedVars } from '@grafana/data';
import { safeStringifyValue } from 'app/core/utils/explore';
import { ExploreFieldLinkModel } from 'app/features/explore/utils/links';
import { GetFieldLinksFn } from 'app/plugins/panel/logs/types';
import { parseLogsFrame } from '../logsFrame';
export type FieldDef = {
keys: string[];
values: string[];
links?: Array<LinkModel<Field>> | ExploreFieldLinkModel[];
fieldIndex: number;
};
/**
* Returns all fields for log row which consists of fields we parse from the message itself and additional fields
* found in the dataframe (they may contain links).
*/
export const getAllFields = (row: LogRowModel, getFieldLinks?: GetFieldLinksFn) => {
return getDataframeFields(row, getFieldLinks);
};
/**
* A log line may contain many links that would all need to go on their own logs detail row
* This iterates through and creates a FieldDef (row) per link.
*/
export const createLogLineLinks = (hiddenFieldsWithLinks: FieldDef[]): FieldDef[] => {
let fieldsWithLinksFromVariableMap: FieldDef[] = [];
hiddenFieldsWithLinks.forEach((linkField) => {
linkField.links?.forEach((link: LinkModel | ExploreFieldLinkModel) => {
if ('variables' in link && link.variables.length > 0) {
// convert ExploreFieldLinkModel to LinkModel by omitting variables field
const fieldDefFromLink: LinkModel = {
href: link.href,
title: link.title,
origin: link.origin,
onClick: link.onClick,
target: link.target,
};
const variableKeys = link.variables.map((variable) => {
const varName = variable.variableName;
const fieldPath = variable.fieldPath ? `.${variable.fieldPath}` : '';
return `${varName}${fieldPath}`;
});
const variableValues = link.variables.map((variable) => (variable.found ? variable.value : ''));
fieldsWithLinksFromVariableMap.push({
keys: variableKeys,
values: variableValues,
links: [fieldDefFromLink],
fieldIndex: linkField.fieldIndex,
});
}
});
});
return fieldsWithLinksFromVariableMap;
};
/**
* creates fields from the dataframe-fields, adding data-links, when field.config.links exists
*/
export const getDataframeFields = (row: LogRowModel, getFieldLinks?: GetFieldLinksFn): FieldDef[] => {
const nonEmptyVisibleFields = getNonEmptyVisibleFields(row);
return nonEmptyVisibleFields.map((field) => {
const vars: ScopedVars = {
__labels: {
text: 'Labels',
value: {
tags: { ...row.labels },
},
},
};
const links = getFieldLinks ? getFieldLinks(field, row.rowIndex, row.dataFrame, vars) : [];
const fieldVal = field.values[row.rowIndex];
const outputVal =
typeof fieldVal === 'string' || typeof fieldVal === 'number' ? fieldVal.toString() : safeStringifyValue(fieldVal);
return {
keys: [field.name],
values: [outputVal],
links: links,
fieldIndex: field.index,
};
});
};
type VisOptions = {
keepTimestamp?: boolean;
keepBody?: boolean;
};
// return the fields (their indices to be exact) that should be visible
// based on the logs dataframe structure
function getVisibleFieldIndices(frame: DataFrame, opts: VisOptions): Set<number> {
const logsFrame = parseLogsFrame(frame);
if (logsFrame === null) {
// should not really happen
return new Set();
}
// we want to show every "extra" field
const visibleFieldIndices = new Set(logsFrame.extraFields.map((f) => f.index));
// we always show the severity field
if (logsFrame.severityField !== null) {
visibleFieldIndices.add(logsFrame.severityField.index);
}
if (opts.keepBody) {
visibleFieldIndices.add(logsFrame.bodyField.index);
}
if (opts.keepTimestamp) {
visibleFieldIndices.add(logsFrame.timeField.index);
}
return visibleFieldIndices;
}
// split the dataframe's fields into visible and hidden arrays.
// note: does not do any row-level checks,
// for example does not check if the field's values are nullish
// or not at a givn row.
export function separateVisibleFields(
frame: DataFrame,
opts?: VisOptions
): { visible: FieldWithIndex[]; hidden: FieldWithIndex[] } {
const fieldsWithIndex: FieldWithIndex[] = frame.fields.map((field, index) => ({ ...field, index }));
const visibleFieldIndices = getVisibleFieldIndices(frame, opts ?? {});
const [visible, hidden] = partition(fieldsWithIndex, (f) => {
// hidden fields are always hidden
if (f.config.custom?.hidden) {
return false;
}
// fields with data-links are visible
if ((f.config.links ?? []).length > 0) {
return true;
}
return visibleFieldIndices.has(f.index);
});
return { visible, hidden };
}
// Optimized version of separateVisibleFields() to only return visible fields for getAllFields()
function getNonEmptyVisibleFields(row: LogRowModel, opts?: VisOptions): FieldWithIndex[] {
const frame = row.dataFrame;
const visibleFieldIndices = getVisibleFieldIndices(frame, opts ?? {});
const visibleFields: FieldWithIndex[] = [];
for (let index = 0; index < frame.fields.length; index++) {
const field = frame.fields[index];
// ignore empty fields
if (field.values[row.rowIndex] == null) {
continue;
}
// hidden fields are always hidden
if (field.config.custom?.hidden) {
continue;
}
// fields with data-links are visible
if ((field.config.links && field.config.links.length > 0) || visibleFieldIndices.has(index)) {
visibleFields.push({ ...field, index });
}
}
return visibleFields;
}