Skip to content

Commit

Permalink
Support setting log level filter via decoder options.
Browse files Browse the repository at this point in the history
  • Loading branch information
junhaoliao committed Sep 2, 2024
1 parent 50fc54d commit ccee8b5
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 29 deletions.
8 changes: 4 additions & 4 deletions new-log-viewer/src/services/LogFileManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
Decoder,
DecoderOptionsType,
DecoderOptions,
LOG_EVENT_FILE_END_IDX,
} from "../typings/decoders";
import {MAX_V8_STRING_LENGTH} from "../typings/js";
Expand Down Expand Up @@ -103,7 +103,7 @@ class LogFileManager {
static async create (
fileSrc: FileSrcType,
pageSize: number,
decoderOptions: DecoderOptionsType
decoderOptions: DecoderOptions
): Promise<LogFileManager> {
const {fileName, fileData} = await loadFile(fileSrc);
const decoder = await LogFileManager.#initDecoder(fileName, fileData, decoderOptions);
Expand All @@ -123,7 +123,7 @@ class LogFileManager {
static async #initDecoder (
fileName: string,
fileData: Uint8Array,
decoderOptions: DecoderOptionsType
decoderOptions: DecoderOptions
): Promise<Decoder> {
let decoder: Decoder;
if (fileName.endsWith(".jsonl")) {
Expand All @@ -148,7 +148,7 @@ class LogFileManager {
*
* @param options
*/
setDecoderOptions (options: DecoderOptionsType) {
setDecoderOptions (options: DecoderOptions) {
this.#decoder.setDecoderOptions(options);
}

Expand Down
54 changes: 41 additions & 13 deletions new-log-viewer/src/services/decoders/JsonlDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Nullable} from "../../typings/common";
import {
Decoder,
DecodeResultType,
JsonlDecoderOptionsType,
JsonlDecoderOptions,
JsonLogEvent,
LOG_EVENT_FILE_END_IDX,
LogEventCount,
Expand All @@ -30,12 +30,14 @@ class JsonlDecoder implements Decoder {

#dataArray: Nullable<Uint8Array>;

#logLevelKey: string = "level";
readonly #logLevelKey: string;

#timestampKey: string = "@timestamp";
readonly #timestampKey: string;

#logEvents: JsonLogEvent[] = [];

#filteredLogIndices: number[];

#invalidLogEventIdxToRawLine: Map<number, string> = new Map();

// @ts-expect-error #formatter is set in the constructor by `setDecoderOptions()`
Expand All @@ -46,7 +48,12 @@ class JsonlDecoder implements Decoder {
* @param decoderOptions
* @throws {Error} if the initial decoder options are erroneous.
*/
constructor (dataArray: Uint8Array, decoderOptions: JsonlDecoderOptionsType) {
constructor (dataArray: Uint8Array, decoderOptions: JsonlDecoderOptions) {
this.#filteredLogIndices = [];

this.#logLevelKey = decoderOptions.logLevelKey;
this.#timestampKey = decoderOptions.timestampKey;

const isOptionSet = this.setDecoderOptions(decoderOptions);
if (false === isOptionSet) {
throw new Error(
Expand All @@ -57,7 +64,7 @@ class JsonlDecoder implements Decoder {
}

getEstimatedNumEvents (): number {
return this.#logEvents.length;
return this.#filteredLogIndices.length;
}

buildIdx (beginIdx: number, endIdx: number): Nullable<LogEventCount> {
Expand All @@ -66,6 +73,7 @@ class JsonlDecoder implements Decoder {
}

this.#deserialize();
this.#filterLogs(null);
const numInvalidEvents = Array.from(this.#invalidLogEventIdxToRawLine.keys())
.filter((eventIdx) => (beginIdx <= eventIdx && eventIdx < endIdx))
.length;
Expand All @@ -76,25 +84,30 @@ class JsonlDecoder implements Decoder {
};
}

setDecoderOptions (options: JsonlDecoderOptionsType): boolean {
// FIXME: If options changed then parse log events again.
setDecoderOptions (options: JsonlDecoderOptions): boolean {
// Note `options.timestampKey` and `options.logLevelKey` are not set by this method.
this.#formatter = new LogbackFormatter(options);
this.#logLevelKey = options.logLevelKey;
this.#timestampKey = options.timestampKey;
this.#filterLogs(options.logLevelFilter);

return true;
}

decode (beginIdx: number, endIdx: number): Nullable<DecodeResultType[]> {
if (0 > beginIdx || this.#logEvents.length < endIdx) {
if (0 > beginIdx || this.#filteredLogIndices.length < endIdx) {
return null;
}

// eslint-disable-next-line no-warning-comments
// TODO We could probably optimize this to avoid checking `#invalidLogEventIdxToRawLine` on
// every iteration.
const results: DecodeResultType[] = [];
for (let logEventIdx = beginIdx; logEventIdx < endIdx; logEventIdx++) {
for (let filteredLogEventIdx = beginIdx;
filteredLogEventIdx < endIdx;
filteredLogEventIdx++) {
// Explicit cast since typescript thinks `#filteredLogIndices[filteredLogEventIdx]` can
// be undefined, but it shouldn't be since we performed a bounds check at the beginning
// of the method.
const logEventIdx = this.#filteredLogIndices[filteredLogEventIdx] as number;
let logLevel: LOG_LEVEL;
let message: string;
let timestamp: number;
Expand All @@ -104,8 +117,7 @@ class JsonlDecoder implements Decoder {
timestamp = INVALID_TIMESTAMP_VALUE;
} else {
// Explicit cast since typescript thinks `#logEvents[logEventIdx]` can be undefined,
// but it shouldn't be since we performed a bounds check at the beginning of the
// method.
// but it shouldn't be since the index comes from a class-internal filter.
const logEvent: JsonLogEvent = this.#logEvents[logEventIdx] as JsonLogEvent;
logLevel = logEvent.level;
message = this.#formatter.formatLogEvent(logEvent);
Expand Down Expand Up @@ -172,6 +184,22 @@ class JsonlDecoder implements Decoder {
this.#dataArray = null;
}

#filterLogs (logLevelFilter: Nullable<LOG_LEVEL[]>) {
this.#filteredLogIndices.length = 0;
if (null === logLevelFilter) {
this.#filteredLogIndices = Array.from(
{length: this.#logEvents.length},
(_, index) => index
);

return;
}
this.#logEvents.forEach((logEvent, index) => {
if (logEvent.level in logLevelFilter) {
this.#filteredLogIndices.push(index);
}
});
}

/**
* Parses the log level from the given log event.
Expand Down
4 changes: 2 additions & 2 deletions new-log-viewer/src/typings/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {JsonlDecoderOptionsType} from "./decoders";
import {JsonlDecoderOptions} from "./decoders";


enum THEME_NAME {
Expand All @@ -24,7 +24,7 @@ enum LOCAL_STORAGE_KEY {
/* eslint-enable @typescript-eslint/prefer-literal-enum-member */

type ConfigMap = {
[CONFIG_KEY.DECODER_OPTIONS]: JsonlDecoderOptionsType,
[CONFIG_KEY.DECODER_OPTIONS]: Partial<JsonlDecoderOptions>,
[CONFIG_KEY.THEME]: THEME_NAME,
[CONFIG_KEY.PAGE_SIZE]: number,
};
Expand Down
14 changes: 9 additions & 5 deletions new-log-viewer/src/typings/decoders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@ interface LogEventCount {
numInvalidEvents: number,
}

interface GenericDecoderOptions {
logLevelFilter: Nullable<LOG_LEVEL[]>,
}

/**
* Options for the JSONL decoder.
*
* @property formatString The format string to use to serialize records as plain text.
* @property logLevelKey The key of the kv-pair that contains the log level in every record.
* @property timestampKey The key of the kv-pair that contains the timestamp in every record.
*/
interface JsonlDecoderOptionsType {
interface JsonlDecoderOptions extends GenericDecoderOptions {
formatString: string,
logLevelKey: string,
timestampKey: string,
}

type DecoderOptionsType = JsonlDecoderOptionsType;
type DecoderOptions = JsonlDecoderOptions;

/**
* A log event parsed from a JSON log.
Expand Down Expand Up @@ -71,7 +75,7 @@ interface Decoder {
* @param options
* @return Whether the options were successfully set.
*/
setDecoderOptions(options: DecoderOptionsType): boolean;
setDecoderOptions(options: DecoderOptions): boolean;

/**
* Decodes the log events in the range `[beginIdx, endIdx)`.
Expand All @@ -94,8 +98,8 @@ export {LOG_EVENT_FILE_END_IDX};
export type {
Decoder,
DecodeResultType,
DecoderOptionsType,
JsonlDecoderOptionsType,
DecoderOptions,
JsonlDecoderOptions,
JsonLogEvent,
LogEventCount,
};
6 changes: 3 additions & 3 deletions new-log-viewer/src/typings/worker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {DecoderOptionsType} from "./decoders";
import {DecoderOptions} from "./decoders";
import {LOG_LEVEL} from "./logs";


Expand Down Expand Up @@ -53,11 +53,11 @@ type WorkerReqMap = {
fileSrc: FileSrcType,
pageSize: number,
cursor: CursorType,
decoderOptions: DecoderOptionsType
decoderOptions: DecoderOptions
},
[WORKER_REQ_CODE.LOAD_PAGE]: {
cursor: CursorType,
decoderOptions?: DecoderOptionsType
decoderOptions?: DecoderOptions
},
};

Expand Down
4 changes: 2 additions & 2 deletions new-log-viewer/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
LOCAL_STORAGE_KEY,
THEME_NAME,
} from "../typings/config";
import {DecoderOptionsType} from "../typings/decoders";
import {DecoderOptions} from "../typings/decoders";


const MAX_PAGE_SIZE = 1_000_000;
Expand Down Expand Up @@ -122,7 +122,7 @@ const getConfig = <T extends CONFIG_KEY>(key: T): ConfigMap[T] => {
timestampKey: window.localStorage.getItem(
LOCAL_STORAGE_KEY.DECODER_OPTIONS_TIMESTAMP_KEY
),
} as DecoderOptionsType;
} as DecoderOptions;
break;
case CONFIG_KEY.THEME: {
value = window.localStorage.getItem(LOCAL_STORAGE_KEY.THEME);
Expand Down

0 comments on commit ccee8b5

Please sign in to comment.