mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 18:34:52 -06:00
* bring in source from database
* bring in transformations from database
* add regex transformations to scopevar
* Consolidate types, add better example, cleanup
* Add var only if match
* Change ScopedVar to not require text, do not leak transformation-made variables between links
* Add mappings and start implementing logfmt
* Add mappings and start implementing logfmt
* Remove mappings, turn off global regex
* Add example yaml and omit transformations if empty
* Fix the yaml
* Add logfmt transformation
* Cleanup transformations and yaml
* add transformation field to FE types and use it, safeStringify logfmt values
* Add tests, only safe stringify if non-string, fix bug with safe stringify where it would return empty string with false value
* Add test for transformation field
* Do not add null transformations object
* Add provisioning (to be removed) and show log lines with links
* Only display links if change to query was made
* Break out transformation logic, add tests to backend code
* Fix lint errors I understand 😅
* Fix the backend lint error
* Remove unnecessary code and mark new Transformations object as internal
* Add support for named capture groups
* Remove type assertion
* Remove variable name from transformation
* Add test for overriding regexes
* Add back variable name field, but change to mapValue
* fix go api test
* Change transformation types to enum, add better provisioning checks for bad type name and format
* Change transformation types to enum, add better provisioning checks for bad type name and format
* Check for expression with regex transformations
* Remove isInterpolated variable, add option to always use format function
* Add template variable check to links
* Use new functions
* Filter log line at render, remove extra createSpanLink imports
* Add scrollable to long log messages
* Remove test that is no longer accurate
* Remove test correlation
* Add tests, fix duplicate key issue
* WIP: show log line links key/value pairs
* Some not great style changes
* Change LogDetailsRow for better multi value formatting
* Cleanup
* Add additional information around variable regex, implement PR feedback
* Display name with fieldPath if applicable
* Add variables with fieldPaths to test
* Count empty string as undefined variable
* Add better commented version of function, fix tests by removing new variable
* Modify when links show
* Remove sample yaml
* If a link has no variables, set value to field name, and some formatting issues
* Add comments and change variable names to be more clear, add back logic where needed, add test coverage for new scenario
* Fix formatting of replaceInVariableRegex comment
* Remove changes from Grafana-data, move logic into explore
* Rename function and property to match similar format
* Move types to type files and consolidate definitions, rename functions, change field definitions to accept arrays of keys/values, move function to parser, hide actions on multi key/value rows
* Add tests to logParser’s new function
153 lines
4.2 KiB
TypeScript
153 lines
4.2 KiB
TypeScript
import { countBy, chain } from 'lodash';
|
|
|
|
import { LogLevel, LogRowModel, LogLabelStatsModel, LogsModel, LogsSortOrder } from '@grafana/data';
|
|
|
|
import { getDataframeFields } from './components/logParser';
|
|
|
|
/**
|
|
* Returns the log level of a log line.
|
|
* Parse the line for level words. If no level is found, it returns `LogLevel.unknown`.
|
|
*
|
|
* Example: `getLogLevel('WARN 1999-12-31 this is great') // LogLevel.warn`
|
|
*/
|
|
export function getLogLevel(line: string): LogLevel {
|
|
if (!line) {
|
|
return LogLevel.unknown;
|
|
}
|
|
let level = LogLevel.unknown;
|
|
let currentIndex: number | undefined = undefined;
|
|
|
|
for (const key of Object.keys(LogLevel)) {
|
|
const regexp = new RegExp(`\\b${key}\\b`, 'i');
|
|
const result = regexp.exec(line);
|
|
|
|
if (result) {
|
|
if (currentIndex === undefined || result.index < currentIndex) {
|
|
level = (LogLevel as any)[key];
|
|
currentIndex = result.index;
|
|
}
|
|
}
|
|
}
|
|
return level;
|
|
}
|
|
|
|
export function getLogLevelFromKey(key: string | number): LogLevel {
|
|
const level = (LogLevel as any)[key.toString().toLowerCase()];
|
|
if (level) {
|
|
return level;
|
|
}
|
|
|
|
return LogLevel.unknown;
|
|
}
|
|
|
|
export function calculateLogsLabelStats(rows: LogRowModel[], label: string): LogLabelStatsModel[] {
|
|
// Consider only rows that have the given label
|
|
const rowsWithLabel = rows.filter((row) => row.labels[label] !== undefined);
|
|
const rowCount = rowsWithLabel.length;
|
|
|
|
// Get label value counts for eligible rows
|
|
const countsByValue = countBy(rowsWithLabel, (row) => (row as LogRowModel).labels[label]);
|
|
return getSortedCounts(countsByValue, rowCount);
|
|
}
|
|
|
|
export function calculateStats(values: unknown[]): LogLabelStatsModel[] {
|
|
const nonEmptyValues = values.filter((value) => value !== undefined && value !== null);
|
|
const countsByValue = countBy(nonEmptyValues);
|
|
return getSortedCounts(countsByValue, nonEmptyValues.length);
|
|
}
|
|
|
|
const getSortedCounts = (countsByValue: { [value: string]: number }, rowCount: number) => {
|
|
return chain(countsByValue)
|
|
.map((count, value) => ({ count, value, proportion: count / rowCount }))
|
|
.sortBy('count')
|
|
.reverse()
|
|
.value();
|
|
};
|
|
|
|
export const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
|
// compare milliseconds
|
|
if (a.timeEpochMs < b.timeEpochMs) {
|
|
return -1;
|
|
}
|
|
|
|
if (a.timeEpochMs > b.timeEpochMs) {
|
|
return 1;
|
|
}
|
|
|
|
// if milliseconds are equal, compare nanoseconds
|
|
if (a.timeEpochNs < b.timeEpochNs) {
|
|
return -1;
|
|
}
|
|
|
|
if (a.timeEpochNs > b.timeEpochNs) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
export const sortInDescendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
|
// compare milliseconds
|
|
if (a.timeEpochMs > b.timeEpochMs) {
|
|
return -1;
|
|
}
|
|
|
|
if (a.timeEpochMs < b.timeEpochMs) {
|
|
return 1;
|
|
}
|
|
|
|
// if milliseconds are equal, compare nanoseconds
|
|
if (a.timeEpochNs > b.timeEpochNs) {
|
|
return -1;
|
|
}
|
|
|
|
if (a.timeEpochNs < b.timeEpochNs) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
export const sortLogsResult = (logsResult: LogsModel | null, sortOrder: LogsSortOrder): LogsModel => {
|
|
const rows = logsResult ? sortLogRows(logsResult.rows, sortOrder) : [];
|
|
return logsResult ? { ...logsResult, rows } : { hasUniqueLabels: false, rows };
|
|
};
|
|
|
|
export const sortLogRows = (logRows: LogRowModel[], sortOrder: LogsSortOrder) =>
|
|
sortOrder === LogsSortOrder.Ascending ? logRows.sort(sortInAscendingOrder) : logRows.sort(sortInDescendingOrder);
|
|
|
|
// Currently supports only error condition in Loki logs
|
|
export const checkLogsError = (logRow: LogRowModel): { hasError: boolean; errorMessage?: string } => {
|
|
if (logRow.labels.__error__) {
|
|
return {
|
|
hasError: true,
|
|
errorMessage: logRow.labels.__error__,
|
|
};
|
|
}
|
|
return {
|
|
hasError: false,
|
|
};
|
|
};
|
|
|
|
export const escapeUnescapedString = (string: string) =>
|
|
string.replace(/\\r\\n|\\n|\\t|\\r/g, (match: string) => (match.slice(1) === 't' ? '\t' : '\n'));
|
|
|
|
export function logRowsToReadableJson(logs: LogRowModel[]) {
|
|
return logs.map((log) => {
|
|
const fields = getDataframeFields(log).reduce<Record<string, string>>((acc, field) => {
|
|
const key = field.keys[0];
|
|
acc[key] = field.values[0];
|
|
return acc;
|
|
}, {});
|
|
|
|
return {
|
|
line: log.entry,
|
|
timestamp: log.timeEpochNs,
|
|
fields: {
|
|
...fields,
|
|
...log.labels,
|
|
},
|
|
};
|
|
});
|
|
}
|