Skip to content

Commit

Permalink
[Observability] Split up observability-utils package (elastic#199801)
Browse files Browse the repository at this point in the history
Split up observability-utils package in browser, common, server. Also
made a small change to `withSpan` to automatically log operation times
when the debug level for the logger is enabled.

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
dgieselaar and kibanamachine authored Nov 13, 2024
1 parent 3cbdcc3 commit 11a752d
Show file tree
Hide file tree
Showing 88 changed files with 1,224 additions and 98 deletions.
4 changes: 3 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,9 @@ x-pack/packages/observability/alerting_rule_utils @elastic/obs-ux-management-tea
x-pack/packages/observability/alerting_test_data @elastic/obs-ux-management-team
x-pack/packages/observability/get_padded_alert_time_range_util @elastic/obs-ux-management-team
x-pack/packages/observability/logs_overview @elastic/obs-ux-logs-team
x-pack/packages/observability/observability_utils @elastic/observability-ui
x-pack/packages/observability/observability_utils/observability_utils_browser @elastic/observability-ui
x-pack/packages/observability/observability_utils/observability_utils_common @elastic/observability-ui
x-pack/packages/observability/observability_utils/observability_utils_server @elastic/observability-ui
x-pack/packages/observability/synthetics_test_data @elastic/obs-ux-management-team
x-pack/packages/rollup @elastic/kibana-management
x-pack/packages/search/shared_ui @elastic/search-kibana
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,9 @@
"@kbn/observability-plugin": "link:x-pack/plugins/observability_solution/observability",
"@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_solution/observability_shared",
"@kbn/observability-synthetics-test-data": "link:x-pack/packages/observability/synthetics_test_data",
"@kbn/observability-utils": "link:x-pack/packages/observability/observability_utils",
"@kbn/observability-utils-browser": "link:x-pack/packages/observability/observability_utils/observability_utils_browser",
"@kbn/observability-utils-common": "link:x-pack/packages/observability/observability_utils/observability_utils_common",
"@kbn/observability-utils-server": "link:x-pack/packages/observability/observability_utils/observability_utils_server",
"@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider",
"@kbn/open-telemetry-instrumented-plugin": "link:test/common/plugins/otel_metrics",
"@kbn/openapi-common": "link:packages/kbn-openapi-common",
Expand Down
45 changes: 40 additions & 5 deletions packages/kbn-apm-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import agent from 'elastic-apm-node';
import agent, { Logger } from 'elastic-apm-node';
import asyncHooks from 'async_hooks';

export interface SpanOptions {
Expand All @@ -34,14 +34,48 @@ const runInNewContext = <T extends (...args: any[]) => any>(cb: T): ReturnType<T

export async function withSpan<T>(
optionsOrName: SpanOptions | string,
cb: (span?: Span) => Promise<T>
cb: (span?: Span) => Promise<T>,
logger?: Logger
): Promise<T> {
const options = parseSpanOptions(optionsOrName);

const { name, type, subtype, labels, intercept } = options;

let time: number | undefined;
if (logger?.isLevelEnabled('debug')) {
time = performance.now();
}

function logTook(failed: boolean) {
if (time) {
logger?.debug(
() =>
`Operation ${name}${failed ? ` (failed)` : ''} ${
Math.round(performance.now() - time!) / 1000
}s`
);
}
}

const withLogTook = [
<TR>(res: TR): TR | Promise<TR> => {
logTook(false);
return res;
},
(err: any): never => {
logTook(true);
throw err;
},
];

if (!agent.isStarted()) {
return cb();
const promise = cb();
// make sure tests that mock out the callback with a sync
// function don't fail.
if (typeof promise === 'object' && 'then' in promise) {
return promise.then(...withLogTook);
}
return promise;
}

let createdSpan: Span | undefined;
Expand All @@ -57,7 +91,7 @@ export async function withSpan<T>(
createdSpan = agent.startSpan(name) ?? undefined;

if (!createdSpan) {
return cb();
return cb().then(...withLogTook);
}
}

Expand All @@ -76,7 +110,7 @@ export async function withSpan<T>(
}

if (!span) {
return promise;
return promise.then(...withLogTook);
}

const targetedSpan = span;
Expand All @@ -98,6 +132,7 @@ export async function withSpan<T>(
}

return promise
.then(...withLogTook)
.then((res) => {
if (!targetedSpan.outcome || targetedSpan.outcome === 'unknown') {
targetedSpan.outcome = 'success';
Expand Down
8 changes: 6 additions & 2 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -1330,8 +1330,12 @@
"@kbn/observability-shared-plugin/*": ["x-pack/plugins/observability_solution/observability_shared/*"],
"@kbn/observability-synthetics-test-data": ["x-pack/packages/observability/synthetics_test_data"],
"@kbn/observability-synthetics-test-data/*": ["x-pack/packages/observability/synthetics_test_data/*"],
"@kbn/observability-utils": ["x-pack/packages/observability/observability_utils"],
"@kbn/observability-utils/*": ["x-pack/packages/observability/observability_utils/*"],
"@kbn/observability-utils-browser": ["x-pack/packages/observability/observability_utils/observability_utils_browser"],
"@kbn/observability-utils-browser/*": ["x-pack/packages/observability/observability_utils/observability_utils_browser/*"],
"@kbn/observability-utils-common": ["x-pack/packages/observability/observability_utils/observability_utils_common"],
"@kbn/observability-utils-common/*": ["x-pack/packages/observability/observability_utils/observability_utils_common/*"],
"@kbn/observability-utils-server": ["x-pack/packages/observability/observability_utils/observability_utils_server"],
"@kbn/observability-utils-server/*": ["x-pack/packages/observability/observability_utils/observability_utils_server/*"],
"@kbn/oidc-provider-plugin": ["x-pack/test/security_api_integration/plugins/oidc_provider"],
"@kbn/oidc-provider-plugin/*": ["x-pack/test/security_api_integration/plugins/oidc_provider/*"],
"@kbn/open-telemetry-instrumented-plugin": ["test/common/plugins/otel_metrics"],
Expand Down
2 changes: 2 additions & 0 deletions x-pack/packages/ai-infra/inference-common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,5 @@ export {
isInferenceInternalError,
isInferenceRequestError,
} from './src/errors';

export { truncateList } from './src/truncate_list';
5 changes: 0 additions & 5 deletions x-pack/packages/observability/observability_utils/README.md

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export function useAbortController() {

return {
signal: controller.signal,
abort: () => {
controller.abort();
},
refresh: () => {
setController(() => new AbortController());
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,32 @@ export type AbortableAsyncState<T> = (T extends Promise<infer TReturn>
? State<TReturn>
: State<T>) & { refresh: () => void };

export type AbortableAsyncStateOf<T extends AbortableAsyncState<any>> =
T extends AbortableAsyncState<infer TResponse> ? Awaited<TResponse> : never;

interface UseAbortableAsyncOptions<T> {
clearValueOnNext?: boolean;
unsetValueOnError?: boolean;
defaultValue?: () => T;
onError?: (error: Error) => void;
}

export type UseAbortableAsync<
TAdditionalParameters extends Record<string, any> = {},
TAdditionalOptions extends Record<string, any> = {}
> = <T>(
fn: ({}: { signal: AbortSignal } & TAdditionalParameters) => T | Promise<T>,
deps: any[],
options?: UseAbortableAsyncOptions<T> & TAdditionalOptions
) => AbortableAsyncState<T>;

export function useAbortableAsync<T>(
fn: ({}: { signal: AbortSignal }) => T | Promise<T>,
deps: any[],
options?: { clearValueOnNext?: boolean; defaultValue?: () => T }
options?: UseAbortableAsyncOptions<T>
): AbortableAsyncState<T> {
const clearValueOnNext = options?.clearValueOnNext;
const unsetValueOnError = options?.unsetValueOnError;

const controllerRef = useRef(new AbortController());

Expand All @@ -43,6 +63,15 @@ export function useAbortableAsync<T>(
setError(undefined);
}

function handleError(err: Error) {
setError(err);
if (unsetValueOnError) {
setValue(undefined);
}
setLoading(false);
options?.onError?.(err);
}

try {
const response = fn({ signal: controller.signal });
if (isPromise(response)) {
Expand All @@ -52,22 +81,15 @@ export function useAbortableAsync<T>(
setError(undefined);
setValue(nextValue);
})
.catch((err) => {
setValue(undefined);
if (!controller.signal.aborted) {
setError(err);
}
})
.catch(handleError)
.finally(() => setLoading(false));
} else {
setError(undefined);
setValue(response);
setLoading(false);
}
} catch (err) {
setValue(undefined);
setError(err);
setLoading(false);
handleError(err);
}

return () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { TimeRange } from '@kbn/data-plugin/common';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { useCallback, useEffect, useMemo, useState } from 'react';

export function useDateRange({ data }: { data: DataPublicPluginStart }): {
timeRange: TimeRange;
absoluteTimeRange: {
start: number;
end: number;
};
setTimeRange: React.Dispatch<React.SetStateAction<TimeRange>>;
} {
const timefilter = data.query.timefilter.timefilter;

const [timeRange, setTimeRange] = useState(() => timefilter.getTime());

const [absoluteTimeRange, setAbsoluteTimeRange] = useState(() => timefilter.getAbsoluteTime());

useEffect(() => {
const timeUpdateSubscription = timefilter.getTimeUpdate$().subscribe({
next: () => {
setTimeRange(() => timefilter.getTime());
setAbsoluteTimeRange(() => timefilter.getAbsoluteTime());
},
});

return () => {
timeUpdateSubscription.unsubscribe();
};
}, [timefilter]);

const setTimeRangeMemoized: React.Dispatch<React.SetStateAction<TimeRange>> = useCallback(
(nextOrCallback) => {
const val =
typeof nextOrCallback === 'function'
? nextOrCallback(timefilter.getTime())
: nextOrCallback;

timefilter.setTime(val);
},
[timefilter]
);

const asEpoch = useMemo(() => {
return {
start: new Date(absoluteTimeRange.from).getTime(),
end: new Date(absoluteTimeRange.to).getTime(),
};
}, [absoluteTimeRange]);

return {
timeRange,
absoluteTimeRange: asEpoch,
setTimeRange: setTimeRangeMemoized,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useState, useEffect, useMemo, useCallback } from 'react';

export function useLocalStorage<T>(key: string, defaultValue: T) {
// This is necessary to fix a race condition issue.
// It guarantees that the latest value will be always returned after the value is updated
const [storageUpdate, setStorageUpdate] = useState(0);

const item = useMemo(() => {
return getFromStorage(key, defaultValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key, storageUpdate, defaultValue]);

const saveToStorage = useCallback(
(value: T) => {
if (value === undefined) {
window.localStorage.removeItem(key);
} else {
window.localStorage.setItem(key, JSON.stringify(value));
setStorageUpdate(storageUpdate + 1);
}
},
[key, storageUpdate]
);

useEffect(() => {
function onUpdate(event: StorageEvent) {
if (event.key === key) {
setStorageUpdate(storageUpdate + 1);
}
}
window.addEventListener('storage', onUpdate);
return () => {
window.removeEventListener('storage', onUpdate);
};
}, [key, setStorageUpdate, storageUpdate]);

return useMemo(() => [item, saveToStorage] as const, [item, saveToStorage]);
}

function getFromStorage<T>(keyName: string, defaultValue: T) {
const storedItem = window.localStorage.getItem(keyName);

if (storedItem !== null) {
try {
return JSON.parse(storedItem) as T;
} catch (err) {
window.localStorage.removeItem(keyName);
// eslint-disable-next-line no-console
console.log(`Unable to decode: ${keyName}`);
}
}
return defaultValue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: [
'<rootDir>/x-pack/packages/observability/observability_utils/observability_utils_browser',
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "shared-browser",
"id": "@kbn/observability-utils-browser",
"owner": "@elastic/observability-ui"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/observability-utils-browser",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/data-plugin",
"@kbn/core-ui-settings-browser",
"@kbn/std",
]
}
Loading

0 comments on commit 11a752d

Please sign in to comment.