[core.logging] Ensure LogMeta is ECS-compliant. (#96350)

This commit is contained in:
Luke Elmers 2021-04-20 09:31:32 -06:00 committed by GitHub
parent 366691a9c8
commit 12b245c4e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
99 changed files with 2641 additions and 931 deletions

View file

@ -7,5 +7,5 @@
<b>Signature:</b>
```typescript
error: (msg: string, meta: LogMeta) => void;
error: <Meta extends LogMeta = LogMeta>(msg: string, meta: Meta) => void;
```

View file

@ -16,7 +16,7 @@ export interface SavedObjectsMigrationLogger
| Property | Type | Description |
| --- | --- | --- |
| [debug](./kibana-plugin-core-server.savedobjectsmigrationlogger.debug.md) | <code>(msg: string) =&gt; void</code> | |
| [error](./kibana-plugin-core-server.savedobjectsmigrationlogger.error.md) | <code>(msg: string, meta: LogMeta) =&gt; void</code> | |
| [error](./kibana-plugin-core-server.savedobjectsmigrationlogger.error.md) | <code>&lt;Meta extends LogMeta = LogMeta&gt;(msg: string, meta: Meta) =&gt; void</code> | |
| [info](./kibana-plugin-core-server.savedobjectsmigrationlogger.info.md) | <code>(msg: string) =&gt; void</code> | |
| [warn](./kibana-plugin-core-server.savedobjectsmigrationlogger.warn.md) | <code>(msg: string) =&gt; void</code> | |
| [warning](./kibana-plugin-core-server.savedobjectsmigrationlogger.warning.md) | <code>(msg: string) =&gt; void</code> | |

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-agent.html
*
* @internal
*/
export interface EcsAgent {
build?: { original: string };
ephemeral_id?: string;
id?: string;
name?: string;
type?: string;
version?: string;
}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-as.html
*
* @internal
*/
export interface EcsAutonomousSystem {
number?: number;
organization?: { name: string };
}

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-base.html
*
* @internal
*/
export interface EcsBase {
['@timestamp']: string;
labels?: Record<string, unknown>;
message?: string;
tags?: string[];
}

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsAutonomousSystem } from './autonomous_system';
import { EcsGeo } from './geo';
import { EcsNestedUser } from './user';
interface NestedFields {
as?: EcsAutonomousSystem;
geo?: EcsGeo;
user?: EcsNestedUser;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-client.html
*
* @internal
*/
export interface EcsClient extends NestedFields {
address?: string;
bytes?: number;
domain?: string;
ip?: string;
mac?: string;
nat?: { ip?: string; port?: number };
packets?: number;
port?: number;
registered_domain?: string;
subdomain?: string;
top_level_domain?: string;
}

View file

@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-cloud.html
*
* @internal
*/
export interface EcsCloud {
account?: { id?: string; name?: string };
availability_zone?: string;
instance?: { id?: string; name?: string };
machine?: { type: string };
project?: { id?: string; name?: string };
provider?: string;
region?: string;
service?: { name: string };
}

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-code_signature.html
*
* @internal
*/
export interface EcsCodeSignature {
exists?: boolean;
signing_id?: string;
status?: string;
subject_name?: string;
team_id?: string;
trusted?: boolean;
valid?: boolean;
}

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-container.html
*
* @internal
*/
export interface EcsContainer {
id?: string;
image?: { name?: string; tag?: string[] };
labels?: Record<string, unknown>;
name?: string;
runtime?: string;
}

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsAutonomousSystem } from './autonomous_system';
import { EcsGeo } from './geo';
import { EcsNestedUser } from './user';
interface NestedFields {
as?: EcsAutonomousSystem;
geo?: EcsGeo;
user?: EcsNestedUser;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-destination.html
*
* @internal
*/
export interface EcsDestination extends NestedFields {
address?: string;
bytes?: number;
domain?: string;
ip?: string;
mac?: string;
nat?: { ip?: string; port?: number };
packets?: number;
port?: number;
registered_domain?: string;
subdomain?: string;
top_level_domain?: string;
}

View file

@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsCodeSignature } from './code_signature';
import { EcsHash } from './hash';
import { EcsPe } from './pe';
interface NestedFields {
code_signature?: EcsCodeSignature;
hash?: EcsHash;
pe?: EcsPe;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-dll.html
*
* @internal
*/
export interface EcsDll extends NestedFields {
name?: string;
path?: string;
}

View file

@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-dns.html
*
* @internal
*/
export interface EcsDns {
answers?: Answer[];
header_flags?: string[];
id?: number;
op_code?: string;
question?: Question;
resolved_ip?: string[];
response_code?: string;
type?: string;
}
interface Answer {
data: string;
class?: string;
name?: string;
ttl?: number;
type?: string;
}
interface Question {
class?: string;
name?: string;
registered_domain?: string;
subdomain?: string;
top_level_domain?: string;
type?: string;
}

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-error.html
*
* @internal
*/
export interface EcsError {
code?: string;
id?: string;
message?: string;
stack_trace?: string;
type?: string;
}

View file

@ -0,0 +1,91 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-event.html
*
* @internal
*/
export interface EcsEvent {
action?: string;
category?: EcsEventCategory[];
code?: string;
created?: string;
dataset?: string;
duration?: number;
end?: string;
hash?: string;
id?: string;
ingested?: string;
kind?: EcsEventKind;
module?: string;
original?: string;
outcome?: EcsEventOutcome;
provider?: string;
reason?: string;
reference?: string;
risk_score?: number;
risk_score_norm?: number;
sequence?: number;
severity?: number;
start?: string;
timezone?: string;
type?: EcsEventType[];
url?: string;
}
/**
* @public
*/
export type EcsEventCategory =
| 'authentication'
| 'configuration'
| 'database'
| 'driver'
| 'file'
| 'host'
| 'iam'
| 'intrusion_detection'
| 'malware'
| 'network'
| 'package'
| 'process'
| 'registry'
| 'session'
| 'web';
/**
* @public
*/
export type EcsEventKind = 'alert' | 'event' | 'metric' | 'state' | 'pipeline_error' | 'signal';
/**
* @public
*/
export type EcsEventOutcome = 'failure' | 'success' | 'unknown';
/**
* @public
*/
export type EcsEventType =
| 'access'
| 'admin'
| 'allowed'
| 'change'
| 'connection'
| 'creation'
| 'deletion'
| 'denied'
| 'end'
| 'error'
| 'group'
| 'info'
| 'installation'
| 'protocol'
| 'start'
| 'user';

View file

@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsCodeSignature } from './code_signature';
import { EcsHash } from './hash';
import { EcsPe } from './pe';
import { EcsX509 } from './x509';
interface NestedFields {
code_signature?: EcsCodeSignature;
hash?: EcsHash;
pe?: EcsPe;
x509?: EcsX509;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-file.html
*
* @internal
*/
export interface EcsFile extends NestedFields {
accessed?: string;
attributes?: string[];
created?: string;
ctime?: string;
device?: string;
directory?: string;
drive_letter?: string;
extension?: string;
gid?: string;
group?: string;
inode?: string;
// Technically this is a known list, but it's massive, so we'll just accept a string for now :)
// https://www.iana.org/assignments/media-types/media-types.xhtml
mime_type?: string;
mode?: string;
mtime?: string;
name?: string;
owner?: string;
path?: string;
'path.text'?: string;
size?: number;
target_path?: string;
'target_path.text'?: string;
type?: string;
uid?: string;
}

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-geo.html
*
* @internal
*/
export interface EcsGeo {
city_name?: string;
continent_code?: string;
continent_name?: string;
country_iso_code?: string;
country_name?: string;
location?: GeoPoint;
name?: string;
postal_code?: string;
region_iso_code?: string;
region_name?: string;
timezone?: string;
}
interface GeoPoint {
lat: number;
lon: number;
}

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-group.html
*
* @internal
*/
export interface EcsGroup {
domain?: string;
id?: string;
name?: string;
}

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-hash.html
*
* @internal
*/
export interface EcsHash {
md5?: string;
sha1?: string;
sha256?: string;
sha512?: string;
ssdeep?: string;
}

View file

@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsGeo } from './geo';
import { EcsOs } from './os';
import { EcsNestedUser } from './user';
interface NestedFields {
geo?: EcsGeo;
os?: EcsOs;
/** @deprecated */
user?: EcsNestedUser;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-host.html
*
* @internal
*/
export interface EcsHost extends NestedFields {
architecture?: string;
cpu?: { usage: number };
disk?: Disk;
domain?: string;
hostname?: string;
id?: string;
ip?: string[];
mac?: string[];
name?: string;
network?: Network;
type?: string;
uptime?: number;
}
interface Disk {
read?: { bytes: number };
write?: { bytes: number };
}
interface Network {
egress?: { bytes?: number; packets?: number };
ingress?: { bytes?: number; packets?: number };
}

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-http.html
*
* @internal
*/
export interface EcsHttp {
request?: Request;
response?: Response;
version?: string;
}
interface Request {
body?: { bytes?: number; content?: string };
bytes?: number;
id?: string;
// We can't provide predefined values here because ECS requires preserving the
// original casing for anomaly detection use cases.
method?: string;
mime_type?: string;
referrer?: string;
}
interface Response {
body?: { bytes?: number; content?: string };
bytes?: number;
mime_type?: string;
status_code?: number;
}

View file

@ -0,0 +1,97 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsBase } from './base';
import { EcsAgent } from './agent';
import { EcsAutonomousSystem } from './autonomous_system';
import { EcsClient } from './client';
import { EcsCloud } from './cloud';
import { EcsContainer } from './container';
import { EcsDestination } from './destination';
import { EcsDns } from './dns';
import { EcsError } from './error';
import { EcsEvent } from './event';
import { EcsFile } from './file';
import { EcsGroup } from './group';
import { EcsHost } from './host';
import { EcsHttp } from './http';
import { EcsLog } from './log';
import { EcsNetwork } from './network';
import { EcsObserver } from './observer';
import { EcsOrganization } from './organization';
import { EcsPackage } from './package';
import { EcsProcess } from './process';
import { EcsRegistry } from './registry';
import { EcsRelated } from './related';
import { EcsRule } from './rule';
import { EcsServer } from './server';
import { EcsService } from './service';
import { EcsSource } from './source';
import { EcsThreat } from './threat';
import { EcsTls } from './tls';
import { EcsTracing } from './tracing';
import { EcsUrl } from './url';
import { EcsUser } from './user';
import { EcsUserAgent } from './user_agent';
import { EcsVulnerability } from './vulnerability';
export { EcsEventCategory, EcsEventKind, EcsEventOutcome, EcsEventType } from './event';
interface EcsField {
/**
* These typings were written as of ECS 1.9.0.
* Don't change this value without checking the rest
* of the types to conform to that ECS version.
*
* https://www.elastic.co/guide/en/ecs/1.9/index.html
*/
version: '1.9.0';
}
/**
* Represents the full ECS schema.
*
* @public
*/
export type Ecs = EcsBase &
EcsTracing & {
ecs: EcsField;
agent?: EcsAgent;
as?: EcsAutonomousSystem;
client?: EcsClient;
cloud?: EcsCloud;
container?: EcsContainer;
destination?: EcsDestination;
dns?: EcsDns;
error?: EcsError;
event?: EcsEvent;
file?: EcsFile;
group?: EcsGroup;
host?: EcsHost;
http?: EcsHttp;
log?: EcsLog;
network?: EcsNetwork;
observer?: EcsObserver;
organization?: EcsOrganization;
package?: EcsPackage;
process?: EcsProcess;
registry?: EcsRegistry;
related?: EcsRelated;
rule?: EcsRule;
server?: EcsServer;
service?: EcsService;
source?: EcsSource;
threat?: EcsThreat;
tls?: EcsTls;
url?: EcsUrl;
user?: EcsUser;
user_agent?: EcsUserAgent;
vulnerability?: EcsVulnerability;
};

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-interface.html
*
* @internal
*/
export interface EcsInterface {
alias?: string;
id?: string;
name?: string;
}

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-log.html
*
* @internal
*/
export interface EcsLog {
file?: { path: string };
level?: string;
logger?: string;
origin?: Origin;
original?: string;
syslog?: Syslog;
}
interface Origin {
file?: { line?: number; name?: string };
function?: string;
}
interface Syslog {
facility?: { code?: number; name?: string };
priority?: number;
severity?: { code?: number; name?: string };
}

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsVlan } from './vlan';
interface NestedFields {
inner?: { vlan?: EcsVlan };
vlan?: EcsVlan;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-network.html
*
* @internal
*/
export interface EcsNetwork extends NestedFields {
application?: string;
bytes?: number;
community_id?: string;
direction?: string;
forwarded_ip?: string;
iana_number?: string;
name?: string;
packets?: number;
protocol?: string;
transport?: string;
type?: string;
}

View file

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsGeo } from './geo';
import { EcsInterface } from './interface';
import { EcsOs } from './os';
import { EcsVlan } from './vlan';
interface NestedFields {
egress?: NestedEgressFields;
geo?: EcsGeo;
ingress?: NestedIngressFields;
os?: EcsOs;
}
interface NestedEgressFields {
interface?: EcsInterface;
vlan?: EcsVlan;
}
interface NestedIngressFields {
interface?: EcsInterface;
vlan?: EcsVlan;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-observer.html
*
* @internal
*/
export interface EcsObserver extends NestedFields {
egress?: Egress;
hostname?: string;
ingress?: Ingress;
ip?: string[];
mac?: string[];
name?: string;
product?: string;
serial_number?: string;
type?: string;
vendor?: string;
version?: string;
}
interface Egress extends NestedEgressFields {
zone?: string;
}
interface Ingress extends NestedIngressFields {
zone?: string;
}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-organization.html
*
* @internal
*/
export interface EcsOrganization {
id?: string;
name?: string;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-os.html
*
* @internal
*/
export interface EcsOs {
family?: string;
full?: string;
kernel?: string;
name?: string;
platform?: string;
type?: string;
version?: string;
}

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-package.html
*
* @internal
*/
export interface EcsPackage {
architecture?: string;
build_version?: string;
checksum?: string;
description?: string;
install_scope?: string;
installed?: string;
license?: string;
name?: string;
path?: string;
reference?: string;
size?: number;
type?: string;
version?: string;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-pe.html
*
* @internal
*/
export interface EcsPe {
architecture?: string;
company?: string;
description?: string;
file_version?: string;
imphash?: string;
original_file_name?: string;
product?: string;
}

View file

@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsCodeSignature } from './code_signature';
import { EcsHash } from './hash';
import { EcsPe } from './pe';
interface NestedFields {
code_signature?: EcsCodeSignature;
hash?: EcsHash;
parent?: EcsProcess;
pe?: EcsPe;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-process.html
*
* @internal
*/
export interface EcsProcess extends NestedFields {
args?: string[];
args_count?: number;
command_line?: string;
entity_id?: string;
executable?: string;
exit_code?: number;
name?: string;
pgid?: number;
pid?: number;
ppid?: number;
start?: string;
thread?: { id?: number; name?: string };
title?: string;
uptime?: number;
working_directory?: string;
}

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-registry.html
*
* @internal
*/
export interface EcsRegistry {
data?: Data;
hive?: string;
key?: string;
path?: string;
value?: string;
}
interface Data {
bytes?: string;
strings?: string[];
type?: string;
}

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-related.html
*
* @internal
*/
export interface EcsRelated {
hash?: string[];
hosts?: string[];
ip?: string[];
user?: string[];
}

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-rule.html
*
* @internal
*/
export interface EcsRule {
author?: string[];
category?: string;
description?: string;
id?: string;
license?: string;
name?: string;
reference?: string;
ruleset?: string;
uuid?: string;
version?: string;
}

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsAutonomousSystem } from './autonomous_system';
import { EcsGeo } from './geo';
import { EcsNestedUser } from './user';
interface NestedFields {
as?: EcsAutonomousSystem;
geo?: EcsGeo;
user?: EcsNestedUser;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-server.html
*
* @internal
*/
export interface EcsServer extends NestedFields {
address?: string;
bytes?: number;
domain?: string;
ip?: string;
mac?: string;
nat?: { ip?: string; port?: number };
packets?: number;
port?: number;
registered_domain?: string;
subdomain?: string;
top_level_domain?: string;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-service.html
*
* @internal
*/
export interface EcsService {
ephemeral_id?: string;
id?: string;
name?: string;
node?: { name: string };
state?: string;
type?: string;
version?: string;
}

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsAutonomousSystem } from './autonomous_system';
import { EcsGeo } from './geo';
import { EcsNestedUser } from './user';
interface NestedFields {
as?: EcsAutonomousSystem;
geo?: EcsGeo;
user?: EcsNestedUser;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-source.html
*
* @internal
*/
export interface EcsSource extends NestedFields {
address?: string;
bytes?: number;
domain?: string;
ip?: string;
mac?: string;
nat?: { ip?: string; port?: number };
packets?: number;
port?: number;
registered_domain?: string;
subdomain?: string;
top_level_domain?: string;
}

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-threat.html
*
* @internal
*/
export interface EcsThreat {
framework?: string;
tactic?: Tactic;
technique?: Technique;
}
interface Tactic {
id?: string[];
name?: string[];
reference?: string[];
}
interface Technique {
id?: string[];
name?: string[];
reference?: string[];
subtechnique?: Technique;
}

View file

@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsX509 } from './x509';
interface NestedClientFields {
x509?: EcsX509;
}
interface NestedServerFields {
x509?: EcsX509;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-tls.html
*
* @internal
*/
export interface EcsTls {
cipher?: string;
client?: Client;
curve?: string;
established?: boolean;
next_protocol?: string;
resumed?: boolean;
server?: Server;
version?: string;
version_protocol?: string;
}
interface Client extends NestedClientFields {
certificate?: string;
certificate_chain?: string[];
hash?: Hash;
issuer?: string;
ja3?: string;
not_after?: string;
not_before?: string;
server_name?: string;
subject?: string;
supported_ciphers?: string[];
}
interface Server extends NestedServerFields {
certificate?: string;
certificate_chain?: string[];
hash?: Hash;
issuer?: string;
ja3s?: string;
not_after?: string;
not_before?: string;
subject?: string;
}
interface Hash {
md5?: string;
sha1?: string;
sha256?: string;
}

View file

@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* Unlike other ECS field sets, tracing fields are not nested under the field
* set name (i.e. `trace.id` is valid, `tracing.trace.id` is not). So, like
* the base fields, we will need to do an intersection with these types at
* the root level.
*
* https://www.elastic.co/guide/en/ecs/1.9/ecs-tracing.html
*
* @internal
*/
export interface EcsTracing {
span?: { id?: string };
trace?: { id?: string };
transaction?: { id?: string };
}

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-url.html
*
* @internal
*/
export interface EcsUrl {
domain?: string;
extension?: string;
fragment?: string;
full?: string;
original?: string;
password?: string;
path?: string;
port?: number;
query?: string;
registered_domain?: string;
scheme?: string;
subdomain?: string;
top_level_domain?: string;
username?: string;
}

View file

@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsGroup } from './group';
interface NestedFields {
group?: EcsGroup;
}
/**
* `User` is unlike most other fields which can be reused in multiple places
* in that ECS places restrictions on which individual properties can be reused;
*
* Specifically, `changes`, `effective`, and `target` may be used if `user` is
* placed at the root level, but not if it is nested inside another field like
* `destination`. A more detailed explanation of these nuances can be found at:
*
* https://www.elastic.co/guide/en/ecs/1.9/ecs-user-usage.html
*
* As a result, we need to export a separate `NestedUser` type to import into
* other interfaces internally. This contains the reusable subset of properties
* from `User`.
*
* @internal
*/
export interface EcsNestedUser extends NestedFields {
domain?: string;
email?: string;
full_name?: string;
hash?: string;
id?: string;
name?: string;
roles?: string[];
}
/**
* @internal
*/
export interface EcsUser extends EcsNestedUser {
changes?: EcsNestedUser;
effective?: EcsNestedUser;
target?: EcsNestedUser;
}

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsOs } from './os';
interface NestedFields {
os?: EcsOs;
}
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-user_agent.html
*
* @internal
*/
export interface EcsUserAgent extends NestedFields {
device?: { name: string };
name?: string;
original?: string;
version?: string;
}

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-vlan.html
*
* @internal
*/
export interface EcsVlan {
id?: string;
name?: string;
}

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-vulnerability.html
*
* @internal
*/
export interface EcsVulnerability {
category?: string[];
classification?: string;
description?: string;
enumeration?: string;
id?: string;
reference?: string;
report_id?: string;
scanner?: { vendor: string };
score?: Score;
severity?: string;
}
interface Score {
base?: number;
environmental?: number;
temporal?: number;
version?: string;
}

View file

@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-x509.html
*
* @internal
*/
export interface EcsX509 {
alternative_names?: string[];
issuer?: Issuer;
not_after?: string;
not_before?: string;
public_key_algorithm?: string;
public_key_curve?: string;
public_key_exponent?: number;
public_key_size?: number;
serial_number?: string;
signature_algorithm?: string;
subject?: Subject;
version_number?: string;
}
interface Issuer {
common_name?: string[];
country?: string[];
distinguished_name?: string;
locality?: string[];
organization?: string[];
organizational_unit?: string[];
state_or_province?: string[];
}
interface Subject {
common_name?: string[];
country?: string[];
distinguished_name?: string;
locality?: string[];
organization?: string[];
organizational_unit?: string[];
state_or_province?: string[];
}

View file

@ -8,7 +8,9 @@
export { LogLevel, LogLevelId } from './log_level';
export { LogRecord } from './log_record';
export { Logger, LogMeta } from './logger';
export { Logger } from './logger';
export { LogMeta } from './log_meta';
export { LoggerFactory } from './logger_factory';
export { Layout } from './layout';
export { Appender, DisposableAppender } from './appenders';
export { Ecs, EcsEventCategory, EcsEventKind, EcsEventOutcome, EcsEventType } from './ecs';

View file

@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EcsBase } from './ecs/base';
import { EcsAgent } from './ecs/agent';
import { EcsAutonomousSystem } from './ecs/autonomous_system';
import { EcsClient } from './ecs/client';
import { EcsCloud } from './ecs/cloud';
import { EcsContainer } from './ecs/container';
import { EcsDestination } from './ecs/destination';
import { EcsDns } from './ecs/dns';
import { EcsError } from './ecs/error';
import { EcsEvent } from './ecs/event';
import { EcsFile } from './ecs/file';
import { EcsGroup } from './ecs/group';
import { EcsHost } from './ecs/host';
import { EcsHttp } from './ecs/http';
import { EcsLog } from './ecs/log';
import { EcsNetwork } from './ecs/network';
import { EcsObserver } from './ecs/observer';
import { EcsOrganization } from './ecs/organization';
import { EcsPackage } from './ecs/package';
import { EcsProcess } from './ecs/process';
import { EcsRegistry } from './ecs/registry';
import { EcsRelated } from './ecs/related';
import { EcsRule } from './ecs/rule';
import { EcsServer } from './ecs/server';
import { EcsService } from './ecs/service';
import { EcsSource } from './ecs/source';
import { EcsThreat } from './ecs/threat';
import { EcsTls } from './ecs/tls';
import { EcsTracing } from './ecs/tracing';
import { EcsUrl } from './ecs/url';
import { EcsUser } from './ecs/user';
import { EcsUserAgent } from './ecs/user_agent';
import { EcsVulnerability } from './ecs/vulnerability';
/**
* Represents the ECS schema with the following reserved keys excluded:
* - `ecs`
* - `@timestamp`
* - `message`
* - `log.level`
* - `log.logger`
*
* @public
*/
export type LogMeta = Omit<EcsBase, '@timestamp' | 'message'> &
EcsTracing & {
agent?: EcsAgent;
as?: EcsAutonomousSystem;
client?: EcsClient;
cloud?: EcsCloud;
container?: EcsContainer;
destination?: EcsDestination;
dns?: EcsDns;
error?: EcsError;
event?: EcsEvent;
file?: EcsFile;
group?: EcsGroup;
host?: EcsHost;
http?: EcsHttp;
log?: Omit<EcsLog, 'level' | 'logger'>;
network?: EcsNetwork;
observer?: EcsObserver;
organization?: EcsOrganization;
package?: EcsPackage;
process?: EcsProcess;
registry?: EcsRegistry;
related?: EcsRelated;
rule?: EcsRule;
server?: EcsServer;
service?: EcsService;
source?: EcsSource;
threat?: EcsThreat;
tls?: EcsTls;
url?: EcsUrl;
user?: EcsUser;
user_agent?: EcsUserAgent;
vulnerability?: EcsVulnerability;
};

View file

@ -6,17 +6,9 @@
* Side Public License, v 1.
*/
import { LogMeta } from './log_meta';
import { LogRecord } from './log_record';
/**
* Contextual metadata
*
* @public
*/
export interface LogMeta {
[key: string]: any;
}
/**
* Logger exposes all the necessary methods to log any type of information and
* this is the interface used by the logging consumers including plugins.
@ -30,28 +22,28 @@ export interface Logger {
* @param message - The log message
* @param meta -
*/
trace(message: string, meta?: LogMeta): void;
trace<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void;
/**
* Log messages useful for debugging and interactive investigation
* @param message - The log message
* @param meta -
*/
debug(message: string, meta?: LogMeta): void;
debug<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void;
/**
* Logs messages related to general application flow
* @param message - The log message
* @param meta -
*/
info(message: string, meta?: LogMeta): void;
info<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void;
/**
* Logs abnormal or unexpected errors or messages
* @param errorOrMessage - An Error object or message string to log
* @param meta -
*/
warn(errorOrMessage: string | Error, meta?: LogMeta): void;
warn<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void;
/**
* Logs abnormal or unexpected errors or messages that caused a failure in the application flow
@ -59,7 +51,7 @@ export interface Logger {
* @param errorOrMessage - An Error object or message string to log
* @param meta -
*/
error(errorOrMessage: string | Error, meta?: LogMeta): void;
error<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void;
/**
* Logs abnormal or unexpected errors or messages that caused an unrecoverable failure
@ -67,7 +59,7 @@ export interface Logger {
* @param errorOrMessage - An Error object or message string to log
* @param meta -
*/
fatal(errorOrMessage: string | Error, meta?: LogMeta): void;
fatal<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void;
/** @internal */
log(record: LogRecord): void;

View file

@ -31,13 +31,23 @@ export const writePidFile = async ({
if (pidConfig.exclusive) {
throw new Error(message);
} else {
logger.warn(message, { path, pid });
logger.warn(message, {
process: {
pid: process.pid,
path,
},
});
}
}
await writeFile(path, pid);
logger.debug(`wrote pid file to ${path}`, { path, pid });
logger.debug(`wrote pid file to ${path}`, {
process: {
pid: process.pid,
path,
},
});
const clean = once(() => {
unlink(path);

View file

@ -334,7 +334,7 @@ export class HttpServer {
const log = this.logger.get('http', 'server', 'response');
this.handleServerResponseEvent = (request) => {
const { message, ...meta } = getEcsResponseLog(request, this.log);
const { message, meta } = getEcsResponseLog(request, this.log);
log.debug(message!, meta);
};

View file

@ -81,7 +81,8 @@ describe('getEcsResponseLog', () => {
},
});
const result = getEcsResponseLog(req, logger);
expect(result.http.response.responseTime).toBe(1000);
// @ts-expect-error ECS custom field
expect(result.meta.http.response.responseTime).toBe(1000);
});
test('with response.info.responded', () => {
@ -92,14 +93,16 @@ describe('getEcsResponseLog', () => {
},
});
const result = getEcsResponseLog(req, logger);
expect(result.http.response.responseTime).toBe(500);
// @ts-expect-error ECS custom field
expect(result.meta.http.response.responseTime).toBe(500);
});
test('excludes responseTime from message if none is provided', () => {
const req = createMockHapiRequest();
const result = getEcsResponseLog(req, logger);
expect(result.message).toMatchInlineSnapshot(`"GET /path 200 - 1.2KB"`);
expect(result.http.response.responseTime).toBeUndefined();
// @ts-expect-error ECS custom field
expect(result.meta.http.response.responseTime).toBeUndefined();
});
});
@ -112,7 +115,7 @@ describe('getEcsResponseLog', () => {
},
});
const result = getEcsResponseLog(req, logger);
expect(result.url.query).toMatchInlineSnapshot(`"a=hello&b=world"`);
expect(result.meta.url!.query).toMatchInlineSnapshot(`"a=hello&b=world"`);
expect(result.message).toMatchInlineSnapshot(`"GET /path?a=hello&b=world 200 - 1.2KB"`);
});
@ -121,7 +124,7 @@ describe('getEcsResponseLog', () => {
query: { a: '¡hola!' },
});
const result = getEcsResponseLog(req, logger);
expect(result.url.query).toMatchInlineSnapshot(`"a=%C2%A1hola!"`);
expect(result.meta.url!.query).toMatchInlineSnapshot(`"a=%C2%A1hola!"`);
expect(result.message).toMatchInlineSnapshot(`"GET /path?a=%C2%A1hola! 200 - 1.2KB"`);
});
});
@ -145,7 +148,7 @@ describe('getEcsResponseLog', () => {
response: Boom.badRequest(),
});
const result = getEcsResponseLog(req, logger);
expect(result.http.response.status_code).toBe(400);
expect(result.meta.http!.response!.status_code).toBe(400);
});
describe('filters sensitive headers', () => {
@ -155,14 +158,16 @@ describe('getEcsResponseLog', () => {
response: { headers: { 'content-length': 123, 'set-cookie': 'c' } },
});
const result = getEcsResponseLog(req, logger);
expect(result.http.request.headers).toMatchInlineSnapshot(`
// @ts-expect-error ECS custom field
expect(result.meta.http.request.headers).toMatchInlineSnapshot(`
Object {
"authorization": "[REDACTED]",
"cookie": "[REDACTED]",
"user-agent": "hi",
}
`);
expect(result.http.response.headers).toMatchInlineSnapshot(`
// @ts-expect-error ECS custom field
expect(result.meta.http.response.headers).toMatchInlineSnapshot(`
Object {
"content-length": 123,
"set-cookie": "[REDACTED]",
@ -196,9 +201,12 @@ describe('getEcsResponseLog', () => {
}
`);
responseLog.http.request.headers.a = 'testA';
responseLog.http.request.headers.b[1] = 'testB';
responseLog.http.request.headers.c = 'testC';
// @ts-expect-error ECS custom field
responseLog.meta.http.request.headers.a = 'testA';
// @ts-expect-error ECS custom field
responseLog.meta.http.request.headers.b[1] = 'testB';
// @ts-expect-error ECS custom field
responseLog.meta.http.request.headers.c = 'testC';
expect(reqHeaders).toMatchInlineSnapshot(`
Object {
"a": "foo",
@ -244,48 +252,41 @@ describe('getEcsResponseLog', () => {
});
describe('ecs', () => {
test('specifies correct ECS version', () => {
const req = createMockHapiRequest();
const result = getEcsResponseLog(req, logger);
expect(result.ecs.version).toBe('1.7.0');
});
test('provides an ECS-compatible response', () => {
const req = createMockHapiRequest();
const result = getEcsResponseLog(req, logger);
expect(result).toMatchInlineSnapshot(`
Object {
"client": Object {
"ip": undefined,
},
"ecs": Object {
"version": "1.7.0",
},
"http": Object {
"request": Object {
"headers": Object {
"user-agent": "",
},
"method": "GET",
"mime_type": "application/json",
"referrer": "localhost:5601/app/home",
},
"response": Object {
"body": Object {
"bytes": 1234,
},
"headers": Object {},
"responseTime": undefined,
"status_code": 200,
},
},
"message": "GET /path 200 - 1.2KB",
"url": Object {
"path": "/path",
"query": "",
},
"user_agent": Object {
"original": "",
"meta": Object {
"client": Object {
"ip": undefined,
},
"http": Object {
"request": Object {
"headers": Object {
"user-agent": "",
},
"method": "GET",
"mime_type": "application/json",
"referrer": "localhost:5601/app/home",
},
"response": Object {
"body": Object {
"bytes": 1234,
},
"headers": Object {},
"responseTime": undefined,
"status_code": 200,
},
},
"url": Object {
"path": "/path",
"query": "",
},
"user_agent": Object {
"original": "",
},
},
}
`);

View file

@ -11,10 +11,9 @@ import { isBoom } from '@hapi/boom';
import type { Request } from '@hapi/hapi';
import numeral from '@elastic/numeral';
import { LogMeta } from '@kbn/logging';
import { EcsEvent, Logger } from '../../logging';
import { Logger } from '../../logging';
import { getResponsePayloadBytes } from './get_payload_size';
const ECS_VERSION = '1.7.0';
const FORBIDDEN_HEADERS = ['authorization', 'cookie', 'set-cookie'];
const REDACTED_HEADER_TEXT = '[REDACTED]';
@ -44,7 +43,7 @@ function cloneAndFilterHeaders(headers?: HapiHeaders) {
*
* @internal
*/
export function getEcsResponseLog(request: Request, log: Logger): LogMeta {
export function getEcsResponseLog(request: Request, log: Logger) {
const { path, response } = request;
const method = request.method.toUpperCase();
@ -66,9 +65,7 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta {
const bytes = getResponsePayloadBytes(response, log);
const bytesMsg = bytes ? ` - ${numeral(bytes).format('0.0b')}` : '';
const meta: EcsEvent = {
ecs: { version: ECS_VERSION },
message: `${method} ${pathWithQuery} ${status_code}${responseTimeMsg}${bytesMsg}`,
const meta: LogMeta = {
client: {
ip: request.info.remoteAddress,
},
@ -77,7 +74,7 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta {
method,
mime_type: request.mime,
referrer: request.info.referrer,
// @ts-expect-error Headers are not yet part of ECS: https://github.com/elastic/ecs/issues/232.
// @ts-expect-error ECS custom field: https://github.com/elastic/ecs/issues/232.
headers: requestHeaders,
},
response: {
@ -85,7 +82,7 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta {
bytes,
},
status_code,
// @ts-expect-error Headers are not yet part of ECS: https://github.com/elastic/ecs/issues/232.
// @ts-expect-error ECS custom field: https://github.com/elastic/ecs/issues/232.
headers: responseHeaders,
// responseTime is a custom non-ECS field
responseTime: !isNaN(responseTime) ? responseTime : undefined,
@ -100,5 +97,8 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta {
},
};
return meta;
return {
message: `${method} ${pathWithQuery} ${status_code}${responseTimeMsg}${bytesMsg}`,
meta,
};
}

View file

@ -238,6 +238,11 @@ export type { IRenderOptions } from './rendering';
export type {
Logger,
LoggerFactory,
Ecs,
EcsEventCategory,
EcsEventKind,
EcsEventOutcome,
EcsEventType,
LogMeta,
LogRecord,
LogLevel,

View file

@ -15,6 +15,9 @@ exports[`appends records via multiple appenders.: file logs 2`] = `
exports[`asLoggerFactory() only allows to create new loggers. 1`] = `
Object {
"@timestamp": "2012-01-30T22:33:22.011-05:00",
"ecs": Object {
"version": "1.9.0",
},
"log": Object {
"level": "TRACE",
"logger": "test.context",
@ -29,6 +32,9 @@ Object {
exports[`asLoggerFactory() only allows to create new loggers. 2`] = `
Object {
"@timestamp": "2012-01-30T17:33:22.011-05:00",
"ecs": Object {
"version": "1.9.0",
},
"log": Object {
"level": "INFO",
"logger": "test.context",
@ -44,6 +50,9 @@ Object {
exports[`asLoggerFactory() only allows to create new loggers. 3`] = `
Object {
"@timestamp": "2012-01-30T12:33:22.011-05:00",
"ecs": Object {
"version": "1.9.0",
},
"log": Object {
"level": "FATAL",
"logger": "test.context",
@ -58,6 +67,9 @@ Object {
exports[`flushes memory buffer logger and switches to real logger once config is provided: buffered messages 1`] = `
Object {
"@timestamp": "2012-02-01T09:33:22.011-05:00",
"ecs": Object {
"version": "1.9.0",
},
"log": Object {
"level": "INFO",
"logger": "test.context",
@ -73,6 +85,9 @@ Object {
exports[`flushes memory buffer logger and switches to real logger once config is provided: new messages 1`] = `
Object {
"@timestamp": "2012-01-31T23:33:22.011-05:00",
"ecs": Object {
"version": "1.9.0",
},
"log": Object {
"level": "INFO",
"logger": "test.context",

View file

@ -26,12 +26,14 @@ describe('MetaRewritePolicy', () => {
describe('mode: update', () => {
it('updates existing properties in LogMeta', () => {
// @ts-expect-error ECS custom meta
const log = createLogRecord({ a: 'before' });
const policy = createPolicy('update', [{ path: 'a', value: 'after' }]);
expect(policy.rewrite(log).meta!.a).toBe('after');
});
it('updates nested properties in LogMeta', () => {
// @ts-expect-error ECS custom meta
const log = createLogRecord({ a: 'before a', b: { c: 'before b.c' }, d: [0, 1] });
const policy = createPolicy('update', [
{ path: 'a', value: 'after a' },
@ -60,6 +62,7 @@ describe('MetaRewritePolicy', () => {
{ path: 'd', value: 'hi' },
]);
const log = createLogRecord({
// @ts-expect-error ECS custom meta
a: 'a',
b: 'b',
c: 'c',
@ -80,6 +83,7 @@ describe('MetaRewritePolicy', () => {
{ path: 'a.b', value: 'foo' },
{ path: 'a.c', value: 'bar' },
]);
// @ts-expect-error ECS custom meta
const log = createLogRecord({ a: { b: 'existing meta' } });
const { meta } = policy.rewrite(log);
expect(meta!.a.b).toBe('foo');
@ -106,12 +110,14 @@ describe('MetaRewritePolicy', () => {
describe('mode: remove', () => {
it('removes existing properties in LogMeta', () => {
// @ts-expect-error ECS custom meta
const log = createLogRecord({ a: 'goodbye' });
const policy = createPolicy('remove', [{ path: 'a' }]);
expect(policy.rewrite(log).meta!.a).toBeUndefined();
});
it('removes nested properties in LogMeta', () => {
// @ts-expect-error ECS custom meta
const log = createLogRecord({ a: 'a', b: { c: 'b.c' }, d: [0, 1] });
const policy = createPolicy('remove', [{ path: 'b.c' }, { path: 'd[1]' }]);
expect(policy.rewrite(log).meta).toMatchInlineSnapshot(`
@ -127,6 +133,7 @@ describe('MetaRewritePolicy', () => {
});
it('has no effect if property does not exist', () => {
// @ts-expect-error ECS custom meta
const log = createLogRecord({ a: 'a' });
const policy = createPolicy('remove', [{ path: 'b' }]);
expect(policy.rewrite(log).meta).toMatchInlineSnapshot(`

View file

@ -85,8 +85,8 @@ describe('RewriteAppender', () => {
const appender = new RewriteAppender(config);
appenderMocks.forEach((mock) => appender.addAppender(...mock));
const log1 = createLogRecord({ a: 'b' });
const log2 = createLogRecord({ c: 'd' });
const log1 = createLogRecord({ user_agent: { name: 'a' } });
const log2 = createLogRecord({ user_agent: { name: 'b' } });
appender.append(log1);
@ -109,8 +109,8 @@ describe('RewriteAppender', () => {
const appender = new RewriteAppender(config);
appender.addAppender(...createAppenderMock('mock1'));
const log1 = createLogRecord({ a: 'b' });
const log2 = createLogRecord({ c: 'd' });
const log1 = createLogRecord({ user_agent: { name: 'a' } });
const log2 = createLogRecord({ user_agent: { name: 'b' } });
appender.append(log1);

View file

@ -1,129 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/**
* Typings for some ECS fields which core uses internally.
* These are not a complete set of ECS typings and should not
* be used externally; the only types included here are ones
* currently used in core.
*
* @internal
*/
export interface EcsEvent {
/**
* These typings were written as of ECS 1.7.0.
* Don't change this value without checking the rest
* of the types to conform to that ECS version.
*
* https://www.elastic.co/guide/en/ecs/1.7/index.html
*/
ecs: { version: '1.7.0' };
// base fields
['@timestamp']?: string;
labels?: Record<string, unknown>;
message?: string;
tags?: string[];
// other fields
client?: EcsClientField;
event?: EcsEventField;
http?: EcsHttpField;
process?: EcsProcessField;
url?: EcsUrlField;
user_agent?: EcsUserAgentField;
}
/** @internal */
export enum EcsEventKind {
ALERT = 'alert',
EVENT = 'event',
METRIC = 'metric',
STATE = 'state',
PIPELINE_ERROR = 'pipeline_error',
SIGNAL = 'signal',
}
/** @internal */
export enum EcsEventCategory {
AUTHENTICATION = 'authentication',
CONFIGURATION = 'configuration',
DATABASE = 'database',
DRIVER = 'driver',
FILE = 'file',
HOST = 'host',
IAM = 'iam',
INTRUSION_DETECTION = 'intrusion_detection',
MALWARE = 'malware',
NETWORK = 'network',
PACKAGE = 'package',
PROCESS = 'process',
WEB = 'web',
}
/** @internal */
export enum EcsEventType {
ACCESS = 'access',
ADMIN = 'admin',
ALLOWED = 'allowed',
CHANGE = 'change',
CONNECTION = 'connection',
CREATION = 'creation',
DELETION = 'deletion',
DENIED = 'denied',
END = 'end',
ERROR = 'error',
GROUP = 'group',
INFO = 'info',
INSTALLATION = 'installation',
PROTOCOL = 'protocol',
START = 'start',
USER = 'user',
}
interface EcsEventField {
kind?: EcsEventKind;
category?: EcsEventCategory[];
type?: EcsEventType;
}
interface EcsProcessField {
uptime?: number;
}
interface EcsClientField {
ip?: string;
}
interface EcsHttpFieldRequest {
body?: { bytes?: number; content?: string };
method?: string;
mime_type?: string;
referrer?: string;
}
interface EcsHttpFieldResponse {
body?: { bytes?: number; content?: string };
bytes?: number;
status_code?: number;
}
interface EcsHttpField {
version?: string;
request?: EcsHttpFieldRequest;
response?: EcsHttpFieldResponse;
}
interface EcsUrlField {
path?: string;
query?: string;
}
interface EcsUserAgentField {
original?: string;
}

View file

@ -9,6 +9,11 @@ export { LogLevel } from '@kbn/logging';
export type {
DisposableAppender,
Appender,
Ecs,
EcsEventCategory,
EcsEventKind,
EcsEventOutcome,
EcsEventType,
LogRecord,
Layout,
LoggerFactory,
@ -16,8 +21,6 @@ export type {
Logger,
LogLevelId,
} from '@kbn/logging';
export { EcsEventType, EcsEventCategory, EcsEventKind } from './ecs';
export type { EcsEvent } from './ecs';
export { config } from './logging_config';
export type {
LoggingConfigType,

View file

@ -1,13 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`\`format()\` correctly formats record. 1`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-1\\",\\"error\\":{\\"message\\":\\"Some error message\\",\\"type\\":\\"Some error name\\",\\"stack_trace\\":\\"Some error stack\\"},\\"log\\":{\\"level\\":\\"FATAL\\",\\"logger\\":\\"context-1\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 1`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-1\\",\\"error\\":{\\"message\\":\\"Some error message\\",\\"type\\":\\"Some error name\\",\\"stack_trace\\":\\"Some error stack\\"},\\"log\\":{\\"level\\":\\"FATAL\\",\\"logger\\":\\"context-1\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 2`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-2\\",\\"log\\":{\\"level\\":\\"ERROR\\",\\"logger\\":\\"context-2\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 2`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-2\\",\\"log\\":{\\"level\\":\\"ERROR\\",\\"logger\\":\\"context-2\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 3`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-3\\",\\"log\\":{\\"level\\":\\"WARN\\",\\"logger\\":\\"context-3\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 3`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-3\\",\\"log\\":{\\"level\\":\\"WARN\\",\\"logger\\":\\"context-3\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 4`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-4\\",\\"log\\":{\\"level\\":\\"DEBUG\\",\\"logger\\":\\"context-4\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 4`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-4\\",\\"log\\":{\\"level\\":\\"DEBUG\\",\\"logger\\":\\"context-4\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 5`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-5\\",\\"log\\":{\\"level\\":\\"INFO\\",\\"logger\\":\\"context-5\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 5`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-5\\",\\"log\\":{\\"level\\":\\"INFO\\",\\"logger\\":\\"context-5\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 6`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-6\\",\\"log\\":{\\"level\\":\\"TRACE\\",\\"logger\\":\\"context-6\\"},\\"process\\":{\\"pid\\":5355}}"`;
exports[`\`format()\` correctly formats record. 6`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-6\\",\\"log\\":{\\"level\\":\\"TRACE\\",\\"logger\\":\\"context-6\\"},\\"process\\":{\\"pid\\":5355}}"`;

View file

@ -94,6 +94,7 @@ test('`format()` correctly formats record with meta-data', () => {
})
)
).toStrictEqual({
ecs: { version: '1.9.0' },
'@timestamp': '2012-02-01T09:30:22.011-05:00',
log: {
level: 'DEBUG',
@ -135,6 +136,7 @@ test('`format()` correctly formats error record with meta-data', () => {
})
)
).toStrictEqual({
ecs: { version: '1.9.0' },
'@timestamp': '2012-02-01T09:30:22.011-05:00',
log: {
level: 'DEBUG',
@ -156,34 +158,6 @@ test('`format()` correctly formats error record with meta-data', () => {
});
});
test('format() meta can override @timestamp', () => {
const layout = new JsonLayout();
expect(
JSON.parse(
layout.format({
message: 'foo',
timestamp,
level: LogLevel.Debug,
context: 'bar',
pid: 3,
meta: {
'@timestamp': '2099-05-01T09:30:22.011-05:00',
},
})
)
).toStrictEqual({
'@timestamp': '2099-05-01T09:30:22.011-05:00',
message: 'foo',
log: {
level: 'DEBUG',
logger: 'bar',
},
process: {
pid: 3,
},
});
});
test('format() meta can merge override logs', () => {
const layout = new JsonLayout();
expect(
@ -202,6 +176,7 @@ test('format() meta can merge override logs', () => {
})
)
).toStrictEqual({
ecs: { version: '1.9.0' },
'@timestamp': '2012-02-01T09:30:22.011-05:00',
message: 'foo',
log: {
@ -215,29 +190,118 @@ test('format() meta can merge override logs', () => {
});
});
test('format() meta can override log level objects', () => {
test('format() meta can not override message', () => {
const layout = new JsonLayout();
expect(
JSON.parse(
layout.format({
timestamp,
context: '123',
message: 'foo',
level: LogLevel.Error,
timestamp,
level: LogLevel.Debug,
context: 'bar',
pid: 3,
meta: {
log: {
level: 'FATAL',
},
message: 'baz',
},
})
)
).toStrictEqual({
ecs: { version: '1.9.0' },
'@timestamp': '2012-02-01T09:30:22.011-05:00',
message: 'foo',
log: {
level: 'FATAL',
logger: '123',
level: 'DEBUG',
logger: 'bar',
},
process: {
pid: 3,
},
});
});
test('format() meta can not override ecs version', () => {
const layout = new JsonLayout();
expect(
JSON.parse(
layout.format({
message: 'foo',
timestamp,
level: LogLevel.Debug,
context: 'bar',
pid: 3,
meta: {
message: 'baz',
},
})
)
).toStrictEqual({
ecs: { version: '1.9.0' },
'@timestamp': '2012-02-01T09:30:22.011-05:00',
message: 'foo',
log: {
level: 'DEBUG',
logger: 'bar',
},
process: {
pid: 3,
},
});
});
test('format() meta can not override logger or level', () => {
const layout = new JsonLayout();
expect(
JSON.parse(
layout.format({
message: 'foo',
timestamp,
level: LogLevel.Debug,
context: 'bar',
pid: 3,
meta: {
log: {
level: 'IGNORE',
logger: 'me',
},
},
})
)
).toStrictEqual({
ecs: { version: '1.9.0' },
'@timestamp': '2012-02-01T09:30:22.011-05:00',
message: 'foo',
log: {
level: 'DEBUG',
logger: 'bar',
},
process: {
pid: 3,
},
});
});
test('format() meta can not override timestamp', () => {
const layout = new JsonLayout();
expect(
JSON.parse(
layout.format({
message: 'foo',
timestamp,
level: LogLevel.Debug,
context: 'bar',
pid: 3,
meta: {
'@timestamp': '2099-02-01T09:30:22.011-05:00',
},
})
)
).toStrictEqual({
ecs: { version: '1.9.0' },
'@timestamp': '2012-02-01T09:30:22.011-05:00',
message: 'foo',
log: {
level: 'DEBUG',
logger: 'bar',
},
process: {
pid: 3,

View file

@ -9,7 +9,7 @@
import moment from 'moment-timezone';
import { merge } from '@kbn/std';
import { schema } from '@kbn/config-schema';
import { LogRecord, Layout } from '@kbn/logging';
import { Ecs, LogRecord, Layout } from '@kbn/logging';
const { literal, object } = schema;
@ -42,7 +42,8 @@ export class JsonLayout implements Layout {
}
public format(record: LogRecord): string {
const log = {
const log: Ecs = {
ecs: { version: '1.9.0' },
'@timestamp': moment(record.timestamp).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
message: record.message,
error: JsonLayout.errorToSerializableObject(record.error),
@ -54,7 +55,8 @@ export class JsonLayout implements Layout {
pid: record.pid,
},
};
const output = record.meta ? merge(log, record.meta) : log;
const output = record.meta ? merge({ ...record.meta }, log) : log;
return JSON.stringify(output);
}
}

View file

@ -45,6 +45,7 @@ test('`trace()` correctly forms `LogRecord` and passes it to all appenders.', ()
});
}
// @ts-expect-error ECS custom meta
logger.trace('message-2', { trace: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
@ -75,6 +76,7 @@ test('`debug()` correctly forms `LogRecord` and passes it to all appenders.', ()
});
}
// @ts-expect-error ECS custom meta
logger.debug('message-2', { debug: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
@ -105,6 +107,7 @@ test('`info()` correctly forms `LogRecord` and passes it to all appenders.', ()
});
}
// @ts-expect-error ECS custom meta
logger.info('message-2', { info: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
@ -150,6 +153,7 @@ test('`warn()` correctly forms `LogRecord` and passes it to all appenders.', ()
});
}
// @ts-expect-error ECS custom meta
logger.warn('message-3', { warn: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(3);
@ -195,6 +199,7 @@ test('`error()` correctly forms `LogRecord` and passes it to all appenders.', ()
});
}
// @ts-expect-error ECS custom meta
logger.error('message-3', { error: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(3);
@ -240,6 +245,7 @@ test('`fatal()` correctly forms `LogRecord` and passes it to all appenders.', ()
});
}
// @ts-expect-error ECS custom meta
logger.fatal('message-3', { fatal: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(3);

View file

@ -21,28 +21,28 @@ export class BaseLogger implements Logger {
private readonly factory: LoggerFactory
) {}
public trace(message: string, meta?: LogMeta): void {
this.log(this.createLogRecord(LogLevel.Trace, message, meta));
public trace<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Trace, message, meta));
}
public debug(message: string, meta?: LogMeta): void {
this.log(this.createLogRecord(LogLevel.Debug, message, meta));
public debug<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Debug, message, meta));
}
public info(message: string, meta?: LogMeta): void {
this.log(this.createLogRecord(LogLevel.Info, message, meta));
public info<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Info, message, meta));
}
public warn(errorOrMessage: string | Error, meta?: LogMeta): void {
this.log(this.createLogRecord(LogLevel.Warn, errorOrMessage, meta));
public warn<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Warn, errorOrMessage, meta));
}
public error(errorOrMessage: string | Error, meta?: LogMeta): void {
this.log(this.createLogRecord(LogLevel.Error, errorOrMessage, meta));
public error<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Error, errorOrMessage, meta));
}
public fatal(errorOrMessage: string | Error, meta?: LogMeta): void {
this.log(this.createLogRecord(LogLevel.Fatal, errorOrMessage, meta));
public fatal<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Fatal, errorOrMessage, meta));
}
public log(record: LogRecord) {
@ -59,10 +59,10 @@ export class BaseLogger implements Logger {
return this.factory.get(...[this.context, ...childContextPaths]);
}
private createLogRecord(
private createLogRecord<Meta extends LogMeta>(
level: LogLevel,
errorOrMessage: string | Error,
meta?: LogMeta
meta?: Meta
): LogRecord {
if (isError(errorOrMessage)) {
return {

View file

@ -49,6 +49,7 @@ test('uses default memory buffer logger until config is provided', () => {
// We shouldn't create new buffer appender for another context name.
const anotherLogger = system.get('test', 'context2');
// @ts-expect-error ECS custom meta
anotherLogger.fatal('fatal message', { some: 'value' });
expect(bufferAppendSpy).toHaveBeenCalledTimes(2);
@ -62,6 +63,7 @@ test('flushes memory buffer logger and switches to real logger once config is pr
const logger = system.get('test', 'context');
logger.trace('buffered trace message');
// @ts-expect-error ECS custom meta
logger.info('buffered info message', { some: 'value' });
logger.fatal('buffered fatal message');
@ -159,6 +161,7 @@ test('attaches appenders to appenders that declare refs', async () => {
);
const testLogger = system.get('tests');
// @ts-expect-error ECS custom meta
testLogger.warn('This message goes to a test context.', { a: 'hi', b: 'remove me' });
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
@ -233,6 +236,7 @@ test('asLoggerFactory() only allows to create new loggers.', async () => {
);
logger.trace('buffered trace message');
// @ts-expect-error ECS custom meta
logger.info('buffered info message', { some: 'value' });
logger.fatal('buffered fatal message');

View file

@ -66,7 +66,7 @@ describe('getEcsOpsMetricsLog', () => {
it('correctly formats process uptime', () => {
const logMeta = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics));
expect(logMeta.process!.uptime).toEqual(1);
expect(logMeta.meta.process!.uptime).toEqual(1);
});
it('excludes values from the message if unavailable', () => {
@ -80,44 +80,40 @@ describe('getEcsOpsMetricsLog', () => {
expect(logMeta.message).toMatchInlineSnapshot(`""`);
});
it('specifies correct ECS version', () => {
const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics());
expect(logMeta.ecs.version).toBe('1.7.0');
});
it('provides an ECS-compatible response', () => {
const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics());
expect(logMeta).toMatchInlineSnapshot(`
Object {
"ecs": Object {
"version": "1.7.0",
},
"event": Object {
"category": Array [
"process",
"host",
],
"kind": "metric",
"type": "info",
},
"host": Object {
"os": Object {
"load": Object {
"15m": 1,
"1m": 1,
"5m": 1,
},
},
},
"message": "memory: 1.0B load: [1.00,1.00,1.00] delay: 1.000",
"process": Object {
"eventLoopDelay": 1,
"memory": Object {
"heap": Object {
"usedInBytes": 1,
"meta": Object {
"event": Object {
"category": Array [
"process",
"host",
],
"kind": "metric",
"type": Array [
"info",
],
},
"host": Object {
"os": Object {
"load": Object {
"15m": 1,
"1m": 1,
"5m": 1,
},
},
},
"uptime": 0,
"process": Object {
"eventLoopDelay": 1,
"memory": Object {
"heap": Object {
"usedInBytes": 1,
},
},
"uptime": 0,
},
},
}
`);
@ -125,8 +121,8 @@ describe('getEcsOpsMetricsLog', () => {
it('logs ECS fields in the log meta', () => {
const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics());
expect(logMeta.event!.kind).toBe('metric');
expect(logMeta.event!.category).toEqual(expect.arrayContaining(['process', 'host']));
expect(logMeta.event!.type).toBe('info');
expect(logMeta.meta.event!.kind).toBe('metric');
expect(logMeta.meta.event!.category).toEqual(expect.arrayContaining(['process', 'host']));
expect(logMeta.meta.event!.type).toEqual(expect.arrayContaining(['info']));
});
});

View file

@ -7,16 +7,15 @@
*/
import numeral from '@elastic/numeral';
import { EcsEvent, EcsEventKind, EcsEventCategory, EcsEventType } from '../../logging';
import { LogMeta } from '@kbn/logging';
import { OpsMetrics } from '..';
const ECS_VERSION = '1.7.0';
/**
* Converts ops metrics into ECS-compliant `LogMeta` for logging
*
* @internal
*/
export function getEcsOpsMetricsLog(metrics: OpsMetrics): EcsEvent {
export function getEcsOpsMetricsLog(metrics: OpsMetrics) {
const { process, os } = metrics;
const processMemoryUsedInBytes = process?.memory?.heap?.used_in_bytes;
const processMemoryUsedInBytesMsg = processMemoryUsedInBytes
@ -51,13 +50,11 @@ export function getEcsOpsMetricsLog(metrics: OpsMetrics): EcsEvent {
})}] `
: '';
return {
ecs: { version: ECS_VERSION },
message: `${processMemoryUsedInBytesMsg}${uptimeValMsg}${loadValsMsg}${eventLoopDelayValMsg}`,
const meta: LogMeta = {
event: {
kind: EcsEventKind.METRIC,
category: [EcsEventCategory.PROCESS, EcsEventCategory.HOST],
type: EcsEventType.INFO,
kind: 'metric',
category: ['process', 'host'],
type: ['info'],
},
process: {
uptime: uptimeVal,
@ -71,8 +68,14 @@ export function getEcsOpsMetricsLog(metrics: OpsMetrics): EcsEvent {
},
host: {
os: {
// @ts-expect-error custom fields not yet part of ECS
load: loadEntries,
},
},
};
return {
message: `${processMemoryUsedInBytesMsg}${uptimeValMsg}${loadValsMsg}${eventLoopDelayValMsg}`,
meta,
};
}

View file

@ -182,16 +182,15 @@ describe('MetricsService', () => {
Array [
"",
Object {
"ecs": Object {
"version": "1.7.0",
},
"event": Object {
"category": Array [
"process",
"host",
],
"kind": "metric",
"type": "info",
"type": Array [
"info",
],
},
"host": Object {
"os": Object {

View file

@ -73,7 +73,7 @@ export class MetricsService
private async refreshMetrics() {
const metrics = await this.metricsCollector!.collect();
const { message, ...meta } = getEcsOpsMetricsLog(metrics);
const { message, meta } = getEcsOpsMetricsLog(metrics);
this.opsMetricsLogger.debug(message!, meta);
this.metricsCollector!.reset();
this.metrics$.next(metrics);

View file

@ -24,7 +24,7 @@ export interface SavedObjectsMigrationLogger {
*/
warning: (msg: string) => void;
warn: (msg: string) => void;
error: (msg: string, meta: LogMeta) => void;
error: <Meta extends LogMeta = LogMeta>(msg: string, meta: Meta) => void;
}
export class MigrationLogger implements SavedObjectsMigrationLogger {

View file

@ -211,86 +211,90 @@ describe('migrationsStateActionMachine', () => {
Array [
"[.my-so-index] INIT -> LEGACY_DELETE",
Object {
"batchSize": 1000,
"controlState": "LEGACY_DELETE",
"currentAlias": ".my-so-index",
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
],
"outdatedDocuments": Array [
"1234",
],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
"kibana": Object {
"migrationState": Object {
"batchSize": 1000,
"controlState": "LEGACY_DELETE",
"currentAlias": ".my-so-index",
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
],
"outdatedDocuments": Array [
"1234",
],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"type": Object {
"type": "keyword",
"preMigrationScript": Object {
"_tag": "None",
},
},
},
"unusedTypesQuery": Object {
"_tag": "Some",
"value": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "fleet-agent-events",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
"type": Object {
"type": "keyword",
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
},
"unusedTypesQuery": Object {
"_tag": "Some",
"value": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "fleet-agent-events",
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
],
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
],
Array [
@ -303,90 +307,94 @@ describe('migrationsStateActionMachine', () => {
Array [
"[.my-so-index] LEGACY_DELETE -> FATAL",
Object {
"batchSize": 1000,
"controlState": "FATAL",
"currentAlias": ".my-so-index",
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
Object {
"level": "info",
"message": "Log from FATAL control state",
},
],
"outdatedDocuments": Array [
"1234",
],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
"kibana": Object {
"migrationState": Object {
"batchSize": 1000,
"controlState": "FATAL",
"currentAlias": ".my-so-index",
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
Object {
"level": "info",
"message": "Log from FATAL control state",
},
],
"outdatedDocuments": Array [
"1234",
],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"type": Object {
"type": "keyword",
"preMigrationScript": Object {
"_tag": "None",
},
},
},
"unusedTypesQuery": Object {
"_tag": "Some",
"value": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "fleet-agent-events",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
"type": Object {
"type": "keyword",
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
},
"unusedTypesQuery": Object {
"_tag": "Some",
"value": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "fleet-agent-events",
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
],
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
],
]
@ -490,84 +498,88 @@ describe('migrationsStateActionMachine', () => {
Array [
"[.my-so-index] INIT -> LEGACY_REINDEX",
Object {
"batchSize": 1000,
"controlState": "LEGACY_REINDEX",
"currentAlias": ".my-so-index",
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_REINDEX control state",
},
],
"outdatedDocuments": Array [],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
"kibana": Object {
"migrationState": Object {
"batchSize": 1000,
"controlState": "LEGACY_REINDEX",
"currentAlias": ".my-so-index",
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_REINDEX control state",
},
],
"outdatedDocuments": Array [],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"type": Object {
"type": "keyword",
"preMigrationScript": Object {
"_tag": "None",
},
},
},
"unusedTypesQuery": Object {
"_tag": "Some",
"value": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "fleet-agent-events",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
"type": Object {
"type": "keyword",
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
},
"unusedTypesQuery": Object {
"_tag": "Some",
"value": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "fleet-agent-events",
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
],
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
],
Array [
@ -577,88 +589,92 @@ describe('migrationsStateActionMachine', () => {
Array [
"[.my-so-index] LEGACY_REINDEX -> LEGACY_DELETE",
Object {
"batchSize": 1000,
"controlState": "LEGACY_DELETE",
"currentAlias": ".my-so-index",
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_REINDEX control state",
},
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
],
"outdatedDocuments": Array [],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"preMigrationScript": Object {
"_tag": "None",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
"kibana": Object {
"migrationState": Object {
"batchSize": 1000,
"controlState": "LEGACY_DELETE",
"currentAlias": ".my-so-index",
"indexPrefix": ".my-so-index",
"kibanaVersion": "7.11.0",
"legacyIndex": ".my-so-index",
"logs": Array [
Object {
"level": "info",
"message": "Log from LEGACY_REINDEX control state",
},
Object {
"level": "info",
"message": "Log from LEGACY_DELETE control state",
},
],
"outdatedDocuments": Array [],
"outdatedDocumentsQuery": Object {
"bool": Object {
"should": Array [],
},
},
"type": Object {
"type": "keyword",
"preMigrationScript": Object {
"_tag": "None",
},
},
},
"unusedTypesQuery": Object {
"_tag": "Some",
"value": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "fleet-agent-events",
},
"reason": "the fatal reason",
"retryAttempts": 5,
"retryCount": 0,
"retryDelay": 0,
"targetIndexMappings": Object {
"properties": Object {},
},
"tempIndex": ".my-so-index_7.11.0_reindex_temp",
"tempIndexMappings": Object {
"dynamic": false,
"properties": Object {
"migrationVersion": Object {
"dynamic": "true",
"type": "object",
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
"type": Object {
"type": "keyword",
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
},
"unusedTypesQuery": Object {
"_tag": "Some",
"value": Object {
"bool": Object {
"must_not": Array [
Object {
"term": Object {
"type": "fleet-agent-events",
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
Object {
"term": Object {
"type": "tsvb-validation-telemetry",
},
],
},
},
Object {
"bool": Object {
"must": Array [
Object {
"match": Object {
"type": "search-session",
},
},
Object {
"match": Object {
"search-session.persisted": false,
},
},
],
},
},
],
},
],
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
},
"versionAlias": ".my-so-index_7.11.0",
"versionIndex": ".my-so-index_7.11.0_001",
},
],
]

View file

@ -13,6 +13,12 @@ import { CorruptSavedObjectError } from '../migrations/core/migrate_raw_docs';
import { Model, Next, stateActionMachine } from './state_action_machine';
import { State } from './types';
interface StateLogMeta extends LogMeta {
kibana: {
migrationState: State;
};
}
type ExecutionLog = Array<
| {
type: 'transition';
@ -35,9 +41,15 @@ const logStateTransition = (
tookMs: number
) => {
if (newState.logs.length > oldState.logs.length) {
newState.logs
.slice(oldState.logs.length)
.forEach((log) => logger[log.level](logMessagePrefix + log.message));
newState.logs.slice(oldState.logs.length).forEach((log) => {
const getLogger = (level: keyof Logger) => {
if (level === 'error') {
return logger[level] as Logger['error'];
}
return logger[level] as Logger['info'];
};
getLogger(log.level)(logMessagePrefix + log.message);
});
}
logger.info(
@ -58,7 +70,14 @@ const dumpExecutionLog = (logger: Logger, logMessagePrefix: string, executionLog
logger.error(logMessagePrefix + 'migration failed, dumping execution log:');
executionLog.forEach((log) => {
if (log.type === 'transition') {
logger.info(logMessagePrefix + `${log.prevControlState} -> ${log.controlState}`, log.state);
logger.info<StateLogMeta>(
logMessagePrefix + `${log.prevControlState} -> ${log.controlState}`,
{
kibana: {
migrationState: log.state,
},
}
);
}
if (log.type === 'response') {
logger.info(logMessagePrefix + `${log.controlState} RESPONSE`, log.res as LogMeta);

View file

@ -49,6 +49,11 @@ import { DeleteTemplateParams } from 'elasticsearch';
import { DetailedPeerCertificate } from 'tls';
import { Duration } from 'moment';
import { Duration as Duration_2 } from 'moment-timezone';
import { Ecs } from '@kbn/logging';
import { EcsEventCategory } from '@kbn/logging';
import { EcsEventKind } from '@kbn/logging';
import { EcsEventOutcome } from '@kbn/logging';
import { EcsEventType } from '@kbn/logging';
import { EnvironmentMode } from '@kbn/config';
import { estypes } from '@elastic/elasticsearch';
import { ExistsParams } from 'elasticsearch';
@ -891,6 +896,16 @@ export interface DiscoveredPlugin {
readonly requiredPlugins: readonly PluginName[];
}
export { Ecs }
export { EcsEventCategory }
export { EcsEventKind }
export { EcsEventOutcome }
export { EcsEventType }
// @public
export type ElasticsearchClient = Omit<KibanaClient, 'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close'> & {
transport: {
@ -2792,7 +2807,7 @@ export interface SavedObjectsMigrationLogger {
// (undocumented)
debug: (msg: string) => void;
// (undocumented)
error: (msg: string, meta: LogMeta) => void;
error: <Meta extends LogMeta = LogMeta>(msg: string, meta: Meta) => void;
// (undocumented)
info: (msg: string) => void;
// (undocumented)

View file

@ -12,7 +12,7 @@ import { isDeepStrictEqual } from 'util';
import { CoreService } from '../../types';
import { CoreContext } from '../core_context';
import { Logger } from '../logging';
import { Logger, LogMeta } from '../logging';
import { InternalElasticsearchServiceSetup } from '../elasticsearch';
import { InternalHttpServiceSetup } from '../http';
import { InternalSavedObjectsServiceSetup } from '../saved_objects';
@ -26,6 +26,10 @@ import { ServiceStatus, CoreStatus, InternalStatusServiceSetup } from './types';
import { getSummaryStatus } from './get_summary_status';
import { PluginsStatusService } from './plugins_status';
interface StatusLogMeta extends LogMeta {
kibana: { status: ServiceStatus };
}
interface SetupDeps {
elasticsearch: Pick<InternalElasticsearchServiceSetup, 'status$'>;
environment: InternalEnvironmentServiceSetup;
@ -70,7 +74,11 @@ export class StatusService implements CoreService<InternalStatusServiceSetup> {
...Object.entries(coreStatus),
...Object.entries(pluginsStatus),
]);
this.logger.debug(`Recalculated overall status`, { status: summary });
this.logger.debug<StatusLogMeta>(`Recalculated overall status`, {
kibana: {
status: summary,
},
});
return summary;
}),
distinctUntilChanged(isDeepStrictEqual),

View file

@ -131,8 +131,12 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
Array [
"Upgrade config from 4.0.0 to 4.0.1",
Object {
"newVersion": "4.0.1",
"prevVersion": "4.0.0",
"kibana": Object {
"config": Object {
"newVersion": "4.0.1",
"prevVersion": "4.0.0",
},
},
},
],
]

View file

@ -10,10 +10,16 @@ import { defaults } from 'lodash';
import { SavedObjectsClientContract } from '../../saved_objects/types';
import { SavedObjectsErrorHelpers } from '../../saved_objects/';
import { Logger } from '../../logging';
import { Logger, LogMeta } from '../../logging';
import { getUpgradeableConfig } from './get_upgradeable_config';
interface ConfigLogMeta extends LogMeta {
kibana: {
config: { prevVersion: string; newVersion: string };
};
}
interface Options {
savedObjectsClient: SavedObjectsClientContract;
version: string;
@ -60,9 +66,13 @@ export async function createOrUpgradeSavedConfig(
}
if (upgradeableConfig) {
log.debug(`Upgrade config from ${upgradeableConfig.id} to ${version}`, {
prevVersion: upgradeableConfig.id,
newVersion: version,
log.debug<ConfigLogMeta>(`Upgrade config from ${upgradeableConfig.id} to ${version}`, {
kibana: {
config: {
prevVersion: upgradeableConfig.id,
newVersion: version,
},
},
});
}
}

View file

@ -187,10 +187,13 @@ describe('UsageCountersService', () => {
await tick();
// number of incrementCounter calls + number of retries
expect(mockIncrementCounter).toBeCalledTimes(2 + 1);
expect(logger.debug).toHaveBeenNthCalledWith(1, 'Store counters into savedObjects', [
mockError,
'pass',
]);
expect(logger.debug).toHaveBeenNthCalledWith(1, 'Store counters into savedObjects', {
kibana: {
usageCounters: {
results: [mockError, 'pass'],
},
},
});
});
it('buffers counters within `bufferDurationMs` time', async () => {

View file

@ -13,7 +13,7 @@ import {
SavedObjectsServiceSetup,
SavedObjectsServiceStart,
} from 'src/core/server';
import type { Logger } from 'src/core/server';
import type { Logger, LogMeta } from 'src/core/server';
import moment from 'moment';
import { CounterMetric, UsageCounter } from './usage_counter';
@ -23,6 +23,10 @@ import {
serializeCounterKey,
} from './saved_objects';
interface UsageCountersLogMeta extends LogMeta {
kibana: { usageCounters: { results: unknown[] } };
}
export interface UsageCountersServiceDeps {
logger: Logger;
retryCount: number;
@ -116,7 +120,11 @@ export class UsageCountersService {
rxOp.concatMap((counters) => this.storeDate$(counters, internalRepository))
)
.subscribe((results) => {
this.logger.debug('Store counters into savedObjects', results);
this.logger.debug<UsageCountersLogMeta>('Store counters into savedObjects', {
kibana: {
usageCounters: { results },
},
});
});
this.flushCache$.next();

View file

@ -18,7 +18,7 @@ import {
KibanaRequest,
SavedObjectsUtils,
} from '../../../../src/core/server';
import { AuditLogger, EventOutcome } from '../../security/server';
import { AuditLogger } from '../../security/server';
import { ActionType } from '../common';
import { ActionTypeRegistry } from './action_type_registry';
import { validateConfig, validateSecrets, ActionExecutorContract } from './lib';
@ -146,7 +146,7 @@ export class ActionsClient {
connectorAuditEvent({
action: ConnectorAuditAction.CREATE,
savedObject: { type: 'action', id },
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
})
);
@ -218,7 +218,7 @@ export class ActionsClient {
connectorAuditEvent({
action: ConnectorAuditAction.UPDATE,
savedObject: { type: 'action', id },
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
})
);
@ -452,7 +452,7 @@ export class ActionsClient {
this.auditLogger?.log(
connectorAuditEvent({
action: ConnectorAuditAction.DELETE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'action', id },
})
);

View file

@ -9,7 +9,7 @@ import { curry } from 'lodash';
import { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
import { Logger } from '../../../../../src/core/server';
import { Logger, LogMeta } from '../../../../../src/core/server';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
import { withoutControlCharacters } from './lib/string_utils';
@ -66,7 +66,7 @@ async function executor(
const sanitizedMessage = withoutControlCharacters(params.message);
try {
logger[params.level](`Server log: ${sanitizedMessage}`);
(logger[params.level] as Logger['info'])<LogMeta>(`Server log: ${sanitizedMessage}`);
} catch (err) {
const message = i18n.translate('xpack.actions.builtin.serverLog.errorLoggingErrorMessage', {
defaultMessage: 'error logging message',

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { EventOutcome } from '../../../security/server/audit';
import { ConnectorAuditAction, connectorAuditEvent } from './audit_events';
describe('#connectorAuditEvent', () => {
@ -13,7 +12,7 @@ describe('#connectorAuditEvent', () => {
expect(
connectorAuditEvent({
action: ConnectorAuditAction.CREATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'action', id: 'ACTION_ID' },
})
).toMatchInlineSnapshot(`
@ -21,9 +20,13 @@ describe('#connectorAuditEvent', () => {
"error": undefined,
"event": Object {
"action": "connector_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "unknown",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"saved_object": Object {
@ -47,9 +50,13 @@ describe('#connectorAuditEvent', () => {
"error": undefined,
"event": Object {
"action": "connector_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "success",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"saved_object": Object {
@ -77,9 +84,13 @@ describe('#connectorAuditEvent', () => {
},
"event": Object {
"action": "connector_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "failure",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"saved_object": Object {

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import { AuditEvent, EventOutcome, EventCategory, EventType } from '../../../security/server';
import type { EcsEventOutcome, EcsEventType } from 'src/core/server';
import { AuditEvent } from '../../../security/server';
export enum ConnectorAuditAction {
CREATE = 'connector_create',
@ -27,18 +28,18 @@ const eventVerbs: Record<ConnectorAuditAction, VerbsTuple> = {
connector_execute: ['execute', 'executing', 'executed'],
};
const eventTypes: Record<ConnectorAuditAction, EventType | undefined> = {
connector_create: EventType.CREATION,
connector_get: EventType.ACCESS,
connector_update: EventType.CHANGE,
connector_delete: EventType.DELETION,
connector_find: EventType.ACCESS,
const eventTypes: Record<ConnectorAuditAction, EcsEventType | undefined> = {
connector_create: 'creation',
connector_get: 'access',
connector_update: 'change',
connector_delete: 'deletion',
connector_find: 'access',
connector_execute: undefined,
};
export interface ConnectorAuditEventParams {
action: ConnectorAuditAction;
outcome?: EventOutcome;
outcome?: EcsEventOutcome;
savedObject?: NonNullable<AuditEvent['kibana']>['saved_object'];
error?: Error;
}
@ -53,7 +54,7 @@ export function connectorAuditEvent({
const [present, progressive, past] = eventVerbs[action];
const message = error
? `Failed attempt to ${present} ${doc}`
: outcome === EventOutcome.UNKNOWN
: outcome === 'unknown'
? `User is ${progressive} ${doc}`
: `User has ${past} ${doc}`;
const type = eventTypes[action];
@ -62,9 +63,9 @@ export function connectorAuditEvent({
message,
event: {
action,
category: EventCategory.DATABASE,
type,
outcome: outcome ?? (error ? EventOutcome.FAILURE : EventOutcome.SUCCESS),
category: ['database'],
type: type ? [type] : undefined,
outcome: outcome ?? (error ? 'failure' : 'success'),
},
kibana: {
saved_object: savedObject,

View file

@ -6,6 +6,7 @@
*/
import {
LogMeta,
SavedObjectMigrationMap,
SavedObjectUnsanitizedDoc,
SavedObjectMigrationFn,
@ -14,6 +15,10 @@ import {
import { RawAction } from '../types';
import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server';
interface ActionsLogMeta extends LogMeta {
migrations: { actionDocument: SavedObjectUnsanitizedDoc<RawAction> };
}
type ActionMigration = (
doc: SavedObjectUnsanitizedDoc<RawAction>
) => SavedObjectUnsanitizedDoc<RawAction>;
@ -50,9 +55,13 @@ function executeMigrationWithErrorHandling(
try {
return migrationFunc(doc, context);
} catch (ex) {
context.log.error(
context.log.error<ActionsLogMeta>(
`encryptedSavedObject ${version} migration failed for action ${doc.id} with error: ${ex.message}`,
{ actionDocument: doc }
{
migrations: {
actionDocument: doc,
},
}
);
}
return doc;

View file

@ -51,7 +51,7 @@ import { IEventLogClient } from '../../../../plugins/event_log/server';
import { parseIsoOrRelativeDate } from '../lib/iso_or_relative_date';
import { alertInstanceSummaryFromEventLog } from '../lib/alert_instance_summary_from_event_log';
import { IEvent } from '../../../event_log/server';
import { AuditLogger, EventOutcome } from '../../../security/server';
import { AuditLogger } from '../../../security/server';
import { parseDuration } from '../../common/parse_duration';
import { retryIfConflicts } from '../lib/retry_if_conflicts';
import { partiallyUpdateAlert } from '../saved_objects';
@ -293,7 +293,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.CREATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id },
})
);
@ -598,7 +598,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.DELETE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id },
})
);
@ -671,7 +671,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.UPDATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id },
})
);
@ -850,7 +850,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.UPDATE_API_KEY,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id },
})
);
@ -935,7 +935,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.ENABLE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id },
})
);
@ -1036,7 +1036,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.DISABLE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id },
})
);
@ -1112,7 +1112,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.MUTE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id },
})
);
@ -1173,7 +1173,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.UNMUTE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id },
})
);
@ -1234,7 +1234,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.MUTE_INSTANCE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id: alertId },
})
);
@ -1300,7 +1300,7 @@ export class AlertsClient {
this.auditLogger?.log(
alertAuditEvent({
action: AlertAuditAction.UNMUTE_INSTANCE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id: alertId },
})
);

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { EventOutcome } from '../../../security/server/audit';
import { AlertAuditAction, alertAuditEvent } from './audit_events';
describe('#alertAuditEvent', () => {
@ -13,7 +12,7 @@ describe('#alertAuditEvent', () => {
expect(
alertAuditEvent({
action: AlertAuditAction.CREATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'alert', id: 'ALERT_ID' },
})
).toMatchInlineSnapshot(`
@ -21,9 +20,13 @@ describe('#alertAuditEvent', () => {
"error": undefined,
"event": Object {
"action": "alert_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "unknown",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"saved_object": Object {
@ -47,9 +50,13 @@ describe('#alertAuditEvent', () => {
"error": undefined,
"event": Object {
"action": "alert_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "success",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"saved_object": Object {
@ -77,9 +84,13 @@ describe('#alertAuditEvent', () => {
},
"event": Object {
"action": "alert_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "failure",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"saved_object": Object {

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import { AuditEvent, EventOutcome, EventCategory, EventType } from '../../../security/server';
import { EcsEventOutcome, EcsEventType } from 'src/core/server';
import { AuditEvent } from '../../../security/server';
export enum AlertAuditAction {
CREATE = 'alert_create',
@ -39,24 +40,24 @@ const eventVerbs: Record<AlertAuditAction, VerbsTuple> = {
alert_instance_unmute: ['unmute instance of', 'unmuting instance of', 'unmuted instance of'],
};
const eventTypes: Record<AlertAuditAction, EventType> = {
alert_create: EventType.CREATION,
alert_get: EventType.ACCESS,
alert_update: EventType.CHANGE,
alert_update_api_key: EventType.CHANGE,
alert_enable: EventType.CHANGE,
alert_disable: EventType.CHANGE,
alert_delete: EventType.DELETION,
alert_find: EventType.ACCESS,
alert_mute: EventType.CHANGE,
alert_unmute: EventType.CHANGE,
alert_instance_mute: EventType.CHANGE,
alert_instance_unmute: EventType.CHANGE,
const eventTypes: Record<AlertAuditAction, EcsEventType> = {
alert_create: 'creation',
alert_get: 'access',
alert_update: 'change',
alert_update_api_key: 'change',
alert_enable: 'change',
alert_disable: 'change',
alert_delete: 'deletion',
alert_find: 'access',
alert_mute: 'change',
alert_unmute: 'change',
alert_instance_mute: 'change',
alert_instance_unmute: 'change',
};
export interface AlertAuditEventParams {
action: AlertAuditAction;
outcome?: EventOutcome;
outcome?: EcsEventOutcome;
savedObject?: NonNullable<AuditEvent['kibana']>['saved_object'];
error?: Error;
}
@ -71,7 +72,7 @@ export function alertAuditEvent({
const [present, progressive, past] = eventVerbs[action];
const message = error
? `Failed attempt to ${present} ${doc}`
: outcome === EventOutcome.UNKNOWN
: outcome === 'unknown'
? `User is ${progressive} ${doc}`
: `User has ${past} ${doc}`;
const type = eventTypes[action];
@ -80,9 +81,9 @@ export function alertAuditEvent({
message,
event: {
action,
category: EventCategory.DATABASE,
type,
outcome: outcome ?? (error ? EventOutcome.FAILURE : EventOutcome.SUCCESS),
category: ['database'],
type: type ? [type] : undefined,
outcome: outcome ?? (error ? 'failure' : 'success'),
},
kibana: {
saved_object: savedObject,

View file

@ -252,10 +252,12 @@ describe('7.10.0 migrates with failure', () => {
expect(migrationContext.log.error).toHaveBeenCalledWith(
`encryptedSavedObject 7.10.0 migration failed for alert ${alert.id} with error: Can't migrate!`,
{
alertDocument: {
...alert,
attributes: {
...alert.attributes,
migrations: {
alertDocument: {
...alert,
attributes: {
...alert.attributes,
},
},
},
}

View file

@ -6,6 +6,7 @@
*/
import {
LogMeta,
SavedObjectMigrationMap,
SavedObjectUnsanitizedDoc,
SavedObjectMigrationFn,
@ -20,6 +21,10 @@ const SIEM_APP_ID = 'securitySolution';
const SIEM_SERVER_APP_ID = 'siem';
export const LEGACY_LAST_MODIFIED_VERSION = 'pre-7.10.0';
interface AlertLogMeta extends LogMeta {
migrations: { alertDocument: SavedObjectUnsanitizedDoc<RawAlert> };
}
type AlertMigration = (
doc: SavedObjectUnsanitizedDoc<RawAlert>
) => SavedObjectUnsanitizedDoc<RawAlert>;
@ -84,9 +89,13 @@ function executeMigrationWithErrorHandling(
try {
return migrationFunc(doc, context);
} catch (ex) {
context.log.error(
context.log.error<AlertLogMeta>(
`encryptedSavedObject ${version} migration failed for alert ${doc.id} with error: ${ex.message}`,
{ alertDocument: doc }
{
migrations: {
alertDocument: doc,
},
}
);
}
return doc;

View file

@ -13,9 +13,9 @@ auditLogger.log({
message: 'User is updating dashboard [id=123]',
event: {
action: 'saved_object_update',
category: EventCategory.DATABASE,
type: EventType.CHANGE,
outcome: EventOutcome.UNKNOWN,
category: ['database'],
type: ['change'],
outcome: 'unknown',
},
kibana: {
saved_object: { type: 'dashboard', id: '123' },

View file

@ -12,7 +12,6 @@ import { httpServerMock } from 'src/core/server/mocks';
import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock';
import { AuthenticationResult } from '../authentication';
import {
EventOutcome,
httpRequestEvent,
SavedObjectAction,
savedObjectEvent,
@ -26,7 +25,7 @@ describe('#savedObjectEvent', () => {
expect(
savedObjectEvent({
action: SavedObjectAction.CREATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' },
})
).toMatchInlineSnapshot(`
@ -34,9 +33,13 @@ describe('#savedObjectEvent', () => {
"error": undefined,
"event": Object {
"action": "saved_object_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "unknown",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"add_to_spaces": undefined,
@ -62,9 +65,13 @@ describe('#savedObjectEvent', () => {
"error": undefined,
"event": Object {
"action": "saved_object_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "success",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"add_to_spaces": undefined,
@ -94,9 +101,13 @@ describe('#savedObjectEvent', () => {
},
"event": Object {
"action": "saved_object_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "failure",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"add_to_spaces": undefined,
@ -197,9 +208,13 @@ describe('#savedObjectEvent', () => {
"error": undefined,
"event": Object {
"action": "saved_object_remove_references",
"category": "database",
"category": Array [
"database",
],
"outcome": "success",
"type": "change",
"type": Array [
"change",
],
},
"kibana": Object {
"add_to_spaces": undefined,
@ -228,7 +243,9 @@ describe('#userLoginEvent', () => {
"error": undefined,
"event": Object {
"action": "user_login",
"category": "authentication",
"category": Array [
"authentication",
],
"outcome": "success",
},
"kibana": Object {
@ -264,7 +281,9 @@ describe('#userLoginEvent', () => {
},
"event": Object {
"action": "user_login",
"category": "authentication",
"category": Array [
"authentication",
],
"outcome": "failure",
},
"kibana": Object {
@ -291,7 +310,9 @@ describe('#httpRequestEvent', () => {
Object {
"event": Object {
"action": "http_request",
"category": "web",
"category": Array [
"web",
],
"outcome": "unknown",
},
"http": Object {
@ -328,7 +349,9 @@ describe('#httpRequestEvent', () => {
Object {
"event": Object {
"action": "http_request",
"category": "web",
"category": Array [
"web",
],
"outcome": "unknown",
},
"http": Object {
@ -354,7 +377,7 @@ describe('#spaceAuditEvent', () => {
expect(
spaceAuditEvent({
action: SpaceAuditAction.CREATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'space', id: 'SPACE_ID' },
})
).toMatchInlineSnapshot(`
@ -362,9 +385,13 @@ describe('#spaceAuditEvent', () => {
"error": undefined,
"event": Object {
"action": "space_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "unknown",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"saved_object": Object {
@ -388,9 +415,13 @@ describe('#spaceAuditEvent', () => {
"error": undefined,
"event": Object {
"action": "space_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "success",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"saved_object": Object {
@ -418,9 +449,13 @@ describe('#spaceAuditEvent', () => {
},
"event": Object {
"action": "space_create",
"category": "database",
"category": Array [
"database",
],
"outcome": "failure",
"type": "creation",
"type": Array [
"creation",
],
},
"kibana": Object {
"saved_object": Object {

View file

@ -5,36 +5,20 @@
* 2.0.
*/
import type { KibanaRequest } from 'src/core/server';
import type { EcsEventOutcome, EcsEventType, KibanaRequest, LogMeta } from 'src/core/server';
import type { AuthenticationResult } from '../authentication/authentication_result';
/**
* Audit event schema using ECS format: https://www.elastic.co/guide/en/ecs/1.6/index.html
* Audit event schema using ECS format: https://www.elastic.co/guide/en/ecs/1.9/index.html
*
* If you add additional fields to the schema ensure you update the Kibana Filebeat module:
* https://github.com/elastic/beats/tree/master/filebeat/module/kibana
*
* @public
*/
export interface AuditEvent {
/**
* Human readable message describing action, outcome and user.
*
* @example
* Failed attempt to login using basic provider [name=basic1]
*/
export interface AuditEvent extends LogMeta {
message: string;
event: {
action: string;
category?: EventCategory;
type?: EventType;
outcome?: EventOutcome;
};
user?: {
name: string;
roles?: readonly string[];
};
kibana?: {
/**
* The ID of the space associated with this event.
@ -77,41 +61,6 @@ export interface AuditEvent {
*/
delete_from_spaces?: readonly string[];
};
error?: {
code?: string;
message?: string;
};
http?: {
request?: {
method?: string;
};
};
url?: {
domain?: string;
path?: string;
port?: number;
query?: string;
scheme?: string;
};
}
export enum EventCategory {
DATABASE = 'database',
WEB = 'web',
AUTHENTICATION = 'authentication',
}
export enum EventType {
CREATION = 'creation',
ACCESS = 'access',
CHANGE = 'change',
DELETION = 'deletion',
}
export enum EventOutcome {
SUCCESS = 'success',
FAILURE = 'failure',
UNKNOWN = 'unknown',
}
export interface HttpRequestParams {
@ -125,8 +74,8 @@ export function httpRequestEvent({ request }: HttpRequestParams): AuditEvent {
message: `User is requesting [${url.pathname}] endpoint`,
event: {
action: 'http_request',
category: EventCategory.WEB,
outcome: EventOutcome.UNKNOWN,
category: ['web'],
outcome: 'unknown',
},
http: {
request: {
@ -160,12 +109,12 @@ export function userLoginEvent({
: `Failed attempt to login using ${authenticationType} provider [name=${authenticationProvider}]`,
event: {
action: 'user_login',
category: EventCategory.AUTHENTICATION,
outcome: authenticationResult.user ? EventOutcome.SUCCESS : EventOutcome.FAILURE,
category: ['authentication'],
outcome: authenticationResult.user ? 'success' : 'failure',
},
user: authenticationResult.user && {
name: authenticationResult.user.username,
roles: authenticationResult.user.roles,
roles: authenticationResult.user.roles as string[],
},
kibana: {
space_id: undefined, // Ensure this does not get populated by audit service
@ -223,23 +172,23 @@ const savedObjectAuditVerbs: Record<SavedObjectAction, VerbsTuple> = {
],
};
const savedObjectAuditTypes: Record<SavedObjectAction, EventType> = {
saved_object_create: EventType.CREATION,
saved_object_get: EventType.ACCESS,
saved_object_resolve: EventType.ACCESS,
saved_object_update: EventType.CHANGE,
saved_object_delete: EventType.DELETION,
saved_object_find: EventType.ACCESS,
saved_object_add_to_spaces: EventType.CHANGE,
saved_object_delete_from_spaces: EventType.CHANGE,
saved_object_open_point_in_time: EventType.CREATION,
saved_object_close_point_in_time: EventType.DELETION,
saved_object_remove_references: EventType.CHANGE,
const savedObjectAuditTypes: Record<SavedObjectAction, EcsEventType> = {
saved_object_create: 'creation',
saved_object_get: 'access',
saved_object_resolve: 'access',
saved_object_update: 'change',
saved_object_delete: 'deletion',
saved_object_find: 'access',
saved_object_add_to_spaces: 'change',
saved_object_delete_from_spaces: 'change',
saved_object_open_point_in_time: 'creation',
saved_object_close_point_in_time: 'deletion',
saved_object_remove_references: 'change',
};
export interface SavedObjectEventParams {
action: SavedObjectAction;
outcome?: EventOutcome;
outcome?: EcsEventOutcome;
savedObject?: NonNullable<AuditEvent['kibana']>['saved_object'];
addToSpaces?: readonly string[];
deleteFromSpaces?: readonly string[];
@ -258,13 +207,13 @@ export function savedObjectEvent({
const [present, progressive, past] = savedObjectAuditVerbs[action];
const message = error
? `Failed attempt to ${present} ${doc}`
: outcome === EventOutcome.UNKNOWN
: outcome === 'unknown'
? `User is ${progressive} ${doc}`
: `User has ${past} ${doc}`;
const type = savedObjectAuditTypes[action];
if (
type === EventType.ACCESS &&
type === 'access' &&
savedObject &&
(savedObject.type === 'config' || savedObject.type === 'telemetry')
) {
@ -275,9 +224,9 @@ export function savedObjectEvent({
message,
event: {
action,
category: EventCategory.DATABASE,
type,
outcome: outcome ?? (error ? EventOutcome.FAILURE : EventOutcome.SUCCESS),
category: ['database'],
type: [type],
outcome: outcome ?? (error ? 'failure' : 'success'),
},
kibana: {
saved_object: savedObject,
@ -307,17 +256,17 @@ const spaceAuditVerbs: Record<SpaceAuditAction, VerbsTuple> = {
space_find: ['access', 'accessing', 'accessed'],
};
const spaceAuditTypes: Record<SpaceAuditAction, EventType> = {
space_create: EventType.CREATION,
space_get: EventType.ACCESS,
space_update: EventType.CHANGE,
space_delete: EventType.DELETION,
space_find: EventType.ACCESS,
const spaceAuditTypes: Record<SpaceAuditAction, EcsEventType> = {
space_create: 'creation',
space_get: 'access',
space_update: 'change',
space_delete: 'deletion',
space_find: 'access',
};
export interface SpacesAuditEventParams {
action: SpaceAuditAction;
outcome?: EventOutcome;
outcome?: EcsEventOutcome;
savedObject?: NonNullable<AuditEvent['kibana']>['saved_object'];
error?: Error;
}
@ -332,7 +281,7 @@ export function spaceAuditEvent({
const [present, progressive, past] = spaceAuditVerbs[action];
const message = error
? `Failed attempt to ${present} ${doc}`
: outcome === EventOutcome.UNKNOWN
: outcome === 'unknown'
? `User is ${progressive} ${doc}`
: `User has ${past} ${doc}`;
const type = spaceAuditTypes[action];
@ -341,9 +290,9 @@ export function spaceAuditEvent({
message,
event: {
action,
category: EventCategory.DATABASE,
type,
outcome: outcome ?? (error ? EventOutcome.FAILURE : EventOutcome.SUCCESS),
category: ['database'],
type: [type],
outcome: outcome ?? (error ? 'failure' : 'success'),
},
kibana: {
saved_object: savedObject,

View file

@ -19,7 +19,6 @@ import { licenseMock } from '../../common/licensing/index.mock';
import type { ConfigType } from '../config';
import { ConfigSchema } from '../config';
import type { AuditEvent } from './audit_events';
import { EventCategory, EventOutcome, EventType } from './audit_events';
import {
AuditService,
createLoggingConfig,
@ -185,10 +184,8 @@ describe('#asScoped', () => {
await auditSetup.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } });
expect(logger.info).toHaveBeenCalledWith('MESSAGE', {
ecs: { version: '1.6.0' },
event: { action: 'ACTION' },
kibana: { space_id: 'default', session_id: 'SESSION_ID' },
message: 'MESSAGE',
trace: { id: 'REQUEST_ID' },
user: { name: 'jdoe', roles: ['admin'] },
});
@ -349,21 +346,25 @@ describe('#createLoggingConfig', () => {
});
describe('#filterEvent', () => {
const event: AuditEvent = {
message: 'this is my audit message',
event: {
action: 'http_request',
category: EventCategory.WEB,
type: EventType.ACCESS,
outcome: EventOutcome.SUCCESS,
},
user: {
name: 'jdoe',
},
kibana: {
space_id: 'default',
},
};
let event: AuditEvent;
beforeEach(() => {
event = {
message: 'this is my audit message',
event: {
action: 'http_request',
category: ['web'],
type: ['access'],
outcome: 'success',
},
user: {
name: 'jdoe',
},
kibana: {
space_id: 'default',
},
};
});
test('keeps event when ignore filters are undefined or empty', () => {
expect(filterEvent(event, undefined)).toBeTruthy();
@ -421,6 +422,66 @@ describe('#filterEvent', () => {
).toBeTruthy();
});
test('keeps event when one item per category does not match', () => {
event = {
message: 'this is my audit message',
event: {
action: 'http_request',
category: ['authentication', 'web'],
type: ['access'],
outcome: 'success',
},
user: {
name: 'jdoe',
},
kibana: {
space_id: 'default',
},
};
expect(
filterEvent(event, [
{
actions: ['http_request'],
categories: ['web', 'NO_MATCH'],
types: ['access'],
outcomes: ['success'],
spaces: ['default'],
},
])
).toBeTruthy();
});
test('keeps event when one item per type does not match', () => {
event = {
message: 'this is my audit message',
event: {
action: 'http_request',
category: ['web'],
type: ['access', 'user'],
outcome: 'success',
},
user: {
name: 'jdoe',
},
kibana: {
space_id: 'default',
},
};
expect(
filterEvent(event, [
{
actions: ['http_request'],
categories: ['web'],
types: ['access', 'NO_MATCH'],
outcomes: ['success'],
spaces: ['default'],
},
])
).toBeTruthy();
});
test('filters out event when all criteria in a single rule match', () => {
expect(
filterEvent(event, [
@ -441,6 +502,66 @@ describe('#filterEvent', () => {
])
).toBeFalsy();
});
test('filters out event when all categories match', () => {
event = {
message: 'this is my audit message',
event: {
action: 'http_request',
category: ['authentication', 'web'],
type: ['access'],
outcome: 'success',
},
user: {
name: 'jdoe',
},
kibana: {
space_id: 'default',
},
};
expect(
filterEvent(event, [
{
actions: ['http_request'],
categories: ['authentication', 'web'],
types: ['access'],
outcomes: ['success'],
spaces: ['default'],
},
])
).toBeFalsy();
});
test('filters out event when all types match', () => {
event = {
message: 'this is my audit message',
event: {
action: 'http_request',
category: ['web'],
type: ['access', 'user'],
outcome: 'success',
},
user: {
name: 'jdoe',
},
kibana: {
space_id: 'default',
},
};
expect(
filterEvent(event, [
{
actions: ['http_request'],
categories: ['web'],
types: ['access', 'user'],
outcomes: ['success'],
spaces: ['default'],
},
])
).toBeFalsy();
});
});
describe('#getLogger', () => {

View file

@ -37,15 +37,6 @@ export interface AuditLogger {
log: (event: AuditEvent | undefined) => void;
}
interface AuditLogMeta extends AuditEvent {
ecs: {
version: string;
};
trace: {
id: string;
};
}
export interface AuditServiceSetup {
asScoped: (request: KibanaRequest) => AuditLogger;
getLogger: (id?: string) => LegacyAuditLogger;
@ -146,7 +137,7 @@ export class AuditService {
* message: 'User is updating dashboard [id=123]',
* event: {
* action: 'saved_object_update',
* outcome: EventOutcome.UNKNOWN
* outcome: 'unknown'
* },
* kibana: {
* saved_object: { type: 'dashboard', id: '123' }
@ -161,13 +152,12 @@ export class AuditService {
const spaceId = getSpaceId(request);
const user = getCurrentUser(request);
const sessionId = await getSID(request);
const meta: AuditLogMeta = {
ecs: { version: ECS_VERSION },
const meta: AuditEvent = {
...event,
user:
(user && {
name: user.username,
roles: user.roles,
roles: user.roles as string[],
}) ||
event.user,
kibana: {
@ -178,7 +168,8 @@ export class AuditService {
trace: { id: request.id },
};
if (filterEvent(meta, config.ignore_filters)) {
this.ecsLogger.info(event.message!, meta);
const { message, ...eventMeta } = meta;
this.ecsLogger.info(message, eventMeta);
}
};
return { log };
@ -243,6 +234,13 @@ export const createLoggingConfig = (config: ConfigType['audit']) =>
],
}));
/**
* Evaluates the list of provided ignore rules, and filters out events only
* if *all* rules match the event.
*
* For event fields that can contain an array of multiple values, every value
* must be matched by an ignore rule for the event to be excluded.
*/
export function filterEvent(
event: AuditEvent,
ignoreFilters: ConfigType['audit']['ignore_filters']
@ -250,10 +248,10 @@ export function filterEvent(
if (ignoreFilters) {
return !ignoreFilters.some(
(rule) =>
(!rule.actions || rule.actions.includes(event.event.action)) &&
(!rule.categories || rule.categories.includes(event.event.category!)) &&
(!rule.types || rule.types.includes(event.event.type!)) &&
(!rule.outcomes || rule.outcomes.includes(event.event.outcome!)) &&
(!rule.actions || rule.actions.includes(event.event?.action!)) &&
(!rule.categories || event.event?.category?.every((c) => rule.categories?.includes(c))) &&
(!rule.types || event.event?.type?.every((t) => rule.types?.includes(t))) &&
(!rule.outcomes || rule.outcomes.includes(event.event?.outcome!)) &&
(!rule.spaces || rule.spaces.includes(event.kibana?.space_id!))
);
}

View file

@ -8,9 +8,6 @@
export { AuditService, AuditServiceSetup, AuditLogger, LegacyAuditLogger } from './audit_service';
export {
AuditEvent,
EventCategory,
EventType,
EventOutcome,
userLoginEvent,
httpRequestEvent,
savedObjectEvent,

View file

@ -337,7 +337,7 @@ describe('Authenticator', () => {
expect(auditLogger.log).toHaveBeenCalledTimes(1);
expect(auditLogger.log).toHaveBeenCalledWith(
expect.objectContaining({
event: { action: 'user_login', category: 'authentication', outcome: 'success' },
event: { action: 'user_login', category: ['authentication'], outcome: 'success' },
})
);
});
@ -353,7 +353,7 @@ describe('Authenticator', () => {
expect(auditLogger.log).toHaveBeenCalledTimes(1);
expect(auditLogger.log).toHaveBeenCalledWith(
expect.objectContaining({
event: { action: 'user_login', category: 'authentication', outcome: 'failure' },
event: { action: 'user_login', category: ['authentication'], outcome: 'failure' },
})
);
});

View file

@ -27,14 +27,7 @@ export type {
GrantAPIKeyResult,
} from './authentication';
export type { CheckPrivilegesPayload } from './authorization';
export {
LegacyAuditLogger,
AuditLogger,
AuditEvent,
EventCategory,
EventType,
EventOutcome,
} from './audit';
export { LegacyAuditLogger, AuditLogger, AuditEvent } from './audit';
export type { SecurityPluginSetup, SecurityPluginStart };
export type { AuthenticatedUser } from '../common/model';

View file

@ -5,11 +5,10 @@
* 2.0.
*/
import type { SavedObjectsClientContract } from 'src/core/server';
import type { EcsEventOutcome, SavedObjectsClientContract } from 'src/core/server';
import { httpServerMock, savedObjectsClientMock } from 'src/core/server/mocks';
import type { AuditEvent } from '../audit';
import { EventOutcome } from '../audit';
import { auditServiceMock, securityAuditLoggerMock } from '../audit/index.mock';
import { Actions } from '../authorization';
import type { SavedObjectActions } from '../authorization/actions/saved_object';
@ -199,8 +198,8 @@ const expectObjectNamespaceFiltering = async (
};
const expectAuditEvent = (
action: AuditEvent['event']['action'],
outcome: AuditEvent['event']['outcome'],
action: string,
outcome: EcsEventOutcome,
savedObject?: Required<AuditEvent>['kibana']['saved_object']
) => {
expect(clientOpts.auditLogger.log).toHaveBeenCalledWith(
@ -445,14 +444,14 @@ describe('#addToNamespaces', () => {
await client.addToNamespaces(type, id, namespaces);
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_add_to_spaces', EventOutcome.UNKNOWN, { type, id });
expectAuditEvent('saved_object_add_to_spaces', 'unknown', { type, id });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.addToNamespaces(type, id, namespaces)).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_add_to_spaces', EventOutcome.FAILURE, { type, id });
expectAuditEvent('saved_object_add_to_spaces', 'failure', { type, id });
});
});
@ -515,16 +514,16 @@ describe('#bulkCreate', () => {
const options = { namespace };
await expectSuccess(client.bulkCreate, { objects, options });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2);
expectAuditEvent('saved_object_create', EventOutcome.UNKNOWN, { type: obj1.type, id: obj1.id });
expectAuditEvent('saved_object_create', EventOutcome.UNKNOWN, { type: obj2.type, id: obj2.id });
expectAuditEvent('saved_object_create', 'unknown', { type: obj1.type, id: obj1.id });
expectAuditEvent('saved_object_create', 'unknown', { type: obj2.type, id: obj2.id });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.bulkCreate([obj1, obj2], { namespace })).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2);
expectAuditEvent('saved_object_create', EventOutcome.FAILURE, { type: obj1.type, id: obj1.id });
expectAuditEvent('saved_object_create', EventOutcome.FAILURE, { type: obj2.type, id: obj2.id });
expectAuditEvent('saved_object_create', 'failure', { type: obj1.type, id: obj1.id });
expectAuditEvent('saved_object_create', 'failure', { type: obj2.type, id: obj2.id });
});
});
@ -573,16 +572,16 @@ describe('#bulkGet', () => {
const options = { namespace };
await expectSuccess(client.bulkGet, { objects, options });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2);
expectAuditEvent('saved_object_get', EventOutcome.SUCCESS, obj1);
expectAuditEvent('saved_object_get', EventOutcome.SUCCESS, obj2);
expectAuditEvent('saved_object_get', 'success', obj1);
expectAuditEvent('saved_object_get', 'success', obj2);
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.bulkGet([obj1, obj2], { namespace })).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2);
expectAuditEvent('saved_object_get', EventOutcome.FAILURE, obj1);
expectAuditEvent('saved_object_get', EventOutcome.FAILURE, obj2);
expectAuditEvent('saved_object_get', 'failure', obj1);
expectAuditEvent('saved_object_get', 'failure', obj2);
});
});
@ -642,16 +641,16 @@ describe('#bulkUpdate', () => {
const options = { namespace };
await expectSuccess(client.bulkUpdate, { objects, options });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2);
expectAuditEvent('saved_object_update', EventOutcome.UNKNOWN, { type: obj1.type, id: obj1.id });
expectAuditEvent('saved_object_update', EventOutcome.UNKNOWN, { type: obj2.type, id: obj2.id });
expectAuditEvent('saved_object_update', 'unknown', { type: obj1.type, id: obj1.id });
expectAuditEvent('saved_object_update', 'unknown', { type: obj2.type, id: obj2.id });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.bulkUpdate<any>([obj1, obj2], { namespace })).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2);
expectAuditEvent('saved_object_update', EventOutcome.FAILURE, { type: obj1.type, id: obj1.id });
expectAuditEvent('saved_object_update', EventOutcome.FAILURE, { type: obj2.type, id: obj2.id });
expectAuditEvent('saved_object_update', 'failure', { type: obj1.type, id: obj1.id });
expectAuditEvent('saved_object_update', 'failure', { type: obj2.type, id: obj2.id });
});
});
@ -744,14 +743,14 @@ describe('#create', () => {
const options = { id: 'mock-saved-object-id', namespace };
await expectSuccess(client.create, { type, attributes, options });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_create', EventOutcome.UNKNOWN, { type, id: expect.any(String) });
expectAuditEvent('saved_object_create', 'unknown', { type, id: expect.any(String) });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.create(type, attributes, { namespace })).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_create', EventOutcome.FAILURE, { type, id: expect.any(String) });
expectAuditEvent('saved_object_create', 'failure', { type, id: expect.any(String) });
});
});
@ -789,14 +788,14 @@ describe('#delete', () => {
const options = { namespace };
await expectSuccess(client.delete, { type, id, options });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_delete', EventOutcome.UNKNOWN, { type, id });
expectAuditEvent('saved_object_delete', 'unknown', { type, id });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.delete(type, id)).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_delete', EventOutcome.FAILURE, { type, id });
expectAuditEvent('saved_object_delete', 'failure', { type, id });
});
});
@ -936,8 +935,8 @@ describe('#find', () => {
const options = Object.freeze({ type: type1, namespaces: ['some-ns'] });
await expectSuccess(client.find, { options });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2);
expectAuditEvent('saved_object_find', EventOutcome.SUCCESS, obj1);
expectAuditEvent('saved_object_find', EventOutcome.SUCCESS, obj2);
expectAuditEvent('saved_object_find', 'success', obj1);
expectAuditEvent('saved_object_find', 'success', obj2);
});
test(`adds audit event when not successful`, async () => {
@ -946,7 +945,7 @@ describe('#find', () => {
);
await client.find({ type: type1 });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_find', EventOutcome.FAILURE);
expectAuditEvent('saved_object_find', 'failure');
});
});
@ -989,14 +988,14 @@ describe('#get', () => {
const options = { namespace };
await expectSuccess(client.get, { type, id, options });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_get', EventOutcome.SUCCESS, { type, id });
expectAuditEvent('saved_object_get', 'success', { type, id });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.get(type, id, { namespace })).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_get', EventOutcome.FAILURE, { type, id });
expectAuditEvent('saved_object_get', 'failure', { type, id });
});
});
@ -1023,14 +1022,14 @@ describe('#openPointInTimeForType', () => {
const options = { namespace };
await expectSuccess(client.openPointInTimeForType, { type, options });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_open_point_in_time', EventOutcome.UNKNOWN);
expectAuditEvent('saved_object_open_point_in_time', 'unknown');
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.openPointInTimeForType(type, { namespace })).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_open_point_in_time', EventOutcome.FAILURE);
expectAuditEvent('saved_object_open_point_in_time', 'failure');
});
});
@ -1054,7 +1053,7 @@ describe('#closePointInTime', () => {
const options = { namespace };
await client.closePointInTime(id, options);
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_close_point_in_time', EventOutcome.UNKNOWN);
expectAuditEvent('saved_object_close_point_in_time', 'unknown');
});
});
@ -1153,14 +1152,14 @@ describe('#resolve', () => {
const options = { namespace };
await expectSuccess(client.resolve, { type, id, options }, 'resolve');
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_resolve', EventOutcome.SUCCESS, { type, id: resolvedId });
expectAuditEvent('saved_object_resolve', 'success', { type, id: resolvedId });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.resolve(type, id, { namespace })).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_resolve', EventOutcome.FAILURE, { type, id });
expectAuditEvent('saved_object_resolve', 'failure', { type, id });
});
});
@ -1239,14 +1238,14 @@ describe('#deleteFromNamespaces', () => {
clientOpts.baseClient.deleteFromNamespaces.mockReturnValue(apiCallReturnValue as any);
await client.deleteFromNamespaces(type, id, namespaces);
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_delete_from_spaces', EventOutcome.UNKNOWN, { type, id });
expectAuditEvent('saved_object_delete_from_spaces', 'unknown', { type, id });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.deleteFromNamespaces(type, id, namespaces)).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_delete_from_spaces', EventOutcome.FAILURE, { type, id });
expectAuditEvent('saved_object_delete_from_spaces', 'failure', { type, id });
});
});
@ -1290,14 +1289,14 @@ describe('#update', () => {
const options = { namespace };
await expectSuccess(client.update, { type, id, attributes, options });
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_update', EventOutcome.UNKNOWN, { type, id });
expectAuditEvent('saved_object_update', 'unknown', { type, id });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.update(type, id, attributes, { namespace })).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_update', EventOutcome.FAILURE, { type, id });
expectAuditEvent('saved_object_update', 'failure', { type, id });
});
});
@ -1341,14 +1340,14 @@ describe('#removeReferencesTo', () => {
await client.removeReferencesTo(type, id);
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_remove_references', EventOutcome.UNKNOWN, { type, id });
expectAuditEvent('saved_object_remove_references', 'unknown', { type, id });
});
test(`adds audit event when not successful`, async () => {
clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error());
await expect(() => client.removeReferencesTo(type, id)).rejects.toThrow();
expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1);
expectAuditEvent('saved_object_remove_references', EventOutcome.FAILURE, { type, id });
expectAuditEvent('saved_object_remove_references', 'failure', { type, id });
});
});

View file

@ -28,7 +28,7 @@ import type {
import { SavedObjectsUtils } from '../../../../../src/core/server';
import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../common/constants';
import type { AuditLogger, SecurityAuditLogger } from '../audit';
import { EventOutcome, SavedObjectAction, savedObjectEvent } from '../audit';
import { SavedObjectAction, savedObjectEvent } from '../audit';
import type { Actions, CheckSavedObjectsPrivileges } from '../authorization';
import type { CheckPrivilegesResponse } from '../authorization/types';
import type { SpacesService } from '../plugin';
@ -116,7 +116,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
this.auditLogger.log(
savedObjectEvent({
action: SavedObjectAction.CREATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type, id: optionsWithId.id },
})
);
@ -178,7 +178,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
this.auditLogger.log(
savedObjectEvent({
action: SavedObjectAction.CREATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type, id },
})
)
@ -205,7 +205,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
this.auditLogger.log(
savedObjectEvent({
action: SavedObjectAction.DELETE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type, id },
})
);
@ -400,7 +400,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
this.auditLogger.log(
savedObjectEvent({
action: SavedObjectAction.UPDATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type, id },
})
);
@ -446,7 +446,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
this.auditLogger.log(
savedObjectEvent({
action: SavedObjectAction.ADD_TO_SPACES,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type, id },
addToSpaces: namespaces,
})
@ -483,7 +483,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
this.auditLogger.log(
savedObjectEvent({
action: SavedObjectAction.DELETE_FROM_SPACES,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type, id },
deleteFromSpaces: namespaces,
})
@ -524,7 +524,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
this.auditLogger.log(
savedObjectEvent({
action: SavedObjectAction.UPDATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type, id },
})
)
@ -560,7 +560,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
savedObjectEvent({
action: SavedObjectAction.REMOVE_REFERENCES,
savedObject: { type, id },
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
})
);
@ -592,7 +592,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
this.auditLogger.log(
savedObjectEvent({
action: SavedObjectAction.OPEN_POINT_IN_TIME,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
})
);
@ -611,7 +611,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
this.auditLogger.log(
savedObjectEvent({
action: SavedObjectAction.CLOSE_POINT_IN_TIME,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
})
);

View file

@ -6,13 +6,14 @@
*/
import { deepFreeze } from '@kbn/std';
import type { EcsEventOutcome } from 'src/core/server';
import { SavedObjectsErrorHelpers } from 'src/core/server';
import { httpServerMock } from 'src/core/server/mocks';
import type { GetAllSpacesPurpose, Space } from '../../../spaces/server';
import { spacesClientMock } from '../../../spaces/server/mocks';
import type { AuditEvent, AuditLogger } from '../audit';
import { EventOutcome, SpaceAuditAction } from '../audit';
import { SpaceAuditAction } from '../audit';
import { auditServiceMock } from '../audit/index.mock';
import type { AuthorizationServiceSetup } from '../authorization';
import { authorizationMock } from '../authorization/index.mock';
@ -135,8 +136,8 @@ const expectSuccessAuditLogging = (
const expectAuditEvent = (
auditLogger: AuditLogger,
action: AuditEvent['event']['action'],
outcome: AuditEvent['event']['outcome'],
action: string,
outcome: EcsEventOutcome,
savedObject?: Required<AuditEvent>['kibana']['saved_object']
) => {
expect(auditLogger.log).toHaveBeenCalledWith(
@ -194,15 +195,15 @@ describe('SecureSpacesClientWrapper', () => {
expect(response).toEqual(spaces);
expectNoAuthorizationCheck(authorization);
expectNoAuditLogging(legacyAuditLogger);
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.SUCCESS, {
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', {
type: 'space',
id: spaces[0].id,
});
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.SUCCESS, {
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', {
type: 'space',
id: spaces[1].id,
});
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.SUCCESS, {
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', {
type: 'space',
id: spaces[2].id,
});
@ -285,7 +286,7 @@ describe('SecureSpacesClientWrapper', () => {
);
expectForbiddenAuditLogging(legacyAuditLogger, username, 'getAll');
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.FAILURE);
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'failure');
});
test(`returns spaces that the user is authorized for`, async () => {
@ -330,7 +331,7 @@ describe('SecureSpacesClientWrapper', () => {
);
expectSuccessAuditLogging(legacyAuditLogger, username, 'getAll', [spaces[0].id]);
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.SUCCESS, {
expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', {
type: 'space',
id: spaces[0].id,
});
@ -351,7 +352,7 @@ describe('SecureSpacesClientWrapper', () => {
expect(response).toEqual(spaces[0]);
expectNoAuthorizationCheck(authorization);
expectNoAuditLogging(legacyAuditLogger);
expectAuditEvent(auditLogger, SpaceAuditAction.GET, EventOutcome.SUCCESS, {
expectAuditEvent(auditLogger, SpaceAuditAction.GET, 'success', {
type: 'space',
id: spaces[0].id,
});
@ -392,7 +393,7 @@ describe('SecureSpacesClientWrapper', () => {
});
expectForbiddenAuditLogging(legacyAuditLogger, username, 'get', spaceId);
expectAuditEvent(auditLogger, SpaceAuditAction.GET, EventOutcome.FAILURE, {
expectAuditEvent(auditLogger, SpaceAuditAction.GET, 'failure', {
type: 'space',
id: spaces[0].id,
});
@ -432,7 +433,7 @@ describe('SecureSpacesClientWrapper', () => {
});
expectSuccessAuditLogging(legacyAuditLogger, username, 'get', [spaceId]);
expectAuditEvent(auditLogger, SpaceAuditAction.GET, EventOutcome.SUCCESS, {
expectAuditEvent(auditLogger, SpaceAuditAction.GET, 'success', {
type: 'space',
id: spaceId,
});
@ -457,7 +458,7 @@ describe('SecureSpacesClientWrapper', () => {
expect(response).toEqual(space);
expectNoAuthorizationCheck(authorization);
expectNoAuditLogging(legacyAuditLogger);
expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, EventOutcome.UNKNOWN, {
expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, 'unknown', {
type: 'space',
id: space.id,
});
@ -495,7 +496,7 @@ describe('SecureSpacesClientWrapper', () => {
});
expectForbiddenAuditLogging(legacyAuditLogger, username, 'create');
expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, EventOutcome.FAILURE, {
expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, 'failure', {
type: 'space',
id: space.id,
});
@ -534,7 +535,7 @@ describe('SecureSpacesClientWrapper', () => {
});
expectSuccessAuditLogging(legacyAuditLogger, username, 'create');
expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, EventOutcome.UNKNOWN, {
expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, 'unknown', {
type: 'space',
id: space.id,
});
@ -559,7 +560,7 @@ describe('SecureSpacesClientWrapper', () => {
expect(response).toEqual(space.id);
expectNoAuthorizationCheck(authorization);
expectNoAuditLogging(legacyAuditLogger);
expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, EventOutcome.UNKNOWN, {
expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, 'unknown', {
type: 'space',
id: space.id,
});
@ -597,7 +598,7 @@ describe('SecureSpacesClientWrapper', () => {
});
expectForbiddenAuditLogging(legacyAuditLogger, username, 'update');
expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, EventOutcome.FAILURE, {
expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, 'failure', {
type: 'space',
id: space.id,
});
@ -636,7 +637,7 @@ describe('SecureSpacesClientWrapper', () => {
});
expectSuccessAuditLogging(legacyAuditLogger, username, 'update');
expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, EventOutcome.UNKNOWN, {
expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, 'unknown', {
type: 'space',
id: space.id,
});
@ -660,7 +661,7 @@ describe('SecureSpacesClientWrapper', () => {
expect(baseClient.delete).toHaveBeenCalledWith(space.id);
expectNoAuthorizationCheck(authorization);
expectNoAuditLogging(legacyAuditLogger);
expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, EventOutcome.UNKNOWN, {
expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, 'unknown', {
type: 'space',
id: space.id,
});
@ -698,7 +699,7 @@ describe('SecureSpacesClientWrapper', () => {
});
expectForbiddenAuditLogging(legacyAuditLogger, username, 'delete');
expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, EventOutcome.FAILURE, {
expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, 'failure', {
type: 'space',
id: space.id,
});
@ -735,7 +736,7 @@ describe('SecureSpacesClientWrapper', () => {
});
expectSuccessAuditLogging(legacyAuditLogger, username, 'delete');
expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, EventOutcome.UNKNOWN, {
expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, 'unknown', {
type: 'space',
id: space.id,
});

View file

@ -17,7 +17,7 @@ import type {
Space,
} from '../../../spaces/server';
import type { AuditLogger } from '../audit';
import { EventOutcome, SpaceAuditAction, spaceAuditEvent } from '../audit';
import { SpaceAuditAction, spaceAuditEvent } from '../audit';
import type { AuthorizationServiceSetup } from '../authorization';
import type { SecurityPluginSetup } from '../plugin';
import type { LegacySpacesAuditLogger } from './legacy_audit_logger';
@ -207,7 +207,7 @@ export class SecureSpacesClientWrapper implements ISpacesClient {
this.auditLogger.log(
spaceAuditEvent({
action: SpaceAuditAction.CREATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'space', id: space.id },
})
);
@ -238,7 +238,7 @@ export class SecureSpacesClientWrapper implements ISpacesClient {
this.auditLogger.log(
spaceAuditEvent({
action: SpaceAuditAction.UPDATE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'space', id },
})
);
@ -269,7 +269,7 @@ export class SecureSpacesClientWrapper implements ISpacesClient {
this.auditLogger.log(
spaceAuditEvent({
action: SpaceAuditAction.DELETE,
outcome: EventOutcome.UNKNOWN,
outcome: 'unknown',
savedObject: { type: 'space', id },
})
);