mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Feature Flags] ECS-compliant logger (#214231)
## Summary The OpenFeature clients receive a logger, but it logs errors like `log('something went wrong', error)`. Our core logger then removes the `error.message` as it prefers the message provided as the first argument. This PR wraps the logger to make sure that we handle this type of usage. cc @pmuellr as he found out about this bug ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
29a8ac5210
commit
98986a86a1
3 changed files with 104 additions and 1 deletions
|
@ -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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { Logger as OpenFeatureLogger } from '@openfeature/server-sdk';
|
||||
import { type MockedLogger, loggerMock } from '@kbn/logging-mocks';
|
||||
import { createOpenFeatureLogger } from './create_open_feature_logger';
|
||||
|
||||
const LOG_LEVELS = ['debug', 'info', 'warn', 'error'] as const;
|
||||
|
||||
describe('createOpenFeatureLogger', () => {
|
||||
let kbnLogger: MockedLogger;
|
||||
let openFeatureLogger: OpenFeatureLogger;
|
||||
|
||||
beforeEach(() => {
|
||||
kbnLogger = loggerMock.create();
|
||||
openFeatureLogger = createOpenFeatureLogger(kbnLogger);
|
||||
});
|
||||
|
||||
test.each(LOG_LEVELS)('should log.%s() a simple message', (logLevel) => {
|
||||
openFeatureLogger[logLevel]('message');
|
||||
expect(kbnLogger[logLevel]).toHaveBeenCalledWith('message', { extraArguments: [] });
|
||||
});
|
||||
|
||||
test.each(LOG_LEVELS)('should log.%s() a message with 1 argument (non-error)', (logLevel) => {
|
||||
openFeatureLogger[logLevel]('message', 'something else');
|
||||
expect(kbnLogger[logLevel]).toHaveBeenCalledWith('message', {
|
||||
extraArguments: ['something else'],
|
||||
});
|
||||
});
|
||||
|
||||
test.each(LOG_LEVELS)(
|
||||
'should log.%s() a message with 1 argument (error) in their expected ECS field',
|
||||
(logLevel) => {
|
||||
const error = new Error('Something went wrong');
|
||||
openFeatureLogger[logLevel]('An error occurred:', error);
|
||||
expect(kbnLogger[logLevel]).toHaveBeenCalledWith('An error occurred:', { error });
|
||||
}
|
||||
);
|
||||
|
||||
test.each(LOG_LEVELS)('should log.%s() a message with additional arguments', (logLevel) => {
|
||||
openFeatureLogger[logLevel]('message', 'something else', 'another thing', { foo: 'bar' });
|
||||
expect(kbnLogger[logLevel]).toHaveBeenCalledWith('message', {
|
||||
extraArguments: ['something else', 'another thing', { foo: 'bar' }],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { Logger as OpenFeatureLogger } from '@openfeature/server-sdk';
|
||||
import type { Logger, LogMeta } from '@kbn/logging';
|
||||
|
||||
interface LogMetaWithExtraArguments extends LogMeta {
|
||||
extraArguments: unknown[];
|
||||
}
|
||||
|
||||
function normalizeLogArguments(
|
||||
logger: Logger,
|
||||
logLevel: 'debug' | 'info' | 'warn' | 'error',
|
||||
message: string,
|
||||
...args: unknown[]
|
||||
) {
|
||||
// Special case when calling log('something went wrong', error)
|
||||
if (args.length === 1 && args[0] instanceof Error) {
|
||||
logger[logLevel](message, { error: args[0] });
|
||||
} else {
|
||||
logger[logLevel]<LogMetaWithExtraArguments>(message, { extraArguments: args });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The way OpenFeature logs messages is very similar to the console.log approach,
|
||||
* which is not compatible with our LogMeta approach. This can result in our log removing information like any 3rd+
|
||||
* arguments passed or the error.message when using log('message', error).
|
||||
*
|
||||
* This wrapper addresses this by making it ECS-compliant.
|
||||
* @param logger The Kibana logger
|
||||
*/
|
||||
export function createOpenFeatureLogger(logger: Logger): OpenFeatureLogger {
|
||||
return {
|
||||
debug: (message: string, ...args: unknown[]) =>
|
||||
normalizeLogArguments(logger, 'debug', message, ...args),
|
||||
info: (message: string, ...args: unknown[]) =>
|
||||
normalizeLogArguments(logger, 'info', message, ...args),
|
||||
warn: (message: string, ...args: unknown[]) =>
|
||||
normalizeLogArguments(logger, 'warn', message, ...args),
|
||||
error: (message: string, ...args: unknown[]) =>
|
||||
normalizeLogArguments(logger, 'error', message, ...args),
|
||||
};
|
||||
}
|
|
@ -25,6 +25,7 @@ import {
|
|||
import deepMerge from 'deepmerge';
|
||||
import { filter, switchMap, startWith, Subject } from 'rxjs';
|
||||
import { get } from 'lodash';
|
||||
import { createOpenFeatureLogger } from './create_open_feature_logger';
|
||||
import { setProviderWithRetries } from './set_provider_with_retries';
|
||||
import { type FeatureFlagsConfig, featureFlagsConfig } from './feature_flags_config';
|
||||
|
||||
|
@ -56,7 +57,7 @@ export class FeatureFlagsService {
|
|||
constructor(private readonly core: CoreContext) {
|
||||
this.logger = core.logger.get('feature-flags-service');
|
||||
this.featureFlagsClient = OpenFeature.getClient();
|
||||
OpenFeature.setLogger(this.logger.get('open-feature'));
|
||||
OpenFeature.setLogger(createOpenFeatureLogger(this.logger.get('open-feature')));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue