Skip to content

Commit

Permalink
junhao review
Browse files Browse the repository at this point in the history
  • Loading branch information
davemarco committed Nov 22, 2024
1 parent 267b1f9 commit 2385919
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 53 deletions.
11 changes: 6 additions & 5 deletions src/components/modals/SettingsModal/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>[:<formatter-name>[:<formatter-options>]]}\`. \`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
\`{<field-name>[:<formatter-name>[:<formatter-options>]]}\`, 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,
Expand Down
6 changes: 3 additions & 3 deletions src/services/decoders/ClpIrDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/decoders/JsonlDecoder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
LogEvent,
LogLevelFilter,
} from "../../../typings/logs";
import YscopeFormatter from "../../formatters/YScopeFormatter";
import YscopeFormatter from "../../formatters/YscopeFormatter";
import {
convertToDayjsTimestamp,
convertToLogLevelValue,
Expand Down
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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<string>) {
if (null !== options) {
throw Error(`RoundFormatter does not support options "${options}"`);
Expand Down
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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<string> = null;

constructor (options: Nullable<string>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
Formatter,
FormatterOptionsType,
REPLACEMENT_CHARACTER_REGEX,
YScopeFieldFormatter,
YScopeFieldPlaceholder,
YscopeFieldFormatter,
YscopeFieldPlaceholder,
} from "../../../typings/formatters";
import {LogEvent} from "../../../typings/logs";
import {
Expand All @@ -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)) {
Expand All @@ -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.
Expand All @@ -82,7 +78,7 @@ class YScopeFormatter implements Formatter {
const {fieldNameKeys, formatterName, formatterOptions} =
splitFieldPlaceholder(groupMatch);

let fieldFormatter: Nullable<YScopeFieldFormatter> = null;
let fieldFormatter: Nullable<YscopeFieldFormatter> = null;
if (null !== formatterName) {
const FieldFormatterConstructor = YSCOPE_FIELD_FORMATTER_MAP[formatterName];
if ("undefined" === typeof FieldFormatterConstructor) {
Expand All @@ -103,4 +99,4 @@ class YScopeFormatter implements Formatter {
}
}

export default YScopeFormatter;
export default YscopeFormatter;
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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,
});
Expand All @@ -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, "");
};

/**
Expand All @@ -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, "\\");
};

/**
Expand All @@ -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);
};


Expand All @@ -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) {
Expand Down Expand Up @@ -144,8 +151,9 @@ const splitFieldPlaceholder = (placeholderString: string): {
let [
fieldName,
formatterName,
formatterOptions
]: Nullable<string|undefined>[] = placeholderString.split(COLON_REGEX, 3);
formatterOptions,
]: Nullable<string|undefined>[
] = placeholderString.split(COLON_REGEX, 3);

fieldName = validateComponent(fieldName);
if (null === fieldName) {
Expand Down
20 changes: 10 additions & 10 deletions src/typings/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ interface Formatter {
formatLogEvent: (logEvent: LogEvent) => string
}

interface YScopeFieldFormatter {
interface YscopeFieldFormatter {

/**
* Formats the given field.
Expand All @@ -51,25 +51,25 @@ interface YScopeFieldFormatter {
/**
* Type for list of currently supported Yscope field formatters.
*/
type YScopeFieldFormatterMap = {
[key: string]: new (options: Nullable<string>) => YScopeFieldFormatter;
type YscopeFieldFormatterMap = {
[key: string]: new (options: Nullable<string>) => YscopeFieldFormatter;
};

/**
* Parsed field placeholder from a Yscope format string.
*/
type YScopeFieldPlaceholder = {
type YscopeFieldPlaceholder = {
fieldNameKeys: string[],
fieldFormatter: Nullable<YScopeFieldFormatter>,
fieldFormatter: Nullable<YscopeFieldFormatter>,

// 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.

Expand Down Expand Up @@ -106,9 +106,9 @@ const PERIOD_REGEX = Object.freeze(/(?<!\\)\./);
export type {
Formatter,
FormatterOptionsType,
YScopeFieldFormatter,
YScopeFieldFormatterMap,
YScopeFieldPlaceholder,
YscopeFieldFormatter,
YscopeFieldFormatterMap,
YscopeFieldPlaceholder,
};

export {
Expand Down

0 comments on commit 2385919

Please sign in to comment.