New Logs Panel: fix unwrapped logs display (#107391)
* LogLine: remove new lines in unwrapped mode
* Displayed fields: fix displayed fields when the body is included
* LogLine: prevent overflows from extremely long lines with incorrect width measurement
* Update tests
* virtualization: test calculateFieldDimensions
* getGridTemplateColumns: consider displayed fields to set the grid sizes
* LogList: hide overflowing fields in unwrapped mode
* processing: extract regex
* LogLine: improve hover and selected state
* virtualization: strip ansi color codes for measurement
* Update tests
* LogLine: improve log line pre-resize state
* Revert "LogLine: improve log line pre-resize state"
This reverts commit a6b4ddded5.
* LogLine: improve hover/active color
This commit is contained in:
@@ -46,6 +46,7 @@ export const createLogLine = (
|
||||
order: LogsSortOrder.Descending,
|
||||
timeZone: 'browser',
|
||||
virtualization: undefined,
|
||||
wrapLogMessage: true,
|
||||
}
|
||||
): LogListModel => {
|
||||
const logs = preProcessLogs([createLogRow(overrides)], processOptions);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CoreApp, createTheme, LogsDedupStrategy, LogsSortOrder } from '@grafana
|
||||
import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
|
||||
import { createLogLine } from '../__mocks__/logRow';
|
||||
|
||||
import { getStyles, LogLine, Props } from './LogLine';
|
||||
import { getGridTemplateColumns, getStyles, LogLine, Props } from './LogLine';
|
||||
import { LogListFontSize } from './LogList';
|
||||
import { LogListContextProvider } from './LogListContext';
|
||||
import { LogListSearchContext } from './LogListSearchContext';
|
||||
@@ -36,7 +36,7 @@ describe.each(fontSizes)('LogLine', (fontSize: LogListFontSize) => {
|
||||
beforeEach(() => {
|
||||
log = createLogLine(
|
||||
{ labels: { place: 'luna' }, entry: `log message 1` },
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization, wrapLogMessage: true }
|
||||
);
|
||||
contextProps.logs = [log];
|
||||
contextProps.fontSize = fontSize;
|
||||
@@ -226,7 +226,7 @@ describe.each(fontSizes)('LogLine', (fontSize: LogListFontSize) => {
|
||||
jest.spyOn(virtualization, 'getTruncationLength').mockReturnValue(5);
|
||||
log = createLogLine(
|
||||
{ labels: { place: 'luna' }, entry: `log message 1` },
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization, wrapLogMessage: true }
|
||||
);
|
||||
});
|
||||
|
||||
@@ -425,3 +425,89 @@ describe.each(fontSizes)('LogLine', (fontSize: LogListFontSize) => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGridTemplateColumns', () => {
|
||||
test('Gets the template columns for the default visualization mode', () => {
|
||||
expect(
|
||||
getGridTemplateColumns(
|
||||
[
|
||||
{
|
||||
field: 'timestamp',
|
||||
width: 23,
|
||||
},
|
||||
{
|
||||
field: 'level',
|
||||
width: 4,
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
).toBe('23px 4px 1fr');
|
||||
});
|
||||
|
||||
test('Gets the template columns when displayed fields are used', () => {
|
||||
expect(
|
||||
getGridTemplateColumns(
|
||||
[
|
||||
{
|
||||
field: 'timestamp',
|
||||
width: 23,
|
||||
},
|
||||
{
|
||||
field: 'level',
|
||||
width: 4,
|
||||
},
|
||||
],
|
||||
['field']
|
||||
)
|
||||
).toBe('23px 4px');
|
||||
});
|
||||
|
||||
test('Gets the template columns when displayed fields are used', () => {
|
||||
expect(
|
||||
getGridTemplateColumns(
|
||||
[
|
||||
{
|
||||
field: 'timestamp',
|
||||
width: 23,
|
||||
},
|
||||
{
|
||||
field: 'level',
|
||||
width: 4,
|
||||
},
|
||||
{
|
||||
field: 'field',
|
||||
width: 4,
|
||||
},
|
||||
],
|
||||
['field']
|
||||
)
|
||||
).toBe('23px 4px 4px');
|
||||
});
|
||||
|
||||
test('Gets the template columns when displayed fields are used', () => {
|
||||
expect(
|
||||
getGridTemplateColumns(
|
||||
[
|
||||
{
|
||||
field: 'timestamp',
|
||||
width: 23,
|
||||
},
|
||||
{
|
||||
field: 'level',
|
||||
width: 4,
|
||||
},
|
||||
{
|
||||
field: 'field',
|
||||
width: 4,
|
||||
},
|
||||
{
|
||||
field: LOG_LINE_BODY_FIELD_NAME,
|
||||
width: 20,
|
||||
},
|
||||
],
|
||||
['field']
|
||||
)
|
||||
).toBe('23px 4px 4px 20px');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -192,7 +192,7 @@ const LogLineComponent = memo(
|
||||
{/* A button element could be used but in Safari it prevents text selection. Fallback available for a11y in LogLineMenu */}
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */}
|
||||
<div
|
||||
className={`${wrapLogMessage ? styles.wrappedLogLine : `${styles.unwrappedLogLine} unwrapped-log-line`} ${collapsed === true ? styles.collapsedLogLine : ''} ${enableLogDetails ? styles.clickable : ''}`}
|
||||
className={`${styles.fieldsWrapper} ${detailsShown ? styles.detailsDisplayed : ''} ${wrapLogMessage ? styles.wrappedLogLine : `${styles.unwrappedLogLine} unwrapped-log-line`} ${collapsed === true ? styles.collapsedLogLine : ''} ${enableLogDetails ? styles.clickable : ''}`}
|
||||
style={
|
||||
collapsed && virtualization
|
||||
? { maxHeight: `${virtualization.getTruncationLineCount() * virtualization.getLineHeight()}px` }
|
||||
@@ -350,9 +350,10 @@ const LogLineBody = ({ log, styles }: { log: LogListModel; styles: LogLineStyles
|
||||
return <span className="field log-syntax-highlight" dangerouslySetInnerHTML={{ __html: log.highlightedBody }} />;
|
||||
};
|
||||
|
||||
export function getGridTemplateColumns(dimensions: LogFieldDimension[]) {
|
||||
export function getGridTemplateColumns(dimensions: LogFieldDimension[], displayedFields: string[]) {
|
||||
const columns = dimensions.map((dimension) => dimension.width).join('px ');
|
||||
return `${columns}px 1fr`;
|
||||
const logLineWidth = displayedFields.length > 0 ? '' : ' 1fr';
|
||||
return `${columns}px${logLineWidth}`;
|
||||
}
|
||||
|
||||
export type LogLineStyles = ReturnType<typeof getStyles>;
|
||||
@@ -368,6 +369,8 @@ export const getStyles = (theme: GrafanaTheme2, virtualization?: LogLineVirtuali
|
||||
parsedField: theme.colors.text.primary,
|
||||
};
|
||||
|
||||
const hoverColor = tinycolor(theme.colors.background.canvas).darken(4).toRgbString();
|
||||
|
||||
return {
|
||||
logLine: css({
|
||||
color: tinycolor(theme.colors.text.secondary).setAlpha(0.75).toRgbString(),
|
||||
@@ -379,7 +382,7 @@ export const getStyles = (theme: GrafanaTheme2, virtualization?: LogLineVirtuali
|
||||
lineHeight: theme.typography.body.lineHeight,
|
||||
wordBreak: 'break-all',
|
||||
'&:hover': {
|
||||
background: theme.isDark ? `hsla(0, 0%, 0%, 0.3)` : `hsla(0, 0%, 0%, 0.1)`,
|
||||
background: hoverColor,
|
||||
},
|
||||
'&.infinite-scroll': {
|
||||
'&::before': {
|
||||
@@ -440,7 +443,7 @@ export const getStyles = (theme: GrafanaTheme2, virtualization?: LogLineVirtuali
|
||||
lineHeight: theme.typography.bodySmall.lineHeight,
|
||||
}),
|
||||
detailsDisplayed: css({
|
||||
background: theme.isDark ? `hsla(0, 0%, 0%, 0.5)` : `hsla(0, 0%, 0%, 0.1)`,
|
||||
background: hoverColor,
|
||||
}),
|
||||
pinnedLogLine: css({
|
||||
backgroundColor: tinycolor(theme.colors.info.transparent).setAlpha(0.25).toString(),
|
||||
@@ -525,6 +528,9 @@ export const getStyles = (theme: GrafanaTheme2, virtualization?: LogLineVirtuali
|
||||
gridColumnGap: theme.spacing(FIELD_GAP_MULTIPLIER),
|
||||
whiteSpace: 'pre',
|
||||
paddingBottom: theme.spacing(0.75),
|
||||
'& .field': {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
}),
|
||||
wrappedLogLine: css({
|
||||
alignSelf: 'flex-start',
|
||||
@@ -537,6 +543,11 @@ export const getStyles = (theme: GrafanaTheme2, virtualization?: LogLineVirtuali
|
||||
marginRight: 0,
|
||||
},
|
||||
}),
|
||||
fieldsWrapper: css({
|
||||
'&:hover': {
|
||||
background: hoverColor,
|
||||
},
|
||||
}),
|
||||
collapsedLogLine: css({
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
|
||||
@@ -248,7 +248,7 @@ const LogListComponent = ({
|
||||
() => (wrapLogMessage ? [] : virtualization.calculateFieldDimensions(processedLogs, displayedFields)),
|
||||
[displayedFields, processedLogs, virtualization, wrapLogMessage]
|
||||
);
|
||||
const styles = useStyles2(getStyles, dimensions, { showTime });
|
||||
const styles = useStyles2(getStyles, dimensions, displayedFields, { showTime });
|
||||
const widthContainer = wrapperRef.current ?? containerElement;
|
||||
const {
|
||||
closePopoverMenu,
|
||||
@@ -283,13 +283,13 @@ const LogListComponent = ({
|
||||
setProcessedLogs(
|
||||
preProcessLogs(
|
||||
logs,
|
||||
{ getFieldLinks, escape: forceEscape ?? false, order: sortOrder, timeZone, virtualization },
|
||||
{ getFieldLinks, escape: forceEscape ?? false, order: sortOrder, timeZone, virtualization, wrapLogMessage },
|
||||
grammar
|
||||
)
|
||||
);
|
||||
virtualization.resetLogLineSizes();
|
||||
listRef.current?.resetAfterIndex(0);
|
||||
}, [forceEscape, getFieldLinks, grammar, loading, logs, sortOrder, timeZone, virtualization]);
|
||||
}, [forceEscape, getFieldLinks, grammar, loading, logs, sortOrder, timeZone, virtualization, wrapLogMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
listRef.current?.resetAfterIndex(0);
|
||||
@@ -469,13 +469,18 @@ const LogListComponent = ({
|
||||
);
|
||||
};
|
||||
|
||||
function getStyles(theme: GrafanaTheme2, dimensions: LogFieldDimension[], { showTime }: { showTime: boolean }) {
|
||||
function getStyles(
|
||||
theme: GrafanaTheme2,
|
||||
dimensions: LogFieldDimension[],
|
||||
displayedFields: string[],
|
||||
{ showTime }: { showTime: boolean }
|
||||
) {
|
||||
const columns = showTime ? dimensions : dimensions.filter((_, index) => index > 0);
|
||||
return {
|
||||
logList: css({
|
||||
'& .unwrapped-log-line': {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: getGridTemplateColumns(columns),
|
||||
gridTemplateColumns: getGridTemplateColumns(columns, displayedFields),
|
||||
},
|
||||
}),
|
||||
logListContainer: css({
|
||||
|
||||
@@ -79,6 +79,7 @@ describe('preProcessLogs', () => {
|
||||
getFieldLinks,
|
||||
order: LogsSortOrder.Descending,
|
||||
timeZone: 'browser',
|
||||
wrapLogMessage: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,9 +93,53 @@ describe('preProcessLogs', () => {
|
||||
entry: `35.191.12.195 - accounts.google.com:test@grafana.com [18/Mar/2025:08:58:38 +0000] 200 "POST /grafana/api/ds/query?ds_type=prometheus&requestId=SQR461 HTTP/1.1" 59460 "https://test.example.com/?orgId=1" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" "95.91.240.90, 34.107.247.24"`,
|
||||
logLevel: LogLevel.critical,
|
||||
});
|
||||
const logListModel = new LogListModel(logRowModel, { escape: false, timeZone: 'browser ' });
|
||||
const logListModel = new LogListModel(logRowModel, { escape: false, timeZone: 'browser ', wrapLogMessage: true });
|
||||
expect(logListModel).toMatchObject(logRowModel);
|
||||
});
|
||||
|
||||
test('Unwrapped log lines strip new lines', () => {
|
||||
const logListModel = createLogLine(
|
||||
{ labels: { place: `lu\nna` }, entry: `log\n message\n 1` },
|
||||
{
|
||||
escape: false,
|
||||
order: LogsSortOrder.Descending,
|
||||
timeZone: 'browser',
|
||||
wrapLogMessage: false, // unwrapped
|
||||
}
|
||||
);
|
||||
expect(logListModel.getDisplayedFieldValue('place')).toBe('luna');
|
||||
expect(logListModel.body).toBe('log message 1');
|
||||
});
|
||||
|
||||
test('Wrapped log lines do not modify new lines', () => {
|
||||
const logListModel = createLogLine(
|
||||
{ labels: { place: `lu\nna` }, entry: `log\n message\n 1` },
|
||||
{
|
||||
escape: false,
|
||||
order: LogsSortOrder.Descending,
|
||||
timeZone: 'browser',
|
||||
wrapLogMessage: true, // wrapped
|
||||
}
|
||||
);
|
||||
expect(logListModel.getDisplayedFieldValue('place')).toBe(logListModel.labels['place']);
|
||||
expect(logListModel.body).toBe(logListModel.raw);
|
||||
});
|
||||
|
||||
test('Strips ansi colors for measurement', () => {
|
||||
const logListModel = createLogLine(
|
||||
{ entry: `log \u001B[31mmessage\u001B[0m 1` },
|
||||
{
|
||||
escape: false,
|
||||
order: LogsSortOrder.Descending,
|
||||
timeZone: 'browser',
|
||||
wrapLogMessage: true,
|
||||
}
|
||||
);
|
||||
expect(logListModel.getDisplayedFieldValue(LOG_LINE_BODY_FIELD_NAME, false)).toBe(
|
||||
`log \u001B[31mmessage\u001B[0m 1`
|
||||
);
|
||||
expect(logListModel.getDisplayedFieldValue(LOG_LINE_BODY_FIELD_NAME, true)).toBe('log message 1');
|
||||
});
|
||||
});
|
||||
|
||||
test('Orders logs', () => {
|
||||
@@ -176,7 +221,7 @@ describe('preProcessLogs', () => {
|
||||
entry = new Array(2 * virtualization.getTruncationLength(null)).fill('e').join('');
|
||||
longLog = createLogLine(
|
||||
{ entry, labels: { field: 'value' } },
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization, wrapLogMessage: true }
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ansicolor from 'ansicolor';
|
||||
import Prism, { Grammar } from 'prismjs';
|
||||
|
||||
import { DataFrame, dateTimeFormat, Labels, LogLevel, LogRowModel, LogsSortOrder } from '@grafana/data';
|
||||
@@ -11,6 +12,7 @@ import { generateLogGrammar, generateTextMatchGrammar } from './grammar';
|
||||
import { LogLineVirtualization } from './virtualization';
|
||||
|
||||
const TRUNCATION_DEFAULT_LENGTH = 50000;
|
||||
const NEWLINES_REGEX = /(\r\n|\n|\r)/g;
|
||||
|
||||
export class LogListModel implements LogRowModel {
|
||||
collapsed: boolean | undefined = undefined;
|
||||
@@ -47,8 +49,12 @@ export class LogListModel implements LogRowModel {
|
||||
private _fields: FieldDef[] | undefined = undefined;
|
||||
private _getFieldLinks: GetFieldLinksFn | undefined = undefined;
|
||||
private _virtualization?: LogLineVirtualization;
|
||||
private _wrapLogMessage: boolean;
|
||||
|
||||
constructor(log: LogRowModel, { escape, getFieldLinks, grammar, timeZone, virtualization }: PreProcessLogOptions) {
|
||||
constructor(
|
||||
log: LogRowModel,
|
||||
{ escape, getFieldLinks, grammar, timeZone, virtualization, wrapLogMessage }: PreProcessLogOptions
|
||||
) {
|
||||
// LogRowModel
|
||||
this.datasourceType = log.datasourceType;
|
||||
this.dataFrame = log.dataFrame;
|
||||
@@ -82,6 +88,7 @@ export class LogListModel implements LogRowModel {
|
||||
defaultWithMS: true,
|
||||
});
|
||||
this._virtualization = virtualization;
|
||||
this._wrapLogMessage = wrapLogMessage;
|
||||
|
||||
let raw = log.raw;
|
||||
if (escape && log.hasUnescapedContent) {
|
||||
@@ -95,6 +102,9 @@ export class LogListModel implements LogRowModel {
|
||||
this._body = this.collapsed
|
||||
? this.raw.substring(0, this._virtualization?.getTruncationLength(null) ?? TRUNCATION_DEFAULT_LENGTH)
|
||||
: this.raw;
|
||||
if (!this._wrapLogMessage) {
|
||||
this._body = this._body.replace(NEWLINES_REGEX, '');
|
||||
}
|
||||
}
|
||||
return this._body;
|
||||
}
|
||||
@@ -123,25 +133,31 @@ export class LogListModel implements LogRowModel {
|
||||
return checkLogsSampled(this);
|
||||
}
|
||||
|
||||
getDisplayedFieldValue(fieldName: string): string {
|
||||
getDisplayedFieldValue(fieldName: string, stripAnsi = false): string {
|
||||
if (fieldName === LOG_LINE_BODY_FIELD_NAME) {
|
||||
return this.body;
|
||||
return stripAnsi ? ansicolor.strip(this.body) : this.body;
|
||||
}
|
||||
let fieldValue = '';
|
||||
if (this.labels[fieldName] != null) {
|
||||
return this.labels[fieldName];
|
||||
}
|
||||
const field = this.fields.find((field) => {
|
||||
return field.keys[0] === fieldName;
|
||||
});
|
||||
fieldValue = this.labels[fieldName];
|
||||
} else {
|
||||
const field = this.fields.find((field) => {
|
||||
return field.keys[0] === fieldName;
|
||||
});
|
||||
|
||||
return field ? field.values.toString() : '';
|
||||
fieldValue = field ? field.values.toString() : '';
|
||||
}
|
||||
if (!this._wrapLogMessage) {
|
||||
return fieldValue.replace(NEWLINES_REGEX, '');
|
||||
}
|
||||
return fieldValue;
|
||||
}
|
||||
|
||||
updateCollapsedState(displayedFields: string[], container: HTMLDivElement | null) {
|
||||
const lineLength =
|
||||
displayedFields.length > 0
|
||||
? displayedFields.map((field) => this.getDisplayedFieldValue(field)).join('').length
|
||||
: this.raw.length;
|
||||
? displayedFields.map((field) => this.getDisplayedFieldValue(field, true)).join('').length
|
||||
: this.entry.length;
|
||||
const collapsed =
|
||||
lineLength >= (this._virtualization?.getTruncationLength(container) ?? TRUNCATION_DEFAULT_LENGTH)
|
||||
? true
|
||||
@@ -172,15 +188,18 @@ export interface PreProcessOptions {
|
||||
order: LogsSortOrder;
|
||||
timeZone: string;
|
||||
virtualization?: LogLineVirtualization;
|
||||
wrapLogMessage: boolean;
|
||||
}
|
||||
|
||||
export const preProcessLogs = (
|
||||
logs: LogRowModel[],
|
||||
{ escape, getFieldLinks, order, timeZone, virtualization }: PreProcessOptions,
|
||||
{ escape, getFieldLinks, order, timeZone, virtualization, wrapLogMessage }: PreProcessOptions,
|
||||
grammar?: Grammar
|
||||
): LogListModel[] => {
|
||||
const orderedLogs = sortLogRows(logs, order);
|
||||
return orderedLogs.map((log) => preProcessLog(log, { escape, getFieldLinks, grammar, timeZone, virtualization }));
|
||||
return orderedLogs.map((log) =>
|
||||
preProcessLog(log, { escape, getFieldLinks, grammar, timeZone, virtualization, wrapLogMessage })
|
||||
);
|
||||
};
|
||||
|
||||
interface PreProcessLogOptions {
|
||||
@@ -189,6 +208,7 @@ interface PreProcessLogOptions {
|
||||
grammar?: Grammar;
|
||||
timeZone: string;
|
||||
virtualization?: LogLineVirtualization;
|
||||
wrapLogMessage: boolean;
|
||||
}
|
||||
const preProcessLog = (log: LogRowModel, options: PreProcessLogOptions): LogListModel => {
|
||||
return new LogListModel(log, options);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createTheme, LogsSortOrder } from '@grafana/data';
|
||||
import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
|
||||
import { createLogLine } from '../__mocks__/logRow';
|
||||
|
||||
import { LogListModel } from './processing';
|
||||
import { LogListModel, PreProcessOptions } from './processing';
|
||||
import { LogLineVirtualization, getLogLineSize, DisplayOptions } from './virtualization';
|
||||
|
||||
describe('Virtualization', () => {
|
||||
@@ -28,12 +28,16 @@ describe('Virtualization', () => {
|
||||
hasSampledLogs: false,
|
||||
};
|
||||
|
||||
const preProcessOptions: PreProcessOptions = {
|
||||
escape: false,
|
||||
order: LogsSortOrder.Descending,
|
||||
timeZone: 'browser',
|
||||
virtualization,
|
||||
wrapLogMessage: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
log = createLogLine(
|
||||
{ labels: { place: 'luna' }, entry: `log message 1` },
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
);
|
||||
//virtualization = new LogLineVirtualization(createTheme(), 'default');
|
||||
log = createLogLine({ labels: { place: 'luna' }, entry: `log message 1` }, preProcessOptions);
|
||||
container = document.createElement('div');
|
||||
jest.spyOn(container, 'clientWidth', 'get').mockReturnValue(CONTAINER_SIZE);
|
||||
LETTER_WIDTH = virtualization.measureTextWidth('e');
|
||||
@@ -86,7 +90,7 @@ describe('Virtualization', () => {
|
||||
entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join(''),
|
||||
logLevel: undefined,
|
||||
},
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
preProcessOptions
|
||||
);
|
||||
|
||||
const size = getLogLineSize(virtualization, [log], container, [], { ...defaultOptions, wrap: true }, 0);
|
||||
@@ -96,7 +100,7 @@ describe('Virtualization', () => {
|
||||
test('Measures a multi-line log line with level, controls, and displayed time', () => {
|
||||
log = createLogLine(
|
||||
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
preProcessOptions
|
||||
);
|
||||
|
||||
const size = getLogLineSize(
|
||||
@@ -118,7 +122,7 @@ describe('Virtualization', () => {
|
||||
entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join(''),
|
||||
logLevel: undefined,
|
||||
},
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
preProcessOptions
|
||||
);
|
||||
|
||||
const size = getLogLineSize(
|
||||
@@ -136,7 +140,7 @@ describe('Virtualization', () => {
|
||||
test('Measures displayed fields in a log line with level, controls, and displayed time', () => {
|
||||
log = createLogLine(
|
||||
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
preProcessOptions
|
||||
);
|
||||
|
||||
const size = getLogLineSize(
|
||||
@@ -154,7 +158,7 @@ describe('Virtualization', () => {
|
||||
test('Measures a multi-line log line with duplicates', () => {
|
||||
log = createLogLine(
|
||||
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
preProcessOptions
|
||||
);
|
||||
log.duplicates = 1;
|
||||
|
||||
@@ -173,7 +177,7 @@ describe('Virtualization', () => {
|
||||
test('Measures a multi-line log line with errors', () => {
|
||||
log = createLogLine(
|
||||
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
preProcessOptions
|
||||
);
|
||||
|
||||
const size = getLogLineSize(
|
||||
@@ -191,7 +195,7 @@ describe('Virtualization', () => {
|
||||
test('Measures a multi-line sampled log line', () => {
|
||||
log = createLogLine(
|
||||
{ labels: { place: 'luna' }, entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join('') },
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
preProcessOptions
|
||||
);
|
||||
|
||||
const size = getLogLineSize(
|
||||
@@ -214,6 +218,29 @@ describe('Virtualization', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateFieldDimensions', () => {
|
||||
test('Measures displayed fields including the log line body', () => {
|
||||
expect(virtualization.calculateFieldDimensions([log], ['place', LOG_LINE_BODY_FIELD_NAME])).toEqual([
|
||||
{
|
||||
field: 'timestamp',
|
||||
width: 23,
|
||||
},
|
||||
{
|
||||
field: 'level',
|
||||
width: 4,
|
||||
},
|
||||
{
|
||||
field: 'place',
|
||||
width: 4,
|
||||
},
|
||||
{
|
||||
field: '___LOG_LINE_BODY___',
|
||||
width: 13,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('With small font size', () => {
|
||||
const virtualization = new LogLineVirtualization(createTheme(), 'small');
|
||||
|
||||
@@ -232,7 +259,7 @@ describe('Virtualization', () => {
|
||||
entry: new Array(TWO_LINES_OF_CHARACTERS).fill('e').join(''),
|
||||
logLevel: undefined,
|
||||
},
|
||||
{ escape: false, order: LogsSortOrder.Descending, timeZone: 'browser', virtualization }
|
||||
preProcessOptions
|
||||
);
|
||||
|
||||
const size = getLogLineSize(
|
||||
|
||||
@@ -2,8 +2,6 @@ import ansicolor from 'ansicolor';
|
||||
|
||||
import { BusEventWithPayload, GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody';
|
||||
|
||||
import { LogListFontSize } from './LogList';
|
||||
import { LogListModel } from './processing';
|
||||
|
||||
@@ -195,7 +193,7 @@ export class LogLineVirtualization {
|
||||
levelWidth = Math.round(width);
|
||||
}
|
||||
for (const field of displayedFields) {
|
||||
width = this.measureTextWidth(logs[i].getDisplayedFieldValue(field));
|
||||
width = this.measureTextWidth(logs[i].getDisplayedFieldValue(field, true));
|
||||
fieldWidths[field] = !fieldWidths[field] || width > fieldWidths[field] ? Math.round(width) : fieldWidths[field];
|
||||
}
|
||||
}
|
||||
@@ -210,10 +208,6 @@ export class LogLineVirtualization {
|
||||
},
|
||||
];
|
||||
for (const field in fieldWidths) {
|
||||
// Skip the log line when it's a displayed field
|
||||
if (field === LOG_LINE_BODY_FIELD_NAME) {
|
||||
continue;
|
||||
}
|
||||
dimensions.push({
|
||||
field,
|
||||
width: fieldWidths[field],
|
||||
@@ -294,7 +288,7 @@ export function getLogLineSize(
|
||||
textToMeasure += logs[index].displayLevel ?? '';
|
||||
}
|
||||
for (const field of displayedFields) {
|
||||
textToMeasure = logs[index].getDisplayedFieldValue(field) + textToMeasure;
|
||||
textToMeasure = logs[index].getDisplayedFieldValue(field, true) + textToMeasure;
|
||||
}
|
||||
if (!displayedFields.length) {
|
||||
textToMeasure += ansicolor.strip(logs[index].body);
|
||||
|
||||
Reference in New Issue
Block a user