* create kbn-legacy-logging package and start to move things * fix rotator tests * fix logging system test mocks * move logging format to the package * move logging setup to package * adapt legacy logging server * remove usage of legacy config in the legacy logging server * move legacy logging server to package * remove `??` syntax from package * update generated doc * fix a few things due to month old merge * remove typings from project * move reconfigureLogging to package * add basic README file * update generated doc * remove old typings * add typing for legacy logging events * remove `??` from packages * fix / improve event types usages * remove suffix from tsconfig
174 lines
5.6 KiB
TypeScript
174 lines
5.6 KiB
TypeScript
/*
|
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
|
* license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright
|
|
* ownership. Elasticsearch B.V. licenses this file to you under
|
|
* the Apache License, Version 2.0 (the "License"); you may
|
|
* not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
import Stream from 'stream';
|
|
import moment from 'moment-timezone';
|
|
import _ from 'lodash';
|
|
import queryString from 'query-string';
|
|
import numeral from '@elastic/numeral';
|
|
import chalk from 'chalk';
|
|
// @ts-expect-error missing type def
|
|
import stringify from 'json-stringify-safe';
|
|
import { inspect } from 'util';
|
|
|
|
import { applyFiltersToKeys } from './utils';
|
|
import { getLogEventData } from './metadata';
|
|
import { LegacyLoggingConfig } from './schema';
|
|
import {
|
|
AnyEvent,
|
|
isResponseEvent,
|
|
isOpsEvent,
|
|
isErrorEvent,
|
|
isLogEvent,
|
|
isUndeclaredErrorEvent,
|
|
} from './log_events';
|
|
|
|
export type LogFormatConfig = Pick<LegacyLoggingConfig, 'json' | 'dest' | 'timezone' | 'filter'>;
|
|
|
|
function serializeError(err: any = {}) {
|
|
return {
|
|
message: err.message,
|
|
name: err.name,
|
|
stack: err.stack,
|
|
code: err.code,
|
|
signal: err.signal,
|
|
};
|
|
}
|
|
|
|
const levelColor = function (code: number) {
|
|
if (code < 299) return chalk.green(String(code));
|
|
if (code < 399) return chalk.yellow(String(code));
|
|
if (code < 499) return chalk.magentaBright(String(code));
|
|
return chalk.red(String(code));
|
|
};
|
|
|
|
export abstract class BaseLogFormat extends Stream.Transform {
|
|
constructor(private readonly config: LogFormatConfig) {
|
|
super({
|
|
readableObjectMode: false,
|
|
writableObjectMode: true,
|
|
});
|
|
}
|
|
|
|
abstract format(data: Record<string, any>): string;
|
|
|
|
filter(data: Record<string, unknown>) {
|
|
if (!this.config.filter) {
|
|
return data;
|
|
}
|
|
return applyFiltersToKeys(data, this.config.filter);
|
|
}
|
|
|
|
_transform(event: AnyEvent, enc: string, next: Stream.TransformCallback) {
|
|
const data = this.filter(this.readEvent(event));
|
|
this.push(this.format(data) + '\n');
|
|
next();
|
|
}
|
|
|
|
extractAndFormatTimestamp(data: Record<string, any>, format?: string) {
|
|
const { timezone } = this.config;
|
|
const date = moment(data['@timestamp']);
|
|
if (timezone) {
|
|
date.tz(timezone);
|
|
}
|
|
return date.format(format);
|
|
}
|
|
|
|
readEvent(event: AnyEvent) {
|
|
const data: Record<string, any> = {
|
|
type: event.event,
|
|
'@timestamp': event.timestamp,
|
|
tags: [...(event.tags || [])],
|
|
pid: event.pid,
|
|
};
|
|
|
|
if (isResponseEvent(event)) {
|
|
_.defaults(data, _.pick(event, ['method', 'statusCode']));
|
|
|
|
const source = _.get(event, 'source', {});
|
|
data.req = {
|
|
url: event.path,
|
|
method: event.method || '',
|
|
headers: event.headers,
|
|
remoteAddress: source.remoteAddress,
|
|
userAgent: source.userAgent,
|
|
referer: source.referer,
|
|
};
|
|
|
|
const contentLength =
|
|
event.responsePayload === 'object'
|
|
? stringify(event.responsePayload).length
|
|
: String(event.responsePayload).length;
|
|
|
|
data.res = {
|
|
statusCode: event.statusCode,
|
|
responseTime: event.responseTime,
|
|
contentLength,
|
|
};
|
|
|
|
const query = queryString.stringify(event.query, { sort: false });
|
|
if (query) {
|
|
data.req.url += '?' + query;
|
|
}
|
|
|
|
data.message = data.req.method.toUpperCase() + ' ';
|
|
data.message += data.req.url;
|
|
data.message += ' ';
|
|
data.message += levelColor(data.res.statusCode);
|
|
data.message += ' ';
|
|
data.message += chalk.gray(data.res.responseTime + 'ms');
|
|
data.message += chalk.gray(' - ' + numeral(contentLength).format('0.0b'));
|
|
} else if (isOpsEvent(event)) {
|
|
_.defaults(data, _.pick(event, ['pid', 'os', 'proc', 'load']));
|
|
data.message = chalk.gray('memory: ');
|
|
data.message += numeral(_.get(data, 'proc.mem.heapUsed')).format('0.0b');
|
|
data.message += ' ';
|
|
data.message += chalk.gray('uptime: ');
|
|
data.message += numeral(_.get(data, 'proc.uptime')).format('00:00:00');
|
|
data.message += ' ';
|
|
data.message += chalk.gray('load: [');
|
|
data.message += _.get(data, 'os.load', [])
|
|
.map((val: number) => {
|
|
return numeral(val).format('0.00');
|
|
})
|
|
.join(' ');
|
|
data.message += chalk.gray(']');
|
|
data.message += ' ';
|
|
data.message += chalk.gray('delay: ');
|
|
data.message += numeral(_.get(data, 'proc.delay')).format('0.000');
|
|
} else if (isErrorEvent(event)) {
|
|
data.level = 'error';
|
|
data.error = serializeError(event.error);
|
|
data.url = event.url;
|
|
const message = _.get(event, 'error.message');
|
|
data.message = message || 'Unknown error (no message)';
|
|
} else if (isUndeclaredErrorEvent(event)) {
|
|
data.type = 'error';
|
|
data.level = _.includes(event.tags, 'fatal') ? 'fatal' : 'error';
|
|
data.error = serializeError(event.error);
|
|
const message = _.get(event, 'error.message');
|
|
data.message = message || 'Unknown error object (no message)';
|
|
} else if (isLogEvent(event)) {
|
|
_.assign(data, getLogEventData(event.data));
|
|
} else {
|
|
data.message = _.isString(event.data) ? event.data : inspect(event.data);
|
|
}
|
|
return data;
|
|
}
|
|
}
|