[Logs UI] Transmit and render array field values in log entries (#81385)

Co-authored-by: Alejandro Fernández Gómez <antarticonorte@gmail.com>
This commit is contained in:
Felix Stürmer 2020-10-28 12:53:02 +01:00 committed by GitHub
parent 7e0b9ffad0
commit 756c3f16e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 3967 additions and 2969 deletions

View file

@ -5,6 +5,7 @@
*/
import * as rt from 'io-ts';
import { jsonArrayRT } from '../../typed_json';
import { logEntriesCursorRT } from './common';
export const LOG_ENTRIES_PATH = '/api/log_entries/entries';
@ -54,7 +55,7 @@ export const logMessageConstantPartRT = rt.type({
});
export const logMessageFieldPartRT = rt.type({
field: rt.string,
value: rt.unknown,
value: jsonArrayRT,
highlights: rt.array(rt.string),
});
@ -64,7 +65,7 @@ export const logTimestampColumnRT = rt.type({ columnId: rt.string, timestamp: rt
export const logFieldColumnRT = rt.type({
columnId: rt.string,
field: rt.string,
value: rt.unknown,
value: jsonArrayRT,
highlights: rt.array(rt.string),
});
export const logMessageColumnRT = rt.type({

View file

@ -16,7 +16,7 @@ export const logEntriesItemRequestRT = rt.type({
export type LogEntriesItemRequest = rt.TypeOf<typeof logEntriesItemRequestRT>;
const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.string });
const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.array(rt.string) });
const logEntriesItemRT = rt.type({
id: rt.string,
index: rt.string,

View file

@ -4,11 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
export type JsonValue = null | boolean | number | string | JsonObject | JsonArray;
import * as rt from 'io-ts';
import { JsonArray, JsonObject, JsonValue } from '../../../../src/plugins/kibana_utils/common';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface JsonArray extends Array<JsonValue> {}
export const jsonScalarRT = rt.union([rt.null, rt.boolean, rt.number, rt.string]);
export interface JsonObject {
[key: string]: JsonValue;
}
export const jsonValueRT: rt.Type<JsonValue> = rt.recursion('JsonValue', () =>
rt.union([jsonScalarRT, jsonArrayRT, jsonObjectRT])
);
export const jsonArrayRT: rt.Type<JsonArray> = rt.recursion('JsonArray', () =>
rt.array(jsonValueRT)
);
export const jsonObjectRT: rt.Type<JsonObject> = rt.recursion('JsonObject', () =>
rt.record(rt.string, jsonValueRT)
);
export { JsonValue, JsonArray, JsonObject };

View file

@ -29,7 +29,7 @@ describe('LogEntryActionsMenu component', () => {
<ProviderWrapper>
<LogEntryActionsMenu
logItem={{
fields: [{ field: 'host.ip', value: 'HOST_IP' }],
fields: [{ field: 'host.ip', value: ['HOST_IP'] }],
id: 'ITEM_ID',
index: 'INDEX',
key: {
@ -59,7 +59,7 @@ describe('LogEntryActionsMenu component', () => {
<ProviderWrapper>
<LogEntryActionsMenu
logItem={{
fields: [{ field: 'container.id', value: 'CONTAINER_ID' }],
fields: [{ field: 'container.id', value: ['CONTAINER_ID'] }],
id: 'ITEM_ID',
index: 'INDEX',
key: {
@ -89,7 +89,7 @@ describe('LogEntryActionsMenu component', () => {
<ProviderWrapper>
<LogEntryActionsMenu
logItem={{
fields: [{ field: 'kubernetes.pod.uid', value: 'POD_UID' }],
fields: [{ field: 'kubernetes.pod.uid', value: ['POD_UID'] }],
id: 'ITEM_ID',
index: 'INDEX',
key: {
@ -120,9 +120,9 @@ describe('LogEntryActionsMenu component', () => {
<LogEntryActionsMenu
logItem={{
fields: [
{ field: 'container.id', value: 'CONTAINER_ID' },
{ field: 'host.ip', value: 'HOST_IP' },
{ field: 'kubernetes.pod.uid', value: 'POD_UID' },
{ field: 'container.id', value: ['CONTAINER_ID'] },
{ field: 'host.ip', value: ['HOST_IP'] },
{ field: 'kubernetes.pod.uid', value: ['POD_UID'] },
],
id: 'ITEM_ID',
index: 'INDEX',
@ -189,7 +189,7 @@ describe('LogEntryActionsMenu component', () => {
<ProviderWrapper>
<LogEntryActionsMenu
logItem={{
fields: [{ field: 'trace.id', value: '1234567' }],
fields: [{ field: 'trace.id', value: ['1234567'] }],
id: 'ITEM_ID',
index: 'INDEX',
key: {
@ -221,8 +221,8 @@ describe('LogEntryActionsMenu component', () => {
<LogEntryActionsMenu
logItem={{
fields: [
{ field: 'trace.id', value: '1234567' },
{ field: '@timestamp', value: timestamp },
{ field: 'trace.id', value: ['1234567'] },
{ field: '@timestamp', value: [timestamp] },
],
id: 'ITEM_ID',
index: 'INDEX',

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import * as rt from 'io-ts';
import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo } from 'react';
@ -12,7 +11,6 @@ import { useVisibilityState } from '../../../utils/use_visibility_state';
import { getTraceUrl } from '../../../../../apm/public';
import { LogEntriesItem } from '../../../../common/http_api';
import { useLinkProps, LinkDescriptor } from '../../../hooks/use_link_props';
import { decodeOrThrow } from '../../../../common/runtime_types';
const UPTIME_FIELDS = ['container.id', 'host.ip', 'kubernetes.pod.uid'];
@ -97,12 +95,7 @@ const getUptimeLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {
.filter(({ field, value }) => value != null && UPTIME_FIELDS.includes(field))
.reduce<string[]>((acc, fieldItem) => {
const { field, value } = fieldItem;
try {
const parsedValue = decodeOrThrow(rt.array(rt.string))(JSON.parse(value));
return acc.concat(parsedValue.map((val) => `${field}:${val}`));
} catch (e) {
return acc.concat([`${field}:${value}`]);
}
return acc.concat(value.map((val) => `${field}:${val}`));
}, []);
if (searchExpressions.length === 0) {
@ -119,7 +112,7 @@ const getUptimeLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {
const getAPMLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {
const traceIdEntry = logItem.fields.find(
({ field, value }) => value != null && field === 'trace.id'
({ field, value }) => value[0] != null && field === 'trace.id'
);
if (!traceIdEntry) {
@ -127,7 +120,7 @@ const getAPMLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {
}
const timestampField = logItem.fields.find(({ field }) => field === '@timestamp');
const timestamp = timestampField ? timestampField.value : null;
const timestamp = timestampField ? timestampField.value[0] : null;
const { rangeFrom, rangeTo } = timestamp
? (() => {
const from = new Date(timestamp);
@ -142,6 +135,6 @@ const getAPMLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => {
return {
app: 'apm',
hash: getTraceUrl({ traceId: traceIdEntry.value, rangeFrom, rangeTo }),
hash: getTraceUrl({ traceId: traceIdEntry.value[0], rangeFrom, rangeTo }),
};
};

View file

@ -94,7 +94,7 @@ export const LogEntryFlyout = ({
onClick={createFilterHandler(item)}
/>
</EuiToolTip>
{item.value}
{formatValue(item.value)}
</span>
),
},
@ -147,3 +147,7 @@ export const InfraFlyoutLoadingPanel = euiStyled.div`
bottom: 0;
left: 0;
`;
function formatValue(value: string[]) {
return value.length > 1 ? value.join(', ') : value[0];
}

View file

@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import stringify from 'json-stable-stringify';
import React from 'react';
import { euiStyled } from '../../../../../observability/public';
import { JsonArray, JsonValue } from '../../../../common/typed_json';
import { ActiveHighlightMarker, highlightFieldValue, HighlightMarker } from './highlighting';
export const FieldValue: React.FC<{
highlightTerms: string[];
isActiveHighlight: boolean;
value: JsonArray;
}> = React.memo(({ highlightTerms, isActiveHighlight, value }) => {
if (value.length === 1) {
return (
<>
{highlightFieldValue(
formatValue(value[0]),
highlightTerms,
isActiveHighlight ? ActiveHighlightMarker : HighlightMarker
)}
</>
);
} else if (value.length > 1) {
return (
<ul data-test-subj="LogEntryFieldValues">
{value.map((entry, i) => (
<CommaSeparatedLi
key={`LogEntryFieldValue-${i}`}
data-test-subj={`LogEntryFieldValue-${i}`}
>
{highlightFieldValue(
formatValue(entry),
highlightTerms,
isActiveHighlight ? ActiveHighlightMarker : HighlightMarker
)}
</CommaSeparatedLi>
))}
</ul>
);
}
return null;
});
const formatValue = (value: JsonValue): string => {
if (typeof value === 'string') {
return value;
}
return stringify(value);
};
const CommaSeparatedLi = euiStyled.li`
display: inline;
&:not(:last-child) {
margin-right: 1ex;
&::after {
content: ',';
}
}
`;

View file

@ -39,7 +39,9 @@ export const LogEntryColumn = euiStyled.div.attrs(() => ({
overflow: hidden;
`;
export const LogEntryColumnContent = euiStyled.div`
export const LogEntryColumnContent = euiStyled.div.attrs({
'data-test-subj': 'LogEntryColumnContent',
})`
flex: 1 0 0%;
padding: 2px ${COLUMN_PADDING}px;
`;

View file

@ -4,85 +4,113 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import React from 'react';
import { EuiThemeProvider } from '../../../../../observability/public';
import { LogFieldColumn } from '../../../../common/http_api';
import { LogEntryFieldColumn } from './log_entry_field_column';
import { LogColumn } from '../../../../common/http_api';
describe('LogEntryFieldColumn', () => {
it('should output a <ul> when displaying an Array of values', () => {
const column: LogColumn = {
it('renders a single value without a wrapping list', () => {
const column: LogFieldColumn = {
columnId: 'TEST_COLUMN',
field: 'TEST_FIELD',
value: ['a'],
highlights: [],
};
const renderResult = render(
<LogEntryFieldColumn
columnValue={column}
highlights={[]}
isActiveHighlight={false}
wrapMode="pre-wrapped"
/>,
{ wrapper: EuiThemeProvider }
);
expect(renderResult.getByTestId('LogEntryColumnContent')).toHaveTextContent(/^a$/);
expect(renderResult.queryByTestId('LogEntryFieldValues')).toBe(null);
});
it('renders an array of values as a list', () => {
const column: LogFieldColumn = {
columnId: 'TEST_COLUMN',
field: 'TEST_FIELD',
value: ['a', 'b', 'c'],
highlights: [],
};
const component = mount(
const renderResult = render(
<LogEntryFieldColumn
columnValue={column}
highlights={[]}
isActiveHighlight={false}
wrapMode="pre-wrapped"
/>,
{ wrappingComponent: EuiThemeProvider } as any // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/36075
{ wrapper: EuiThemeProvider }
);
expect(component.exists('ul')).toBe(true);
expect(
component.containsAllMatchingElements([
<li key="LogEntryFieldColumn-a-0">a</li>,
<li key="LogEntryFieldColumn-b-1">b</li>,
<li key="LogEntryFieldColumn-c-2">c</li>,
])
).toBe(true);
expect(renderResult.getByTestId('LogEntryFieldValues')).not.toBeEmptyDOMElement();
expect(renderResult.getByTestId('LogEntryFieldValue-0')).toHaveTextContent('a');
expect(renderResult.getByTestId('LogEntryFieldValue-1')).toHaveTextContent('b');
expect(renderResult.getByTestId('LogEntryFieldValue-2')).toHaveTextContent('c');
});
it('should output a text representation of a passed complex value', () => {
const column: LogColumn = {
it('renders a text representation of a single complex object', () => {
const column: LogFieldColumn = {
columnId: 'TEST_COLUMN',
field: 'TEST_FIELD',
value: {
lat: 1,
lon: 2,
},
value: [
{
lat: 1,
lon: 2,
},
],
highlights: [],
};
const component = mount(
const renderResult = render(
<LogEntryFieldColumn
columnValue={column}
highlights={[]}
isActiveHighlight={false}
wrapMode="pre-wrapped"
/>,
{ wrappingComponent: EuiThemeProvider } as any // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/36075
{ wrapper: EuiThemeProvider }
);
expect(component.text()).toEqual('{"lat":1,"lon":2}');
expect(renderResult.getByTestId('LogEntryColumnContent')).toHaveTextContent(
'{"lat":1,"lon":2}'
);
});
it('should output just text when passed a non-Array', () => {
const column: LogColumn = {
it('renders text representations of a multiple complex objects', () => {
const column: LogFieldColumn = {
columnId: 'TEST_COLUMN',
field: 'TEST_FIELD',
value: 'foo',
value: [
{
lat: 1,
lon: 2,
},
[3, 4],
],
highlights: [],
};
const component = mount(
const renderResult = render(
<LogEntryFieldColumn
columnValue={column}
highlights={[]}
isActiveHighlight={false}
wrapMode="pre-wrapped"
/>,
{ wrappingComponent: EuiThemeProvider } as any // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/36075
{ wrapper: EuiThemeProvider }
);
expect(component.exists('ul')).toBe(false);
expect(component.text()).toEqual('foo');
expect(renderResult.getByTestId('LogEntryFieldValues')).not.toBeEmptyDOMElement();
expect(renderResult.getByTestId('LogEntryFieldValue-0')).toHaveTextContent('{"lat":1,"lon":2}');
expect(renderResult.getByTestId('LogEntryFieldValue-1')).toHaveTextContent('[3,4]');
});
});

View file

@ -4,14 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import stringify from 'json-stable-stringify';
import React, { useMemo } from 'react';
import React from 'react';
import { euiStyled } from '../../../../../observability/public';
import { isFieldColumn, isHighlightFieldColumn } from '../../../utils/log_entry';
import { ActiveHighlightMarker, highlightFieldValue, HighlightMarker } from './highlighting';
import { LogEntryColumnContent } from './log_entry_column';
import { LogColumn } from '../../../../common/http_api';
import { isFieldColumn, isHighlightFieldColumn } from '../../../utils/log_entry';
import { FieldValue } from './field_value';
import { LogEntryColumnContent } from './log_entry_column';
import {
longWrappedContentStyle,
preWrappedContentStyle,
@ -32,44 +30,20 @@ export const LogEntryFieldColumn: React.FunctionComponent<LogEntryFieldColumnPro
isActiveHighlight,
wrapMode,
}) => {
const value = useMemo(() => {
if (isFieldColumn(columnValue)) {
return columnValue.value;
}
if (isFieldColumn(columnValue)) {
return (
<FieldColumnContent wrapMode={wrapMode}>
<FieldValue
highlightTerms={isHighlightFieldColumn(firstHighlight) ? firstHighlight.highlights : []}
isActiveHighlight={isActiveHighlight}
value={columnValue.value}
/>
</FieldColumnContent>
);
} else {
return null;
}, [columnValue]);
const formattedValue = Array.isArray(value) ? (
<ul>
{value.map((entry, i) => (
<CommaSeparatedLi key={`LogEntryFieldColumn-${i}`}>
{highlightFieldValue(
entry,
isHighlightFieldColumn(firstHighlight) ? firstHighlight.highlights : [],
isActiveHighlight ? ActiveHighlightMarker : HighlightMarker
)}
</CommaSeparatedLi>
))}
</ul>
) : (
highlightFieldValue(
typeof value === 'string' ? value : stringify(value),
isHighlightFieldColumn(firstHighlight) ? firstHighlight.highlights : [],
isActiveHighlight ? ActiveHighlightMarker : HighlightMarker
)
);
return <FieldColumnContent wrapMode={wrapMode}>{formattedValue}</FieldColumnContent>;
};
const CommaSeparatedLi = euiStyled.li`
display: inline;
&:not(:last-child) {
margin-right: 1ex;
&::after {
content: ',';
}
}
`;
};
interface LogEntryColumnContentProps {
wrapMode: WrapMode;

View file

@ -0,0 +1,82 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { render } from '@testing-library/react';
import React from 'react';
import { EuiThemeProvider } from '../../../../../observability/public';
import { LogMessageColumn } from '../../../../common/http_api';
import { LogEntryMessageColumn } from './log_entry_message_column';
describe('LogEntryMessageColumn', () => {
it('renders a single scalar field value without a wrapping list', () => {
const column: LogMessageColumn = {
columnId: 'TEST_COLUMN',
message: [{ field: 'TEST_FIELD', value: ['VALUE'], highlights: [] }],
};
const renderResult = render(
<LogEntryMessageColumn
columnValue={column}
highlights={[]}
isActiveHighlight={false}
wrapMode="pre-wrapped"
/>,
{ wrapper: EuiThemeProvider }
);
expect(renderResult.getByTestId('LogEntryColumnContent')).toHaveTextContent(/^VALUE$/);
expect(renderResult.queryByTestId('LogEntryFieldValues')).toBe(null);
});
it('renders a single array of scalar field values as a list', () => {
const column: LogMessageColumn = {
columnId: 'TEST_COLUMN',
message: [{ field: 'TEST_FIELD', value: ['VALUE_1', 'VALUE_2'], highlights: [] }],
};
const renderResult = render(
<LogEntryMessageColumn
columnValue={column}
highlights={[]}
isActiveHighlight={false}
wrapMode="pre-wrapped"
/>,
{ wrapper: EuiThemeProvider }
);
expect(renderResult.getByTestId('LogEntryFieldValues')).not.toBeEmptyDOMElement();
expect(renderResult.getByTestId('LogEntryFieldValue-0')).toHaveTextContent('VALUE_1');
expect(renderResult.getByTestId('LogEntryFieldValue-1')).toHaveTextContent('VALUE_2');
});
it('renders a complex message with an array of complex field values', () => {
const column: LogMessageColumn = {
columnId: 'TEST_COLUMN',
message: [
{ constant: 'CONSTANT_1' },
{ field: 'TEST_FIELD', value: [{ lat: 1, lon: 2 }, 'VALUE_2'], highlights: [] },
{ constant: 'CONSTANT_2' },
],
};
const renderResult = render(
<LogEntryMessageColumn
columnValue={column}
highlights={[]}
isActiveHighlight={false}
wrapMode="pre-wrapped"
/>,
{ wrapper: EuiThemeProvider }
);
expect(renderResult.getByTestId('LogEntryColumnContent')).toHaveTextContent(
/^CONSTANT_1.*{"lat":1,"lon":2}.*VALUE_2.*CONSTANT_2$/
);
expect(renderResult.getByTestId('LogEntryFieldValues')).not.toBeEmptyDOMElement();
expect(renderResult.getByTestId('LogEntryFieldValue-0')).toHaveTextContent('{"lat":1,"lon":2}');
expect(renderResult.getByTestId('LogEntryFieldValue-1')).toHaveTextContent('VALUE_2');
});
});

View file

@ -5,17 +5,16 @@
*/
import React, { memo, useMemo } from 'react';
import stringify from 'json-stable-stringify';
import { euiStyled } from '../../../../../observability/public';
import { LogColumn, LogMessagePart } from '../../../../common/http_api';
import {
isConstantSegment,
isFieldSegment,
isHighlightFieldSegment,
isHighlightMessageColumn,
isMessageColumn,
isHighlightFieldSegment,
} from '../../../utils/log_entry';
import { ActiveHighlightMarker, highlightFieldValue, HighlightMarker } from './highlighting';
import { FieldValue } from './field_value';
import { LogEntryColumnContent } from './log_entry_column';
import {
longWrappedContentStyle,
@ -23,7 +22,6 @@ import {
unwrappedContentStyle,
WrapMode,
} from './text_styles';
import { LogColumn, LogMessagePart } from '../../../../common/http_api';
interface LogEntryMessageColumnProps {
columnValue: LogColumn;
@ -65,10 +63,10 @@ const formatMessageSegments = (
highlights: LogColumn[],
isActiveHighlight: boolean
) =>
messageSegments.map((messageSegment, index) =>
formatMessageSegment(
messageSegment,
highlights.map((highlight) => {
messageSegments.map((messageSegment, index) => {
if (isFieldSegment(messageSegment)) {
// we only support one highlight for now
const [firstHighlight = []] = highlights.map((highlight) => {
if (isHighlightMessageColumn(highlight)) {
const segment = highlight.message[index];
if (isHighlightFieldSegment(segment)) {
@ -76,30 +74,19 @@ const formatMessageSegments = (
}
}
return [];
}),
isActiveHighlight
)
);
});
const formatMessageSegment = (
messageSegment: LogMessagePart,
[firstHighlight = []]: string[][], // we only support one highlight for now
isActiveHighlight: boolean
): React.ReactNode => {
if (isFieldSegment(messageSegment)) {
const value =
typeof messageSegment.value === 'string'
? messageSegment.value
: stringify(messageSegment.value);
return (
<FieldValue
highlightTerms={firstHighlight}
isActiveHighlight={isActiveHighlight}
key={`MessageSegment-${index}`}
value={messageSegment.value}
/>
);
} else if (isConstantSegment(messageSegment)) {
return messageSegment.constant;
}
return highlightFieldValue(
value,
firstHighlight,
isActiveHighlight ? ActiveHighlightMarker : HighlightMarker
);
} else if (isConstantSegment(messageSegment)) {
return messageSegment.constant;
}
return 'failed to format message';
};
return 'failed to format message';
});

View file

@ -86,7 +86,7 @@ export const CategoryExampleMessage: React.FunctionComponent<{
<LogEntryMessageColumn
columnValue={{
columnId: messageColumnId,
message: [{ field: 'message', value: message, highlights: [] }],
message: [{ field: 'message', value: [message], highlights: [] }],
}}
highlights={noHighlights}
isActiveHighlight={false}
@ -98,7 +98,7 @@ export const CategoryExampleMessage: React.FunctionComponent<{
columnValue={{
columnId: datasetColumnId,
field: 'event.dataset',
value: humanFriendlyDataset,
value: [humanFriendlyDataset],
highlights: [],
}}
highlights={noHighlights}

View file

@ -165,7 +165,7 @@ export const LogEntryExampleMessage: React.FunctionComponent<Props> = ({
<LogEntryMessageColumn
columnValue={{
columnId: messageColumnId,
message: [{ field: 'message', value: message, highlights: [] }],
message: [{ field: 'message', value: [message], highlights: [] }],
}}
highlights={noHighlights}
isActiveHighlight={false}
@ -177,7 +177,7 @@ export const LogEntryExampleMessage: React.FunctionComponent<Props> = ({
columnValue={{
columnId: datasetColumnId,
field: 'event.dataset',
value: humanFriendlyDataset,
value: [humanFriendlyDataset],
highlights: [],
}}
highlights={noHighlights}

View file

@ -15,6 +15,7 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../pl
import { SpacesPluginSetup } from '../../../../../../plugins/spaces/server';
import { PluginSetupContract as AlertingPluginContract } from '../../../../../alerts/server';
import { MlPluginSetup } from '../../../../../ml/server';
import { JsonArray, JsonValue } from '../../../../common/typed_json';
export interface InfraServerPluginDeps {
home: HomeServerPluginSetup;
@ -111,7 +112,10 @@ export type SearchHit = SearchResponse<object>['hits']['hits'][0];
export interface SortedSearchHit extends SearchHit {
sort: any[];
_source: {
[field: string]: any;
[field: string]: JsonValue;
};
fields: {
[field: string]: JsonArray;
};
}

View file

@ -4,18 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
import { timeMilliseconds } from 'd3-time';
import { fold, map } from 'fp-ts/lib/Either';
import { constant, identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import * as runtimeTypes from 'io-ts';
import { compact, first } from 'lodash';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, fold } from 'fp-ts/lib/Either';
import { identity, constant } from 'fp-ts/lib/function';
import { RequestHandlerContext } from 'src/core/server';
import { JsonValue } from '../../../../common/typed_json';
import { JsonArray } from '../../../../common/typed_json';
import {
LogEntriesAdapter,
LogItemHit,
LogEntriesParams,
LogEntryDocument,
LogEntryQuery,
@ -28,13 +27,6 @@ import { KibanaFramework } from '../framework/kibana_framework_adapter';
const TIMESTAMP_FORMAT = 'epoch_millis';
interface LogItemHit {
_index: string;
_id: string;
fields: { [key: string]: [value: unknown] };
sort: [number, number];
}
export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
constructor(private readonly framework: KibanaFramework) {}
@ -231,13 +223,14 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
function mapHitsToLogEntryDocuments(hits: SortedSearchHit[], fields: string[]): LogEntryDocument[] {
return hits.map((hit) => {
const logFields = fields.reduce<{ [fieldName: string]: JsonValue }>(
(flattenedFields, field) => {
if (field in hit.fields) {
flattenedFields[field] = hit.fields[field][0];
}
return flattenedFields;
},
const logFields = fields.reduce<{ [fieldName: string]: JsonArray }>(
(flattenedFields, field) =>
field in hit.fields
? {
...flattenedFields,
[field]: hit.fields[field],
}
: flattenedFields,
{}
);
@ -338,8 +331,9 @@ const LogSummaryDateRangeBucketRuntimeType = runtimeTypes.intersection([
}),
]);
export interface LogSummaryDateRangeBucket
extends runtimeTypes.TypeOf<typeof LogSummaryDateRangeBucketRuntimeType> {}
export type LogSummaryDateRangeBucket = runtimeTypes.TypeOf<
typeof LogSummaryDateRangeBucketRuntimeType
>;
const LogSummaryResponseRuntimeType = runtimeTypes.type({
aggregations: runtimeTypes.type({
@ -349,5 +343,4 @@ const LogSummaryResponseRuntimeType = runtimeTypes.type({
}),
});
export interface LogSummaryResponse
extends runtimeTypes.TypeOf<typeof LogSummaryResponseRuntimeType> {}
export type LogSummaryResponse = runtimeTypes.TypeOf<typeof LogSummaryResponseRuntimeType>;

View file

@ -13,251 +13,290 @@ describe('Filebeat Rules', () => {
describe('in ECS format', () => {
test('Apache2 Access', () => {
const flattenedDocument = {
'@timestamp': '2016-12-26T16:22:13.000Z',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'apache.access',
'event.module': 'apache',
'fileset.name': 'access',
'http.request.method': 'GET',
'http.request.referrer': '-',
'http.response.body.bytes': 499,
'http.response.status_code': 404,
'http.version': '1.1',
'input.type': 'log',
'log.offset': 73,
'service.type': 'apache',
'source.address': '192.168.33.1',
'source.ip': '192.168.33.1',
'url.original': '/hello',
'user.name': '-',
'user_agent.device': 'Other',
'user_agent.major': '50',
'user_agent.minor': '0',
'user_agent.name': 'Firefox',
'user_agent.original':
'@timestamp': ['2016-12-26T16:22:13.000Z'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['apache.access'],
'event.module': ['apache'],
'fileset.name': ['access'],
'http.request.method': ['GET'],
'http.request.referrer': ['-'],
'http.response.body.bytes': [499],
'http.response.status_code': [404],
'http.version': ['1.1'],
'input.type': ['log'],
'log.offset': [73],
'service.type': ['apache'],
'source.address': ['192.168.33.1'],
'source.ip': ['192.168.33.1'],
'url.original': ['/hello'],
'user.name': ['-'],
'user_agent.device': ['Other'],
'user_agent.major': ['50'],
'user_agent.minor': ['0'],
'user_agent.name': ['Firefox'],
'user_agent.original': [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:50.0) Gecko/20100101 Firefox/50.0',
'user_agent.os.full_name': 'Mac OS X 10.12',
'user_agent.os.major': '10',
'user_agent.os.minor': '12',
'user_agent.os.name': 'Mac OS X',
],
'user_agent.os.full_name': ['Mac OS X 10.12'],
'user_agent.os.major': ['10'],
'user_agent.os.minor': ['12'],
'user_agent.os.name': ['Mac OS X'],
};
const highlights = {
'http.request.method': ['GET'],
};
expect(format(flattenedDocument, highlights)).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[",
},
Object {
"field": "event.module",
"highlights": Array [],
"value": "apache",
},
Object {
"constant": "][access] ",
},
Object {
"field": "source.ip",
"highlights": Array [],
"value": "192.168.33.1",
},
Object {
"constant": " ",
},
Object {
"field": "user.name",
"highlights": Array [],
"value": "-",
},
Object {
"constant": " \\"",
},
Object {
"field": "http.request.method",
"highlights": Array [
"GET",
],
"value": "GET",
},
Object {
"constant": " ",
},
Object {
"field": "url.original",
"highlights": Array [],
"value": "/hello",
},
Object {
"constant": " HTTP/",
},
Object {
"field": "http.version",
"highlights": Array [],
"value": "1.1",
},
Object {
"constant": "\\" ",
},
Object {
"field": "http.response.status_code",
"highlights": Array [],
"value": "404",
},
Object {
"constant": " ",
},
Object {
"field": "http.response.body.bytes",
"highlights": Array [],
"value": "499",
},
]
`);
Array [
Object {
"constant": "[",
},
Object {
"field": "event.module",
"highlights": Array [],
"value": Array [
"apache",
],
},
Object {
"constant": "][access] ",
},
Object {
"field": "source.ip",
"highlights": Array [],
"value": Array [
"192.168.33.1",
],
},
Object {
"constant": " ",
},
Object {
"field": "user.name",
"highlights": Array [],
"value": Array [
"-",
],
},
Object {
"constant": " \\"",
},
Object {
"field": "http.request.method",
"highlights": Array [
"GET",
],
"value": Array [
"GET",
],
},
Object {
"constant": " ",
},
Object {
"field": "url.original",
"highlights": Array [],
"value": Array [
"/hello",
],
},
Object {
"constant": " HTTP/",
},
Object {
"field": "http.version",
"highlights": Array [],
"value": Array [
"1.1",
],
},
Object {
"constant": "\\" ",
},
Object {
"field": "http.response.status_code",
"highlights": Array [],
"value": Array [
404,
],
},
Object {
"constant": " ",
},
Object {
"field": "http.response.body.bytes",
"highlights": Array [],
"value": Array [
499,
],
},
]
`);
});
test('Apache2 Error', () => {
const flattenedDocument = {
'@timestamp': '2016-12-26T16:22:08.000Z',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'apache.error',
'event.module': 'apache',
'fileset.name': 'error',
'input.type': 'log',
'log.level': 'error',
'log.offset': 0,
message: 'File does not exist: /var/www/favicon.ico',
'service.type': 'apache',
'source.address': '192.168.33.1',
'source.ip': '192.168.33.1',
'@timestamp': ['2016-12-26T16:22:08.000Z'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['apache.error'],
'event.module': ['apache'],
'fileset.name': ['error'],
'input.type': ['log'],
'log.level': ['error'],
'log.offset': [0],
message: ['File does not exist: /var/www/favicon.ico'],
'service.type': ['apache'],
'source.address': ['192.168.33.1'],
'source.ip': ['192.168.33.1'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[apache][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": "error",
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": "File does not exist: /var/www/favicon.ico",
},
]
`);
Array [
Object {
"constant": "[apache][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": Array [
"error",
],
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": Array [
"File does not exist: /var/www/favicon.ico",
],
},
]
`);
});
});
describe('in pre-ECS format', () => {
test('Apache2 Access', () => {
const flattenedDocument = {
'apache2.access': true,
'apache2.access.remote_ip': '192.168.1.42',
'apache2.access.user_name': 'admin',
'apache2.access.method': 'GET',
'apache2.access.url': '/faqs',
'apache2.access.http_version': '1.1',
'apache2.access.response_code': '200',
'apache2.access.body_sent.bytes': 1024,
'apache2.access.remote_ip': ['192.168.1.42'],
'apache2.access.user_name': ['admin'],
'apache2.access.method': ['GET'],
'apache2.access.url': ['/faqs'],
'apache2.access.http_version': ['1.1'],
'apache2.access.response_code': ['200'],
'apache2.access.body_sent.bytes': [1024],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[apache][access] ",
},
Object {
"field": "apache2.access.remote_ip",
"highlights": Array [],
"value": "192.168.1.42",
},
Object {
"constant": " ",
},
Object {
"field": "apache2.access.user_name",
"highlights": Array [],
"value": "admin",
},
Object {
"constant": " \\"",
},
Object {
"field": "apache2.access.method",
"highlights": Array [],
"value": "GET",
},
Object {
"constant": " ",
},
Object {
"field": "apache2.access.url",
"highlights": Array [],
"value": "/faqs",
},
Object {
"constant": " HTTP/",
},
Object {
"field": "apache2.access.http_version",
"highlights": Array [],
"value": "1.1",
},
Object {
"constant": "\\" ",
},
Object {
"field": "apache2.access.response_code",
"highlights": Array [],
"value": "200",
},
Object {
"constant": " ",
},
Object {
"field": "apache2.access.body_sent.bytes",
"highlights": Array [],
"value": "1024",
},
]
`);
Array [
Object {
"constant": "[apache][access] ",
},
Object {
"field": "apache2.access.remote_ip",
"highlights": Array [],
"value": Array [
"192.168.1.42",
],
},
Object {
"constant": " ",
},
Object {
"field": "apache2.access.user_name",
"highlights": Array [],
"value": Array [
"admin",
],
},
Object {
"constant": " \\"",
},
Object {
"field": "apache2.access.method",
"highlights": Array [],
"value": Array [
"GET",
],
},
Object {
"constant": " ",
},
Object {
"field": "apache2.access.url",
"highlights": Array [],
"value": Array [
"/faqs",
],
},
Object {
"constant": " HTTP/",
},
Object {
"field": "apache2.access.http_version",
"highlights": Array [],
"value": Array [
"1.1",
],
},
Object {
"constant": "\\" ",
},
Object {
"field": "apache2.access.response_code",
"highlights": Array [],
"value": Array [
"200",
],
},
Object {
"constant": " ",
},
Object {
"field": "apache2.access.body_sent.bytes",
"highlights": Array [],
"value": Array [
1024,
],
},
]
`);
});
test('Apache2 Error', () => {
const flattenedDocument = {
'apache2.error.message':
'apache2.error.message': [
'AH00489: Apache/2.4.18 (Ubuntu) configured -- resuming normal operations',
'apache2.error.level': 'notice',
],
'apache2.error.level': ['notice'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[apache][",
},
Object {
"field": "apache2.error.level",
"highlights": Array [],
"value": "notice",
},
Object {
"constant": "] ",
},
Object {
"field": "apache2.error.message",
"highlights": Array [],
"value": "AH00489: Apache/2.4.18 (Ubuntu) configured -- resuming normal operations",
},
]
`);
Array [
Object {
"constant": "[apache][",
},
Object {
"field": "apache2.error.level",
"highlights": Array [],
"value": Array [
"notice",
],
},
Object {
"constant": "] ",
},
Object {
"field": "apache2.error.message",
"highlights": Array [],
"value": Array [
"AH00489: Apache/2.4.18 (Ubuntu) configured -- resuming normal operations",
],
},
]
`);
});
});
});

View file

@ -8,7 +8,7 @@ export const filebeatApache2Rules = [
{
// pre-ECS
when: {
exists: ['apache2.access'],
existsPrefix: ['apache2.access'],
},
format: [
{

View file

@ -13,345 +13,605 @@ describe('Filebeat Rules', () => {
describe('in ECS format', () => {
test('auditd log with outcome', () => {
const flattenedDocument = {
'@timestamp': '2016-12-07T02:17:21.515Z',
'auditd.log': {
addr: '96.241.146.97',
cipher: 'chacha20-poly1305@openssh.com',
direction: 'from-server',
ksize: '512',
laddr: '10.142.0.2',
lport: '22',
pfs: 'curve25519-sha256@libssh.org',
rport: '63927',
sequence: 406,
ses: '4294967295',
spid: '1299',
subj: 'system_u:system_r:sshd_t:s0-s0:c0.c1023',
},
'ecs.version': '1.0.0-beta2',
'event.action': 'crypto_session',
'event.dataset': 'auditd.log',
'event.module': 'auditd',
'event.outcome': 'success',
'fileset.name': 'log',
'input.type': 'log',
'log.offset': 783,
message: 'op=start',
process: { executable: '/usr/sbin/sshd', pid: 1298 },
'service.type': 'auditd',
user: { 'audit.id': '4294967295', id: '0', 'saved.id': '74' },
'@timestamp': ['2016-12-07T02:17:21.515Z'],
'auditd.log.addr': ['96.241.146.97'],
'auditd.log.cipher': ['chacha20-poly1305@openssh.com'],
'auditd.log.direction': ['from-server'],
'auditd.log.ksize': ['512'],
'auditd.log.laddr': ['10.142.0.2'],
'auditd.log.lport': ['22'],
'auditd.log.pfs': ['curve25519-sha256@libssh.org'],
'auditd.log.rport': ['63927'],
'auditd.log.sequence': [406],
'auditd.log.ses': ['4294967295'],
'auditd.log.spid': ['1299'],
'auditd.log.subj': ['system_u:system_r:sshd_t:s0-s0:c0.c1023'],
'ecs.version': ['1.0.0-beta2'],
'event.action': ['crypto_session'],
'event.dataset': ['auditd.log'],
'event.module': ['auditd'],
'event.outcome': ['success'],
'fileset.name': ['log'],
'input.type': ['log'],
'log.offset': [783],
message: ['op=start'],
'process.executable': ['/usr/sbin/sshd'],
'process.pid': [1298],
'service.type': ['auditd'],
'user.audit.id': ['4294967295'],
'user.id': ['0'],
'user.saved.id': ['74'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[AuditD][",
},
Object {
"field": "event.action",
"highlights": Array [],
"value": "crypto_session",
},
Object {
"constant": "]",
},
Object {
"constant": " ",
},
Object {
"field": "event.outcome",
"highlights": Array [],
"value": "success",
},
Object {
"constant": " ",
},
Object {
"constant": "user",
},
Object {
"constant": "=",
},
Object {
"field": "user",
"highlights": Array [],
"value": "{\\"audit.id\\":\\"4294967295\\",\\"id\\":\\"0\\",\\"saved.id\\":\\"74\\"}",
},
Object {
"constant": " ",
},
Object {
"constant": "process",
},
Object {
"constant": "=",
},
Object {
"field": "process",
"highlights": Array [],
"value": "{\\"executable\\":\\"/usr/sbin/sshd\\",\\"pid\\":1298}",
},
Object {
"constant": " ",
},
Object {
"field": "auditd.log",
"highlights": Array [],
"value": "{\\"addr\\":\\"96.241.146.97\\",\\"cipher\\":\\"chacha20-poly1305@openssh.com\\",\\"direction\\":\\"from-server\\",\\"ksize\\":\\"512\\",\\"laddr\\":\\"10.142.0.2\\",\\"lport\\":\\"22\\",\\"pfs\\":\\"curve25519-sha256@libssh.org\\",\\"rport\\":\\"63927\\",\\"sequence\\":406,\\"ses\\":\\"4294967295\\",\\"spid\\":\\"1299\\",\\"subj\\":\\"system_u:system_r:sshd_t:s0-s0:c0.c1023\\"}",
},
Object {
"constant": " ",
},
Object {
"field": "message",
"highlights": Array [],
"value": "op=start",
},
]
`);
Array [
Object {
"constant": "[AuditD][",
},
Object {
"field": "event.action",
"highlights": Array [],
"value": Array [
"crypto_session",
],
},
Object {
"constant": "]",
},
Object {
"constant": " ",
},
Object {
"field": "event.outcome",
"highlights": Array [],
"value": Array [
"success",
],
},
Object {
"constant": " ",
},
Object {
"constant": "user",
},
Object {
"constant": "=",
},
Object {
"field": "user.audit.id",
"highlights": Array [],
"value": Array [
"4294967295",
],
},
Object {
"field": "user.id",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "user.saved.id",
"highlights": Array [],
"value": Array [
"74",
],
},
Object {
"constant": " ",
},
Object {
"constant": "process",
},
Object {
"constant": "=",
},
Object {
"field": "process.executable",
"highlights": Array [],
"value": Array [
"/usr/sbin/sshd",
],
},
Object {
"field": "process.pid",
"highlights": Array [],
"value": Array [
1298,
],
},
Object {
"constant": " ",
},
Object {
"field": "auditd.log.addr",
"highlights": Array [],
"value": Array [
"96.241.146.97",
],
},
Object {
"field": "auditd.log.cipher",
"highlights": Array [],
"value": Array [
"chacha20-poly1305@openssh.com",
],
},
Object {
"field": "auditd.log.direction",
"highlights": Array [],
"value": Array [
"from-server",
],
},
Object {
"field": "auditd.log.ksize",
"highlights": Array [],
"value": Array [
"512",
],
},
Object {
"field": "auditd.log.laddr",
"highlights": Array [],
"value": Array [
"10.142.0.2",
],
},
Object {
"field": "auditd.log.lport",
"highlights": Array [],
"value": Array [
"22",
],
},
Object {
"field": "auditd.log.pfs",
"highlights": Array [],
"value": Array [
"curve25519-sha256@libssh.org",
],
},
Object {
"field": "auditd.log.rport",
"highlights": Array [],
"value": Array [
"63927",
],
},
Object {
"field": "auditd.log.sequence",
"highlights": Array [],
"value": Array [
406,
],
},
Object {
"field": "auditd.log.ses",
"highlights": Array [],
"value": Array [
"4294967295",
],
},
Object {
"field": "auditd.log.spid",
"highlights": Array [],
"value": Array [
"1299",
],
},
Object {
"field": "auditd.log.subj",
"highlights": Array [],
"value": Array [
"system_u:system_r:sshd_t:s0-s0:c0.c1023",
],
},
Object {
"constant": " ",
},
Object {
"field": "message",
"highlights": Array [],
"value": Array [
"op=start",
],
},
]
`);
});
test('auditd log without outcome', () => {
const flattenedDocument = {
'@timestamp': '2017-01-31T20:17:14.891Z',
'auditd.log': {
a0: '9',
a1: '7f564b2672a0',
a2: 'b8',
a3: '0',
exit: '184',
items: '0',
sequence: 18877199,
ses: '4294967295',
success: 'yes',
syscall: '44',
tty: '(none)',
},
'ecs.version': '1.0.0-beta2',
'event.action': 'syscall',
'event.dataset': 'auditd.log',
'event.module': 'auditd',
'fileset.name': 'log',
'host.architecture': 'x86_64',
'input.type': 'log',
'log.offset': 174,
process: {
executable: '/usr/libexec/strongswan/charon (deleted)',
name: 'charon',
pid: 1281,
ppid: 1240,
},
'service.type': 'auditd',
user: {
'audit.id': '4294967295',
'effective.group.id': '0',
'effective.id': '0',
'filesystem.group.id': '0',
'filesystem.id': '0',
'group.id': '0',
id: '0',
'saved.group.id': '0',
'saved.id': '0',
},
'@timestamp': ['2017-01-31T20:17:14.891Z'],
'auditd.log.a0': ['9'],
'auditd.log.a1': ['7f564b2672a0'],
'auditd.log.a2': ['b8'],
'auditd.log.a3': ['0'],
'auditd.log.exit': ['184'],
'auditd.log.items': ['0'],
'auditd.log.sequence': [18877199],
'auditd.log.ses': ['4294967295'],
'auditd.log.success': ['yes'],
'auditd.log.syscall': ['44'],
'auditd.log.tty': ['(none)'],
'ecs.version': ['1.0.0-beta2'],
'event.action': ['syscall'],
'event.dataset': ['auditd.log'],
'event.module': ['auditd'],
'fileset.name': ['log'],
'host.architecture': ['x86_64'],
'input.type': ['log'],
'log.offset': [174],
'process.executable': ['/usr/libexec/strongswan/charon (deleted)'],
'process.name': ['charon'],
'process.pid': [1281],
'process.ppid': [1240],
'service.type': ['auditd'],
'user.audit.id': ['4294967295'],
'user.effective.group.id': ['0'],
'user.effective.id': ['0'],
'user.filesystem.group.id': ['0'],
'user.filesystem.id': ['0'],
'user.group.id': ['0'],
'user.id': ['0'],
'user.saved.group.id': ['0'],
'user.saved.id': ['0'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[AuditD][",
},
Object {
"field": "event.action",
"highlights": Array [],
"value": "syscall",
},
Object {
"constant": "]",
},
Object {
"constant": " ",
},
Object {
"constant": "user",
},
Object {
"constant": "=",
},
Object {
"field": "user",
"highlights": Array [],
"value": "{\\"audit.id\\":\\"4294967295\\",\\"effective.group.id\\":\\"0\\",\\"effective.id\\":\\"0\\",\\"filesystem.group.id\\":\\"0\\",\\"filesystem.id\\":\\"0\\",\\"group.id\\":\\"0\\",\\"id\\":\\"0\\",\\"saved.group.id\\":\\"0\\",\\"saved.id\\":\\"0\\"}",
},
Object {
"constant": " ",
},
Object {
"constant": "process",
},
Object {
"constant": "=",
},
Object {
"field": "process",
"highlights": Array [],
"value": "{\\"executable\\":\\"/usr/libexec/strongswan/charon (deleted)\\",\\"name\\":\\"charon\\",\\"pid\\":1281,\\"ppid\\":1240}",
},
Object {
"constant": " ",
},
Object {
"field": "auditd.log",
"highlights": Array [],
"value": "{\\"a0\\":\\"9\\",\\"a1\\":\\"7f564b2672a0\\",\\"a2\\":\\"b8\\",\\"a3\\":\\"0\\",\\"exit\\":\\"184\\",\\"items\\":\\"0\\",\\"sequence\\":18877199,\\"ses\\":\\"4294967295\\",\\"success\\":\\"yes\\",\\"syscall\\":\\"44\\",\\"tty\\":\\"(none)\\"}",
},
Object {
"constant": " ",
},
Object {
"field": "message",
"highlights": Array [],
"value": "undefined",
},
]
`);
Array [
Object {
"constant": "[AuditD][",
},
Object {
"field": "event.action",
"highlights": Array [],
"value": Array [
"syscall",
],
},
Object {
"constant": "]",
},
Object {
"constant": " ",
},
Object {
"constant": "user",
},
Object {
"constant": "=",
},
Object {
"field": "user.audit.id",
"highlights": Array [],
"value": Array [
"4294967295",
],
},
Object {
"field": "user.effective.group.id",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "user.effective.id",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "user.filesystem.group.id",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "user.filesystem.id",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "user.group.id",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "user.id",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "user.saved.group.id",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "user.saved.id",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"constant": " ",
},
Object {
"constant": "process",
},
Object {
"constant": "=",
},
Object {
"field": "process.executable",
"highlights": Array [],
"value": Array [
"/usr/libexec/strongswan/charon (deleted)",
],
},
Object {
"field": "process.name",
"highlights": Array [],
"value": Array [
"charon",
],
},
Object {
"field": "process.pid",
"highlights": Array [],
"value": Array [
1281,
],
},
Object {
"field": "process.ppid",
"highlights": Array [],
"value": Array [
1240,
],
},
Object {
"constant": " ",
},
Object {
"field": "auditd.log.a0",
"highlights": Array [],
"value": Array [
"9",
],
},
Object {
"field": "auditd.log.a1",
"highlights": Array [],
"value": Array [
"7f564b2672a0",
],
},
Object {
"field": "auditd.log.a2",
"highlights": Array [],
"value": Array [
"b8",
],
},
Object {
"field": "auditd.log.a3",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "auditd.log.exit",
"highlights": Array [],
"value": Array [
"184",
],
},
Object {
"field": "auditd.log.items",
"highlights": Array [],
"value": Array [
"0",
],
},
Object {
"field": "auditd.log.sequence",
"highlights": Array [],
"value": Array [
18877199,
],
},
Object {
"field": "auditd.log.ses",
"highlights": Array [],
"value": Array [
"4294967295",
],
},
Object {
"field": "auditd.log.success",
"highlights": Array [],
"value": Array [
"yes",
],
},
Object {
"field": "auditd.log.syscall",
"highlights": Array [],
"value": Array [
"44",
],
},
Object {
"field": "auditd.log.tty",
"highlights": Array [],
"value": Array [
"(none)",
],
},
Object {
"constant": " ",
},
Object {
"field": "message",
"highlights": Array [],
"value": Array [],
},
]
`);
});
});
describe('in pre-ECS format', () => {
test('auditd IPSEC rule', () => {
const event = {
'@timestamp': '2017-01-31T20:17:14.891Z',
'auditd.log.auid': '4294967295',
'auditd.log.dst': '192.168.0.0',
'auditd.log.dst_prefixlen': '16',
'auditd.log.op': 'SPD-delete',
'auditd.log.record_type': 'MAC_IPSEC_EVENT',
'auditd.log.res': '1',
'auditd.log.sequence': 18877201,
'auditd.log.ses': '4294967295',
'auditd.log.src': '192.168.2.0',
'auditd.log.src_prefixlen': '24',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'auditd.log',
'event.module': 'auditd',
'fileset.name': 'log',
'input.type': 'log',
'log.offset': 0,
'@timestamp': ['2017-01-31T20:17:14.891Z'],
'auditd.log.auid': ['4294967295'],
'auditd.log.dst': ['192.168.0.0'],
'auditd.log.dst_prefixlen': ['16'],
'auditd.log.op': ['SPD-delete'],
'auditd.log.record_type': ['MAC_IPSEC_EVENT'],
'auditd.log.res': ['1'],
'auditd.log.sequence': [18877201],
'auditd.log.ses': ['4294967295'],
'auditd.log.src': ['192.168.2.0'],
'auditd.log.src_prefixlen': ['24'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['auditd.log'],
'event.module': ['auditd'],
'fileset.name': ['log'],
'input.type': ['log'],
'log.offset': [0],
};
const message = format(event, {});
expect(message).toEqual([
{ constant: '[AuditD][' },
{ field: 'auditd.log.record_type', highlights: [], value: 'MAC_IPSEC_EVENT' },
{ field: 'auditd.log.record_type', highlights: [], value: ['MAC_IPSEC_EVENT'] },
{ constant: '] src:' },
{ field: 'auditd.log.src', highlights: [], value: '192.168.2.0' },
{ field: 'auditd.log.src', highlights: [], value: ['192.168.2.0'] },
{ constant: ' dst:' },
{ field: 'auditd.log.dst', highlights: [], value: '192.168.0.0' },
{ field: 'auditd.log.dst', highlights: [], value: ['192.168.0.0'] },
{ constant: ' op:' },
{ field: 'auditd.log.op', highlights: [], value: 'SPD-delete' },
{ field: 'auditd.log.op', highlights: [], value: ['SPD-delete'] },
]);
});
test('AuditD SYSCALL rule', () => {
const event = {
'@timestamp': '2017-01-31T20:17:14.891Z',
'auditd.log.a0': '9',
'auditd.log.a1': '7f564b2672a0',
'auditd.log.a2': 'b8',
'auditd.log.a3': '0',
'auditd.log.arch': 'x86_64',
'auditd.log.auid': '4294967295',
'auditd.log.comm': 'charon',
'auditd.log.egid': '0',
'auditd.log.euid': '0',
'auditd.log.exe': '/usr/libexec/strongswan/charon (deleted)',
'auditd.log.exit': '184',
'auditd.log.fsgid': '0',
'auditd.log.fsuid': '0',
'auditd.log.gid': '0',
'auditd.log.items': '0',
'auditd.log.pid': '1281',
'auditd.log.ppid': '1240',
'auditd.log.record_type': 'SYSCALL',
'auditd.log.sequence': 18877199,
'auditd.log.ses': '4294967295',
'auditd.log.sgid': '0',
'auditd.log.success': 'yes',
'auditd.log.suid': '0',
'auditd.log.syscall': '44',
'auditd.log.tty': '(none)',
'auditd.log.uid': '0',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'auditd.log',
'event.module': 'auditd',
'fileset.name': 'log',
'input.type': 'log',
'log.offset': 174,
'@timestamp': ['2017-01-31T20:17:14.891Z'],
'auditd.log.a0': ['9'],
'auditd.log.a1': ['7f564b2672a0'],
'auditd.log.a2': ['b8'],
'auditd.log.a3': ['0'],
'auditd.log.arch': ['x86_64'],
'auditd.log.auid': ['4294967295'],
'auditd.log.comm': ['charon'],
'auditd.log.egid': ['0'],
'auditd.log.euid': ['0'],
'auditd.log.exe': ['/usr/libexec/strongswan/charon (deleted)'],
'auditd.log.exit': ['184'],
'auditd.log.fsgid': ['0'],
'auditd.log.fsuid': ['0'],
'auditd.log.gid': ['0'],
'auditd.log.items': ['0'],
'auditd.log.pid': ['1281'],
'auditd.log.ppid': ['1240'],
'auditd.log.record_type': ['SYSCALL'],
'auditd.log.sequence': [18877199],
'auditd.log.ses': ['4294967295'],
'auditd.log.sgid': ['0'],
'auditd.log.success': ['yes'],
'auditd.log.suid': ['0'],
'auditd.log.syscall': ['44'],
'auditd.log.tty': ['(none)'],
'auditd.log.uid': ['0'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['auditd.log'],
'event.module': ['auditd'],
'fileset.name': ['log'],
'input.type': ['log'],
'log.offset': [174],
};
const message = format(event, {});
expect(message).toEqual([
{ constant: '[AuditD][' },
{ field: 'auditd.log.record_type', highlights: [], value: 'SYSCALL' },
{ field: 'auditd.log.record_type', highlights: [], value: ['SYSCALL'] },
{ constant: '] exe:' },
{
field: 'auditd.log.exe',
highlights: [],
value: '/usr/libexec/strongswan/charon (deleted)',
value: ['/usr/libexec/strongswan/charon (deleted)'],
},
{ constant: ' gid:' },
{ field: 'auditd.log.gid', highlights: [], value: '0' },
{ field: 'auditd.log.gid', highlights: [], value: ['0'] },
{ constant: ' uid:' },
{ field: 'auditd.log.uid', highlights: [], value: '0' },
{ field: 'auditd.log.uid', highlights: [], value: ['0'] },
{ constant: ' tty:' },
{ field: 'auditd.log.tty', highlights: [], value: '(none)' },
{ field: 'auditd.log.tty', highlights: [], value: ['(none)'] },
{ constant: ' pid:' },
{ field: 'auditd.log.pid', highlights: [], value: '1281' },
{ field: 'auditd.log.pid', highlights: [], value: ['1281'] },
{ constant: ' ppid:' },
{ field: 'auditd.log.ppid', highlights: [], value: '1240' },
{ field: 'auditd.log.ppid', highlights: [], value: ['1240'] },
]);
});
test('AuditD events with msg rule', () => {
const event = {
'@timestamp': '2017-01-31T20:17:14.891Z',
'auditd.log.auid': '4294967295',
'auditd.log.record_type': 'EXAMPLE',
'auditd.log.msg': 'some kind of message',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'auditd.log',
'event.module': 'auditd',
'fileset.name': 'log',
'input.type': 'log',
'log.offset': 174,
'@timestamp': ['2017-01-31T20:17:14.891Z'],
'auditd.log.auid': ['4294967295'],
'auditd.log.record_type': ['EXAMPLE'],
'auditd.log.msg': ['some kind of message'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['auditd.log'],
'event.module': ['auditd'],
'fileset.name': ['log'],
'input.type': ['log'],
'log.offset': [174],
};
const message = format(event, {});
expect(message).toEqual([
{ constant: '[AuditD][' },
{ field: 'auditd.log.record_type', highlights: [], value: 'EXAMPLE' },
{ field: 'auditd.log.record_type', highlights: [], value: ['EXAMPLE'] },
{ constant: '] ' },
{
field: 'auditd.log.msg',
highlights: [],
value: 'some kind of message',
value: ['some kind of message'],
},
]);
});
test('AuditD catchall rule', () => {
const event = {
'@timestamp': '2017-01-31T20:17:14.891Z',
'auditd.log.auid': '4294967295',
'auditd.log.record_type': 'EXAMPLE',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'auditd.log',
'event.module': 'auditd',
'fileset.name': 'log',
'input.type': 'log',
'log.offset': 174,
'@timestamp': ['2017-01-31T20:17:14.891Z'],
'auditd.log.auid': ['4294967295'],
'auditd.log.record_type': ['EXAMPLE'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['auditd.log'],
'event.module': ['auditd'],
'fileset.name': ['log'],
'input.type': ['log'],
'log.offset': [174],
};
const message = format(event, {});
expect(message).toEqual([
{ constant: '[AuditD][' },
{ field: 'auditd.log.record_type', highlights: [], value: 'EXAMPLE' },
{ field: 'auditd.log.record_type', highlights: [], value: ['EXAMPLE'] },
{ constant: '] Event without message.' },
]);
});

View file

@ -4,24 +4,28 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { labelField } from './helpers';
import { LogMessageFormattingRule } from '../rule_types';
import { labelFieldsPrefix } from './helpers';
const commonActionField = [{ constant: '[AuditD][' }, { field: 'event.action' }, { constant: ']' }];
const commonOutcomeField = [{ constant: ' ' }, { field: 'event.outcome' }];
export const filebeatAuditdRules = [
export const filebeatAuditdRules: LogMessageFormattingRule[] = [
{
// ECS format with outcome
when: {
exists: ['ecs.version', 'event.action', 'event.outcome', 'auditd.log'],
all: [
{ exists: ['ecs.version', 'event.action', 'event.outcome'] },
{ existsPrefix: ['auditd.log'] },
],
},
format: [
...commonActionField,
...commonOutcomeField,
...labelField('user', 'user'),
...labelField('process', 'process'),
...labelFieldsPrefix('user', 'user'),
...labelFieldsPrefix('process', 'process'),
{ constant: ' ' },
{ field: 'auditd.log' },
{ fieldsPrefix: 'auditd.log' },
{ constant: ' ' },
{ field: 'message' },
],
@ -29,14 +33,14 @@ export const filebeatAuditdRules = [
{
// ECS format without outcome
when: {
exists: ['ecs.version', 'event.action', 'auditd.log'],
all: [{ exists: ['ecs.version', 'event.action'] }, { existsPrefix: ['auditd.log'] }],
},
format: [
...commonActionField,
...labelField('user', 'user'),
...labelField('process', 'process'),
...labelFieldsPrefix('user', 'user'),
...labelFieldsPrefix('process', 'process'),
{ constant: ' ' },
{ field: 'auditd.log' },
{ fieldsPrefix: 'auditd.log' },
{ constant: ' ' },
{ field: 'message' },
],
@ -44,10 +48,10 @@ export const filebeatAuditdRules = [
{
// pre-ECS IPSEC_EVENT Rule
when: {
exists: ['auditd.log.record_type', 'auditd.log.src', 'auditd.log.dst', 'auditd.log.op'],
values: {
'auditd.log.record_type': 'MAC_IPSEC_EVENT',
},
all: [
{ exists: ['auditd.log.record_type', 'auditd.log.src', 'auditd.log.dst', 'auditd.log.op'] },
{ values: { 'auditd.log.record_type': 'MAC_IPSEC_EVENT' } },
],
},
format: [
{ constant: '[AuditD][' },
@ -63,18 +67,20 @@ export const filebeatAuditdRules = [
{
// pre-ECS SYSCALL Rule
when: {
exists: [
'auditd.log.record_type',
'auditd.log.exe',
'auditd.log.gid',
'auditd.log.uid',
'auditd.log.tty',
'auditd.log.pid',
'auditd.log.ppid',
all: [
{
exists: [
'auditd.log.record_type',
'auditd.log.exe',
'auditd.log.gid',
'auditd.log.uid',
'auditd.log.tty',
'auditd.log.pid',
'auditd.log.ppid',
],
},
{ values: { 'auditd.log.record_type': 'SYSCALL' } },
],
values: {
'auditd.log.record_type': 'SYSCALL',
},
},
format: [
{ constant: '[AuditD][' },

View file

@ -13,135 +13,155 @@ describe('Filebeat Rules', () => {
describe('in pre-ECS format', () => {
test('icinga debug log', () => {
const flattenedDocument = {
'@timestamp': '2017-04-04T11:43:09.000Z',
'event.dataset': 'icinga.debug',
'fileset.module': 'icinga',
'fileset.name': 'debug',
'icinga.debug.facility': 'GraphiteWriter',
'icinga.debug.message':
'@timestamp': ['2017-04-04T11:43:09.000Z'],
'event.dataset': ['icinga.debug'],
'fileset.module': ['icinga'],
'fileset.name': ['debug'],
'icinga.debug.facility': ['GraphiteWriter'],
'icinga.debug.message': [
"Add to metric list:'icinga2.demo.services.procs.procs.perfdata.procs.warn 250 1491306189'.",
'icinga.debug.severity': 'debug',
'input.type': 'log',
offset: 0,
'prospector.type': 'log',
],
'icinga.debug.severity': ['debug'],
'input.type': ['log'],
offset: [0],
'prospector.type': ['log'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[Icinga][",
},
Object {
"field": "icinga.debug.facility",
"highlights": Array [],
"value": "GraphiteWriter",
},
Object {
"constant": "][",
},
Object {
"field": "icinga.debug.severity",
"highlights": Array [],
"value": "debug",
},
Object {
"constant": "] ",
},
Object {
"field": "icinga.debug.message",
"highlights": Array [],
"value": "Add to metric list:'icinga2.demo.services.procs.procs.perfdata.procs.warn 250 1491306189'.",
},
]
`);
Array [
Object {
"constant": "[Icinga][",
},
Object {
"field": "icinga.debug.facility",
"highlights": Array [],
"value": Array [
"GraphiteWriter",
],
},
Object {
"constant": "][",
},
Object {
"field": "icinga.debug.severity",
"highlights": Array [],
"value": Array [
"debug",
],
},
Object {
"constant": "] ",
},
Object {
"field": "icinga.debug.message",
"highlights": Array [],
"value": Array [
"Add to metric list:'icinga2.demo.services.procs.procs.perfdata.procs.warn 250 1491306189'.",
],
},
]
`);
});
test('icinga main log', () => {
const flattenedDocument = {
'@timestamp': '2017-04-04T09:16:34.000Z',
'event.dataset': 'icinga.main',
'fileset.module': 'icinga',
'fileset.name': 'main',
'icinga.main.facility': 'Notification',
'icinga.main.message':
'@timestamp': ['2017-04-04T09:16:34.000Z'],
'event.dataset': ['icinga.main'],
'fileset.module': ['icinga'],
'fileset.name': ['main'],
'icinga.main.facility': ['Notification'],
'icinga.main.message': [
"Sending 'Recovery' notification 'demo!load!mail-icingaadmin for user 'on-call'",
'icinga.main.severity': 'information',
'input.type': 'log',
offset: 0,
'prospector.type': 'log',
],
'icinga.main.severity': ['information'],
'input.type': ['log'],
offset: [0],
'prospector.type': ['log'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[Icinga][",
},
Object {
"field": "icinga.main.facility",
"highlights": Array [],
"value": "Notification",
},
Object {
"constant": "][",
},
Object {
"field": "icinga.main.severity",
"highlights": Array [],
"value": "information",
},
Object {
"constant": "] ",
},
Object {
"field": "icinga.main.message",
"highlights": Array [],
"value": "Sending 'Recovery' notification 'demo!load!mail-icingaadmin for user 'on-call'",
},
]
`);
Array [
Object {
"constant": "[Icinga][",
},
Object {
"field": "icinga.main.facility",
"highlights": Array [],
"value": Array [
"Notification",
],
},
Object {
"constant": "][",
},
Object {
"field": "icinga.main.severity",
"highlights": Array [],
"value": Array [
"information",
],
},
Object {
"constant": "] ",
},
Object {
"field": "icinga.main.message",
"highlights": Array [],
"value": Array [
"Sending 'Recovery' notification 'demo!load!mail-icingaadmin for user 'on-call'",
],
},
]
`);
});
test('icinga startup log', () => {
const flattenedDocument = {
'event.dataset': 'icinga.startup',
'fileset.module': 'icinga',
'fileset.name': 'startup',
'icinga.startup.facility': 'cli',
'icinga.startup.message': 'Icinga application loader (version: r2.6.3-1)',
'icinga.startup.severity': 'information',
'input.type': 'log',
offset: 0,
'prospector.type': 'log',
'event.dataset': ['icinga.startup'],
'fileset.module': ['icinga'],
'fileset.name': ['startup'],
'icinga.startup.facility': ['cli'],
'icinga.startup.message': ['Icinga application loader (version: r2.6.3-1)'],
'icinga.startup.severity': ['information'],
'input.type': ['log'],
offset: [0],
'prospector.type': ['log'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[Icinga][",
},
Object {
"field": "icinga.startup.facility",
"highlights": Array [],
"value": "cli",
},
Object {
"constant": "][",
},
Object {
"field": "icinga.startup.severity",
"highlights": Array [],
"value": "information",
},
Object {
"constant": "] ",
},
Object {
"field": "icinga.startup.message",
"highlights": Array [],
"value": "Icinga application loader (version: r2.6.3-1)",
},
]
`);
Array [
Object {
"constant": "[Icinga][",
},
Object {
"field": "icinga.startup.facility",
"highlights": Array [],
"value": Array [
"cli",
],
},
Object {
"constant": "][",
},
Object {
"field": "icinga.startup.severity",
"highlights": Array [],
"value": Array [
"information",
],
},
Object {
"constant": "] ",
},
Object {
"field": "icinga.startup.message",
"highlights": Array [],
"value": Array [
"Icinga application loader (version: r2.6.3-1)",
],
},
]
`);
});
});
});

View file

@ -13,48 +13,54 @@ describe('Filebeat Rules', () => {
describe('in ECS format', () => {
test('kafka log', () => {
const flattenedDocument = {
'@timestamp': '2017-08-04T10:48:21.063Z',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'kafka.log',
'event.module': 'kafka',
'fileset.name': 'log',
'input.type': 'log',
'kafka.log.class': 'kafka.controller.KafkaController',
'kafka.log.component': 'Controller 0',
'log.level': 'INFO',
'log.offset': 131,
message: '0 successfully elected as the controller',
'service.type': 'kafka',
'@timestamp': ['2017-08-04T10:48:21.063Z'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['kafka.log'],
'event.module': ['kafka'],
'fileset.name': ['log'],
'input.type': ['log'],
'kafka.log.class': ['kafka.controller.KafkaController'],
'kafka.log.component': ['Controller 0'],
'log.level': ['INFO'],
'log.offset': [131],
message: ['0 successfully elected as the controller'],
'service.type': ['kafka'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": "kafka.log",
},
Object {
"constant": "][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": "INFO",
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": "0 successfully elected as the controller",
},
]
`);
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": Array [
"kafka.log",
],
},
Object {
"constant": "][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": Array [
"INFO",
],
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": Array [
"0 successfully elected as the controller",
],
},
]
`);
});
});
});

View file

@ -13,194 +13,256 @@ describe('Filebeat Rules', () => {
describe('in ECS format', () => {
test('logstash log', () => {
const flattenedDocument = {
'@timestamp': '2017-10-23T14:20:12.046Z',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'logstash.log',
'event.module': 'logstash',
'fileset.name': 'log',
'input.type': 'log',
'log.level': 'INFO',
'log.offset': 0,
'logstash.log.module': 'logstash.modules.scaffold',
message:
'@timestamp': ['2017-10-23T14:20:12.046Z'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['logstash.log'],
'event.module': ['logstash'],
'fileset.name': ['log'],
'input.type': ['log'],
'log.level': ['INFO'],
'log.offset': [0],
'logstash.log.module': ['logstash.modules.scaffold'],
message: [
'Initializing module {:module_name=>"fb_apache", :directory=>"/usr/share/logstash/modules/fb_apache/configuration"}',
'service.type': 'logstash',
],
'service.type': ['logstash'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": "logstash.log",
},
Object {
"constant": "][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": "INFO",
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": "Initializing module {:module_name=>\\"fb_apache\\", :directory=>\\"/usr/share/logstash/modules/fb_apache/configuration\\"}",
},
]
`);
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": Array [
"logstash.log",
],
},
Object {
"constant": "][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": Array [
"INFO",
],
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": Array [
"Initializing module {:module_name=>\\"fb_apache\\", :directory=>\\"/usr/share/logstash/modules/fb_apache/configuration\\"}",
],
},
]
`);
});
test('logstash slowlog', () => {
const flattenedDocument = {
'@timestamp': '2017-10-30T09:57:58.243Z',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'logstash.slowlog',
'event.duration': 3027675106,
'event.module': 'logstash',
'fileset.name': 'slowlog',
'input.type': 'log',
'log.level': 'WARN',
'log.offset': 0,
'logstash.slowlog': {
event:
'"{\\"@version\\":\\"1\\",\\"@timestamp\\":\\"2017-10-30T13:57:55.130Z\\",\\"host\\":\\"sashimi\\",\\"sequence\\":0,\\"message\\":\\"Hello world!\\"}"',
module: 'slowlog.logstash.filters.sleep',
plugin_name: 'sleep',
plugin_params:
'{"time"=>3, "id"=>"e4e12a4e3082615c5427079bf4250dbfa338ebac10f8ea9912d7b98a14f56b8c"}',
plugin_type: 'filters',
took_in_millis: 3027,
},
'service.type': 'logstash',
'@timestamp': ['2017-10-30T09:57:58.243Z'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['logstash.slowlog'],
'event.duration': [3027675106],
'event.module': ['logstash'],
'fileset.name': ['slowlog'],
'input.type': ['log'],
'log.level': ['WARN'],
'log.offset': [0],
'logstash.slowlog.event': [
'"{\\"@version\\":\\"1\\",\\"@timestamp\\":\\"2017-10-30T13:57:55.130Z\\",\\"host\\":\\"sashimi\\",\\"sequence\\":0,\\"message\\":\\"Hello world!\\"}"',
],
'logstash.slowlog.module': ['slowlog.logstash.filters.sleep'],
'logstash.slowlog.plugin_name': ['sleep'],
'logstash.slowlog.plugin_params': [
'{"time"=>3, "id"=>"e4e12a4e3082615c5427079bf4250dbfa338ebac10f8ea9912d7b98a14f56b8c"}',
],
'logstash.slowlog.plugin_type': ['filters'],
'logstash.slowlog.took_in_millis': [3027],
'service.type': ['logstash'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[Logstash][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": "WARN",
},
Object {
"constant": "] ",
},
Object {
"field": "logstash.slowlog",
"highlights": Array [],
"value": "{\\"event\\":\\"\\\\\\"{\\\\\\\\\\\\\\"@version\\\\\\\\\\\\\\":\\\\\\\\\\\\\\"1\\\\\\\\\\\\\\",\\\\\\\\\\\\\\"@timestamp\\\\\\\\\\\\\\":\\\\\\\\\\\\\\"2017-10-30T13:57:55.130Z\\\\\\\\\\\\\\",\\\\\\\\\\\\\\"host\\\\\\\\\\\\\\":\\\\\\\\\\\\\\"sashimi\\\\\\\\\\\\\\",\\\\\\\\\\\\\\"sequence\\\\\\\\\\\\\\":0,\\\\\\\\\\\\\\"message\\\\\\\\\\\\\\":\\\\\\\\\\\\\\"Hello world!\\\\\\\\\\\\\\"}\\\\\\"\\",\\"module\\":\\"slowlog.logstash.filters.sleep\\",\\"plugin_name\\":\\"sleep\\",\\"plugin_params\\":\\"{\\\\\\"time\\\\\\"=>3, \\\\\\"id\\\\\\"=>\\\\\\"e4e12a4e3082615c5427079bf4250dbfa338ebac10f8ea9912d7b98a14f56b8c\\\\\\"}\\",\\"plugin_type\\":\\"filters\\",\\"took_in_millis\\":3027}",
},
]
`);
Array [
Object {
"constant": "[Logstash][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": Array [
"WARN",
],
},
Object {
"constant": "] ",
},
Object {
"field": "logstash.slowlog.event",
"highlights": Array [],
"value": Array [
"\\"{\\\\\\"@version\\\\\\":\\\\\\"1\\\\\\",\\\\\\"@timestamp\\\\\\":\\\\\\"2017-10-30T13:57:55.130Z\\\\\\",\\\\\\"host\\\\\\":\\\\\\"sashimi\\\\\\",\\\\\\"sequence\\\\\\":0,\\\\\\"message\\\\\\":\\\\\\"Hello world!\\\\\\"}\\"",
],
},
Object {
"field": "logstash.slowlog.module",
"highlights": Array [],
"value": Array [
"slowlog.logstash.filters.sleep",
],
},
Object {
"field": "logstash.slowlog.plugin_name",
"highlights": Array [],
"value": Array [
"sleep",
],
},
Object {
"field": "logstash.slowlog.plugin_params",
"highlights": Array [],
"value": Array [
"{\\"time\\"=>3, \\"id\\"=>\\"e4e12a4e3082615c5427079bf4250dbfa338ebac10f8ea9912d7b98a14f56b8c\\"}",
],
},
Object {
"field": "logstash.slowlog.plugin_type",
"highlights": Array [],
"value": Array [
"filters",
],
},
Object {
"field": "logstash.slowlog.took_in_millis",
"highlights": Array [],
"value": Array [
3027,
],
},
]
`);
});
});
describe('in pre-ECS format', () => {
test('logstash log', () => {
const flattenedDocument = {
'@timestamp': '2017-10-23T14:20:12.046Z',
'event.dataset': 'logstash.log',
'fileset.module': 'logstash',
'fileset.name': 'log',
'input.type': 'log',
'logstash.log.level': 'INFO',
'logstash.log.message':
'@timestamp': ['2017-10-23T14:20:12.046Z'],
'event.dataset': ['logstash.log'],
'fileset.module': ['logstash'],
'fileset.name': ['log'],
'input.type': ['log'],
'logstash.log.level': ['INFO'],
'logstash.log.message': [
'Initializing module {:module_name=>"fb_apache", :directory=>"/usr/share/logstash/modules/fb_apache/configuration"}',
'logstash.log.module': 'logstash.modules.scaffold',
offset: 0,
'prospector.type': 'log',
],
'logstash.log.module': ['logstash.modules.scaffold'],
offset: [0],
'prospector.type': ['log'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[Logstash][",
},
Object {
"field": "logstash.log.level",
"highlights": Array [],
"value": "INFO",
},
Object {
"constant": "] ",
},
Object {
"field": "logstash.log.module",
"highlights": Array [],
"value": "logstash.modules.scaffold",
},
Object {
"constant": " - ",
},
Object {
"field": "logstash.log.message",
"highlights": Array [],
"value": "Initializing module {:module_name=>\\"fb_apache\\", :directory=>\\"/usr/share/logstash/modules/fb_apache/configuration\\"}",
},
]
`);
Array [
Object {
"constant": "[Logstash][",
},
Object {
"field": "logstash.log.level",
"highlights": Array [],
"value": Array [
"INFO",
],
},
Object {
"constant": "] ",
},
Object {
"field": "logstash.log.module",
"highlights": Array [],
"value": Array [
"logstash.modules.scaffold",
],
},
Object {
"constant": " - ",
},
Object {
"field": "logstash.log.message",
"highlights": Array [],
"value": Array [
"Initializing module {:module_name=>\\"fb_apache\\", :directory=>\\"/usr/share/logstash/modules/fb_apache/configuration\\"}",
],
},
]
`);
});
test('logstash slowlog', () => {
const flattenedDocument = {
'@timestamp': '2017-10-30T09:57:58.243Z',
'event.dataset': 'logstash.slowlog',
'fileset.module': 'logstash',
'fileset.name': 'slowlog',
'input.type': 'log',
'logstash.slowlog.event':
'@timestamp': ['2017-10-30T09:57:58.243Z'],
'event.dataset': ['logstash.slowlog'],
'fileset.module': ['logstash'],
'fileset.name': ['slowlog'],
'input.type': ['log'],
'logstash.slowlog.event': [
'"{\\"@version\\":\\"1\\",\\"@timestamp\\":\\"2017-10-30T13:57:55.130Z\\",\\"host\\":\\"sashimi\\",\\"sequence\\":0,\\"message\\":\\"Hello world!\\"}"',
'logstash.slowlog.level': 'WARN',
'logstash.slowlog.message':
],
'logstash.slowlog.level': ['WARN'],
'logstash.slowlog.message': [
'event processing time {:plugin_params=>{"time"=>3, "id"=>"e4e12a4e3082615c5427079bf4250dbfa338ebac10f8ea9912d7b98a14f56b8c"}, :took_in_nanos=>3027675106, :took_in_millis=>3027, :event=>"{\\"@version\\":\\"1\\",\\"@timestamp\\":\\"2017-10-30T13:57:55.130Z\\",\\"host\\":\\"sashimi\\",\\"sequence\\":0,\\"message\\":\\"Hello world!\\"}"}',
'logstash.slowlog.module': 'slowlog.logstash.filters.sleep',
'logstash.slowlog.plugin_name': 'sleep',
'logstash.slowlog.plugin_params':
],
'logstash.slowlog.module': ['slowlog.logstash.filters.sleep'],
'logstash.slowlog.plugin_name': ['sleep'],
'logstash.slowlog.plugin_params': [
'{"time"=>3, "id"=>"e4e12a4e3082615c5427079bf4250dbfa338ebac10f8ea9912d7b98a14f56b8c"}',
'logstash.slowlog.plugin_type': 'filters',
'logstash.slowlog.took_in_millis': 3027,
'logstash.slowlog.took_in_nanos': 3027675106,
offset: 0,
'prospector.type': 'log',
],
'logstash.slowlog.plugin_type': ['filters'],
'logstash.slowlog.took_in_millis': [3027],
'logstash.slowlog.took_in_nanos': [3027675106],
offset: [0],
'prospector.type': ['log'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[Logstash][",
},
Object {
"field": "logstash.slowlog.level",
"highlights": Array [],
"value": "WARN",
},
Object {
"constant": "] ",
},
Object {
"field": "logstash.slowlog.module",
"highlights": Array [],
"value": "slowlog.logstash.filters.sleep",
},
Object {
"constant": " - ",
},
Object {
"field": "logstash.slowlog.message",
"highlights": Array [],
"value": "event processing time {:plugin_params=>{\\"time\\"=>3, \\"id\\"=>\\"e4e12a4e3082615c5427079bf4250dbfa338ebac10f8ea9912d7b98a14f56b8c\\"}, :took_in_nanos=>3027675106, :took_in_millis=>3027, :event=>\\"{\\\\\\"@version\\\\\\":\\\\\\"1\\\\\\",\\\\\\"@timestamp\\\\\\":\\\\\\"2017-10-30T13:57:55.130Z\\\\\\",\\\\\\"host\\\\\\":\\\\\\"sashimi\\\\\\",\\\\\\"sequence\\\\\\":0,\\\\\\"message\\\\\\":\\\\\\"Hello world!\\\\\\"}\\"}",
},
]
`);
Array [
Object {
"constant": "[Logstash][",
},
Object {
"field": "logstash.slowlog.level",
"highlights": Array [],
"value": Array [
"WARN",
],
},
Object {
"constant": "] ",
},
Object {
"field": "logstash.slowlog.module",
"highlights": Array [],
"value": Array [
"slowlog.logstash.filters.sleep",
],
},
Object {
"constant": " - ",
},
Object {
"field": "logstash.slowlog.message",
"highlights": Array [],
"value": Array [
"event processing time {:plugin_params=>{\\"time\\"=>3, \\"id\\"=>\\"e4e12a4e3082615c5427079bf4250dbfa338ebac10f8ea9912d7b98a14f56b8c\\"}, :took_in_nanos=>3027675106, :took_in_millis=>3027, :event=>\\"{\\\\\\"@version\\\\\\":\\\\\\"1\\\\\\",\\\\\\"@timestamp\\\\\\":\\\\\\"2017-10-30T13:57:55.130Z\\\\\\",\\\\\\"host\\\\\\":\\\\\\"sashimi\\\\\\",\\\\\\"sequence\\\\\\":0,\\\\\\"message\\\\\\":\\\\\\"Hello world!\\\\\\"}\\"}",
],
},
]
`);
});
});
});

View file

@ -34,7 +34,7 @@ export const filebeatLogstashRules = [
{
// ECS
when: {
exists: ['ecs.version', 'logstash.slowlog'],
all: [{ exists: ['ecs.version'] }, { existsPrefix: ['logstash.slowlog'] }],
},
format: [
{
@ -47,7 +47,7 @@ export const filebeatLogstashRules = [
constant: '] ',
},
{
field: 'logstash.slowlog',
fieldsPrefix: 'logstash.slowlog',
},
],
},

View file

@ -13,40 +13,45 @@ describe('Filebeat Rules', () => {
describe('in pre-ECS format', () => {
test('mongodb log', () => {
const flattenedDocument = {
'@timestamp': '2018-02-05T12:44:56.677Z',
'event.dataset': 'mongodb.log',
'fileset.module': 'mongodb',
'fileset.name': 'log',
'input.type': 'log',
'mongodb.log.component': 'STORAGE',
'mongodb.log.context': 'initandlisten',
'mongodb.log.message':
'@timestamp': ['2018-02-05T12:44:56.677Z'],
'event.dataset': ['mongodb.log'],
'fileset.module': ['mongodb'],
'fileset.name': ['log'],
'input.type': ['log'],
'mongodb.log.component': ['STORAGE'],
'mongodb.log.context': ['initandlisten'],
'mongodb.log.message': [
'wiredtiger_open config: create,cache_size=8G,session_max=20000,eviction=(threads_max=4),config_base=false,statistics=(fast),log=(enabled=true,archive=true,path=journal,compressor=snappy),file_manager=(close_idle_time=100000),checkpoint=(wait=60,log_size=2GB),statistics_log=(wait=0),',
'mongodb.log.severity': 'I',
offset: 281,
'prospector.type': 'log',
],
'mongodb.log.severity': ['I'],
offset: [281],
'prospector.type': ['log'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[MongoDB][",
},
Object {
"field": "mongodb.log.component",
"highlights": Array [],
"value": "STORAGE",
},
Object {
"constant": "] ",
},
Object {
"field": "mongodb.log.message",
"highlights": Array [],
"value": "wiredtiger_open config: create,cache_size=8G,session_max=20000,eviction=(threads_max=4),config_base=false,statistics=(fast),log=(enabled=true,archive=true,path=journal,compressor=snappy),file_manager=(close_idle_time=100000),checkpoint=(wait=60,log_size=2GB),statistics_log=(wait=0),",
},
]
`);
Array [
Object {
"constant": "[MongoDB][",
},
Object {
"field": "mongodb.log.component",
"highlights": Array [],
"value": Array [
"STORAGE",
],
},
Object {
"constant": "] ",
},
Object {
"field": "mongodb.log.message",
"highlights": Array [],
"value": Array [
"wiredtiger_open config: create,cache_size=8G,session_max=20000,eviction=(threads_max=4),config_base=false,statistics=(fast),log=(enabled=true,archive=true,path=journal,compressor=snappy),file_manager=(close_idle_time=100000),checkpoint=(wait=60,log_size=2GB),statistics_log=(wait=0),",
],
},
]
`);
});
});
});

View file

@ -13,139 +13,158 @@ describe('Filebeat Rules', () => {
describe('in ECS format', () => {
test('mysql error log', () => {
const flattenedDocument = {
'@timestamp': '2016-12-09T12:08:33.335Z',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'mysql.error',
'event.module': 'mysql',
'fileset.name': 'error',
'input.type': 'log',
'log.level': 'Warning',
'log.offset': 92,
message:
'@timestamp': ['2016-12-09T12:08:33.335Z'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['mysql.error'],
'event.module': ['mysql'],
'fileset.name': ['error'],
'input.type': ['log'],
'log.level': ['Warning'],
'log.offset': [92],
message: [
'TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).',
'mysql.thread_id': 0,
'service.type': 'mysql',
],
'mysql.thread_id': [0],
'service.type': ['mysql'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": "mysql.error",
},
Object {
"constant": "][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": "Warning",
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": "TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).",
},
]
`);
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": Array [
"mysql.error",
],
},
Object {
"constant": "][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": Array [
"Warning",
],
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": Array [
"TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).",
],
},
]
`);
});
test('mysql slowlog', () => {
const flattenedDocument = {
'@timestamp': '2018-08-07T08:27:47.000Z',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'mysql.slowlog',
'event.duration': 4071491000,
'event.module': 'mysql',
'fileset.name': 'slowlog',
'input.type': 'log',
'@timestamp': ['2018-08-07T08:27:47.000Z'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['mysql.slowlog'],
'event.duration': [4071491000],
'event.module': ['mysql'],
'fileset.name': ['slowlog'],
'input.type': ['log'],
'log.flags': ['multiline'],
'log.offset': 526,
'mysql.slowlog.current_user': 'appuser',
'mysql.slowlog.lock_time.sec': 0.000212,
'mysql.slowlog.query':
'log.offset': [526],
'mysql.slowlog.current_user': ['appuser'],
'mysql.slowlog.lock_time.sec': [0.000212],
'mysql.slowlog.query': [
'SELECT mcu.mcu_guid, mcu.cus_guid, mcu.mcu_url, mcu.mcu_crawlelements, mcu.mcu_order, GROUP_CONCAT(mca.mca_guid SEPARATOR ";") as mca_guid\n FROM kat_mailcustomerurl mcu, kat_customer cus, kat_mailcampaign mca\n WHERE cus.cus_guid = mcu.cus_guid\n AND cus.pro_code = \'CYB\'\n AND cus.cus_offline = 0\n AND mca.cus_guid = cus.cus_guid\n AND (mcu.mcu_date IS NULL OR mcu.mcu_date < CURDATE())\n AND mcu.mcu_crawlelements IS NOT NULL\n GROUP BY mcu.mcu_guid\n ORDER BY mcu.mcu_order ASC\n LIMIT 1000;',
'mysql.slowlog.rows_examined': 1489615,
'mysql.slowlog.rows_sent': 1000,
'mysql.thread_id': 10997316,
'service.type': 'mysql',
'source.domain': 'apphost',
'source.ip': '1.1.1.1',
'user.name': 'appuser',
],
'mysql.slowlog.rows_examined': [1489615],
'mysql.slowlog.rows_sent': [1000],
'mysql.thread_id': [10997316],
'service.type': ['mysql'],
'source.domain': ['apphost'],
'source.ip': ['1.1.1.1'],
'user.name': ['appuser'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[MySQL][slowlog] ",
},
Object {
"field": "user.name",
"highlights": Array [],
"value": "appuser",
},
Object {
"constant": "@",
},
Object {
"field": "source.domain",
"highlights": Array [],
"value": "apphost",
},
Object {
"constant": " [",
},
Object {
"field": "source.ip",
"highlights": Array [],
"value": "1.1.1.1",
},
Object {
"constant": "] ",
},
Object {
"constant": " - ",
},
Object {
"field": "event.duration",
"highlights": Array [],
"value": "4071491000",
},
Object {
"constant": " ns - ",
},
Object {
"field": "mysql.slowlog.query",
"highlights": Array [],
"value": "SELECT mcu.mcu_guid, mcu.cus_guid, mcu.mcu_url, mcu.mcu_crawlelements, mcu.mcu_order, GROUP_CONCAT(mca.mca_guid SEPARATOR \\";\\") as mca_guid
FROM kat_mailcustomerurl mcu, kat_customer cus, kat_mailcampaign mca
WHERE cus.cus_guid = mcu.cus_guid
AND cus.pro_code = 'CYB'
AND cus.cus_offline = 0
AND mca.cus_guid = cus.cus_guid
AND (mcu.mcu_date IS NULL OR mcu.mcu_date < CURDATE())
AND mcu.mcu_crawlelements IS NOT NULL
GROUP BY mcu.mcu_guid
ORDER BY mcu.mcu_order ASC
LIMIT 1000;",
},
]
`);
Array [
Object {
"constant": "[MySQL][slowlog] ",
},
Object {
"field": "user.name",
"highlights": Array [],
"value": Array [
"appuser",
],
},
Object {
"constant": "@",
},
Object {
"field": "source.domain",
"highlights": Array [],
"value": Array [
"apphost",
],
},
Object {
"constant": " [",
},
Object {
"field": "source.ip",
"highlights": Array [],
"value": Array [
"1.1.1.1",
],
},
Object {
"constant": "] ",
},
Object {
"constant": " - ",
},
Object {
"field": "event.duration",
"highlights": Array [],
"value": Array [
4071491000,
],
},
Object {
"constant": " ns - ",
},
Object {
"field": "mysql.slowlog.query",
"highlights": Array [],
"value": Array [
"SELECT mcu.mcu_guid, mcu.cus_guid, mcu.mcu_url, mcu.mcu_crawlelements, mcu.mcu_order, GROUP_CONCAT(mca.mca_guid SEPARATOR \\";\\") as mca_guid
FROM kat_mailcustomerurl mcu, kat_customer cus, kat_mailcampaign mca
WHERE cus.cus_guid = mcu.cus_guid
AND cus.pro_code = 'CYB'
AND cus.cus_offline = 0
AND mca.cus_guid = cus.cus_guid
AND (mcu.mcu_date IS NULL OR mcu.mcu_date < CURDATE())
AND mcu.mcu_crawlelements IS NOT NULL
GROUP BY mcu.mcu_guid
ORDER BY mcu.mcu_order ASC
LIMIT 1000;",
],
},
]
`);
});
});
describe('in pre-ECS format', () => {
test('mysql error log', () => {
const errorDoc = {
'mysql.error.message':
'mysql.error.message': [
"Access denied for user 'petclinicdd'@'47.153.152.234' (using password: YES)",
],
};
const message = format(errorDoc, {});
expect(message).toEqual([
@ -155,18 +174,18 @@ Array [
{
field: 'mysql.error.message',
highlights: [],
value: "Access denied for user 'petclinicdd'@'47.153.152.234' (using password: YES)",
value: ["Access denied for user 'petclinicdd'@'47.153.152.234' (using password: YES)"],
},
]);
});
test('mysql slow log', () => {
const errorDoc = {
'mysql.slowlog.query': 'select * from hosts',
'mysql.slowlog.query_time.sec': 5,
'mysql.slowlog.user': 'admin',
'mysql.slowlog.ip': '192.168.1.42',
'mysql.slowlog.host': 'webserver-01',
'mysql.slowlog.query': ['select * from hosts'],
'mysql.slowlog.query_time.sec': [5],
'mysql.slowlog.user': ['admin'],
'mysql.slowlog.ip': ['192.168.1.42'],
'mysql.slowlog.host': ['webserver-01'],
};
const message = format(errorDoc, {});
expect(message).toEqual([
@ -176,7 +195,7 @@ Array [
{
field: 'mysql.slowlog.user',
highlights: [],
value: 'admin',
value: ['admin'],
},
{
constant: '@',
@ -184,7 +203,7 @@ Array [
{
field: 'mysql.slowlog.host',
highlights: [],
value: 'webserver-01',
value: ['webserver-01'],
},
{
constant: ' [',
@ -192,7 +211,7 @@ Array [
{
field: 'mysql.slowlog.ip',
highlights: [],
value: '192.168.1.42',
value: ['192.168.1.42'],
},
{
constant: '] ',
@ -203,7 +222,7 @@ Array [
{
field: 'mysql.slowlog.query_time.sec',
highlights: [],
value: '5',
value: [5],
},
{
constant: ' s - ',
@ -211,7 +230,7 @@ Array [
{
field: 'mysql.slowlog.query',
highlights: [],
value: 'select * from hosts',
value: ['select * from hosts'],
},
]);
});

View file

@ -13,252 +13,293 @@ describe('Filebeat Rules', () => {
describe('in ECS format', () => {
test('Nginx Access', () => {
const flattenedDocument = {
'@timestamp': '2017-05-29T19:02:48.000Z',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'nginx.access',
'event.module': 'nginx',
'fileset.name': 'access',
'http.request.method': 'GET',
'http.request.referrer': '-',
'http.response.body.bytes': 612,
'http.response.status_code': 404,
'http.version': '1.1',
'input.type': 'log',
'log.offset': 183,
'service.type': 'nginx',
'source.ip': '172.17.0.1',
'url.original': '/stringpatch',
'user.name': '-',
'user_agent.device': 'Other',
'user_agent.major': '15',
'user_agent.minor': '0',
'user_agent.name': 'Firefox Alpha',
'user_agent.original':
'@timestamp': ['2017-05-29T19:02:48.000Z'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['nginx.access'],
'event.module': ['nginx'],
'fileset.name': ['access'],
'http.request.method': ['GET'],
'http.request.referrer': ['-'],
'http.response.body.bytes': [612],
'http.response.status_code': [404],
'http.version': ['1.1'],
'input.type': ['log'],
'log.offset': [183],
'service.type': ['nginx'],
'source.ip': ['172.17.0.1'],
'url.original': ['/stringpatch'],
'user.name': ['-'],
'user_agent.device': ['Other'],
'user_agent.major': ['15'],
'user_agent.minor': ['0'],
'user_agent.name': ['Firefox Alpha'],
'user_agent.original': [
'Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2',
'user_agent.os.full_name': 'Windows 7',
'user_agent.os.name': 'Windows 7',
'user_agent.patch': 'a2',
],
'user_agent.os.full_name': ['Windows 7'],
'user_agent.os.name': ['Windows 7'],
'user_agent.patch': ['a2'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[",
},
Object {
"field": "event.module",
"highlights": Array [],
"value": "nginx",
},
Object {
"constant": "][access] ",
},
Object {
"field": "source.ip",
"highlights": Array [],
"value": "172.17.0.1",
},
Object {
"constant": " ",
},
Object {
"field": "user.name",
"highlights": Array [],
"value": "-",
},
Object {
"constant": " \\"",
},
Object {
"field": "http.request.method",
"highlights": Array [],
"value": "GET",
},
Object {
"constant": " ",
},
Object {
"field": "url.original",
"highlights": Array [],
"value": "/stringpatch",
},
Object {
"constant": " HTTP/",
},
Object {
"field": "http.version",
"highlights": Array [],
"value": "1.1",
},
Object {
"constant": "\\" ",
},
Object {
"field": "http.response.status_code",
"highlights": Array [],
"value": "404",
},
Object {
"constant": " ",
},
Object {
"field": "http.response.body.bytes",
"highlights": Array [],
"value": "612",
},
]
`);
Array [
Object {
"constant": "[",
},
Object {
"field": "event.module",
"highlights": Array [],
"value": Array [
"nginx",
],
},
Object {
"constant": "][access] ",
},
Object {
"field": "source.ip",
"highlights": Array [],
"value": Array [
"172.17.0.1",
],
},
Object {
"constant": " ",
},
Object {
"field": "user.name",
"highlights": Array [],
"value": Array [
"-",
],
},
Object {
"constant": " \\"",
},
Object {
"field": "http.request.method",
"highlights": Array [],
"value": Array [
"GET",
],
},
Object {
"constant": " ",
},
Object {
"field": "url.original",
"highlights": Array [],
"value": Array [
"/stringpatch",
],
},
Object {
"constant": " HTTP/",
},
Object {
"field": "http.version",
"highlights": Array [],
"value": Array [
"1.1",
],
},
Object {
"constant": "\\" ",
},
Object {
"field": "http.response.status_code",
"highlights": Array [],
"value": Array [
404,
],
},
Object {
"constant": " ",
},
Object {
"field": "http.response.body.bytes",
"highlights": Array [],
"value": Array [
612,
],
},
]
`);
});
test('Nginx Error', () => {
const flattenedDocument = {
'@timestamp': '2016-10-25T14:49:34.000Z',
'ecs.version': '1.0.0-beta2',
'event.dataset': 'nginx.error',
'event.module': 'nginx',
'fileset.name': 'error',
'input.type': 'log',
'log.level': 'error',
'log.offset': 0,
message:
'@timestamp': ['2016-10-25T14:49:34.000Z'],
'ecs.version': ['1.0.0-beta2'],
'event.dataset': ['nginx.error'],
'event.module': ['nginx'],
'fileset.name': ['error'],
'input.type': ['log'],
'log.level': ['error'],
'log.offset': [0],
message: [
'open() "/usr/local/Cellar/nginx/1.10.2_1/html/favicon.ico" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost:8080", referrer: "http://localhost:8080/"',
'nginx.error.connection_id': 1,
'process.pid': 54053,
'process.thread.id': 0,
'service.type': 'nginx',
],
'nginx.error.connection_id': [1],
'process.pid': [54053],
'process.thread.id': [0],
'service.type': ['nginx'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[nginx]",
},
Object {
"constant": "[",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": "error",
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": "open() \\"/usr/local/Cellar/nginx/1.10.2_1/html/favicon.ico\\" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: \\"GET /favicon.ico HTTP/1.1\\", host: \\"localhost:8080\\", referrer: \\"http://localhost:8080/\\"",
},
]
`);
Array [
Object {
"constant": "[nginx]",
},
Object {
"constant": "[",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": Array [
"error",
],
},
Object {
"constant": "] ",
},
Object {
"field": "message",
"highlights": Array [],
"value": Array [
"open() \\"/usr/local/Cellar/nginx/1.10.2_1/html/favicon.ico\\" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: \\"GET /favicon.ico HTTP/1.1\\", host: \\"localhost:8080\\", referrer: \\"http://localhost:8080/\\"",
],
},
]
`);
});
});
describe('in pre-ECS format', () => {
test('Nginx Access', () => {
const flattenedDocument = {
'nginx.access': true,
'nginx.access.remote_ip': '192.168.1.42',
'nginx.access.user_name': 'admin',
'nginx.access.method': 'GET',
'nginx.access.url': '/faq',
'nginx.access.http_version': '1.1',
'nginx.access.body_sent.bytes': 1024,
'nginx.access.response_code': 200,
'nginx.access': [true],
'nginx.access.remote_ip': ['192.168.1.42'],
'nginx.access.user_name': ['admin'],
'nginx.access.method': ['GET'],
'nginx.access.url': ['/faq'],
'nginx.access.http_version': ['1.1'],
'nginx.access.body_sent.bytes': [1024],
'nginx.access.response_code': [200],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[nginx][access] ",
},
Object {
"field": "nginx.access.remote_ip",
"highlights": Array [],
"value": "192.168.1.42",
},
Object {
"constant": " ",
},
Object {
"field": "nginx.access.user_name",
"highlights": Array [],
"value": "admin",
},
Object {
"constant": " \\"",
},
Object {
"field": "nginx.access.method",
"highlights": Array [],
"value": "GET",
},
Object {
"constant": " ",
},
Object {
"field": "nginx.access.url",
"highlights": Array [],
"value": "/faq",
},
Object {
"constant": " HTTP/",
},
Object {
"field": "nginx.access.http_version",
"highlights": Array [],
"value": "1.1",
},
Object {
"constant": "\\" ",
},
Object {
"field": "nginx.access.response_code",
"highlights": Array [],
"value": "200",
},
Object {
"constant": " ",
},
Object {
"field": "nginx.access.body_sent.bytes",
"highlights": Array [],
"value": "1024",
},
]
`);
Array [
Object {
"constant": "[nginx][access] ",
},
Object {
"field": "nginx.access.remote_ip",
"highlights": Array [],
"value": Array [
"192.168.1.42",
],
},
Object {
"constant": " ",
},
Object {
"field": "nginx.access.user_name",
"highlights": Array [],
"value": Array [
"admin",
],
},
Object {
"constant": " \\"",
},
Object {
"field": "nginx.access.method",
"highlights": Array [],
"value": Array [
"GET",
],
},
Object {
"constant": " ",
},
Object {
"field": "nginx.access.url",
"highlights": Array [],
"value": Array [
"/faq",
],
},
Object {
"constant": " HTTP/",
},
Object {
"field": "nginx.access.http_version",
"highlights": Array [],
"value": Array [
"1.1",
],
},
Object {
"constant": "\\" ",
},
Object {
"field": "nginx.access.response_code",
"highlights": Array [],
"value": Array [
200,
],
},
Object {
"constant": " ",
},
Object {
"field": "nginx.access.body_sent.bytes",
"highlights": Array [],
"value": Array [
1024,
],
},
]
`);
});
test('Nginx Error', () => {
const flattenedDocument = {
'nginx.error.message':
'nginx.error.message': [
'connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: localhost, request: "GET /php-status?json= HTTP/1.1", upstream: "fastcgi://[::1]:9000", host: "localhost"',
'nginx.error.level': 'error',
],
'nginx.error.level': ['error'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[nginx]",
},
Object {
"constant": "[",
},
Object {
"field": "nginx.error.level",
"highlights": Array [],
"value": "error",
},
Object {
"constant": "] ",
},
Object {
"field": "nginx.error.message",
"highlights": Array [],
"value": "connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: localhost, request: \\"GET /php-status?json= HTTP/1.1\\", upstream: \\"fastcgi://[::1]:9000\\", host: \\"localhost\\"",
},
]
`);
Array [
Object {
"constant": "[nginx]",
},
Object {
"constant": "[",
},
Object {
"field": "nginx.error.level",
"highlights": Array [],
"value": Array [
"error",
],
},
Object {
"constant": "] ",
},
Object {
"field": "nginx.error.message",
"highlights": Array [],
"value": Array [
"connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: localhost, request: \\"GET /php-status?json= HTTP/1.1\\", upstream: \\"fastcgi://[::1]:9000\\", host: \\"localhost\\"",
],
},
]
`);
});
});
});

View file

@ -13,65 +13,139 @@ describe('Filebeat Rules', () => {
describe('in pre-ECS format', () => {
test('osquery result log', () => {
const flattenedDocument = {
'@timestamp': '2017-12-28T14:40:08.000Z',
'event.dataset': 'osquery.result',
'fileset.module': 'osquery',
'fileset.name': 'result',
'input.type': 'log',
offset: 0,
'osquery.result.action': 'removed',
'osquery.result.calendar_time': 'Thu Dec 28 14:40:08 2017 UTC',
'osquery.result.columns': {
blocks: '122061322',
blocks_available: '75966945',
blocks_free: '121274885',
blocks_size: '4096',
device: '/dev/disk1s4',
device_alias: '/dev/disk1s4',
flags: '345018372',
inodes: '9223372036854775807',
inodes_free: '9223372036854775804',
path: '/private/var/vm',
type: 'apfs',
},
'osquery.result.counter': '1',
'osquery.result.decorations.host_uuid': '4AB2906D-5516-5794-AF54-86D1D7F533F3',
'osquery.result.decorations.username': 'tsg',
'osquery.result.epoch': '0',
'osquery.result.host_identifier': '192-168-0-4.rdsnet.ro',
'osquery.result.name': 'pack_it-compliance_mounts',
'osquery.result.unix_time': '1514472008',
'prospector.type': 'log',
'@timestamp': ['2017-12-28T14:40:08.000Z'],
'event.dataset': ['osquery.result'],
'fileset.module': ['osquery'],
'fileset.name': ['result'],
'input.type': ['log'],
offset: [0],
'osquery.result.action': ['removed'],
'osquery.result.calendar_time': ['Thu Dec 28 14:40:08 2017 UTC'],
'osquery.result.columns.blocks': ['122061322'],
'osquery.result.columns.blocks_available': ['75966945'],
'osquery.result.columns.blocks_free': ['121274885'],
'osquery.result.columns.blocks_size': ['4096'],
'osquery.result.columns.device': ['/dev/disk1s4'],
'osquery.result.columns.device_alias': ['/dev/disk1s4'],
'osquery.result.columns.flags': ['345018372'],
'osquery.result.columns.inodes': ['9223372036854775807'],
'osquery.result.columns.inodes_free': ['9223372036854775804'],
'osquery.result.columns.path': ['/private/var/vm'],
'osquery.result.columns.type': ['apfs'],
'osquery.result.counter': ['1'],
'osquery.result.decorations.host_uuid': ['4AB2906D-5516-5794-AF54-86D1D7F533F3'],
'osquery.result.decorations.username': ['tsg'],
'osquery.result.epoch': ['0'],
'osquery.result.host_identifier': ['192-168-0-4.rdsnet.ro'],
'osquery.result.name': ['pack_it-compliance_mounts'],
'osquery.result.unix_time': ['1514472008'],
'prospector.type': ['log'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[Osquery][",
},
Object {
"field": "osquery.result.action",
"highlights": Array [],
"value": "removed",
},
Object {
"constant": "] ",
},
Object {
"field": "osquery.result.host_identifier",
"highlights": Array [],
"value": "192-168-0-4.rdsnet.ro",
},
Object {
"constant": " ",
},
Object {
"field": "osquery.result.columns",
"highlights": Array [],
"value": "{\\"blocks\\":\\"122061322\\",\\"blocks_available\\":\\"75966945\\",\\"blocks_free\\":\\"121274885\\",\\"blocks_size\\":\\"4096\\",\\"device\\":\\"/dev/disk1s4\\",\\"device_alias\\":\\"/dev/disk1s4\\",\\"flags\\":\\"345018372\\",\\"inodes\\":\\"9223372036854775807\\",\\"inodes_free\\":\\"9223372036854775804\\",\\"path\\":\\"/private/var/vm\\",\\"type\\":\\"apfs\\"}",
},
]
`);
Array [
Object {
"constant": "[Osquery][",
},
Object {
"field": "osquery.result.action",
"highlights": Array [],
"value": Array [
"removed",
],
},
Object {
"constant": "] ",
},
Object {
"field": "osquery.result.host_identifier",
"highlights": Array [],
"value": Array [
"192-168-0-4.rdsnet.ro",
],
},
Object {
"constant": " ",
},
Object {
"field": "osquery.result.columns.blocks",
"highlights": Array [],
"value": Array [
"122061322",
],
},
Object {
"field": "osquery.result.columns.blocks_available",
"highlights": Array [],
"value": Array [
"75966945",
],
},
Object {
"field": "osquery.result.columns.blocks_free",
"highlights": Array [],
"value": Array [
"121274885",
],
},
Object {
"field": "osquery.result.columns.blocks_size",
"highlights": Array [],
"value": Array [
"4096",
],
},
Object {
"field": "osquery.result.columns.device",
"highlights": Array [],
"value": Array [
"/dev/disk1s4",
],
},
Object {
"field": "osquery.result.columns.device_alias",
"highlights": Array [],
"value": Array [
"/dev/disk1s4",
],
},
Object {
"field": "osquery.result.columns.flags",
"highlights": Array [],
"value": Array [
"345018372",
],
},
Object {
"field": "osquery.result.columns.inodes",
"highlights": Array [],
"value": Array [
"9223372036854775807",
],
},
Object {
"field": "osquery.result.columns.inodes_free",
"highlights": Array [],
"value": Array [
"9223372036854775804",
],
},
Object {
"field": "osquery.result.columns.path",
"highlights": Array [],
"value": Array [
"/private/var/vm",
],
},
Object {
"field": "osquery.result.columns.type",
"highlights": Array [],
"value": Array [
"apfs",
],
},
]
`);
});
});
});

View file

@ -27,7 +27,7 @@ export const filebeatOsqueryRules = [
constant: ' ',
},
{
field: 'osquery.result.columns',
fieldsPrefix: 'osquery.result.columns',
},
],
},

View file

@ -13,112 +13,129 @@ describe('Filebeat Rules', () => {
describe('in pre-ECS format', () => {
test('traefik access log', () => {
const flattenedDocument = {
'@timestamp': '2017-10-02T20:22:08.000Z',
'event.dataset': 'traefik.access',
'fileset.module': 'traefik',
'fileset.name': 'access',
'input.type': 'log',
offset: 280,
'prospector.type': 'log',
'traefik.access.backend_url': 'http://172.19.0.3:5601',
'traefik.access.body_sent.bytes': 0,
'traefik.access.duration': 3,
'traefik.access.frontend_name': 'Host-host1',
'traefik.access.geoip.city_name': 'Berlin',
'traefik.access.geoip.continent_name': 'Europe',
'traefik.access.geoip.country_iso_code': 'DE',
'traefik.access.geoip.location.lat': 52.4908,
'traefik.access.geoip.location.lon': 13.3275,
'traefik.access.geoip.region_iso_code': 'DE-BE',
'traefik.access.geoip.region_name': 'Land Berlin',
'traefik.access.http_version': '1.1',
'traefik.access.method': 'GET',
'traefik.access.referrer': 'http://example.com/login',
'traefik.access.remote_ip': '85.181.35.98',
'traefik.access.request_count': 271,
'traefik.access.response_code': '304',
'traefik.access.url': '/ui/favicons/favicon.ico',
'traefik.access.user_agent.device': 'Other',
'traefik.access.user_agent.major': '61',
'traefik.access.user_agent.minor': '0',
'traefik.access.user_agent.name': 'Chrome',
'traefik.access.user_agent.original':
'@timestamp': ['2017-10-02T20:22:08.000Z'],
'event.dataset': ['traefik.access'],
'fileset.module': ['traefik'],
'fileset.name': ['access'],
'input.type': ['log'],
offset: [280],
'prospector.type': ['log'],
'traefik.access.backend_url': ['http://172.19.0.3:5601'],
'traefik.access.body_sent.bytes': [0],
'traefik.access.duration': [3],
'traefik.access.frontend_name': ['Host-host1'],
'traefik.access.geoip.city_name': ['Berlin'],
'traefik.access.geoip.continent_name': ['Europe'],
'traefik.access.geoip.country_iso_code': ['DE'],
'traefik.access.geoip.location.lat': [52.4908],
'traefik.access.geoip.location.lon': [13.3275],
'traefik.access.geoip.region_iso_code': ['DE-BE'],
'traefik.access.geoip.region_name': ['Land Berlin'],
'traefik.access.http_version': ['1.1'],
'traefik.access.method': ['GET'],
'traefik.access.referrer': ['http://example.com/login'],
'traefik.access.remote_ip': ['85.181.35.98'],
'traefik.access.request_count': [271],
'traefik.access.response_code': ['304'],
'traefik.access.url': ['/ui/favicons/favicon.ico'],
'traefik.access.user_agent.device': ['Other'],
'traefik.access.user_agent.major': ['61'],
'traefik.access.user_agent.minor': ['0'],
'traefik.access.user_agent.name': ['Chrome'],
'traefik.access.user_agent.original': [
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
'traefik.access.user_agent.os': 'Linux',
'traefik.access.user_agent.os_name': 'Linux',
'traefik.access.user_agent.patch': '3163',
'traefik.access.user_identifier': '-',
'traefik.access.user_name': '-',
],
'traefik.access.user_agent.os': ['Linux'],
'traefik.access.user_agent.os_name': ['Linux'],
'traefik.access.user_agent.patch': ['3163'],
'traefik.access.user_identifier': ['-'],
'traefik.access.user_name': ['-'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[traefik][access] ",
},
Object {
"field": "traefik.access.remote_ip",
"highlights": Array [],
"value": "85.181.35.98",
},
Object {
"constant": " ",
},
Object {
"field": "traefik.access.frontend_name",
"highlights": Array [],
"value": "Host-host1",
},
Object {
"constant": " -> ",
},
Object {
"field": "traefik.access.backend_url",
"highlights": Array [],
"value": "http://172.19.0.3:5601",
},
Object {
"constant": " \\"",
},
Object {
"field": "traefik.access.method",
"highlights": Array [],
"value": "GET",
},
Object {
"constant": " ",
},
Object {
"field": "traefik.access.url",
"highlights": Array [],
"value": "/ui/favicons/favicon.ico",
},
Object {
"constant": " HTTP/",
},
Object {
"field": "traefik.access.http_version",
"highlights": Array [],
"value": "1.1",
},
Object {
"constant": "\\" ",
},
Object {
"field": "traefik.access.response_code",
"highlights": Array [],
"value": "304",
},
Object {
"constant": " ",
},
Object {
"field": "traefik.access.body_sent.bytes",
"highlights": Array [],
"value": "0",
},
]
`);
Array [
Object {
"constant": "[traefik][access] ",
},
Object {
"field": "traefik.access.remote_ip",
"highlights": Array [],
"value": Array [
"85.181.35.98",
],
},
Object {
"constant": " ",
},
Object {
"field": "traefik.access.frontend_name",
"highlights": Array [],
"value": Array [
"Host-host1",
],
},
Object {
"constant": " -> ",
},
Object {
"field": "traefik.access.backend_url",
"highlights": Array [],
"value": Array [
"http://172.19.0.3:5601",
],
},
Object {
"constant": " \\"",
},
Object {
"field": "traefik.access.method",
"highlights": Array [],
"value": Array [
"GET",
],
},
Object {
"constant": " ",
},
Object {
"field": "traefik.access.url",
"highlights": Array [],
"value": Array [
"/ui/favicons/favicon.ico",
],
},
Object {
"constant": " HTTP/",
},
Object {
"field": "traefik.access.http_version",
"highlights": Array [],
"value": Array [
"1.1",
],
},
Object {
"constant": "\\" ",
},
Object {
"field": "traefik.access.response_code",
"highlights": Array [],
"value": Array [
"304",
],
},
Object {
"constant": " ",
},
Object {
"field": "traefik.access.body_sent.bytes",
"highlights": Array [],
"value": Array [
0,
],
},
]
`);
});
});
});

View file

@ -15,154 +15,174 @@ describe('Generic Rules', () => {
describe('configurable message rules', () => {
test('includes the event.dataset and log.level if present', () => {
const flattenedDocument = {
'@timestamp': '2016-12-26T16:22:13.000Z',
'event.dataset': 'generic.test',
'log.level': 'TEST_LEVEL',
first_generic_message: 'TEST_MESSAGE',
'@timestamp': ['2016-12-26T16:22:13.000Z'],
'event.dataset': ['generic.test'],
'log.level': ['TEST_LEVEL'],
first_generic_message: ['TEST_MESSAGE'],
};
const highlights = {
first_generic_message: ['TEST'],
};
expect(format(flattenedDocument, highlights)).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": "generic.test",
},
Object {
"constant": "][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": "TEST_LEVEL",
},
Object {
"constant": "] ",
},
Object {
"field": "first_generic_message",
"highlights": Array [
"TEST",
],
"value": "TEST_MESSAGE",
},
]
`);
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": Array [
"generic.test",
],
},
Object {
"constant": "][",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": Array [
"TEST_LEVEL",
],
},
Object {
"constant": "] ",
},
Object {
"field": "first_generic_message",
"highlights": Array [
"TEST",
],
"value": Array [
"TEST_MESSAGE",
],
},
]
`);
});
test('includes the log.level if present', () => {
const flattenedDocument = {
'@timestamp': '2016-12-26T16:22:13.000Z',
'log.level': 'TEST_LEVEL',
first_generic_message: 'TEST_MESSAGE',
'@timestamp': ['2016-12-26T16:22:13.000Z'],
'log.level': ['TEST_LEVEL'],
first_generic_message: ['TEST_MESSAGE'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": "TEST_LEVEL",
},
Object {
"constant": "] ",
},
Object {
"field": "first_generic_message",
"highlights": Array [],
"value": "TEST_MESSAGE",
},
]
`);
Array [
Object {
"constant": "[",
},
Object {
"field": "log.level",
"highlights": Array [],
"value": Array [
"TEST_LEVEL",
],
},
Object {
"constant": "] ",
},
Object {
"field": "first_generic_message",
"highlights": Array [],
"value": Array [
"TEST_MESSAGE",
],
},
]
`);
});
test('includes the message', () => {
const firstFlattenedDocument = {
'@timestamp': '2016-12-26T16:22:13.000Z',
first_generic_message: 'FIRST_TEST_MESSAGE',
'@timestamp': ['2016-12-26T16:22:13.000Z'],
first_generic_message: ['FIRST_TEST_MESSAGE'],
};
expect(format(firstFlattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"field": "first_generic_message",
"highlights": Array [],
"value": "FIRST_TEST_MESSAGE",
},
]
`);
Array [
Object {
"field": "first_generic_message",
"highlights": Array [],
"value": Array [
"FIRST_TEST_MESSAGE",
],
},
]
`);
const secondFlattenedDocument = {
'@timestamp': '2016-12-26T16:22:13.000Z',
second_generic_message: 'SECOND_TEST_MESSAGE',
'@timestamp': ['2016-12-26T16:22:13.000Z'],
second_generic_message: ['SECOND_TEST_MESSAGE'],
};
expect(format(secondFlattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"field": "second_generic_message",
"highlights": Array [],
"value": "SECOND_TEST_MESSAGE",
},
]
`);
Array [
Object {
"field": "second_generic_message",
"highlights": Array [],
"value": Array [
"SECOND_TEST_MESSAGE",
],
},
]
`);
});
});
describe('log.original fallback', () => {
test('includes the event.dataset if present', () => {
const flattenedDocument = {
'@timestamp': '2016-12-26T16:22:13.000Z',
'event.dataset': 'generic.test',
'log.original': 'TEST_MESSAGE',
'@timestamp': ['2016-12-26T16:22:13.000Z'],
'event.dataset': ['generic.test'],
'log.original': ['TEST_MESSAGE'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": "generic.test",
},
Object {
"constant": "] ",
},
Object {
"field": "log.original",
"highlights": Array [],
"value": "TEST_MESSAGE",
},
]
`);
Array [
Object {
"constant": "[",
},
Object {
"field": "event.dataset",
"highlights": Array [],
"value": Array [
"generic.test",
],
},
Object {
"constant": "] ",
},
Object {
"field": "log.original",
"highlights": Array [],
"value": Array [
"TEST_MESSAGE",
],
},
]
`);
});
test('includes the original message', () => {
const flattenedDocument = {
'@timestamp': '2016-12-26T16:22:13.000Z',
'log.original': 'TEST_MESSAGE',
'@timestamp': ['2016-12-26T16:22:13.000Z'],
'log.original': ['TEST_MESSAGE'],
};
expect(format(flattenedDocument, {})).toMatchInlineSnapshot(`
Array [
Object {
"field": "log.original",
"highlights": Array [],
"value": "TEST_MESSAGE",
},
]
`);
Array [
Object {
"field": "log.original",
"highlights": Array [],
"value": Array [
"TEST_MESSAGE",
],
},
]
`);
});
});
});

View file

@ -10,3 +10,10 @@ export const labelField = (label: string, field: string) => [
{ constant: '=' },
{ field },
];
export const labelFieldsPrefix = (label: string, fieldsPrefix: string) => [
{ constant: ' ' },
{ constant: label },
{ constant: '=' },
{ fieldsPrefix },
];

View file

@ -4,66 +4,62 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { convertDocumentSourceToLogItemFields } from './convert_document_source_to_log_item_fields';
import { convertESFieldsToLogItemFields } from './convert_document_source_to_log_item_fields';
describe('convertDocumentSourceToLogItemFields', () => {
test('should convert document', () => {
const doc = {
agent: {
hostname: 'demo-stack-client-01',
id: '7adef8b6-2ab7-45cd-a0d5-b3baad735f1b',
type: 'filebeat',
ephemeral_id: 'a0c8164b-3564-4e32-b0bf-f4db5a7ae566',
version: '7.0.0',
},
describe('convertESFieldsToLogItemFields', () => {
test('Converts the fields collection to LogItemFields', () => {
const esFields = {
'agent.hostname': ['demo-stack-client-01'],
'agent.id': ['7adef8b6-2ab7-45cd-a0d5-b3baad735f1b'],
'agent.type': ['filebeat'],
'agent.ephemeral_id': ['a0c8164b-3564-4e32-b0bf-f4db5a7ae566'],
'agent.version': ['7.0.0'],
tags: ['prod', 'web'],
metadata: [
{ key: 'env', value: 'prod' },
{ key: 'stack', value: 'web' },
],
host: {
hostname: 'packer-virtualbox-iso-1546820004',
name: 'demo-stack-client-01',
},
'host.hostname': ['packer-virtualbox-iso-1546820004'],
'host.name': ['demo-stack-client-01'],
};
const fields = convertDocumentSourceToLogItemFields(doc);
const fields = convertESFieldsToLogItemFields(esFields);
expect(fields).toEqual([
{
field: 'agent.hostname',
value: 'demo-stack-client-01',
value: ['demo-stack-client-01'],
},
{
field: 'agent.id',
value: '7adef8b6-2ab7-45cd-a0d5-b3baad735f1b',
value: ['7adef8b6-2ab7-45cd-a0d5-b3baad735f1b'],
},
{
field: 'agent.type',
value: 'filebeat',
value: ['filebeat'],
},
{
field: 'agent.ephemeral_id',
value: 'a0c8164b-3564-4e32-b0bf-f4db5a7ae566',
value: ['a0c8164b-3564-4e32-b0bf-f4db5a7ae566'],
},
{
field: 'agent.version',
value: '7.0.0',
value: ['7.0.0'],
},
{
field: 'tags',
value: '["prod","web"]',
value: ['prod', 'web'],
},
{
field: 'metadata',
value: '[{"key":"env","value":"prod"},{"key":"stack","value":"web"}]',
value: ['{"key":"env","value":"prod"}', '{"key":"stack","value":"web"}'],
},
{
field: 'host.hostname',
value: 'packer-virtualbox-iso-1546820004',
value: ['packer-virtualbox-iso-1546820004'],
},
{
field: 'host.name',
value: 'demo-stack-client-01',
value: ['demo-stack-client-01'],
},
]);
});

View file

@ -5,39 +5,21 @@
*/
import stringify from 'json-stable-stringify';
import { isArray, isPlainObject } from 'lodash';
import { JsonObject } from '../../../../common/typed_json';
import { LogEntriesItemField } from '../../../../common/http_api';
import { JsonArray } from '../../../../common/typed_json';
const isJsonObject = (subject: any): subject is JsonObject => {
return isPlainObject(subject);
};
const serializeValue = (value: any): string => {
if (isArray(value) || isPlainObject(value)) {
return stringify(value);
}
return `${value}`;
};
export const convertESFieldsToLogItemFields = (fields: {
[field: string]: [value: unknown];
}): LogEntriesItemField[] => {
return Object.keys(fields).map((field) => ({ field, value: serializeValue(fields[field][0]) }));
};
export const convertDocumentSourceToLogItemFields = (
source: JsonObject,
path: string[] = [],
fields: LogEntriesItemField[] = []
): LogEntriesItemField[] => {
return Object.keys(source).reduce((acc, key) => {
const value = source[key];
const nextPath = [...path, key];
if (isJsonObject(value)) {
return convertDocumentSourceToLogItemFields(value, nextPath, acc);
const serializeValue = (value: JsonArray): string[] => {
return value.map((v) => {
if (typeof v === 'object' && v != null) {
return stringify(v);
} else {
return `${v}`;
}
const field = { field: nextPath.join('.'), value: serializeValue(value) };
return [...acc, field];
}, fields);
});
};
export const convertESFieldsToLogItemFields = (fields: {
[field: string]: JsonArray;
}): LogEntriesItemField[] => {
return Object.keys(fields).map((field) => ({ field, value: serializeValue(fields[field]) }));
};

View file

@ -7,7 +7,7 @@
import { sortBy } from 'lodash';
import { RequestHandlerContext } from 'src/core/server';
import { JsonObject } from '../../../../common/typed_json';
import { JsonArray, JsonObject } from '../../../../common/typed_json';
import {
LogEntriesSummaryBucket,
LogEntriesSummaryHighlightsBucket,
@ -163,8 +163,8 @@ export class InfraLogEntriesDomain {
return {
columnId: column.fieldColumn.id,
field: column.fieldColumn.field,
value: doc.fields[column.fieldColumn.field],
highlights: doc.highlights[column.fieldColumn.field] || [],
value: doc.fields[column.fieldColumn.field] ?? [],
highlights: doc.highlights[column.fieldColumn.field] ?? [],
};
}
}
@ -252,8 +252,8 @@ export class InfraLogEntriesDomain {
): Promise<LogEntriesItem> {
const document = await this.adapter.getLogItem(requestContext, id, sourceConfiguration);
const defaultFields = [
{ field: '_index', value: document._index },
{ field: '_id', value: document._id },
{ field: '_index', value: [document._index] },
{ field: '_id', value: [document._id] },
];
return {
@ -310,10 +310,10 @@ export class InfraLogEntriesDomain {
}
}
interface LogItemHit {
export interface LogItemHit {
_index: string;
_id: string;
fields: { [field: string]: [value: unknown] };
fields: { [field: string]: [value: JsonArray] };
sort: [number, number];
}
@ -400,9 +400,9 @@ const createHighlightQueryDsl = (phrase: string, fields: string[]) => ({
const getContextFromDoc = (doc: LogEntryDocument): LogEntry['context'] => {
// Get all context fields, then test for the presence and type of the ones that go together
const containerId = doc.fields['container.id'];
const hostName = doc.fields['host.name'];
const logFilePath = doc.fields['log.file.path'];
const containerId = doc.fields['container.id']?.[0];
const hostName = doc.fields['host.name']?.[0];
const logFilePath = doc.fields['log.file.path']?.[0];
if (typeof containerId === 'string') {
return { 'container.id': containerId };

View file

@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import stringify from 'json-stable-stringify';
import { InfraLogMessageSegment } from '../../../graphql/types';
import { LogMessagePart } from '../../../../common/http_api/log_entries';
import { JsonArray, JsonValue } from '../../../../common/typed_json';
import {
LogMessageFormattingCondition,
LogMessageFormattingFieldValueConditionValue,
LogMessageFormattingInstruction,
LogMessageFormattingRule,
} from './rule_types';
@ -30,7 +30,7 @@ export function compileFormattingRules(
)
)
),
format(fields, highlights): InfraLogMessageSegment[] {
format(fields, highlights): LogMessagePart[] {
for (const compiledRule of compiledRules) {
if (compiledRule.fulfillsCondition(fields)) {
return compiledRule.format(fields, highlights);
@ -59,16 +59,34 @@ const compileRule = (rule: LogMessageFormattingRule): CompiledLogMessageFormatti
const compileCondition = (
condition: LogMessageFormattingCondition
): CompiledLogMessageFormattingCondition =>
[compileExistsCondition, compileFieldValueCondition].reduce(
(compiledCondition, compile) => compile(condition) || compiledCondition,
catchAllCondition
);
[
compileAllCondition,
compileExistsCondition,
compileExistsPrefixCondition,
compileFieldValueCondition,
].reduce((compiledCondition, compile) => compile(condition) || compiledCondition, falseCondition);
const catchAllCondition: CompiledLogMessageFormattingCondition = {
const falseCondition: CompiledLogMessageFormattingCondition = {
conditionFields: [] as string[],
fulfillsCondition: () => false,
};
const compileAllCondition = (
condition: LogMessageFormattingCondition
): CompiledLogMessageFormattingCondition | null => {
if (!('all' in condition)) {
return null;
}
const compiledConditions = condition.all.map(compileCondition);
return {
conditionFields: compiledConditions.flatMap(({ conditionFields }) => conditionFields),
fulfillsCondition: (fields: Fields) =>
compiledConditions.every(({ fulfillsCondition }) => fulfillsCondition(fields)),
};
};
const compileExistsCondition = (condition: LogMessageFormattingCondition) =>
'exists' in condition
? {
@ -78,13 +96,24 @@ const compileExistsCondition = (condition: LogMessageFormattingCondition) =>
}
: null;
const compileExistsPrefixCondition = (condition: LogMessageFormattingCondition) =>
'existsPrefix' in condition
? {
conditionFields: condition.existsPrefix.map((prefix) => `${prefix}.*`),
fulfillsCondition: (fields: Fields) =>
condition.existsPrefix.every((fieldNamePrefix) =>
Object.keys(fields).some((field) => field.startsWith(`${fieldNamePrefix}.`))
),
}
: null;
const compileFieldValueCondition = (condition: LogMessageFormattingCondition) =>
'values' in condition
? {
conditionFields: Object.keys(condition.values),
fulfillsCondition: (fields: Fields) =>
Object.entries(condition.values).every(
([fieldName, expectedValue]) => fields[fieldName] === expectedValue
Object.entries(condition.values).every(([fieldName, expectedValue]) =>
equalsOrContains(fields[fieldName] ?? [], expectedValue)
),
}
: null;
@ -116,7 +145,11 @@ const compileFormattingInstructions = (
const compileFormattingInstruction = (
formattingInstruction: LogMessageFormattingInstruction
): CompiledLogMessageFormattingInstruction =>
[compileFieldReferenceFormattingInstruction, compileConstantFormattingInstruction].reduce(
[
compileFieldReferenceFormattingInstruction,
compileFieldsPrefixReferenceFormattingInstruction,
compileConstantFormattingInstruction,
].reduce(
(compiledFormattingInstruction, compile) =>
compile(formattingInstruction) || compiledFormattingInstruction,
catchAllFormattingInstruction
@ -138,19 +171,44 @@ const compileFieldReferenceFormattingInstruction = (
? {
formattingFields: [formattingInstruction.field],
format: (fields, highlights) => {
const value = fields[formattingInstruction.field];
const highlightedValues = highlights[formattingInstruction.field];
const value = fields[formattingInstruction.field] ?? [];
const highlightedValues = highlights[formattingInstruction.field] ?? [];
return [
{
field: formattingInstruction.field,
value: typeof value === 'object' ? stringify(value) : `${value}`,
highlights: highlightedValues || [],
value,
highlights: highlightedValues,
},
];
},
}
: null;
const compileFieldsPrefixReferenceFormattingInstruction = (
formattingInstruction: LogMessageFormattingInstruction
): CompiledLogMessageFormattingInstruction | null =>
'fieldsPrefix' in formattingInstruction
? {
formattingFields: [`${formattingInstruction.fieldsPrefix}.*`],
format: (fields, highlights) => {
const matchingFields = Object.keys(fields).filter((field) =>
field.startsWith(`${formattingInstruction.fieldsPrefix}.`)
);
return matchingFields.flatMap((field) => {
const value = fields[field] ?? [];
const highlightedValues = highlights[field] ?? [];
return [
{
field,
value,
highlights: highlightedValues,
},
];
});
},
}
: null;
const compileConstantFormattingInstruction = (
formattingInstruction: LogMessageFormattingInstruction
): CompiledLogMessageFormattingInstruction | null =>
@ -165,8 +223,21 @@ const compileConstantFormattingInstruction = (
}
: null;
const equalsOrContains = (
operand: JsonValue,
value: LogMessageFormattingFieldValueConditionValue
): boolean => {
if (Array.isArray(operand)) {
return operand.includes(value);
} else if (typeof operand === 'object' && operand !== null) {
return Object.values(operand).includes(value);
} else {
return operand === value;
}
};
export interface Fields {
[fieldName: string]: string | number | object | boolean | null;
[fieldName: string]: JsonArray;
}
export interface Highlights {
@ -176,7 +247,7 @@ export interface Highlights {
export interface CompiledLogMessageFormattingRule {
requiredFields: string[];
fulfillsCondition(fields: Fields): boolean;
format(fields: Fields, highlights: Highlights): InfraLogMessageSegment[];
format(fields: Fields, highlights: Highlights): LogMessagePart[];
}
export interface CompiledLogMessageFormattingCondition {
@ -186,5 +257,5 @@ export interface CompiledLogMessageFormattingCondition {
export interface CompiledLogMessageFormattingInstruction {
formattingFields: string[];
format(fields: Fields, highlights: Highlights): InfraLogMessageSegment[];
format(fields: Fields, highlights: Highlights): LogMessagePart[];
}

View file

@ -4,33 +4,52 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { JsonValue } from '../../../../common/typed_json';
export interface LogMessageFormattingRule {
when: LogMessageFormattingCondition;
format: LogMessageFormattingInstruction[];
}
export type LogMessageFormattingCondition =
| LogMessageFormattingAllCondition
| LogMessageFormattingExistsCondition
| LogMessageFormattingExistsPrefixCondition
| LogMessageFormattingFieldValueCondition;
export interface LogMessageFormattingAllCondition {
all: LogMessageFormattingCondition[];
}
export interface LogMessageFormattingExistsCondition {
exists: string[];
}
export interface LogMessageFormattingExistsPrefixCondition {
existsPrefix: string[];
}
export interface LogMessageFormattingFieldValueCondition {
values: {
[fieldName: string]: string | number | boolean | null;
[fieldName: string]: LogMessageFormattingFieldValueConditionValue;
};
}
export type LogMessageFormattingFieldValueConditionValue = JsonValue;
export type LogMessageFormattingInstruction =
| LogMessageFormattingFieldReference
| LogMessageFormattingFieldsPrefixReference
| LogMessageFormattingConstant;
export interface LogMessageFormattingFieldReference {
field: string;
}
export interface LogMessageFormattingFieldsPrefixReference {
fieldsPrefix: string;
}
export interface LogMessageFormattingConstant {
constant: string;
}

View file

@ -44,107 +44,107 @@ export default function ({ getService }: FtrProviderContext) {
expect(logItem.fields).to.eql([
{
field: '@timestamp',
value: '2018-10-17T19:42:22.000Z',
value: ['2018-10-17T19:42:22.000Z'],
},
{
field: '_id',
value: 'yT2Mg2YBh-opCxJv8Vqj',
value: ['yT2Mg2YBh-opCxJv8Vqj'],
},
{
field: '_index',
value: 'filebeat-7.0.0-alpha1-2018.10.17',
value: ['filebeat-7.0.0-alpha1-2018.10.17'],
},
{
field: 'apache2.access.body_sent.bytes',
value: '1336',
value: ['1336'],
},
{
field: 'apache2.access.http_version',
value: '1.1',
value: ['1.1'],
},
{
field: 'apache2.access.method',
value: 'GET',
value: ['GET'],
},
{
field: 'apache2.access.referrer',
value: '-',
value: ['-'],
},
{
field: 'apache2.access.remote_ip',
value: '10.128.0.11',
value: ['10.128.0.11'],
},
{
field: 'apache2.access.response_code',
value: '200',
value: ['200'],
},
{
field: 'apache2.access.url',
value: '/a-fresh-start-will-put-you-on-your-way',
value: ['/a-fresh-start-will-put-you-on-your-way'],
},
{
field: 'apache2.access.user_agent.device',
value: 'Other',
value: ['Other'],
},
{
field: 'apache2.access.user_agent.name',
value: 'Other',
value: ['Other'],
},
{
field: 'apache2.access.user_agent.os',
value: 'Other',
value: ['Other'],
},
{
field: 'apache2.access.user_agent.os_name',
value: 'Other',
value: ['Other'],
},
{
field: 'apache2.access.user_name',
value: '-',
value: ['-'],
},
{
field: 'beat.hostname',
value: 'demo-stack-apache-01',
value: ['demo-stack-apache-01'],
},
{
field: 'beat.name',
value: 'demo-stack-apache-01',
value: ['demo-stack-apache-01'],
},
{
field: 'beat.version',
value: '7.0.0-alpha1',
value: ['7.0.0-alpha1'],
},
{
field: 'fileset.module',
value: 'apache2',
value: ['apache2'],
},
{
field: 'fileset.name',
value: 'access',
value: ['access'],
},
{
field: 'host.name',
value: 'demo-stack-apache-01',
value: ['demo-stack-apache-01'],
},
{
field: 'input.type',
value: 'log',
value: ['log'],
},
{
field: 'offset',
value: '5497614',
value: ['5497614'],
},
{
field: 'prospector.type',
value: 'log',
value: ['log'],
},
{
field: 'read_timestamp',
value: '2018-10-17T19:42:23.160Z',
value: ['2018-10-17T19:42:23.160Z'],
},
{
field: 'source',
value: '/var/log/apache2/access.log',
value: ['/var/log/apache2/access.log'],
},
]);
});