|
|
|
@ -18,7 +18,7 @@ import { getLogRowStyles } from './getLogRowStyles'; |
|
|
|
|
import { stylesFactory } from '../../themes/stylesFactory'; |
|
|
|
|
import { selectThemeVariant } from '../../themes/selectThemeVariant'; |
|
|
|
|
|
|
|
|
|
import { parseMessage, FieldDef } from './logParser'; |
|
|
|
|
import { getAllFields } from './logParser'; |
|
|
|
|
|
|
|
|
|
//Components
|
|
|
|
|
import { LogDetailsRow } from './LogDetailsRow'; |
|
|
|
@ -61,61 +61,6 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { |
|
|
|
|
class UnThemedLogDetails extends PureComponent<Props> { |
|
|
|
|
getParser = memoizeOne(getParser); |
|
|
|
|
|
|
|
|
|
getDerivedFields = memoizeOne((row: LogRowModel): FieldDef[] => { |
|
|
|
|
return ( |
|
|
|
|
row.dataFrame.fields |
|
|
|
|
.map((field, index) => ({ ...field, index })) |
|
|
|
|
// Remove Id which we use for react key and entry field which we are showing as the log message. Also remove hidden fields.
|
|
|
|
|
.filter( |
|
|
|
|
(field, index) => !('id' === field.name || row.entryFieldIndex === index || field.config.custom?.hidden) |
|
|
|
|
) |
|
|
|
|
// Filter out fields without values. For example in elastic the fields are parsed from the document which can
|
|
|
|
|
// have different structure per row and so the dataframe is pretty sparse.
|
|
|
|
|
.filter(field => { |
|
|
|
|
const value = field.values.get(row.rowIndex); |
|
|
|
|
// Not sure exactly what will be the empty value here. And we want to keep 0 as some values can be non
|
|
|
|
|
// string.
|
|
|
|
|
return value !== null && value !== undefined; |
|
|
|
|
}) |
|
|
|
|
.map(field => { |
|
|
|
|
const { getFieldLinks } = this.props; |
|
|
|
|
const links = getFieldLinks ? getFieldLinks(field, row.rowIndex) : []; |
|
|
|
|
return { |
|
|
|
|
key: field.name, |
|
|
|
|
value: field.values.get(row.rowIndex).toString(), |
|
|
|
|
links: links, |
|
|
|
|
fieldIndex: field.index, |
|
|
|
|
}; |
|
|
|
|
}) |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns all fields for log row which consists of fields we parse from the message itself and any derived fields |
|
|
|
|
* setup in data source config. |
|
|
|
|
*/ |
|
|
|
|
getAllFields = memoizeOne((row: LogRowModel) => { |
|
|
|
|
const fields = parseMessage(row.entry); |
|
|
|
|
const derivedFields = this.getDerivedFields(row); |
|
|
|
|
const fieldsMap = [...derivedFields, ...fields].reduce((acc, field) => { |
|
|
|
|
// Strip enclosing quotes for hashing. When values are parsed from log line the quotes are kept, but if same
|
|
|
|
|
// value is in the dataFrame it will be without the quotes. We treat them here as the same value.
|
|
|
|
|
const value = field.value.replace(/(^")|("$)/g, ''); |
|
|
|
|
const fieldHash = `${field.key}=${value}`; |
|
|
|
|
if (acc[fieldHash]) { |
|
|
|
|
acc[fieldHash].links = [...(acc[fieldHash].links || []), ...(field.links || [])]; |
|
|
|
|
} else { |
|
|
|
|
acc[fieldHash] = field; |
|
|
|
|
} |
|
|
|
|
return acc; |
|
|
|
|
}, {} as { [key: string]: FieldDef }); |
|
|
|
|
|
|
|
|
|
const allFields = Object.values(fieldsMap); |
|
|
|
|
allFields.sort(sortFieldsLinkFirst); |
|
|
|
|
|
|
|
|
|
return allFields; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
getStatsForParsedField = (key: string) => { |
|
|
|
|
const matcher = this.getParser(this.props.row.entry)!.buildMatcher(key); |
|
|
|
|
return calculateFieldStats(this.props.getRows(), matcher); |
|
|
|
@ -135,12 +80,13 @@ class UnThemedLogDetails extends PureComponent<Props> { |
|
|
|
|
onClickShowParsedField, |
|
|
|
|
onClickHideParsedField, |
|
|
|
|
showParsedFields, |
|
|
|
|
getFieldLinks, |
|
|
|
|
} = this.props; |
|
|
|
|
const style = getLogRowStyles(theme, row.logLevel); |
|
|
|
|
const styles = getStyles(theme); |
|
|
|
|
const labels = row.labels ? row.labels : {}; |
|
|
|
|
const labelsAvailable = Object.keys(labels).length > 0; |
|
|
|
|
const fields = this.getAllFields(row); |
|
|
|
|
const fields = getAllFields(row, getFieldLinks); |
|
|
|
|
const parsedFieldsAvailable = fields && fields.length > 0; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
@ -219,15 +165,5 @@ class UnThemedLogDetails extends PureComponent<Props> { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function sortFieldsLinkFirst(fieldA: FieldDef, fieldB: FieldDef) { |
|
|
|
|
if (fieldA.links?.length && !fieldB.links?.length) { |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
if (!fieldA.links?.length && fieldB.links?.length) { |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
return fieldA.key > fieldB.key ? 1 : fieldA.key < fieldB.key ? -1 : 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export const LogDetails = withTheme(UnThemedLogDetails); |
|
|
|
|
LogDetails.displayName = 'LogDetails'; |
|
|
|
|