From 2385919e03bfd3bc0691f65f2aa42ff20e4139af Mon Sep 17 00:00:00 2001 From: Dave Marco Date: Fri, 22 Nov 2024 01:11:38 +0000 Subject: [PATCH] junhao review --- .../modals/SettingsModal/SettingsDialog.tsx | 11 ++++--- src/services/decoders/ClpIrDecoder.ts | 6 ++-- src/services/decoders/JsonlDecoder/index.ts | 2 +- .../FieldFormatters/RoundFormatter.ts | 4 +-- .../FieldFormatters/TimestampFormatter.ts | 4 +-- .../index.ts | 32 ++++++++----------- .../utils.ts | 32 ++++++++++++------- src/typings/formatters.ts | 20 ++++++------ 8 files changed, 58 insertions(+), 53 deletions(-) rename src/services/formatters/{YScopeFormatter => YscopeFormatter}/FieldFormatters/RoundFormatter.ts (86%) rename src/services/formatters/{YScopeFormatter => YscopeFormatter}/FieldFormatters/TimestampFormatter.ts (90%) rename src/services/formatters/{YScopeFormatter => YscopeFormatter}/index.ts (79%) rename src/services/formatters/{YScopeFormatter => YscopeFormatter}/utils.ts (80%) diff --git a/src/components/modals/SettingsModal/SettingsDialog.tsx b/src/components/modals/SettingsModal/SettingsDialog.tsx index 85c6f5b1..b119d7f0 100644 --- a/src/components/modals/SettingsModal/SettingsDialog.tsx +++ b/src/components/modals/SettingsModal/SettingsDialog.tsx @@ -33,11 +33,12 @@ import ThemeSwitchToggle from "./ThemeSwitchToggle"; const CONFIG_FORM_FIELDS = [ { - helperText: `[JSON] Log messages conversion pattern. Add field-placeholders to insert - fields from JSON log events. A field-placeholder uses the following syntax: - \`{[:[:]]}\`. \`field-name\` is required, - while \`formatter-name\` and \`formatter-options\` are optional. See the default pattern - for an example.`, + helperText: `[JSON] Log message conversion pattern: use field placeholders to insert + values from JSON log events. The syntax is + \`{[:[:]]}\`, where \`field-name\` is + required, while \`formatter-name\` and \`formatter-options\` are optional. For example, + the following placeholder would format a timestamp field with name \`@timestamp\`: + \`{@timestamp:timestamp:YYYY-MM-DD HH\\:mm\\:ss.SSS}\`.`, initialValue: getConfig(CONFIG_KEY.DECODER_OPTIONS).formatString, label: "Decoder: Format string", name: LOCAL_STORAGE_KEY.DECODER_OPTIONS_FORMAT_STRING, diff --git a/src/services/decoders/ClpIrDecoder.ts b/src/services/decoders/ClpIrDecoder.ts index 801e6bd3..81383259 100644 --- a/src/services/decoders/ClpIrDecoder.ts +++ b/src/services/decoders/ClpIrDecoder.ts @@ -12,7 +12,7 @@ import { import {Formatter} from "../../typings/formatters"; import {JsonObject} from "../../typings/js"; import {LogLevelFilter} from "../../typings/logs"; -import YScopeFormatter from "../formatters/YScopeFormatter"; +import YscopeFormatter from "../formatters/YscopeFormatter"; import { convertToDayjsTimestamp, isJsonObject, @@ -39,7 +39,7 @@ class ClpIrDecoder implements Decoder { this.#streamType = streamType; this.#streamReader = streamReader; this.#formatter = (streamType === CLP_IR_STREAM_TYPE.STRUCTURED) ? - new YScopeFormatter({formatString: decoderOptions.formatString}) : + new YscopeFormatter({formatString: decoderOptions.formatString}) : null; } @@ -87,7 +87,7 @@ class ClpIrDecoder implements Decoder { } setFormatterOptions (options: DecoderOptions): boolean { - this.#formatter = new YScopeFormatter({formatString: options.formatString}); + this.#formatter = new YscopeFormatter({formatString: options.formatString}); return true; } diff --git a/src/services/decoders/JsonlDecoder/index.ts b/src/services/decoders/JsonlDecoder/index.ts index 74b5d0e2..5405ca00 100644 --- a/src/services/decoders/JsonlDecoder/index.ts +++ b/src/services/decoders/JsonlDecoder/index.ts @@ -16,7 +16,7 @@ import { LogEvent, LogLevelFilter, } from "../../../typings/logs"; -import YscopeFormatter from "../../formatters/YScopeFormatter"; +import YscopeFormatter from "../../formatters/YscopeFormatter"; import { convertToDayjsTimestamp, convertToLogLevelValue, diff --git a/src/services/formatters/YScopeFormatter/FieldFormatters/RoundFormatter.ts b/src/services/formatters/YscopeFormatter/FieldFormatters/RoundFormatter.ts similarity index 86% rename from src/services/formatters/YScopeFormatter/FieldFormatters/RoundFormatter.ts rename to src/services/formatters/YscopeFormatter/FieldFormatters/RoundFormatter.ts index 277fce1c..0bfd9282 100644 --- a/src/services/formatters/YScopeFormatter/FieldFormatters/RoundFormatter.ts +++ b/src/services/formatters/YscopeFormatter/FieldFormatters/RoundFormatter.ts @@ -1,5 +1,5 @@ import {Nullable} from "../../../../typings/common"; -import {YScopeFieldFormatter} from "../../../../typings/formatters"; +import {YscopeFieldFormatter} from "../../../../typings/formatters"; import {JsonValue} from "../../../../typings/js"; import {jsonValueToString} from "../utils"; @@ -9,7 +9,7 @@ import {jsonValueToString} from "../utils"; * For non-numerical values, the field's value is converted to a string then returned as-is. * Options: none. */ -class RoundFormatter implements YScopeFieldFormatter { +class RoundFormatter implements YscopeFieldFormatter { constructor (options: Nullable) { if (null !== options) { throw Error(`RoundFormatter does not support options "${options}"`); diff --git a/src/services/formatters/YScopeFormatter/FieldFormatters/TimestampFormatter.ts b/src/services/formatters/YscopeFormatter/FieldFormatters/TimestampFormatter.ts similarity index 90% rename from src/services/formatters/YScopeFormatter/FieldFormatters/TimestampFormatter.ts rename to src/services/formatters/YscopeFormatter/FieldFormatters/TimestampFormatter.ts index 653c40df..e0847121 100644 --- a/src/services/formatters/YScopeFormatter/FieldFormatters/TimestampFormatter.ts +++ b/src/services/formatters/YscopeFormatter/FieldFormatters/TimestampFormatter.ts @@ -1,7 +1,7 @@ import {Dayjs} from "dayjs"; import {Nullable} from "../../../../typings/common"; -import {YScopeFieldFormatter} from "../../../../typings/formatters"; +import {YscopeFieldFormatter} from "../../../../typings/formatters"; import {JsonValue} from "../../../../typings/js"; import {convertToDayjsTimestamp} from "../../../decoders/JsonlDecoder/utils"; @@ -10,7 +10,7 @@ import {convertToDayjsTimestamp} from "../../../decoders/JsonlDecoder/utils"; * A formatter for timestamp values, using a specified date-time pattern. * Options: If no pattern is provided, defaults to ISO 8601 format. */ -class TimestampFormatter implements YScopeFieldFormatter { +class TimestampFormatter implements YscopeFieldFormatter { #dateFormat: Nullable = null; constructor (options: Nullable) { diff --git a/src/services/formatters/YScopeFormatter/index.ts b/src/services/formatters/YscopeFormatter/index.ts similarity index 79% rename from src/services/formatters/YScopeFormatter/index.ts rename to src/services/formatters/YscopeFormatter/index.ts index 5169c72a..7abb2692 100644 --- a/src/services/formatters/YScopeFormatter/index.ts +++ b/src/services/formatters/YscopeFormatter/index.ts @@ -4,8 +4,8 @@ import { Formatter, FormatterOptionsType, REPLACEMENT_CHARACTER_REGEX, - YScopeFieldFormatter, - YScopeFieldPlaceholder, + YscopeFieldFormatter, + YscopeFieldPlaceholder, } from "../../../typings/formatters"; import {LogEvent} from "../../../typings/logs"; import { @@ -19,12 +19,12 @@ import { /** * A formatter that uses a YScope format string to format log events into a string. See - * `YScopeFormatterOptionsType` for details about the format string. + * `YscopeFormatterOptionsType` for details about the format string. */ -class YScopeFormatter implements Formatter { +class YscopeFormatter implements Formatter { readonly #processedFormatString: string; - #fieldPlaceholders: YScopeFieldPlaceholder[] = []; + #fieldPlaceholders: YscopeFieldPlaceholder[] = []; constructor (options: FormatterOptionsType) { if (REPLACEMENT_CHARACTER_REGEX.test(options.formatString)) { @@ -37,32 +37,28 @@ class YScopeFormatter implements Formatter { } formatLogEvent (logEvent: LogEvent): string { - let formattedLog = ""; - - // Keeps track of the last position in format string. + const formattedLogFragments: string[] = []; let lastIndex = 0; for (const fieldPlaceholder of this.#fieldPlaceholders) { const formatStringFragment = this.#processedFormatString.slice(lastIndex, fieldPlaceholder.range.start); - const cleanedFragment = removeEscapeCharacters(formatStringFragment); - - formattedLog += cleanedFragment; - formattedLog += getFormattedField(logEvent, fieldPlaceholder); + formattedLogFragments.push(removeEscapeCharacters(formatStringFragment)); + formattedLogFragments.push(getFormattedField(logEvent, fieldPlaceholder)); lastIndex = fieldPlaceholder.range.end; } const remainder = this.#processedFormatString.slice(lastIndex); - formattedLog += removeEscapeCharacters(remainder); + formattedLogFragments.push(removeEscapeCharacters(remainder)); - return `${formattedLog}\n`; + return `${formattedLogFragments.join("")}\n`; } /** * Parses field placeholders in format string. For each field placeholder, creates a - * corresponding `YScopeFieldFormatter` using the placeholder's field name, formatter type, - * and formatter options. Each `YScopeFieldFormatter` is then stored on the + * corresponding `YscopeFieldFormatter` using the placeholder's field name, formatter type, + * and formatter options. Each `YscopeFieldFormatter` is then stored on the * class-level array `#fieldPlaceholders`. * * @throws Error if `FIELD_PLACEHOLDER_REGEX` does not contain a capture group. @@ -82,7 +78,7 @@ class YScopeFormatter implements Formatter { const {fieldNameKeys, formatterName, formatterOptions} = splitFieldPlaceholder(groupMatch); - let fieldFormatter: Nullable = null; + let fieldFormatter: Nullable = null; if (null !== formatterName) { const FieldFormatterConstructor = YSCOPE_FIELD_FORMATTER_MAP[formatterName]; if ("undefined" === typeof FieldFormatterConstructor) { @@ -103,4 +99,4 @@ class YScopeFormatter implements Formatter { } } -export default YScopeFormatter; +export default YscopeFormatter; diff --git a/src/services/formatters/YScopeFormatter/utils.ts b/src/services/formatters/YscopeFormatter/utils.ts similarity index 80% rename from src/services/formatters/YScopeFormatter/utils.ts rename to src/services/formatters/YscopeFormatter/utils.ts index db5b2c5d..bfc0f3ca 100644 --- a/src/services/formatters/YScopeFormatter/utils.ts +++ b/src/services/formatters/YscopeFormatter/utils.ts @@ -6,8 +6,8 @@ import { REPLACEMENT_CHARACTER, REPLACEMENT_CHARACTER_REGEX, SINGLE_BACKSLASH_REGEX, - YScopeFieldFormatterMap, - YScopeFieldPlaceholder, + YscopeFieldFormatterMap, + YscopeFieldPlaceholder, } from "../../../typings/formatters"; import {JsonValue} from "../../../typings/js"; import {LogEvent} from "../../../typings/logs"; @@ -16,10 +16,15 @@ import RoundFormatter from "./FieldFormatters/RoundFormatter"; import TimestampFormatter from "./FieldFormatters/TimestampFormatter"; +// Initialize commonly used regular expressions to facilitate reuse. +const singleBackslashPattern = new RegExp(SINGLE_BACKSLASH_REGEX, "g"); +const doubleBacklashPattern = new RegExp(DOUBLE_BACKSLASH_REGEX, "g"); +const replacementCharacterPattern = new RegExp(REPLACEMENT_CHARACTER_REGEX, "g"); + /** * List of currently supported field formatters. */ -const YSCOPE_FIELD_FORMATTER_MAP: YScopeFieldFormatterMap = Object.freeze({ +const YSCOPE_FIELD_FORMATTER_MAP: YscopeFieldFormatterMap = Object.freeze({ timestamp: TimestampFormatter, round: RoundFormatter, }); @@ -33,8 +38,7 @@ const YSCOPE_FIELD_FORMATTER_MAP: YScopeFieldFormatterMap = Object.freeze({ * @return Modified string. */ const removeBackslash = (str: string): string => { - const pattern = new RegExp(SINGLE_BACKSLASH_REGEX, "g"); - return str.replaceAll(pattern, ""); + return str.replaceAll(singleBackslashPattern, ""); }; /** @@ -46,8 +50,7 @@ const removeBackslash = (str: string): string => { * @return Modified string. */ const replaceReplacementCharacter = (str: string): string => { - const pattern = new RegExp(REPLACEMENT_CHARACTER_REGEX, "g"); - return str.replaceAll(pattern, "\\"); + return str.replaceAll(replacementCharacterPattern, "\\"); }; /** @@ -64,13 +67,17 @@ const removeEscapeCharacters = (str: string): string => { /** * Replaces all escaped backslashes in format string with replacement character. + * Replacement character is a rare character that is unlikely to be in user format string. + * Writing regex to distinguish between a single escape character ("\") and an escaped backslash + * ("\\") is challenging especially when they are in series. It is simpler to just replace + * escaped backslashes with a rare character and add them back after parsing field placeholder + * with regex is finished. * * @param formatString * @return Modified format string. */ const replaceDoubleBacklash = (formatString: string): string => { - const pattern = new RegExp(DOUBLE_BACKSLASH_REGEX, "g"); - return formatString.replaceAll(pattern, REPLACEMENT_CHARACTER); + return formatString.replaceAll(doubleBacklashPattern, REPLACEMENT_CHARACTER); }; @@ -97,7 +104,7 @@ const jsonValueToString = (input: JsonValue | undefined): string => { */ const getFormattedField = ( logEvent: LogEvent, - fieldPlaceholder: YScopeFieldPlaceholder + fieldPlaceholder: YscopeFieldPlaceholder ): string => { const nestedValue = getNestedJsonValue(logEvent.fields, fieldPlaceholder.fieldNameKeys); if ("undefined" === typeof nestedValue) { @@ -144,8 +151,9 @@ const splitFieldPlaceholder = (placeholderString: string): { let [ fieldName, formatterName, - formatterOptions - ]: Nullable[] = placeholderString.split(COLON_REGEX, 3); + formatterOptions, + ]: Nullable[ +] = placeholderString.split(COLON_REGEX, 3); fieldName = validateComponent(fieldName); if (null === fieldName) { diff --git a/src/typings/formatters.ts b/src/typings/formatters.ts index 57867a81..3cb860cb 100644 --- a/src/typings/formatters.ts +++ b/src/typings/formatters.ts @@ -37,7 +37,7 @@ interface Formatter { formatLogEvent: (logEvent: LogEvent) => string } -interface YScopeFieldFormatter { +interface YscopeFieldFormatter { /** * Formats the given field. @@ -51,25 +51,25 @@ interface YScopeFieldFormatter { /** * Type for list of currently supported Yscope field formatters. */ -type YScopeFieldFormatterMap = { - [key: string]: new (options: Nullable) => YScopeFieldFormatter; +type YscopeFieldFormatterMap = { + [key: string]: new (options: Nullable) => YscopeFieldFormatter; }; /** * Parsed field placeholder from a Yscope format string. */ -type YScopeFieldPlaceholder = { +type YscopeFieldPlaceholder = { fieldNameKeys: string[], - fieldFormatter: Nullable, + fieldFormatter: Nullable, // Location of field placeholder in format string including braces. range: {start: number, end: number} } /** - * Unicode REPLACEMENT CHARACTER `U+FFFD` to substitute escaped backslash (`\\`) in format string. + * Unicode replacement character `U+FFFD` to substitute escaped backslash (`\\`) in format string. */ -const REPLACEMENT_CHARACTER = Object.freeze("�"); +const REPLACEMENT_CHARACTER = "�"; // Patterns to assist parsing YScope format string. @@ -106,9 +106,9 @@ const PERIOD_REGEX = Object.freeze(/(?